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
@@ -100,6 +100,7 @@ module RuboCop
100
100
  pattern_matching_nodes(condition).any?
101
101
 
102
102
  return unless (msg = message(node))
103
+ return if modifier_sharing_line_in_logical_operator?(node)
103
104
 
104
105
  add_offense(node.loc.keyword, message: format(msg, keyword: node.keyword)) do |corrector|
105
106
  next if part_of_ignored_node?(node)
@@ -167,11 +168,11 @@ module RuboCop
167
168
  return to_modifier_form_with_move_comment(node, indent(node), comment)
168
169
  end
169
170
 
170
- heredoc_ranges = heredoc_ranges_in(node.if_branch)
171
+ heredoc_ranges = heredoc_ranges_in(node)
171
172
  unless heredoc_ranges.empty?
172
173
  remove_heredoc(corrector, heredoc_ranges)
173
174
 
174
- return to_normal_form_with_heredoc(node, indent(node), heredoc_ranges)
175
+ return to_normal_form_with_heredoc(node, indent(node))
175
176
  end
176
177
 
177
178
  to_normal_form(node, indent(node))
@@ -233,11 +234,18 @@ module RuboCop
233
234
  end
234
235
 
235
236
  def collection_from_ancestor(node)
236
- return node if node&.type?(:array, :call)
237
+ return node if node&.type?(:array, :call, :and, :or)
237
238
 
238
239
  node.parent if node&.type?(:pair)
239
240
  end
240
241
 
242
+ def modifier_sharing_line_in_logical_operator?(node)
243
+ collection = find_containing_collection(node)
244
+ return false if !node.modifier_form? || !collection&.operator_keyword?
245
+
246
+ another_modifier_if_on_same_line?(node)
247
+ end
248
+
241
249
  def another_modifier_if_on_same_line?(node)
242
250
  collection = find_containing_collection(node)
243
251
  return false unless collection
@@ -280,15 +288,14 @@ module RuboCop
280
288
  end
281
289
  end
282
290
 
283
- def to_normal_form_with_heredoc(node, indentation, heredoc_ranges)
284
- heredoc_lines = heredoc_ranges.map(&:source)
285
-
286
- <<~RUBY.chomp
287
- #{node.keyword} #{node.condition.source}
288
- #{indentation} #{node.body.source}
289
- #{heredoc_lines.join("\n")}
290
- #{indentation}end
291
- RUBY
291
+ def to_normal_form_with_heredoc(node, indentation)
292
+ [
293
+ "#{node.keyword} #{node.condition.source}",
294
+ *heredoc_ranges_in(node.condition).map(&:source),
295
+ "#{indentation} #{node.body.source}",
296
+ *heredoc_ranges_in(node.if_branch).map(&:source),
297
+ "#{indentation}end"
298
+ ].join("\n")
292
299
  end
293
300
 
294
301
  def to_modifier_form_with_move_comment(node, indentation, comment)
@@ -59,6 +59,8 @@ module RuboCop
59
59
  MSG_AVOID_IT_PARAMETER = 'Avoid using `it` block parameter.'
60
60
  MSG_AVOID_IT_PARAMETER_MULTILINE = 'Avoid using `it` block parameter for multi-line blocks.'
61
61
 
62
+ SCOPE_TYPES = %i[any_def class module sclass].freeze
63
+
62
64
  minimum_target_ruby_version 3.4
63
65
 
64
66
  # `Style/StabbyLambdaParentheses` wraps the parameter this cop removes in the same pass,
@@ -67,6 +69,10 @@ module RuboCop
67
69
  [Style::Lambda, Style::StabbyLambdaParentheses]
68
70
  end
69
71
 
72
+ def on_new_investigation
73
+ @it_variable_definitions = nil
74
+ end
75
+
70
76
  def on_block(node)
71
77
  return unless style == :always
72
78
  return unless node.arguments.one?
