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
@@ -212,16 +212,23 @@ module RuboCop
212
212
  end
213
213
 
214
214
  def remove_local_variable_assignment_part(corrector, node)
215
- corrector.remove(node.loc.name.begin.join(node.expression.source_range.begin))
216
- end
217
-
218
- def variable_in_loop_condition?(assignment_node, variable)
219
- return false if assignment_node.each_ancestor(:any_def).any?
215
+ expression = node.expression
216
+ assignment_part = node.loc.name.begin.join(expression.source_range.begin)
220
217
 
221
- loop_node = assignment_node.each_ancestor.find do |ancestor|
222
- ancestor.type?(*VariableForce::LOOP_TYPES)
218
+ # `x = 1, 2` cannot become `1, 2`, so the values become an array literal.
219
+ if expression.array_type? && !expression.bracketed?
220
+ corrector.replace(assignment_part, '[')
221
+ corrector.insert_after(expression, ']')
222
+ else
223
+ corrector.remove(assignment_part)
223
224
  end
225
+ end
224
226
 
227
+ def variable_in_loop_condition?(assignment_node, variable)
228
+ # A method defined inside the loop has its own scope, so the loop condition
229
+ # can only read the variable if the loop is nearer than any method definition.
230
+ loop_node = assignment_node.each_ancestor(*VariableForce::LOOP_TYPES, :any_def).first
231
+ return false if !loop_node || loop_node.any_def_type?
225
232
  return false unless loop_node.respond_to?(:condition)
226
233
 
227
234
  condition_node = loop_node.condition
@@ -35,15 +35,13 @@ module RuboCop
35
35
  ASSIGNMENT_TYPES = %i[lvasgn ivasgn cvasgn gvasgn].freeze
36
36
 
37
37
  def on_def(node)
38
- return unless node.body
38
+ return if !node.body || node.void_context?
39
39
 
40
40
  last_expr = last_expression(node.body)
41
41
  return unless setter_call_to_local_variable?(last_expr)
42
42
 
43
- tracker = MethodVariableTracker.new(node.body)
44
43
  receiver, = *last_expr
45
- variable_name, = *receiver
46
- return unless tracker.contain_local_object?(variable_name)
44
+ return unless contain_local_object?(node.body, receiver)
47
45
 
48
46
  loc_name = receiver.loc.name
49
47
 
@@ -66,6 +64,11 @@ module RuboCop
66
64
  expression.is_a?(Array) ? expression.last : expression
67
65
  end
68
66
 
67
+ def contain_local_object?(body, receiver)
68
+ variable_name, = *receiver
69
+ MethodVariableTracker.new(body).contain_local_object?(variable_name)
70
+ end
71
+
69
72
  # This class tracks variable assignments in a method body
70
73
  # and if a variable contains object passed as argument at the end of
71
74
  # the method.
@@ -271,7 +271,14 @@ module RuboCop
271
271
  return if node.parent.type?(:if, :case, :when, :case_match, :in_pattern)
272
272
  return if (def_node = node.each_ancestor(:any_def).first) && def_node.assignment_method?
273
273
 
274
- corrector.remove(range_with_surrounding_space(range: node.source_range, side: :left))
274
+ range = range_with_surrounding_space(range: void_expression_range(node), side: :left)
275
+ corrector.remove(range)
276
+ end
277
+
278
+ def void_expression_range(node)
279
+ return node.source_range unless node.respond_to?(:heredoc?) && node.heredoc?
280
+
281
+ node.source_range.join(node.loc.heredoc_end)
275
282
  end
276
283
 
277
284
  def autocorrect_nonmutating_send(corrector, node, suggestion)
@@ -40,6 +40,8 @@ module RuboCop
40
40
  class ClassLength < Base
41
41
  include CodeLength
42
42
 
43
+ LABEL = 'Class'
44
+
43
45
  def on_class(node)
44
46
  check_code_length(node)
45
47
  end
@@ -60,8 +62,8 @@ module RuboCop
60
62
 
61
63
  private
62
64
 
