rubocop 1.75.8 → 1.81.7

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 (176) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +20 -16
  3. data/config/default.yml +121 -28
  4. data/config/obsoletion.yml +6 -3
  5. data/exe/rubocop +1 -8
  6. data/lib/rubocop/cli/command/auto_generate_config.rb +2 -2
  7. data/lib/rubocop/cli.rb +18 -3
  8. data/lib/rubocop/config_loader.rb +4 -39
  9. data/lib/rubocop/config_loader_resolver.rb +5 -4
  10. data/lib/rubocop/config_store.rb +5 -0
  11. data/lib/rubocop/cop/autocorrect_logic.rb +4 -4
  12. data/lib/rubocop/cop/bundler/ordered_gems.rb +1 -1
  13. data/lib/rubocop/cop/correctors/alignment_corrector.rb +7 -4
  14. data/lib/rubocop/cop/correctors/for_to_each_corrector.rb +7 -2
  15. data/lib/rubocop/cop/correctors/parentheses_corrector.rb +5 -2
  16. data/lib/rubocop/cop/gemspec/attribute_assignment.rb +91 -0
  17. data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +0 -22
  18. data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +1 -1
  19. data/lib/rubocop/cop/gemspec/require_mfa.rb +15 -1
  20. data/lib/rubocop/cop/internal_affairs/example_description.rb +1 -1
  21. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +4 -4
  22. data/lib/rubocop/cop/internal_affairs/node_pattern_groups.rb +4 -1
  23. data/lib/rubocop/cop/internal_affairs/node_type_group.rb +3 -2
  24. data/lib/rubocop/cop/internal_affairs/on_send_without_on_csend.rb +1 -1
  25. data/lib/rubocop/cop/internal_affairs/useless_restrict_on_send.rb +1 -1
  26. data/lib/rubocop/cop/layout/class_structure.rb +1 -1
  27. data/lib/rubocop/cop/layout/closing_parenthesis_indentation.rb +1 -1
  28. data/lib/rubocop/cop/layout/dot_position.rb +1 -1
  29. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +30 -12
  30. data/lib/rubocop/cop/layout/empty_lines_after_module_inclusion.rb +101 -0
  31. data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +1 -1
  32. data/lib/rubocop/cop/layout/empty_lines_around_arguments.rb +8 -29
  33. data/lib/rubocop/cop/layout/empty_lines_around_class_body.rb +1 -1
  34. data/lib/rubocop/cop/layout/hash_alignment.rb +2 -5
  35. data/lib/rubocop/cop/layout/line_length.rb +35 -6
  36. data/lib/rubocop/cop/layout/multiline_operation_indentation.rb +8 -4
  37. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +8 -0
  38. data/lib/rubocop/cop/layout/space_around_keyword.rb +6 -1
  39. data/lib/rubocop/cop/layout/space_around_operators.rb +8 -0
  40. data/lib/rubocop/cop/layout/space_before_brackets.rb +2 -9
  41. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +7 -2
  42. data/lib/rubocop/cop/layout/trailing_whitespace.rb +1 -1
  43. data/lib/rubocop/cop/lint/ambiguous_range.rb +5 -0
  44. data/lib/rubocop/cop/lint/constant_overwritten_in_rescue.rb +3 -2
  45. data/lib/rubocop/cop/lint/cop_directive_syntax.rb +13 -7
  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_methods.rb +25 -4
  49. data/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb +5 -42
  50. data/lib/rubocop/cop/lint/empty_interpolation.rb +14 -1
  51. data/lib/rubocop/cop/lint/float_comparison.rb +4 -4
  52. data/lib/rubocop/cop/lint/identity_comparison.rb +19 -15
  53. data/lib/rubocop/cop/lint/literal_as_condition.rb +34 -28
  54. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +17 -8
  55. data/lib/rubocop/cop/lint/numeric_operation_with_constant_result.rb +1 -0
  56. data/lib/rubocop/cop/lint/redundant_regexp_quantifiers.rb +1 -1
  57. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +101 -2
  58. data/lib/rubocop/cop/lint/redundant_type_conversion.rb +4 -4
  59. data/lib/rubocop/cop/lint/require_range_parentheses.rb +1 -1
  60. data/lib/rubocop/cop/lint/rescue_exception.rb +1 -4
  61. data/lib/rubocop/cop/lint/rescue_type.rb +1 -1
  62. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +4 -4
  63. data/lib/rubocop/cop/lint/self_assignment.rb +31 -5
  64. data/lib/rubocop/cop/lint/shadowed_argument.rb +7 -7
  65. data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +5 -0
  66. data/lib/rubocop/cop/lint/uri_escape_unescape.rb +2 -0
  67. data/lib/rubocop/cop/lint/useless_access_modifier.rb +29 -4
  68. data/lib/rubocop/cop/lint/useless_default_value_argument.rb +90 -0
  69. data/lib/rubocop/cop/lint/useless_numeric_operation.rb +1 -0
  70. data/lib/rubocop/cop/lint/useless_or.rb +98 -0
  71. data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +3 -3
  72. data/lib/rubocop/cop/lint/utils/nil_receiver_checker.rb +121 -0
  73. data/lib/rubocop/cop/lint/void.rb +7 -0
  74. data/lib/rubocop/cop/message_annotator.rb +1 -1
  75. data/lib/rubocop/cop/mixin/alignment.rb +1 -1
  76. data/lib/rubocop/cop/mixin/check_line_breakable.rb +1 -1
  77. data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +1 -7
  78. data/lib/rubocop/cop/mixin/frozen_string_literal.rb +1 -1
  79. data/lib/rubocop/cop/mixin/gemspec_help.rb +22 -0
  80. data/lib/rubocop/cop/mixin/line_length_help.rb +24 -8
  81. data/lib/rubocop/cop/mixin/ordered_gem_node.rb +1 -1
  82. data/lib/rubocop/cop/mixin/trailing_comma.rb +1 -1
  83. data/lib/rubocop/cop/naming/file_name.rb +2 -2
  84. data/lib/rubocop/cop/naming/method_name.rb +129 -13
  85. data/lib/rubocop/cop/naming/predicate_method.rb +319 -0
  86. data/lib/rubocop/cop/naming/{predicate_name.rb → predicate_prefix.rb} +4 -4
  87. data/lib/rubocop/cop/security/eval.rb +2 -1
  88. data/lib/rubocop/cop/security/json_load.rb +33 -11
  89. data/lib/rubocop/cop/security/open.rb +1 -0
  90. data/lib/rubocop/cop/style/access_modifier_declarations.rb +1 -1
  91. data/lib/rubocop/cop/style/accessor_grouping.rb +13 -1
  92. data/lib/rubocop/cop/style/arguments_forwarding.rb +11 -17
  93. data/lib/rubocop/cop/style/array_intersect.rb +99 -35
  94. data/lib/rubocop/cop/style/array_intersect_with_single_element.rb +47 -0
  95. data/lib/rubocop/cop/style/bitwise_predicate.rb +8 -1
  96. data/lib/rubocop/cop/style/block_delimiters.rb +1 -1
  97. data/lib/rubocop/cop/style/case_like_if.rb +1 -1
  98. data/lib/rubocop/cop/style/collection_querying.rb +167 -0
  99. data/lib/rubocop/cop/style/conditional_assignment.rb +11 -5
  100. data/lib/rubocop/cop/style/constant_visibility.rb +14 -9
  101. data/lib/rubocop/cop/style/dig_chain.rb +1 -1
  102. data/lib/rubocop/cop/style/double_negation.rb +1 -1
  103. data/lib/rubocop/cop/style/empty_string_inside_interpolation.rb +100 -0
  104. data/lib/rubocop/cop/style/endless_method.rb +15 -2
  105. data/lib/rubocop/cop/style/explicit_block_argument.rb +1 -1
  106. data/lib/rubocop/cop/style/exponential_notation.rb +3 -2
  107. data/lib/rubocop/cop/style/fetch_env_var.rb +32 -6
  108. data/lib/rubocop/cop/style/float_division.rb +15 -1
  109. data/lib/rubocop/cop/style/hash_conversion.rb +16 -8
  110. data/lib/rubocop/cop/style/hash_syntax.rb +1 -1
  111. data/lib/rubocop/cop/style/if_unless_modifier.rb +13 -6
  112. data/lib/rubocop/cop/style/infinite_loop.rb +1 -1
  113. data/lib/rubocop/cop/style/inverse_methods.rb +1 -1
  114. data/lib/rubocop/cop/style/it_assignment.rb +69 -12
  115. data/lib/rubocop/cop/style/it_block_parameter.rb +36 -15
  116. data/lib/rubocop/cop/style/map_to_hash.rb +1 -3
  117. data/lib/rubocop/cop/style/map_to_set.rb +1 -3
  118. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +4 -6
  119. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +16 -0
  120. data/lib/rubocop/cop/style/min_max_comparison.rb +13 -5
  121. data/lib/rubocop/cop/style/nil_comparison.rb +9 -7
  122. data/lib/rubocop/cop/style/one_line_conditional.rb +17 -9
  123. data/lib/rubocop/cop/style/parallel_assignment.rb +32 -20
  124. data/lib/rubocop/cop/style/redundant_array_flatten.rb +50 -0
  125. data/lib/rubocop/cop/style/redundant_begin.rb +34 -0
  126. data/lib/rubocop/cop/style/redundant_condition.rb +1 -1
  127. data/lib/rubocop/cop/style/redundant_exception.rb +1 -1
  128. data/lib/rubocop/cop/style/redundant_fetch_block.rb +1 -9
  129. data/lib/rubocop/cop/style/redundant_format.rb +26 -5
  130. data/lib/rubocop/cop/style/redundant_freeze.rb +1 -1
  131. data/lib/rubocop/cop/style/redundant_interpolation.rb +12 -3
  132. data/lib/rubocop/cop/style/redundant_line_continuation.rb +1 -1
  133. data/lib/rubocop/cop/style/redundant_parentheses.rb +55 -16
  134. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +4 -0
  135. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +8 -0
  136. data/lib/rubocop/cop/style/redundant_self.rb +8 -5
  137. data/lib/rubocop/cop/style/safe_navigation.rb +44 -12
  138. data/lib/rubocop/cop/style/semicolon.rb +23 -7
  139. data/lib/rubocop/cop/style/single_line_methods.rb +7 -4
  140. data/lib/rubocop/cop/style/sole_nested_conditional.rb +40 -3
  141. data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +1 -1
  142. data/lib/rubocop/cop/style/string_concatenation.rb +17 -13
  143. data/lib/rubocop/cop/style/symbol_array.rb +1 -1
  144. data/lib/rubocop/cop/style/symbol_proc.rb +1 -1
  145. data/lib/rubocop/cop/style/trailing_comma_in_arguments.rb +45 -0
  146. data/lib/rubocop/cop/style/trailing_comma_in_block_args.rb +1 -1
  147. data/lib/rubocop/cop/style/unless_else.rb +10 -9
  148. data/lib/rubocop/cop/utils/format_string.rb +10 -0
  149. data/lib/rubocop/cop/variable_force/variable.rb +1 -1
  150. data/lib/rubocop/cop/variable_force.rb +25 -8
  151. data/lib/rubocop/cops_documentation_generator.rb +5 -4
  152. data/lib/rubocop/formatter/disabled_config_formatter.rb +18 -5
  153. data/lib/rubocop/formatter/fuubar_style_formatter.rb +1 -1
  154. data/lib/rubocop/formatter/markdown_formatter.rb +1 -0
  155. data/lib/rubocop/formatter/offense_count_formatter.rb +1 -1
  156. data/lib/rubocop/formatter/pacman_formatter.rb +1 -0
  157. data/lib/rubocop/lsp/diagnostic.rb +25 -24
  158. data/lib/rubocop/lsp/routes.rb +65 -9
  159. data/lib/rubocop/lsp/runtime.rb +2 -2
  160. data/lib/rubocop/lsp/server.rb +2 -2
  161. data/lib/rubocop/lsp/stdin_runner.rb +0 -16
  162. data/lib/rubocop/pending_cops_reporter.rb +56 -0
  163. data/lib/rubocop/result_cache.rb +14 -12
  164. data/lib/rubocop/rspec/expect_offense.rb +9 -3
  165. data/lib/rubocop/runner.rb +6 -4
  166. data/lib/rubocop/server/cache.rb +4 -2
  167. data/lib/rubocop/server/client_command/base.rb +10 -0
  168. data/lib/rubocop/server/client_command/exec.rb +2 -1
  169. data/lib/rubocop/server/client_command/start.rb +11 -1
  170. data/lib/rubocop/target_finder.rb +9 -9
  171. data/lib/rubocop/target_ruby.rb +10 -1
  172. data/lib/rubocop/version.rb +1 -1
  173. data/lib/rubocop.rb +12 -1
  174. data/lib/ruby_lsp/rubocop/addon.rb +25 -10
  175. data/lib/ruby_lsp/rubocop/runtime_adapter.rb +49 -15
  176. metadata +18 -7
