rubocop 1.92.0 → 1.92.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 (104) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +1 -0
  3. data/lib/rubocop/config.rb +0 -4
  4. data/lib/rubocop/config_obsoletion.rb +9 -0
  5. data/lib/rubocop/cop/badge.rb +1 -1
  6. data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +16 -1
  7. data/lib/rubocop/cop/correctors/empty_line_corrector.rb +1 -1
  8. data/lib/rubocop/cop/correctors/lambda_literal_to_method_corrector.rb +2 -2
  9. data/lib/rubocop/cop/correctors/multiline_literal_brace_corrector.rb +34 -0
  10. data/lib/rubocop/cop/layout/argument_alignment.rb +5 -1
  11. data/lib/rubocop/cop/layout/class_structure.rb +21 -7
  12. data/lib/rubocop/cop/layout/dot_position.rb +25 -15
  13. data/lib/rubocop/cop/layout/empty_line_after_multiline_condition.rb +11 -7
  14. data/lib/rubocop/cop/layout/empty_lines_after_module_inclusion.rb +1 -1
  15. data/lib/rubocop/cop/layout/empty_lines_around_block_body.rb +9 -3
  16. data/lib/rubocop/cop/layout/end_alignment.rb +5 -15
  17. data/lib/rubocop/cop/layout/extra_spacing.rb +18 -7
  18. data/lib/rubocop/cop/layout/first_argument_indentation.rb +26 -1
  19. data/lib/rubocop/cop/layout/first_array_element_line_break.rb +15 -0
  20. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +11 -5
  21. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +5 -3
  22. data/lib/rubocop/cop/layout/indentation_consistency.rb +22 -5
  23. data/lib/rubocop/cop/layout/line_length.rb +127 -11
  24. data/lib/rubocop/cop/layout/multiline_hash_brace_layout.rb +6 -4
  25. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +16 -5
  26. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +1 -1
  27. data/lib/rubocop/cop/layout/single_line_block_chain.rb +3 -3
  28. data/lib/rubocop/cop/layout/space_around_operators.rb +31 -4
  29. data/lib/rubocop/cop/layout/space_before_block_braces.rb +12 -0
  30. data/lib/rubocop/cop/layout/space_inside_block_braces.rb +1 -1
  31. data/lib/rubocop/cop/layout/space_inside_range_literal.rb +29 -11
  32. data/lib/rubocop/cop/layout/space_inside_reference_brackets.rb +2 -12
  33. data/lib/rubocop/cop/lint/ambiguous_regexp_literal.rb +6 -1
  34. data/lib/rubocop/cop/lint/heredoc_method_call_position.rb +4 -0
  35. data/lib/rubocop/cop/lint/interpolation_check.rb +20 -17
  36. data/lib/rubocop/cop/lint/nested_percent_literal.rb +3 -0
  37. data/lib/rubocop/cop/lint/number_conversion.rb +2 -0
  38. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +22 -1
  39. data/lib/rubocop/cop/lint/percent_string_array.rb +75 -9
  40. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +27 -8
  41. data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +11 -4
  42. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +47 -1
  43. data/lib/rubocop/cop/lint/symbol_conversion.rb +1 -0
  44. data/lib/rubocop/cop/lint/useless_setter_call.rb +7 -4
  45. data/lib/rubocop/cop/mixin/check_line_breakable.rb +7 -6
  46. data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +3 -1
  47. data/lib/rubocop/cop/mixin/comments_help.rb +1 -1
  48. data/lib/rubocop/cop/mixin/endless_method_body_help.rb +36 -0
  49. data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +26 -5
  50. data/lib/rubocop/cop/mixin/statement_modifier.rb +17 -3
  51. data/lib/rubocop/cop/mixin/trailing_comma.rb +1 -1
  52. data/lib/rubocop/cop/mixin/visibility_help.rb +2 -1
  53. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +1 -1
  54. data/lib/rubocop/cop/style/access_modifier_declarations.rb +30 -2
  55. data/lib/rubocop/cop/style/and_or.rb +34 -11
  56. data/lib/rubocop/cop/style/arguments_forwarding.rb +28 -2
  57. data/lib/rubocop/cop/style/block_delimiters.rb +6 -0
  58. data/lib/rubocop/cop/style/case_equality.rb +14 -3
  59. data/lib/rubocop/cop/style/class_and_module_children.rb +6 -3
  60. data/lib/rubocop/cop/style/class_methods_definitions.rb +7 -0
  61. data/lib/rubocop/cop/style/collection_querying.rb +79 -46
  62. data/lib/rubocop/cop/style/combinable_loops.rb +4 -0
  63. data/lib/rubocop/cop/style/concat_array_literals.rb +13 -1
  64. data/lib/rubocop/cop/style/empty_string_inside_interpolation.rb +53 -11
  65. data/lib/rubocop/cop/style/endless_method.rb +38 -6
  66. data/lib/rubocop/cop/style/float_division.rb +16 -1
  67. data/lib/rubocop/cop/style/for.rb +14 -1
  68. data/lib/rubocop/cop/style/guard_clause.rb +22 -21
  69. data/lib/rubocop/cop/style/hash_as_last_array_item.rb +4 -0
  70. data/lib/rubocop/cop/style/hash_lookup_method.rb +10 -0
  71. data/lib/rubocop/cop/style/if_unless_modifier.rb +19 -12
  72. data/lib/rubocop/cop/style/it_block_parameter.rb +63 -2
  73. data/lib/rubocop/cop/style/lambda.rb +16 -1
  74. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +36 -4
  75. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +1 -0
  76. data/lib/rubocop/cop/style/method_def_parentheses.rb +9 -1
  77. data/lib/rubocop/cop/style/mixin_grouping.rb +12 -3
  78. data/lib/rubocop/cop/style/mutable_constant.rb +25 -7
  79. data/lib/rubocop/cop/style/numeric_predicate.rb +7 -0
  80. data/lib/rubocop/cop/style/parentheses_around_condition.rb +19 -1
  81. data/lib/rubocop/cop/style/quoted_symbols.rb +5 -7
  82. data/lib/rubocop/cop/style/raise_args.rb +80 -11
  83. data/lib/rubocop/cop/style/reduce_to_hash.rb +32 -34
  84. data/lib/rubocop/cop/style/redundant_begin.rb +26 -3
  85. data/lib/rubocop/cop/style/redundant_exception.rb +13 -9
  86. data/lib/rubocop/cop/style/redundant_freeze.rb +1 -2
  87. data/lib/rubocop/cop/style/redundant_interpolation_unfreeze.rb +24 -1
  88. data/lib/rubocop/cop/style/redundant_self.rb +9 -1
  89. data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +28 -0
  90. data/lib/rubocop/cop/style/redundant_sort.rb +38 -4
  91. data/lib/rubocop/cop/style/require_order.rb +16 -8
  92. data/lib/rubocop/cop/style/rescue_modifier.rb +6 -0
  93. data/lib/rubocop/cop/style/send_with_literal_method_name.rb +4 -0
  94. data/lib/rubocop/cop/style/single_line_methods.rb +26 -0
  95. data/lib/rubocop/cop/style/string_concatenation.rb +12 -1
  96. data/lib/rubocop/cop/style/struct_inheritance.rb +6 -0
  97. data/lib/rubocop/cop/style/symbol_proc.rb +1 -1
  98. data/lib/rubocop/lsp/routes.rb +23 -23
  99. data/lib/rubocop/plugin/configuration_integrator.rb +24 -3
  100. data/lib/rubocop/runner.rb +1 -1
  101. data/lib/rubocop/server/cache.rb +4 -4
  102. data/lib/rubocop/target_ruby.rb +21 -9
  103. data/lib/rubocop/version.rb +1 -1
  104. metadata +2 -2