63
- def message(length, max_length)
64
- format('Class has too many lines. [%<length>d/%<max>d]', length: length, max: max_length)
65
+ def cop_label
66
+ LABEL
65
67
  end
66
68
 
67
69
  def find_expression_within_parent(parent)
@@ -48,10 +48,6 @@ module RuboCop
48
48
 
49
49
  1
50
50
  end
51
-
52
- def count_block?(block)
53
- KNOWN_ITERATING_METHODS.include? block.method_name
54
- end
55
51
  end
56
52
  end
57
53
  end
@@ -38,6 +38,8 @@ module RuboCop
38
38
  class ModuleLength < Base
39
39
  include CodeLength
40
40
 
41
+ LABEL = 'Module'
42
+
41
43
  def on_module(node)
42
44
  check_code_length(node)
43
45
  end
@@ -53,8 +55,8 @@ module RuboCop
53
55
  (casgn nil? _ (any_block (send (const {nil? cbase} :Module) :new) ...))
54
56
  PATTERN
55
57
 
56
- def message(length, max_length)
57
- format('Module has too many lines. [%<length>d/%<max>d]', length: length, max: max_length)
58
+ def cop_label
59
+ LABEL
58
60
  end
59
61
  end
60
62
  end
@@ -15,6 +15,10 @@ module RuboCop
15
15
  cop_config['IndentationWidth'] || config.for_cop('Layout/IndentationWidth')['Width'] || 2
16
16
  end
17
17
 
18
+ def using_tabs?
19
+ config.for_cop('Layout/IndentationStyle')['EnforcedStyle'] == 'tabs'
20
+ end
21
+
18
22
  def indentation(node)
19
23
  offset(node) + (SPACE * configured_indentation_width)
20
24
  end
@@ -76,11 +76,7 @@ module RuboCop
76
76
  def extract_first_element_over_column_limit(node, elements, max)
77
77
  line = node.first_line
78
78
 
79
- # If a `send` or `csend` node is not parenthesized, don't move the first element, because it
80
- # can result in changed behavior or a syntax error.
81
- if node.call_type? && !node.parenthesized? && !first_argument_is_heredoc?(node)
82
- elements = elements.drop(1)
83
- end
79
+ elements = elements.drop(1) unless first_element_movable?(node)
84
80
 
85
81
  i = 0
86
82
  i += 1 while within_column_limit?(elements[i], max, line)
@@ -93,23 +89,35 @@ module RuboCop
93
89
  end
94
90
 
95
91
  # @api private
96
- def first_argument_is_heredoc?(node)
97
- first_argument = node.first_argument
92
+ # If a method call or definition is not parenthesized, the first element cannot be moved,
93
+ # because it can result in changed behavior or a syntax error.
94
+ def first_element_movable?(node)
95
+ return !node.arguments.loc.begin.nil? if node.any_def_type?
96
+ return true if !node.call_type? || node.parenthesized?
98
97
 
99
- first_argument.respond_to?(:heredoc?) && first_argument.heredoc?
98
+ node.first_argument.respond_to?(:heredoc?) && node.first_argument.heredoc?
100
99
  end
101
100
 
102
101
  # @api private
103
102
  # If a `send` or `csend` node contains a heredoc argument, splitting cannot happen
104
103
  # after the heredoc or else it will cause a syntax error.
105
104
  def shift_elements_for_heredoc_arg(node, elements, index)
106
- return index unless node.type?(:call, :array)
105
+ return index unless node.type?(:call, :array, :hash)
106
+ return index unless (heredoc_pos = heredoc_begin_position(node))
107
+
108
+ last_safe = elements.rindex { |element| element.source_range.begin_pos <= heredoc_pos }
109
+ return nil if last_safe.nil? ||
110
+ (last_safe.zero? && elements.first.source_range.end_pos > heredoc_pos)
107
111
 
108
- heredoc_index = elements.index { |arg| arg.respond_to?(:heredoc?) && arg.heredoc? }
109
- return index unless heredoc_index
110
- return nil if heredoc_index.zero?
112
+ [index, last_safe + 1].min
113
+ end
111
114
 
