rubocop 1.92.0 → 1.92.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.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +1 -0
  3. data/lib/rubocop/cop/badge.rb +1 -1
  4. data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +16 -1
  5. data/lib/rubocop/cop/correctors/lambda_literal_to_method_corrector.rb +2 -2
  6. data/lib/rubocop/cop/correctors/multiline_literal_brace_corrector.rb +34 -0
  7. data/lib/rubocop/cop/layout/argument_alignment.rb +5 -1
  8. data/lib/rubocop/cop/layout/class_structure.rb +21 -7
  9. data/lib/rubocop/cop/layout/dot_position.rb +25 -15
  10. data/lib/rubocop/cop/layout/empty_line_after_multiline_condition.rb +11 -7
  11. data/lib/rubocop/cop/layout/empty_lines_around_block_body.rb +9 -3
  12. data/lib/rubocop/cop/layout/end_alignment.rb +3 -7
  13. data/lib/rubocop/cop/layout/extra_spacing.rb +1 -1
  14. data/lib/rubocop/cop/layout/first_argument_indentation.rb +26 -1
  15. data/lib/rubocop/cop/layout/first_array_element_line_break.rb +15 -0
  16. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +11 -5
  17. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +5 -3
  18. data/lib/rubocop/cop/layout/indentation_consistency.rb +22 -5
  19. data/lib/rubocop/cop/layout/line_length.rb +104 -6
  20. data/lib/rubocop/cop/layout/multiline_hash_brace_layout.rb +6 -4
  21. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +16 -5
  22. data/lib/rubocop/cop/layout/single_line_block_chain.rb +3 -3
  23. data/lib/rubocop/cop/layout/space_inside_reference_brackets.rb +2 -12
  24. data/lib/rubocop/cop/lint/ambiguous_regexp_literal.rb +6 -1
  25. data/lib/rubocop/cop/lint/heredoc_method_call_position.rb +4 -0
  26. data/lib/rubocop/cop/lint/number_conversion.rb +2 -0
  27. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +47 -1
  28. data/lib/rubocop/cop/lint/symbol_conversion.rb +1 -0
  29. data/lib/rubocop/cop/lint/useless_setter_call.rb +7 -4
  30. data/lib/rubocop/cop/mixin/check_line_breakable.rb +7 -6
  31. data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +3 -1
  32. data/lib/rubocop/cop/mixin/endless_method_body_help.rb +36 -0
  33. data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +9 -4
  34. data/lib/rubocop/cop/mixin/statement_modifier.rb +3 -2
  35. data/lib/rubocop/cop/mixin/visibility_help.rb +2 -1
  36. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +1 -1
  37. data/lib/rubocop/cop/style/access_modifier_declarations.rb +30 -2
  38. data/lib/rubocop/cop/style/and_or.rb +3 -3
  39. data/lib/rubocop/cop/style/arguments_forwarding.rb +28 -2
  40. data/lib/rubocop/cop/style/block_delimiters.rb +6 -0
  41. data/lib/rubocop/cop/style/class_and_module_children.rb +2 -0
  42. data/lib/rubocop/cop/style/class_methods_definitions.rb +7 -0
  43. data/lib/rubocop/cop/style/collection_querying.rb +79 -46
  44. data/lib/rubocop/cop/style/combinable_loops.rb +4 -0
  45. data/lib/rubocop/cop/style/concat_array_literals.rb +13 -1
  46. data/lib/rubocop/cop/style/empty_string_inside_interpolation.rb +53 -11
  47. data/lib/rubocop/cop/style/endless_method.rb +5 -1
  48. data/lib/rubocop/cop/style/for.rb +14 -1
  49. data/lib/rubocop/cop/style/guard_clause.rb +22 -21
  50. data/lib/rubocop/cop/style/hash_as_last_array_item.rb +4 -0
  51. data/lib/rubocop/cop/style/if_unless_modifier.rb +19 -12
  52. data/lib/rubocop/cop/style/it_block_parameter.rb +63 -2
  53. data/lib/rubocop/cop/style/lambda.rb +16 -1
  54. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +36 -4
  55. data/lib/rubocop/cop/style/mixin_grouping.rb +12 -3
  56. data/lib/rubocop/cop/style/mutable_constant.rb +25 -7
  57. data/lib/rubocop/cop/style/parentheses_around_condition.rb +1 -1
  58. data/lib/rubocop/cop/style/raise_args.rb +19 -9
  59. data/lib/rubocop/cop/style/redundant_begin.rb +2 -1
  60. data/lib/rubocop/cop/style/redundant_freeze.rb +1 -2
  61. data/lib/rubocop/cop/style/redundant_interpolation_unfreeze.rb +24 -1
  62. data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +28 -0
  63. data/lib/rubocop/cop/style/redundant_sort.rb +38 -4
  64. data/lib/rubocop/cop/style/require_order.rb +16 -8
  65. data/lib/rubocop/cop/style/rescue_modifier.rb +6 -0
  66. data/lib/rubocop/cop/style/send_with_literal_method_name.rb +4 -0
  67. data/lib/rubocop/cop/style/single_line_methods.rb +5 -0
  68. data/lib/rubocop/cop/style/struct_inheritance.rb +6 -0
  69. data/lib/rubocop/cop/style/symbol_proc.rb +1 -1
  70. data/lib/rubocop/lsp/routes.rb +23 -23
  71. data/lib/rubocop/server/cache.rb +4 -4
  72. data/lib/rubocop/target_ruby.rb +21 -9
  73. data/lib/rubocop/version.rb +1 -1
  74. metadata +2 -2
