rubocop 1.75.5 → 1.76.2

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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +20 -14
  3. data/config/default.yml +55 -7
  4. data/config/obsoletion.yml +6 -3
  5. data/lib/rubocop/cop/autocorrect_logic.rb +18 -10
  6. data/lib/rubocop/cop/bundler/ordered_gems.rb +1 -1
  7. data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +50 -6
  8. data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +1 -1
  9. data/lib/rubocop/cop/internal_affairs/node_pattern_groups.rb +1 -0
  10. data/lib/rubocop/cop/layout/class_structure.rb +35 -0
  11. data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +7 -3
  12. data/lib/rubocop/cop/layout/first_argument_indentation.rb +1 -1
  13. data/lib/rubocop/cop/layout/space_before_brackets.rb +6 -32
  14. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +1 -0
  15. data/lib/rubocop/cop/lint/ambiguous_range.rb +5 -0
  16. data/lib/rubocop/cop/lint/deprecated_class_methods.rb +1 -1
  17. data/lib/rubocop/cop/lint/duplicate_methods.rb +84 -2
  18. data/lib/rubocop/cop/lint/empty_interpolation.rb +3 -1
  19. data/lib/rubocop/cop/lint/float_comparison.rb +27 -0
  20. data/lib/rubocop/cop/lint/identity_comparison.rb +19 -15
  21. data/lib/rubocop/cop/lint/literal_as_condition.rb +16 -24
  22. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +4 -4
  23. data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +5 -0
  24. data/lib/rubocop/cop/lint/useless_access_modifier.rb +21 -4
  25. data/lib/rubocop/cop/lint/useless_assignment.rb +2 -0
  26. data/lib/rubocop/cop/lint/useless_default_value_argument.rb +90 -0
  27. data/lib/rubocop/cop/lint/useless_or.rb +98 -0
  28. data/lib/rubocop/cop/metrics/abc_size.rb +1 -1
  29. data/lib/rubocop/cop/mixin/frozen_string_literal.rb +1 -1
  30. data/lib/rubocop/cop/mixin/ordered_gem_node.rb +1 -1
  31. data/lib/rubocop/cop/naming/predicate_method.rb +245 -0
  32. data/lib/rubocop/cop/naming/{predicate_name.rb → predicate_prefix.rb} +2 -2
  33. data/lib/rubocop/cop/style/access_modifier_declarations.rb +32 -10
  34. data/lib/rubocop/cop/style/command_literal.rb +1 -1
  35. data/lib/rubocop/cop/style/comparable_between.rb +3 -0
  36. data/lib/rubocop/cop/style/conditional_assignment.rb +3 -1
  37. data/lib/rubocop/cop/style/data_inheritance.rb +7 -0
  38. data/lib/rubocop/cop/style/def_with_parentheses.rb +18 -5
  39. data/lib/rubocop/cop/style/empty_string_inside_interpolation.rb +100 -0
  40. data/lib/rubocop/cop/style/if_unless_modifier.rb +22 -4
  41. data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +4 -7
  42. data/lib/rubocop/cop/style/it_block_parameter.rb +33 -14
  43. data/lib/rubocop/cop/style/map_to_hash.rb +11 -0
  44. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +1 -1
  45. data/lib/rubocop/cop/style/min_max_comparison.rb +13 -5
  46. data/lib/rubocop/cop/style/multiline_if_modifier.rb +2 -0
  47. data/lib/rubocop/cop/style/percent_q_literals.rb +1 -1
  48. data/lib/rubocop/cop/style/redundant_array_flatten.rb +50 -0
  49. data/lib/rubocop/cop/style/redundant_format.rb +6 -1
  50. data/lib/rubocop/cop/style/redundant_parentheses.rb +23 -5
  51. data/lib/rubocop/cop/style/redundant_self.rb +5 -5
  52. data/lib/rubocop/cop/style/regexp_literal.rb +1 -1
  53. data/lib/rubocop/cop/style/safe_navigation.rb +24 -11
  54. data/lib/rubocop/cop/style/sole_nested_conditional.rb +4 -2
  55. data/lib/rubocop/cop/style/string_concatenation.rb +1 -2
  56. data/lib/rubocop/cop/style/struct_inheritance.rb +8 -1
  57. data/lib/rubocop/cop/style/trailing_comma_in_block_args.rb +1 -1
  58. data/lib/rubocop/cop/team.rb +1 -1
  59. data/lib/rubocop/cop/variable_force/assignment.rb +7 -3
  60. data/lib/rubocop/formatter/disabled_config_formatter.rb +1 -0
  61. data/lib/rubocop/formatter/html_formatter.rb +1 -1
  62. data/lib/rubocop/rspec/expect_offense.rb +9 -3
  63. data/lib/rubocop/version.rb +1 -1
  64. data/lib/rubocop.rb +6 -1
  65. metadata +13 -11
