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
@@ -114,9 +114,31 @@ module RuboCop
114
114
  method_call, _lhs, method, _rhs = inverse_candidate?(node)
115
115
  return unless method_call && method
116
116
 
117
- corrector.remove(not_to_receiver(node, method_call))
117
+ remove_negation(corrector, node, method, method_call)
118
118
  corrector.replace(method_call.loc.selector, inverse_methods[method].to_s)
119
- remove_end_parenthesis(corrector, node, method, method_call)
119
+ end
120
+
121
+ def remove_negation(corrector, node, method, method_call)
122
+ if !keep_parentheses?(node, method_call)
123
+ corrector.remove(not_to_receiver(node, method_call))
124
+ remove_end_parenthesis(corrector, node, method, method_call)
125
+ elsif node.parenthesized?
126
+ corrector.remove(node.loc.selector)
127
+ else
128
+ corrector.remove(node.loc.selector.join(node.receiver.source_range.begin))
129
+ end
130
+ end
131
+
132
+ def keep_parentheses?(node, method_call)
133
+ return false unless node.receiver.begin_type? || node.parenthesized?
134
+ return false unless method_call.operator_method?
135
+ return false unless (parent = node.parent)&.call_type?
136
+
137
+ binary_operator?(parent) || parent.receiver.equal?(node)
138
+ end
139
+
140
+ def binary_operator?(node)
141
+ node.operator_method? && !node.loc.dot && !node.method?(:[]) && !node.method?(:[]=)
120
142
  end
121
143
 
122
144
  def correct_inverse_block(corrector, node)
@@ -59,8 +59,20 @@ module RuboCop
59
59
  MSG_AVOID_IT_PARAMETER = 'Avoid using `it` block parameter.'
60
60
  MSG_AVOID_IT_PARAMETER_MULTILINE = 'Avoid using `it` block parameter for multi-line blocks.'
61
61
 
62
+ SCOPE_TYPES = %i[any_def class module sclass].freeze
63
+
62
64
  minimum_target_ruby_version 3.4
63
65
 
66
+ # `Style/StabbyLambdaParentheses` wraps the parameter this cop removes in the same pass,
67
+ # which would leave an empty parameter list that forbids `it`.
68
+ def self.autocorrect_incompatible_with
69
+ [Style::Lambda, Style::StabbyLambdaParentheses]
70
+ end
71
+
72
+ def on_new_investigation
73
+ @it_variable_definitions = nil
74
+ end
75
+
64
76
  def on_block(node)
65
77
  return unless style == :always
66
78
  return unless node.arguments.one?
@@ -68,9 +80,10 @@ module RuboCop
68
80
  # `restarg`, `kwrestarg`, `blockarg` nodes can return early.
69
81
  return unless node.first_argument.arg_type?
70
82
 
71
- variables = find_block_variables(node, node.first_argument.source)
83
+ block_argument_name = node.first_argument.source
84
+ return if keep_parameter_name?(node.body, block_argument_name)
72
85
 
73
- variables.each do |variable|
86
+ find_block_variables(node, block_argument_name).each do |variable|
74
87
  add_offense(variable, message: MSG_USE_IT_PARAMETER) do |corrector|
75
88
  corrector.remove(node.arguments)
76
89
  corrector.replace(variable, 'it')
@@ -109,12 +122,89 @@ module RuboCop
109
122
  private
110
123
 
111
124
  def find_block_variables(node, block_argument_name)
112
- return [] unless node.body
125
+ return [] if !node.body || it_variable_in_scope?(node)
126
+
127
+ ranges = inner_block_ranges(node.body)
128
+
129
+ block_variables(node.body, block_argument_name).reject do |variable|
130
+ ranges.any? { |range| range.contains?(variable.source_range) }
131
+ end
132
+ end
133
+
134
+ def it_variable_in_scope?(node)
135
+ scope = nearest_scope(node)
136
+ return true if it_argument_in_scope?(node, scope)
113
137
 
