rubocop 1.91.0 → 1.92.0

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 (236) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +3 -1
  4. data/lib/rubocop/cached_data.rb +47 -7
  5. data/lib/rubocop/changed_files.rb +35 -13
  6. data/lib/rubocop/cli/command/cop_names.rb +57 -0
  7. data/lib/rubocop/cli/command/execute_runner.rb +2 -2
  8. data/lib/rubocop/cli/command/explain.rb +34 -0
  9. data/lib/rubocop/cli/command/show_cops.rb +6 -0
  10. data/lib/rubocop/cli/command/show_docs_url.rb +6 -4
  11. data/lib/rubocop/cli.rb +3 -2
  12. data/lib/rubocop/comment_config.rb +17 -8
  13. data/lib/rubocop/config_loader.rb +6 -3
  14. data/lib/rubocop/config_loader_resolver.rb +37 -12
  15. data/lib/rubocop/config_obsoletion/removed_cop.rb +1 -1
  16. data/lib/rubocop/config_validator.rb +11 -0
  17. data/lib/rubocop/cop/autocorrect_logic.rb +10 -5
  18. data/lib/rubocop/cop/base.rb +42 -21
  19. data/lib/rubocop/cop/bundler/gem_comment.rb +0 -1
  20. data/lib/rubocop/cop/bundler/ordered_gems.rb +0 -8
  21. data/lib/rubocop/cop/cop_documentation.rb +81 -0
  22. data/lib/rubocop/cop/cop_explanation.rb +140 -0
  23. data/lib/rubocop/cop/correctors/alignment_corrector.rb +13 -1
  24. data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +22 -7
  25. data/lib/rubocop/cop/correctors/empty_line_corrector.rb +1 -1
  26. data/lib/rubocop/cop/correctors/lambda_literal_to_method_corrector.rb +23 -2
  27. data/lib/rubocop/cop/correctors/parentheses_corrector.rb +45 -8
  28. data/lib/rubocop/cop/documentation.rb +1 -1
  29. data/lib/rubocop/cop/gemspec/dependency_version.rb +0 -6
  30. data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +0 -6
  31. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +2 -5
  32. data/lib/rubocop/cop/internal_affairs/node_type_group.rb +0 -7
  33. data/lib/rubocop/cop/internal_affairs/redundant_context_config_parameter.rb +1 -7
  34. data/lib/rubocop/cop/internal_affairs/style_detected_api_use.rb +1 -7
  35. data/lib/rubocop/cop/layout/block_alignment.rb +27 -0
  36. data/lib/rubocop/cop/layout/case_indentation.rb +25 -9
  37. data/lib/rubocop/cop/layout/class_structure.rb +9 -1
  38. data/lib/rubocop/cop/layout/def_end_alignment.rb +4 -0
  39. data/lib/rubocop/cop/layout/empty_line_after_multiline_condition.rb +14 -5
  40. data/lib/rubocop/cop/layout/empty_lines.rb +5 -2
  41. data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +1 -0
  42. data/lib/rubocop/cop/layout/empty_lines_around_block_body.rb +1 -1
  43. data/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb +23 -1
  44. data/lib/rubocop/cop/layout/end_alignment.rb +16 -10
  45. data/lib/rubocop/cop/layout/extra_spacing.rb +4 -0
  46. data/lib/rubocop/cop/layout/first_argument_indentation.rb +1 -0
  47. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +5 -3
  48. data/lib/rubocop/cop/layout/first_method_argument_line_break.rb +6 -0
  49. data/lib/rubocop/cop/layout/first_method_parameter_line_break.rb +4 -0
  50. data/lib/rubocop/cop/layout/hash_alignment.rb +16 -12
  51. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +2 -0
  52. data/lib/rubocop/cop/layout/heredoc_indentation.rb +15 -0
  53. data/lib/rubocop/cop/layout/indentation_style.rb +4 -1
  54. data/lib/rubocop/cop/layout/indentation_width.rb +2 -2
  55. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +8 -1
  56. data/lib/rubocop/cop/layout/line_end_string_concatenation_indentation.rb +25 -9
  57. data/lib/rubocop/cop/layout/line_length.rb +52 -18
  58. data/lib/rubocop/cop/layout/multiline_array_brace_layout.rb +12 -0
  59. data/lib/rubocop/cop/layout/multiline_block_layout.rb +8 -1
  60. data/lib/rubocop/cop/layout/multiline_hash_brace_layout.rb +7 -0
  61. data/lib/rubocop/cop/layout/multiline_method_call_brace_layout.rb +6 -0
  62. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +14 -0
  63. data/lib/rubocop/cop/layout/multiline_method_definition_brace_layout.rb +4 -0
  64. data/lib/rubocop/cop/layout/redundant_line_break.rb +22 -1
  65. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +15 -2
  66. data/lib/rubocop/cop/layout/single_line_block_chain.rb +10 -2
  67. data/lib/rubocop/cop/layout/space_after_not.rb +5 -0
  68. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +6 -11
  69. data/lib/rubocop/cop/layout/space_around_keyword.rb +10 -0
  70. data/lib/rubocop/cop/layout/space_before_block_braces.rb +4 -1
  71. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +15 -2
  72. data/lib/rubocop/cop/layout/space_inside_block_braces.rb +31 -8
  73. data/lib/rubocop/cop/lint/ambiguous_operator.rb +1 -1
  74. data/lib/rubocop/cop/lint/ambiguous_operator_precedence.rb +2 -1
  75. data/lib/rubocop/cop/lint/empty_conditional_body.rb +3 -1
  76. data/lib/rubocop/cop/lint/literal_as_condition.rb +15 -0
  77. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +63 -19
  78. data/lib/rubocop/cop/lint/loop.rb +24 -2
  79. data/lib/rubocop/cop/lint/misplaced_magic_comment.rb +71 -11
  80. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +18 -3
  81. data/lib/rubocop/cop/lint/number_conversion.rb +1 -1
  82. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +12 -1
  83. data/lib/rubocop/cop/lint/redundant_dir_glob_sort.rb +13 -2
  84. data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +18 -1
  85. data/lib/rubocop/cop/lint/useless_assignment.rb +14 -7
  86. data/lib/rubocop/cop/lint/void.rb +8 -1
  87. data/lib/rubocop/cop/metrics/class_length.rb +4 -2
  88. data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +0 -4
  89. data/lib/rubocop/cop/metrics/module_length.rb +4 -2
  90. data/lib/rubocop/cop/mixin/alignment.rb +4 -0
  91. data/lib/rubocop/cop/mixin/check_line_breakable.rb +17 -14
  92. data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +5 -1
  93. data/lib/rubocop/cop/mixin/code_length.rb +10 -2
  94. data/lib/rubocop/cop/mixin/empty_lines_around_body.rb +12 -1
  95. data/lib/rubocop/cop/mixin/endless_method_body_help.rb +69 -0
  96. data/lib/rubocop/cop/mixin/forbidden_identifiers.rb +2 -2
  97. data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +15 -1
  98. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +26 -2
  99. data/lib/rubocop/cop/mixin/min_branches_count.rb +3 -1
  100. data/lib/rubocop/cop/mixin/multiline_element_line_breaks.rb +11 -1
  101. data/lib/rubocop/cop/mixin/multiline_literal_brace_layout.rb +9 -0
  102. data/lib/rubocop/cop/mixin/ordered_gem_node.rb +1 -1
  103. data/lib/rubocop/cop/mixin/pattern_matching_help.rb +35 -0
  104. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +17 -2
  105. data/lib/rubocop/cop/mixin/project_index_help.rb +15 -0
  106. data/lib/rubocop/cop/mixin/reparsed_equivalence.rb +13 -3
  107. data/lib/rubocop/cop/mixin/statement_modifier.rb +22 -2
  108. data/lib/rubocop/cop/mixin.rb +2 -0
  109. data/lib/rubocop/cop/naming/accessor_method_name.rb +0 -16
  110. data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
  111. data/lib/rubocop/cop/naming/heredoc_delimiter_case.rb +9 -2
  112. data/lib/rubocop/cop/naming/heredoc_delimiter_naming.rb +5 -3
  113. data/lib/rubocop/cop/naming/predicate_prefix.rb +7 -22
  114. data/lib/rubocop/cop/offense.rb +59 -3
  115. data/lib/rubocop/cop/registry.rb +1 -1
  116. data/lib/rubocop/cop/security/json_load.rb +16 -4
  117. data/lib/rubocop/cop/style/access_modifier_declarations.rb +7 -0
  118. data/lib/rubocop/cop/style/alias.rb +18 -5
  119. data/lib/rubocop/cop/style/ambiguous_endless_method_definition.rb +4 -3
  120. data/lib/rubocop/cop/style/and_or.rb +4 -0
  121. data/lib/rubocop/cop/style/arguments_forwarding.rb +4 -1
  122. data/lib/rubocop/cop/style/attr.rb +3 -4
  123. data/lib/rubocop/cop/style/bitwise_predicate.rb +6 -4
  124. data/lib/rubocop/cop/style/block_delimiters.rb +29 -3
  125. data/lib/rubocop/cop/style/case_like_if.rb +6 -3
  126. data/lib/rubocop/cop/style/class_and_module_children.rb +54 -16
  127. data/lib/rubocop/cop/style/class_methods_definitions.rb +8 -0
  128. data/lib/rubocop/cop/style/combinable_loops.rb +85 -11
  129. data/lib/rubocop/cop/style/concat_array_literals.rb +46 -7
  130. data/lib/rubocop/cop/style/conditional_assignment.rb +4 -0
  131. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +65 -9
  132. data/lib/rubocop/cop/style/double_negation.rb +11 -0
  133. data/lib/rubocop/cop/style/empty_literal.rb +1 -1
  134. data/lib/rubocop/cop/style/empty_method.rb +3 -1
  135. data/lib/rubocop/cop/style/endless_method.rb +67 -6
  136. data/lib/rubocop/cop/style/exact_regexp_match.rb +45 -8
  137. data/lib/rubocop/cop/style/explicit_block_argument.rb +50 -10
  138. data/lib/rubocop/cop/style/float_division.rb +22 -2
  139. data/lib/rubocop/cop/style/for.rb +40 -2
  140. data/lib/rubocop/cop/style/format_string.rb +28 -6
  141. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +2 -1
  142. data/lib/rubocop/cop/style/guard_clause.rb +9 -1
  143. data/lib/rubocop/cop/style/hash_conversion.rb +28 -8
  144. data/lib/rubocop/cop/style/hash_lookup_method.rb +32 -9
  145. data/lib/rubocop/cop/style/hash_syntax.rb +16 -3
  146. data/lib/rubocop/cop/style/identical_conditional_branches.rb +22 -3
  147. data/lib/rubocop/cop/style/if_inside_else.rb +21 -1
  148. data/lib/rubocop/cop/style/if_unless_modifier.rb +19 -20
  149. data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +4 -0
  150. data/lib/rubocop/cop/style/if_with_semicolon.rb +47 -4
  151. data/lib/rubocop/cop/style/inverse_methods.rb +24 -2
  152. data/lib/rubocop/cop/style/it_block_parameter.rb +32 -3
  153. data/lib/rubocop/cop/style/keyword_parameters_order.rb +3 -1
  154. data/lib/rubocop/cop/style/lambda.rb +18 -0
  155. data/lib/rubocop/cop/style/lambda_call.rb +7 -2
  156. data/lib/rubocop/cop/style/map_into_array.rb +1 -2
  157. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +45 -10
  158. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +25 -2
  159. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +26 -3
  160. data/lib/rubocop/cop/style/method_def_parentheses.rb +29 -3
  161. data/lib/rubocop/cop/style/missing_else.rb +7 -1
  162. data/lib/rubocop/cop/style/multiline_if_modifier.rb +45 -5
  163. data/lib/rubocop/cop/style/multiline_in_pattern_then.rb +7 -0
  164. data/lib/rubocop/cop/style/multiline_method_signature.rb +2 -5
  165. data/lib/rubocop/cop/style/multiline_ternary_operator.rb +7 -3
  166. data/lib/rubocop/cop/style/multiline_when_then.rb +7 -0
  167. data/lib/rubocop/cop/style/mutable_constant.rb +28 -1
  168. data/lib/rubocop/cop/style/negated_if_else_condition.rb +2 -1
  169. data/lib/rubocop/cop/style/negative_array_index.rb +9 -1
  170. data/lib/rubocop/cop/style/nested_modifier.rb +27 -8
  171. data/lib/rubocop/cop/style/nested_ternary_operator.rb +26 -8
  172. data/lib/rubocop/cop/style/next.rb +24 -10
  173. data/lib/rubocop/cop/style/nil_comparison.rb +16 -4
  174. data/lib/rubocop/cop/style/non_nil_check.rb +1 -1
  175. data/lib/rubocop/cop/style/not.rb +10 -5
  176. data/lib/rubocop/cop/style/numeric_predicate.rb +21 -5
  177. data/lib/rubocop/cop/style/one_line_conditional.rb +13 -2
  178. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +14 -3
  179. data/lib/rubocop/cop/style/reduce_to_hash.rb +17 -1
  180. data/lib/rubocop/cop/style/redundant_begin.rb +20 -3
  181. data/lib/rubocop/cop/style/redundant_condition.rb +30 -13
  182. data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +6 -0
  183. data/lib/rubocop/cop/style/redundant_exception.rb +15 -3
  184. data/lib/rubocop/cop/style/redundant_fetch_block.rb +9 -1
  185. data/lib/rubocop/cop/style/redundant_freeze.rb +10 -1
  186. data/lib/rubocop/cop/style/redundant_parentheses.rb +12 -1
  187. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +7 -0
  188. data/lib/rubocop/cop/style/redundant_regexp_constructor.rb +20 -0
  189. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +4 -0
  190. data/lib/rubocop/cop/style/regexp_literal.rb +75 -26
  191. data/lib/rubocop/cop/style/require_order.rb +9 -0
  192. data/lib/rubocop/cop/style/rescue_modifier.rb +5 -9
  193. data/lib/rubocop/cop/style/return_nil.rb +17 -2
  194. data/lib/rubocop/cop/style/return_nil_in_predicate_method_definition.rb +11 -1
  195. data/lib/rubocop/cop/style/sample.rb +9 -8
  196. data/lib/rubocop/cop/style/self_assignment.rb +12 -15
  197. data/lib/rubocop/cop/style/semicolon.rb +16 -5
  198. data/lib/rubocop/cop/style/single_line_methods.rb +16 -4
  199. data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +29 -5
  200. data/lib/rubocop/cop/style/string_concatenation.rb +9 -3
  201. data/lib/rubocop/cop/style/super_arguments.rb +3 -0
  202. data/lib/rubocop/cop/style/symbol_proc.rb +23 -5
  203. data/lib/rubocop/cop/style/ternary_parentheses.rb +23 -1
  204. data/lib/rubocop/cop/style/trailing_underscore_variable.rb +23 -19
  205. data/lib/rubocop/cop/style/while_until_modifier.rb +1 -1
  206. data/lib/rubocop/cop/style/yoda_condition.rb +24 -9
  207. data/lib/rubocop/cop/style/yoda_expression.rb +10 -1
  208. data/lib/rubocop/cop/team.rb +10 -5
  209. data/lib/rubocop/cop/variable_force/assignment.rb +1 -1
  210. data/lib/rubocop/cop/variable_force/variable.rb +14 -1
  211. data/lib/rubocop/formatter/base_formatter.rb +22 -0
  212. data/lib/rubocop/formatter/formatter_set.rb +4 -0
  213. data/lib/rubocop/formatter/json_formatter.rb +71 -1
  214. data/lib/rubocop/formatter/offense_count_formatter.rb +3 -1
  215. data/lib/rubocop/formatter/pacman_formatter.rb +3 -1
  216. data/lib/rubocop/formatter/sarif_formatter.rb +12 -7
  217. data/lib/rubocop/formatter/worst_offenders_formatter.rb +3 -1
  218. data/lib/rubocop/lsp/runtime.rb +47 -11
  219. data/lib/rubocop/lsp/stdin_runner.rb +11 -5
  220. data/lib/rubocop/lsp.rb +5 -2
  221. data/lib/rubocop/mcp/autocorrection_request.rb +52 -0
  222. data/lib/rubocop/mcp/explain_tool.rb +68 -0
  223. data/lib/rubocop/mcp/scope.rb +65 -0
  224. data/lib/rubocop/mcp/server.rb +184 -76
  225. data/lib/rubocop/mcp/source_files.rb +35 -0
  226. data/lib/rubocop/offense_limit.rb +30 -0
  227. data/lib/rubocop/options.rb +45 -10
  228. data/lib/rubocop/plugin/configuration_integrator.rb +12 -5
  229. data/lib/rubocop/remote_config.rb +4 -1
  230. data/lib/rubocop/result_cache.rb +20 -13
  231. data/lib/rubocop/runner.rb +109 -41
  232. data/lib/rubocop/server/cache.rb +2 -2
  233. data/lib/rubocop/util.rb +69 -0
  234. data/lib/rubocop/version.rb +1 -1
  235. data/lib/rubocop.rb +5 -0
  236. metadata +16 -5
