rubocop 1.91.0 → 1.92.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) 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_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/badge.rb +1 -1
  19. data/lib/rubocop/cop/base.rb +42 -21
  20. data/lib/rubocop/cop/bundler/gem_comment.rb +0 -1
  21. data/lib/rubocop/cop/bundler/ordered_gems.rb +0 -8
  22. data/lib/rubocop/cop/cop_documentation.rb +81 -0
  23. data/lib/rubocop/cop/cop_explanation.rb +140 -0
  24. data/lib/rubocop/cop/correctors/alignment_corrector.rb +13 -1
  25. data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +37 -7
  26. data/lib/rubocop/cop/correctors/empty_line_corrector.rb +1 -1
  27. data/lib/rubocop/cop/correctors/lambda_literal_to_method_corrector.rb +25 -4
  28. data/lib/rubocop/cop/correctors/multiline_literal_brace_corrector.rb +34 -0
  29. data/lib/rubocop/cop/correctors/parentheses_corrector.rb +45 -8
  30. data/lib/rubocop/cop/documentation.rb +1 -1
  31. data/lib/rubocop/cop/gemspec/dependency_version.rb +0 -6
  32. data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +0 -6
  33. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +2 -5
  34. data/lib/rubocop/cop/internal_affairs/node_type_group.rb +0 -7
  35. data/lib/rubocop/cop/internal_affairs/redundant_context_config_parameter.rb +1 -7
  36. data/lib/rubocop/cop/internal_affairs/style_detected_api_use.rb +1 -7
  37. data/lib/rubocop/cop/layout/argument_alignment.rb +5 -1
  38. data/lib/rubocop/cop/layout/block_alignment.rb +27 -0
  39. data/lib/rubocop/cop/layout/case_indentation.rb +25 -9
  40. data/lib/rubocop/cop/layout/class_structure.rb +29 -7
  41. data/lib/rubocop/cop/layout/def_end_alignment.rb +4 -0
  42. data/lib/rubocop/cop/layout/dot_position.rb +25 -15
  43. data/lib/rubocop/cop/layout/empty_line_after_multiline_condition.rb +21 -8
  44. data/lib/rubocop/cop/layout/empty_lines.rb +5 -2
  45. data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +1 -0
  46. data/lib/rubocop/cop/layout/empty_lines_around_block_body.rb +9 -3
  47. data/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb +23 -1
  48. data/lib/rubocop/cop/layout/end_alignment.rb +19 -17
  49. data/lib/rubocop/cop/layout/extra_spacing.rb +4 -0
  50. data/lib/rubocop/cop/layout/first_argument_indentation.rb +27 -1
  51. data/lib/rubocop/cop/layout/first_array_element_line_break.rb +15 -0
  52. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +16 -8
  53. data/lib/rubocop/cop/layout/first_method_argument_line_break.rb +6 -0
  54. data/lib/rubocop/cop/layout/first_method_parameter_line_break.rb +4 -0
  55. data/lib/rubocop/cop/layout/hash_alignment.rb +16 -12
  56. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +7 -3
  57. data/lib/rubocop/cop/layout/heredoc_indentation.rb +15 -0
  58. data/lib/rubocop/cop/layout/indentation_consistency.rb +22 -5
  59. data/lib/rubocop/cop/layout/indentation_style.rb +4 -1
  60. data/lib/rubocop/cop/layout/indentation_width.rb +2 -2
  61. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +8 -1
  62. data/lib/rubocop/cop/layout/line_end_string_concatenation_indentation.rb +25 -9
  63. data/lib/rubocop/cop/layout/line_length.rb +153 -21
  64. data/lib/rubocop/cop/layout/multiline_array_brace_layout.rb +12 -0
  65. data/lib/rubocop/cop/layout/multiline_block_layout.rb +8 -1
  66. data/lib/rubocop/cop/layout/multiline_hash_brace_layout.rb +9 -0
  67. data/lib/rubocop/cop/layout/multiline_method_call_brace_layout.rb +6 -0
  68. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +30 -5
  69. data/lib/rubocop/cop/layout/multiline_method_definition_brace_layout.rb +4 -0
  70. data/lib/rubocop/cop/layout/redundant_line_break.rb +22 -1
  71. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +15 -2
  72. data/lib/rubocop/cop/layout/single_line_block_chain.rb +10 -2
  73. data/lib/rubocop/cop/layout/space_after_not.rb +5 -0
  74. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +6 -11
  75. data/lib/rubocop/cop/layout/space_around_keyword.rb +10 -0
  76. data/lib/rubocop/cop/layout/space_before_block_braces.rb +4 -1
  77. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +15 -2
  78. data/lib/rubocop/cop/layout/space_inside_block_braces.rb +31 -8
  79. data/lib/rubocop/cop/layout/space_inside_reference_brackets.rb +2 -12
  80. data/lib/rubocop/cop/lint/ambiguous_operator.rb +1 -1
  81. data/lib/rubocop/cop/lint/ambiguous_operator_precedence.rb +2 -1
  82. data/lib/rubocop/cop/lint/ambiguous_regexp_literal.rb +6 -1
  83. data/lib/rubocop/cop/lint/empty_conditional_body.rb +3 -1
  84. data/lib/rubocop/cop/lint/heredoc_method_call_position.rb +4 -0
  85. data/lib/rubocop/cop/lint/literal_as_condition.rb +15 -0
  86. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +63 -19
  87. data/lib/rubocop/cop/lint/loop.rb +24 -2
  88. data/lib/rubocop/cop/lint/misplaced_magic_comment.rb +71 -11
  89. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +18 -3
  90. data/lib/rubocop/cop/lint/number_conversion.rb +3 -1
  91. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +12 -1
  92. data/lib/rubocop/cop/lint/redundant_dir_glob_sort.rb +13 -2
  93. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +47 -1
  94. data/lib/rubocop/cop/lint/symbol_conversion.rb +1 -0
  95. data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +18 -1
  96. data/lib/rubocop/cop/lint/useless_assignment.rb +14 -7
  97. data/lib/rubocop/cop/lint/useless_setter_call.rb +7 -4
  98. data/lib/rubocop/cop/lint/void.rb +8 -1
  99. data/lib/rubocop/cop/metrics/class_length.rb +4 -2
  100. data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +0 -4
  101. data/lib/rubocop/cop/metrics/module_length.rb +4 -2
  102. data/lib/rubocop/cop/mixin/alignment.rb +4 -0
  103. data/lib/rubocop/cop/mixin/check_line_breakable.rb +23 -19
  104. data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +8 -2
  105. data/lib/rubocop/cop/mixin/code_length.rb +10 -2
  106. data/lib/rubocop/cop/mixin/empty_lines_around_body.rb +12 -1
  107. data/lib/rubocop/cop/mixin/endless_method_body_help.rb +105 -0
  108. data/lib/rubocop/cop/mixin/forbidden_identifiers.rb +2 -2
  109. data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +24 -5
  110. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +26 -2
  111. data/lib/rubocop/cop/mixin/min_branches_count.rb +3 -1
  112. data/lib/rubocop/cop/mixin/multiline_element_line_breaks.rb +11 -1
  113. data/lib/rubocop/cop/mixin/multiline_literal_brace_layout.rb +9 -0
  114. data/lib/rubocop/cop/mixin/ordered_gem_node.rb +1 -1
  115. data/lib/rubocop/cop/mixin/pattern_matching_help.rb +35 -0
  116. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +17 -2
  117. data/lib/rubocop/cop/mixin/project_index_help.rb +15 -0
  118. data/lib/rubocop/cop/mixin/reparsed_equivalence.rb +13 -3
  119. data/lib/rubocop/cop/mixin/statement_modifier.rb +25 -4
  120. data/lib/rubocop/cop/mixin/visibility_help.rb +2 -1
  121. data/lib/rubocop/cop/mixin.rb +2 -0
  122. data/lib/rubocop/cop/naming/accessor_method_name.rb +0 -16
  123. data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
  124. data/lib/rubocop/cop/naming/heredoc_delimiter_case.rb +9 -2
  125. data/lib/rubocop/cop/naming/heredoc_delimiter_naming.rb +5 -3
  126. data/lib/rubocop/cop/naming/predicate_prefix.rb +7 -22
  127. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +1 -1
  128. data/lib/rubocop/cop/offense.rb +59 -3
  129. data/lib/rubocop/cop/registry.rb +1 -1
  130. data/lib/rubocop/cop/security/json_load.rb +16 -4
  131. data/lib/rubocop/cop/style/access_modifier_declarations.rb +37 -2
  132. data/lib/rubocop/cop/style/alias.rb +18 -5
  133. data/lib/rubocop/cop/style/ambiguous_endless_method_definition.rb +4 -3
  134. data/lib/rubocop/cop/style/and_or.rb +6 -2
  135. data/lib/rubocop/cop/style/arguments_forwarding.rb +32 -3
  136. data/lib/rubocop/cop/style/attr.rb +3 -4
  137. data/lib/rubocop/cop/style/bitwise_predicate.rb +6 -4
  138. data/lib/rubocop/cop/style/block_delimiters.rb +35 -3
  139. data/lib/rubocop/cop/style/case_like_if.rb +6 -3
  140. data/lib/rubocop/cop/style/class_and_module_children.rb +56 -16
  141. data/lib/rubocop/cop/style/class_methods_definitions.rb +15 -0
  142. data/lib/rubocop/cop/style/collection_querying.rb +79 -46
  143. data/lib/rubocop/cop/style/combinable_loops.rb +89 -11
  144. data/lib/rubocop/cop/style/concat_array_literals.rb +58 -7
  145. data/lib/rubocop/cop/style/conditional_assignment.rb +4 -0
  146. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +65 -9
  147. data/lib/rubocop/cop/style/double_negation.rb +11 -0
  148. data/lib/rubocop/cop/style/empty_literal.rb +1 -1
  149. data/lib/rubocop/cop/style/empty_method.rb +3 -1
  150. data/lib/rubocop/cop/style/empty_string_inside_interpolation.rb +53 -11
  151. data/lib/rubocop/cop/style/endless_method.rb +71 -6
  152. data/lib/rubocop/cop/style/exact_regexp_match.rb +45 -8
  153. data/lib/rubocop/cop/style/explicit_block_argument.rb +50 -10
  154. data/lib/rubocop/cop/style/float_division.rb +22 -2
  155. data/lib/rubocop/cop/style/for.rb +53 -2
  156. data/lib/rubocop/cop/style/format_string.rb +28 -6
  157. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +2 -1
  158. data/lib/rubocop/cop/style/guard_clause.rb +31 -22
  159. data/lib/rubocop/cop/style/hash_as_last_array_item.rb +4 -0
  160. data/lib/rubocop/cop/style/hash_conversion.rb +28 -8
  161. data/lib/rubocop/cop/style/hash_lookup_method.rb +32 -9
  162. data/lib/rubocop/cop/style/hash_syntax.rb +16 -3
  163. data/lib/rubocop/cop/style/identical_conditional_branches.rb +22 -3
  164. data/lib/rubocop/cop/style/if_inside_else.rb +21 -1
  165. data/lib/rubocop/cop/style/if_unless_modifier.rb +32 -26
  166. data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +4 -0
  167. data/lib/rubocop/cop/style/if_with_semicolon.rb +47 -4
  168. data/lib/rubocop/cop/style/inverse_methods.rb +24 -2
  169. data/lib/rubocop/cop/style/it_block_parameter.rb +94 -4
  170. data/lib/rubocop/cop/style/keyword_parameters_order.rb +3 -1
  171. data/lib/rubocop/cop/style/lambda.rb +34 -1
  172. data/lib/rubocop/cop/style/lambda_call.rb +7 -2
  173. data/lib/rubocop/cop/style/map_into_array.rb +1 -2
  174. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +80 -13
  175. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +25 -2
  176. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +26 -3
  177. data/lib/rubocop/cop/style/method_def_parentheses.rb +29 -3
  178. data/lib/rubocop/cop/style/missing_else.rb +7 -1
  179. data/lib/rubocop/cop/style/mixin_grouping.rb +12 -3
  180. data/lib/rubocop/cop/style/multiline_if_modifier.rb +45 -5
  181. data/lib/rubocop/cop/style/multiline_in_pattern_then.rb +7 -0
  182. data/lib/rubocop/cop/style/multiline_method_signature.rb +2 -5
  183. data/lib/rubocop/cop/style/multiline_ternary_operator.rb +7 -3
  184. data/lib/rubocop/cop/style/multiline_when_then.rb +7 -0
  185. data/lib/rubocop/cop/style/mutable_constant.rb +52 -7
  186. data/lib/rubocop/cop/style/negated_if_else_condition.rb +2 -1
  187. data/lib/rubocop/cop/style/negative_array_index.rb +9 -1
  188. data/lib/rubocop/cop/style/nested_modifier.rb +27 -8
  189. data/lib/rubocop/cop/style/nested_ternary_operator.rb +26 -8
  190. data/lib/rubocop/cop/style/next.rb +24 -10
  191. data/lib/rubocop/cop/style/nil_comparison.rb +16 -4
  192. data/lib/rubocop/cop/style/non_nil_check.rb +1 -1
  193. data/lib/rubocop/cop/style/not.rb +10 -5
  194. data/lib/rubocop/cop/style/numeric_predicate.rb +21 -5
  195. data/lib/rubocop/cop/style/one_line_conditional.rb +13 -2
  196. data/lib/rubocop/cop/style/parentheses_around_condition.rb +1 -1
  197. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +14 -3
  198. data/lib/rubocop/cop/style/raise_args.rb +19 -9
  199. data/lib/rubocop/cop/style/reduce_to_hash.rb +17 -1
  200. data/lib/rubocop/cop/style/redundant_begin.rb +22 -4
  201. data/lib/rubocop/cop/style/redundant_condition.rb +30 -13
  202. data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +6 -0
  203. data/lib/rubocop/cop/style/redundant_exception.rb +15 -3
  204. data/lib/rubocop/cop/style/redundant_fetch_block.rb +9 -1
  205. data/lib/rubocop/cop/style/redundant_freeze.rb +11 -3
  206. data/lib/rubocop/cop/style/redundant_interpolation_unfreeze.rb +24 -1
  207. data/lib/rubocop/cop/style/redundant_parentheses.rb +12 -1
  208. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +7 -0
  209. data/lib/rubocop/cop/style/redundant_regexp_constructor.rb +20 -0
  210. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +4 -0
  211. data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +28 -0
  212. data/lib/rubocop/cop/style/redundant_sort.rb +38 -4
  213. data/lib/rubocop/cop/style/regexp_literal.rb +75 -26
  214. data/lib/rubocop/cop/style/require_order.rb +25 -8
  215. data/lib/rubocop/cop/style/rescue_modifier.rb +11 -9
  216. data/lib/rubocop/cop/style/return_nil.rb +17 -2
  217. data/lib/rubocop/cop/style/return_nil_in_predicate_method_definition.rb +11 -1
  218. data/lib/rubocop/cop/style/sample.rb +9 -8
  219. data/lib/rubocop/cop/style/self_assignment.rb +12 -15
  220. data/lib/rubocop/cop/style/semicolon.rb +16 -5
  221. data/lib/rubocop/cop/style/send_with_literal_method_name.rb +4 -0
  222. data/lib/rubocop/cop/style/single_line_methods.rb +21 -4
  223. data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +29 -5
  224. data/lib/rubocop/cop/style/string_concatenation.rb +9 -3
  225. data/lib/rubocop/cop/style/struct_inheritance.rb +6 -0
  226. data/lib/rubocop/cop/style/super_arguments.rb +3 -0
  227. data/lib/rubocop/cop/style/symbol_proc.rb +24 -6
  228. data/lib/rubocop/cop/style/ternary_parentheses.rb +23 -1
  229. data/lib/rubocop/cop/style/trailing_underscore_variable.rb +23 -19
  230. data/lib/rubocop/cop/style/while_until_modifier.rb +1 -1
  231. data/lib/rubocop/cop/style/yoda_condition.rb +24 -9
  232. data/lib/rubocop/cop/style/yoda_expression.rb +10 -1
  233. data/lib/rubocop/cop/team.rb +10 -5
  234. data/lib/rubocop/cop/variable_force/assignment.rb +1 -1
  235. data/lib/rubocop/cop/variable_force/variable.rb +14 -1
  236. data/lib/rubocop/formatter/base_formatter.rb +22 -0
  237. data/lib/rubocop/formatter/formatter_set.rb +4 -0
  238. data/lib/rubocop/formatter/json_formatter.rb +71 -1
  239. data/lib/rubocop/formatter/offense_count_formatter.rb +3 -1
  240. data/lib/rubocop/formatter/pacman_formatter.rb +3 -1
  241. data/lib/rubocop/formatter/sarif_formatter.rb +12 -7
  242. data/lib/rubocop/formatter/worst_offenders_formatter.rb +3 -1
  243. data/lib/rubocop/lsp/routes.rb +23 -23
  244. data/lib/rubocop/lsp/runtime.rb +47 -11
  245. data/lib/rubocop/lsp/stdin_runner.rb +11 -5
  246. data/lib/rubocop/lsp.rb +5 -2
  247. data/lib/rubocop/mcp/autocorrection_request.rb +52 -0
  248. data/lib/rubocop/mcp/explain_tool.rb +68 -0
  249. data/lib/rubocop/mcp/scope.rb +65 -0
  250. data/lib/rubocop/mcp/server.rb +184 -76
  251. data/lib/rubocop/mcp/source_files.rb +35 -0
  252. data/lib/rubocop/offense_limit.rb +30 -0
  253. data/lib/rubocop/options.rb +45 -10
  254. data/lib/rubocop/plugin/configuration_integrator.rb +12 -5
  255. data/lib/rubocop/remote_config.rb +4 -1
  256. data/lib/rubocop/result_cache.rb +20 -13
  257. data/lib/rubocop/runner.rb +109 -41
  258. data/lib/rubocop/server/cache.rb +6 -6
  259. data/lib/rubocop/target_ruby.rb +21 -9
  260. data/lib/rubocop/util.rb +69 -0
  261. data/lib/rubocop/version.rb +1 -1
  262. data/lib/rubocop.rb +5 -0
  263. metadata +16 -5