@@ -0,0 +1,245 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Naming
6
+ # Checks that predicate methods end with `?` and non-predicate methods do not.
7
+ #
8
+ # The names of predicate methods (methods that return a boolean value) should end
9
+ # in a question mark. Methods that don't return a boolean, shouldn't
10
+ # end in a question mark.
11
+ #
12
+ # The cop assesses a predicate method as one that returns boolean values. Likewise,
13
+ # a method that only returns literal values is assessed as non-predicate. The cop does
14
+ # not make an assessment if the return type is unknown (method calls, variables, etc.).
15
+ #
16
+ # NOTE: Operator methods (`def ==`, etc.) are ignored.
17
+ #
18
+ # By default, the cop runs in `conservative` mode, which allows a method to be named
19
+ # with a question mark as long as at least one return value is boolean. In `aggressive`
20
+ # mode, methods with a question mark will register an offense if any known non-boolean
21
+ # return values are detected.
22
+ #
23
+ # The cop also has `AllowedMethods` configuration in order to prevent the cop from
24
+ # registering an offense from a method name that does not confirm to the naming
25
+ # guidelines. By default, `call` is allowed. The cop also has `AllowedPatterns`
26
+ # configuration to allow method names by regular expression.
27
+ #
28
+ # The cop can furthermore be configured to allow all bang methods (method names
29
+ # ending with `!`), with `AllowBangMethods: true` (default false).
30
+ #
31
+ # @example Mode: conservative (default)
32
+ # # bad
33
+ # def foo
34
+ # bar == baz
35
+ # end
36
+ #
37
+ # # good
38
+ # def foo?
39
+ # bar == baz
40
+ # end
41
+ #
42
+ # # bad
43
+ # def foo?
44
+ # 5
45
+ # end
46
+ #
47
+ # # good
48
+ # def foo
49
+ # 5
50
+ # end
51
+ #
52
+ # # good - operator method
53
+ # def ==(other)
54
+ # hash == other.hash
55
+ # end
56
+ #
57
+ # # good - at least one return value is boolean
58
+ # def foo?
59
+ # return unless bar?
60
+ # true
61
+ # end
62
+ #
63
+ # # ok - return type is not known
64
+ # def foo?
65
+ # bar
66
+ # end
67
+ #
68
+ # # ok - return type is not known
69
+ # def foo
70
+ # bar?
71
+ # end
72
+ #
73
+ # @example Mode: aggressive
74
+ # # bad - the method returns nil in some cases
75
+ # def foo?
76
+ # return unless bar?
77
+ # true
78
+ # end
79
+ #
80
+ # @example AllowBangMethods: false (default)
81
+ # # bad
82
+ # def save!
83
+ # true
84
+ # end
85
+ #
86
+ # @example AllowBangMethods: true
87
+ # # good
88
+ # def save!
89
+ # true
90
+ # end
91
+ #
92
+ class PredicateMethod < Base
93
+ include AllowedMethods
94
+ include AllowedPattern
95
+
96
+ MSG_PREDICATE = 'Predicate method names should end with `?`.'
97
+ MSG_NON_PREDICATE = 'Non-predicate method names should not end with `?`.'
98
+
99
+ def on_def(node)
100
+ return if allowed?(node)
101
+
102
+ return_values = return_values(node.body)
103
+ return if acceptable?(return_values)
104
+
105
+ if node.predicate_method? && potential_non_predicate?(return_values)
106
+ add_offense(node.loc.name, message: MSG_NON_PREDICATE)
107
+ elsif !node.predicate_method? && all_return_values_boolean?(return_values)
108
+ add_offense(node.loc.name, message: MSG_PREDICATE)
109
+ end
110
+ end
111
+ alias on_defs on_def
112
+
113
+ private
114
+
115
+ def allowed?(node)
116
+ allowed_method?(node.method_name) ||
117
+ matches_allowed_pattern?(node.method_name) ||
118
+ allowed_bang_method?(node) ||
119
+ node.operator_method? ||
120
+ node.body.nil?
121
+ end
122
+
123
+ def acceptable?(return_values)
124
+ # In `conservative` mode, if the method returns `super`, `zsuper`, or a
125
+ # non-comparison method call, the method name is acceptable.
126
+ return false unless conservative?
127
+
128
+ return_values.any? do |value|
129
+ value.type?(:super, :zsuper) || non_comparison_call?(value)
130
+ end
131
+ end
132
+
133
+ def non_comparison_call?(value)
134
+ value.call_type? && !value.comparison_method?
135
+ end
136
+
137
+ def return_values(node)
138
+ # Collect all the (implicit and explicit) return values of a node
139
+ return_values = Set.new(node.begin_type? ? [] : [extract_return_value(node)])
140
+
141
+ node.each_descendant(:return) do |return_node|
142
+ return_values << extract_return_value(return_node)
143
+ end
144
+
145
+ last_value = last_value(node)
146
+ return_values << last_value if last_value
147
+
148
+ process_return_values(return_values)
149
+ end
150
+
151
+ def all_return_values_boolean?(return_values)
152
+ values = return_values.reject { |value| value.type?(:super, :zsuper) }
153
+ return false if values.empty?
154
+
155
+ values.all? { |value| boolean_return?(value) }
156
+ end
157
+
158
+ def boolean_return?(value)
159
+ value.boolean_type? || (value.call_type? && value.comparison_method?)
160
+ end
161
+
162
+ def potential_non_predicate?(return_values)
163
+ # Assumes a method to be non-predicate if all return values are non-boolean literals.
164
+ #
165
+ # In `Mode: conservative`, if any of the return values is a boolean,
166
+ # the method name is acceptable.
167
+ # In `Mode: aggressive`, all return values must be booleans for a predicate
168
+ # method, or else an offense will be registered.
169
+ return false if conservative? && return_values.any? { |value| boolean_return?(value) }
170
+
171
+ return_values.any? do |value|
172
+ value.literal? && !value.boolean_type?
173
+ end
174
+ end
175
+
176
+ def extract_return_value(node)
177
+ return node unless node.return_type?
178
+
179
+ # `return` without a value is a `nil` return.
180
+ return s(:nil) if node.arguments.empty?
181
+
182
+ # When there's a multiple return, it cannot be a predicate
183
+ # so just return an `array` sexp for simplicity.
184
+ return s(:array) unless node.arguments.one?
185
+
186
+ node.first_argument
187
+ end
188
+
189
+ def last_value(node)
190
+ value = node.begin_type? ? node.children.last : node
191
+ value&.return_type? ? extract_return_value(value) : value
192
+ end
193
+
194
+ def process_return_values(return_values)
195
+ return_values.flat_map do |value|
196
+ if value.conditional?
197
+ process_return_values(extract_conditional_branches(value))
198
+ elsif and_or?(value)
199
+ process_return_values(extract_and_or_clauses(value))
200
+ else
201
+ value
202
+ end
203
+ end
204
+ end
205
+
206
+ def and_or?(node)
207
+ node.type?(:and, :or)
208
+ end
209
+
210
+ def extract_and_or_clauses(node)
211
+ # Recursively traverse an `and` or `or` node to collect all clauses within
212
+ return node unless and_or?(node)
213
+
214
+ [extract_and_or_clauses(node.lhs), extract_and_or_clauses(node.rhs)].flatten
215
+ end
216
+
217
+ def extract_conditional_branches(node)
218
+ return node unless node.conditional?
219
+
220
+ if node.type?(:while, :until)
221
+ # If there is no body, act as implicit `nil`.
222
+ node.body ? [last_value(node.body)] : [s(:nil)]
223
+ else
224
+ # Branches with no value act as an implicit `nil`.
225
+ node.branches.filter_map { |branch| branch ? last_value(branch) : s(:nil) }
226
+ end
227
+ end
228
+
229
+ def conservative?
230
+ cop_config.fetch('Mode', :conservative).to_sym == :conservative
231
+ end
232
+
233
+ def allowed_bang_method?(node)
234
+ return false unless allow_bang_methods?
235
+
236
+ node.bang_method?
237
+ end
238
+
239
+ def allow_bang_methods?
240
+ cop_config.fetch('AllowBangMethods', false)
241
+ end
242
+ end
243
+ end
244
+ end
245
+ end
@@ -100,7 +100,7 @@ module RuboCop
100
100
  # # good