@@ -74,7 +74,7 @@ module RuboCop
74
74
 
75
75
  arguments = [exception_node, message_node].compact.map(&:source).join(', ')
76
76
 
77
- if node.parent && requires_parens?(node.parent)
77
+ if parenthesize?(node)
78
78
  "#{node.method_name}(#{arguments})"
79
79
  else
80
80
  "#{node.method_name} #{arguments}"
@@ -86,31 +86,60 @@ module RuboCop
86
86
  return if message_nodes.size > 1
87
87
 
88
88
  argument = message_nodes.first.source
89
- exception_class = exception_node.receiver&.source || exception_node.source
89
+ exception_class = exception_class_node(exception_node).source
90
90
 
91
- if node.parent && requires_parens?(node.parent)
91
+ if parenthesize?(node)
92
92
  "#{node.method_name}(#{exception_class}.new(#{argument}))"
93
93
  else
94
94
  "#{node.method_name} #{exception_class}.new(#{argument})"
95
95
  end
96
96
  end
97
97
 
98
+ def exception_class_node(exception_node)
99
+ use_new_method?(exception_node) ? exception_node.receiver : exception_node
100
+ end
101
+
98
102
  def check_compact(node)
99
103
  if node.arguments.size > 1
100
104
  exception = node.first_argument