@@ -132,10 +132,11 @@ module RuboCop
132
132
  # private alias_method :qux, :foo
133
133
  #
134
134
  # end
135
- class AccessModifierDeclarations < Base
135
+ class AccessModifierDeclarations < Base # rubocop:disable Metrics/ClassLength
136
136
  extend AutoCorrector
137
137
 
138
138
  include ConfigurableEnforcedStyle
139
+ include EndlessMethodBodyHelp
139
140
  include RangeHelp
140
141
 
141
142
  GROUP_STYLE_MESSAGE = [
@@ -169,6 +170,10 @@ module RuboCop
169
170
  (send nil? :alias_method _ _))
170
171
  PATTERN
171
172
 
173
+ def self.autocorrect_incompatible_with
174
+ [Style::EndlessMethod, Style::SingleLineMethods]
175
+ end
176
+
172
177
  def on_send(node)
173
178
  return if allowed?(node)
174
179
 
@@ -248,10 +253,33 @@ module RuboCop
248
253
 
249
254
  access_modifier_is_inlined?(node) && !right_siblings_same_inline_method?(node)
250
255
  else
251
- access_modifier_is_not_inlined?(node) && select_grouped_def_nodes(node).any?
256
+ access_modifier_is_not_inlined?(node) && inlinable?(select_grouped_def_nodes(node))
252
257
  end
253
258
  end
254
259
 
260
+ def inlinable?(def_nodes)
261
+ return false if def_nodes.empty?
262
+
263
+ def_nodes.none? { |def_node| def_node.endless? && body_unsafe_to_inline?(def_node.body) }
264
+ end
265
+
266
+ def body_unsafe_to_inline?(body)
267
+ return true if body.each_node(:any_block).any? { |block| do_end_block?(block) }
268
+
269
+ target_ruby_version < 4.0 && command_body?(body)
270
+ end
271
+
272
+ def do_end_block?(block)
273
+ block.keywords? && !block.send_node.lambda_literal?
274
+ end
275
+
276
+ def command_body?(body)
277
+ body = body.body while body.rescue_type?
278
+ return true if body.send_type? && body.prefix_not? && !body.parenthesized?
279
+
280
+ command_call?(body)
281
+ end
282
+
255
283
  def correctable_group_offense?(node)
256
284
  return false unless group_style?
257
285
  return false if allowed?(node)
@@ -49,7 +49,7 @@ module RuboCop
49
49
  MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
50
50
 
51
51
  def self.autocorrect_incompatible_with
52
- [Style::MethodCallWithArgsParentheses, Style::ReturnNilInPredicateMethodDefinition]
52
+ [Style::MethodCallWithArgsParentheses, Style::ReturnNil, Style::ReturnNilInPredicateMethodDefinition]
53
53
  end
54
54
 
55
55
  def on_and(node)
@@ -73,7 +73,7 @@ module RuboCop
73
73
  message = message(node)
74
74
  add_offense(node.loc.operator, message: message) do |corrector|
75
75
  node.each_child_node do |expr|