@@ -78,7 +78,10 @@ module RuboCop
78
78
  ].freeze
79
79
 
80
80
  def on_if(node)
81
- return if node.modifier_form? || node.elsif_conditional? || !offense?(node)
81
+ return unless redundant_condition_offense?(node)
82
+ return if node.each_ancestor(:if).any? do |ancestor|
83
+ redundant_condition_offense?(ancestor)
84
+ end
82
85
 
83
86
  message = message(node)
84
87
 
@@ -89,6 +92,10 @@ module RuboCop
89
92
 
90
93
  private
91
94
 
95
+ def redundant_condition_offense?(node)
96
+ !node.modifier_form? && !node.elsif_conditional? && offense?(node)
97
+ end
98
+
92
99
  def message(node)
93
100
  if redundant_condition?(node)
94
101
  REDUNDANT_CONDITION
@@ -240,10 +247,10 @@ module RuboCop
240
247
  end
241
248
 
242
249
  # rubocop:disable-next Metrics/AbcSize
243
- def if_source(if_branch, arithmetic_operation)
250
+ def if_source(if_branch, binary_operator)
244
251
  if branches_have_method?(if_branch.parent) && if_branch.parenthesized?
245
252
  if_branch.source.delete_suffix(')')
246
- elsif arithmetic_operation
253
+ elsif binary_operator
247
254
  argument_source = if_branch.first_argument.source