@@ -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
 
@@ -49,6 +46,7 @@ module RuboCop
49
46
  empty_parentheses?(node) ||
50
47
  first_arg_begins_with_hash_literal?(node) ||
51
48
  rescue?(node) ||
49
+ in_pattern_matching_in_method_argument?(node) ||
52
50
  allowed_pin_operator?(node) ||
53
51
  allowed_expression?(node)
54
52
  end
@@ -122,6 +120,13 @@ module RuboCop
122
120
  hash_literal && first_argument?(node) && !parentheses?(hash_literal) && !parenthesized
123
121
  end
124
122
 
123
+ def in_pattern_matching_in_method_argument?(begin_node)
124
+ return false unless begin_node.parent&.call_type?
125
+ return false unless (node = begin_node.children.first)
126
+
127
+ target_ruby_version <= 2.7 ? node.match_pattern_type? : node.match_pattern_p_type?
128
+ end
129
+
125
130
  def method_chain_begins_with_hash_literal(node)
126
131
  return if node.nil?
127
132
  return node if node.hash_type?
@@ -134,14 +139,14 @@ module RuboCop
134
139
  node = begin_node.children.first
135
140
 
136
141
  if (message = find_offense_message(begin_node, node))
137
- if node.range_type? && !argument_of_parenthesized_method_call?(begin_node)
142
+ if node.range_type? && !argument_of_parenthesized_method_call?(begin_node, node)
138
143
  begin_node = begin_node.parent