76
- if expr.send_type?
76
+ if expr.call_type?
77
77
  correct_send(expr, corrector)
78
78
  elsif expr.type?(:return, :next, :break, :yield) || expr.assignment?
79
79
  correct_other(expr, corrector)
@@ -120,7 +120,7 @@ module RuboCop
120
120
  # however, 'not x' also parses as (send x :!)
121
121
  def correct_not(node, receiver, corrector)
122
122
  if node.prefix_bang?
123
- return unless receiver.send_type?
123
+ return unless receiver.call_type?
124
124
 
125
125
  correct_send(receiver, corrector)
126
126
  elsif node.prefix_not?
@@ -169,7 +169,7 @@ module RuboCop
169
169
 
170
170
  return if send_classifications.empty?
171
171
 
172
- if only_forwards_all?(send_classifications)
172
+ if forward_all?(send_nodes, send_classifications)
173
173
  add_forward_all_offenses(node, send_classifications, forwardable_args)
174
174
  elsif target_ruby_version >= 3.2
175
175
  add_post_ruby_32_offenses(node, send_classifications, forwardable_args)
@@ -192,11 +192,33 @@ module RuboCop
192
192
  [restarg_node, kwrestarg_node, blockarg_node]
193
193
  end
194
194
 
195
+ def forward_all?(send_nodes, send_classifications)
196
+ only_forwards_all?(send_classifications) &&
197
+ !anonymous_forwarding_elsewhere?(send_nodes, send_classifications)
198
+ end
199
+
195
200
  def only_forwards_all?(send_classifications)
196
201
  all_classifications = %i[all all_anonymous].freeze
197
202
  send_classifications.all? { |_, c, _, _| all_classifications.include?(c) }
198
203
  end
199
204
 
205
+ def anonymous_forwarding_elsewhere?(send_nodes, send_classifications)
206
+ classified = send_classifications.map(&:first)
207
+
208
+ send_nodes.any? do |send_node|
209
+ classified.none? { |node| node.equal?(send_node) } &&
210
+ forwards_anonymous_args?(send_node)
211
+ end
212
+ end
213
+
214
+ def forwards_anonymous_args?(send_node)
215
+ send_node.arguments.any? do |arg|
216
+ next arg.children.any?(&:forwarded_kwrestarg_type?) if arg.hash_type?
217
+
218
+ arg.forwarded_restarg_type?
219
+ end
220
+ end
221
+
200
222
  # rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
201
223
  def add_forward_all_offenses(node, send_classifications, forwardable_args)
202
224
  rest_arg, kwrest_arg, block_arg = *forwardable_args
@@ -376,12 +398,16 @@ module RuboCop
376
398
 
377
399
  def add_parens_if_missing(node, corrector)
378
400
  return if parentheses?(node)
379
- return if node.send_type? && node.method?(:[])
401
+ return if node.call_type? && operator_or_setter_call?(node)
380
402
  return unless (@parenthesized_nodes ||= Set.new.compare_by_identity).add?(node)
381
403
 
382
404
  add_parentheses(node, corrector)
383
405
  end
384
406
 
407
+ def operator_or_setter_call?(node)
408
+ node.setter_method? || (node.operator_method? && !node.loc.dot)
409
+ end
410
+
385
411
  # Classifies send nodes for possible rest/kwrest/all (including block) forwarding.
386
412
  class SendNodeClassifier
387
413
  extend NodePattern::Macros
@@ -306,6 +306,8 @@ module RuboCop
306
306
  end
307
307
 
308
308
  def move_comment_before_block(corrector, comment, block_node, closing_brace)
309
+ return if directive_comment?(comment)
310
+
309
311
  range = block_node.chained? ? end_of_chain(block_node.parent).source_range : closing_brace
310
312
 
311
313
  # It is possible that there is code between the block and the comment
@@ -319,6 +321,10 @@ module RuboCop
319
321
  corrector.insert_before(block_node, "#{comment.text}\n")
320
322
  end
321
323
 
324
+ def directive_comment?(comment)
325
+ comment.text.match?(DirectiveComment::DIRECTIVE_COMMENT_REGEXP)
326
+ end
327
+
322
328
  def source_range_before_comment(range, comment)
323
329
  range = range.end.join(comment.source_range.begin)
324
330
 