@@ -242,7 +242,13 @@ module RuboCop
242
242
  extend AutoCorrector
243
243
 
244
244
  def self.autocorrect_incompatible_with
245
- [Style::NestedParenthesizedCalls, Style::RescueModifier, Style::TrailingCommaInArguments]
245
+ [
246
+ Style::NestedParenthesizedCalls,
247
+ Style::RedundantParentheses,
248
+ Style::RescueModifier,
249
+ Style::TernaryParentheses,
250
+ Style::TrailingCommaInArguments
251
+ ]
246
252
  end
247
253
 
248
254
  def on_send(node)
@@ -269,7 +275,24 @@ module RuboCop
269
275
  return false unless node.arguments.one?
270
276
 
271
277
  first_node = node.first_argument
272
- first_node.begin_type? && first_node.parenthesized_call?
278
+ return false unless first_node.begin_type? && first_node.parenthesized_call?
279
+
280
+ first_node.children.one? && mergeable_parentheses?(first_node.children.first)
281
+ end
282
+
283
+ def mergeable_parentheses?(node)
284
+ case node.type
285
+ when :and, :or
286
+ !node.semantic_operator?
287
+ when :if, :while, :until
288
+ !node.modifier_form?
289
+ when :send
290
+ !node.keyword?
291
+ when :rescue, :masgn, :match_pattern, :match_pattern_p
292
+ false
293
+ else
294
+ true
295
+ end
273
296
  end