139
144
  end
140
145
 
141
146
  return offense(begin_node, message)
142
147
  end
143
148
 
144
- check_send(begin_node, node) if node.call_type?
149
+ check_send(begin_node, node) if call_node?(node)
145
150
  end
146
151
 
147
152
  # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
@@ -156,8 +161,12 @@ module RuboCop
156
161
  if node.lambda_or_proc? && (node.braces? || node.send_node.lambda_literal?)
157
162
  return 'an expression'
158
163
  end
164
+ if disallowed_one_line_pattern_matching?(begin_node, node)
165
+ return 'a one-line pattern matching'
166
+ end
159
167
  return 'an interpolated expression' if interpolation?(begin_node)
160
- return 'a method argument' if argument_of_parenthesized_method_call?(begin_node)
168
+ return 'a method argument' if argument_of_parenthesized_method_call?(begin_node, node)
169
+ return 'a one-line rescue' if oneline_rescue_parentheses_required?(begin_node, node)
161
170
 
162
171
  return if begin_node.chained?
163
172
 
@@ -180,14 +189,24 @@ module RuboCop
180
189
  # @!method interpolation?(node)
181
190
  def_node_matcher :interpolation?, '[^begin ^^dstr]'
182
191
 
183
- def argument_of_parenthesized_method_call?(begin_node)
184
- node = begin_node.children.first
185
- return false if node.basic_conditional? || method_call_parentheses_required?(node)
192
+ def argument_of_parenthesized_method_call?(begin_node, node)
193
+ if node.basic_conditional? || node.rescue_type? || method_call_parentheses_required?(node)
194
+ return false
195
+ end
186
196
  return false unless (parent = begin_node.parent)