@@ -152,7 +152,9 @@ module RuboCop
152
152
  # making autocorrection ping-pong between the two forms.
153
153
  return if style_for_kind(node.body.type) == :nested
154
154
  return unless compactible_namespace?(node)
155
+ return if node.each_ancestor(:class, :module).any? { |ancestor| ignored_node?(ancestor) }
155
156
 
157
+ ignore_node(node)
156
158
  compact_node(corrector, node)
157
159
  remove_end(corrector, node.body)
158
160
  unindent(corrector, node)
@@ -75,7 +75,10 @@ module RuboCop
75
75
  return unless def_nodes(node).all? { |def_node| no_code_after?(def_node.source_range) }
76
76
 
77
77
  add_offense(node, message: MSG_SCLASS) do |corrector|
78
+ next if inside_corrected_sclass?(node)
79
+
78
80
  autocorrect_sclass(node, corrector)
81
+ ignore_node(node)
79
82
  end
80
83
  end
81
84
 
@@ -120,6 +123,10 @@ module RuboCop
120
123
  trailing.empty? || trailing.start_with?('#')
121
124
  end
122
125
 
126
+ def inside_corrected_sclass?(node)
127
+ node.each_ancestor(:sclass).any? { |ancestor| ignored_node?(ancestor) }
128
+ end
129
+
123
130
  def autocorrect_sclass(node, corrector)
124
131
  rewritten_defs = []
125
132
 
@@ -15,31 +15,24 @@ module RuboCop
15
15
  # would yield false positives. For example, `String` implements `length`
16
16
  # and `size`, but it doesn't include `Enumerable`.
17
17
  #
18
+ # When `count` is used without a block, it measures the collection's
19
+ # size, so the offense is corrected to `empty?` or `!empty?`. The
20
+ # predicate methods `any?`, `none?` and `one?` test element truthiness
21
+ # instead and would return different results for collections with falsey
22
+ # elements (e.g. `[false].count.zero?` is `false`, but `[false].none?`
23
+ # is `true`). For the same reason, `count == 1` and `count > 1` without
24
+ # a block are not flagged.
25
+ #
18
26
  # @safety
19
27
  # The cop is unsafe because receiver might not include `Enumerable`, or
20
28
  # it has nonstandard implementation of `count` or any replacement
21
29
  # methods.
22
30
  #
23
- # It's also unsafe because for collections with falsey values, expressions
24
- # with `count` without a block return a different result than methods `any?`,
25
- # `none?` and `one?`:
26
- #
27
- # [source,ruby]
28
- # ----
29
- # [nil, false].count.positive?
30
- # [nil].count == 1
31
- # # => true
32
- #
33
- # [nil, false].any?
34
- # [nil].one?
35
- # # => false
36
- #
37
- # [nil].count == 0
38
- # # => false
39
- #
40
- # [nil].none?
41
- # # => true
42
- # ----
31
+ # Blockless `count` is corrected to `empty?`, which `Enumerable` doesn't
32
+ # define. `Range`, `Enumerator` (including lazy ones), `Struct`, `StringIO`
33
+ # and any class that just includes `Enumerable` respond to `count` but not
34
+ # to `empty?`, so `(1...1).count.zero?` is `true` while `(1...1).empty?`
35
+ # raises `NoMethodError`.
43
36
  #
44
37
  # Autocorrection is unsafe when replacement methods don't iterate over
45
38
  # every element in collection and the given block runs side effects:
@@ -60,12 +53,14 @@ module RuboCop
60
53
  # x.count > 0
61
54
  # x.count != 0
62
55
  #
56
+ # # good
57
+ # !x.empty?
58
+ #
59
+ # # bad
63
60
  # x.count(&:foo?).positive?
64
61
  # x.count { |item| item.foo? }.positive?
65
62
  #
66
63
  # # good
67
- # x.any?
68
- #
69
64
  # x.any?(&:foo?)
70
65
  # x.any? { |item| item.foo? }
71
66
  #
@@ -74,24 +69,33 @@ module RuboCop
74
69
  # x.count == 0
75
70
  #
76
71
  # # good
77
- # x.none?
72
+ # x.empty?
78
73
  #
79
74
  # # bad