101
105
  return if exception.send_type? && exception.first_argument&.hash_type?
106
+ return if use_new_method?(exception) && exception.arguments?
102
107
 
103
108
  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
109
+ correct_raise(node) { correct_to_compact(corrector, node) }
108
110
  end
109
111
  else
110
112
  correct_style_detected
111
113
  end
112
114
  end
113
115
 
116
+ def correct_to_compact(corrector, node)
117
+ if heredoc_message?(node)
118
+ correct_heredoc_exploded_to_compact(corrector, node)
119
+ else
120
+ corrector.replace(node, correction_exploded_to_compact(node))
121
+ end
122
+ end
123
+
124
+ def heredoc_message?(node)
125
+ node.parenthesized? && node.arguments.size == 2 && heredoc?(node.last_argument)
126
+ end
127
+
128
+ def heredoc?(node)
129
+ node.each_node(:any_str).any?(&:heredoc?)
130
+ end
131
+
132
+ def correct_heredoc_exploded_to_compact(corrector, node)
133
+ exception_node, message_node = *node.arguments
134
+ exception_class = exception_class_node(exception_node)
135
+ corrector.replace(range_between(exception_class, message_node), '.new(')
136
+ corrector.insert_after(message_node, ')')
137
+ end
138
+
139
+ def range_between(left_node, right_node)
140
+ left_node.source_range.end.join(right_node.source_range.begin)
141
+ end
142
+
114
143
  def check_exploded(node)
115
144
  return correct_style_detected unless node.arguments.one?
116
145
 
@@ -121,13 +150,43 @@ module RuboCop
121
150
  return if allowed_non_exploded_type?(first_arg)
122
151
 
123
152
  add_offense(node, message: format(EXPLODED_MSG, method: node.method_name)) do |corrector|
124
- replacement = correction_compact_to_exploded(node)
153
+ correct_raise(node) { correct_to_exploded(corrector, node) }
154
+ end
155
+ end
125
156
 
126
- corrector.replace(node, replacement)
127
- opposite_style_detected
157
+ def correct_to_exploded(corrector, node)
158
+ if heredoc_new_argument?(node)
159
+ correct_heredoc_compact_to_exploded(corrector, node)
160
+ else
161
+ corrector.replace(node, correction_compact_to_exploded(node))
128
162
  end
129
163
  end
130
164
 
165
+ def heredoc_new_argument?(node)
166
+ new_call = node.first_argument
167
+ return false unless new_call.arguments.one? && heredoc?(new_call.first_argument)
168
+
169
+ node.parenthesized? || !parenthesize?(node)
170
+ end
171
+
172
+ def correct_heredoc_compact_to_exploded(corrector, node)
173
+ new_call = node.first_argument
174
+ corrector.replace(range_between(new_call.receiver, new_call.first_argument), ', ')
175
+ corrector.remove(new_call.loc.end) if new_call.loc.end
176
+ end
177
+
178
+ def correct_raise(node)
179
+ opposite_style_detected
180
+ return if inside_corrected_raise?(node)
181
+
182
+ yield
183
+ ignore_node(node)
184
+ end
185
+
186
+ def inside_corrected_raise?(node)
187
+ node.each_ancestor(:send).any? { |ancestor| ignored_node?(ancestor) }
188
+ end
189
+
131
190
  def use_new_method?(first_arg)