114
- node.body.each_descendant(:lvar).select do |descendant|
138
+ it_variable_definitions.any? do |definition|
139
+ nearest_scope(definition).equal?(scope) && visible_in_block?(definition, node)
140
+ end
141
+ end
142
+
143
+ def it_variable_definitions
144
+ @it_variable_definitions ||=
145
+ variable_definitions(processed_source.ast).filter_map do |name, definition|
146
+ definition if name == 'it'
147
+ end
148
+ end
149
+
150
+ def nearest_scope(node)
151
+ node.each_ancestor(*SCOPE_TYPES).first
152
+ end
153
+
154
+ def it_argument_in_scope?(node, scope)
155
+ owners = node.each_ancestor(:block).select { |block| nearest_scope(block).equal?(scope) }
156
+ owners << scope if scope&.any_def_type?
157
+
158
+ owners.any? { |owner| owner.arguments.argument_list.any? { |arg| arg.name == :it } }
159
+ end
160
+
161
+ def visible_in_block?(definition, node)
162
+ return true if definition.source_range.begin_pos < node.source_range.begin_pos
163
+
164
+ node.source_range.contains?(definition.source_range)
165
+ end
166
+
167
+ def inner_block_ranges(body)
168
+ body.each_node(:any_block).map { |block| block.loc.begin.join(block.loc.end) }
169
+ end
170
+
171
+ def keep_parameter_name?(body, block_arg_name)
172
+ used_within_inner_block?(body, block_arg_name) || reassigned?(body, block_arg_name)
173
+ end
174
+
175
+ def used_within_inner_block?(body, block_argument_name)
176
+ return false unless body
177
+
178
+ ranges = inner_block_ranges(body)
179
+
180
+ block_variables(body, block_argument_name).any? do |variable|
181
+ ranges.any? { |range| range.contains?(variable.source_range) }
182
+ end
183
+ end
184
+
185
+ def reassigned?(body, block_argument_name)
186
+ return false unless body
187
+
188
+ variable_definitions(body).any? { |name, _definition| name == block_argument_name }
189
+ end
190
+
191
+ def block_variables(body, block_argument_name)
192
+ body.each_descendant(:lvar).select do |descendant|
115
193
  descendant.source == block_argument_name
116
194
  end
117
195
  end
196
+
197
+ def variable_definitions(node)
198
+ node.each_node(:lvasgn, :match_var, :match_with_lvasgn).flat_map do |definition|
199
+ names = if definition.match_with_lvasgn_type?
200
+ definition.children.first.each_capture(named: true).map(&:name)
201
+ else
202
+ [definition.children.first.to_s]
203
+ end
204
+
205
+ names.map { |name| [name, definition] }
206
+ end
207
+ end
118
208
  end
119
209
  end
120
210
  end
@@ -75,7 +75,9 @@ module RuboCop
75
75
 
76
76
  def remove_kwargs(kwarg_nodes, corrector)
77
77
  kwarg_nodes.each do |kwarg|
78
- with_space = range_with_surrounding_space(kwarg.source_range)
78
+ range = range_with_surrounding_space(kwarg.source_range, side: :left, whitespace: true)
79
+ with_space = range_with_surrounding_space(range, side: :right)
80
+
79
81
  corrector.remove(range_with_surrounding_comma(with_space, :left))
80
82
  end
81
83
  end
@@ -48,6 +48,8 @@ module RuboCop
48
48
  # end
49
49
  class Lambda < Base
50
50
  include ConfigurableEnforcedStyle
51
+ include PatternMatchingHelp
52
+ include RescueNode
51
53
  extend AutoCorrector
52
54
 
53
55
  LITERAL_MESSAGE = 'Use the `-> { ... }` lambda literal syntax for %<modifier>s lambdas.'
@@ -71,6 +73,7 @@ module RuboCop
71
73
  selector = node.send_node.source