80
- # x.count == 1
81
- # x.one?
75
+ # x.count(&:foo?).zero?
76
+ # x.count(&:foo?) == 0
77
+ #
78
+ # # good
79
+ # x.none?(&:foo?)
80
+ #
81
+ # # bad
82
+ # x.count(&:foo?) == 1
83
+ #
84
+ # # good
85
+ # x.one?(&:foo?)
82
86
  #
83
87
  # @example AllCops:ActiveSupportExtensionsEnabled: false (default)
84
88
  #
85
89
  # # good
86
- # x.count > 1
90
+ # x.count(&:foo?) > 1
87
91
  #
88
92
  # @example AllCops:ActiveSupportExtensionsEnabled: true
89
93
  #
90
94
  # # bad
91
- # x.count > 1
95
+ # x.count(&:foo?) > 1
92
96
  #
93
97
  # # good
94
- # x.many?
98
+ # x.many?(&:foo?)
95
99
  #
96
100
  class CollectionQuerying < Base
97
101
  include RangeHelp
@@ -102,13 +106,21 @@ module RuboCop
102
106
  RESTRICT_ON_SEND = %i[positive? > != zero? ==].freeze
103
107
 
104
108
  REPLACEMENTS = {
105
- [:positive?, nil] => :any?,
106
- [:>, 0] => :any?,
107
- [:!=, 0] => :any?,
108
- [:zero?, nil] => :none?,
109
- [:==, 0] => :none?,
110
- [:==, 1] => :one?,
111
- [:>, 1] => :many?
109
+ [:positive?, nil] => 'any?',
110
+ [:>, 0] => 'any?',
111
+ [:!=, 0] => 'any?',
112
+ [:zero?, nil] => 'none?',
113
+ [:==, 0] => 'none?',
114
+ [:==, 1] => 'one?',
115
+ [:>, 1] => 'many?'
116
+ }.freeze
117
+
118
+ NO_BLOCK_REPLACEMENTS = {
119
+ [:positive?, nil] => '!empty?',
120
+ [:>, 0] => '!empty?',
121
+ [:!=, 0] => '!empty?',
122
+ [:zero?, nil] => 'empty?',
123
+ [:==, 0] => 'empty?'
112
124
  }.freeze
113
125
 
114
126
  # @!method count_predicate(node)
@@ -132,28 +144,49 @@ module RuboCop
132
144
  def on_send(node)
133
145
  return unless (count_node = count_predicate(node))
134
146
 
135
- replacement_method = replacement_method(node)
147
+ replacement = replacement_method(node, count_node)
136
148
 
137
- return unless replacement_supported?(replacement_method)
149
+ return unless replacement_supported?(node, replacement)
138
150
 
139
151
  offense_range = count_node.loc.selector.join(node.source_range.end)
140
- add_offense(offense_range,
141
- message: format(MSG, prefer: replacement_method)) do |corrector|
142
- corrector.replace(count_node.loc.selector, replacement_method)
143
- corrector.remove(removal_range(node))
152
+ add_offense(offense_range, message: format(MSG, prefer: replacement)) do |corrector|
153
+ autocorrect(corrector, node, count_node, replacement)
144
154
  end
145
155
  end
146
156
 
147
157
  private
148
158
 
149
- def replacement_method(node)
150
- REPLACEMENTS.fetch([node.method_name, node.first_argument&.value])
159
+ def replacement_method(node, count_node)
160
+ key = [node.method_name, node.first_argument&.value]
161
+ return REPLACEMENTS.fetch(key) if block_given_to_count?(count_node)
162
+
163
+ NO_BLOCK_REPLACEMENTS[key]
164
+ end
165
+
166
+ def block_given_to_count?(count_node)
167
+ count_node.parent&.any_block_type? || count_node.last_argument&.block_pass_type?
168
+ end
169
+
170
+ def replacement_supported?(node, replacement)
171
+ return false unless replacement
172
+ return false if replacement == 'many?' && !active_support_extensions_enabled?
173
+
174
+ !(replacement.start_with?('!') && receiver_of_call?(node))
151
175
  end
152
176
 
153
- def replacement_supported?(method_name)
154
- return true if active_support_extensions_enabled?
177
+ def receiver_of_call?(node)
178
+ node.parent&.call_type? && node.parent.receiver == node && node.parent.loc.dot
179
+ end
180
+
181
+ def autocorrect(corrector, node, count_node, replacement)
182
+ if replacement.start_with?('!')
183
+ corrector.insert_before(count_node, '!')
184
+ corrector.replace(count_node.loc.selector, 'empty?')
185
+ else
186
+ corrector.replace(count_node.loc.selector, replacement)
187
+ end
155
188
 