132
191
  first_arg.send_type? && first_arg.receiver && first_arg.method?(:new)
133
192
  end
@@ -151,8 +210,18 @@ module RuboCop
151
210
  Array(cop_config['AllowedCompactTypes']).include?(type)
152
211
  end
153
212
 
213
+ def parenthesize?(node)
214
+ return true if node.parenthesized?
215
+
216
+ node.parent && requires_parens?(node.parent)
217
+ end
218
+
154
219
  def requires_parens?(parent)
155
- parent.operator_keyword? || (parent.if_type? && parent.ternary?)
220
+ return true if parent.operator_keyword?
221
+ return true if parent.type?(:call, :super, :yield, :array, :pair)
222
+ return true if parent.any_def_type? && parent.endless?
223
+
224
+ parent.if_type? && parent.ternary?
156
225
  end
157
226
  end
158
227
  end
@@ -100,6 +100,7 @@ module RuboCop
100
100
  return unless key
101
101
  return if accumulator_used_in_expressions?(block_node, key, value)
102
102
  return if nested_match?(key) || nested_match?(value)
103
+ return if heredoc_in_inject_numblock?(block_node, key, value)
103
104
 
104
105
  register_offense(node, block_node, key, value)
105
106
  end
@@ -124,6 +125,12 @@ module RuboCop
124
125
  end
125
126
  end
126
127
 
128
+ def heredoc_in_inject_numblock?(block_node, key, value)
129
+ return false if !block_node.numblock_type? || block_node.method?(:each_with_object)
130
+
131
+ [key, value].any? { |node| node.each_node(:any_str).any?(&:heredoc?) }
132
+ end
133
+
127
134
  def accumulator_name(block_node)
128
135
  index = block_node.method?(:each_with_object) ? 1 : 0
129
136
  block_node.argument_list[index].name
@@ -136,42 +143,20 @@ module RuboCop
136
143
  def register_offense(send_node, block_node, key_expr, value_expr)
137
144
  message = format(MSG, method: send_node.method_name)
138
145
 
146
+ # Each part is replaced separately so that heredoc bodies between them are kept.
139
147
  add_offense(send_node.loc.selector, message: message) do |corrector|
140
- corrector.replace(
141
- replacement_range(send_node, block_node),
142
- replacement(block_node, key_expr, value_expr)
143
- )
144
- end
145
- end
148
+ corrector.replace(method_call_range(send_node), 'to_h')
146
149
 
147
- def replacement(block_node, key_expr, value_expr)
148
- key_source = adjusted_source(key_expr, block_node)
149
- value_source = adjusted_source(value_expr, block_node)
150
- body = "[#{key_source}, #{value_source}]"
151
-
152
- if block_node.numblock_type?
153
- block_node.braces? ? "to_h { #{body} }" : do_end_replacement(block_node, body)
154
- else
155
- named_block_replacement(block_node, body)
156
- end
157
- end
150
+ unless block_node.numblock_type?
151
+ corrector.replace(block_node.arguments, "|#{element_arg_source(block_node)}|")
152
+ end
158
153
 
159
- def named_block_replacement(block_node, body)
160
- arg = element_arg_source(block_node)
161
- if block_node.braces?
162
- "to_h { |#{arg}| #{body} }"
163
- else
164
- do_end_replacement(block_node, body, arg)
154
+ correct_body(corrector, block_node, key_expr, value_expr)
165
155
  end
166
156
  end
167
157
 
