rubocop 1.79.2 → 1.82.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 (153) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +35 -7
  4. data/config/obsoletion.yml +4 -0
  5. data/exe/rubocop +1 -8
  6. data/lib/rubocop/cli/command/auto_generate_config.rb +2 -2
  7. data/lib/rubocop/cli.rb +8 -3
  8. data/lib/rubocop/comment_config.rb +62 -17
  9. data/lib/rubocop/config_loader.rb +5 -2
  10. data/lib/rubocop/config_loader_resolver.rb +7 -6
  11. data/lib/rubocop/config_store.rb +5 -0
  12. data/lib/rubocop/cop/autocorrect_logic.rb +8 -4
  13. data/lib/rubocop/cop/bundler/ordered_gems.rb +1 -2
  14. data/lib/rubocop/cop/correctors/alignment_corrector.rb +8 -7
  15. data/lib/rubocop/cop/correctors/for_to_each_corrector.rb +7 -2
  16. data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +1 -2
  17. data/lib/rubocop/cop/gemspec/ruby_version_globals_usage.rb +10 -5
  18. data/lib/rubocop/cop/internal_affairs/location_exists.rb +28 -2
  19. data/lib/rubocop/cop/internal_affairs/node_pattern_groups/ast_processor.rb +1 -1
  20. data/lib/rubocop/cop/internal_affairs/node_pattern_groups.rb +3 -1
  21. data/lib/rubocop/cop/internal_affairs/on_send_without_on_csend.rb +1 -1
  22. data/lib/rubocop/cop/layout/class_structure.rb +1 -1
  23. data/lib/rubocop/cop/layout/dot_position.rb +1 -1
  24. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +3 -0
  25. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +30 -12
  26. data/lib/rubocop/cop/layout/empty_lines_after_module_inclusion.rb +1 -1
  27. data/lib/rubocop/cop/layout/end_alignment.rb +4 -0
  28. data/lib/rubocop/cop/layout/hash_alignment.rb +2 -5
  29. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +2 -2
  30. data/lib/rubocop/cop/layout/heredoc_indentation.rb +1 -4
  31. data/lib/rubocop/cop/layout/indentation_style.rb +1 -1
  32. data/lib/rubocop/cop/layout/indentation_width.rb +12 -1
  33. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +1 -1
  34. data/lib/rubocop/cop/layout/line_length.rb +17 -5
  35. data/lib/rubocop/cop/layout/multiline_block_layout.rb +2 -0
  36. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +5 -1
  37. data/lib/rubocop/cop/layout/multiline_operation_indentation.rb +8 -4
  38. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +13 -3
  39. data/lib/rubocop/cop/layout/space_after_comma.rb +2 -10
  40. data/lib/rubocop/cop/layout/space_after_semicolon.rb +1 -1
  41. data/lib/rubocop/cop/layout/space_around_keyword.rb +1 -1
  42. data/lib/rubocop/cop/layout/trailing_whitespace.rb +1 -1
  43. data/lib/rubocop/cop/lint/circular_argument_reference.rb +47 -3
  44. data/lib/rubocop/cop/lint/constant_overwritten_in_rescue.rb +3 -2
  45. data/lib/rubocop/cop/lint/cop_directive_syntax.rb +14 -8
  46. data/lib/rubocop/cop/lint/debugger.rb +0 -2
  47. data/lib/rubocop/cop/lint/deprecated_open_ssl_constant.rb +4 -1
  48. data/lib/rubocop/cop/lint/duplicate_match_pattern.rb +4 -4
  49. data/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb +5 -42
  50. data/lib/rubocop/cop/lint/else_layout.rb +19 -0
  51. data/lib/rubocop/cop/lint/empty_interpolation.rb +11 -0
  52. data/lib/rubocop/cop/lint/literal_as_condition.rb +4 -0
  53. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +1 -1
  54. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +17 -8
  55. data/lib/rubocop/cop/lint/no_return_in_begin_end_blocks.rb +4 -0
  56. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +23 -9
  57. data/lib/rubocop/cop/lint/redundant_require_statement.rb +4 -2
  58. data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +7 -1
  59. data/lib/rubocop/cop/lint/rescue_exception.rb +1 -4
  60. data/lib/rubocop/cop/lint/self_assignment.rb +15 -6
  61. data/lib/rubocop/cop/lint/shadowed_argument.rb +7 -7
  62. data/lib/rubocop/cop/lint/unreachable_code.rb +5 -3
  63. data/lib/rubocop/cop/lint/uri_escape_unescape.rb +2 -0
  64. data/lib/rubocop/cop/lint/useless_assignment.rb +44 -16
  65. data/lib/rubocop/cop/lint/useless_or.rb +15 -2
  66. data/lib/rubocop/cop/lint/utils/nil_receiver_checker.rb +1 -1
  67. data/lib/rubocop/cop/lint/void.rb +7 -0
  68. data/lib/rubocop/cop/message_annotator.rb +1 -1
  69. data/lib/rubocop/cop/metrics/utils/abc_size_calculator.rb +4 -3
  70. data/lib/rubocop/cop/mixin/check_line_breakable.rb +1 -1
  71. data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +2 -4
  72. data/lib/rubocop/cop/mixin/code_length.rb +1 -1
  73. data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +1 -7
  74. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +1 -1
  75. data/lib/rubocop/cop/mixin/line_length_help.rb +21 -2
  76. data/lib/rubocop/cop/mixin/method_complexity.rb +1 -1
  77. data/lib/rubocop/cop/mixin/multiline_expression_indentation.rb +1 -1
  78. data/lib/rubocop/cop/mixin/multiline_literal_brace_layout.rb +1 -1
  79. data/lib/rubocop/cop/mixin/space_after_punctuation.rb +5 -4
  80. data/lib/rubocop/cop/mixin/statement_modifier.rb +0 -6
  81. data/lib/rubocop/cop/mixin/trailing_comma.rb +8 -5
  82. data/lib/rubocop/cop/naming/method_name.rb +5 -3
  83. data/lib/rubocop/cop/naming/predicate_method.rb +19 -6
  84. data/lib/rubocop/cop/security/json_load.rb +33 -11
  85. data/lib/rubocop/cop/style/array_intersect.rb +46 -12
  86. data/lib/rubocop/cop/style/array_intersect_with_single_element.rb +47 -0
  87. data/lib/rubocop/cop/style/bare_percent_literals.rb +1 -2
  88. data/lib/rubocop/cop/style/bitwise_predicate.rb +8 -1
  89. data/lib/rubocop/cop/style/case_equality.rb +11 -13
  90. data/lib/rubocop/cop/style/class_and_module_children.rb +1 -0
  91. data/lib/rubocop/cop/style/conditional_assignment.rb +8 -14
  92. data/lib/rubocop/cop/style/constant_visibility.rb +17 -12
  93. data/lib/rubocop/cop/style/double_negation.rb +1 -1
  94. data/lib/rubocop/cop/style/empty_method.rb +0 -6
  95. data/lib/rubocop/cop/style/endless_method.rb +15 -2
  96. data/lib/rubocop/cop/style/explicit_block_argument.rb +1 -1
  97. data/lib/rubocop/cop/style/float_division.rb +15 -1
  98. data/lib/rubocop/cop/style/guard_clause.rb +0 -11
  99. data/lib/rubocop/cop/style/hash_syntax.rb +1 -1
  100. data/lib/rubocop/cop/style/if_unless_modifier.rb +3 -3
  101. data/lib/rubocop/cop/style/infinite_loop.rb +1 -1
  102. data/lib/rubocop/cop/style/method_call_with_args_parentheses/require_parentheses.rb +12 -1
  103. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +17 -4
  104. data/lib/rubocop/cop/style/module_member_existence_check.rb +74 -0
  105. data/lib/rubocop/cop/style/multiline_method_signature.rb +2 -4
  106. data/lib/rubocop/cop/style/nil_comparison.rb +9 -7
  107. data/lib/rubocop/cop/style/one_line_conditional.rb +17 -9
  108. data/lib/rubocop/cop/style/operator_method_call.rb +11 -2
  109. data/lib/rubocop/cop/style/parallel_assignment.rb +2 -2
  110. data/lib/rubocop/cop/style/redundant_argument.rb +2 -0
  111. data/lib/rubocop/cop/style/redundant_begin.rb +34 -0
  112. data/lib/rubocop/cop/style/redundant_condition.rb +1 -1
  113. data/lib/rubocop/cop/style/redundant_exception.rb +1 -1
  114. data/lib/rubocop/cop/style/redundant_format.rb +26 -5
  115. data/lib/rubocop/cop/style/redundant_interpolation.rb +11 -2
  116. data/lib/rubocop/cop/style/redundant_parentheses.rb +14 -11
  117. data/lib/rubocop/cop/style/redundant_percent_q.rb +1 -2
  118. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +9 -0
  119. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +8 -0
  120. data/lib/rubocop/cop/style/redundant_sort.rb +7 -7
  121. data/lib/rubocop/cop/style/safe_navigation.rb +18 -1
  122. data/lib/rubocop/cop/style/semicolon.rb +23 -7
  123. data/lib/rubocop/cop/style/sole_nested_conditional.rb +8 -1
  124. data/lib/rubocop/cop/style/string_concatenation.rb +17 -13
  125. data/lib/rubocop/cop/style/super_arguments.rb +2 -2
  126. data/lib/rubocop/cop/style/symbol_array.rb +1 -1
  127. data/lib/rubocop/cop/style/trailing_comma_in_arguments.rb +45 -0
  128. data/lib/rubocop/cop/style/trailing_underscore_variable.rb +11 -11
  129. data/lib/rubocop/cop/style/unless_else.rb +10 -9
  130. data/lib/rubocop/cop/util.rb +2 -3
  131. data/lib/rubocop/cop/utils/format_string.rb +10 -0
  132. data/lib/rubocop/cop/variable_force/variable.rb +1 -1
  133. data/lib/rubocop/cop/variable_force.rb +9 -7
  134. data/lib/rubocop/cops_documentation_generator.rb +4 -4
  135. data/lib/rubocop/directive_comment.rb +46 -3
  136. data/lib/rubocop/formatter/disabled_config_formatter.rb +19 -5
  137. data/lib/rubocop/lsp/diagnostic.rb +10 -14
  138. data/lib/rubocop/lsp/routes.rb +31 -2
  139. data/lib/rubocop/lsp/stdin_runner.rb +0 -16
  140. data/lib/rubocop/magic_comment.rb +20 -0
  141. data/lib/rubocop/rake_task.rb +1 -1
  142. data/lib/rubocop/remote_config.rb +7 -8
  143. data/lib/rubocop/result_cache.rb +39 -28
  144. data/lib/rubocop/rspec/shared_contexts.rb +2 -2
  145. data/lib/rubocop/rspec/support.rb +1 -1
  146. data/lib/rubocop/runner.rb +10 -4
  147. data/lib/rubocop/target_finder.rb +9 -9
  148. data/lib/rubocop/target_ruby.rb +11 -2
  149. data/lib/rubocop/version.rb +1 -1
  150. data/lib/rubocop.rb +2 -0
  151. data/lib/ruby_lsp/rubocop/addon.rb +23 -8
  152. data/lib/ruby_lsp/rubocop/runtime_adapter.rb +49 -15
  153. metadata +9 -7