@@ -75,7 +81,7 @@ module RuboCop
75
81
  return unless node.first_argument.arg_type?
76
82
 
77
83
  block_argument_name = node.first_argument.source
78
- return if used_within_inner_block?(node.body, block_argument_name)
84
+ return if keep_parameter_name?(node.body, block_argument_name)
79
85
 
80
86
  find_block_variables(node, block_argument_name).each do |variable|
81
87
  add_offense(variable, message: MSG_USE_IT_PARAMETER) do |corrector|
@@ -116,7 +122,7 @@ module RuboCop
116
122
  private
117
123
 
118
124
  def find_block_variables(node, block_argument_name)
119
- return [] unless node.body
125
+ return [] if !node.body || it_variable_in_scope?(node)
120
126
 
121
127
  ranges = inner_block_ranges(node.body)
122
128
 
@@ -125,10 +131,47 @@ module RuboCop
125
131
  end
126
132
  end
127
133
 
134
+ def it_variable_in_scope?(node)
135
+ scope = nearest_scope(node)
136
+ return true if it_argument_in_scope?(node, scope)
137
+
138
+ it_variable_definitions.any? do |definition|
139
+ nearest_scope(definition).equal?(scope) && visible_in_block?(definition, node)
140
+ end
141
+ end
142
+
143
+ def it_variable_definitions
144
+ @it_variable_definitions ||=
145
+ variable_definitions(processed_source.ast).filter_map do |name, definition|
146
+ definition if name == 'it'
147
+ end
148
+ end
149
+
150
+ def nearest_scope(node)
151
+ node.each_ancestor(*SCOPE_TYPES).first
152
+ end
153
+
154
+ def it_argument_in_scope?(node, scope)
155
+ owners = node.each_ancestor(:block).select { |block| nearest_scope(block).equal?(scope) }
156
+ owners << scope if scope&.any_def_type?
157
+
158
+ owners.any? { |owner| owner.arguments.argument_list.any? { |arg| arg.name == :it } }
159
+ end
160
+
161
+ def visible_in_block?(definition, node)
162
+ return true if definition.source_range.begin_pos < node.source_range.begin_pos
163
+
164
+ node.source_range.contains?(definition.source_range)
165
+ end
166
+
128
167
  def inner_block_ranges(body)
129
168
  body.each_node(:any_block).map { |block| block.loc.begin.join(block.loc.end) }
130
169
  end
131
170
 
171
+ def keep_parameter_name?(body, block_arg_name)
172
+ used_within_inner_block?(body, block_arg_name) || reassigned?(body, block_arg_name)
173
+ end
174
+
132
175
  def used_within_inner_block?(body, block_argument_name)
133
176
  return false unless body
134
177
 
@@ -139,11 +182,29 @@ module RuboCop
139
182
  end
140
183
  end
141
184
 
185
+ def reassigned?(body, block_argument_name)
186
+ return false unless body
187
+
188
+ variable_definitions(body).any? { |name, _definition| name == block_argument_name }
189
+ end
190
+
142
191
  def block_variables(body, block_argument_name)
143
192
  body.each_descendant(:lvar).select do |descendant|
144
193
  descendant.source == block_argument_name
145
194
  end
146
195
  end
196
+
197
+ def variable_definitions(node)
198
+ node.each_node(:lvasgn, :match_var, :match_with_lvasgn).flat_map do |definition|
199
+ names = if definition.match_with_lvasgn_type?
200
+ definition.children.first.each_capture(named: true).map(&:name)
201
+ else
202
+ [definition.children.first.to_s]
203
+ end
204
+
205
+ names.map { |name| [name, definition] }
206
+ end
207
+ end
147
208
  end
148
209
  end
149
210
  end
@@ -129,12 +129,27 @@ module RuboCop
129
129
 
130
130
  return unless node.arguments?
131
131
 