168
- def do_end_replacement(block_node, body, arg = nil)
169
- args = arg ? " |#{arg}|" : ''
170
- "to_h do#{args}\n#{indent(block_node)} #{body}\n#{indent(block_node)}end"
171
- end
172
-
173
- def replacement_range(send_node, block_node)
174
- range_between(send_node.loc.selector.begin_pos, block_node.source_range.end_pos)
158
+ def method_call_range(send_node)
159
+ range_between(send_node.loc.selector.begin_pos, send_node.source_range.end_pos)
175
160
  end
176
161
 
177
162
  def element_arg_source(block_node)
@@ -182,6 +167,16 @@ module RuboCop
182
167
  end
183
168
  end
184
169
 
170
+ def correct_body(corrector, block_node, key_expr, value_expr)
171
+ key_source = adjusted_source(key_expr, block_node)
172
+ value_source = adjusted_source(value_expr, block_node)
173
+ body = block_node.body
174
+ assignment = body.begin_type? ? body.children.first : body
175
+
176
+ corrector.replace(assignment, "[#{key_source}, #{value_source}]")
177
+ corrector.remove(accumulator_range(body.children.last)) if body.begin_type?
178
+ end
179
+
185
180
  def adjusted_source(expr_node, block_node)
186
181
  source = element_source(expr_node)
187
182
  return source unless block_node.numblock_type?
@@ -191,10 +186,6 @@ module RuboCop
191
186
  source.gsub('_2', '_1')
192
187
  end
193
188
 
194
- def indent(node)
195
- ' ' * node.source_range.column
196
- end
197
-
198
189
  def element_source(expr_node)
199
190
  if command_call?(expr_node) || expr_node.assignment? || expr_node.rescue_type?
200
191
  return "(#{expr_node.source})"
@@ -210,6 +201,13 @@ module RuboCop
210
201
 
211
202
  node.arguments? && !node.parenthesized? && !node.operator_method? && !node.setter_method?
212
203
  end
204
+
205
+ def accumulator_range(accumulator)
206
+ range = range_with_surrounding_space(accumulator.source_range, side: :left)
207
+ return range unless range.source_buffer.source[range.begin_pos - 1] == ';'
208
+
209
+ range_with_surrounding_space(range.adjust(begin_pos: -1), side: :left, newlines: false)
210
+ end
213
211
  end
214
212
  end
215
213
  end
@@ -67,6 +67,7 @@ module RuboCop
67
67
  extend AutoCorrector
68
68
 
69
69
  MSG = 'Redundant `begin` block detected.'
70
+ SPACE_INSIDE_PARENS_STYLES = %w[space compact].freeze
70
71
 
71
72
  def self.autocorrect_incompatible_with
72
73
  [Style::BlockDelimiters]
@@ -132,7 +133,8 @@ module RuboCop
132
133
  empty_begin?(node) ||
133
134
  begin_block_has_multiline_statements?(node) ||
134
135
  contain_rescue_or_ensure?(node) ||
135
- valid_context_using_only_begin?(node)
136
+ valid_context_using_only_begin?(node) ||
137
+ ternary_operand_requiring_begin?(node)
136
138
  end
137
139
 
138
140
  def register_offense(node)
@@ -191,7 +193,8 @@ module RuboCop
191
193
  def use_modifier_form_after_multiline_begin_block?(node)
192
194
  return false unless (parent = node.parent)
193
195
 
194
- node.multiline? && parent.if_type? && parent.modifier_form?
196
+ node.multiline? && parent.if_type? && parent.modifier_form? &&
197
+ !parent.condition.equal?(node)
195
198
  end
196
199
 
197
200
  def correct_modifier_form_after_multiline_begin_block(corrector, node)
@@ -228,6 +231,12 @@ module RuboCop
228
231
  parent&.send_type? || parent&.operator_keyword?
229
232
  end
230
233
 
234
+ def ternary_operand_requiring_begin?(node)
235
+ return false unless node.parent&.if_type? && node.parent.ternary?
236
+
237
+ !node.children.one? || low_precedence_statement?(node.children.first)
238
+ end
239
+
231
240
  def valid_begin_assignment?(node)