72
74
 
73
75
  return unless offending_selector?(node, selector)
76
+ return if uncorrectable_lambda_literal?(node)
74
77
 
75
78
  add_offense(node.send_node, message: message(node, selector)) do |corrector|
76
79
  if node.send_node.lambda_literal?
@@ -91,6 +94,21 @@ module RuboCop
91
94
  selector == OFFENDING_SELECTORS[:style][style][lines]
92
95
  end
93
96
 
97
+ def uncorrectable_lambda_literal?(node)
98
+ return false unless node.send_node.lambda_literal?
99
+ return true if in_pattern?(node)
100
+ return false unless rescue_or_ensure_clause?(node.body)
101
+
102
+ LambdaLiteralToMethodCorrector.new(node).replace_delimiters?
103
+ end
104
+
105
+ def rescue_or_ensure_clause?(body)
106
+ return false unless body
107
+ return true if body.ensure_type?
108
+
109
+ body.rescue_type? && !rescue_modifier?(body.resbody_branches.first)
110
+ end
111
+
94
112
  def message(node, selector)
95
113
  message = selector == '->' ? METHOD_MESSAGE : LITERAL_MESSAGE
96
114
 
@@ -111,12 +129,27 @@ module RuboCop
111
129
 
112
130
  return unless node.arguments?
113
131
 
114
- arg_str = "(#{lambda_arg_string(node.arguments)})"
132
+ arg_str = lambda_arg_string(node.arguments)
133
+ arg_str = " #{arg_str} " if space_inside_parens?
134
+ arg_str = "(#{arg_str})"
135
+ arg_str = " #{arg_str}" if space_in_lambda_literal_required?
115
136
 
116
137
  corrector.insert_after(node.send_node, arg_str)
117
138
  corrector.remove(arguments_with_whitespace(node))
118
139
  end
119
140
 
141
+ def space_inside_parens?
142
+ return false unless config.cop_enabled?('Layout/SpaceInsideParens')
143
+
144
+ %w[space compact].include?(config.for_cop('Layout/SpaceInsideParens')['EnforcedStyle'])
145
+ end
146
+
147
+ def space_in_lambda_literal_required?
148
+ return false unless config.cop_enabled?('Layout/SpaceInLambdaLiteral')
149
+
150
+ config.for_cop('Layout/SpaceInLambdaLiteral')['EnforcedStyle'] == 'require_space'
151
+ end
152
+
120
153
  def arguments_with_whitespace(node)
121
154
  node.loc.begin.end.join(node.arguments.loc.end)
122
155
  end
@@ -28,8 +28,9 @@ module RuboCop
28
28
  def on_send(node)
29
29
  return unless node.receiver
30
30
  # Rewriting the call rebuilds it as a single expression, which would drop
31
- # any comments inside the argument list, so leave it to be fixed manually.
32
- return if comments_in_node?(node)
31
+ # any comments or heredoc bodies inside the argument list, so leave it to be
32
+ # fixed manually.
33
+ return if comments_in_node?(node) || heredoc_argument?(node)
33
34
 
34
35
  if offense?(node)
35
36
  prefer = prefer(node)
@@ -59,6 +60,10 @@ module RuboCop
59
60
  end
60
61
  end
61
62
 
63
+ def heredoc_argument?(node)
64
+ node.arguments.any? { |argument| argument.each_node(:any_str).any?(&:heredoc?) }
65
+ end
66
+
62
67
  def offense?(node)
63
68
  (explicit_style? && node.implicit_call?) || (implicit_style? && !node.implicit_call?)
64
69
  end
@@ -112,8 +112,7 @@ module RuboCop
112
112
 
113
113
  def on_block(node)
114
114
  return unless each_block_with_push?(node)
115
-
116
- dest_var = find_dest_var(node)
115
+ return unless (dest_var = find_dest_var(node))
117
116
 
118
117
  if offending_empty_array_tap?(node, dest_var)