112
- heredoc_index >= index ? index : heredoc_index + 1
115
+ # @api private
116
+ def heredoc_begin_position(node)
117
+ children = node.call_type? ? node.arguments : node.children
118
+ children.flat_map { |child| [child, *child.each_descendant(:any_str)] }
119
+ .find { |descendant| descendant.type?(:any_str) && descendant.heredoc? }
120
+ &.source_range&.begin_pos
113
121
  end
114
122
 
115
123
  # @api private
@@ -128,9 +136,7 @@ module RuboCop
128
136
  # or redundant edits, we only mark one offense at a time.
129
137
  return true if contained_by_breakable_collection_on_same_line?(node)
130
138
 
131
- return true if contained_by_multiline_collection_that_could_be_broken_up?(node)
132
-
133
- false
139
+ contained_by_multiline_collection_that_could_be_broken_up?(node)
134
140
  end
135
141
 
136
142
  # @api private
@@ -145,9 +151,7 @@ module RuboCop
145
151
  # break before the second argument and the rest of the
146
152
  # argument will get auto-formatted onto separate lines
147
153
  # by other cops.
148
- has_second_element = elements.length >= 2
149
-
150
- starts_with_bracket && has_second_element
154
+ starts_with_bracket && elements.length >= 2
151
155
  end
152
156
 
153
157
  # @api private
@@ -5,6 +5,8 @@ module RuboCop
5
5
  # Checks for code on multiple lines that could be rewritten on a single line
6
6
  # without changing semantics or exceeding the `Max` parameter of `Layout/LineLength`.
7
7
  module CheckSingleLineSuitability
8
+ include LineLengthHelp
9
+
8
10
  def suitable_as_single_line?(node)
9
11
  !too_long?(node) &&
10
12
  !comment_within?(node) &&
@@ -14,10 +16,14 @@ module RuboCop
14
16
  private
15
17
 
16
18
  def too_long?(node)
19
+ too_long_as_single_line?(node.first_line..node.last_line)
20
+ end
21
+
22
+ def too_long_as_single_line?(line_range)
17
23
  return false unless max_line_length
18
24
 
19
- lines = processed_source.lines[(node.first_line - 1)...node.last_line]
20
- to_single_line(lines.join("\n")).length > max_line_length
25
+ lines = processed_source.lines[(line_range.begin - 1)...line_range.end]
26
+ line_length(to_single_line(lines.join("\n"))) > max_line_length
21
27
  end
22
28
 
23
29
  def to_single_line(source)
@@ -59,12 +59,20 @@ module RuboCop
59
59
  return node.loc.name if node.casgn_type?
60
60
 
61
61
  if LSP.enabled?
62
- end_range = node.loc?(:name) ? node.loc.name : node.loc.begin
63
- node.source_range.begin.join(end_range)
62
+ node.source_range.begin.join(header_end(node))
64
63
  else
65
64
  node.source_range
66
65
  end
67
66
  end
67
+
68
+ # Where the header of a definition ends: the name of a class, module or
69
+ # method, the `self` of `class << self`, or the opening of a block.
70
+ def header_end(node)
71
+ return node.loc.name if node.loc?(:name)
72
+ return node.identifier.source_range if node.sclass_type?
73
+
74
+ node.loc.begin
75
+ end
68
76
  end
69
77
  end
70
78
  end
@@ -66,7 +66,7 @@ module RuboCop
66
66
  # the line at which the parent node ends.
67
67
  # rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
68
68
  def find_end_line(node)
69
- if node.if_type?
69
+ if node.if_type? && !node.modifier_form?
70
70
  if node.else?
71
71
  node.loc.else.line
72
72
  elsif node.ternary?
@@ -5,7 +5,7 @@ module RuboCop
5
5
  module Layout
6
6
  # Common functionality for checking if presence/absence of empty lines
7
7
  # around some kind of body matches the configuration.
8
- module EmptyLinesAroundBody
8
+ module EmptyLinesAroundBody # rubocop:disable Metrics/ModuleLength
9
9
  extend NodePattern::Macros
10
10
  include ConfigurableEnforcedStyle
