rubocop 1.91.0 → 1.92.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (278) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +4 -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.rb +0 -4
  14. data/lib/rubocop/config_loader.rb +6 -3
  15. data/lib/rubocop/config_loader_resolver.rb +37 -12
  16. data/lib/rubocop/config_obsoletion/removed_cop.rb +1 -1
  17. data/lib/rubocop/config_obsoletion.rb +9 -0
  18. data/lib/rubocop/config_validator.rb +11 -0
  19. data/lib/rubocop/cop/autocorrect_logic.rb +10 -5
  20. data/lib/rubocop/cop/badge.rb +1 -1
  21. data/lib/rubocop/cop/base.rb +42 -21
  22. data/lib/rubocop/cop/bundler/gem_comment.rb +0 -1
  23. data/lib/rubocop/cop/bundler/ordered_gems.rb +0 -8
  24. data/lib/rubocop/cop/cop_documentation.rb +81 -0
  25. data/lib/rubocop/cop/cop_explanation.rb +140 -0
  26. data/lib/rubocop/cop/correctors/alignment_corrector.rb +13 -1
  27. data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +37 -7
  28. data/lib/rubocop/cop/correctors/empty_line_corrector.rb +1 -1
  29. data/lib/rubocop/cop/correctors/lambda_literal_to_method_corrector.rb +25 -4
  30. data/lib/rubocop/cop/correctors/multiline_literal_brace_corrector.rb +34 -0
  31. data/lib/rubocop/cop/correctors/parentheses_corrector.rb +45 -8
  32. data/lib/rubocop/cop/documentation.rb +1 -1
  33. data/lib/rubocop/cop/gemspec/dependency_version.rb +0 -6
  34. data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +0 -6
  35. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +2 -5
  36. data/lib/rubocop/cop/internal_affairs/node_type_group.rb +0 -7
  37. data/lib/rubocop/cop/internal_affairs/redundant_context_config_parameter.rb +1 -7
  38. data/lib/rubocop/cop/internal_affairs/style_detected_api_use.rb +1 -7
  39. data/lib/rubocop/cop/layout/argument_alignment.rb +5 -1
  40. data/lib/rubocop/cop/layout/block_alignment.rb +27 -0
  41. data/lib/rubocop/cop/layout/case_indentation.rb +25 -9
  42. data/lib/rubocop/cop/layout/class_structure.rb +29 -7
  43. data/lib/rubocop/cop/layout/def_end_alignment.rb +4 -0
  44. data/lib/rubocop/cop/layout/dot_position.rb +25 -15
  45. data/lib/rubocop/cop/layout/empty_line_after_multiline_condition.rb +21 -8
  46. data/lib/rubocop/cop/layout/empty_lines.rb +5 -2
  47. data/lib/rubocop/cop/layout/empty_lines_after_module_inclusion.rb +1 -1
  48. data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +1 -0
  49. data/lib/rubocop/cop/layout/empty_lines_around_block_body.rb +9 -3
  50. data/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb +23 -1
  51. data/lib/rubocop/cop/layout/end_alignment.rb +21 -25
  52. data/lib/rubocop/cop/layout/extra_spacing.rb +21 -6
  53. data/lib/rubocop/cop/layout/first_argument_indentation.rb +27 -1
  54. data/lib/rubocop/cop/layout/first_array_element_line_break.rb +15 -0
  55. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +16 -8
  56. data/lib/rubocop/cop/layout/first_method_argument_line_break.rb +6 -0
  57. data/lib/rubocop/cop/layout/first_method_parameter_line_break.rb +4 -0
  58. data/lib/rubocop/cop/layout/hash_alignment.rb +16 -12
  59. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +7 -3
  60. data/lib/rubocop/cop/layout/heredoc_indentation.rb +15 -0
  61. data/lib/rubocop/cop/layout/indentation_consistency.rb +22 -5
  62. data/lib/rubocop/cop/layout/indentation_style.rb +4 -1
  63. data/lib/rubocop/cop/layout/indentation_width.rb +2 -2
  64. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +8 -1
  65. data/lib/rubocop/cop/layout/line_end_string_concatenation_indentation.rb +25 -9
  66. data/lib/rubocop/cop/layout/line_length.rb +176 -26
  67. data/lib/rubocop/cop/layout/multiline_array_brace_layout.rb +12 -0
  68. data/lib/rubocop/cop/layout/multiline_block_layout.rb +8 -1
  69. data/lib/rubocop/cop/layout/multiline_hash_brace_layout.rb +9 -0
  70. data/lib/rubocop/cop/layout/multiline_method_call_brace_layout.rb +6 -0
  71. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +30 -5
  72. data/lib/rubocop/cop/layout/multiline_method_definition_brace_layout.rb +4 -0
  73. data/lib/rubocop/cop/layout/redundant_line_break.rb +22 -1
  74. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +16 -3
  75. data/lib/rubocop/cop/layout/single_line_block_chain.rb +10 -2
  76. data/lib/rubocop/cop/layout/space_after_not.rb +5 -0
  77. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +6 -11
  78. data/lib/rubocop/cop/layout/space_around_keyword.rb +10 -0
  79. data/lib/rubocop/cop/layout/space_around_operators.rb +31 -4
  80. data/lib/rubocop/cop/layout/space_before_block_braces.rb +16 -1
  81. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +15 -2
  82. data/lib/rubocop/cop/layout/space_inside_block_braces.rb +32 -9
  83. data/lib/rubocop/cop/layout/space_inside_range_literal.rb +29 -11
  84. data/lib/rubocop/cop/layout/space_inside_reference_brackets.rb +2 -12
  85. data/lib/rubocop/cop/lint/ambiguous_operator.rb +1 -1
  86. data/lib/rubocop/cop/lint/ambiguous_operator_precedence.rb +2 -1
  87. data/lib/rubocop/cop/lint/ambiguous_regexp_literal.rb +6 -1
  88. data/lib/rubocop/cop/lint/empty_conditional_body.rb +3 -1
  89. data/lib/rubocop/cop/lint/heredoc_method_call_position.rb +4 -0
  90. data/lib/rubocop/cop/lint/interpolation_check.rb +20 -17
  91. data/lib/rubocop/cop/lint/literal_as_condition.rb +15 -0
  92. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +63 -19
  93. data/lib/rubocop/cop/lint/loop.rb +24 -2
  94. data/lib/rubocop/cop/lint/misplaced_magic_comment.rb +71 -11
  95. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +18 -3
  96. data/lib/rubocop/cop/lint/nested_percent_literal.rb +3 -0
  97. data/lib/rubocop/cop/lint/number_conversion.rb +3 -1
  98. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +22 -1
  99. data/lib/rubocop/cop/lint/percent_string_array.rb +75 -9
  100. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +39 -9
  101. data/lib/rubocop/cop/lint/redundant_dir_glob_sort.rb +13 -2
  102. data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +11 -4
  103. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +47 -1
  104. data/lib/rubocop/cop/lint/symbol_conversion.rb +1 -0
  105. data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +18 -1
  106. data/lib/rubocop/cop/lint/useless_assignment.rb +14 -7
  107. data/lib/rubocop/cop/lint/useless_setter_call.rb +7 -4
  108. data/lib/rubocop/cop/lint/void.rb +8 -1
  109. data/lib/rubocop/cop/metrics/class_length.rb +4 -2
  110. data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +0 -4
  111. data/lib/rubocop/cop/metrics/module_length.rb +4 -2
  112. data/lib/rubocop/cop/mixin/alignment.rb +4 -0
  113. data/lib/rubocop/cop/mixin/check_line_breakable.rb +23 -19
  114. data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +8 -2
  115. data/lib/rubocop/cop/mixin/code_length.rb +10 -2
  116. data/lib/rubocop/cop/mixin/comments_help.rb +1 -1
  117. data/lib/rubocop/cop/mixin/empty_lines_around_body.rb +12 -1
  118. data/lib/rubocop/cop/mixin/endless_method_body_help.rb +105 -0
  119. data/lib/rubocop/cop/mixin/forbidden_identifiers.rb +2 -2
  120. data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +41 -6
  121. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +26 -2
  122. data/lib/rubocop/cop/mixin/min_branches_count.rb +3 -1
  123. data/lib/rubocop/cop/mixin/multiline_element_line_breaks.rb +11 -1
  124. data/lib/rubocop/cop/mixin/multiline_literal_brace_layout.rb +9 -0
  125. data/lib/rubocop/cop/mixin/ordered_gem_node.rb +1 -1
  126. data/lib/rubocop/cop/mixin/pattern_matching_help.rb +35 -0
  127. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +17 -2
  128. data/lib/rubocop/cop/mixin/project_index_help.rb +15 -0
  129. data/lib/rubocop/cop/mixin/reparsed_equivalence.rb +13 -3
  130. data/lib/rubocop/cop/mixin/statement_modifier.rb +39 -5
  131. data/lib/rubocop/cop/mixin/trailing_comma.rb +1 -1
  132. data/lib/rubocop/cop/mixin/visibility_help.rb +2 -1
  133. data/lib/rubocop/cop/mixin.rb +2 -0
  134. data/lib/rubocop/cop/naming/accessor_method_name.rb +0 -16
  135. data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
  136. data/lib/rubocop/cop/naming/heredoc_delimiter_case.rb +9 -2
  137. data/lib/rubocop/cop/naming/heredoc_delimiter_naming.rb +5 -3
  138. data/lib/rubocop/cop/naming/predicate_prefix.rb +7 -22
  139. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +1 -1
  140. data/lib/rubocop/cop/offense.rb +59 -3
  141. data/lib/rubocop/cop/registry.rb +1 -1
  142. data/lib/rubocop/cop/security/json_load.rb +16 -4
  143. data/lib/rubocop/cop/style/access_modifier_declarations.rb +37 -2
  144. data/lib/rubocop/cop/style/alias.rb +18 -5
  145. data/lib/rubocop/cop/style/ambiguous_endless_method_definition.rb +4 -3
  146. data/lib/rubocop/cop/style/and_or.rb +37 -10
  147. data/lib/rubocop/cop/style/arguments_forwarding.rb +32 -3
  148. data/lib/rubocop/cop/style/attr.rb +3 -4
  149. data/lib/rubocop/cop/style/bitwise_predicate.rb +6 -4
  150. data/lib/rubocop/cop/style/block_delimiters.rb +35 -3
  151. data/lib/rubocop/cop/style/case_equality.rb +14 -3
  152. data/lib/rubocop/cop/style/case_like_if.rb +6 -3
  153. data/lib/rubocop/cop/style/class_and_module_children.rb +57 -16
  154. data/lib/rubocop/cop/style/class_methods_definitions.rb +15 -0
  155. data/lib/rubocop/cop/style/collection_querying.rb +79 -46
  156. data/lib/rubocop/cop/style/combinable_loops.rb +89 -11
  157. data/lib/rubocop/cop/style/concat_array_literals.rb +58 -7
  158. data/lib/rubocop/cop/style/conditional_assignment.rb +4 -0
  159. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +65 -9
  160. data/lib/rubocop/cop/style/double_negation.rb +11 -0
  161. data/lib/rubocop/cop/style/empty_literal.rb +1 -1
  162. data/lib/rubocop/cop/style/empty_method.rb +3 -1
  163. data/lib/rubocop/cop/style/empty_string_inside_interpolation.rb +53 -11
  164. data/lib/rubocop/cop/style/endless_method.rb +104 -11
  165. data/lib/rubocop/cop/style/exact_regexp_match.rb +45 -8
  166. data/lib/rubocop/cop/style/explicit_block_argument.rb +50 -10
  167. data/lib/rubocop/cop/style/float_division.rb +37 -2
  168. data/lib/rubocop/cop/style/for.rb +53 -2
  169. data/lib/rubocop/cop/style/format_string.rb +28 -6
  170. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +2 -1
  171. data/lib/rubocop/cop/style/guard_clause.rb +31 -22
  172. data/lib/rubocop/cop/style/hash_as_last_array_item.rb +4 -0
  173. data/lib/rubocop/cop/style/hash_conversion.rb +28 -8
  174. data/lib/rubocop/cop/style/hash_lookup_method.rb +42 -9
  175. data/lib/rubocop/cop/style/hash_syntax.rb +16 -3
  176. data/lib/rubocop/cop/style/identical_conditional_branches.rb +22 -3
  177. data/lib/rubocop/cop/style/if_inside_else.rb +21 -1
  178. data/lib/rubocop/cop/style/if_unless_modifier.rb +32 -26
  179. data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +4 -0
  180. data/lib/rubocop/cop/style/if_with_semicolon.rb +47 -4
  181. data/lib/rubocop/cop/style/inverse_methods.rb +24 -2
  182. data/lib/rubocop/cop/style/it_block_parameter.rb +94 -4
  183. data/lib/rubocop/cop/style/keyword_parameters_order.rb +3 -1
  184. data/lib/rubocop/cop/style/lambda.rb +34 -1
  185. data/lib/rubocop/cop/style/lambda_call.rb +7 -2
  186. data/lib/rubocop/cop/style/map_into_array.rb +1 -2
  187. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +80 -13
  188. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +26 -2
  189. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +26 -3
  190. data/lib/rubocop/cop/style/method_def_parentheses.rb +38 -4
  191. data/lib/rubocop/cop/style/missing_else.rb +7 -1
  192. data/lib/rubocop/cop/style/mixin_grouping.rb +12 -3
  193. data/lib/rubocop/cop/style/multiline_if_modifier.rb +45 -5
  194. data/lib/rubocop/cop/style/multiline_in_pattern_then.rb +7 -0
  195. data/lib/rubocop/cop/style/multiline_method_signature.rb +2 -5
  196. data/lib/rubocop/cop/style/multiline_ternary_operator.rb +7 -3
  197. data/lib/rubocop/cop/style/multiline_when_then.rb +7 -0
  198. data/lib/rubocop/cop/style/mutable_constant.rb +52 -7
  199. data/lib/rubocop/cop/style/negated_if_else_condition.rb +2 -1
  200. data/lib/rubocop/cop/style/negative_array_index.rb +9 -1
  201. data/lib/rubocop/cop/style/nested_modifier.rb +27 -8
  202. data/lib/rubocop/cop/style/nested_ternary_operator.rb +26 -8
  203. data/lib/rubocop/cop/style/next.rb +24 -10
  204. data/lib/rubocop/cop/style/nil_comparison.rb +16 -4
  205. data/lib/rubocop/cop/style/non_nil_check.rb +1 -1
  206. data/lib/rubocop/cop/style/not.rb +10 -5
  207. data/lib/rubocop/cop/style/numeric_predicate.rb +28 -5
  208. data/lib/rubocop/cop/style/one_line_conditional.rb +13 -2
  209. data/lib/rubocop/cop/style/parentheses_around_condition.rb +19 -1
  210. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +14 -3
  211. data/lib/rubocop/cop/style/quoted_symbols.rb +5 -7
  212. data/lib/rubocop/cop/style/raise_args.rb +80 -11
  213. data/lib/rubocop/cop/style/reduce_to_hash.rb +47 -33
  214. data/lib/rubocop/cop/style/redundant_begin.rb +45 -5
  215. data/lib/rubocop/cop/style/redundant_condition.rb +30 -13
  216. data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +6 -0
  217. data/lib/rubocop/cop/style/redundant_exception.rb +27 -11
  218. data/lib/rubocop/cop/style/redundant_fetch_block.rb +9 -1
  219. data/lib/rubocop/cop/style/redundant_freeze.rb +11 -3
  220. data/lib/rubocop/cop/style/redundant_interpolation_unfreeze.rb +24 -1
  221. data/lib/rubocop/cop/style/redundant_parentheses.rb +12 -1
  222. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +7 -0
  223. data/lib/rubocop/cop/style/redundant_regexp_constructor.rb +20 -0
  224. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +4 -0
  225. data/lib/rubocop/cop/style/redundant_self.rb +9 -1
  226. data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +28 -0
  227. data/lib/rubocop/cop/style/redundant_sort.rb +38 -4
  228. data/lib/rubocop/cop/style/regexp_literal.rb +75 -26
  229. data/lib/rubocop/cop/style/require_order.rb +25 -8
  230. data/lib/rubocop/cop/style/rescue_modifier.rb +11 -9
  231. data/lib/rubocop/cop/style/return_nil.rb +17 -2
  232. data/lib/rubocop/cop/style/return_nil_in_predicate_method_definition.rb +11 -1
  233. data/lib/rubocop/cop/style/sample.rb +9 -8
  234. data/lib/rubocop/cop/style/self_assignment.rb +12 -15
  235. data/lib/rubocop/cop/style/semicolon.rb +16 -5
  236. data/lib/rubocop/cop/style/send_with_literal_method_name.rb +4 -0
  237. data/lib/rubocop/cop/style/single_line_methods.rb +42 -4
  238. data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +29 -5
  239. data/lib/rubocop/cop/style/string_concatenation.rb +21 -4
  240. data/lib/rubocop/cop/style/struct_inheritance.rb +6 -0
  241. data/lib/rubocop/cop/style/super_arguments.rb +3 -0
  242. data/lib/rubocop/cop/style/symbol_proc.rb +24 -6
  243. data/lib/rubocop/cop/style/ternary_parentheses.rb +23 -1
  244. data/lib/rubocop/cop/style/trailing_underscore_variable.rb +23 -19
  245. data/lib/rubocop/cop/style/while_until_modifier.rb +1 -1
  246. data/lib/rubocop/cop/style/yoda_condition.rb +24 -9
  247. data/lib/rubocop/cop/style/yoda_expression.rb +10 -1
  248. data/lib/rubocop/cop/team.rb +10 -5
  249. data/lib/rubocop/cop/variable_force/assignment.rb +1 -1
  250. data/lib/rubocop/cop/variable_force/variable.rb +14 -1
  251. data/lib/rubocop/formatter/base_formatter.rb +22 -0
  252. data/lib/rubocop/formatter/formatter_set.rb +4 -0
  253. data/lib/rubocop/formatter/json_formatter.rb +71 -1
  254. data/lib/rubocop/formatter/offense_count_formatter.rb +3 -1
  255. data/lib/rubocop/formatter/pacman_formatter.rb +3 -1
  256. data/lib/rubocop/formatter/sarif_formatter.rb +12 -7
  257. data/lib/rubocop/formatter/worst_offenders_formatter.rb +3 -1
  258. data/lib/rubocop/lsp/routes.rb +23 -23
  259. data/lib/rubocop/lsp/runtime.rb +47 -11
  260. data/lib/rubocop/lsp/stdin_runner.rb +11 -5
  261. data/lib/rubocop/lsp.rb +5 -2
  262. data/lib/rubocop/mcp/autocorrection_request.rb +52 -0
  263. data/lib/rubocop/mcp/explain_tool.rb +68 -0
  264. data/lib/rubocop/mcp/scope.rb +65 -0
  265. data/lib/rubocop/mcp/server.rb +184 -76
  266. data/lib/rubocop/mcp/source_files.rb +35 -0
  267. data/lib/rubocop/offense_limit.rb +30 -0
  268. data/lib/rubocop/options.rb +45 -10
  269. data/lib/rubocop/plugin/configuration_integrator.rb +36 -8
  270. data/lib/rubocop/remote_config.rb +4 -1
  271. data/lib/rubocop/result_cache.rb +20 -13
  272. data/lib/rubocop/runner.rb +110 -42
  273. data/lib/rubocop/server/cache.rb +6 -6
  274. data/lib/rubocop/target_ruby.rb +21 -9
  275. data/lib/rubocop/util.rb +69 -0
  276. data/lib/rubocop/version.rb +1 -1
  277. data/lib/rubocop.rb +5 -0
  278. metadata +16 -5