187
197
 
188
198
  parent.call_type? && parent.parenthesized? && parent.receiver != begin_node
189
199
  end
190
200
 
201
+ def oneline_rescue_parentheses_required?(begin_node, node)
202
+ return false unless node.rescue_type?
203
+ return false unless (parent = begin_node.parent)
204
+ return false if parent.if_type? && parent.ternary?
205
+ return false if parent.conditional? && parent.condition == begin_node
206
+
207
+ !parent.type?(:call, :array, :pair)
208
+ end
209
+
191
210
  def method_call_parentheses_required?(node)
192
211
  return false unless node.call_type?
193
212
 
@@ -198,10 +217,16 @@ module RuboCop
198
217
  !!config.for_enabled_cop('Style/ParenthesesAroundCondition')['AllowInMultilineConditions']
199
218
  end
200
219
 
220
+ def call_node?(node)
221
+ node.call_type? || (node.any_block_type? && node.braces? && !node.lambda_or_proc?)
222
+ end
223
+
201
224
  def check_send(begin_node, node)
225
+ node = node.send_node if node.any_block_type?
226
+
202
227
  return check_unary(begin_node, node) if node.unary_operation?
203
228
 
204
- return unless method_call_with_redundant_parentheses?(node)
229
+ return unless method_call_with_redundant_parentheses?(begin_node, node)
205
230
  return if call_chain_starts_with_int?(begin_node, node) ||