119
118
  asgn = dest_var.declaration_node
@@ -30,7 +30,7 @@ module RuboCop
30
30
 
31
31
  private
32
32
 
33
- def omit_parentheses(node) # rubocop:disable Metrics/PerceivedComplexity
33
+ def omit_parentheses(node) # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
34
34
  return unless node.parenthesized?
35
35
  return if inside_endless_method_def?(node)
36
36
  return if require_parentheses_for_hash_value_omission?(node)
@@ -40,6 +40,9 @@ module RuboCop
40
40
  return if legitimate_call_with_parentheses?(node)
41
41
  return if allowed_camel_case_method_call?(node)
42
42
  return if allowed_string_interpolation_method_call?(node)
43
+ return if code_after_closing_parenthesis_on_its_own_line?(node)
44
+ return if comment_before_first_argument?(node)
45
+ return if args_parenthesized?(node)
43
46
 
44
47
  (@pending_omit_offenses ||= []) << node
45
48
  end
@@ -104,14 +107,33 @@ module RuboCop
104
107
  inside_string_interpolation?(node)
105
108
  end
106
109
 
110
+ def code_after_closing_parenthesis_on_its_own_line?(node)
111
+ closing = node.loc.end
112
+ return false if node.arguments.none? || node.last_argument.last_line == closing.line
113
+
114
+ rest = closing.source_line[(closing.column + 1)..].strip
115
+
116
+ !rest.empty? && !rest.start_with?('#')
117
+ end
118
+
119
+ # Without the parentheses, a comment before the first argument would end the call there,
120
+ # and a line continuation can't carry the call past a comment.
121
+ def comment_before_first_argument?(node)
122
+ return false unless (first_argument = node.first_argument)
123
+
124
+ lines = node.loc.begin.line...first_argument.first_line
125
+ processed_source.each_comment_in_lines(lines).any?
126
+ end
127
+
107
128
  def parentheses_at_the_end_of_multiline_call?(node)
108
- node.multiline? &&
109
- node.loc.begin.source_line
110
- .gsub(TRAILING_WHITESPACE_REGEX, '')
111
- .end_with?('(')
129
+ return false unless node.multiline?
130
+
131
+ opening = node.loc.begin
132
+
133
+ opening.source_line[(opening.column + 1)..].gsub(TRAILING_WHITESPACE_REGEX, '').empty?
112
134
  end
113
135
 
114
- def legitimate_call_with_parentheses?(node) # rubocop:disable Metrics/PerceivedComplexity
136
+ def legitimate_call_with_parentheses?(node) # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
115
137
  call_in_literals?(node) ||
116
138
  node.parent&.when_type? ||
117
139
  call_with_ambiguous_arguments?(node) ||
@@ -121,7 +143,9 @@ module RuboCop
121
143
  allowed_multiline_call_with_parentheses?(node) ||
122
144
  allowed_chained_call_with_parentheses?(node) ||
123
145
  assignment_in_condition?(node) ||
124
- forwards_anonymous_rest_arguments?(node)
146
+ command_assignment_in_expression?(node) ||
147
+ forwards_anonymous_rest_arguments?(node) ||
148
+ trailing_comma?(node)
125
149
  end
126
150
 
127
151
  def call_in_literals?(node)
@@ -137,9 +161,13 @@ module RuboCop
137
161
  parent = node.parent&.any_block_type? ? node.parent.parent : node.parent
138
162
  return false unless parent
139
163
 
140
- logical_operator?(parent) ||
141
- (parent.send_type? &&
142
- parent.arguments.any? { |argument| logical_operator?(argument) })
164
+ if logical_operator?(parent) || command_assignment_before_keyword_operator?(parent)
165
+ return true
166
+ end
167
+
168
+ parent.send_type? && parent.arguments.any? do |argument|
169
+ logical_operator?(argument)
170
+ end
143
171
  end
144
172
 