248
255
 
249
256
  "#{if_branch.receiver.source} #{if_branch.method_name} (#{argument_source}"
@@ -257,8 +264,8 @@ module RuboCop
257
264
  end
258
265
  end
259
266
 
260
- def else_source(else_branch, arithmetic_operation) # rubocop:disable Metrics/AbcSize
261
- if arithmetic_operation
267
+ def else_source(else_branch, binary_operator) # rubocop:disable Metrics/AbcSize
268
+ if binary_operator
262
269
  "#{else_branch.first_argument.source})"
263
270
  elsif branches_have_method?(else_branch.parent)
264
271
  else_source_if_has_method(else_branch)
@@ -295,11 +302,11 @@ module RuboCop
295
302
 
296
303
  def make_ternary_form(node)
297
304
  _condition, if_branch, else_branch = *node # rubocop:disable InternalAffairs/NodeDestructuring -- takes all three branches in one step
298
- arithmetic_operation = use_arithmetic_operation?(if_branch)
305
+ binary_operator = branches_have_method?(node) && binary_operator?(if_branch)
299
306
 
300
307
  ternary_form = [
301
- if_source(if_branch, arithmetic_operation),
302
- else_source(else_branch, arithmetic_operation)
308
+ if_source(if_branch, binary_operator),
309
+ else_source(else_branch, binary_operator)
303
310
  ].join(' || ')