206
231
  do_end_block_in_method_chain?(begin_node, node)
207
232
 
@@ -212,8 +237,7 @@ module RuboCop
212
237
  return if begin_node.chained?
213
238
 
214
239
  node = node.children.first while suspect_unary?(node)
215
-
216
- return if node.send_type? && !method_call_with_redundant_parentheses?(node)
240
+ return unless method_call_with_redundant_parentheses?(begin_node, node)
217
241
 
218
242
  offense(begin_node, 'a unary operation')
219
243
  end
@@ -242,6 +266,15 @@ module RuboCop
242
266
  end
243
267
  end
244
268
 
269
+ def disallowed_one_line_pattern_matching?(begin_node, node)
270
+ if (parent = begin_node.parent)
271
+ return false if parent.any_def_type? && parent.endless?
272
+ return false if parent.assignment?
273
+ end
274
+
275
+ node.any_match_pattern_type? && node.each_ancestor.none?(&:operator_keyword?)
276
+ end
277
+
245
278
  def raised_to_power_negative_numeric?(begin_node, node)
246
279
  return false unless node.numeric_type?
247
280
 
@@ -266,13 +299,19 @@ module RuboCop
266
299
  end
267
300
  end
268
301
 
269
- def method_call_with_redundant_parentheses?(node)
270
- 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?)
271
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
272
309
 
273
- 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)
274
313
 
275
- args.empty? || parentheses?(send_node) || square_brackets?(send_node)
314
+ parentheses?(begin_node) && begin_node.parent.children.one?
276
315
  end
277
316
 
278
317
  def only_begin_arg?(args)
@@ -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,8 @@ module RuboCop
73
74
  new_argument.gsub!("'", "\\\\'")
74
75
  new_argument.gsub!('\"', '"')
75
76
  quote = "'"
77
+ elsif new_argument.include?("\\'")
78
+ quote = "'"
76
79
  elsif new_argument.include?('\'')
77
80
  new_argument.gsub!("'", "\\\\'")
78
81
  quote = "'"
@@ -84,6 +87,7 @@ module RuboCop
84
87
 
85
88
  "#{quote}#{new_argument}#{quote}"
86
89
  end
90
+ # rubocop:enable Metrics/MethodLength
87
91
 
88
92
  def replacement(regexp_node)
89
93
  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
@@ -67,6 +67,9 @@ module RuboCop
67
67
 
68
68
  def on_or_asgn(node)
69
69
  allow_self(node.lhs)
70
+
71
+ lhs_name = node.lhs.lvasgn_type? ? node.lhs.name : node.lhs
72
+ add_lhs_to_local_variables_scopes(node.rhs, lhs_name)
70
73
  end
71
74
  alias on_and_asgn on_or_asgn
72
75
 
@@ -123,11 +126,11 @@ module RuboCop
123
126
  def on_if(node)
124
127
  # Allow conditional nodes to use `self` in the condition if that variable
125
128
  # name is used in an `lvasgn` or `masgn` within the `if`.