145
173
  def call_in_optional_arguments?(node)
@@ -229,6 +257,15 @@ module RuboCop
229
257
  node.operator_keyword? && node.logical_operator?
230
258
  end
231
259
 
260
+ def command_assignment_before_keyword_operator?(node)
261
+ return false unless node.assignment?
262
+
263
+ node = node.parent while node.parent&.assignment?
264
+ return false unless (operator = node.parent)
265
+
266
+ operator.operator_keyword? && operator.semantic_operator?
267
+ end
268
+
232
269
  def hash_literal?(node)
233
270
  node.hash_type? && node.braces?
234
271
  end
@@ -244,7 +281,7 @@ module RuboCop
244
281
  end
245
282
 
246
283
  def assigned_before?(node, target)
247
- node.assignment? && node.loc.operator.begin < target.loc.begin
284
+ node.assignment? && node.loc.operator.begin < target.source_range.begin
248
285
  end
249
286
 
250
287
  def inside_string_interpolation?(node)
@@ -261,11 +298,41 @@ module RuboCop
261
298
  parent.assignment? && (grandparent.conditional? || grandparent.when_type?)
262
299
  end
263
300
 
301
+ # rubocop:disable-next Metrics/PerceivedComplexity
302
+ def command_assignment_in_expression?(node)
303
+ value = node.parent&.type?(:any_block, :rescue) ? node.parent : node
304
+ assignment = outermost_assignment(value)
305
+ return false if assignment.equal?(value)
306
+
307
+ call_in_literals?(assignment) ||
308
+ call_in_logical_operators?(assignment) ||
309
+ call_in_optional_arguments?(assignment) ||
310
+ call_as_argument_or_chain?(assignment) ||
311
+ assignment.parent&.type?(:masgn, :return, :next, :break, :defined?)
312
+ end
313
+
314
+ def outermost_assignment(value)
315
+ parent = value.parent
316
+ return value if !parent&.assignment? || parent.masgn_type?
317
+ return value unless assigned_before?(parent, value)
318
+
319
+ outermost_assignment(parent)
320
+ end
321
+
264
322
  def forwards_anonymous_rest_arguments?(node)
323
+ node.arguments.any? do |argument|
324
+ next true if argument.forwarded_restarg_type?
325
+
326
+ argument.hash_type? && argument.children.any?(&:forwarded_kwrestarg_type?)
327
+ end
328
+ end
329
+
330
+ def trailing_comma?(node)
265
331
  return false unless (last_argument = node.last_argument)
266
- return true if last_argument.forwarded_restarg_type?
267
332
 
268
- last_argument.hash_type? && last_argument.children.any?(&:forwarded_kwrestarg_type?)
333
+ range = last_argument.source_range.end.join(node.loc.end.begin)
334
+
335
+ processed_source.tokens_within(range).any?(&:comma?)
269
336
  end
270
337
  end
271
338
  end
@@ -242,7 +242,14 @@ module RuboCop
242
242
  extend AutoCorrector
243
243
 
244
244
  def self.autocorrect_incompatible_with
245
- [Style::NestedParenthesizedCalls, Style::RescueModifier, Style::TrailingCommaInArguments]
245
+ [
246
+ Layout::HeredocArgumentClosingParenthesis,
247
+ Style::NestedParenthesizedCalls,
248
+ Style::RedundantParentheses,
249
+ Style::RescueModifier,
250
+ Style::TernaryParentheses,
251
+ Style::TrailingCommaInArguments
252
+ ]
246
253
  end
247
254
 
248
255
  def on_send(node)
@@ -269,7 +276,24 @@ module RuboCop
269
276
  return false unless node.arguments.one?
270
277
 
271
278
  first_node = node.first_argument