274
297
  end
275
298
  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)
@@ -176,6 +191,17 @@ module RuboCop
176
191
 
177
192
  last_argument.blockarg_type? && last_argument.name.nil?
178
193
  end
194
+
195
+ def arguments_on_own_line?(node)
196
+ return false unless (first_argument = node.first_argument)
197
+ return false unless parentheses?(node.arguments)
198
+
199
+ node.arguments.loc.begin.line != first_argument.first_line
200
+ end
201
+
202
+ def destructuring_first_argument?(node)
203
+ node.first_argument&.mlhs_type?
204
+ end
179
205
  end
180
206
  end
181
207
  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
@@ -17,6 +17,7 @@ module RuboCop
17
17
  class MultilineIfModifier < Base
18
18
  include StatementModifier
19
19
  include Alignment
20
+ include RangeHelp
20
21
  extend AutoCorrector
21
22
 
22
23
  MSG = 'Favor a normal %<keyword>s-statement over a modifier ' \
@@ -27,31 +28,70 @@ module RuboCop
27
28
  return unless node.modifier_form? && node.body.multiline?
28
29
 
29
30
  add_offense(node, message: format(MSG, keyword: node.keyword)) do |corrector|
30
- corrector.replace(node, to_normal_if(node))
31
+ autocorrect(corrector, node)
31
32
  end