101
101
  # def_node_matcher(:even?) { |value| }
102
102
  #
103
- class PredicateName < Base
103
+ class PredicatePrefix < Base
104
104
  include AllowedMethods
105
105
 
106
106
  # @!method dynamic_method_define(node)
@@ -143,7 +143,7 @@ module RuboCop
143
143
  next if predicate_prefixes.include?(forbidden_prefix)
144
144
 
145
145
  raise ValidationError, <<~MSG.chomp
146
- The `Naming/PredicateName` cop is misconfigured. Prefix #{forbidden_prefix} must be included in NamePrefix because it is included in ForbiddenPrefixes.
146
+ The `Naming/PredicatePrefix` cop is misconfigured. Prefix #{forbidden_prefix} must be included in NamePrefix because it is included in ForbiddenPrefixes.
147
147
  MSG
148
148
  end
149
149
  end
@@ -195,15 +195,27 @@ module RuboCop
195
195
  def autocorrect(corrector, node)
196
196
  case style
197
197
  when :group
198
- def_nodes = find_corresponding_def_nodes(node)
199
- return unless def_nodes.any?
200
-
201
- replace_defs(corrector, node, def_nodes)
198
+ autocorrect_group_style(corrector, node)
202
199
  when :inline
200
+ autocorrect_inline_style(corrector, node)
201
+ end
202
+ end
203
+
204
+ def autocorrect_group_style(corrector, node)
205
+ def_nodes = find_corresponding_def_nodes(node)
206
+ return unless def_nodes.any?
207
+
208
+ replace_defs(corrector, node, def_nodes)
209
+ end
210
+
211
+ def autocorrect_inline_style(corrector, node)
212
+ if node.parent&.begin_type?
213
+ remove_modifier_node_within_begin(corrector, node, node.parent)
214
+ else
203
215
  remove_nodes(corrector, node)