232
241
  node.parent&.assignment? && (!node.children.one? || too_long_when_joined?(node))
233
242
  end
@@ -238,14 +247,28 @@ module RuboCop
238
247
  return false unless max && statement.single_line?
239
248
 
240
249
  length = statement.source.length
241
- length += 2 if low_precedence_statement?(statement)
250
+ length += parentheses_width if low_precedence_statement?(statement)
251
+
242
252
  node.loc.column + length > max
243
253
  end
244
254
 
255
+ def parentheses_width
256
+ space_inside_parens? ? 4 : 2
257
+ end
258
+
259
+ def space_inside_parens?
260
+ return false unless config.cop_enabled?('Layout/SpaceInsideParens')
261
+
262
+ style = config.for_cop('Layout/SpaceInsideParens')['EnforcedStyle']
263
+
264
+ SPACE_INSIDE_PARENS_STYLES.include?(style)
265
+ end
266
+
245
267
  def inspect_branches(node)
246
268
  node.branches.each do |branch|
247
269
  next unless branch&.kwbegin_type?
248
270
  next if branch.rescue_node || branch.ensure_node
271
+ next if ternary_operand_requiring_begin?(branch)
249
272
 
250
273
  register_offense(branch)
251
274
  end
@@ -37,23 +37,18 @@ module RuboCop
37
37
  private
38
38
 
39
39
  def fix_exploded(node)
40
- exploded?(node) do |command, message|
40
+ exploded?(node) do |exception, message|
41
41
  # `raise RuntimeError, nil` uses the class name as the message, so
42
42
  # rewriting it to `raise nil.to_s` (an empty message) would change it.
43
43
  next if message.nil_type?
44
44
 
45
45
  add_offense(node, message: MSG_1) do |corrector|
46
- corrector.replace(node, replaced_exploded(node, command, message))
46
+ corrector.remove(exception.source_range.join(message.source_range.begin))
47
+ corrector.replace(message, message_source(message))
47
48
  end
48
49
  end
49
50
  end
50
51
 
51
- def replaced_exploded(node, command, message)
52
- arg = message_source(message)
53
- arg = node.parenthesized? ? "(#{arg})" : " #{arg}"
54
- "#{command}#{arg}"
55
- end
56
-
57
52
  def string_message?(message)
58
53
  message.any_str_type?
59
54
  end
@@ -63,11 +58,20 @@ module RuboCop
63
58
  next if message.nil_type?
64
59
 
65
60
  add_offense(node, message: MSG_2) do |corrector|
61
+ # Replacing the call would drop a heredoc body that sits inside its parentheses.
62
+ next if heredoc_body_inside?(new_call, message)
63
+
66
64
  corrector.replace(new_call, message_source(message))
67
65
  end
68
66
  end
69
67
  end
70
68
 
69
+ def heredoc_body_inside?(new_call, message)
70
+ message.each_node(:any_str).any? do |str|
71
+ str.heredoc? && str.loc.heredoc_end.end_pos < new_call.source_range.end_pos
72
+ end
73
+ end
74
+
71
75
  def message_source(message)
72
76
  if string_message?(message)
73
77
  message.source
@@ -90,7 +94,7 @@ module RuboCop
90
94
 
91
95
  # @!method exploded?(node)
92
96
  def_node_matcher :exploded?, <<~PATTERN
93
- (send nil? ${:raise :fail} (const {nil? cbase} :RuntimeError) $_)
97
+ (send nil? {:raise :fail} $(const {nil? cbase} :RuntimeError) $_)
94
98
  PATTERN
95
99
 
96
100
  # @!method compact?(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
@@ -115,7 +115,7 @@ module RuboCop
115
115
 
116
116
  def on_send(node)
117
117
  return unless node.self_receiver? && regular_method_call?(node)
118
- return if node.parent&.mlhs_type?
118
+ return if multiple_assignment_target?(node)
119
119
  return if allowed_send_node?(node)