132
- arg_str = "(#{lambda_arg_string(node.arguments)})"
132
+ arg_str = lambda_arg_string(node.arguments)
133
+ arg_str = " #{arg_str} " if space_inside_parens?
134
+ arg_str = "(#{arg_str})"
135
+ arg_str = " #{arg_str}" if space_in_lambda_literal_required?
133
136
 
134
137
  corrector.insert_after(node.send_node, arg_str)
135
138
  corrector.remove(arguments_with_whitespace(node))
136
139
  end
137
140
 
141
+ def space_inside_parens?
142
+ return false unless config.cop_enabled?('Layout/SpaceInsideParens')
143
+
144
+ %w[space compact].include?(config.for_cop('Layout/SpaceInsideParens')['EnforcedStyle'])
145
+ end
146
+
147
+ def space_in_lambda_literal_required?
148
+ return false unless config.cop_enabled?('Layout/SpaceInLambdaLiteral')
149
+
150
+ config.for_cop('Layout/SpaceInLambdaLiteral')['EnforcedStyle'] == 'require_space'
151
+ end
152
+
138
153
  def arguments_with_whitespace(node)
139
154
  node.loc.begin.end.join(node.arguments.loc.end)
140
155
  end
@@ -30,7 +30,7 @@ module RuboCop
30
30
 
31
31
  private
32
32
 
33
- def omit_parentheses(node) # rubocop:disable Metrics/PerceivedComplexity
33
+ def omit_parentheses(node) # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
34
34
  return unless node.parenthesized?
35
35
  return if inside_endless_method_def?(node)
36
36
  return if require_parentheses_for_hash_value_omission?(node)
@@ -42,6 +42,7 @@ module RuboCop
42
42
  return if allowed_string_interpolation_method_call?(node)
43
43
  return if code_after_closing_parenthesis_on_its_own_line?(node)
44
44
  return if comment_before_first_argument?(node)
45
+ return if args_parenthesized?(node)
45
46
 
46
47
  (@pending_omit_offenses ||= []) << node
47
48
  end
@@ -132,7 +133,7 @@ module RuboCop
132
133
  opening.source_line[(opening.column + 1)..].gsub(TRAILING_WHITESPACE_REGEX, '').empty?
133
134
  end
134
135
 
135
- def legitimate_call_with_parentheses?(node) # rubocop:disable Metrics/PerceivedComplexity
136
+ def legitimate_call_with_parentheses?(node) # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
136
137
  call_in_literals?(node) ||
137
138
  node.parent&.when_type? ||
138
139
  call_with_ambiguous_arguments?(node) ||
@@ -142,7 +143,9 @@ module RuboCop
142
143
  allowed_multiline_call_with_parentheses?(node) ||
143
144
  allowed_chained_call_with_parentheses?(node) ||
144
145
  assignment_in_condition?(node) ||
145
- forwards_anonymous_rest_arguments?(node)
146
+ command_assignment_in_expression?(node) ||
147
+ forwards_anonymous_rest_arguments?(node) ||
148
+ trailing_comma?(node)
146
149
  end
147
150
 
148
151
  def call_in_literals?(node)
@@ -278,7 +281,7 @@ module RuboCop
278
281
  end
279
282
 
280
283
  def assigned_before?(node, target)
281
- node.assignment? && node.loc.operator.begin < target.loc.begin
284
+ node.assignment? && node.loc.operator.begin < target.source_range.begin
282
285
  end
283
286
 
284
287
  def inside_string_interpolation?(node)
@@ -295,6 +298,27 @@ module RuboCop
295
298
  parent.assignment? && (grandparent.conditional? || grandparent.when_type?)
296
299
  end
297
300
 