156
- method_name != :many?
189
+ corrector.remove(removal_range(node))
157
190
  end
158
191
 
159
192
  def removal_range(node)
@@ -65,6 +65,10 @@ module RuboCop
65
65
 
66
66
  MSG = 'Combine this loop with the previous loop.'
67
67
 
68
+ def self.autocorrect_incompatible_with
69
+ [Layout::BlockEndNewline, Layout::RedundantLineBreak]
70
+ end
71
+
68
72
  def on_block(node)
69
73
  return unless node.parent&.begin_type?
70
74
  return unless collection_looping_method?(node)
@@ -118,7 +118,8 @@ module RuboCop
118
118
  # A comment or a heredoc body there can't be moved out of the way.
119
119
  return unless closing_ranges.all? { |range| range.source.match?(/\A[\s,]*\]\z/) }
120
120
 
121
- corrector.replace(node.loc.selector, 'push')
121
+ replace_selector(corrector, node)
122
+
122
123
  node.arguments.zip(closing_ranges) do |argument, closing_range|
123
124
  corrector.remove(argument.loc.begin)
124
125
  corrector.remove(closing_range)
@@ -133,6 +134,17 @@ module RuboCop
133
134
 
134
135
  range_between(argument.children.last.source_range.end_pos, argument.loc.end.end_pos)
135
136
  end
137
+
138
+ def replace_selector(corrector, node)
139
+ if node.parenthesized? || node.single_line?
140
+ corrector.replace(node.loc.selector, 'push')
141
+ else
142
+ range = node.loc.selector.join(node.first_argument.source_range.begin)
143
+
144
+ corrector.replace(range, 'push(')
145
+ corrector.insert_after(node.last_argument.loc.end, ')')
146
+ end
147
+ end
136
148
  end
137
149
  end
138
150
  end
@@ -45,6 +45,8 @@ module RuboCop
45
45
  MSG_TRAILING_CONDITIONAL = 'Do not use trailing conditionals in string interpolation.'
46
46
  MSG_TERNARY = 'Do not return empty strings in string interpolation.'
47
47
 
48
+ INTERPOLATED_TYPES = %i[dstr dsym regexp xstr].freeze
49
+
48
50
  def on_interpolation(node)
49
51
  node.each_child_node(:if) do |child_node|
50
52
  if style == :trailing_conditional
@@ -63,28 +65,54 @@ module RuboCop
63
65
  return if child_node.modifier_form?
64
66
 
65
67
  if empty_if_outcome?(child_node)
66
- ternary_style_autocorrect(child_node, child_node.else_branch.source, 'unless')
68
+ ternary_style_autocorrect(child_node, child_node.else_branch, 'unless')
67
69
  end
68
70
 
69
71
  return unless empty_else_outcome?(child_node)
70
72
 
71
- ternary_style_autocorrect(child_node, child_node.if_branch.source, 'if')
73
+ ternary_style_autocorrect(child_node, child_node.if_branch, 'if')
72
74
  end
73
75
 
74
76
  def ternary_correction(node, child_node)
75
77
  return unless child_node.modifier_form?
76
78
 
77
- ternary_component = if child_node.unless?
78
- "'' : #{child_node.if_branch.source}"
79
- else
80
- "#{child_node.if_branch.source} : ''"
81
- end
82
-
83
79
  add_offense(node, message: MSG_TRAILING_CONDITIONAL) do |corrector|
84
- corrector.replace(node, "\#{#{child_node.condition.source} ? #{ternary_component}}")
80
+ next if nested_trailing_conditional?(child_node)
81
+
82
+ autocorrect_to_ternary(corrector, child_node)
83
+ end
84
+ end
85
+
86
+ def nested_trailing_conditional?(if_node)
87
+ if_node.each_descendant(:if).any? do |node|
88
+ node.modifier_form? && node.parent.parent&.type?(*INTERPOLATED_TYPES)
85
89
  end
86
90
  end
87
91
 