304
311
  ternary_form += ')' if branches_have_method?(node) && if_branch.parenthesized?
305
312
 
@@ -320,17 +327,27 @@ module RuboCop
320
327
 
321
328
  def require_parentheses?(node)
322
329
  (node.basic_conditional? && node.modifier_form?) ||
323
- node.range_type? ||
324
- node.rescue_type? ||
325
- (node.respond_to?(:semantic_operator?) && node.semantic_operator?)
330
+ node.range_type? || node.rescue_type? ||
331
+ (node.respond_to?(:semantic_operator?) && node.semantic_operator?) ||
332
+ low_precedence_keyword?(node)
333
+ end
334
+
335
+ # e.g. `not foo`, `return foo`, and `yield foo`
336
+ def low_precedence_keyword?(node)
337
+ return node.prefix_not? if node.send_type?
338
+
339
+ node.type?(:return, :next, :break, :yield, :super) &&
340
+ node.arguments? && !node.parenthesized?
326
341
  end
327
342
 
328
343
  def require_braces?(node)
329
344
  node.hash_type? && !node.braces?
330
345
  end
331
346
 
332
- def use_arithmetic_operation?(node)
333
- node.respond_to?(:arithmetic_operation?) && node.arithmetic_operation?
347
+ # Any binary operator binds tighter than `||`, so the fallback must be
348
+ # parenthesized as its argument, e.g. `stack << (foo || :new)`.
349
+ def binary_operator?(node)
350
+ node.binary_operation? && !node.dot?
334
351
  end
335
352
 
336
353
  def without_argument_parentheses_method?(node)
@@ -25,6 +25,12 @@ module RuboCop
25
25
  MSG = 'Remove the redundant double splat and braces, use keyword arguments directly.'
26
26
  MERGE_METHODS = %i[merge merge!].freeze
27
27
 