301
+ # rubocop:disable-next Metrics/PerceivedComplexity
302
+ def command_assignment_in_expression?(node)
303
+ value = node.parent&.type?(:any_block, :rescue) ? node.parent : node
304
+ assignment = outermost_assignment(value)
305
+ return false if assignment.equal?(value)
306
+
307
+ call_in_literals?(assignment) ||
308
+ call_in_logical_operators?(assignment) ||
309
+ call_in_optional_arguments?(assignment) ||
310
+ call_as_argument_or_chain?(assignment) ||
311
+ assignment.parent&.type?(:masgn, :return, :next, :break, :defined?)
312
+ end
313
+
314
+ def outermost_assignment(value)
315
+ parent = value.parent
316
+ return value if !parent&.assignment? || parent.masgn_type?
317
+ return value unless assigned_before?(parent, value)
318
+
319
+ outermost_assignment(parent)
320
+ end
321
+
298
322
  def forwards_anonymous_rest_arguments?(node)
299
323
  node.arguments.any? do |argument|
300
324
  next true if argument.forwarded_restarg_type?
@@ -302,6 +326,14 @@ module RuboCop
302
326
  argument.hash_type? && argument.children.any?(&:forwarded_kwrestarg_type?)
303
327
  end
304
328
  end
329
+
330
+ def trailing_comma?(node)
331
+ return false unless (last_argument = node.last_argument)
332
+
333
+ range = last_argument.source_range.end.join(node.loc.end.begin)
334
+
335
+ processed_source.tokens_within(range).any?(&:comma?)
336
+ end
305
337
  end
306
338
  end
307
339
  end
@@ -117,17 +117,26 @@ module RuboCop
117
117
 
118
118
  def separate_mixins(node)
119
119
  arguments = node.arguments.reverse
120
- mixins = ["#{node.method_name} #{arguments.first.source}"]
120
+ mixins = [mixin_call(node.method_name, arguments.first.source, node.parenthesized?)]
121
121
 
122
122
  arguments[1..].inject(mixins) do |replacement, arg|
123
- replacement << "#{indent(node)}#{node.method_name} #{arg.source}"
123
+ call = mixin_call(node.method_name, arg.source, node.parenthesized?)
124
+ replacement << "#{indent(node)}#{call}"
124
125
  end.join("\n")
125
126
  end
126
127
 
127
128
  def group_mixins(node, mixins)
128
129
  mixin_names = mixins.reverse.flat_map { |mixin| mixin.arguments.map(&:source) }
129
130
 
130
- "#{node.method_name} #{mixin_names.join(', ')}"
131
+ mixin_call(node.method_name, mixin_names.join(', '), mixins.any?(&:parenthesized?))
132
+ end
133
+
134
+ def mixin_call(method_name, arguments_source, parenthesized)
135
+ if parenthesized
136
+ "#{method_name}(#{arguments_source})"
137
+ else
138
+ "#{method_name} #{arguments_source}"
139
+ end
131
140
  end
132
141
  end
133
142
  end
@@ -150,7 +150,7 @@ module RuboCop
150
150
  # is the last element and spans multiple lines, the trailing comma lands between the literal
151
151
  # and the `.freeze` appended in the same pass.
152
152
  def self.autocorrect_incompatible_with
153
- [Style::TrailingCommaInArrayLiteral, Style::TrailingCommaInHashLiteral]
153
+ [Style::HashAsLastArrayItem, Style::TrailingCommaInArrayLiteral, Style::TrailingCommaInHashLiteral]
154
154
  end
155
155
 
156
156
  def on_casgn(node)
@@ -228,15 +228,32 @@ module RuboCop
228
228
  return
229
229
  end
230
230
 
231
+ return expand_omitted_hash_value(corrector, node.parent) if omitted_hash_value?(node)
232
+
233
+ wrap_for_freeze(corrector, node, expr)
234
+ corrector.insert_after(expr, '.freeze')
235
+
236
+ freeze_nested_literals(corrector, node) if recursive?
237
+ end
238
+
239
+ def omitted_hash_value?(node)
240
+ node.parent&.pair_type? && node.parent.value_omission?
241
+ end
242
+
243
+ def expand_omitted_hash_value(corrector, pair)
244
+ key = pair.key.value
245
+
246
+ corrector.replace(pair, "#{key}: #{key}.freeze")
247
+ end
248
+
249
+ def wrap_for_freeze(corrector, node, expr)
231
250
  if node.array_type? && !node.bracketed?