@@ -37,6 +37,7 @@ module RuboCop
37
37
  # array.sum(0)
38
38
  # exit(true)
39
39
  # exit!(false)
40
+ # string.to_i(10)
40
41
  # string.split(" ")
41
42
  # "first\nsecond".split(" ")
42
43
  # string.chomp("\n")
@@ -49,6 +50,7 @@ module RuboCop
49
50
  # array.sum
50
51
  # exit
51
52
  # exit!
53
+ # string.to_i
52
54
  # string.split
53
55
  # "first second".split
54
56
  # string.chomp
@@ -85,6 +85,29 @@ module RuboCop
85
85
  end
86
86
  alias on_defs on_def
87
87
 
88
+ def on_if(node)
89
+ return if node.modifier_form?
90
+
91
+ inspect_branches(node)
92
+ end
93
+
94
+ def on_case(node)
95
+ inspect_branches(node)
96
+ end
97
+ alias on_case_match on_case
98
+
99
+ def on_while(node)
100
+ return if node.modifier_form?
101
+
102
+ body = node.body
103
+
104
+ return unless body&.kwbegin_type?
105
+ return if body.rescue_node || body.ensure_node
106
+
107
+ register_offense(body)
108
+ end
109
+ alias on_until on_while
110
+
88
111
  def on_block(node)