126
- node.child_nodes.each do |child_node|
127
- if child_node.lvasgn_type?
128
- add_lhs_to_local_variables_scopes(node.condition, child_node.lhs)
129
- elsif child_node.masgn_type?
130
- add_masgn_lhs_variables(node.condition, child_node.lhs)
129
+ node.each_descendant(:lvasgn, :masgn) do |descendant_node|
130
+ if descendant_node.lvasgn_type?
131
+ add_lhs_to_local_variables_scopes(node.condition, descendant_node.lhs)
132
+ else
133
+ add_masgn_lhs_variables(node.condition, descendant_node.lhs)
131
134
  end
132
135
  end
133
136
  end
@@ -86,6 +86,10 @@ module RuboCop
86
86
  # foo.baz = bar if foo
87
87
  # foo.baz + bar if foo
88
88
  # foo.bar > 2 if foo
89
+ #
90
+ # foo ? foo[index] : nil # Ignored `foo&.[](index)` due to unclear readability benefit.
91
+ # foo ? foo[idx] = v : nil # Ignored `foo&.[]=(idx, v)` due to unclear readability benefit.
92
+ # foo ? foo * 42 : nil # Ignored `foo&.*(42)` due to unclear readability benefit.
89
93
  class SafeNavigation < Base # rubocop:disable Metrics/ClassLength
90
94
  include NilMethods
91
95
  include RangeHelp
@@ -138,6 +142,7 @@ module RuboCop
138
142
  # @!method strip_begin(node)
139
143
  def_node_matcher :strip_begin, '{ (begin $!begin) $!(begin) }'
140
144
 
145
+ # rubocop:disable Metrics/AbcSize
141
146
  def on_if(node)
142
147
  return if allowed_if_condition?(node)
143
148
 
@@ -146,13 +151,16 @@ module RuboCop
146
151
 
147
152
  body = extract_if_body(node)
148
153
  method_call = receiver.parent
154
+ return if dotless_operator_call?(method_call) || method_call.double_colon?
149
155
 
150
156
  removal_ranges = [begin_range(node, body), end_range(node, body)]
151
157
 
152
158
  report_offense(node, method_chain, method_call, *removal_ranges) do |corrector|
159
+ corrector.replace(receiver, checked_variable.source) if checked_variable.csend_type?
153
160
  corrector.insert_before(method_call.loc.dot, '&') unless method_call.safe_navigation?
154
161
  end
155
162
  end
163
+ # rubocop:enable Metrics/AbcSize
156
164
 
157
165
  def on_and(node) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
158
166
  collect_and_clauses(node).each do |(lhs, lhs_operator_range), (rhs, _rhs_operator_range)|
@@ -181,6 +189,8 @@ module RuboCop
181
189
  end
182
190
  end
183
191
 
192
+ private
193
+
184
194
  def report_offense(node, rhs, rhs_receiver, *removal_ranges, offense_range: node)
185
195
  add_offense(offense_range) do |corrector|
186
196
  next if ignored_node?(node)
@@ -198,8 +208,6 @@ module RuboCop
198
208
  end
199
209
  end
200
210
 
201
- private
202
-
203
211
  def find_method_chain(node)
204
212
  return node unless node&.parent&.call_type?
205
213
 
@@ -235,7 +243,7 @@ module RuboCop
235
243
  return false if !matching_nodes?(lhs_receiver, rhs_receiver) || rhs_receiver.nil?
236
244
  return false if use_var_only_in_unless_modifier?(node, lhs_receiver)
237
245
  return false if chain_length(rhs, rhs_receiver) > max_chain_length
238
- return false if unsafe_method_used?(rhs, rhs_receiver.parent)
246
+ return false if unsafe_method_used?(node, rhs, rhs_receiver.parent)
239
247
  return false if rhs.send_type? && rhs.method?(:empty?)
240
248
 
241
249
  true
@@ -253,6 +261,20 @@ module RuboCop
253
261
  end
254
262
  end
255
263
 
264
+ def dotless_operator_call?(method_call)
265
+ return true if dotless_operator_method?(method_call)
266
+
267
+ method_call = method_call.parent while method_call.parent.send_type?
268
+
269
+ dotless_operator_method?(method_call)
270
+ end
271
+
272
+ def dotless_operator_method?(method_call)
273
+ return false if method_call.loc.dot
274
+
275
+ method_call.method?(:[]) || method_call.method?(:[]=) || method_call.operator_method?
276
+ end
277
+
256
278
  def handle_comments(corrector, node, method_call)