32
33
  ignore_node(node)
33
34
  end
34
35
 
35
36
  private
36
37
 
37
- def to_normal_if(node)
38
+ def autocorrect(corrector, node)
39
+ condition_heredoc, body_heredoc = [node.condition, node.body].map do |part|
40
+ next unless (range = heredoc_after(part, node))
41
+
42
+ corrector.remove(range)
43
+ range.source.chomp
44
+ end
45
+ corrector.replace(node, to_normal_if(node, condition_heredoc, body_heredoc))
46
+ end
47
+
48
+ # Heredocs opened on the modifier line have their bodies after it, so
49
+ # they are moved along with the part of the `if` they belong to.
50
+ def heredoc_after(part, node)
51
+ ranges = part.each_node(:any_str).select(&:heredoc?).map do |heredoc|
52
+ heredoc.loc.heredoc_body.join(heredoc.loc.heredoc_end)
53
+ end
54
+ ranges.select! { |range| range.line > node.last_line }
55
+ return if ranges.empty?
56
+
57
+ range_by_whole_lines(ranges.reduce(:join), include_final_newline: true)
58
+ end
59
+
60
+ def to_normal_if(node, condition_heredoc, body_heredoc)
38
61
  indented_body = indented_body(node.body, node)
39
62
  condition = "#{node.keyword} #{node.condition.source}"