11
11
  include RangeHelp
@@ -14,6 +14,8 @@ module RuboCop
14
14
  MSG_MISSING = 'Empty line missing at %<kind>s body %<location>s.'
15
15
  MSG_DEFERRED = 'Empty line missing before first %<type>s definition'
16
16
 
17
+ LEADING_DOT_TYPES = %i[tDOT tANDDOT].to_set.freeze
18
+
17
19
  private
18
20
 
19
21
  # @!method constant_definition?(node)
@@ -29,6 +31,7 @@ module RuboCop
29
31
 
30
32
  first_line = adjusted_first_line || node.source_range.first_line
31
33
  last_line = node.source_range.last_line
34
+ return if first_line >= last_line
32
35
 
33
36
  case style
34
37
  when :empty_lines_except_namespace
@@ -99,12 +102,20 @@ module RuboCop
99
102
  return unless yield(processed_source.lines[line])
100
103
 
101
104
  offset = style == :empty_lines && msg.include?('end.') ? 2 : 1
105
+ return if style == :empty_lines && leading_dot_line?(line + offset)
106
+
102
107
  range = source_range(processed_source.buffer, line + offset, 0)
103
108
  add_offense(range, message: msg) do |corrector|
104
109
  EmptyLineCorrector.correct(corrector, [style, range])
105
110
  end
106
111
  end
107
112
 
113
+ def leading_dot_line?(line)
114
+ first_token = processed_source.sorted_tokens.find { |token| token.line == line }
115
+
116
+ LEADING_DOT_TYPES.include?(first_token&.type)
117
+ end
118
+
108
119
  def check_deferred_empty_line(body)
109
120
  node = first_empty_line_required_child(body)
110
121
  return unless node
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ # Common functionality for checking whether a method body can become the
6
+ # body of an endless method definition without changing its meaning.
7
+ module EndlessMethodBodyHelp
8
+ private
9
+
10
+ def endless_method_body?(body)
11
+ return false if body.type?(:begin, :kwbegin, :rescue, :ensure, :masgn)
12
+ return false if low_precedence_body?(body)
13
+ return false if ends_with_omitted_hash_value?(body) || ends_with_anonymous_argument?(body)
14
+ return false if body.assignment? && multiple_values?(assigned_value(body))
15
+
16
+ !command_disallowed_in_endless_body?(body)
17
+ end
18
+
19
+ # `def foo = bar and baz` parses as `(def foo = bar) and baz`.
20
+ def low_precedence_body?(body)
21
+ body.type?(:any_match_pattern, :while_post, :until_post) ||
22
+ (body.type?(:and, :or) && body.semantic_operator?) ||
23
+ (body.type?(:if, :while, :until) && body.modifier_form?)
24
+ end
25
+
26
+ # `foo = 1, 2` and `foo = *bar` assign an array without brackets.
27
+ def multiple_values?(node)
28
+ node.array_type? && !node.bracketed?
29
+ end
30
+
31
+ def ends_with_omitted_hash_value?(body)
32
+ body.each_descendant(:pair).any? do |pair|
33
+ pair.value_omission? && pair.source_range.end_pos == body.source_range.end_pos
34
+ end
35
+ end
36
+
37
+ def ends_with_anonymous_argument?(body)
38
+ forwarding = %i[forwarded_restarg forwarded_kwrestarg block_pass]
39
+
40
+ body.each_descendant(*forwarding).any? do |argument|
41
+ argument.source_range.end_pos == body.source_range.end_pos &&
42
+ (!argument.block_pass_type? || argument.children.first.nil?)
43
+ end
44
+ end
45
+
46
+ def command_disallowed_in_endless_body?(body)
47
+ return true if body.assignment? && command_call?(assigned_value(body))
48
+ return true if body_disallowed_in_argument?(body)
49
+ return true if negated_command_call?(body)
50
+
51
+ body.each_node(:any_block).any? do |block|
52
+ block.keywords? && command_call?(block.send_node)
53
+ end
54
+ end
55
+
56
+ def body_disallowed_in_argument?(body)
57
+ command_body_in_argument?(body) || do_end_block_body_in_command_argument?(body)
58
+ end
59
+
60
+ # `private def foo = bar baz` parses only on Ruby 4.0+, and only as the sole argument.
61
+ def command_body_in_argument?(body)
62
+ return false unless command_call?(body)
63
+
64
+ call = argument_receiving_call(body.parent)
65
+ return false if call.nil?
66
+
67
+ target_ruby_version < 4.0 || !call.arguments.one?
68
+ end
69
+
70
+ def argument_receiving_call(def_node)
71
+ call = def_node.parent
72
+ return unless call&.call_type?
73
+
74
+ call if call.arguments.any? { |argument| argument.equal?(def_node) }
75
+ end
76
+
77
+ def do_end_block_body_in_command_argument?(body)
78
+ call = argument_receiving_call(body.parent)
79
+ return false if call.nil? || call.parenthesized?
80
+
81
+ body.each_node(:any_block).any? do |block|
82
+ block.keywords? && !block.send_node.lambda_literal?
83
+ end
84
+ end
85
+
86
+ def assigned_value(node)
87
+ while node.assignment? || node.rescue_type?
88
+ node = node.rescue_type? ? node.body : node.children.last
89
+ end
90
+ node
91
+ end
92
+
93
+ def command_call?(node)
94
+ node = node.send_node if node.any_block_type?
95
+ return false unless node.type?(:call, :super, :yield)
96
+
97
+ node.arguments? && !node.parenthesized? && !node.operator_method? && !node.setter_method?
98
+ end
99
+
100
+ def negated_command_call?(node)
101
+ node.send_type? && node.prefix_bang? && command_call?(node.receiver)
102
+ end
103
+ end
104
+ end
105
+ end
@@ -7,9 +7,9 @@ module RuboCop
7
7
  SIGILS = '@$' # if a variable starts with a sigil it will be removed