272
- first_node.begin_type? && first_node.parenthesized_call?
279
+ return false unless first_node.begin_type? && first_node.parenthesized_call?
280
+
281
+ first_node.children.one? && mergeable_parentheses?(first_node.children.first)
282
+ end
283
+
284
+ def mergeable_parentheses?(node)
285
+ case node.type
286
+ when :and, :or
287
+ !node.semantic_operator?
288
+ when :if, :while, :until
289
+ !node.modifier_form?
290
+ when :send
291
+ !node.keyword?
292
+ when :rescue, :masgn, :match_pattern, :match_pattern_p
293
+ false
294
+ else
295
+ true
296
+ end
273
297
  end
274
298
  end
275
299
  end
@@ -29,10 +29,16 @@ module RuboCop
29
29
  class MethodCallWithoutArgsParentheses < Base
30
30
  include AllowedMethods
31
31
  include AllowedPattern
32
+ include ReparsedEquivalence
32
33
  extend AutoCorrector
33
34
 
34
35
  MSG = 'Do not use parentheses for method calls with no arguments.'
35
36
 
37
+ def on_new_investigation
38
+ @pending_ranges = []
39
+ super
40
+ end
41
+
36
42
  # rubocop:disable-next Metrics/CyclomaticComplexity
37
43
  def on_send(node)
38
44
  return unless !node.arguments? && node.parenthesized?
@@ -46,15 +52,32 @@ module RuboCop
46
52
  end
47
53
  alias on_csend on_send
48
54
 
55
+ # The parentheses are significant when removing them changes how the
56
+ # code parses, e.g. when a local variable with the same name is in
57
+ # scope (`foo = 1; foo()`) or an operator follows (`foo() -1`). The
58
+ # candidates are verified together, since outside a method definition
59
+ # each one would otherwise reparse the whole file.
60
+ def on_investigation_end
61
+ verified_by_reparse(@pending_ranges).each do |range|
62
+ add_offense(range) do |corrector|
63
+ apply_reparse_correction(corrector, range)
64
+ end
65
+ end
66
+
67
+ super
68
+ end
69
+
49
70
  private
50
71
 
51
72
  def register_offense(node)
52
73
  range = offense_range(node)
53
74
  return if processed_source.contains_comment?(range)
54
75
 
55
- add_offense(range) do |corrector|
56
- corrector.remove(range)
57
- end
76
+ @pending_ranges << range
77
+ end
78
+
79
+ def apply_reparse_correction(corrector, range)
80
+ corrector.remove(range)
58
81
  end
59
82
 
60
83
  def ineligible_node?(node)
@@ -127,7 +127,19 @@ module RuboCop
127
127
 
128
128
  def correct_arguments(arg_node, corrector)
129
129
  corrector.replace(arg_node.loc.begin, ' ')
130
- corrector.remove(arg_node.loc.end)
130
+
131
+ if code_after_arguments?(arg_node)
132
+ corrector.replace(arg_node.loc.end, ';')
133
+ else
134
+ corrector.remove(arg_node.loc.end)
135
+ end
136
+ end
137
+
138
+ def code_after_arguments?(arg_node)
139
+ closing = arg_node.loc.end
140
+ rest = closing.source_line[(closing.column + 1)..].strip
141
+
142
+ !rest.empty? && !rest.start_with?(';', '#')
131
143
  end
132
144
 
133
145
  def forced_parentheses?(node)
@@ -137,8 +149,11 @@ module RuboCop
137
149
  # 3. Argument lists containing an anonymous rest arguments forwarding (`*`)
138
150
  # 4. Argument lists containing an anonymous keyword rest arguments forwarding (`**`)
139
151
  # 5. Argument lists containing an anonymous block forwarding (`&`)
140
- # Removing the parens would be a syntax error here.
141
- node.endless? || anonymous_arguments?(node)
152
+ # 6. Argument lists that begin on a line below the method name
153
+ # 7. Argument lists that begin with a destructuring parameter (`(a, b)`)
154
+ # Removing the parens would be a syntax error, or a change of the parameters, here.
155
+ node.endless? || anonymous_arguments?(node) || arguments_on_own_line?(node) ||
156
+ destructuring_first_argument?(node)
142
157
  end