40
63
  indented_end = "#{offset(node)}end"
41
64
 
42
- [condition, indented_body, indented_end].join("\n")
65
+ lines = [condition, condition_heredoc, indented_body, body_heredoc, indented_end]
66
+ lines.compact.join("\n")
43
67
  end
44
68
 
45
69
  def indented_body(body, node)
46
70
  body_source = "#{offset(node)}#{body.source}"
47
- body_source.each_line.map do |line|
48
- if line == "\n"
71
+ verbatim_lines = verbatim_lines_in(body)
72
+ body_source.each_line.with_index(body.first_line).map do |line, line_number|
73
+ if line == "\n" || verbatim_lines.include?(line_number)
49
74
  line
50
75
  else
51
76
  line.sub(/^\s{#{offset(node).length}}/, indentation(node))
52
77
  end
53
78
  end.join
54
79
  end
80
+
81
+ def verbatim_lines_in(body)
82
+ body.each_node(:any_str).filter_map { |str| verbatim_line_range(str) }.flat_map(&:to_a)
83
+ end
84
+
85
+ # The indentation of a line that starts inside a string is part of the
86
+ # string. Only squiggly heredocs strip it from their bodies, and a `<<`
87
+ # terminator has to stay at the start of its line.
88
+ def verbatim_line_range(str)
89
+ if str.heredoc?
90
+ str.loc.heredoc_body.line..str.loc.heredoc_end.line unless str.source.start_with?('<<~')
91
+ elsif str.loc?(:begin) && str.loc?(:end)
92
+ (str.first_line + 1)..str.last_line
93
+ end
94
+ end
55
95
  end
56
96
  end
57
97
  end
@@ -50,10 +50,17 @@ module RuboCop
50
50
  # Requires `then` for write `in` and its body on the same line.
51
51
  def require_then?(in_pattern_node)
52
52
  return true unless in_pattern_node.pattern.single_line?
53
+ return true if ends_with_endless_range?(in_pattern_node.pattern)
53
54
  return false unless in_pattern_node.body
54
55
 
55
56
  same_line?(in_pattern_node, in_pattern_node.body)
56
57
  end
58
+
59
+ def ends_with_endless_range?(node)
60
+ node.each_node(:range).any? do |range|
61
+ range.end.nil? && range.source_range.end_pos == node.source_range.end_pos
62
+ end
63
+ end
57
64
  end
58
65
  end
59
66
  end
@@ -27,6 +27,7 @@ module RuboCop
27
27
  def on_def(node)
28
28
  return unless node.arguments?
29
29
  return if opening_line(node) == closing_line(node)
30
+ return if node.arguments.any?(&:multiline?)
30
31
  return if correction_exceeds_max_line_length?(node)
31
32
  return unless (begin_of_arguments = node.arguments.loc.begin)
32
33
 
@@ -78,11 +79,7 @@ module RuboCop
78
79
  def correction_exceeds_max_line_length?(node)
79
80
  return false unless max_line_length
80
81
 
81
- indentation_width(node) + definition_width(node) > max_line_length
82
- end
83
-
84
- def indentation_width(node)
85
- processed_source.line_indentation(node.source_range.line)
82
+ node.source_range.column + definition_width(node) > max_line_length
86
83
  end
87
84
 
88
85
  def definition_width(node)
@@ -7,7 +7,8 @@ module RuboCop
7
7
  #
8
8
  # NOTE: `return if ... else ... end` is syntax error. If `return` is used before
9
9
  # multiline ternary operator expression, it will be autocorrected to single-line
10
- # ternary operator. The same is true for `break`, `next`, and method call.
10
+ # ternary operator. The same is true for `break`, `next`, `yield`, `super`,
11
+ # `defined?` without parentheses, and method call.
11
12
  #
12
13
  # @example
13
14
  # # bad
@@ -39,7 +40,7 @@ module RuboCop
39
40
 
40
41
  MSG_IF = 'Avoid multi-line ternary operators, use `if` or `unless` instead.'
41
42
  MSG_SINGLE_LINE = 'Avoid multi-line ternary operators, use single-line instead.'
42
- SINGLE_LINE_TYPES = %i[return break next send csend].freeze
43
+ SINGLE_LINE_TYPES = %i[return break next send csend super yield].freeze
43
44
 
44
45
  def on_if(node)
45
46
  return unless offense?(node)
@@ -90,7 +91,10 @@ module RuboCop
90
91
  end
91
92
 
92
93
  def enforce_single_line_ternary_operator?(node)
93
- SINGLE_LINE_TYPES.include?(node.parent&.type) && !use_assignment_method?(node.parent)
94
+ return false unless (parent = node.parent)
95
+ return !parent.parenthesized? if parent.defined_type?
96
+
97
+ SINGLE_LINE_TYPES.include?(parent.type) && !use_assignment_method?(parent)
94
98
  end
95
99
 
96
100
  def use_assignment_method?(node)
@@ -50,10 +50,17 @@ module RuboCop
50
50
  unless when_node.conditions.first.first_line == when_node.conditions.last.last_line
51
51
  return true
52
52
  end
53
+ return true if ends_with_endless_range?(when_node.conditions.last)
53
54
  return false unless when_node.body
54
55
 
55
56
  same_line?(when_node, when_node.body)
56
57
  end
58
+
59
+ def ends_with_endless_range?(node)
60
+ node.each_node(:range).any? do |range|
61
+ range.end.nil? && range.source_range.end_pos == node.source_range.end_pos
62
+ end
63
+ end
57
64
  end
58
65
  end
59
66
  end
@@ -146,6 +146,13 @@ module RuboCop
146
146
 
147
147
  MSG = 'Freeze mutable objects assigned to constants.'
148
148
 
149
+ # With `Recursive: true`, `.freeze` is appended to a nested literal. When the literal
150
+ # is the last element and spans multiple lines, the trailing comma lands between the literal
151
+ # and the `.freeze` appended in the same pass.
152
+ def self.autocorrect_incompatible_with
153
+ [Style::HashAsLastArrayItem, Style::TrailingCommaInArrayLiteral, Style::TrailingCommaInHashLiteral]
154
+ end
155
+
149
156
  def on_casgn(node)
150
157
  if node.expression.nil? # This is only the case for `CONST += ...` or similar
151
158
  parent = node.parent
@@ -188,10 +195,12 @@ module RuboCop
188
195
  end
189
196
 
190
197
  def strict_check(value)
198
+ return if value.assignment?
191
199
  return if immutable_literal?(value)
192
200
  return if operation_produces_immutable_object?(value)
193
201
  return if frozen_string_literal?(value)
194
202
  return if shareable_constant_value?(value)
203
+ return if parenthesized_immutable_value?(value)
195
204
 
196
205
  true
197
206
  end
@@ -219,15 +228,32 @@ module RuboCop
219
228
  return
220
229
  end
221
230
 
231
+ return expand_omitted_hash_value(corrector, node.parent) if omitted_hash_value?(node)
232
+
233
+ wrap_for_freeze(corrector, node, expr)
234
+ corrector.insert_after(expr, '.freeze')
235
+
236
+ freeze_nested_literals(corrector, node) if recursive?
237
+ end
238
+
239
+ def omitted_hash_value?(node)
240
+ node.parent&.pair_type? && node.parent.value_omission?
241
+ end
242
+
243
+ def expand_omitted_hash_value(corrector, pair)
244
+ key = pair.key.value
245
+
246
+ corrector.replace(pair, "#{key}: #{key}.freeze")
247
+ end
248
+
249
+ def wrap_for_freeze(corrector, node, expr)
222
250
  if node.array_type? && !node.bracketed?
223
251
  corrector.wrap(expr, '[', ']')
252
+ elsif node.hash_type? && !node.braces?
253
+ corrector.wrap(expr, '{ ', ' }')
224
254
  elsif requires_parentheses?(node)
225
255
  corrector.wrap(expr, '(', ')')
226
256
  end
227
-
228
- corrector.insert_after(expr, '.freeze')
229
-
230
- freeze_nested_literals(corrector, node) if recursive?
231
257
  end
232
258
 
233
259
  # Recursively freezes every nested mutable literal inside an array or
@@ -253,13 +279,14 @@ module RuboCop
253
279
  # Returns the child literals of an array or hash node that may
254
280
  # themselves need freezing. For hashes, both keys and values are
255
281
  # included. Percent-literal arrays (e.g. `%w(a b)`) are skipped because
256
- # `.freeze` cannot be appended to their contents.
282
+ # `.freeze` cannot be appended to their contents. Splat elements are
283
+ # skipped because the splatted object is not stored in the array.
257
284
  def literal_children(node)
258
285
  case node.type
259
286
  when :array
260
287
  return [] if node.percent_literal?
261
288
 
262
- node.children
289
+ node.children.reject(&:splat_type?)
263
290
  when :hash
264
291
  node.children.flat_map { |child| child.pair_type? ? child.children : [] }
265
292
  else
@@ -293,12 +320,30 @@ module RuboCop
293
320
  recent_shareable_value? node
294
321
  end
295
322
 
323
+ def parenthesized_immutable_value?(value)
324
+ return false if !value.begin_type? || !value.children.one?
325
+
326
+ inner = value.children.first
327
+
328
+ !inner.assignment? && !strict_check(inner)
329
+ end
330
+
296
331
  def frozen_regexp_or_range_literals?(node)
297
332
  target_ruby_version >= 3.0 && node.type?(:regexp, :range)
298
333
  end
299
334
 
300
335
  def requires_parentheses?(node)
301
- node.range_type? || (node.send_type? && node.loc.dot.nil?)
336
+ if node.range_type? || node.operator_keyword? || unparenthesized_method_call?(node)
337
+ return true
338
+ end
339
+
340
+ node.if_type? && node.ternary?
341
+ end
342
+
343
+ def unparenthesized_method_call?(node)
344
+ return false unless node.call_type?
345
+
346
+ node.loc.dot.nil? || (node.arguments.any? && !node.parenthesized?)
302
347
  end
303
348
 
304
349
  def correct_splat_expansion(corrector, expr, splat_value)
@@ -118,7 +118,8 @@ module RuboCop
118
118
  if node.ternary?
119
119
  node.if_branch
120
120
  else
121
- range_between(node.condition.source_range.end_pos, node.loc.else.begin_pos)
121
+ if_start = node.loc.begin || node.condition.source_range
122
+ range_between(if_start.end_pos, node.loc.else.begin_pos)
122
123
  end
123
124
  end
124
125
 
@@ -11,6 +11,13 @@ module RuboCop
11
11
  # and their combinations, allowing safe replacement when the receiver matches.
12
12
  # It works with variables, instance variables, class variables, and constants.
13
13
  #
14
+ # @safety
15
+ # This cop is unsafe because the receiver may not be an Array. For a Hash,
16
+ # `hash[hash.size - 1]` looks up the key `hash.size - 1`, which `hash[-1]`
17
+ # does not. Even for an Array, `arr[arr.length - n]` wraps around to an
18
+ # element from the end when `n` exceeds the length, while `arr[-n]`
19
+ # returns `nil`.
20
+ #
14
21
  # @example
15
22
  # # bad
16
23
  # arr[arr.count - 2]
@@ -188,8 +195,9 @@ module RuboCop
188
195
  return false
189
196
  end
190
197
  return true if length_receiver.source == array_receiver.source
198
+ return false unless (base_receiver = extract_base_receiver(array_receiver))
191
199
 
192
- !extract_base_receiver(array_receiver).nil?
200
+ base_receiver.source == (extract_base_receiver(length_receiver) || length_receiver).source
193
201
  end
194
202
 
195
203
  def receivers_match_strict?(length_receiver, array_receiver)
@@ -19,6 +19,19 @@ module RuboCop
19
19
 
20
20
  MSG = 'Avoid using nested modifiers.'
21
21
 
22
+ # Operands that would otherwise bind differently next to `&&`/`||`, or
23
+ # not parse at all.
24
+ # @!method low_precedence?(node)
25
+ def_node_matcher :low_precedence?, <<~PATTERN
26
+ {
27
+ [{and or} semantic_operator?]
28
+ [send prefix_not?]
29
+ [if ternary?]
30
+ assignment?
31
+ [{csend yield super} arguments? !parenthesized? !operator_method?]
32
+ }
33
+ PATTERN
34
+
22
35
  def on_while(node)
23
36
  check(node)
24
37
  end
@@ -63,8 +76,10 @@ module RuboCop
63
76
  end
64
77
 
65
78
  def left_hand_operand(node, operator)
66
- expr = node.condition.source
67
- expr = "(#{expr})" if node.condition.or_type? && operator == '&&'
79
+ expr = operand_source(node.condition)
80
+ if (node.condition.or_type? && operator == '&&') || low_precedence?(node.condition)
81
+ expr = "(#{expr})"
82
+ end
68
83
  expr
69
84
  end
70
85
 
@@ -72,17 +87,20 @@ module RuboCop
72
87
  condition = node.condition
73
88
  negated = left_hand_keyword != node.keyword
74
89
 
75
- expr = if condition.send_type? && !condition.arguments.empty? &&
76
- !condition.operator_method?
77
- add_parentheses_to_method_arguments(condition)
78
- else
79
- condition.source
80
- end
90
+ expr = operand_source(condition)
81
91
  expr = "(#{expr})" if requires_parens?(condition, negated)
82
92
  expr = "!#{expr}" if negated
83
93
  expr
84
94
  end
85
95
 
96
+ def operand_source(condition)
97
+ if condition.send_type? && !condition.arguments.empty? && !condition.operator_method?
98
+ add_parentheses_to_method_arguments(condition)
99
+ else
100
+ condition.source
101
+ end
102
+ end
103
+
86
104
  def add_parentheses_to_method_arguments(send_node)
87
105
  expr = +''
88
106
  expr << "#{send_node.receiver.source}." if send_node.receiver
@@ -97,6 +115,7 @@ module RuboCop
97
115
  # whole expression (e.g. `!(a && b)`, not `!a && b`).
98
116
  (negated && node.operator_keyword?) ||
99
117
  node.or_type? ||
118
+ low_precedence?(node) ||
100
119
  !(RuboCop::AST::Node::COMPARISON_OPERATORS & node.children).empty?
101
120
  end
102
121
  end
@@ -26,7 +26,7 @@ module RuboCop
26
26
 
27
27
  node.each_descendant(:if).select(&:ternary?).each do |nested_ternary|
28
28
  add_offense(nested_ternary) do |corrector|
29
- next if part_of_ignored_node?(node)
29
+ next if part_of_ignored_node?(node) || contains_heredoc?(node)
30
30
 
31
31
  autocorrect(corrector, node)
32
32
  ignore_node(node)
@@ -36,21 +36,39 @@ module RuboCop
36
36
 
37
37
  private
38
38
 
39
+ def contains_heredoc?(node)
40
+ node.each_descendant(:any_str).any?(&:heredoc?)
41
+ end
42
+
39
43
  def autocorrect(corrector, if_node)
40
44
  replace_loc_and_whitespace(corrector, if_node.loc.question, "\n")
41
45
  replace_loc_and_whitespace(corrector, if_node.loc.colon, "\nelse\n")
42
- corrector.replace(if_node.if_branch, remove_parentheses(if_node.if_branch.source))
43
- corrector.wrap(if_node, 'if ', "\nend")
44
- end
46
+ remove_parentheses(corrector, if_node.if_branch)
45
47
 
46
- def remove_parentheses(source)
47
- if source.start_with?('(') && source.end_with?(')')
48
- source.delete_prefix('(').delete_suffix(')')
48
+ if modifier_position?(if_node)
49
+ corrector.wrap(if_node, '(if ', "\nend)")
49
50
  else
50
- source
51
+ corrector.wrap(if_node, 'if ', "\nend")
51
52
  end
52
53
  end
53
54
 
55
+ def remove_parentheses(corrector, node)
56
+ return unless node.begin_type? && node.parenthesized_call?
57
+
58
+ corrector.remove(node.loc.begin)
59
+ corrector.remove(node.loc.end)
60
+ end
61
+
62
+ # An `if` right after `return`, as the operand of `not` or `defined?`, or as
63
+ # the first argument of a call without parentheses would be parsed as a modifier.
64
+ def modifier_position?(node)
65
+ return false unless (parent = node.parent)
66
+ return true if parent.type?(:return, :break, :next)
67
+ return false if !parent.type?(:call, :super, :yield, :defined?) || parent.parenthesized?
68
+
69
+ parent.prefix_not? || parent.first_argument.equal?(node)
70
+ end
71
+
54
72
  def replace_loc_and_whitespace(corrector, range, replacement)
55
73
  corrector.replace(
56
74
  range_with_surrounding_space(range: range, whitespace: true),