8
8
 
9
9
  def forbidden_identifier?(name)
10
- name = name.to_s.delete(SIGILS)
10
+ return false if forbidden_identifiers.empty?
11
11
 
12
- forbidden_identifiers.any? && forbidden_identifiers.include?(name)
12
+ forbidden_identifiers.include?(name.to_s.delete(SIGILS))
13
13
  end
14
14
 
15
15
  def forbidden_identifiers
@@ -55,13 +55,21 @@ module RuboCop
55
55
  # Common functionality for checking alignment of hash values.
56
56
  module ValueAlignment
57
57
  def checkable_layout?(node)
58
- !node.pairs_on_same_line? && !node.mixed_delimiters? && alignable_values?(node)
58
+ !node.pairs_on_same_line? && !node.mixed_delimiters? && alignable_values?(node) &&
59
+ !pair_after_kwsplat_on_same_line?(node)
59
60
  end
60
61
 
62
+ # With several enforced styles, deltas are computed even for a hash this style
63
+ # can't check. A pair that doesn't begin its line can't be moved without
64
+ # deleting whatever precedes it, so it's left alone.
61
65
  def deltas(first_pair, current_pair)
66
+ return {} unless current_pair.equal?(first_pair) ||
67
+ Util.begins_its_line?(current_pair.source_range)
68
+
62
69
  key_delta = key_delta(first_pair, current_pair)
63
- separator_delta = separator_delta(first_pair, current_pair, key_delta)
64
- value_delta = value_delta(first_pair, current_pair) - key_delta - separator_delta
70
+ shift = separator_shift(current_pair, key_delta)
71
+ separator_delta = separator_delta(first_pair, current_pair, shift)
72
+ value_delta = value_delta(first_pair, current_pair) - shift - separator_delta
65
73
 
66
74
  { key: key_delta, separator: separator_delta, value: value_delta }
67
75
  end
@@ -78,9 +86,22 @@ module RuboCop
78
86
  pair.value && pair.key.last_line != pair.value.first_line
79
87
  end
80
88
 