232
251
  corrector.wrap(expr, '[', ']')
252
+ elsif node.hash_type? && !node.braces?
253
+ corrector.wrap(expr, '{ ', ' }')
233
254
  elsif requires_parentheses?(node)
234
255
  corrector.wrap(expr, '(', ')')
235
256
  end
236
-
237
- corrector.insert_after(expr, '.freeze')
238
-
239
- freeze_nested_literals(corrector, node) if recursive?
240
257
  end
241
258
 
242
259
  # Recursively freezes every nested mutable literal inside an array or
@@ -262,13 +279,14 @@ module RuboCop
262
279
  # Returns the child literals of an array or hash node that may
263
280
  # themselves need freezing. For hashes, both keys and values are
264
281
  # included. Percent-literal arrays (e.g. `%w(a b)`) are skipped because
265
- # `.freeze` cannot be appended to their contents.
282
+ # `.freeze` cannot be appended to their contents. Splat elements are
283
+ # skipped because the splatted object is not stored in the array.
266
284
  def literal_children(node)
267
285
  case node.type
268
286
  when :array
269
287
  return [] if node.percent_literal?
270
288
 
271
- node.children
289
+ node.children.reject(&:splat_type?)
272
290
  when :hash
273
291
  node.children.flat_map { |child| child.pair_type? ? child.children : [] }
274
292
  else
@@ -83,7 +83,7 @@ module RuboCop
83
83
  control_op_condition(cond) do |first_child, rest_children|
84
84
  return if require_parentheses?(node, first_child)
85
85
  return if semicolon_separated_expressions?(first_child, rest_children)
86
- return if modifier_op?(first_child)
86
+ return if modifier_op?(first_child) || first_child.masgn_type?
87
87
  return if parens_allowed?(cond)
88
88
 
89
89
  message = message(cond)
@@ -101,10 +101,7 @@ module RuboCop
101
101
  return if exception.send_type? && exception.first_argument&.hash_type?
102
102
 
103
103
  add_offense(node, message: format(COMPACT_MSG, method: node.method_name)) do |corrector|
104
- replacement = correction_exploded_to_compact(node)
105
-
106
- corrector.replace(node, replacement)
107
- opposite_style_detected
104
+ replace_raise(corrector, node, correction_exploded_to_compact(node))
108
105
  end
109
106
  else
110
107
  correct_style_detected
@@ -121,13 +118,22 @@ module RuboCop
121
118
  return if allowed_non_exploded_type?(first_arg)
122
119
 
123
120
  add_offense(node, message: format(EXPLODED_MSG, method: node.method_name)) do |corrector|
124
- replacement = correction_compact_to_exploded(node)
125
-
126
- corrector.replace(node, replacement)
127
- opposite_style_detected
121
+ replace_raise(corrector, node, correction_compact_to_exploded(node))
128
122
  end
129
123
  end
130
124
 
125
+ def replace_raise(corrector, node, replacement)
126
+ opposite_style_detected
127
+ return if inside_corrected_raise?(node)
128
+
129
+ corrector.replace(node, replacement)
130
+ ignore_node(node)
131
+ end
132
+
133
+ def inside_corrected_raise?(node)
134
+ node.each_ancestor(:send).any? { |ancestor| ignored_node?(ancestor) }
135
+ end
136
+
131
137
  def use_new_method?(first_arg)
132
138
  first_arg.send_type? && first_arg.receiver && first_arg.method?(:new)
133
139
  end
@@ -152,7 +158,11 @@ module RuboCop
152
158
  end
153
159
 
154
160
  def requires_parens?(parent)