28
+ # `Style/TrailingCommaInHashLiteral` adds a comma to the hash literal whose braces
29
+ # this cop removes in the same pass, which would leave a dangling comma.
30
+ def self.autocorrect_incompatible_with
31
+ [Style::TrailingCommaInHashLiteral]
32
+ end
33
+
28
34
  # rubocop:disable-next Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
29
35
  def on_hash(node)
30
36
  return if node.pairs.empty? || node.pairs.any?(&:hash_rocket?)
@@ -49,7 +49,7 @@ module RuboCop
49
49
  end
50
50
 
51
51
  def replaced_exploded(node, command, message)
52
- arg = string_message?(message) ? message.source : "#{message.source}.to_s"
52
+ arg = message_source(message)
53
53
  arg = node.parenthesized? ? "(#{arg})" : " #{arg}"
54
54
  "#{command}#{arg}"
55
55
  end
@@ -63,19 +63,31 @@ module RuboCop
63
63
  next if message.nil_type?
64
64
 
65
65
  add_offense(node, message: MSG_2) do |corrector|
66
- corrector.replace(new_call, replaced_compact(message))
66
+ corrector.replace(new_call, message_source(message))
67
67
  end
68
68
  end
69
69
  end
70
70
 
71
- def replaced_compact(message)
71
+ def message_source(message)
72
72
  if string_message?(message)
73
73
  message.source
74
+ elsif require_parentheses?(message)
75
+ "(#{message.source}).to_s"
74
76
  else
75
77
  "#{message.source}.to_s"
76
78
  end
77
79
  end
78
80
 
81
+ def require_parentheses?(message)
82
+ if message.call_type?
83
+ !message.method?(:[]) &&
84
+ (message.operator_method? || (message.arguments? && !message.parenthesized?))
85
+ else
86
+ !(message.basic_literal? || message.variable? ||
87
+ message.type?(:const, :self, :begin, :array))
88
+ end
89
+ end
90
+
79
91
  # @!method exploded?(node)
80
92
  def_node_matcher :exploded?, <<~PATTERN
81
93
  (send nil? ${:raise :fail} (const {nil? cbase} :RuntimeError) $_)
@@ -74,7 +74,8 @@ module RuboCop
74
74
  def should_not_check?(send, body)
75
75
  (body&.const_type? && !check_for_constant?) ||
76
76
  (body&.str_type? && !check_for_string?) ||
77
- rails_cache?(send.receiver)
77
+ rails_cache?(send.receiver) ||
78
+ unappendable_argument?(send)
78
79
  end
79
80
 
80
81
  # @!method rails_cache?(node)
@@ -107,6 +108,13 @@ module RuboCop
107
108
  def check_for_string?
108
109
  frozen_string_literals_enabled?
109
110
  end
111
+
112
+ def unappendable_argument?(send)
113
+ argument = send.first_argument
114
+
115
+ argument.type?(:splat, :forwarded_restarg) ||
116
+ (argument.hash_type? && !argument.braces?)
117
+ end
110
118
  end
111
119
  end
112
120
  end
@@ -57,12 +57,21 @@ module RuboCop
57
57
  def_node_matcher :operation_produces_immutable_object?, <<~PATTERN