81
- def separator_delta(first_pair, current_pair, key_delta)
89
+ def pair_after_kwsplat_on_same_line?(node)
90
+ first_pair = node.pairs.first
91
+
92
+ node.children.each_cons(2).any? do |previous, current|
93
+ current.pair_type? && !current.equal?(first_pair) &&
94
+ previous.last_line == current.first_line
95
+ end
96
+ end
97
+
98
+ def separator_shift(current_pair, key_delta)
99
+ current_pair.key.multiline? ? 0 : key_delta
100
+ end
101
+
102
+ def separator_delta(first_pair, current_pair, shift)
82
103
  if current_pair.hash_rocket?
83
- hash_rocket_delta(first_pair, current_pair) - key_delta
104
+ hash_rocket_delta(first_pair, current_pair) - shift
84
105
  else
85
106
  0
86
107
  end
@@ -152,6 +173,17 @@ module RuboCop
152
173
  {}
153
174
  end
154
175
 
176
+ # Keys are right-aligned by the column their last line ends on.
177
+ # A key spanning several lines ends on a line other than the one it starts on,
178
+ # so moving its first line would never change that column and the correction
179
+ # would be repeated on every pass. Such pairs are left alone, and so is every pair
180
+ # when the first key spans several lines, since there is then no column to right-align to.
181
+ def deltas(first_pair, current_pair)
182
+ return {} if first_pair.key.multiline? || current_pair.key.multiline?
183
+
184
+ super
185
+ end
186
+
155
187
  private
156
188
 
157
189
  def key_delta(first_pair, current_pair)
@@ -163,7 +195,10 @@ module RuboCop
163
195
  end
164
196
 
165
197
  def value_delta(first_pair, current_pair)
166
- current_pair.value_omission? ? 0 : first_pair.value_delta(current_pair)
198
+ return 0 if first_pair.equal?(current_pair) || current_pair.value_omission?
199
+
200
+ expected_value_column = first_pair.loc.operator.end.column + 1
201
+ expected_value_column - current_pair.value.loc.column
167
202
  end
168
203
  end
169
204
 
@@ -11,6 +11,15 @@ module RuboCop
11
11
  DO_NOT_MIX_OMIT_VALUE_MSG = "#{DO_NOT_MIX_MSG_PREFIX} #{OMIT_HASH_VALUE_MSG}"
12
12
  DO_NOT_MIX_EXPLICIT_VALUE_MSG = "#{DO_NOT_MIX_MSG_PREFIX} #{EXPLICIT_HASH_VALUE_MSG}"
13
13
 
14
+ # A keyword parameter can be named after a keyword, as in `def foo(if:)`,
15
+ # but the parameter can only be referenced by an omitted hash value,
16
+ # so `{ if: }` has no explicit form: `{ if: if }` is invalid Ruby.
17
+ KEYWORDS = %i[
18
+ __ENCODING__ __FILE__ __LINE__ BEGIN END alias and begin break case class def do
19
+ else elsif end ensure false for if in module next nil not or redo rescue retry
20
+ return self super then true undef unless until when while yield
21
+ ].to_set.freeze
22
+
14
23
  DefNode = Struct.new(:node) do
15
24
  def selector
16
25
  if node.loc?(:selector)
@@ -53,7 +62,7 @@ module RuboCop
53
62
  replacement = "#{hash_key_source}:"
54
63
  self.config_to_allow_offenses = { 'Enabled' => false }
55
64
  else
56
- return unless node.value_omission?
65
+ return if !node.value_omission? || keyword_value?(node)
57
66
 
58
67
  message = EXPLICIT_HASH_VALUE_MSG
59
68
  replacement = "#{hash_key_source}: #{hash_key_source}"
@@ -97,7 +106,16 @@ module RuboCop
97
106
  end
98
107
 
99
108
  def hash_rockets_enforced?(hash_node)
100
- style == :hash_rockets || force_hash_rockets?(hash_node.pairs)
109
+ style == :hash_rockets || force_hash_rockets?(hash_node.pairs) ||
110
+ no_mixed_keys_enforces_hash_rockets?(hash_node.pairs)
111
+ end
112
+
113
+ def no_mixed_keys_enforces_hash_rockets?(pairs)
114
+ case style
115
+ when :no_mixed_keys then !sym_indices?(pairs) || pairs.first.delimiter == '=>'
116
+ when :ruby19_no_mixed_keys then !sym_indices?(pairs)
117
+ else false
118
+ end
101
119
  end