89
112
  return if target_ruby_version < 2.5
90
113
  return if node.send_node.lambda_literal?
@@ -180,6 +203,8 @@ module RuboCop
180
203
  end
181
204
 
182
205
  def begin_block_has_multiline_statements?(node)
206
+ return false unless node.parent
207
+
183
208
  node.children.count >= 2
184
209
  end
185
210
 
@@ -199,6 +224,15 @@ module RuboCop
199
224
  def valid_begin_assignment?(node)
200
225
  node.parent&.assignment? && !node.children.one?
201
226
  end
227
+
228
+ def inspect_branches(node)
229
+ node.branches.each do |branch|
230
+ next unless branch&.kwbegin_type?
231
+ next if branch.rescue_node || branch.ensure_node
232
+
233
+ register_offense(branch)
234
+ end
235
+ end
202
236
  end
203
237
  end
204
238
  end
@@ -247,7 +247,7 @@ module RuboCop
247
247
  "#{if_branch.receiver.source} #{if_branch.method_name} (#{argument_source}"
248
248
  elsif if_branch.true_type?
249
249
  condition = if_branch.parent.condition
250
- return condition.source if condition.arguments.empty?
250
+ return condition.source if condition.arguments.empty? || condition.parenthesized?
251
251
 
252
252
  wrap_arguments_with_parens(condition)
253
253
  else
@@ -51,7 +51,7 @@ module RuboCop
51
51
  end
52
52
 
53
53
  def string_message?(message)
54
- message.type?(:str, :dstr, :xstr)
54
+ message.any_str_type?
55
55
  end
56
56
 
57
57
  def fix_compact(node)
@@ -89,7 +89,7 @@ module RuboCop
89
89
 
90
90
  def on_send(node)
91
91
  format_without_additional_args?(node) do |value|