120
120
  return if it_method_in_block?(node)
121
121
 
@@ -154,6 +154,14 @@ module RuboCop
154
154
  end
155
155
  end
156
156
 
157
+ def multiple_assignment_target?(node)
158
+ return false unless (parent = node.parent)
159
+
160
+ parent = parent.parent if parent.splat_type?
161
+
162
+ parent.mlhs_type?
163
+ end
164
+
157
165
  def allowed_send_node?(node)
158
166
  @allowed_send_nodes.include?(node) ||
159
167
  @local_variables_scopes[node].include?(node.method_name) ||
@@ -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)
@@ -101,17 +101,19 @@ module RuboCop
101
101
  end
102
102
 
103
103
  def find_previous_older_sibling(node) # rubocop:disable Metrics
104
- search_node(node).left_siblings.reverse.find do |sibling|
104
+ oldest = nil
105
+ search_node(node).left_siblings.reverse_each do |sibling|
105
106
  next unless sibling.is_a?(AST::Node)
106
107
 
107
- sibling = sibling_node(sibling)
108
- break unless sibling&.send_type? && sibling.method?(node.method_name)
109
- break unless sibling.arguments? && !sibling.receiver
110
- break unless in_same_section?(sibling, node)
111
- break unless node.first_argument.str_type? && sibling.first_argument.str_type?
108
+ required = sibling_node(sibling)
109
+ break unless required&.send_type? && required.method?(node.method_name)
110
+ break unless required.arguments? && !required.receiver
111
+ break unless in_same_section?(required, node)
112
+ break unless node.first_argument.str_type? && required.first_argument.str_type?
112
113
 
113
- node.first_argument.value < sibling.first_argument.value
114
+ oldest = sibling if node.first_argument.value < required.first_argument.value
114
115
  end
116
+ oldest
115
117
  end
116
118
 
117
119
  def own_lines?(node)
@@ -124,9 +126,15 @@ module RuboCop
124
126
  def autocorrect(corrector, node, previous_older_sibling)
125
127
  range1 = range_with_comments_and_lines(previous_older_sibling)
126
128
  range2 = range_with_comments_and_lines(node.parent.if_type? ? node.parent : node)
129
+ source = range2.source
130
+
131
+ unless source.end_with?("\n")
132
+ source += "\n"
133
+ range2 = range2.with(begin_pos: range2.begin_pos - 1)
134
+ end
127
135
 
128
136
  corrector.remove(range2)
129
- corrector.insert_before(range1, range2.source)
137
+ corrector.insert_before(range1.begin, source)
130
138
  end
131
139
 
132
140
  def search_node(node)
@@ -56,16 +56,22 @@ module RuboCop
56
56
  add_offense(rescue_node) do |corrector|
57
57
  # The heredoc body would be left behind when the rescue body is moved.
58
58
  next if heredoc_end(node)
59
+ next if inside_corrected_rescue_body?(rescue_node)
59
60
 
60
61
  parenthesized = parenthesized?(rescue_node)
61
62
 
62
63
  correct_rescue_block(corrector, rescue_node, parenthesized)
63
64
  ParenthesesCorrector.correct(corrector, rescue_node.parent) if parenthesized
65
+ ignore_node(rescue_node)
64
66
  end
65
67
  end
66
68
 
67
69
  private
68
70
 
71
+ def inside_corrected_rescue_body?(node)
72
+ node.each_ancestor(:resbody).any? { |resbody| ignored_node?(resbody.parent) }
73
+ end
74
+
69
75
  def parenthesized?(node)
70
76
  node.parent && parentheses?(node.parent)
71
77
  end
@@ -64,6 +64,10 @@ module RuboCop
64
64
  undef unless until when while yield
65
65
  ].freeze
66
66
 
67
+ def self.autocorrect_incompatible_with
68
+ [Layout::MultilineMethodArgumentLineBreaks]
69
+ end
70
+
67
71
  # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
68
72
  def on_send(node)
69
73
  return if allow_send? && !node.method?(:public_send)