143
158
 
144
159
  def require_parentheses?(args)
@@ -154,12 +169,20 @@ module RuboCop
154
169
  location = node.arguments.source_range
155
170
 
156
171
  add_offense(location, message: MSG_MISSING) do |corrector|
157
- add_parentheses(node.arguments, corrector)
172
+ corrector.replace(range_before_arguments(node), '(')
173
+ corrector.insert_after(location, ')')
158
174
 
159
175
  unexpected_style_detected 'require_no_parentheses'
160
176
  end
161
177
  end
162
178
 
179
+ def range_before_arguments(node)
180
+ range = node.loc.name.end.join(node.arguments.source_range.begin)
181
+ last_line_break = range.source.rindex("\n")
182
+
183
+ last_line_break ? range.resize(last_line_break) : range
184
+ end
185
+
163
186
  def unwanted_parentheses(args)
164
187
  add_offense(args, message: MSG_PRESENT) do |corrector|
165
188
  # offense is registered on args node when parentheses are unwanted
@@ -176,6 +199,17 @@ module RuboCop
176
199
 
177
200
  last_argument.blockarg_type? && last_argument.name.nil?
178
201
  end
202
+
203
+ def arguments_on_own_line?(node)
204
+ return false unless (first_argument = node.first_argument)
205
+ return false unless parentheses?(node.arguments)
206
+
207
+ node.arguments.loc.begin.line != first_argument.first_line
208
+ end
209
+
210
+ def destructuring_first_argument?(node)
211
+ node.first_argument&.mlhs_type?
212
+ end
179
213
  end
180
214
  end
181
215
  end
@@ -125,13 +125,19 @@ module RuboCop
125
125
  private
126
126
 
127
127
  def check(node)
128
- return if node.else?
128
+ return if node.else? || guard_clause_cop_conflict?(node)
129
129
 
130
130
  add_offense(node, message: format(message_template, type: node.type)) do |corrector|
131
131
  autocorrect(corrector, node)
132
132
  end
133
133
  end
134
134
 
135
+ def guard_clause_cop_conflict?(node)
136
+ return false unless node.if_type? && @config.cop_enabled?('Style/GuardClause')
137
+
138
+ [node.if_branch, node.else_branch].compact.any?(&:guard_clause?)
139
+ end
140
+
135
141
  def message_template
136
142
  case empty_else_style
137
143
  when :empty
@@ -117,17 +117,26 @@ module RuboCop
117
117
 
118
118
  def separate_mixins(node)
119
119
  arguments = node.arguments.reverse
120
- mixins = ["#{node.method_name} #{arguments.first.source}"]
120
+ mixins = [mixin_call(node.method_name, arguments.first.source, node.parenthesized?)]
121
121
 
122
122
  arguments[1..].inject(mixins) do |replacement, arg|
123
- replacement << "#{indent(node)}#{node.method_name} #{arg.source}"
123
+ call = mixin_call(node.method_name, arg.source, node.parenthesized?)
124
+ replacement << "#{indent(node)}#{call}"
124
125
  end.join("\n")
125
126
  end
126
127
 
127
128
  def group_mixins(node, mixins)
128
129
  mixin_names = mixins.reverse.flat_map { |mixin| mixin.arguments.map(&:source) }
129
130
 
130
- "#{node.method_name} #{mixin_names.join(', ')}"
131
+ mixin_call(node.method_name, mixin_names.join(', '), mixins.any?(&:parenthesized?))
132
+ end
133
+
134
+ def mixin_call(method_name, arguments_source, parenthesized)
135
+ if parenthesized
136
+ "#{method_name}(#{arguments_source})"
137
+ else
138
+ "#{method_name} #{arguments_source}"
139
+ end
131
140
  end
132
141
  end
133
142
  end