92
- replacement = value.source
92
+ replacement = escape_control_chars(value.source)
93
93
 
94
94
  add_offense(node, message: message(node, replacement)) do |corrector|
95
95
  corrector.replace(node, replacement)
@@ -134,6 +134,7 @@ module RuboCop
134
134
  end
135
135
  end
136
136
 
137
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
137
138
  def all_fields_literal?(string, arguments)
138
139
  count = 0
139
140
  sequences = RuboCop::Cop::Utils::FormatString.new(string).format_sequences
@@ -141,29 +142,44 @@ module RuboCop
141
142
 
142
143
  sequences.each do |sequence|
143
144
  next if sequence.percent?
145
+ next if unknown_variable_width?(sequence, arguments)
144
146
 
145
147
  hash = arguments.detect(&:hash_type?)
146
148
  next unless (argument = find_argument(sequence, arguments, hash))
147
149
  next unless matching_argument?(sequence, argument)
150
+ next if (sequence.width || sequence.precision) && argument.dstr_type?
148
151
 
149
152
  count += 1
150
153
  end
151
154
 
152
155
  sequences.size == count
153
156
  end
157
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
154
158
 
159
+ # If the sequence has a variable (`*`) width, it cannot be autocorrected
160
+ # if the width is not given as a numeric literal argument
161
+ def unknown_variable_width?(sequence, arguments)
162
+ return false unless sequence.variable_width?
163
+
164
+ argument = arguments[sequence.variable_width_argument_number - 1]
165
+ !numeric?(argument)
166
+ end
167
+
168
+ # rubocop:disable Metrics/AbcSize
155
169
  def find_argument(sequence, arguments, hash)
156
170
  if hash && (sequence.annotated? || sequence.template?)
157
171
  find_hash_value_node(hash, sequence.name.to_sym).first
172
+ elsif sequence.variable_width?
173
+ # If the specifier contains `*`, the argument for the width can be ignored.
174
+ arguments.delete_at(sequence.variable_width_argument_number - 1)
175
+ arguments.shift
158
176
  elsif sequence.arg_number
159
177
  arguments[sequence.arg_number.to_i - 1]
160
178
  else
161
- # If the specifier contains `*`, the following arguments will be used
162
- # to specify the width and can be ignored.
163
- (sequence.arity - 1).times { arguments.shift }
164
179
  arguments.shift
165
180
  end
166
181
  end
182
+ # rubocop:enable Metrics/AbcSize
167
183
 
168
184
  def matching_argument?(sequence, argument)
169
185
  # Template specifiers don't give a type, any acceptable literal type is ok.
@@ -214,7 +230,12 @@ module RuboCop
214
230
  end
215
231
  end
216
232
 
217
- "#{start_delimiter}#{string}#{end_delimiter}"
233
+ "#{start_delimiter}#{escape_control_chars(string)}#{end_delimiter}"
234
+ end
235
+
236
+ # Escape any control characters in the string (eg. `\t` or `\n` become `\\t` or `\\n`)
237
+ def escape_control_chars(string)
238
+ string.gsub(/\p{Cc}/) { |s| s.dump[1..-2] }
218
239
  end
219
240
 
220
241
  def argument_values(arguments)
@@ -49,9 +49,10 @@ module RuboCop
49
49
  def on_dstr(node)
50
50
  return unless single_interpolation?(node)
51
51
 
52
- add_offense(node) do |corrector|
53
- embedded_node = node.children.first
52
+ embedded_node = node.children.first
53
+ return if use_match_pattern?(embedded_node)
54
54
 
55
+ add_offense(node) do |corrector|
55
56
  if variable_interpolation?(embedded_node)
56
57
  autocorrect_variable_interpolation(corrector, embedded_node, node)
57
58
  elsif single_variable_interpolation?(embedded_node)
@@ -71,6 +72,14 @@ module RuboCop
71
72
  !embedded_in_percent_array?(node)
72
73
  end
73
74
 
75
+ def use_match_pattern?(node)
76
+ return false if target_ruby_version <= 2.7
77
+
78
+ node.children.any? do |child|
79
+ child.respond_to?(:match_pattern_type?) && child.match_pattern_type?
80
+ end
81
+ end
82
+
74
83
  def single_variable_interpolation?(node)
75
84
  return false unless node.children.one?
76
85
 
