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
@@ -75,7 +75,7 @@ module RuboCop
75
75
  return if comments_between_else_and_if?(node, else_branch)
76
76
 
77
77
  add_offense(else_branch.loc.keyword) do |corrector|
78
- next if part_of_ignored_node?(node)
78
+ next if part_of_ignored_node?(node) || shares_lines_with_other_code?(else_branch)
79
79
 
80
80
  autocorrect(corrector, else_branch)
81
81
  ignore_node(node)
@@ -84,6 +84,26 @@ module RuboCop
84
84
 
85
85
  private
86
86
 
87
+ # The `elsif` correction moves and removes whole lines, so it can't be applied when
88
+ # the nested `if` condition, its `if` branch or its `end` shares a line with other code.
89
+ # A modifier `if` on the `else` line would leave its body on the `elsif` line.
90
+ def shares_lines_with_other_code?(node)
91
+ return false if node.then?
92
+ return !begins_its_line?(node.source_range) if node.modifier_form?
93
+
94
+ moved_ranges(node).any? { |range| !begins_its_line?(range) || !ends_its_line?(range) }
95
+ end
96
+
97
+ def moved_ranges(node)
98
+ ranges = [if_condition_range(node, node.condition), find_end_range(node)]
99
+ ranges << range_with_comments(node.if_branch) if node.if_branch
100
+ ranges
101
+ end
102
+
103
+ def ends_its_line?(range)
104
+ processed_source.lines[range.last_line - 1][range.last_column..].match?(/\A\s*(#|\z)/)
105
+ end
106
+
87
107
  def autocorrect(corrector, node)
88
108
  if node.then?
89
109
  # If the nested `if` is a then node, correct it first,
@@ -100,6 +100,7 @@ module RuboCop
100
100
  pattern_matching_nodes(condition).any?
101
101
 
102
102
  return unless (msg = message(node))
103
+ return if modifier_sharing_line_in_logical_operator?(node)
103
104
 
104
105
  add_offense(node.loc.keyword, message: format(msg, keyword: node.keyword)) do |corrector|
105
106
  next if part_of_ignored_node?(node)
@@ -159,7 +160,7 @@ module RuboCop
159
160
  corrector.replace(node, replacement)
160
161
  end
161
162
 
162
- def replacement_for_modifier_form(corrector, node) # rubocop:disable Metrics/AbcSize
163
+ def replacement_for_modifier_form(corrector, node)
163
164
  comment = comment_on_node_line(node)
164
165
  if comment && too_long_due_to_comment_after_modifier?(node, comment)
165
166
  remove_comment(corrector, node, comment)
@@ -167,12 +168,11 @@ module RuboCop
167
168
  return to_modifier_form_with_move_comment(node, indent(node), comment)
168
169
  end
169
170
 
170
- last_argument = node.if_branch.last_argument if node.if_branch.send_type?
171
- if last_argument.respond_to?(:heredoc?) && last_argument.heredoc?
172
- heredoc = extract_heredoc_from(last_argument)
173
- remove_heredoc(corrector, heredoc)
171
+ heredoc_ranges = heredoc_ranges_in(node)
172
+ unless heredoc_ranges.empty?
173
+ remove_heredoc(corrector, heredoc_ranges)
174
174
 
175
- return to_normal_form_with_heredoc(node, indent(node), heredoc)
175
+ return to_normal_form_with_heredoc(node, indent(node))
176
176
  end
177
177
 
178
178
  to_normal_form(node, indent(node))
@@ -234,11 +234,18 @@ module RuboCop
234
234
  end
235
235
 
236
236
  def collection_from_ancestor(node)
237
- return node if node&.type?(:array, :call)
237
+ return node if node&.type?(:array, :call, :and, :or)
238
238
 
239
239
  node.parent if node&.type?(:pair)
240
240
  end
241
241
 
242
+ def modifier_sharing_line_in_logical_operator?(node)
243
+ collection = find_containing_collection(node)
244
+ return false if !node.modifier_form? || !collection&.operator_keyword?
245
+
246
+ another_modifier_if_on_same_line?(node)
247
+ end
248
+
242
249
  def another_modifier_if_on_same_line?(node)
243
250
  collection = find_containing_collection(node)
244
251
  return false unless collection
@@ -275,16 +282,20 @@ module RuboCop
275
282
  RUBY
276
283
  end
277
284
 
278
- def to_normal_form_with_heredoc(node, indentation, heredoc)
279
- heredoc_body, heredoc_end = heredoc
285
+ def remove_heredoc(corrector, heredoc_ranges)
286
+ heredoc_ranges.each do |range|
287
+ corrector.remove(range_by_whole_lines(range, include_final_newline: true))
288
+ end
289
+ end
280
290
 
281
- <<~RUBY.chomp
282
- #{node.keyword} #{node.condition.source}
283
- #{indentation} #{node.body.source}
284
- #{indentation} #{heredoc_body.source.chomp}
285
- #{indentation} #{heredoc_end.source.chomp}
286
- #{indentation}end
287
- RUBY
291
+ def to_normal_form_with_heredoc(node, indentation)
292
+ [
293
+ "#{node.keyword} #{node.condition.source}",
294
+ *heredoc_ranges_in(node.condition).map(&:source),
295
+ "#{indentation} #{node.body.source}",
296
+ *heredoc_ranges_in(node.if_branch).map(&:source),
297
+ "#{indentation}end"
298
+ ].join("\n")
288
299
  end
289
300
 
290
301
  def to_modifier_form_with_move_comment(node, indentation, comment)
@@ -294,17 +305,12 @@ module RuboCop
294
305
  RUBY
295
306
  end
296
307
 
297
- def extract_heredoc_from(last_argument)
298
- heredoc_body = last_argument.loc.heredoc_body
299
- heredoc_end = last_argument.loc.heredoc_end
300
-
301
- [heredoc_body, heredoc_end]
302
- end
303
-
304
- def remove_heredoc(corrector, heredoc)
305
- heredoc.each do |range|
306
- corrector.remove(range_by_whole_lines(range, include_final_newline: true))
308
+ def heredoc_ranges_in(body)
309
+ ranges = body.each_node(:any_str).select(&:heredoc?).map do |heredoc|
310
+ range_by_whole_lines(heredoc.loc.heredoc_body.join(heredoc.loc.heredoc_end))
307
311
  end
312
+ # A heredoc opened in another heredoc's interpolation is moved along with it.
313
+ ranges.reject { |range| ranges.any? { |other| other.contains?(range) } }
308
314
  end
309
315
 
310
316
  def comment_on_node_line(node)
@@ -28,6 +28,10 @@ module RuboCop
28
28
 
29
29
  MSG = 'Avoid modifier `%<keyword>s` after another conditional.'
30
30
 
31
+ def self.autocorrect_incompatible_with
32
+ [Style::SoleNestedConditional]
33
+ end
34
+
31
35
  # rubocop:disable-next Metrics/AbcSize
32
36
  def on_if(node)
33
37
  return unless node.modifier_form? && node.body.if_type?
@@ -21,6 +21,10 @@ module RuboCop
21
21
  MSG_NEWLINE = 'Do not use `%<keyword>s %<expr>s;` - use a newline instead.'
22
22
  MSG_TERNARY = 'Do not use `%<keyword>s %<expr>s;` - use a ternary operator instead.'
23
23
 
24
+ LOWER_PRECEDENCE_THAN_TERNARY_TYPES = %i[
25
+ and or if rescue any_match_pattern while until while_post until_post
26
+ ].freeze
27
+
24
28
  def on_normal_if_unless(node)
25
29
  return if node.parent&.if_type?
26
30
  return if part_of_ignored_node?(node)
@@ -77,12 +81,14 @@ module RuboCop
77
81
  def replacement(node)
78
82
  return correct_elsif(node) if node.else_branch&.if_type?
79
83
 
80
- then_code = node.if_branch ? build_expression(node.if_branch) : 'nil'
81
- else_code = node.else_branch ? build_expression(node.else_branch) : 'nil'
84
+ then_code = node.if_branch ? ternary_operand(node.if_branch) : 'nil'
85
+ else_code = node.else_branch ? ternary_operand(node.else_branch) : 'nil'
82
86
 
83
87
  then_code, else_code = else_code, then_code if node.unless?
84
88
 
85
- "#{ternary_condition(node)} ? #{then_code} : #{else_code}"
89
+ ternary = "#{ternary_condition(node)} ? #{then_code} : #{else_code}"
90
+
91
+ operand_of_parent?(node) ? "(#{ternary})" : ternary
86
92
  end
87
93
 
88
94
  def correct_elsif(node)
@@ -108,7 +114,34 @@ module RuboCop
108
114
  # `(a = b) ? c : d`), changing what gets assigned.
109
115
  def ternary_condition(node)
110
116
  condition = node.condition
111
- condition.assignment? ? "(#{condition.source})" : condition.source
117
+ condition.assignment? ? "(#{condition.source})" : ternary_operand(condition)
118
+ end
119
+
120
+ def ternary_operand(expr)
121
+ lower_precedence_than_ternary?(expr) ? "(#{expr.source})" : build_expression(expr)
122
+ end
123
+
124
+ # Expressions that bind looser than `?:` (or cannot appear unparenthesized
125
+ # inside it) would otherwise absorb or break the ternary.
126
+ def lower_precedence_than_ternary?(node)
127
+ return true if node.type?(*LOWER_PRECEDENCE_THAN_TERNARY_TYPES)
128
+ return true if node.send_type? && (node.prefix_not? || prefix_bang_on_command?(node))
129
+
130
+ node.type?(:yield, :super, :defined?) && node.arguments.any? && !node.parenthesized?
131
+ end
132
+
133
+ def operand_of_parent?(node)
134
+ return false unless (parent = node.parent)
135
+ return true if parent.operator_keyword?
136
+
137
+ parent.call_type? && call_operand?(parent, node)
138
+ end
139
+
140
+ def call_operand?(call, node)
141
+ return false if call.prefix_not?
142
+ return true if call.receiver.equal?(node)
143
+
144
+ call.operator_method? && !call.method?(:[]) && !call.method?(:[]=)
112
145
  end
113
146
 
114
147
  def build_else_branch(second_condition)
@@ -131,6 +164,16 @@ module RuboCop
131
164
  result
132
165
  end
133
166
 
167
+ # `!foo a` leaves the arguments of `foo` unparenthesized at the end of the operand,
168
+ # where they would absorb the rest of the ternary.
169
+ def prefix_bang_on_command?(node)
170
+ return false unless node.prefix_bang?
171
+
172
+ receiver = node.receiver
173
+
174
+ require_argument_parentheses?(receiver) || lower_precedence_than_ternary?(receiver)
175
+ end
176
+
134
177
  def require_argument_parentheses?(node)
135
178
  return false if !node.call_type? || node.arithmetic_operation?
136
179
 
@@ -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