155
- parent.operator_keyword? || (parent.if_type? && parent.ternary?)
161
+ return true if parent.operator_keyword?
162
+ return true if parent.type?(:call, :super, :yield, :array, :pair)
163
+ return true if parent.any_def_type? && parent.endless?
164
+
165
+ parent.if_type? && parent.ternary?
156
166
  end
157
167
  end
158
168
  end
@@ -191,7 +191,8 @@ module RuboCop
191
191
  def use_modifier_form_after_multiline_begin_block?(node)
192
192
  return false unless (parent = node.parent)
193
193
 
194
- node.multiline? && parent.if_type? && parent.modifier_form?
194
+ node.multiline? && parent.if_type? && parent.modifier_form? &&
195
+ !parent.condition.equal?(node)
195
196
  end
196
197
 
197
198
  def correct_modifier_form_after_multiline_begin_block(corrector, node)
@@ -29,8 +29,7 @@ module RuboCop
29
29
  operation_produces_immutable_object?(node.receiver))
30
30
 
31
31
  add_offense(node) do |corrector|
32
- corrector.remove(node.loc.dot)
33
- corrector.remove(node.loc.selector)
32
+ corrector.remove(node.loc.dot.join(node.loc.selector))
34
33
  end
35
34
  end
36
35
 
@@ -30,6 +30,10 @@ module RuboCop
30
30
 
31
31
  minimum_target_ruby_version 3.0
32
32
 
33
+ def self.autocorrect_incompatible_with
34
+ [Layout::MultilineMethodCallBraceLayout]
35
+ end
36
+
33
37
  # @!method redundant_unfreeze?(node)
34
38
  def_node_matcher :redundant_unfreeze?, <<~PATTERN