@@ -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
@@ -29,8 +29,7 @@ module RuboCop
29
29
  operation_produces_immutable_object?(node.receiver))
30
30
 
31
31
  add_offense(node) do |corrector|
32
- corrector.remove(node.loc.dot)
33
- corrector.remove(node.loc.selector)
32
+ corrector.remove(node.loc.dot.join(node.loc.selector))
34
33
  end
35
34
  end
36
35
 
@@ -57,12 +56,21 @@ module RuboCop
57
56
  def_node_matcher :operation_produces_immutable_object?, <<~PATTERN
58
57
  {
59
58
  (begin (send {float int} {:+ :- :* :** :/ :% :<<} _))
60
- (begin (send !{(str _) array} {:+ :- :* :** :/ :%} {float int}))
59
+ (begin (send #numeric_operation? {:+ :- :* :** :/ :%} {float int}))
61
60
  (begin (send _ {:== :=== :!= :<= :>= :< :>} _))
62
61
  (send _ {:count :length :size} ...)
63
62
  (any_block (send _ {:count :length :size} ...) ...)
64
63
  }
65
64
  PATTERN
65
+
66
+ # @!method numeric_operation?(node)
67
+ def_node_matcher :numeric_operation?, <<~PATTERN
68
+ {
69
+ float int
70
+ (begin #numeric_operation?)
71
+ (send #numeric_operation? {:+ :- :* :** :/ :%} #numeric_operation?)
72
+ }
73
+ PATTERN
66
74
  end
67
75
  end
68
76
  end
@@ -30,6 +30,10 @@ module RuboCop
30
30
 
31
31
  minimum_target_ruby_version 3.0
32
32
 
33
+ def self.autocorrect_incompatible_with
34
+ [Layout::MultilineMethodCallBraceLayout]
35
+ end
36
+
33
37
  # @!method redundant_unfreeze?(node)
34
38
  def_node_matcher :redundant_unfreeze?, <<~PATTERN
35
39
  {
@@ -41,9 +45,10 @@ module RuboCop
41
45
  def on_dstr(node)
42
46
  return if uninterpolated_string?(node) || uninterpolated_heredoc?(node)
43
47
  return unless redundant_unfreeze?(node.parent)
48
+ return if comment_outside_string?(node)
44
49
 
45
50
  add_offense(offense_range(node.parent)) do |corrector|
46
- corrector.replace(node.parent, node.source)
51
+ remove_unfreeze(corrector, node.parent, node)
47
52
  end
48
53
  end
49
54
 
@@ -56,6 +61,24 @@ module RuboCop
56
61
  node.loc.selector
57
62
  end
58
63
  end
64
+
65
+ # Only the parts around the string are removed, so that an unfrozen string
66
+ # interpolated into it can be corrected in the same pass.
67
+ def remove_unfreeze(corrector, node, string)
68
+ corrector.remove(node.source_range.begin.join(string.source_range.begin))
69
+ corrector.remove(string.source_range.end.join(node.source_range.end))
70
+ end
71
+
72
+ def comment_outside_string?(node)
73
+ call_range = node.parent.source_range
74
+ string_range = node.source_range
75
+
76
+ processed_source.comments.any? do |comment|
77
+ range = comment.source_range
78
+
79
+ call_range.contains?(range) && !string_range.contains?(range)
80
+ end
81
+ end
59
82
  end
60
83
  end
61
84
  end
@@ -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,
@@ -115,7 +115,7 @@ module RuboCop
115
115
 
116
116
  def on_send(node)
117
117
  return unless node.self_receiver? && regular_method_call?(node)
118
- return if node.parent&.mlhs_type?
118
+ return if multiple_assignment_target?(node)
119
119
  return if allowed_send_node?(node)
120
120
  return if it_method_in_block?(node)
121
121
 
@@ -154,6 +154,14 @@ module RuboCop
154
154
  end
155
155
  end
156
156
 
157
+ def multiple_assignment_target?(node)
158
+ return false unless (parent = node.parent)
159
+
160
+ parent = parent.parent if parent.splat_type?
161
+
162
+ parent.mlhs_type?
163
+ end
164
+
157
165
  def allowed_send_node?(node)
158
166
  @allowed_send_nodes.include?(node) ||
159
167
  @local_variables_scopes[node].include?(node.method_name) ||
@@ -23,14 +23,20 @@ module RuboCop
23
23
  # foo = bar unless condition
24
24
  #
25
25
  class RedundantSelfAssignmentBranch < Base
26
+ include RescueNode
26
27
  extend AutoCorrector
27
28
 
28
29
  MSG = 'Remove the self-assignment branch.'
29
30
 
31
+ STATEMENT_BODY_TYPES = %i[
32
+ kwbegin def defs block numblock itblock class module sclass rescue ensure
33
+ ].freeze
34
+
30
35
  def on_lvasgn(node)
31
36
  expression = node.expression
32
37
 
33
38
  return unless use_if_and_else_branch?(expression)
39
+ return unless statement?(node)
34
40
 
35
41
  if_branch = expression.if_branch
36
42
  else_branch = expression.else_branch
@@ -51,6 +57,28 @@ module RuboCop
51
57
  !expression.ternary? || !expression.else?
52
58
  end
53
59
 
60
+ def statement?(node)
61
+ parent = node.parent
62
+ return true if parent.nil? || parent.type?(*STATEMENT_BODY_TYPES)
63
+ return parent.loc.begin.nil? if parent.begin_type?
64
+ return !rescue_modifier?(parent) if parent.resbody_type?
65
+
66
+ statement_branch?(node, parent)
67
+ end
68
+
69
+ def statement_branch?(node, parent)
70
+ case parent.type
71
+ when :if
72
+ !parent.ternary? && !node.equal?(parent.condition)
73
+ when :when, :in_pattern, :while, :until, :for
74
+ node.equal?(parent.body)
75
+ when :case, :case_match
76
+ node.equal?(parent.else_branch)
77
+ else
78
+ false
79
+ end
80
+ end
81
+
54
82
  def inconvertible_to_modifier?(if_branch, else_branch)
55
83
  multiple_statements?(if_branch) || multiple_statements?(else_branch) ||
56
84
  (else_branch.respond_to?(:elsif?) && else_branch.elsif?)
@@ -123,12 +123,29 @@ module RuboCop
123
123
  end
124
124
 
125
125
  def register_offense(node, sort_node, sorter, accessor)
126
+ return if code_after_accessor_kept_apart?(node)
127
+
126
128
  message = message(node, sorter, accessor)
127
129
  add_offense(offense_range(sort_node, node), message: message) do |corrector|
128
130
  autocorrect(corrector, node, sort_node, sorter, accessor)
129
131
  end
130
132
  end
131
133
 
134
+ def code_after_accessor_kept_apart?(node)
135
+ return false if with_logical_operator?(node)
136
+
137
+ source = before_accessor_source(node)
138
+ return false if !source.include?("\n") || source.strip.empty?
139
+
140
+ rest = code_after_accessor(node)
141
+
142
+ !rest.empty? && !rest.match?(/\A(#|&?\.|[)\]},])/)
143
+ end
144
+
145
+ def code_after_accessor(node)
146
+ processed_source.lines[node.last_line - 1][node.source_range.last_column..].strip
147
+ end
148
+
132
149
  def offense_range(sort_node, node)
133
150
  range_between(sort_node.loc.selector.begin_pos, node.source_range.end_pos)
134
151
  end
@@ -146,12 +163,29 @@ module RuboCop
146
163
  end
147
164
 
148
165
  def autocorrect(corrector, node, sort_node, sorter, accessor)
149
- # Remove accessor, e.g. `first` or `[-1]`.
150
- corrector.remove(range_between(accessor_start(node), node.source_range.end_pos))
151
166
  # Replace "sort" or "sort_by" with the appropriate min/max method.
152
167
  corrector.replace(sort_node.loc.selector, suggestion(sorter, accessor, arg_value(node)))
153
- # Replace to avoid syntax errors when followed by a logical operator.
154
- replace_with_logical_operator(corrector, node) if with_logical_operator?(node)
168
+ remove_accessor(corrector, node)
169
+ end
170
+
171
+ def remove_accessor(corrector, node)
172
+ if removable_line_break_before_accessor?(node)
173
+ corrector.remove(node.receiver.source_range.end.join(node.source_range.end))
174
+ else
175
+ corrector.remove(range_between(accessor_start(node), node.source_range.end_pos))
176
+
177
+ replace_with_logical_operator(corrector, node) if with_logical_operator?(node)
178
+ end
179
+ end
180
+
181
+ def removable_line_break_before_accessor?(node)
182
+ source = before_accessor_source(node)
183
+
184
+ source.include?("\n") && source.strip.empty?
185
+ end
186
+
187
+ def before_accessor_source(node)
188
+ node.receiver.source_range.end.join((node.loc.dot || node.loc.selector).begin).source
155
189
  end
156
190
 
157
191
  def replace_with_logical_operator(corrector, node)
@@ -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
@@ -99,25 +101,40 @@ module RuboCop
99
101
  end
100
102
 
101
103
  def find_previous_older_sibling(node) # rubocop:disable Metrics
102
- search_node(node).left_siblings.reverse.find do |sibling|
104
+ oldest = nil
105
+ search_node(node).left_siblings.reverse_each do |sibling|
103
106
  next unless sibling.is_a?(AST::Node)
104
107
 
105
- sibling = sibling_node(sibling)
106
- break unless sibling&.send_type? && sibling.method?(node.method_name)
107
- break unless sibling.arguments? && !sibling.receiver
108
- break unless in_same_section?(sibling, node)
109
- break unless node.first_argument.str_type? && sibling.first_argument.str_type?
108
+ required = sibling_node(sibling)
109
+ break unless required&.send_type? && required.method?(node.method_name)
110
+ break unless required.arguments? && !required.receiver
111
+ break unless in_same_section?(required, node)
112
+ break unless node.first_argument.str_type? && required.first_argument.str_type?
110
113
 
111
- node.first_argument.value < sibling.first_argument.value
114
+ oldest = sibling if node.first_argument.value < required.first_argument.value
112
115
  end
116
+ oldest
117
+ end
118
+
119
+ def own_lines?(node)
120
+ range = node.source_range
121
+ rest_of_line = range.source_buffer.source_line(range.last_line)[range.last_column..]
122
+
123
+ begins_its_line?(range) && rest_of_line.match?(/\A\s*(#.*)?\z/)
113
124
  end
114
125
 
115
126
  def autocorrect(corrector, node, previous_older_sibling)
116
127
  range1 = range_with_comments_and_lines(previous_older_sibling)
117
128
  range2 = range_with_comments_and_lines(node.parent.if_type? ? node.parent : node)
129
+ source = range2.source
130
+
131
+ unless source.end_with?("\n")
132
+ source += "\n"
133
+ range2 = range2.with(begin_pos: range2.begin_pos - 1)
134
+ end
118
135
 
119
136
  corrector.remove(range2)
120
- corrector.insert_before(range1, range2.source)
137
+ corrector.insert_before(range1.begin, source)
121
138
  end
122
139
 
123
140
  def search_node(node)
@@ -54,15 +54,24 @@ 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
+ next if inside_corrected_rescue_body?(rescue_node)
60
+
57
61
  parenthesized = parenthesized?(rescue_node)
58
62
 
59
63
  correct_rescue_block(corrector, rescue_node, parenthesized)
60
64
  ParenthesesCorrector.correct(corrector, rescue_node.parent) if parenthesized
65
+ ignore_node(rescue_node)
61
66
  end
62
67
  end
63
68
 
64
69
  private
65
70
 
71
+ def inside_corrected_rescue_body?(node)
72
+ node.each_ancestor(:resbody).any? { |resbody| ignored_node?(resbody.parent) }
73
+ end
74
+
66
75
  def parenthesized?(node)
67
76
  node.parent && parentheses?(node.parent)
68
77
  end
@@ -95,15 +104,8 @@ module RuboCop
95
104
  end
96
105
 
97
106
  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
107
+ node.each_node(:any_str).select(&:heredoc?).map { |heredoc| heredoc.loc.heredoc_end }
108
+ .max_by(&:end_pos)
107
109
  end
108
110
  end
109
111
  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)