58
58
  {
59
59
  (begin (send {float int} {:+ :- :* :** :/ :% :<<} _))
60
- (begin (send !{(str _) array} {:+ :- :* :** :/ :%} {float int}))
60
+ (begin (send #numeric_operation? {:+ :- :* :** :/ :%} {float int}))
61
61
  (begin (send _ {:== :=== :!= :<= :>= :< :>} _))
62
62
  (send _ {:count :length :size} ...)
63
63
  (any_block (send _ {:count :length :size} ...) ...)
64
64
  }
65
65
  PATTERN
66
+
67
+ # @!method numeric_operation?(node)
68
+ def_node_matcher :numeric_operation?, <<~PATTERN
69
+ {
70
+ float int
71
+ (begin #numeric_operation?)
72
+ (send #numeric_operation? {:+ :- :* :** :/ :%} #numeric_operation?)
73
+ }
74
+ PATTERN
66
75
  end
67
76
  end
68
77
  end
@@ -66,7 +66,8 @@ module RuboCop
66
66
  rescue?(node) ||
67
67
  in_pattern_matching_in_method_argument?(node) ||
68
68
  allowed_pin_operator?(node) ||
69
- allowed_expression?(node)
69
+ allowed_expression?(node) ||
70
+ ParenthesesCorrector.heredoc_strands_code_after_close_paren?(node)
70
71
  end
71
72
 
72
73
  def ignore_syntax?(node)
@@ -232,12 +233,22 @@ module RuboCop
232
233
  node = node.send_node if node.any_block_type?
233
234
 
234
235
  return check_unary(begin_node, node) if node.unary_operation?
236
+ return if setter_in_condition?(begin_node, node)
235
237
 
236
238
  return unless method_call_with_redundant_parentheses?(begin_node, node)
237
239
 
238
240
  offense(begin_node, 'a method call')
239
241
  end
240
242
 
243
+ # Like a variable assignment, `if (foo.bar = baz)` is a safe assignment
244
+ # that `Lint/AssignmentInCondition` asks for, not redundant parentheses.
245
+ def setter_in_condition?(begin_node, node)
246
+ return false unless node.setter_method?
247
+ return false unless (parent = begin_node.parent)&.basic_conditional?
248
+
249
+ parent.condition.equal?(begin_node)
250
+ end
251
+
241
252
  def check_unary(begin_node, node)
242
253
  return if begin_node.chained?
243
254
 
@@ -90,6 +90,8 @@ module RuboCop
90
90
  quote = enforce_double_quotes? ? '"' : "'"
91
91
  end
92
92
 
93
+ new_argument = escape_interpolation_like_text(new_argument) if quote == '"'
94
+
93
95
  "#{quote}#{new_argument}#{quote}"
94
96
  end
95
97
 
@@ -109,6 +111,11 @@ module RuboCop
109
111
  char
110
112
  end.join
111
113
  end
114
+
115
+ # `#{`, `#$` and `#@` would start an interpolation in a double-quoted string.
116
+ def escape_interpolation_like_text(string)
117
+ string.gsub(/#(?=[{$@])/) { '\#' }
118
+ end
112
119
  end
113
120
  end
114
121
  end
@@ -34,12 +34,32 @@ module RuboCop
34
34
  return unless (regexp = redundant_regexp_constructor(node))
35
35
 
36
36
  add_offense(node, message: format(MSG, method: node.method_name)) do |corrector|
37
+ next if ambiguous_slash_regexp?(node, regexp)
38
+
37
39
  # Reuse the inner literal's own source so its delimiters are preserved.
38
40
  # Forcing `/.../` would break patterns like `%r{foo/bar}` that contain
39
41
  # an unescaped slash.
40
42
  corrector.replace(node, regexp.source)
41
43
  end
42
44
  end
45
+
46
+ private
47
+
48
+ # `p / a/` and `p /=a/` parse as division, so a slash regexp starting
49
+ # with whitespace or `=` can't begin the first argument of a command.
50
+ def ambiguous_slash_regexp?(node, regexp)
51
+ return false unless regexp.slash_literal? && regexp.content.match?(/\A[\s=]/)
52
+
53
+ begin_pos = node.source_range.begin_pos
54
+
55
+ node.each_ancestor(:call).any? do |call|
56
+ command?(call) && call.first_argument.source_range.begin_pos == begin_pos
57
+ end
58
+ end
59
+
60
+ def command?(call)
61
+ call.arguments? && !call.parenthesized? && !call.operator_method? && !call.setter_method?
62
+ end
43
63
  end
44
64
  end
45
65
  end
@@ -43,6 +43,10 @@ module RuboCop
43
43
  ALLOWED_OUTSIDE_CHAR_CLASS_METACHAR_ESCAPES = '.*+?{}()|$'.chars.freeze
44
44
  INTERPOLATION_SIGILS = %w[@ $].freeze
45
45
 
46
+ def self.autocorrect_incompatible_with
47
+ [Style::RegexpLiteral]
48
+ end
49
+
46
50
  def on_regexp(node)
47
51
  each_escape(node) do |char, index, within_character_class|
48
52
  next if char.valid_encoding? && allowed_escape?(node, char, index,
@@ -104,6 +104,7 @@ module RuboCop
104
104
  ['{', '}'] => /\\.|[{}]/,
105
105
  ['<', '>'] => /\\.|[<>]/
106
106
  }.freeze
107
+ DELIMITERS_KEEPING_ESCAPE = %w[$ * + . ? ^ | ( ) [ ] { } < >].freeze
107
108
 
108
109
  def on_regexp(node)
109
110
  return if slash_literal?(node) && percent_r_delimiters_conflict?(node)
@@ -119,6 +120,7 @@ module RuboCop
119
120
  add_offense(node, message: message) do |corrector|
120
121
  correct_delimiters(node, corrector)
121
122
  correct_inner_slashes(node, corrector)
123
+ correct_escaped_delimiters(node, corrector)
122
124
  end
123
125
  end
124
126
 
@@ -174,9 +176,8 @@ module RuboCop
174
176
  cop_config['AllowInnerSlashes']
175
177
  end
176
178
 
177
- def node_body(node, include_begin_nodes: false)
178
- types = include_begin_nodes ? %i[str begin] : %i[str]
179
- node.each_child_node(*types).map(&:source).join
179
+ def node_body(node)
180
+ node.each_child_node(:str).map(&:source).join
180
181
  end
181
182
 
182
183
  def slash_literal?(node)
@@ -190,12 +191,20 @@ module RuboCop
190
191
  def allowed_omit_parentheses_with_percent_r_literal?(node)
191
192
  return false unless node.parent&.call_type?
192
193
  return true if node.content.start_with?(' ', '=')
194
+ return true if first_argument_of_command?(node) && node.content.match?(/\A\s/)
193
195
 
194
196
  enforced_style = config.for_cop('Style/MethodCallWithArgsParentheses')['EnforcedStyle']
195
197
 
196
198
  enforced_style == 'omit_parentheses'
197
199
  end
198
200
 
201
+ def first_argument_of_command?(node)
202
+ call = node.parent
203
+ return false unless call.first_argument.equal?(node)
204
+
205
+ !call.parenthesized? && !call.operator_method? && !call.setter_method?
206
+ end
207
+
199
208
  def correct_delimiters(node, corrector)
200
209
  replacement = calculate_replacement(node)
201
210
  corrector.replace(node.loc.begin, replacement.first)
@@ -203,32 +212,16 @@ module RuboCop
203
212
  end
204
213
 
205
214
  def correct_inner_slashes(node, corrector)
206
- regexp_begin = node.loc.begin.end_pos
207
-
208
- inner_slash_indices(node).each do |index|
209
- start = regexp_begin + index
210
-
211
- corrector.replace(
212
- range_between(
213
- start,
214
- start + inner_slash_before_correction(node).length
215
- ),
216
- inner_slash_after_correction(node)
217
- )
218
- end
219
- end
220
-
221
- def inner_slash_indices(node)
222
- text = node_body(node, include_begin_nodes: true)
223
215
  pattern = inner_slash_before_correction(node)
224
- index = -1
225
- indices = []
216
+ replacement = inner_slash_after_correction(node)
226
217
 
227
- while (index = text.index(pattern, index + 1))
228
- indices << index
229
- end
218
+ node.each_child_node(:str) do |str_node|
219
+ inner_slash_indices(str_node.source, pattern).each do |index|
220
+ start = str_node.source_range.begin_pos + index
230
221
 
231
- indices
222
+ corrector.replace(range_between(start, start + pattern.length), replacement)
223
+ end
224
+ end
232
225
  end
233
226
 
234
227
  def inner_slash_before_correction(node)
@@ -239,6 +232,17 @@ module RuboCop
239
232
  inner_slash_for(calculate_replacement(node).first)
240
233
  end
241
234
 
235
+ def inner_slash_indices(text, pattern)
236
+ index = -1
237
+ indices = []
238
+
239
+ while (index = text.index(pattern, index + 1))
240
+ indices << index
241
+ end
242
+
243
+ indices
244
+ end
245
+
242
246
  def inner_slash_for(opening_delimiter)
243
247
  if ['/', '%r/'].include?(opening_delimiter)
244
248
  '\/'
@@ -254,6 +258,51 @@ module RuboCop
254
258
  %w[/ /]
255
259
  end
256
260
  end
261
+
262
+ def correct_escaped_delimiters(node, corrector)
263
+ return unless (delimiter = delimiter_to_unescape(node))
264
+
265
+ node.each_child_node(:str) do |str_node|
266
+ text = str_node.source
267
+
268
+ escaped_delimiter_indices(text, delimiter).each do |index|
269
+ start = str_node.source_range.begin_pos + index
270
+ replacement = escaped_delimiter_replacement(node, delimiter, text[index + 2])
271
+
272
+ corrector.replace(range_between(start, start + 2), replacement)
273
+ end
274
+ end
275
+ end
276
+
277
+ def delimiter_to_unescape(node)
278
+ return if slash_literal?(node)
279
+
280
+ delimiter = node.delimiters.first
281
+
282
+ delimiter unless delimiter == '/' || DELIMITERS_KEEPING_ESCAPE.include?(delimiter)
283
+ end
284
+
285
+ def escaped_delimiter_indices(text, delimiter)
286
+ indices = []
287
+ index = 0
288
+
289
+ while (index = text.index('\\', index))
290
+ indices << index if text[index + 1] == delimiter
291
+ index += 2
292
+ end
293
+
294
+ indices
295
+ end
296
+
297
+ # A bare `#` before `{`, `@` or `$` would start an interpolation between slashes.
298
+ # `\#` still reads as a literal `#` there, except in extended mode, where `%r#a\#{b}#x`
299
+ # reads `#{b}` as a comment; `#\{` keeps both that comment and a `#` inside
300
+ # a character class.
301
+ def escaped_delimiter_replacement(node, delimiter, next_char)
302
+ return delimiter unless delimiter == '#' && next_char&.match?(/[{@$]/)
303
+
304
+ node.extended? ? '#\\' : '\\#'
305
+ end
257
306
  end
258
307
  end
259
308
  end
@@ -88,6 +88,8 @@ module RuboCop
88
88
  return unless previous_older_sibling
89
89
 
90
90
  add_offense(node, message: format(MSG, name: node.method_name)) do |corrector|
91
+ next unless own_lines?(previous_older_sibling) && own_lines?(search_node(node))
92
+
91
93
  autocorrect(corrector, node, previous_older_sibling)
92
94
  end
93
95
  end
@@ -112,6 +114,13 @@ module RuboCop
112
114
  end
113
115
  end
114
116
 
117
+ def own_lines?(node)
118
+ range = node.source_range
119
+ rest_of_line = range.source_buffer.source_line(range.last_line)[range.last_column..]
120
+
121
+ begins_its_line?(range) && rest_of_line.match?(/\A\s*(#.*)?\z/)
122
+ end
123
+
115
124
  def autocorrect(corrector, node, previous_older_sibling)
116
125
  range1 = range_with_comments_and_lines(previous_older_sibling)
117
126
  range2 = range_with_comments_and_lines(node.parent.if_type? ? node.parent : node)
@@ -54,6 +54,9 @@ module RuboCop
54
54
 
55
55
  rescue_node = node.parent
56
56
  add_offense(rescue_node) do |corrector|
57
+ # The heredoc body would be left behind when the rescue body is moved.
58
+ next if heredoc_end(node)
59
+
57
60
  parenthesized = parenthesized?(rescue_node)
58
61
 
59
62
  correct_rescue_block(corrector, rescue_node, parenthesized)
@@ -95,15 +98,8 @@ module RuboCop
95
98
  end
96
99
 
97
100
  def heredoc_end(node)
98
- return unless node.call_type?
99
-
100
- heredoc = node.arguments.reverse.find do |argument|
101
- argument.respond_to?(:heredoc?) && argument.heredoc?
102
- end
103
-
104
- return unless heredoc
105
-
106
- heredoc.loc.heredoc_end
101
+ node.each_node(:any_str).select(&:heredoc?).map { |heredoc| heredoc.loc.heredoc_end }
102
+ .max_by(&:end_pos)
107
103
  end
108
104
  end
109
105
  end
@@ -65,7 +65,7 @@ module RuboCop
65
65
  return if correct_style?(node)
66
66
 
67
67
  add_offense(node) do |corrector|
68
- corrected = style == :return ? 'return' : 'return nil'
68
+ corrected = style == :return ? 'return' : return_nil(node)
69
69
 
70
70
  corrector.replace(node, corrected)
71
71
  end
@@ -79,13 +79,28 @@ module RuboCop
79
79
 
80
80
  def correct_style?(node)
81
81
  (style == :return && !return_nil_node?(node)) ||
82
- (style == :return_nil && !return_node?(node))
82
+ (style == :return_nil && (!return_node?(node) || top_level_return?(node)))
83
+ end
84
+
85
+ def top_level_return?(node)
86
+ node.each_ancestor(:any_block, :any_def).none?
83
87
  end
84
88
 
85
89
  def scoped_node?(node)
86
90
  node.any_def_type? || node.lambda?
87
91
  end
88
92
 
93
+ def return_nil(node)
94
+ return 'return nil' unless (parent = node.parent)
95
+
96
+ if (parent.operator_keyword? && parent.logical_operator?) ||
97
+ (parent.if_type? && parent.ternary?)
98
+ '(return nil)'
99
+ else
100
+ 'return nil'
101
+ end
102
+ end
103
+
89
104
  # @!method chained_send?(node)
90
105
  def_node_matcher :chained_send?, '(send !nil? ...)'
91
106
 
@@ -116,7 +116,17 @@ module RuboCop
116
116
  end
117
117
 
118
118
  def handle_return(return_node)
119
- register_offense(return_node, 'return false') if return_nil?(return_node)
119
+ return unless return_nil?(return_node)
120
+
121
+ replacement = requires_parentheses?(return_node) ? '(return false)' : 'return false'
122
+ register_offense(return_node, replacement)
123
+ end
124
+
125
+ def requires_parentheses?(node)
126
+ parent = node.parent
127
+
128
+ (parent.operator_keyword? && parent.logical_operator?) ||
129
+ (parent.if_type? && parent.ternary?)
120
130
  end
121
131
 
122
132
  def handle_nil(nil_node)
@@ -98,22 +98,23 @@ module RuboCop
98
98
  second.int_type? ? second.to_a.first : :unknown
99
99
  end
100
100
 
101
- # rubocop:disable-next Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
102
101
  def range_size(range_node)
103
- vals = range_node.to_a
104
- return :unknown unless vals.all? { |val| val.nil? || val.int_type? }
105
-
106
- low, high = vals.map { |val| val.nil? ? 0 : val.children[0] }
107
- return :unknown unless low.zero? && high >= 0
102
+ low, high = range_node.to_a
103
+ # An endless range takes the rest of the collection, whatever its size.
104
+ return :unknown unless zero_or_beginless?(low) && high&.int_type? && high.value >= 0
108
105
 
109
106
  case range_node.type
110
107
  when :erange
111
- (low...high).size
108
+ high.value
112
109
  when :irange
113
- (low..high).size
110
+ high.value + 1
114
111
  end
115
112
  end
116
113
 
114
+ def zero_or_beginless?(low)
115
+ low.nil? || (low.int_type? && low.value.zero?)
116
+ end
117
+
117
118
  def source_range(shuffle_node, node)
118
119
  shuffle_node.loc.selector.join(node.source_range.end)
119
120
  end
@@ -37,15 +37,20 @@ module RuboCop
37
37
  private
38
38
 
39
39
  def check(node, var_type)
40
- return unless (rhs = node.expression)
40
+ return unless (rhs = unparenthesized(node.expression))
41
41
 
42
42
  if rhs.send_type? && rhs.arguments.one?
43
43
  check_send_node(node, rhs, node.name, var_type)
44
- elsif rhs.operator_keyword?
44
+ elsif rhs.operator_keyword? && rhs.logical_operator?
45
45
  check_boolean_node(node, rhs, node.name, var_type)
46
46
  end
47
47
  end
48
48
 
49
+ def unparenthesized(node)
50
+ node = node.children.first while node&.begin_type? && node.children.one?
51
+ node
52
+ end
53
+
49
54
  def check_send_node(node, rhs, var_name, var_type)
50
55
  return unless OPS.include?(rhs.method_name)
51
56
 
@@ -68,26 +73,18 @@ module RuboCop
68
73
  end
69
74
 
70
75
  def autocorrect(corrector, node)
71
- rhs = node.expression
76
+ rhs = unparenthesized(node.expression)
72
77
 
73
78
  if rhs.send_type?
74
- autocorrect_send_node(corrector, node, rhs)
79
+ apply_autocorrect(corrector, node, rhs.method_name, rhs.first_argument)
75
80
  elsif rhs.operator_keyword?
76
- autocorrect_boolean_node(corrector, node, rhs)
81
+ apply_autocorrect(corrector, node, rhs.loc.operator.source, rhs.rhs)
77
82
  end
78
83
  end
79
84
 
80
- def autocorrect_send_node(corrector, node, rhs)
81
- apply_autocorrect(corrector, node, rhs, rhs.method_name, rhs.first_argument)
82
- end
83
-
84
- def autocorrect_boolean_node(corrector, node, rhs)
85
- apply_autocorrect(corrector, node, rhs, rhs.loc.operator.source, rhs.rhs)
86
- end
87
-
88
- def apply_autocorrect(corrector, node, rhs, operator, new_rhs)
85
+ def apply_autocorrect(corrector, node, operator, new_rhs)
89
86
  corrector.insert_before(node.loc.operator, operator)
90
- corrector.replace(rhs, new_rhs.source)
87
+ corrector.replace(node.expression, new_rhs.source)
91
88
  end
92
89
  end
93
90
  end