92
+ def autocorrect_to_ternary(corrector, if_node)
93
+ condition = if_node.condition.source
94
+ branch = ternary_branch_source(if_node.if_branch)
95
+ outcomes = if_node.unless? ? "'' : #{branch}" : "#{branch} : ''"
96
+
97
+ corrector.replace(if_node, "#{condition} ? #{outcomes}")
98
+ end
99
+
100
+ def ternary_branch_source(branch)
101
+ return branch.source unless command_call?(branch)
102
+
103
+ arguments = branch.first_argument.source_range.join(branch.last_argument.source_range)
104
+ method = branch.source_range.begin.join(arguments.begin).source.rstrip
105
+
106
+ "#{method}(#{arguments.source})"
107
+ end
108
+
109
+ def command_call?(node)
110
+ return false unless node.type?(:call, :super, :yield) && node.arguments?
111
+ return false if node.call_type? && (node.operator_method? || node.setter_method?)
112
+
113
+ !node.parenthesized?
114
+ end
115
+
88
116
  def empty_if_outcome?(node)
89
117
  empty_branch_outcome?(node.if_branch)
90
118
  end
@@ -99,9 +127,23 @@ module RuboCop
99
127
  branch.nil_type? || (branch.str_type? && branch.value.empty?)
100
128
  end
101
129
 
102
- def ternary_style_autocorrect(node, outcome, condition)
130
+ def ternary_style_autocorrect(node, outcome, keyword)
103
131
  add_offense(node, message: MSG_TERNARY) do |corrector|
104
- corrector.replace(node, "#{outcome} #{condition} #{node.condition.source}")
132
+ next if empty_string_conditional_in_condition?(node)
133
+
134
+ head = node.source_range.begin.join(outcome.source_range.begin)
135
+ tail = outcome.source_range.end.join(node.source_range.end)
136
+
137
+ corrector.remove(head)
138
+ corrector.replace(tail, " #{keyword} #{node.condition.source}")
139
+ end
140
+ end
141
+
142
+ def empty_string_conditional_in_condition?(if_node)
143
+ if_node.condition.each_descendant(:if).any? do |node|
144
+ next false if node.modifier_form? || !node.parent.parent&.type?(*INTERPOLATED_TYPES)
145
+
146
+ empty_if_outcome?(node) || empty_else_outcome?(node)
105
147
  end
106
148
  end
107
149
  end
@@ -147,8 +147,12 @@ module RuboCop
147
147
 
148
148
  # `Style/MethodDefParentheses` adds the same parentheses a parameter list needs in
149
149
  # an endless definition, so letting both correct in the same pass would double them.
150
+ # `Style/GuardClause` turns a single `if` body into several statements,
151
+ # which an endless definition cannot hold.
152
+ # `Style/RaiseArgs` would drop the parentheses of a `raise` that becomes
153
+ # the body in the same pass, which a method passed as an argument needs.
150
154
  def self.autocorrect_incompatible_with
151
- [Style::MethodCallWithArgsParentheses, Style::MethodDefParentheses]
155
+ [Style::GuardClause, Style::MethodCallWithArgsParentheses, Style::MethodDefParentheses, Style::RaiseArgs]
152
156
  end
153
157
 
154
158
  def on_def(node)
@@ -56,8 +56,10 @@ module RuboCop
56
56
 
57
57
  # `Style/RedundantBegin` removes the `begin` of a block body in the same pass,
58
58
  # leaving a `rescue` or `ensure` clause directly in the `for` body.
59
+ # `Style/ItBlockParameter` replaces block parameter references in the body
60
+ # with `it` in the same pass, while they become `for` loop variables.
59
61
  def self.autocorrect_incompatible_with
60
- [Style::RedundantBegin]
62
+ [Style::RedundantBegin, Style::ItBlockParameter]
61
63
  end
62
64
 
63
65
  def on_for(node)
@@ -78,6 +80,7 @@ module RuboCop
78
80
 
79
81
  if style == :for
80
82
  return unless node.receiver
83
+ return unless convertible_to_for?(node)
81
84
  return if rescue_or_ensure_body?(node) || do_end_block_in_collection?(node)
82
85
 
83
86
  add_offense(node, message: PREFER_FOR) do |corrector|
@@ -123,6 +126,16 @@ module RuboCop
123
126
  node.multiline? && node.method?(:each) && !node.send_node.arguments?
124
127
  end
125
128
 