35
39
  {
@@ -41,9 +45,10 @@ module RuboCop
41
45
  def on_dstr(node)
42
46
  return if uninterpolated_string?(node) || uninterpolated_heredoc?(node)
43
47
  return unless redundant_unfreeze?(node.parent)
48
+ return if comment_outside_string?(node)
44
49
 
45
50
  add_offense(offense_range(node.parent)) do |corrector|
46
- corrector.replace(node.parent, node.source)
51
+ remove_unfreeze(corrector, node.parent, node)
47
52
  end
48
53
  end
49
54
 
@@ -56,6 +61,24 @@ module RuboCop
56
61
  node.loc.selector
57
62
  end
58
63
  end
64
+
65
+ # Only the parts around the string are removed, so that an unfrozen string
66
+ # interpolated into it can be corrected in the same pass.
67
+ def remove_unfreeze(corrector, node, string)
68
+ corrector.remove(node.source_range.begin.join(string.source_range.begin))
69
+ corrector.remove(string.source_range.end.join(node.source_range.end))
70
+ end
71
+
72
+ def comment_outside_string?(node)
73
+ call_range = node.parent.source_range
74
+ string_range = node.source_range
75
+
76
+ processed_source.comments.any? do |comment|
77
+ range = comment.source_range
78
+
79
+ call_range.contains?(range) && !string_range.contains?(range)
80
+ end
81
+ end
59
82
  end
60
83
  end
61
84
  end
@@ -23,14 +23,20 @@ module RuboCop
23
23
  # foo = bar unless condition
24
24
  #
25
25
  class RedundantSelfAssignmentBranch < Base
26
+ include RescueNode
26
27
  extend AutoCorrector
27
28
 
28
29
  MSG = 'Remove the self-assignment branch.'
29
30
 
31
+ STATEMENT_BODY_TYPES = %i[
32
+ kwbegin def defs block numblock itblock class module sclass rescue ensure
33
+ ].freeze
34
+
30
35
  def on_lvasgn(node)
31
36
  expression = node.expression
32
37
 
33
38
  return unless use_if_and_else_branch?(expression)
39
+ return unless statement?(node)
34
40
 
35
41
  if_branch = expression.if_branch
36
42
  else_branch = expression.else_branch
@@ -51,6 +57,28 @@ module RuboCop
51
57
  !expression.ternary? || !expression.else?
52
58
  end
53
59
 
60
+ def statement?(node)
61
+ parent = node.parent
62
+ return true if parent.nil? || parent.type?(*STATEMENT_BODY_TYPES)
63
+ return parent.loc.begin.nil? if parent.begin_type?
64
+ return !rescue_modifier?(parent) if parent.resbody_type?
65
+
66
+ statement_branch?(node, parent)
67
+ end
68
+
69
+ def statement_branch?(node, parent)
70
+ case parent.type
71
+ when :if
72
+ !parent.ternary? && !node.equal?(parent.condition)
73
+ when :when, :in_pattern, :while, :until, :for
74
+ node.equal?(parent.body)
75
+ when :case, :case_match
76
+ node.equal?(parent.else_branch)
77
+ else
78
+ false
79
+ end
80
+ end
81
+
54
82
  def inconvertible_to_modifier?(if_branch, else_branch)
55
83
  multiple_statements?(if_branch) || multiple_statements?(else_branch) ||
56
84
  (else_branch.respond_to?(:elsif?) && else_branch.elsif?)
@@ -123,12 +123,29 @@ module RuboCop
123
123
  end
124
124
 
125
125
  def register_offense(node, sort_node, sorter, accessor)
126
+ return if code_after_accessor_kept_apart?(node)
127
+
126
128
  message = message(node, sorter, accessor)
127
129
  add_offense(offense_range(sort_node, node), message: message) do |corrector|
128
130
  autocorrect(corrector, node, sort_node, sorter, accessor)
129
131
  end
130
132
  end
131
133
 
134
+ def code_after_accessor_kept_apart?(node)
135
+ return false if with_logical_operator?(node)
136
+
137
+ source = before_accessor_source(node)
138
+ return false if !source.include?("\n") || source.strip.empty?
139
+
140
+ rest = code_after_accessor(node)
141
+
142
+ !rest.empty? && !rest.match?(/\A(#|&?\.|[)\]},])/)
143
+ end
144
+
145
+ def code_after_accessor(node)
146
+ processed_source.lines[node.last_line - 1][node.source_range.last_column..].strip
147
+ end
148
+
132
149
  def offense_range(sort_node, node)
133
150
  range_between(sort_node.loc.selector.begin_pos, node.source_range.end_pos)
134
151
  end
@@ -146,12 +163,29 @@ module RuboCop
146
163
  end
147
164
 
148
165
  def autocorrect(corrector, node, sort_node, sorter, accessor)
149
- # Remove accessor, e.g. `first` or `[-1]`.
150
- corrector.remove(range_between(accessor_start(node), node.source_range.end_pos))
151
166
  # Replace "sort" or "sort_by" with the appropriate min/max method.
152
167
  corrector.replace(sort_node.loc.selector, suggestion(sorter, accessor, arg_value(node)))
153
- # Replace to avoid syntax errors when followed by a logical operator.
154
- replace_with_logical_operator(corrector, node) if with_logical_operator?(node)
168
+ remove_accessor(corrector, node)
169
+ end
170
+
171
+ def remove_accessor(corrector, node)
172
+ if removable_line_break_before_accessor?(node)
173
+ corrector.remove(node.receiver.source_range.end.join(node.source_range.end))
174
+ else
175
+ corrector.remove(range_between(accessor_start(node), node.source_range.end_pos))
176
+
177
+ replace_with_logical_operator(corrector, node) if with_logical_operator?(node)
178
+ end
179
+ end
180
+
181
+ def removable_line_break_before_accessor?(node)
182
+ source = before_accessor_source(node)
183
+
184
+ source.include?("\n") && source.strip.empty?
185
+ end
186
+
187
+ def before_accessor_source(node)
188
+ node.receiver.source_range.end.join((node.loc.dot || node.loc.selector).begin).source
155
189
  end
156
190
 
157
191
  def replace_with_logical_operator(corrector, node)