@@ -24,9 +24,6 @@ module RuboCop
24
24
  (send `{(send _recv _msg) str array hash const #variable?} :[] ...)
25
25
  PATTERN
26
26
 
27
- # @!method method_node_and_args(node)
28
- def_node_matcher :method_node_and_args, '$(call _recv _msg $...)'
29
-
30
27
  # @!method rescue?(node)
31
28
  def_node_matcher :rescue?, '{^resbody ^^resbody}'
32
29
 
@@ -205,6 +202,7 @@ module RuboCop
205
202
  return false unless node.rescue_type?
206
203
  return false unless (parent = begin_node.parent)
207
204
  return false if parent.if_type? && parent.ternary?
205
+ return false if parent.conditional? && parent.condition == begin_node
208
206
 
209
207
  !parent.type?(:call, :array, :pair)
210
208
  end
@@ -220,7 +218,7 @@ module RuboCop
220
218
  end
221
219
 
222
220
  def call_node?(node)
223
- node.call_type? || (node.any_block_type? && !node.lambda_or_proc?)
221
+ node.call_type? || (node.any_block_type? && node.braces? && !node.lambda_or_proc?)
224
222
  end
225
223
 
226
224
  def check_send(begin_node, node)
@@ -228,7 +226,7 @@ module RuboCop
228
226
 
229
227
  return check_unary(begin_node, node) if node.unary_operation?
230
228
 
231
- return unless method_call_with_redundant_parentheses?(node)
229
+ return unless method_call_with_redundant_parentheses?(begin_node, node)
232
230
  return if call_chain_starts_with_int?(begin_node, node) ||
233
231
  do_end_block_in_method_chain?(begin_node, node)
234
232
 
@@ -239,8 +237,7 @@ module RuboCop
239
237
  return if begin_node.chained?
240
238
 
241
239
  node = node.children.first while suspect_unary?(node)
242
-
243
- return if node.send_type? && !method_call_with_redundant_parentheses?(node)
240
+ return unless method_call_with_redundant_parentheses?(begin_node, node)
244
241
 
245
242
  offense(begin_node, 'a unary operation')
246
243
  end
@@ -302,13 +299,19 @@ module RuboCop
302
299
  end
303
300
  end
304
301
 
305
- def method_call_with_redundant_parentheses?(node)
306
- return false unless node.call_type?
302
+ def method_call_with_redundant_parentheses?(begin_node, node)
303
+ return false unless node.type?(:call, :super, :yield, :defined?)
307
304
  return false if node.prefix_not?
305
+ return true if singular_parenthesized_parent?(begin_node)
306
+
307
+ node.arguments.empty? || parentheses?(node) || square_brackets?(node)
308
+ end
308
309
 
309
- send_node, args = method_node_and_args(node)
310
+ def singular_parenthesized_parent?(begin_node)
311
+ return true unless begin_node.parent
312
+ return false if begin_node.parent.type?(:splat, :kwsplat)
310
313
 
311
- args.empty? || parentheses?(send_node) || square_brackets?(send_node)
314
+ parentheses?(begin_node) && begin_node.parent.children.one?
312
315
  end
313
316
 
314
317
  def only_begin_arg?(args)
@@ -80,8 +80,7 @@ module RuboCop
80
80
  end
81
81
 
82
82
  def string_literal?(node)
83
- node.loc.respond_to?(:begin) && node.loc.respond_to?(:end) &&
84
- node.loc.begin && node.loc.end
83
+ node.loc?(:begin) && node.loc?(:end)
85
84
  end
86
85
 
87
86
  def start_with_percent_q_variant?(string)
@@ -66,6 +66,7 @@ module RuboCop
66
66
  DETERMINISTIC_REGEX.match?(regexp_node.source)
67
67
  end
68
68
 
69
+ # rubocop:disable Metrics/MethodLength
69
70
  def preferred_argument(regexp_node)
70
71
  new_argument = replacement(regexp_node)
71
72
 
@@ -73,6 +74,13 @@ module RuboCop
73
74
  new_argument.gsub!("'", "\\\\'")
74
75
  new_argument.gsub!('\"', '"')
75
76
  quote = "'"
77
+ elsif new_argument.include?("\\'")
78
+ # Add a backslash before single quotes preceded by an even number of backslashes.
79
+ # An even number (including zero) of backslashes before a quote means the quote itself
80
+ # is not escaped.
81
+ # Otherwise an odd number means the quote is already escaped so this doesn't touch it.
82
+ new_argument.gsub!(/(?<!\\)((?:\\\\)*)'/) { "#{::Regexp.last_match(1)}\\'" }
83
+ quote = "'"
76
84
  elsif new_argument.include?('\'')
77
85
  new_argument.gsub!("'", "\\\\'")
78
86
  quote = "'"
@@ -84,6 +92,7 @@ module RuboCop
84
92
 
85
93
  "#{quote}#{new_argument}#{quote}"
86
94
  end
95
+ # rubocop:enable Metrics/MethodLength
87
96
 
88
97
  def replacement(regexp_node)
89
98
  regexp_content = regexp_node.content
@@ -41,6 +41,7 @@ module RuboCop
41
41
  ALLOWED_ALWAYS_ESCAPES = " \n[]^\\#".chars.freeze
42
42
  ALLOWED_WITHIN_CHAR_CLASS_METACHAR_ESCAPES = '-'.chars.freeze
43
43
  ALLOWED_OUTSIDE_CHAR_CLASS_METACHAR_ESCAPES = '.*+?{}()|$'.chars.freeze
44
+ INTERPOLATION_SIGILS = %w[@ $].freeze
44
45
 
45
46
  def on_regexp(node)
46
47
  each_escape(node) do |char, index, within_character_class|
@@ -64,6 +65,7 @@ module RuboCop
64
65
  # different versions of Ruby so that e.g. /\i/ != /i/
65
66
  return true if /[[:alnum:]]/.match?(char)
66
67
  return true if ALLOWED_ALWAYS_ESCAPES.include?(char) || delimiter?(node, char)
68
+ return true if requires_escape_to_avoid_interpolation?(node.source[index], char)
67
69
 
68
70
  if within_character_class
69
71
  ALLOWED_WITHIN_CHAR_CLASS_METACHAR_ESCAPES.include?(char) &&
@@ -95,6 +97,12 @@ module RuboCop
95
97
  delimiters.include?(char)
96
98
  end
97
99
 
100
+ def requires_escape_to_avoid_interpolation?(char_before_escape, escaped_char)
101
+ # Preserve escapes after '#' that would otherwise trigger interpolation:
102
+ # '#@ivar', '#@@cvar', and '#$gvar'.
103
+ char_before_escape == '#' && INTERPOLATION_SIGILS.include?(escaped_char)
104
+ end
105
+
98
106
  def each_escape(node)
99
107
  node.parsed_tree&.traverse&.reduce(0) do |char_class_depth, (event, expr)|
100
108
  yield(expr.text[1], expr.ts, !char_class_depth.zero?) if expr.type == :escape
@@ -26,17 +26,17 @@ module RuboCop
26
26
  #
27
27
  # [source,ruby]
28
28
  # ----
29
- # class MyString < String; end
30
- # strings = [MyString.new('test'), 'test']
31
- # strings.sort.last.class #=> String
32
- # strings.max.class #=> MyString
29
+ # class MyString < String; end
30
+ # strings = [MyString.new('test'), 'test']
31
+ # strings.sort.last.class #=> String
32
+ # strings.max.class #=> MyString
33
33
  # ----
34
34
  #
35
35
  # [source,ruby]
36
36
  # ----
37
- # words = %w(dog horse mouse)
38
- # words.sort_by { |word| word.length }.last #=> 'mouse'
39
- # words.max_by { |word| word.length } #=> 'horse'
37
+ # words = %w(dog horse mouse)
38
+ # words.sort_by { |word| word.length }.last #=> 'mouse'
39
+ # words.max_by { |word| word.length } #=> 'horse'
40
40
  # ----
41
41
  #
42
42
  # @example
@@ -142,6 +142,7 @@ module RuboCop
142
142
  # @!method strip_begin(node)
143
143
  def_node_matcher :strip_begin, '{ (begin $!begin) $!(begin) }'
144
144
 
145
+ # rubocop:disable Metrics/AbcSize
145
146
  def on_if(node)
146
147
  return if allowed_if_condition?(node)
147
148
 
@@ -155,9 +156,11 @@ module RuboCop
155
156
  removal_ranges = [begin_range(node, body), end_range(node, body)]
156
157
 
157
158
  report_offense(node, method_chain, method_call, *removal_ranges) do |corrector|
159
+ corrector.replace(receiver, checked_variable.source) if checked_variable.csend_type?
158
160
  corrector.insert_before(method_call.loc.dot, '&') unless method_call.safe_navigation?
159
161
  end
160
162
  end
163
+ # rubocop:enable Metrics/AbcSize
161
164
 
162
165
  def on_and(node) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
163
166
  collect_and_clauses(node).each do |(lhs, lhs_operator_range), (rhs, _rhs_operator_range)|
@@ -259,8 +262,14 @@ module RuboCop
259
262
  end
260
263
 
261
264
  def dotless_operator_call?(method_call)
265
+ return true if dotless_operator_method?(method_call)
266
+
262
267
  method_call = method_call.parent while method_call.parent.send_type?
263
268
 
269
+ dotless_operator_method?(method_call)
270
+ end
271
+
272
+ def dotless_operator_method?(method_call)
264
273
  return false if method_call.loc.dot
265
274
 
266
275
  method_call.method?(:[]) || method_call.method?(:[]=) || method_call.operator_method?
@@ -335,8 +344,16 @@ module RuboCop
335
344
 
336
345
  def matching_call_nodes?(left, right)
337
346
  return false unless left && right.respond_to?(:call_type?)
347
+ return false unless left.call_type? && right.call_type?
348
+
349
+ # Compare receiver and method name, but ignore the difference between
350
+ # safe navigation method call (`&.`) and dot method call (`.`).
351
+ left_receiver, left_method, *left_args = left.children
352
+ right_receiver, right_method, *right_args = right.children
338
353
 
339
- left.call_type? && right.call_type? && left.children == right.children
354
+ left_method == right_method &&
355
+ matching_nodes?(left_receiver, right_receiver) &&
356
+ left_args == right_args
340
357
  end
341
358
 
342
359
  def chain_length(method_chain, method)
@@ -69,10 +69,11 @@ module RuboCop
69
69
 
70
70
  def each_semicolon
71
71
  tokens_for_lines.each do |line, tokens|
72
- semicolon_pos = semicolon_position(tokens)
72
+ next unless (semicolon_pos = semicolon_position(tokens))
73
+
73
74
  after_expr_pos = semicolon_pos == -1 ? -2 : semicolon_pos
74
75
 
75
- yield line, tokens[semicolon_pos].column, tokens[after_expr_pos] if semicolon_pos
76
+ yield line, tokens[semicolon_pos].column, tokens[after_expr_pos]
76
77
  end
77
78
  end
78
79
 
@@ -119,6 +120,7 @@ module RuboCop
119
120
  tokens[1]&.type == :tSTRING_DBEG && tokens[2]&.semicolon?
120
121
  end
121
122
 
123
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
122
124
  def register_semicolon(line, column, after_expression, token_before_semicolon = nil)
123
125
  range = source_range(processed_source.buffer, line, column)
124
126
 
@@ -130,14 +132,19 @@ module RuboCop
130
132
  # without parentheses.
131
133
  # See: https://github.com/rubocop/rubocop/issues/10791
132
134
  if token_before_semicolon&.regexp_dots?
133
- range_node = find_range_node(token_before_semicolon)
134
- corrector.wrap(range_node, '(', ')') if range_node
135
+ node = find_node(range_nodes, token_before_semicolon)
136
+ elsif token_before_semicolon&.type == :tLABEL
137
+ node = find_node(value_omission_pair_nodes, token_before_semicolon).parent
138
+ space = node.parent.loc.selector.end.join(node.source_range.begin)
139
+ corrector.remove(space)
135
140
  end
136
141
 
142
+ corrector.wrap(node, '(', ')') if node
137
143
  corrector.remove(range)
138
144
  end
139
145
  end
140
146
  end
147
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
141
148
 
142
149
  def expressions_per_line(exprs)
143
150
  # create a map matching lines to the number of expressions on them
@@ -153,9 +160,9 @@ module RuboCop
153
160
  end
154
161
  end
155
162
 
156
- def find_range_node(token_before_semicolon)
157
- range_nodes.detect do |range_node|
158
- range_node.source_range.contains?(token_before_semicolon.pos)
163
+ def find_node(nodes, token_before_semicolon)
164
+ nodes.detect do |node|
165
+ node.source_range.overlaps?(token_before_semicolon.pos)
159
166
  end
160
167
  end
161
168
 
@@ -166,6 +173,15 @@ module RuboCop
166
173
  @range_nodes = ast.range_type? ? [ast] : []
167
174
  @range_nodes.concat(ast.each_descendant(:range).to_a)
168
175
  end
176
+
177
+ def value_omission_pair_nodes
178
+ if instance_variable_defined?(:@value_omission_pair_nodes)
179
+ return @value_omission_pair_nodes
180
+ end
181
+
182
+ ast = processed_source.ast
183
+ @value_omission_pair_nodes = ast.each_descendant(:pair).to_a.select(&:value_omission?)
184
+ end
169
185
  end
170
186
  end
171
187
  end
@@ -129,6 +129,7 @@ module RuboCop
129
129
  corrector.remove(range_with_surrounding_space(range, newlines: false))
130
130
  end
131
131
 
132
+ # rubocop:disable Metrics/AbcSize
132
133
  def correct_for_basic_condition_style(corrector, node, if_branch)
133
134
  range = range_between(
134
135
  node.condition.source_range.end_pos, if_branch.condition.source_range.begin_pos
@@ -137,8 +138,14 @@ module RuboCop
137
138
 
138
139
  corrector.replace(if_branch.condition, chainable_condition(if_branch))
139
140
 
140
- corrector.remove(range_by_whole_lines(node.loc.end, include_final_newline: true))
141
+ end_range = if same_line?(node.loc.end, node.if_branch.loc.end)
142
+ node.loc.end
143
+ else
144
+ range_by_whole_lines(node.loc.end, include_final_newline: true)
145
+ end
146
+ corrector.remove(end_range)
141
147
  end
148
+ # rubocop:enable Metrics/AbcSize
142
149
 
143
150
  def autocorrect_outer_condition_modify_form(corrector, node, if_branch)
144
151
  correct_node(corrector, if_branch)
@@ -100,7 +100,7 @@ module RuboCop
100
100
  node.receiver.str_type? &&
101
101
  node.first_argument.str_type? &&
102
102
  node.multiline? &&
103
- node.source =~ /\+\s*\n/
103
+ node.source.match?(/\+\s*\n/)
104
104
  end
105
105
 
106
106
  def find_topmost_plus_node(node)
@@ -141,22 +141,26 @@ module RuboCop
141
141
  end
142
142
 
143
143
  def replacement(parts)
144
- interpolated_parts = parts.map do |part|
145
- case part.type
146
- when :str
147
- adjust_str(part)
148
- when :dstr
149
- part.children.all?(&:str_type?) ? adjust_str(part) : part.value
150
- else
151
- "\#{#{part.source}}"
152
- end
153
- end
144
+ interpolated_parts = parts.map { |part| adjust_str(part) }
154
145
 
155
146
  "\"#{handle_quotes(interpolated_parts).join}\""
156
147
  end
157
148
 
158
- def adjust_str(node)
159
- single_quoted?(node) ? node.value.gsub(/(\\|")/, '\\\\\&') : node.value.inspect[1..-2]
149
+ def adjust_str(part)
150
+ case part.type
151
+ when :str
152
+ if single_quoted?(part)
153
+ part.value.gsub(/(\\|"|#\{|#@|#\$)/, '\\\\\&')
154
+ else
155
+ part.value.inspect[1..-2]
156
+ end
157
+ when :dstr, :begin
158
+ part.children.map do |child|
159
+ adjust_str(child)
160
+ end.join
161
+ else
162
+ "\#{#{part.source}}"
163
+ end
160
164
  end
161
165
 
162
166
  def handle_quotes(parts)
@@ -109,7 +109,7 @@ module RuboCop
109
109
 
110
110
  def_args.zip(super_args).each do |def_arg, super_arg|
111
111
  next if positional_arg_same?(def_arg, super_arg)
112
- next if positional_rest_arg_same(def_arg, super_arg)
112
+ next if positional_rest_arg_same?(def_arg, super_arg)
113
113
  next if keyword_arg_same?(def_arg, super_arg)
114
114
  next if keyword_rest_arg_same?(def_arg, super_arg)
115
115
  next if block_arg_same?(def_node, super_node, def_arg, super_arg)
@@ -147,7 +147,7 @@ module RuboCop
147
147
  def_arg.name == super_arg.children.first
148
148
  end
149
149
 
150
- def positional_rest_arg_same(def_arg, super_arg)
150
+ def positional_rest_arg_same?(def_arg, super_arg)
151
151
  return false unless def_arg.restarg_type?
152
152
  # anonymous forwarding
153
153
  return true if def_arg.name.nil? && super_arg.forwarded_restarg_type?
@@ -81,7 +81,7 @@ module RuboCop
81
81
 
82
82
  content = *sym
83
83
  content = content.map { |c| c.is_a?(AST::Node) ? c.source : c }.join
84
- content_without_delimiter_pairs = content.gsub(/(\[[^\s\[\]]*\])|(\([^\s\(\)]*\))/, '')
84
+ content_without_delimiter_pairs = content.gsub(/(\[[^\s\[\]]*\])|(\([^\s()]*\))/, '')
85
85
 
86
86
  content.include?(' ') || DELIMITERS.any? do |delimiter|
87
87
  content_without_delimiter_pairs.include?(delimiter)
@@ -10,6 +10,9 @@ module RuboCop
10
10
  # for all parenthesized multi-line method calls with arguments.
11
11
  # * `comma`: Requires a comma after the last argument, but only for
12
12
  # parenthesized method calls where each argument is on its own line.
13
+ # * `diff_comma`: Requires a comma after the last argument, but only
14
+ # when that argument is followed by an immediate newline, even if
15
+ # there is an inline comment on the same line.
13
16
  # * `no_comma`: Requires that there is no comma after the last
14
17
  # argument.
15
18
  #
@@ -75,6 +78,48 @@ module RuboCop
75
78
  # 2,
76
79
  # )
77
80
  #
81
+ # @example EnforcedStyleForMultiline: diff_comma
82
+ # # bad
83
+ # method(1, 2,)
84
+ #
85
+ # # good
86
+ # method(1, 2)
87
+ #
88
+ # # good
89
+ # method(
90
+ # 1, 2,
91
+ # 3,
92
+ # )
93
+ #
94
+ # # good
95
+ # method(
96
+ # 1, 2, 3,
97
+ # )
98
+ #
99
+ # # good
100
+ # method(
101
+ # 1,
102
+ # 2,
103
+ # )
104
+ #
105
+ # # bad
106
+ # method(1, [
107
+ # 2,
108
+ # ],)
109
+ #
110
+ # # good
111
+ # method(1, [
112
+ # 2,
113
+ # ])
114
+ #
115
+ # # bad
116
+ # object[1, 2,
117
+ # 3, 4,]
118
+ #
119
+ # # good
120
+ # object[1, 2,
121
+ # 3, 4]
122
+ #
78
123
  # @example EnforcedStyleForMultiline: no_comma (default)
79
124
  # # bad
80
125
  # method(1, 2,)