257
279
  comments = comments(node)
258
280
  return if comments.empty?
@@ -322,8 +344,16 @@ module RuboCop
322
344
 
323
345
  def matching_call_nodes?(left, right)
324
346
  return false unless left && right.respond_to?(:call_type?)
347
+ return false unless left.call_type? && right.call_type?
325
348
 
326
- left.call_type? && right.call_type? && left.children == right.children
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
353
+
354
+ left_method == right_method &&
355
+ matching_nodes?(left_receiver, right_receiver) &&
356
+ left_args == right_args
327
357
  end
328
358
 
329
359
  def chain_length(method_chain, method)
@@ -334,21 +364,24 @@ module RuboCop
334
364
  end
335
365
  end
336
366
 
337
- def unsafe_method_used?(method_chain, method)
338
- return true if unsafe_method?(method)
367
+ def unsafe_method_used?(node, method_chain, method)
368
+ return true if unsafe_method?(node, method)
339
369
 
340
370
  method.each_ancestor(:send).any? do |ancestor|
341
371
  break true unless config.cop_enabled?('Lint/SafeNavigationChain')
342
372
 
343
- break true if unsafe_method?(ancestor)
373
+ break true if unsafe_method?(node, ancestor)
344
374
  break true if nil_methods.include?(ancestor.method_name)
345
375
  break false if ancestor == method_chain
346
376
  end
347
377
  end
348
378
 
349
- def unsafe_method?(send_node)
350
- negated?(send_node) ||
351
- send_node.assignment? ||
379
+ def unsafe_method?(node, send_node)
380
+ return true if negated?(send_node)
381
+
382
+ return false if node.respond_to?(:ternary?) && node.ternary?
383
+
384
+ send_node.assignment? ||
352
385
  (!send_node.dot? && !send_node.safe_navigation?)
353
386
  end
354
387
 
@@ -377,8 +410,7 @@ module RuboCop
377
410
  method_chain)
378
411
  start_method.each_ancestor do |ancestor|
379
412
  break unless %i[send block].include?(ancestor.type)
380
- next unless ancestor.send_type?
381
- next if ancestor.safe_navigation?
413
+ next if !ancestor.send_type? || ancestor.operator_method?
382
414
 
383
415
  corrector.insert_before(ancestor.loc.dot, '&')
384
416
 
@@ -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
@@ -65,7 +65,7 @@ module RuboCop
65
65
  return false if target_ruby_version < 3.0
66
66
  return false if disallow_endless_method_style?
67
67
  return false unless body_node
68
- return false if body_node.parent.assignment_method? ||
68
+ return false if body_node.basic_conditional? || body_node.parent.assignment_method? ||
69
69
  NOT_SUPPORTED_ENDLESS_METHOD_BODY_TYPES.include?(body_node.type)
70
70
 
71
71
  !body_node.type?(:begin, :kwbegin)
@@ -86,10 +86,10 @@ module RuboCop
86
86
  end
87
87
 
88
88
  def correct_to_endless(corrector, node)
89
- self_receiver = node.self_receiver? ? 'self.' : ''
89
+ receiver = "#{node.receiver.source}." if node.receiver
90
90
  arguments = node.arguments.any? ? node.arguments.source : '()'
91
91
  body_source = method_body_source(node.body)
92
- replacement = "def #{self_receiver}#{node.method_name}#{arguments} = #{body_source}"
92
+ replacement = "def #{receiver}#{node.method_name}#{arguments} = #{body_source}"
93
93
 
94
94
  corrector.replace(node, replacement)
95
95
  end
@@ -130,7 +130,10 @@ module RuboCop
130
130
  end
131
131
 
132
132
  def require_parentheses?(method_body)
133
- method_body.send_type? && !method_body.arguments.empty? && !method_body.comparison_method?
133
+ return false unless method_body.send_type?
134
+ return false if method_body.arithmetic_operation?
135
+
136
+ !method_body.arguments.empty? && !method_body.comparison_method?
134
137
  end
135
138
 
136
139
  def disallow_endless_method_style?
@@ -115,8 +115,9 @@ module RuboCop
115
115
  end
116
116
 