129
+ def convertible_to_for?(node)
130
+ return false if node.type?(:numblock, :itblock)
131
+ return true unless node.arguments?
132
+
133
+ arguments = node.arguments
134
+ return false if arguments.children.one? && arguments.children.first.restarg_type?
135
+
136
+ arguments.each_descendant.all? { |arg| arg.type?(:arg, :restarg, :mlhs) }
137
+ end
138
+
126
139
  def rescue_or_ensure_body?(node)
127
140
  node.body&.type?(:rescue, :ensure)
128
141
  end
@@ -186,40 +186,37 @@ module RuboCop
186
186
  return if trivial?(node)
187
187
 
188
188
  example = "#{conditional_keyword} #{condition.source}; #{scope_exiting_keyword}; end"
189
- replacement = <<~RUBY.chomp
190
- #{conditional_keyword} #{condition.source}
191
- #{scope_exiting_keyword}
192
- end
193
- RUBY
189
+ replacement = "#{conditional_keyword} #{condition.source}\n #{scope_exiting_keyword}"
194
190
  end
195
191
 
196
192
  add_offense(node.loc.keyword, message: format(MSG, example: example)) do |corrector|
197
193
  next if node.else? && guard.nil?
198
194
 
199
- autocorrect(corrector, node, condition, replacement || example, guard)
195
+ autocorrect(corrector, node, example, replacement, guard)
200
196
  end
201
197
  end
202
198
 
203
199
  # rubocop:disable-next Metrics/AbcSize
204
- def autocorrect(corrector, node, condition, replacement, guard)
205
- corrector.replace(node.loc.keyword.join(condition.source_range), replacement)
206
-
207
- if_branch = node.if_branch
208
- else_branch = node.else_branch
209
-
200
+ def autocorrect(corrector, node, example, multiline_replacement, guard)
201
+ range = node.loc.keyword.join(node.condition.source_range)
202
+ corrector.replace(range, multiline_replacement || example)
210
203
  corrector.replace(node.loc.begin, "\n") if node.then?
211
204
 
212
- if (if_heredoc = find_heredoc_argument(if_branch))
213
- autocorrect_heredoc_argument(corrector, node, if_heredoc, else_branch, guard)
214
- elsif (else_heredoc = find_heredoc_argument(else_branch))
215
- autocorrect_heredoc_argument(corrector, node, else_heredoc, if_branch, guard)
216
- else
217
- corrector.remove(node.loc.end)
218
- return unless node.else?
205
+ branches = [node.if_branch, node.else_branch]
206
+ guard_branch, leave_branch = guard == :else ? branches.reverse : branches
219
207
 
220
- corrector.remove(node.loc.else)
221
- corrector.remove(range_of_branch_to_remove(node, guard))
208
+ heredoc = find_heredoc_argument(guard_branch)
209
+ close_multiline_guard_clause(corrector, range, heredoc, guard) if multiline_replacement
210
+
211
+ if heredoc
212
+ return autocorrect_heredoc_argument(corrector, node, heredoc, leave_branch, guard)
222
213
  end
214
+
215
+ corrector.remove(node.loc.end)
216
+ return unless node.else?
217
+
218
+ corrector.remove(node.loc.else)
219
+ corrector.remove(range_of_branch_to_remove(node, guard))
223
220
  end
224
221
 
225
222
  def heredoc?(argument)
@@ -241,6 +238,10 @@ module RuboCop
241
238
  find_heredoc_argument(node.receiver)
242
239
  end
243
240
 
241
+ def close_multiline_guard_clause(corrector, range, heredoc, guard)
242
+ corrector.insert_after(guard && heredoc ? heredoc.loc.heredoc_end : range, "\nend")
243
+ end
244
+
244
245
  def autocorrect_heredoc_argument(corrector, node, heredoc_node, leave_branch, guard)
245
246
  remove_whole_lines(corrector, node.loc.end)
246
247
  return unless node.else?
@@ -44,6 +44,10 @@ module RuboCop
44
44
  include ConfigurableEnforcedStyle
45
45
  extend AutoCorrector
46
46
 
47
+ def self.autocorrect_incompatible_with
48
+ [Style::TrailingCommaInArrayLiteral]
49
+ end
50
+
47
51
  def on_hash(node)
48
52
  return if node.children.first&.kwsplat_type?
49
53
  return unless (array = containing_array(node))