204
- select_grouped_def_nodes(node).each do |grouped_def_node|
205
- insert_inline_modifier(corrector, grouped_def_node, node.method_name)
206
- end
216
+ end
217
+ select_grouped_def_nodes(node).each do |grouped_def_node|
218
+ insert_inline_modifier(corrector, grouped_def_node, node.method_name)
207
219
  end
208
220
  end
209
221
 
@@ -224,9 +236,13 @@ module RuboCop
224
236
  end
225
237
 
226
238
  def offense?(node)
227
- (group_style? && access_modifier_is_inlined?(node) &&
228
- !node.parent&.if_type? && !right_siblings_same_inline_method?(node)) ||
229
- (inline_style? && access_modifier_is_not_inlined?(node))
239
+ if group_style?
240
+ return false if node.parent ? node.parent.if_type? : access_modifier_with_symbol?(node)
241
+
242
+ access_modifier_is_inlined?(node) && !right_siblings_same_inline_method?(node)
243
+ else
244
+ access_modifier_is_not_inlined?(node) && select_grouped_def_nodes(node).any?
245
+ end
230
246
  end
231
247
 
232
248
  def correctable_group_offense?(node)
@@ -331,6 +347,12 @@ module RuboCop
331
347
  end
332
348
  end
333
349
 
