rubocop-rails 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +20 -0
- data/README.md +92 -0
- data/bin/setup +7 -0
- data/config/default.yml +510 -0
- data/lib/rubocop-rails.rb +12 -0
- data/lib/rubocop/cop/mixin/target_rails_version.rb +16 -0
- data/lib/rubocop/cop/rails/action_filter.rb +111 -0
- data/lib/rubocop/cop/rails/active_record_aliases.rb +48 -0
- data/lib/rubocop/cop/rails/active_record_override.rb +82 -0
- data/lib/rubocop/cop/rails/active_support_aliases.rb +69 -0
- data/lib/rubocop/cop/rails/application_controller.rb +36 -0
- data/lib/rubocop/cop/rails/application_job.rb +40 -0
- data/lib/rubocop/cop/rails/application_mailer.rb +40 -0
- data/lib/rubocop/cop/rails/application_record.rb +40 -0
- data/lib/rubocop/cop/rails/assert_not.rb +44 -0
- data/lib/rubocop/cop/rails/belongs_to.rb +102 -0
- data/lib/rubocop/cop/rails/blank.rb +164 -0
- data/lib/rubocop/cop/rails/bulk_change_table.rb +293 -0
- data/lib/rubocop/cop/rails/create_table_with_timestamps.rb +91 -0
- data/lib/rubocop/cop/rails/date.rb +161 -0
- data/lib/rubocop/cop/rails/delegate.rb +132 -0
- data/lib/rubocop/cop/rails/delegate_allow_blank.rb +37 -0
- data/lib/rubocop/cop/rails/dynamic_find_by.rb +91 -0
- data/lib/rubocop/cop/rails/enum_hash.rb +75 -0
- data/lib/rubocop/cop/rails/enum_uniqueness.rb +65 -0
- data/lib/rubocop/cop/rails/environment_comparison.rb +68 -0
- data/lib/rubocop/cop/rails/exit.rb +67 -0
- data/lib/rubocop/cop/rails/file_path.rb +108 -0
- data/lib/rubocop/cop/rails/find_by.rb +55 -0
- data/lib/rubocop/cop/rails/find_each.rb +51 -0
- data/lib/rubocop/cop/rails/has_and_belongs_to_many.rb +25 -0
- data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +106 -0
- data/lib/rubocop/cop/rails/helper_instance_variable.rb +39 -0
- data/lib/rubocop/cop/rails/http_positional_arguments.rb +117 -0
- data/lib/rubocop/cop/rails/http_status.rb +160 -0
- data/lib/rubocop/cop/rails/ignored_skip_action_filter_option.rb +94 -0
- data/lib/rubocop/cop/rails/inverse_of.rb +246 -0
- data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +175 -0
- data/lib/rubocop/cop/rails/link_to_blank.rb +98 -0
- data/lib/rubocop/cop/rails/not_null_column.rb +67 -0
- data/lib/rubocop/cop/rails/output.rb +49 -0
- data/lib/rubocop/cop/rails/output_safety.rb +99 -0
- data/lib/rubocop/cop/rails/pluralization_grammar.rb +107 -0
- data/lib/rubocop/cop/rails/presence.rb +148 -0
- data/lib/rubocop/cop/rails/present.rb +153 -0
- data/lib/rubocop/cop/rails/rake_environment.rb +91 -0
- data/lib/rubocop/cop/rails/read_write_attribute.rb +74 -0
- data/lib/rubocop/cop/rails/redundant_allow_nil.rb +111 -0
- data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +136 -0
- data/lib/rubocop/cop/rails/reflection_class_name.rb +37 -0
- data/lib/rubocop/cop/rails/refute_methods.rb +76 -0
- data/lib/rubocop/cop/rails/relative_date_constant.rb +102 -0
- data/lib/rubocop/cop/rails/request_referer.rb +56 -0
- data/lib/rubocop/cop/rails/reversible_migration.rb +284 -0
- data/lib/rubocop/cop/rails/safe_navigation.rb +85 -0
- data/lib/rubocop/cop/rails/safe_navigation_with_blank.rb +48 -0
- data/lib/rubocop/cop/rails/save_bang.rb +331 -0
- data/lib/rubocop/cop/rails/scope_args.rb +29 -0
- data/lib/rubocop/cop/rails/skips_model_validations.rb +87 -0
- data/lib/rubocop/cop/rails/time_zone.rb +249 -0
- data/lib/rubocop/cop/rails/uniq_before_pluck.rb +105 -0
- data/lib/rubocop/cop/rails/unknown_env.rb +84 -0
- data/lib/rubocop/cop/rails/validation.rb +147 -0
- data/lib/rubocop/cop/rails_cops.rb +61 -0
- data/lib/rubocop/rails.rb +12 -0
- data/lib/rubocop/rails/inject.rb +18 -0
- data/lib/rubocop/rails/version.rb +10 -0
- metadata +148 -0
@@ -0,0 +1,153 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop checks for code that can be written with simpler conditionals
|
7
|
+
# using `Object#present?` defined by Active Support.
|
8
|
+
#
|
9
|
+
# Interaction with `Style/UnlessElse`:
|
10
|
+
# The configuration of `NotBlank` will not produce an offense in the
|
11
|
+
# context of `unless else` if `Style/UnlessElse` is inabled. This is
|
12
|
+
# to prevent interference between the auto-correction of the two cops.
|
13
|
+
#
|
14
|
+
# @example NotNilAndNotEmpty: true (default)
|
15
|
+
# # Converts usages of `!nil? && !empty?` to `present?`
|
16
|
+
#
|
17
|
+
# # bad
|
18
|
+
# !foo.nil? && !foo.empty?
|
19
|
+
#
|
20
|
+
# # bad
|
21
|
+
# foo != nil && !foo.empty?
|
22
|
+
#
|
23
|
+
# # good
|
24
|
+
# foo.present?
|
25
|
+
#
|
26
|
+
# @example NotBlank: true (default)
|
27
|
+
# # Converts usages of `!blank?` to `present?`
|
28
|
+
#
|
29
|
+
# # bad
|
30
|
+
# !foo.blank?
|
31
|
+
#
|
32
|
+
# # bad
|
33
|
+
# not foo.blank?
|
34
|
+
#
|
35
|
+
# # good
|
36
|
+
# foo.present?
|
37
|
+
#
|
38
|
+
# @example UnlessBlank: true (default)
|
39
|
+
# # Converts usages of `unless blank?` to `if present?`
|
40
|
+
#
|
41
|
+
# # bad
|
42
|
+
# something unless foo.blank?
|
43
|
+
#
|
44
|
+
# # good
|
45
|
+
# something if foo.present?
|
46
|
+
class Present < Cop
|
47
|
+
MSG_NOT_BLANK = 'Use `%<prefer>s` instead of `%<current>s`.'
|
48
|
+
MSG_EXISTS_AND_NOT_EMPTY = 'Use `%<prefer>s` instead of ' \
|
49
|
+
'`%<current>s`.'
|
50
|
+
MSG_UNLESS_BLANK = 'Use `if %<prefer>s` instead of ' \
|
51
|
+
'`%<current>s`.'
|
52
|
+
|
53
|
+
def_node_matcher :exists_and_not_empty?, <<~PATTERN
|
54
|
+
(and
|
55
|
+
{
|
56
|
+
(send (send $_ :nil?) :!)
|
57
|
+
(send (send $_ :!) :!)
|
58
|
+
(send $_ :!= nil)
|
59
|
+
$_
|
60
|
+
}
|
61
|
+
{
|
62
|
+
(send (send $_ :empty?) :!)
|
63
|
+
}
|
64
|
+
)
|
65
|
+
PATTERN
|
66
|
+
|
67
|
+
def_node_matcher :not_blank?, '(send (send $_ :blank?) :!)'
|
68
|
+
|
69
|
+
def_node_matcher :unless_blank?, <<~PATTERN
|
70
|
+
(:if $(send $_ :blank?) {nil? (...)} ...)
|
71
|
+
PATTERN
|
72
|
+
|
73
|
+
def on_send(node)
|
74
|
+
return unless cop_config['NotBlank']
|
75
|
+
|
76
|
+
not_blank?(node) do |receiver|
|
77
|
+
add_offense(node,
|
78
|
+
message: format(MSG_NOT_BLANK,
|
79
|
+
prefer: replacement(receiver),
|
80
|
+
current: node.source))
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def on_and(node)
|
85
|
+
return unless cop_config['NotNilAndNotEmpty']
|
86
|
+
|
87
|
+
exists_and_not_empty?(node) do |var1, var2|
|
88
|
+
return unless var1 == var2
|
89
|
+
|
90
|
+
add_offense(node,
|
91
|
+
message: format(MSG_EXISTS_AND_NOT_EMPTY,
|
92
|
+
prefer: replacement(var1),
|
93
|
+
current: node.source))
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def on_or(node)
|
98
|
+
return unless cop_config['NilOrEmpty']
|
99
|
+
|
100
|
+
exists_and_not_empty?(node) do |var1, var2|
|
101
|
+
return unless var1 == var2
|
102
|
+
|
103
|
+
add_offense(node, message: MSG_EXISTS_AND_NOT_EMPTY)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def on_if(node)
|
108
|
+
return unless cop_config['UnlessBlank']
|
109
|
+
return unless node.unless?
|
110
|
+
return if node.else? && config.for_cop('Style/UnlessElse')['Enabled']
|
111
|
+
|
112
|
+
unless_blank?(node) do |method_call, receiver|
|
113
|
+
range = unless_condition(node, method_call)
|
114
|
+
msg = format(MSG_UNLESS_BLANK, prefer: replacement(receiver),
|
115
|
+
current: range.source)
|
116
|
+
add_offense(node, location: range, message: msg)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def autocorrect(node)
|
121
|
+
lambda do |corrector|
|
122
|
+
method_call, variable1 = unless_blank?(node)
|
123
|
+
|
124
|
+
if method_call
|
125
|
+
corrector.replace(node.loc.keyword, 'if')
|
126
|
+
range = method_call.loc.expression
|
127
|
+
else
|
128
|
+
variable1, _variable2 =
|
129
|
+
exists_and_not_empty?(node) || not_blank?(node)
|
130
|
+
range = node.loc.expression
|
131
|
+
end
|
132
|
+
|
133
|
+
corrector.replace(range, replacement(variable1))
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
private
|
138
|
+
|
139
|
+
def unless_condition(node, method_call)
|
140
|
+
if node.modifier_form?
|
141
|
+
node.loc.keyword.join(node.loc.expression.end)
|
142
|
+
else
|
143
|
+
node.loc.expression.begin.join(method_call.loc.expression)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def replacement(node)
|
148
|
+
node.respond_to?(:source) ? "#{node.source}.present?" : 'present?'
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop checks for Rake tasks without the `:environment` task
|
7
|
+
# dependency. The `:environment` task loads application code for other
|
8
|
+
# Rake tasks. Without it, tasks cannot make use of application code like
|
9
|
+
# models.
|
10
|
+
#
|
11
|
+
# You can ignore the offense if the task satisfies at least one of the
|
12
|
+
# following conditions:
|
13
|
+
#
|
14
|
+
# * The task does not need application code.
|
15
|
+
# * The task invokes the `:environment` task.
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
# # bad
|
19
|
+
# task :foo do
|
20
|
+
# do_something
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# # good
|
24
|
+
# task foo: :environment do
|
25
|
+
# do_something
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
class RakeEnvironment < Cop
|
29
|
+
MSG = 'Include `:environment` task as a dependency for all Rake tasks.'
|
30
|
+
|
31
|
+
def_node_matcher :task_definition?, <<~PATTERN
|
32
|
+
(send nil? :task ...)
|
33
|
+
PATTERN
|
34
|
+
|
35
|
+
def on_send(node)
|
36
|
+
return unless task_definition?(node)
|
37
|
+
return if task_name(node) == :default
|
38
|
+
return if with_dependencies?(node)
|
39
|
+
|
40
|
+
add_offense(node)
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def task_name(node)
|
46
|
+
first_arg = node.arguments[0]
|
47
|
+
case first_arg&.type
|
48
|
+
when :sym, :str
|
49
|
+
first_arg.value.to_sym
|
50
|
+
when :hash
|
51
|
+
return nil if first_arg.children.size != 1
|
52
|
+
|
53
|
+
pair = first_arg.children.first
|
54
|
+
key = pair.children.first
|
55
|
+
case key.type
|
56
|
+
when :sym, :str
|
57
|
+
key.value.to_sym
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def with_dependencies?(node)
|
63
|
+
first_arg = node.arguments[0]
|
64
|
+
return false unless first_arg
|
65
|
+
|
66
|
+
if first_arg.hash_type?
|
67
|
+
with_hash_style_dependencies?(first_arg)
|
68
|
+
else
|
69
|
+
task_args = node.arguments[1]
|
70
|
+
return false unless task_args
|
71
|
+
return false unless task_args.hash_type?
|
72
|
+
|
73
|
+
with_hash_style_dependencies?(task_args)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def with_hash_style_dependencies?(hash_node)
|
78
|
+
deps = hash_node.pairs.first&.value
|
79
|
+
return false unless deps
|
80
|
+
|
81
|
+
case deps.type
|
82
|
+
when :array
|
83
|
+
!deps.values.empty?
|
84
|
+
else
|
85
|
+
true
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop checks for the use of the `read_attribute` or `write_attribute`
|
7
|
+
# methods and recommends square brackets instead.
|
8
|
+
#
|
9
|
+
# If an attribute is missing from the instance (for example, when
|
10
|
+
# initialized by a partial `select`) then `read_attribute`
|
11
|
+
# will return nil, but square brackets will raise
|
12
|
+
# an `ActiveModel::MissingAttributeError`.
|
13
|
+
#
|
14
|
+
# Explicitly raising an error in this situation is preferable, and that
|
15
|
+
# is why rubocop recommends using square brackets.
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
#
|
19
|
+
# # bad
|
20
|
+
# x = read_attribute(:attr)
|
21
|
+
# write_attribute(:attr, val)
|
22
|
+
#
|
23
|
+
# # good
|
24
|
+
# x = self[:attr]
|
25
|
+
# self[:attr] = val
|
26
|
+
class ReadWriteAttribute < Cop
|
27
|
+
MSG = 'Prefer `%<prefer>s` over `%<current>s`.'
|
28
|
+
|
29
|
+
def_node_matcher :read_write_attribute?, <<~PATTERN
|
30
|
+
{
|
31
|
+
(send nil? :read_attribute _)
|
32
|
+
(send nil? :write_attribute _ _)
|
33
|
+
}
|
34
|
+
PATTERN
|
35
|
+
|
36
|
+
def on_send(node)
|
37
|
+
return unless read_write_attribute?(node)
|
38
|
+
|
39
|
+
add_offense(node, location: :selector)
|
40
|
+
end
|
41
|
+
|
42
|
+
def autocorrect(node)
|
43
|
+
case node.method_name
|
44
|
+
when :read_attribute
|
45
|
+
replacement = read_attribute_replacement(node)
|
46
|
+
when :write_attribute
|
47
|
+
replacement = write_attribute_replacement(node)
|
48
|
+
end
|
49
|
+
|
50
|
+
->(corrector) { corrector.replace(node.source_range, replacement) }
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def message(node)
|
56
|
+
if node.method?(:read_attribute)
|
57
|
+
format(MSG, prefer: 'self[:attr]', current: 'read_attribute(:attr)')
|
58
|
+
else
|
59
|
+
format(MSG, prefer: 'self[:attr] = val',
|
60
|
+
current: 'write_attribute(:attr, val)')
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def read_attribute_replacement(node)
|
65
|
+
"self[#{node.first_argument.source}]"
|
66
|
+
end
|
67
|
+
|
68
|
+
def write_attribute_replacement(node)
|
69
|
+
"self[#{node.first_argument.source}] = #{node.last_argument.source}"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# Checks Rails model validations for a redundant `allow_nil` when
|
7
|
+
# `allow_blank` is present.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # bad
|
11
|
+
# validates :x, length: { is: 5 }, allow_nil: true, allow_blank: true
|
12
|
+
#
|
13
|
+
# # bad
|
14
|
+
# validates :x, length: { is: 5 }, allow_nil: false, allow_blank: true
|
15
|
+
#
|
16
|
+
# # bad
|
17
|
+
# validates :x, length: { is: 5 }, allow_nil: false, allow_blank: false
|
18
|
+
#
|
19
|
+
# # good
|
20
|
+
# validates :x, length: { is: 5 }, allow_blank: true
|
21
|
+
#
|
22
|
+
# # good
|
23
|
+
# validates :x, length: { is: 5 }, allow_blank: false
|
24
|
+
#
|
25
|
+
# # good
|
26
|
+
# # Here, `nil` is valid but `''` is not
|
27
|
+
# validates :x, length: { is: 5 }, allow_nil: true, allow_blank: false
|
28
|
+
#
|
29
|
+
class RedundantAllowNil < Cop
|
30
|
+
include RangeHelp
|
31
|
+
|
32
|
+
MSG_SAME =
|
33
|
+
'`allow_nil` is redundant when `allow_blank` has the same value.'
|
34
|
+
|
35
|
+
MSG_ALLOW_NIL_FALSE =
|
36
|
+
'`allow_nil: false` is redundant when `allow_blank` is true.'
|
37
|
+
|
38
|
+
def on_send(node)
|
39
|
+
return unless node.method?(:validates)
|
40
|
+
|
41
|
+
allow_nil, allow_blank = find_allow_nil_and_allow_blank(node)
|
42
|
+
return unless allow_nil && allow_blank
|
43
|
+
|
44
|
+
allow_nil_val = allow_nil.children.last
|
45
|
+
allow_blank_val = allow_blank.children.last
|
46
|
+
|
47
|
+
offense(allow_nil_val, allow_blank_val, allow_nil)
|
48
|
+
end
|
49
|
+
|
50
|
+
def autocorrect(node)
|
51
|
+
prv_sib = previous_sibling(node)
|
52
|
+
nxt_sib = next_sibling(node)
|
53
|
+
|
54
|
+
lambda do |corrector|
|
55
|
+
if nxt_sib
|
56
|
+
corrector.remove(range_between(node_beg(node), node_beg(nxt_sib)))
|
57
|
+
elsif prv_sib
|
58
|
+
corrector.remove(range_between(node_end(prv_sib), node_end(node)))
|
59
|
+
else
|
60
|
+
corrector.remove(node.loc.expression)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def offense(allow_nil_val, allow_blank_val, allow_nil)
|
68
|
+
if allow_nil_val.type == allow_blank_val.type
|
69
|
+
add_offense(allow_nil, message: MSG_SAME)
|
70
|
+
elsif allow_nil_val.false_type? && allow_blank_val.true_type?
|
71
|
+
add_offense(allow_nil, message: MSG_ALLOW_NIL_FALSE)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def find_allow_nil_and_allow_blank(node)
|
76
|
+
allow_nil = nil
|
77
|
+
allow_blank = nil
|
78
|
+
|
79
|
+
node.each_descendant do |descendant|
|
80
|
+
next unless descendant.pair_type?
|
81
|
+
|
82
|
+
key = descendant.children.first.source
|
83
|
+
|
84
|
+
allow_nil = descendant if key == 'allow_nil'
|
85
|
+
allow_blank = descendant if key == 'allow_blank'
|
86
|
+
|
87
|
+
break if allow_nil && allow_blank
|
88
|
+
end
|
89
|
+
|
90
|
+
[allow_nil, allow_blank]
|
91
|
+
end
|
92
|
+
|
93
|
+
def previous_sibling(node)
|
94
|
+
node.parent.children[node.sibling_index - 1]
|
95
|
+
end
|
96
|
+
|
97
|
+
def next_sibling(node)
|
98
|
+
node.parent.children[node.sibling_index + 1]
|
99
|
+
end
|
100
|
+
|
101
|
+
def node_beg(node)
|
102
|
+
node.loc.expression.begin_pos
|
103
|
+
end
|
104
|
+
|
105
|
+
def node_end(node)
|
106
|
+
node.loc.expression.end_pos
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop checks for redundant receiver in `with_options`.
|
7
|
+
# Receiver is implicit from Rails 4.2 or higher.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # bad
|
11
|
+
# class Account < ApplicationRecord
|
12
|
+
# with_options dependent: :destroy do |assoc|
|
13
|
+
# assoc.has_many :customers
|
14
|
+
# assoc.has_many :products
|
15
|
+
# assoc.has_many :invoices
|
16
|
+
# assoc.has_many :expenses
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# # good
|
21
|
+
# class Account < ApplicationRecord
|
22
|
+
# with_options dependent: :destroy do
|
23
|
+
# has_many :customers
|
24
|
+
# has_many :products
|
25
|
+
# has_many :invoices
|
26
|
+
# has_many :expenses
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# @example
|
31
|
+
# # bad
|
32
|
+
# with_options options: false do |merger|
|
33
|
+
# merger.invoke(merger.something)
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# # good
|
37
|
+
# with_options options: false do
|
38
|
+
# invoke(something)
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# # good
|
42
|
+
# client = Client.new
|
43
|
+
# with_options options: false do |merger|
|
44
|
+
# client.invoke(merger.something, something)
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# # ok
|
48
|
+
# # When `with_options` includes a block, all scoping scenarios
|
49
|
+
# # cannot be evaluated. Thus, it is ok to include the explicit
|
50
|
+
# # receiver.
|
51
|
+
# with_options options: false do |merger|
|
52
|
+
# merger.invoke
|
53
|
+
# with_another_method do |another_receiver|
|
54
|
+
# merger.invoke(another_receiver)
|
55
|
+
# end
|
56
|
+
# end
|
57
|
+
class RedundantReceiverInWithOptions < Cop
|
58
|
+
extend TargetRailsVersion
|
59
|
+
include RangeHelp
|
60
|
+
|
61
|
+
minimum_target_rails_version 4.2
|
62
|
+
|
63
|
+
MSG = 'Redundant receiver in `with_options`.'
|
64
|
+
|
65
|
+
def_node_matcher :with_options?, <<~PATTERN
|
66
|
+
(block
|
67
|
+
(send nil? :with_options
|
68
|
+
(...))
|
69
|
+
(args
|
70
|
+
$_arg)
|
71
|
+
$_body)
|
72
|
+
PATTERN
|
73
|
+
|
74
|
+
def_node_search :all_block_nodes_in, <<~PATTERN
|
75
|
+
(block ...)
|
76
|
+
PATTERN
|
77
|
+
|
78
|
+
def_node_search :all_send_nodes_in, <<~PATTERN
|
79
|
+
(send ...)
|
80
|
+
PATTERN
|
81
|
+
|
82
|
+
def on_block(node)
|
83
|
+
with_options?(node) do |arg, body|
|
84
|
+
return if body.nil?
|
85
|
+
return unless all_block_nodes_in(body).count.zero?
|
86
|
+
|
87
|
+
send_nodes = all_send_nodes_in(body)
|
88
|
+
|
89
|
+
if send_nodes.all? { |n| same_value?(arg, n.receiver) }
|
90
|
+
send_nodes.each do |send_node|
|
91
|
+
receiver = send_node.receiver
|
92
|
+
add_offense(send_node, location: receiver.source_range)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def autocorrect(node)
|
99
|
+
lambda do |corrector|
|
100
|
+
corrector.remove(node.receiver.source_range)
|
101
|
+
corrector.remove(node.loc.dot)
|
102
|
+
corrector.remove(block_argument_range(node))
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def block_argument_range(node)
|
109
|
+
block_node = node.each_ancestor(:block).first
|
110
|
+
block_argument = block_node.children[1].source_range
|
111
|
+
|
112
|
+
range_between(
|
113
|
+
search_begin_pos_of_space_before_block_argument(
|
114
|
+
block_argument.begin_pos
|
115
|
+
),
|
116
|
+
block_argument.end_pos
|
117
|
+
)
|
118
|
+
end
|
119
|
+
|
120
|
+
def search_begin_pos_of_space_before_block_argument(begin_pos)
|
121
|
+
position = begin_pos - 1
|
122
|
+
|
123
|
+
if processed_source.raw_source[position] == ' '
|
124
|
+
search_begin_pos_of_space_before_block_argument(position)
|
125
|
+
else
|
126
|
+
begin_pos
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def same_value?(arg_node, recv_node)
|
131
|
+
recv_node && recv_node.children[0] == arg_node.children[0]
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|