117
117
  def correct_node(corrector, node)
118
- corrector.replace(node.loc.keyword, 'if') if node.unless?
118
+ corrector.replace(node.loc.keyword, 'if') if node.unless? && !part_of_ignored_node?(node)
119
119
  corrector.replace(node.condition, chainable_condition(node))
120
+ ignore_node(node)
120
121
  end
121
122
 
122
123
  def correct_for_guard_condition_style(corrector, node, if_branch)
@@ -128,6 +129,7 @@ module RuboCop
128
129
  corrector.remove(range_with_surrounding_space(range, newlines: false))
129
130
  end
130
131
 
132
+ # rubocop:disable Metrics/AbcSize
131
133
  def correct_for_basic_condition_style(corrector, node, if_branch)
132
134
  range = range_between(
133
135
  node.condition.source_range.end_pos, if_branch.condition.source_range.begin_pos
@@ -136,8 +138,14 @@ module RuboCop
136
138
 
137
139
  corrector.replace(if_branch.condition, chainable_condition(if_branch))
138
140
 
139
- 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)
140
147
  end
148
+ # rubocop:enable Metrics/AbcSize
141
149
 
142
150
  def autocorrect_outer_condition_modify_form(corrector, node, if_branch)
143
151
  correct_node(corrector, if_branch)
@@ -174,6 +182,8 @@ module RuboCop
174
182
 
175
183
  if parenthesize_method?(condition)
176
184
  parenthesized_method_arguments(condition)
185
+ elsif condition.and_type?
186
+ parenthesized_and(condition)
177
187
  else
178
188
  "(#{condition.source})"
179
189
  end
@@ -185,12 +195,19 @@ module RuboCop
185
195
  end
186
196
 
187
197
  def add_parentheses?(node)
188
- return true if node.assignment? || (node.operator_keyword? && !node.and_type?)
198
+ return true if node.assignment? || node.or_type?
199
+ return true if assignment_in_and?(node)
189
200
  return false unless node.call_type?
190
201
 
191
202
  (node.arguments.any? && !node.parenthesized?) || node.prefix_not?
192
203
  end
193
204
 
205
+ def assignment_in_and?(node)
206
+ return false unless node.and_type?
207
+
208
+ node.each_descendant.any?(&:assignment?)
209
+ end
210
+
194
211
  def parenthesized_method_arguments(node)
195
212
  method_call = node.source_range.begin.join(node.loc.selector.end).source
196
213
  arguments = node.first_argument.source_range.begin.join(node.source_range.end).source
@@ -198,6 +215,26 @@ module RuboCop
198
215
  "#{method_call}(#{arguments})"
199
216
  end
200
217
 
218
+ def parenthesized_and(node)
219
+ # We only need to add parentheses around the last clause if it's an assignment,
220
+ # because other clauses will be unchanged by merging conditionals.
221
+ lhs = node.lhs.source
222
+ rhs = parenthesized_and_clause(node.rhs)
223
+ operator = range_with_surrounding_space(node.loc.operator, whitespace: true).source
224
+
225
+ "#{lhs}#{operator}#{rhs}"
226
+ end
227
+
228
+ def parenthesized_and_clause(node)
229
+ if node.and_type?
230
+ parenthesized_and(node)
231
+ elsif node.assignment?
232
+ "(#{node.source})"
233
+ else
234
+ node.source
235
+ end
236
+ end
237
+
201
238
  def allow_modifier?
202
239
  cop_config['AllowModifier']
203
240
  end
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Check for parentheses around stabby lambda arguments.
6
+ # Checks for parentheses around stabby lambda arguments.
7
7
  # There are two different styles. Defaults to `require_parentheses`.
8
8
  #
9
9
  # @example EnforcedStyle: require_parentheses (default)
@@ -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)
@@ -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)
@@ -270,7 +270,7 @@ module RuboCop
270
270
  end
271
271
 
272
272
  def allow_if_method_has_argument?(send_node)
273
- !!cop_config.fetch('AllowMethodsWithArguments', false) && !send_node.arguments.count.zero?
273
+ !!cop_config.fetch('AllowMethodsWithArguments', false) && send_node.arguments.any?
274
274
  end
275
275
 
276
276
  def allow_comments?