350
+ def remove_modifier_node_within_begin(corrector, modifier_node, begin_node)
351
+ def_node = begin_node.children[1]
352
+ range = modifier_node.source_range.begin.join(def_node.source_range.begin)
353
+ corrector.remove(range)
354
+ end
355
+
334
356
  def def_source(node, def_nodes)
335
357
  [
336
358
  *processed_source.ast_with_comments[node].map(&:text),
@@ -173,7 +173,7 @@ module RuboCop
173
173
  end
174
174
 
175
175
  def preferred_delimiters_config
176
- config.for_cop('Style/PercentLiteralDelimiters') ['PreferredDelimiters']
176
+ config.for_cop('Style/PercentLiteralDelimiters')['PreferredDelimiters']
177
177
  end
178
178
  end
179
179
  end
@@ -9,6 +9,9 @@ module RuboCop
9
9
  # although the difference generally isn't observable. If you require maximum
10
10
  # performance, consider using logical comparison.
11
11
  #
12
+ # @safety
13
+ # This cop is unsafe because the receiver may not respond to `between?`.
14
+ #
12
15
  # @example
13
16
  #
14
17
  # # bad
@@ -451,7 +451,9 @@ module RuboCop
451
451
  corrector.remove_preceding(condition.loc.else, condition.loc.else.column - column)
452
452
  end
453
453
 
454
- return unless condition.loc.end && !same_line?(condition.loc.end, condition)
454
+ return unless condition.loc.end && !same_line?(
455
+ condition.branches.last.parent.else_branch, condition.loc.end
456
+ )
455
457
 
456
458
  corrector.remove_preceding(condition.loc.end, condition.loc.end.column - column)
457
459
  end
@@ -4,6 +4,7 @@ module RuboCop
4
4
  module Cop
5
5
  module Style
6
6
  # Checks for inheritance from `Data.define` to avoid creating the anonymous parent class.
7
+ # Inheriting from `Data.define` adds a superfluous level in inheritance tree.
7
8
  #
8
9
  # @safety
9
10
  # Autocorrection is unsafe because it will change the inheritance
@@ -17,12 +18,18 @@ module RuboCop
17
18
  # end
18
19
  # end
19
20
  #
21
+ # Person.ancestors
22
+ # # => [Person, #<Class:0x000000010b4e14a0>, Data, (...)]
23
+ #
20
24
  # # good
21
25
  # Person = Data.define(:first_name, :last_name) do
22
26
  # def age
23
27
  # 42
24
28
  # end
25
29
  # end
30
+ #
31
+ # Person.ancestors
32
+ # # => [Person, Data, (...)]
26
33
  class DataInheritance < Base
27
34
  include RangeHelp
28
35
  extend AutoCorrector
@@ -25,8 +25,9 @@ module RuboCop
25
25
  # # good
26
26
  # def foo = do_something
27
27
  #
28
- # # good (without parentheses it's a syntax error)
28
+ # # good - without parentheses it's a syntax error
29
29
  # def foo() do_something end
30
+ # def foo()=do_something
30
31
  #
31
32
  # # bad
32
33
  # def Baz.foo()
@@ -38,19 +39,31 @@ module RuboCop
38
39
  # do_something
39
40
  # end
40
41
  class DefWithParentheses < Base
42
+ include RangeHelp
41
43
  extend AutoCorrector
42
44
 
43
45
  MSG = "Omit the parentheses in defs when the method doesn't accept any arguments."
44
46
 
45
47
  def on_def(node)
46
- return if node.single_line? && !node.endless?
47
- return unless !node.arguments? && (node_arguments = node.arguments.source_range)
48
+ return unless !node.arguments? && (arguments_range = node.arguments.source_range)
49
+ return if parentheses_required?(node, arguments_range)
48
50
 
49
- add_offense(node_arguments) do |corrector|
50
- corrector.remove(node_arguments)
51
+ add_offense(arguments_range) do |corrector|
52
+ corrector.remove(arguments_range)
51
53
  end
52
54
  end
53
55
  alias on_defs on_def
56
+
57
+ private
58
+
59
+ def parentheses_required?(node, arguments_range)
60
+ return true if node.single_line? && !node.endless?
61
+
62
+ end_pos = arguments_range.end.end_pos
63
+ token_after_argument = range_between(end_pos, end_pos + 1).source
64
+
65
+ token_after_argument == '='
66
+ end
54
67
  end
55
68
  end
56
69
  end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Checks for empty strings being assigned inside string interpolation.
7
+ #
8
+ # Empty strings are a meaningless outcome inside of string interpolation, so we remove them.
9
+ # Alternatively, when configured to do so, we prioritise using empty strings.
10
+ #
11
+ # While this cop would also apply to variables that are only going to be used as strings,
12
+ # RuboCop can't detect that, so we only check inside of string interpolation.
13
+ #
14
+ # @example EnforcedStyle: trailing_conditional (default)
15
+ # # bad
16
+ # "#{condition ? 'foo' : ''}"
17
+ #
18
+ # # good
19
+ # "#{'foo' if condition}"
20
+ #
21
+ # # bad
22
+ # "#{condition ? '' : 'foo'}"
23
+ #
24
+ # # good
25
+ # "#{'foo' unless condition}"
26
+ #
27
+ # @example EnforcedStyle: ternary
28
+ # # bad
29
+ # "#{'foo' if condition}"
30
+ #
31
+ # # good
32
+ # "#{condition ? 'foo' : ''}"
33
+ #
34
+ # # bad
35
+ # "#{'foo' unless condition}"
36
+ #
37
+ # # good
38
+ # "#{condition ? '' : 'foo'}"
39
+ #
40
+ class EmptyStringInsideInterpolation < Base
41
+ include ConfigurableEnforcedStyle
42
+ include Interpolation
43
+ extend AutoCorrector
44
+
45
+ MSG_TRAILING_CONDITIONAL = 'Do not use trailing conditionals in string interpolation.'
46
+ MSG_TERNARY = 'Do not return empty strings in string interpolation.'
47
+
48
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
49
+ def on_interpolation(node)
50
+ node.each_child_node(:if) do |child_node|
51
+ if style == :trailing_conditional
52
+ if empty_if_outcome?(child_node)
53
+ ternary_style_autocorrect(child_node, child_node.else_branch.source, 'unless')
54
+ end
55
+
56
+ if empty_else_outcome?(child_node)
57
+ ternary_style_autocorrect(child_node, child_node.if_branch.source, 'if')
58
+ end
59
+ elsif style == :ternary
60
+ next unless child_node.modifier_form?
61
+
62
+ ternary_component = if child_node.unless?
63
+ "'' : #{child_node.if_branch.source}"
64
+ else
65
+ "#{child_node.if_branch.source} : ''"
66
+ end
67
+
68
+ add_offense(node, message: MSG_TRAILING_CONDITIONAL) do |corrector|
69
+ corrector.replace(node, "\#{#{child_node.condition.source} ? #{ternary_component}}")
70
+ end
71
+ end
72
+ end
73
+ end
74
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
75
+
76
+ private
77
+
78
+ def empty_if_outcome?(node)
79
+ empty_branch_outcome?(node.if_branch)
80
+ end
81
+
82
+ def empty_else_outcome?(node)
83
+ empty_branch_outcome?(node.else_branch)
84
+ end
85
+
86
+ def empty_branch_outcome?(branch)
87
+ return false unless branch
88
+
89
+ branch.nil_type? || (branch.str_type? && branch.value.empty?)
90
+ end
91
+
92
+ def ternary_style_autocorrect(node, outcome, condition)
93
+ add_offense(node, message: MSG_TERNARY) do |corrector|
94
+ corrector.replace(node, "#{outcome} #{condition} #{node.condition.source}")
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -23,6 +23,17 @@ module RuboCop
23
23
  # end
24
24
  # ----
25
25
  #
26
+ # The code `def method_name = body if condition` is considered a bad case by
27
+ # `Style/AmbiguousEndlessMethodDefinition` cop. So, to respect the user's intention to use
28
+ # an endless method definition in the `if` body, the following code is allowed:
29
+ #
30
+ # [source,ruby]
31
+ # ----
32
+ # if condition
33
+ # def method_name = body
34
+ # end
35
+ # ----
36
+ #
26
37
  # NOTE: It is allowed when `defined?` argument has an undefined value,
27
38
  # because using the modifier form causes the following incompatibility:
28
39
  #
@@ -77,10 +88,14 @@ module RuboCop
77
88
  [Style::SoleNestedConditional]
78
89
  end
79
90
 
91
+ # rubocop:disable Metrics/AbcSize
80
92
  def on_if(node)
93
+ return if endless_method?(node.body)
94
+
81
95
  condition = node.condition
82
96
  return if defined_nodes(condition).any? { |n| defined_argument_is_undefined?(node, n) } ||
83
97
  pattern_matching_nodes(condition).any?
98
+
84
99
  return unless (msg = message(node))
85
100
 
86
101
  add_offense(node.loc.keyword, message: format(msg, keyword: node.keyword)) do |corrector|
@@ -90,9 +105,14 @@ module RuboCop
90
105
  ignore_node(node)
91
106
  end
92
107
  end
108
+ # rubocop:enable Metrics/AbcSize
93
109
 
94
110
  private
95
111
 
112
+ def endless_method?(body)
113
+ body&.any_def_type? && body.endless?
114
+ end
115
+
96
116
  def defined_nodes(condition)
97
117
  if condition.defined_type?
98
118
  [condition]
@@ -112,12 +132,10 @@ module RuboCop
112
132
  end
113
133
 
114
134
  def pattern_matching_nodes(condition)
115
- if condition.type?(:match_pattern, :match_pattern_p)
135
+ if condition.any_match_pattern_type?
116
136
  [condition]
117
137
  else
118
- condition.each_descendant.select do |node|
119
- node.type?(:match_pattern, :match_pattern_p)
120
- end
138
+ condition.each_descendant.select(&:any_match_pattern_type?)
121
139
  end
122
140
  end
123
141
 
@@ -28,19 +28,16 @@ module RuboCop
28
28
 
29
29
  MSG = 'Avoid modifier `%<keyword>s` after another conditional.'
30
30
 
31
+ # rubocop:disable Metrics/AbcSize
31
32
  def on_if(node)
32
33
  return unless node.modifier_form? && node.body.if_type?
33
34
 
34
35
  add_offense(node.loc.keyword, message: format(MSG, keyword: node.keyword)) do |corrector|
35
- keyword = node.if? ? 'if' : 'unless'
36
-
37
- corrector.replace(node, <<~RUBY.chop)
38
- #{keyword} #{node.condition.source}
39
- #{node.if_branch.source}
40
- end
41
- RUBY
36
+ corrector.wrap(node.if_branch, "#{node.keyword} #{node.condition.source}\n", "\nend")
37
+ corrector.remove(node.if_branch.source_range.end.join(node.condition.source_range.end))
42
38
  end
43
39
  end
40
+ # rubocop:enable Metrics/AbcSize
44
41
  end
45
42
  end
46
43
  end