102
120
 
103
121
  def enforced_shorthand_syntax
@@ -210,6 +228,8 @@ module RuboCop
210
228
  def mixed_shorthand_syntax_check(hash_value_type_breakdown)
211
229
  if hash_with_values_that_cant_be_omitted?(hash_value_type_breakdown)
212
230
  each_omitted_value_pair(hash_value_type_breakdown) do |pair_node|
231
+ next if keyword_value?(pair_node)
232
+
213
233
  hash_key_source = pair_node.key.source
214
234
  replacement = "#{hash_key_source}: #{hash_key_source}"
215
235
  register_offense(pair_node, DO_NOT_MIX_EXPLICIT_VALUE_MSG, replacement)
@@ -233,6 +253,10 @@ module RuboCop
233
253
  register_offense(pair_node, OMIT_HASH_VALUE_MSG, replacement)
234
254
  end
235
255
  end
256
+
257
+ def keyword_value?(pair_node)
258
+ KEYWORDS.include?(pair_node.key.value)
259
+ end
236
260
  end
237
261
  end
238
262
  # rubocop:enable Metrics/ModuleLength
@@ -32,7 +32,9 @@ module RuboCop
32
32
  branches << node.if_branch
33
33
 
34
34
  else_branch = node.else_branch
35
- if_conditional_branches(else_branch, branches) if else_branch&.if_type?
35
+ if else_branch&.if_type? && else_branch.elsif?
36
+ if_conditional_branches(else_branch, branches)
37
+ end
36
38
  branches
37
39
  end
38
40
  end
@@ -11,8 +11,10 @@ module RuboCop
11
11
  return if all_on_same_line?(children, ignore_last: ignore_last)
12
12
 
13
13
  last_seen_line = -1
14
- children.each do |child|
14
+ children.each_with_index do |child, index|
15
15
  if last_seen_line >= child.first_line
16
+ next if heredoc_opened_before?(children.first(index), child)
17
+
16
18
  add_offense(child) { |corrector| EmptyLineCorrector.insert_before(corrector, child) }
17
19
  else
18
20
  last_seen_line = child.last_line
@@ -27,6 +29,14 @@ module RuboCop
27
29
 
28
30
  nodes.first.first_line == nodes.last.last_line
29
31
  end
32
+
33
+ def heredoc_opened_before?(previous_children, child)
34
+ previous_children.any? do |previous|
35
+ previous.each_node(:any_str).any? do |str|
36
+ str.heredoc? && same_line?(str, child)
37
+ end
38
+ end
39
+ end
30
40
  end
31
41
  end
32
42
  end
@@ -15,6 +15,7 @@ module RuboCop
15
15
  # If the last node is or contains a conflicting HEREDOC, we don't want
16
16
  # to adjust the brace layout because this will result in invalid code.
17
17
  return if last_line_heredoc?(node.children.last)
18
+ return if heredoc_opened_on_closing_brace_line?(node)
18
19
 
19
20
  check(node)
20
21
  end
@@ -136,6 +137,14 @@ module RuboCop
136
137
 
137
138
  node.children.any? { |child| last_line_heredoc?(child, parent) }
138
139
  end
140
+
141
+ def heredoc_opened_on_closing_brace_line?(node)
142
+ closing_brace_line = node.loc.end.line
143
+
144
+ node.each_descendant(:any_str).any? do |str|
145
+ str.heredoc? && str.first_line == closing_brace_line
146
+ end
147
+ end
139
148
  end
140
149
  end
141
150
  end
@@ -40,7 +40,7 @@ module RuboCop
40
40
  OrderedGemCorrector.correct(
41
41
  processed_source,
42
42
  current,
43
- previous_declaration(current),
43
+ previous,
44
44
  treat_comments_as_separators
45
45
  ).call(corrector)
46
46
  end