rubocop 1.91.0 → 1.92.0

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 (236) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +3 -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/base.rb +42 -21
  19. data/lib/rubocop/cop/bundler/gem_comment.rb +0 -1
  20. data/lib/rubocop/cop/bundler/ordered_gems.rb +0 -8
  21. data/lib/rubocop/cop/cop_documentation.rb +81 -0
  22. data/lib/rubocop/cop/cop_explanation.rb +140 -0
  23. data/lib/rubocop/cop/correctors/alignment_corrector.rb +13 -1
  24. data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +22 -7
  25. data/lib/rubocop/cop/correctors/empty_line_corrector.rb +1 -1
  26. data/lib/rubocop/cop/correctors/lambda_literal_to_method_corrector.rb +23 -2
  27. data/lib/rubocop/cop/correctors/parentheses_corrector.rb +45 -8
  28. data/lib/rubocop/cop/documentation.rb +1 -1
  29. data/lib/rubocop/cop/gemspec/dependency_version.rb +0 -6
  30. data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +0 -6
  31. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +2 -5
  32. data/lib/rubocop/cop/internal_affairs/node_type_group.rb +0 -7
  33. data/lib/rubocop/cop/internal_affairs/redundant_context_config_parameter.rb +1 -7
  34. data/lib/rubocop/cop/internal_affairs/style_detected_api_use.rb +1 -7
  35. data/lib/rubocop/cop/layout/block_alignment.rb +27 -0
  36. data/lib/rubocop/cop/layout/case_indentation.rb +25 -9
  37. data/lib/rubocop/cop/layout/class_structure.rb +9 -1
  38. data/lib/rubocop/cop/layout/def_end_alignment.rb +4 -0
  39. data/lib/rubocop/cop/layout/empty_line_after_multiline_condition.rb +14 -5
  40. data/lib/rubocop/cop/layout/empty_lines.rb +5 -2
  41. data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +1 -0
  42. data/lib/rubocop/cop/layout/empty_lines_around_block_body.rb +1 -1
  43. data/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb +23 -1
  44. data/lib/rubocop/cop/layout/end_alignment.rb +16 -10
  45. data/lib/rubocop/cop/layout/extra_spacing.rb +4 -0
  46. data/lib/rubocop/cop/layout/first_argument_indentation.rb +1 -0
  47. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +5 -3
  48. data/lib/rubocop/cop/layout/first_method_argument_line_break.rb +6 -0
  49. data/lib/rubocop/cop/layout/first_method_parameter_line_break.rb +4 -0
  50. data/lib/rubocop/cop/layout/hash_alignment.rb +16 -12
  51. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +2 -0
  52. data/lib/rubocop/cop/layout/heredoc_indentation.rb +15 -0
  53. data/lib/rubocop/cop/layout/indentation_style.rb +4 -1
  54. data/lib/rubocop/cop/layout/indentation_width.rb +2 -2
  55. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +8 -1
  56. data/lib/rubocop/cop/layout/line_end_string_concatenation_indentation.rb +25 -9
  57. data/lib/rubocop/cop/layout/line_length.rb +52 -18
  58. data/lib/rubocop/cop/layout/multiline_array_brace_layout.rb +12 -0
  59. data/lib/rubocop/cop/layout/multiline_block_layout.rb +8 -1
  60. data/lib/rubocop/cop/layout/multiline_hash_brace_layout.rb +7 -0
  61. data/lib/rubocop/cop/layout/multiline_method_call_brace_layout.rb +6 -0
  62. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +14 -0
  63. data/lib/rubocop/cop/layout/multiline_method_definition_brace_layout.rb +4 -0
  64. data/lib/rubocop/cop/layout/redundant_line_break.rb +22 -1
  65. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +15 -2
  66. data/lib/rubocop/cop/layout/single_line_block_chain.rb +10 -2
  67. data/lib/rubocop/cop/layout/space_after_not.rb +5 -0
  68. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +6 -11
  69. data/lib/rubocop/cop/layout/space_around_keyword.rb +10 -0
  70. data/lib/rubocop/cop/layout/space_before_block_braces.rb +4 -1
  71. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +15 -2
  72. data/lib/rubocop/cop/layout/space_inside_block_braces.rb +31 -8
  73. data/lib/rubocop/cop/lint/ambiguous_operator.rb +1 -1
  74. data/lib/rubocop/cop/lint/ambiguous_operator_precedence.rb +2 -1
  75. data/lib/rubocop/cop/lint/empty_conditional_body.rb +3 -1
  76. data/lib/rubocop/cop/lint/literal_as_condition.rb +15 -0
  77. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +63 -19
  78. data/lib/rubocop/cop/lint/loop.rb +24 -2
  79. data/lib/rubocop/cop/lint/misplaced_magic_comment.rb +71 -11
  80. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +18 -3
  81. data/lib/rubocop/cop/lint/number_conversion.rb +1 -1
  82. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +12 -1
  83. data/lib/rubocop/cop/lint/redundant_dir_glob_sort.rb +13 -2
  84. data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +18 -1
  85. data/lib/rubocop/cop/lint/useless_assignment.rb +14 -7
  86. data/lib/rubocop/cop/lint/void.rb +8 -1
  87. data/lib/rubocop/cop/metrics/class_length.rb +4 -2
  88. data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +0 -4
  89. data/lib/rubocop/cop/metrics/module_length.rb +4 -2
  90. data/lib/rubocop/cop/mixin/alignment.rb +4 -0
  91. data/lib/rubocop/cop/mixin/check_line_breakable.rb +17 -14
  92. data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +5 -1
  93. data/lib/rubocop/cop/mixin/code_length.rb +10 -2
  94. data/lib/rubocop/cop/mixin/empty_lines_around_body.rb +12 -1
  95. data/lib/rubocop/cop/mixin/endless_method_body_help.rb +69 -0
  96. data/lib/rubocop/cop/mixin/forbidden_identifiers.rb +2 -2
  97. data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +15 -1
  98. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +26 -2
  99. data/lib/rubocop/cop/mixin/min_branches_count.rb +3 -1
  100. data/lib/rubocop/cop/mixin/multiline_element_line_breaks.rb +11 -1
  101. data/lib/rubocop/cop/mixin/multiline_literal_brace_layout.rb +9 -0
  102. data/lib/rubocop/cop/mixin/ordered_gem_node.rb +1 -1
  103. data/lib/rubocop/cop/mixin/pattern_matching_help.rb +35 -0
  104. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +17 -2
  105. data/lib/rubocop/cop/mixin/project_index_help.rb +15 -0
  106. data/lib/rubocop/cop/mixin/reparsed_equivalence.rb +13 -3
  107. data/lib/rubocop/cop/mixin/statement_modifier.rb +22 -2
  108. data/lib/rubocop/cop/mixin.rb +2 -0
  109. data/lib/rubocop/cop/naming/accessor_method_name.rb +0 -16
  110. data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
  111. data/lib/rubocop/cop/naming/heredoc_delimiter_case.rb +9 -2
  112. data/lib/rubocop/cop/naming/heredoc_delimiter_naming.rb +5 -3
  113. data/lib/rubocop/cop/naming/predicate_prefix.rb +7 -22
  114. data/lib/rubocop/cop/offense.rb +59 -3
  115. data/lib/rubocop/cop/registry.rb +1 -1
  116. data/lib/rubocop/cop/security/json_load.rb +16 -4
  117. data/lib/rubocop/cop/style/access_modifier_declarations.rb +7 -0
  118. data/lib/rubocop/cop/style/alias.rb +18 -5
  119. data/lib/rubocop/cop/style/ambiguous_endless_method_definition.rb +4 -3
  120. data/lib/rubocop/cop/style/and_or.rb +4 -0
  121. data/lib/rubocop/cop/style/arguments_forwarding.rb +4 -1
  122. data/lib/rubocop/cop/style/attr.rb +3 -4
  123. data/lib/rubocop/cop/style/bitwise_predicate.rb +6 -4
  124. data/lib/rubocop/cop/style/block_delimiters.rb +29 -3
  125. data/lib/rubocop/cop/style/case_like_if.rb +6 -3
  126. data/lib/rubocop/cop/style/class_and_module_children.rb +54 -16
  127. data/lib/rubocop/cop/style/class_methods_definitions.rb +8 -0
  128. data/lib/rubocop/cop/style/combinable_loops.rb +85 -11
  129. data/lib/rubocop/cop/style/concat_array_literals.rb +46 -7
  130. data/lib/rubocop/cop/style/conditional_assignment.rb +4 -0
  131. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +65 -9
  132. data/lib/rubocop/cop/style/double_negation.rb +11 -0
  133. data/lib/rubocop/cop/style/empty_literal.rb +1 -1
  134. data/lib/rubocop/cop/style/empty_method.rb +3 -1
  135. data/lib/rubocop/cop/style/endless_method.rb +67 -6
  136. data/lib/rubocop/cop/style/exact_regexp_match.rb +45 -8
  137. data/lib/rubocop/cop/style/explicit_block_argument.rb +50 -10
  138. data/lib/rubocop/cop/style/float_division.rb +22 -2
  139. data/lib/rubocop/cop/style/for.rb +40 -2
  140. data/lib/rubocop/cop/style/format_string.rb +28 -6
  141. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +2 -1
  142. data/lib/rubocop/cop/style/guard_clause.rb +9 -1
  143. data/lib/rubocop/cop/style/hash_conversion.rb +28 -8
  144. data/lib/rubocop/cop/style/hash_lookup_method.rb +32 -9
  145. data/lib/rubocop/cop/style/hash_syntax.rb +16 -3
  146. data/lib/rubocop/cop/style/identical_conditional_branches.rb +22 -3
  147. data/lib/rubocop/cop/style/if_inside_else.rb +21 -1
  148. data/lib/rubocop/cop/style/if_unless_modifier.rb +19 -20
  149. data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +4 -0
  150. data/lib/rubocop/cop/style/if_with_semicolon.rb +47 -4
  151. data/lib/rubocop/cop/style/inverse_methods.rb +24 -2
  152. data/lib/rubocop/cop/style/it_block_parameter.rb +32 -3
  153. data/lib/rubocop/cop/style/keyword_parameters_order.rb +3 -1
  154. data/lib/rubocop/cop/style/lambda.rb +18 -0
  155. data/lib/rubocop/cop/style/lambda_call.rb +7 -2
  156. data/lib/rubocop/cop/style/map_into_array.rb +1 -2
  157. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +45 -10
  158. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +25 -2
  159. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +26 -3
  160. data/lib/rubocop/cop/style/method_def_parentheses.rb +29 -3
  161. data/lib/rubocop/cop/style/missing_else.rb +7 -1
  162. data/lib/rubocop/cop/style/multiline_if_modifier.rb +45 -5
  163. data/lib/rubocop/cop/style/multiline_in_pattern_then.rb +7 -0
  164. data/lib/rubocop/cop/style/multiline_method_signature.rb +2 -5
  165. data/lib/rubocop/cop/style/multiline_ternary_operator.rb +7 -3
  166. data/lib/rubocop/cop/style/multiline_when_then.rb +7 -0
  167. data/lib/rubocop/cop/style/mutable_constant.rb +28 -1
  168. data/lib/rubocop/cop/style/negated_if_else_condition.rb +2 -1
  169. data/lib/rubocop/cop/style/negative_array_index.rb +9 -1
  170. data/lib/rubocop/cop/style/nested_modifier.rb +27 -8
  171. data/lib/rubocop/cop/style/nested_ternary_operator.rb +26 -8
  172. data/lib/rubocop/cop/style/next.rb +24 -10
  173. data/lib/rubocop/cop/style/nil_comparison.rb +16 -4
  174. data/lib/rubocop/cop/style/non_nil_check.rb +1 -1
  175. data/lib/rubocop/cop/style/not.rb +10 -5
  176. data/lib/rubocop/cop/style/numeric_predicate.rb +21 -5
  177. data/lib/rubocop/cop/style/one_line_conditional.rb +13 -2
  178. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +14 -3
  179. data/lib/rubocop/cop/style/reduce_to_hash.rb +17 -1
  180. data/lib/rubocop/cop/style/redundant_begin.rb +20 -3
  181. data/lib/rubocop/cop/style/redundant_condition.rb +30 -13
  182. data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +6 -0
  183. data/lib/rubocop/cop/style/redundant_exception.rb +15 -3
  184. data/lib/rubocop/cop/style/redundant_fetch_block.rb +9 -1
  185. data/lib/rubocop/cop/style/redundant_freeze.rb +10 -1
  186. data/lib/rubocop/cop/style/redundant_parentheses.rb +12 -1
  187. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +7 -0
  188. data/lib/rubocop/cop/style/redundant_regexp_constructor.rb +20 -0
  189. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +4 -0
  190. data/lib/rubocop/cop/style/regexp_literal.rb +75 -26
  191. data/lib/rubocop/cop/style/require_order.rb +9 -0
  192. data/lib/rubocop/cop/style/rescue_modifier.rb +5 -9
  193. data/lib/rubocop/cop/style/return_nil.rb +17 -2
  194. data/lib/rubocop/cop/style/return_nil_in_predicate_method_definition.rb +11 -1
  195. data/lib/rubocop/cop/style/sample.rb +9 -8
  196. data/lib/rubocop/cop/style/self_assignment.rb +12 -15
  197. data/lib/rubocop/cop/style/semicolon.rb +16 -5
  198. data/lib/rubocop/cop/style/single_line_methods.rb +16 -4
  199. data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +29 -5
  200. data/lib/rubocop/cop/style/string_concatenation.rb +9 -3
  201. data/lib/rubocop/cop/style/super_arguments.rb +3 -0
  202. data/lib/rubocop/cop/style/symbol_proc.rb +23 -5
  203. data/lib/rubocop/cop/style/ternary_parentheses.rb +23 -1
  204. data/lib/rubocop/cop/style/trailing_underscore_variable.rb +23 -19
  205. data/lib/rubocop/cop/style/while_until_modifier.rb +1 -1
  206. data/lib/rubocop/cop/style/yoda_condition.rb +24 -9
  207. data/lib/rubocop/cop/style/yoda_expression.rb +10 -1
  208. data/lib/rubocop/cop/team.rb +10 -5
  209. data/lib/rubocop/cop/variable_force/assignment.rb +1 -1
  210. data/lib/rubocop/cop/variable_force/variable.rb +14 -1
  211. data/lib/rubocop/formatter/base_formatter.rb +22 -0
  212. data/lib/rubocop/formatter/formatter_set.rb +4 -0
  213. data/lib/rubocop/formatter/json_formatter.rb +71 -1
  214. data/lib/rubocop/formatter/offense_count_formatter.rb +3 -1
  215. data/lib/rubocop/formatter/pacman_formatter.rb +3 -1
  216. data/lib/rubocop/formatter/sarif_formatter.rb +12 -7
  217. data/lib/rubocop/formatter/worst_offenders_formatter.rb +3 -1
  218. data/lib/rubocop/lsp/runtime.rb +47 -11
  219. data/lib/rubocop/lsp/stdin_runner.rb +11 -5
  220. data/lib/rubocop/lsp.rb +5 -2
  221. data/lib/rubocop/mcp/autocorrection_request.rb +52 -0
  222. data/lib/rubocop/mcp/explain_tool.rb +68 -0
  223. data/lib/rubocop/mcp/scope.rb +65 -0
  224. data/lib/rubocop/mcp/server.rb +184 -76
  225. data/lib/rubocop/mcp/source_files.rb +35 -0
  226. data/lib/rubocop/offense_limit.rb +30 -0
  227. data/lib/rubocop/options.rb +45 -10
  228. data/lib/rubocop/plugin/configuration_integrator.rb +12 -5
  229. data/lib/rubocop/remote_config.rb +4 -1
  230. data/lib/rubocop/result_cache.rb +20 -13
  231. data/lib/rubocop/runner.rb +109 -41
  232. data/lib/rubocop/server/cache.rb +2 -2
  233. data/lib/rubocop/util.rb +69 -0
  234. data/lib/rubocop/version.rb +1 -1
  235. data/lib/rubocop.rb +5 -0
  236. metadata +16 -5
@@ -72,6 +72,22 @@ module RuboCop
72
72
 
73
73
  MSG = 'Line is too long. [%<length>d/%<max>d]'
74
74
 
75
+ # A trailing backslash, or a trailing unicode, hex, octal, meta or control escape whose
76
+ # payload may be incomplete. An even run of backslashes is complete on its own.
77
+ # Meta and control prefixes chain (`\M-\C-x`, `\c\M-x`), so a whole chain is one escape,
78
+ # and a braced unicode escape may hold several space-separated codepoints.
79
+ ESCAPE_SEQUENCE_TAIL = /
80
+ (?<!\\)(?:\\\\)*
81
+ (
82
+ \\(?:(?:[MC]-|c)\\)*
83
+ (?:u\{[\da-f\x20]*|u[\da-f]{0,4}|x[\da-f]{0,2}|[0-7]{0,3}|[MC]-?|c)?
84
+ )\z
85
+ /xi.freeze
86
+
87
+ def self.autocorrect_incompatible_with
88
+ [Style::MethodCallWithArgsParentheses]
89
+ end
90
+
75
91
  def on_block(node)
76
92
  check_for_breakable_block(node)
77
93
  end
@@ -214,6 +230,8 @@ module RuboCop
214
230
  return if receiver_contains_heredoc?(block_node)
215
231
 
216
232
  line_index = block_node.loc.line - 1
233
+ return if breakable_string_delimiters[line_index]
234
+
217
235
  range = breakable_block_range(block_node)
218
236
  pos = range.begin_pos + 1
219
237
 
@@ -282,7 +300,8 @@ module RuboCop
282
300
  return if source_range.last_column < max
283
301
  return unless (pos = breakable_string_range(node))
284
302
 
285
- pos.end_pos unless pos.end_pos == source_range.begin_pos
303
+ content_begin_pos = node.loc?(:begin) ? node.loc.begin.end_pos : source_range.begin_pos
304
+ pos.end_pos if pos.end_pos > content_begin_pos
286
305
  end
287
306
 
288
307
  # Locate where to break a string that is too long, ensuring that escape characters
@@ -294,14 +313,13 @@ module RuboCop
294
313
  relevant_substr = largest_possible_string(node)
295
314
 
296
315
  if (space_pos = breakable_space_position(node, relevant_substr))
297
- source_range.resize(space_pos + 1)
298
- elsif (escape_pos = relevant_substr.rindex(/\\(u[\da-f]{0,4}|x[\da-f]{0,2})?\z/))
316
+ # A space can separate codepoints inside a braced unicode escape.
317
+ escape_pos = escape_sequence_start(relevant_substr[0..space_pos])
318
+ source_range.resize(escape_pos || (space_pos + 1))
319
+ elsif (escape_pos = escape_sequence_start(relevant_substr))
299
320
  source_range.resize(escape_pos)
300
321
  else
301
- adjustment = max - source_range.last_column - 3
302
- return if adjustment.abs > source_range.size
303
-
304
- source_range.adjust(end_pos: adjustment)
322
+ breakable_string_range_at_limit(source_range)
305
323
  end
306
324
  end
307
325
 
@@ -312,6 +330,19 @@ module RuboCop
312
330
  space_pos
313
331
  end
314
332
 
333
+ def escape_sequence_start(text)
334
+ text.match(ESCAPE_SEQUENCE_TAIL)&.begin(1)
335
+ end
336
+
337
+ def breakable_string_range_at_limit(source_range)
338
+ adjustment = max - source_range.last_column - 3
339
+ return if adjustment.abs > source_range.size
340
+
341
+ cut = source_range.adjust(end_pos: adjustment)
342
+ escape_pos = escape_sequence_start(cut.source)
343
+ escape_pos ? source_range.resize(escape_pos) : cut
344
+ end
345
+
315
346
  def string_content_length(node)
316
347
  content_end = node.loc?(:end) ? node.loc.end.begin_pos : node.source_range.end_pos
317
348
 
@@ -320,7 +351,16 @@ module RuboCop
320
351
 
321
352
  def breakable_dstr_begin_position(node)
322
353
  source_range = node.source_range
323
- source_range.begin_pos if source_range.column < max && source_range.last_column >= max
354
+ return unless source_range.column < max && source_range.last_column >= max
355
+ return if opens_string?(node)
356
+
357
+ source_range.begin_pos
358
+ end
359
+
360
+ def opens_string?(begin_node)
361
+ quote = begin_node.parent.loc.begin
362
+
363
+ quote && quote.end_pos == begin_node.source_range.begin_pos
324
364
  end
325
365
 
326
366
  def breakable_range_by_line_index
@@ -511,16 +551,10 @@ module RuboCop
511
551
  # `Max` - end delimiter (quote) - continuation characters (space and slash)
512
552
  max_length = max - 3
513
553
  # Offset by the string's starting column so the broken line actually fits
514
- # within `Max`. When on the same line as its parent, use the column difference;
515
- # otherwise the string is indented on its own line, so subtract that indentation.
516
- # (Without this, an indented string under a multi-line parent never shortens
517
- # below `Max` and the autocorrect loops, inserting empty `"" \` fragments.)
518
- max_length -= if same_line?(node, node.parent)
519
- column_offset_between(node.loc, node.parent.loc)
520
- else
521
- node.loc.column
522
- end
523
- node.source[0...(max_length)]
554
+ # within `Max`, whatever precedes the string on its line.
555
+ max_length -= node.loc.column
556
+
557
+ node.source[0, [max_length, 0].max]
524
558
  end
525
559
  end
526
560
  end
@@ -106,6 +106,18 @@ module RuboCop
106
106
  ALWAYS_SAME_LINE_MESSAGE = 'The closing array brace must be on the ' \
107
107
  'same line as the last array element.'
108
108
 
109
+ # Moving the closing bracket after a hash literal or method call element while
110
+ # `Layout/MultilineHashBraceLayout` or `Layout/MultilineMethodCallBraceLayout` moves
111
+ # that element's closing brace in the same pass places the bracket after whatever
112
+ # follows the moved brace, such as a comment.
113
+ # Moving it while `Style/ConcatArrayLiterals` removes the brackets in the same pass
114
+ # leaves the bracket behind.
115
+ def self.autocorrect_incompatible_with
116
+ [
117
+ Layout::MultilineHashBraceLayout, Layout::MultilineMethodCallBraceLayout, Style::ConcatArrayLiterals
118
+ ]
119
+ end
120
+
109
121
  def on_array(node)
110
122
  check_brace_layout(node)
111
123
  end
@@ -56,6 +56,13 @@ module RuboCop
56
56
  ARG_MSG = 'Block argument expression is not on the same line as the block start.'
57
57
  PIPE_SIZE = '|'.length
58
58
 
59
+ # `Style/Lambda` moves the block arguments of `lambda` into `->(...)`, and
60
+ # `Style/ItBlockParameter` and `Style/HashEachMethods` remove some or all of them,
61
+ # in the same pass, while this cop moves them next to the block start.
62
+ def self.autocorrect_incompatible_with
63
+ [Style::HashEachMethods, Style::ItBlockParameter, Style::Lambda]
64
+ end
65
+
59
66
  def on_block(node)
60
67
  return if node.single_line?
61
68
 
@@ -155,7 +162,7 @@ module RuboCop
155
162
  end
156
163
 
157
164
  def include_trailing_comma?(args)
158
- arg_count = args.each_descendant(:arg).to_a.size
165
+ arg_count = args.each_descendant.count(&:argument_type?)
159
166
  arg_count == 1 && args.source.include?(',')
160
167
  end
161
168
  end
@@ -106,6 +106,13 @@ module RuboCop
106
106
  ALWAYS_SAME_LINE_MESSAGE = 'Closing hash brace must be on the same ' \
107
107
  'line as the last hash element.'
108
108
 
109
+ # Moving the closing brace while `Style/HashAsLastArrayItem` or
110
+ # `Style/RedundantDoubleSplatHashBraces` removes the braces in the same pass leaves
111
+ # the brace behind.
112
+ def self.autocorrect_incompatible_with
113
+ [Style::HashAsLastArrayItem, Style::RedundantDoubleSplatHashBraces]
114
+ end
115
+
109
116
  def on_hash(node)
110
117
  check_brace_layout(node)
111
118
  end
@@ -106,6 +106,12 @@ module RuboCop
106
106
  ALWAYS_SAME_LINE_MESSAGE = 'Closing method call brace must be on ' \
107
107
  'the same line as the last argument.'
108
108
 
109
+ # Moving the closing parenthesis and removing it under `omit_parentheses` in the same pass
110
+ # leaves the moved parenthesis behind.
111
+ def self.autocorrect_incompatible_with
112
+ [Style::MethodCallWithArgsParentheses]
113
+ end
114
+
109
115
  def on_send(node)
110
116
  check_brace_layout(node)
111
117
  end
@@ -73,14 +73,28 @@ module RuboCop
73
73
  end
74
74
 
75
75
  def find_pair_ancestor(node)
76
+ child = node
76
77
  node.each_ancestor do |ancestor|
77
78
  return ancestor if ancestor.pair_type?
78
79
  break if grouped_expression?(ancestor) || inside_arg_list_parentheses?(node, ancestor)
80
+ break if inside_body?(ancestor, child)
81
+
82
+ child = ancestor
79
83
  end
80
84
 
81
85
  nil
82
86
  end
83
87
 
88
+ # A block, `begin`, `if` or loop body is a separate set of statements, so
89
+ # a method chain in it is not the value of a hash pair the body is nested in.
90
+ # The send node of a block is still part of the value.
91
+ def inside_body?(ancestor, child)
92
+ return !child.equal?(ancestor.send_node) if ancestor.any_block_type?
93
+ return !ancestor.ternary? if ancestor.if_type?
94
+
95
+ ancestor.type?(:kwbegin, :while, :until, :for)
96
+ end
97
+
84
98
  def unwrap_block_node(node)
85
99
  node&.any_block_type? ? node.send_node : node
86
100
  end
@@ -118,6 +118,10 @@ module RuboCop
118
118
  ALWAYS_SAME_LINE_MESSAGE = 'Closing method definition brace must be ' \
119
119
  'on the same line as the last parameter.'
120
120
 
121
+ def self.autocorrect_incompatible_with
122
+ [Style::MultilineMethodSignature]
123
+ end
124
+
121
125
  def on_def(node)
122
126
  check_brace_layout(node.arguments)
123
127
  end
@@ -54,6 +54,11 @@ module RuboCop
54
54
 
55
55
  MSG = 'Redundant line break detected.'
56
56
 
57
+ def on_new_investigation
58
+ super
59
+ @offense_line_ranges = []
60
+ end
61
+
57
62
  def on_lvasgn(node)
58
63
  super unless end_with_percent_blank_string?(processed_source)
59
64
  end
@@ -77,12 +82,15 @@ module RuboCop
77
82
  end
78
83
 
79
84
  def check_assignment(node, _rhs)
80
- return unless offense?(node)
85
+ return unless offense?(node) && !part_of_ignored_node?(node)
81
86
 
82
87
  register_offense(node)
83
88
  end
84
89
 
85
90
  def register_offense(node)
91
+ line_range = joined_line_range(node)
92
+ return if too_long_as_single_line?(line_range)
93
+
86
94
  # The exact single-line correction is verified to parse equivalently
87
95
  # before the offense is registered, so a join that would change how
88
96
  # the code parses is never reported or offered.
@@ -92,6 +100,19 @@ module RuboCop
92
100
  corrector.replace(node, to_single_line(node.source).strip)
93
101
  end
94
102
  ignore_node(node)
103
+ @offense_line_ranges.reject! { |range| line_range.cover?(range) }
104
+ @offense_line_ranges << line_range
105
+ end
106
+
107
+ # Offenses registered in the same pass that share a line with `node`
108
+ # get joined onto that line too, so the combined line has to fit.
109
+ def joined_line_range(node)
110
+ sharing_ranges = @offense_line_ranges.select do |range|
111
+ range.begin <= node.last_line && node.first_line <= range.end
112
+ end
113
+ lines = [node.first_line, node.last_line, *sharing_ranges.flat_map(&:minmax)]
114
+
115
+ lines.min..lines.max
95
116
  end
96
117
 
97
118
  def apply_reparse_correction(corrector, node)
@@ -22,6 +22,7 @@ module RuboCop
22
22
  # puts 'error'
23
23
  # end
24
24
  class RescueEnsureAlignment < Base
25
+ include Alignment
25
26
  include RangeHelp
26
27
  include EndKeywordAlignment
27
28
  extend AutoCorrector
@@ -74,9 +75,10 @@ module RuboCop
74
75
  # Some inline node is sitting before current node.
75
76
  return nil unless whitespace.source.strip.empty?
76
77
 
77
- new_column = alignment_location.column
78
+ indentation = alignment_indentation(alignment_location)
79
+ return nil if indentation.nil?
78
80
 
79
- corrector.replace(whitespace, ' ' * new_column)
81
+ corrector.replace(whitespace, indentation)
80
82
  end
81
83
 
82
84
  def format_message(alignment_node, alignment_loc, kw_loc)
@@ -91,6 +93,17 @@ module RuboCop
91
93
  )
92
94
  end
93
95
 
96
+ # A run of spaces sized to the target column would be turned back into tabs by
97
+ # `Layout/IndentationStyle`, generally landing on a different column, so under
98
+ # tabs the target line's own indentation is reused. There is nothing to reuse
99
+ # when the target does not start its line.
100
+ def alignment_indentation(location)
101
+ return ' ' * location.column unless using_tabs?
102
+ return unless begins_its_line?(location)
103
+
104
+ location.source_line[/\A\s*/]
105
+ end
106
+
94
107
  # rubocop:disable-next Metrics/AbcSize, Metrics/MethodLength
95
108
  def alignment_source(node, starting_loc)
96
109
  ending_loc =
@@ -25,13 +25,17 @@ module RuboCop
25
25
 
26
26
  MSG = 'Put method call on a separate line if chained to a single line block.'
27
27
 
28
+ # `Style/FloatDivision` and `Style/NilComparison` rewrite a method chained to the block
29
+ # in the same pass, which must not start a line of its own.
28
30
  def self.autocorrect_incompatible_with
29
- [Style::MapToHash]
31
+ [Style::FloatDivision, Style::MapToHash, Style::NilComparison]
30
32
  end
31
33
 
32
34
  def on_send(node)
33
35
  range = offending_range(node)
34
- add_offense(range) { |corrector| corrector.insert_before(range, "\n") } if range
36
+ return if !range || heredoc_opened_before?(node.receiver, range)
37
+
38
+ add_offense(range) { |corrector| corrector.insert_before(range, "\n") }
35
39
  end
36
40
  alias on_csend on_send
37
41
 
@@ -63,6 +67,10 @@ module RuboCop
63
67
  # l.(1) has no selector, so we use the opening parenthesis instead
64
68
  node.loc.selector || node.loc.begin
65
69
  end
70
+
71
+ def heredoc_opened_before?(receiver, range)
72
+ receiver.each_node(:any_str).any? { |str| str.heredoc? && str.first_line == range.line }
73
+ end
66
74
  end
67
75
  end
68
76
  end
@@ -20,6 +20,7 @@ module RuboCop
20
20
 
21
21
  def on_send(node)
22
22
  return unless node.prefix_bang? && whitespace_after_operator?(node)
23
+ return if unary_tilde?(node.receiver)
23
24
 
24
25
  add_offense(node) do |corrector|
25
26
  corrector.remove(
@@ -33,6 +34,10 @@ module RuboCop
33
34
  def whitespace_after_operator?(node)
34
35
  node.receiver.source_range.begin_pos - node.source_range.begin_pos > 1
35
36
  end
37
+
38
+ def unary_tilde?(receiver)
39
+ receiver.send_type? && receiver.unary_operation? && receiver.method?(:~)
40
+ end
36
41
  end
37
42
  end
38
43
  end
@@ -6,7 +6,8 @@ module RuboCop
6
6
  # Checks the spacing inside and after block parameters pipes. Line breaks
7
7
  # inside parameter pipes are checked by `Layout/MultilineBlockLayout` and
8
8
  # not by this cop. Spaces inside a lambda's parameter parentheses are left
9
- # to `Layout/SpaceInsideParens` when that cop enforces a conflicting style.
9
+ # to `Layout/SpaceInsideParens` when that cop is enabled, so the examples
10
+ # below apply to a lambda only when it is not.
10
11
  #
11
12
  # @example EnforcedStyleInsidePipes: no_space (default)
12
13
  # # bad
@@ -35,7 +36,7 @@ module RuboCop
35
36
 
36
37
  return unless node.arguments? && pipes?(arguments)
37
38
 
38
- check_inside_pipes(arguments) unless conflicting_space_inside_parens_style?(arguments)
39
+ check_inside_pipes(arguments) unless checked_by_space_inside_parens?(arguments)
39
40
  check_after_closing_pipe(arguments) if node.body
40
41
  check_each_arg(arguments)
41
42
  end
@@ -54,14 +55,8 @@ module RuboCop
54
55
  'EnforcedStyleInsidePipes'
55
56
  end
56
57
 
57
- def conflicting_space_inside_parens_style?(arguments)
58
- return false unless arguments.loc.begin.source == '('
59
-
60
- case config.for_enabled_cop('Layout/SpaceInsideParens')['EnforcedStyle']
61
- when 'no_space' then style == :space
62
- when 'space', 'compact' then style == :no_space
63
- else false
64
- end
58
+ def checked_by_space_inside_parens?(arguments)
59
+ arguments.loc.begin.source == '(' && config.cop_enabled?('Layout/SpaceInsideParens')
65
60
  end
66
61
 
67
62
  def check_inside_pipes(arguments)
@@ -153,7 +148,7 @@ module RuboCop
153
148
  if node
154
149
  corrector.insert_before(node, ' ')
155
150
  else
156
- corrector.insert_after(target, ' ')
151
+ corrector.insert_after(range_between(space_begin_pos, space_begin_pos), ' ')
157
152
  end
158
153
  end
159
154
  end
@@ -209,6 +209,7 @@ module RuboCop
209
209
  end
210
210
 
211
211
  return unless space_after_missing?(range)
212
+ return if block_brace_with_no_space_style?(node, range)
212
213
 
213
214
  add_offense(range, message: format(MSG_AFTER, range: range.source)) do |corrector|
214
215
  corrector.insert_after(range, ' ')
@@ -233,6 +234,15 @@ module RuboCop
233
234
  !/[\s;,#\\)}\].]/.match?(char)
234
235
  end
235
236
 
237
+ def block_brace_with_no_space_style?(node, range)
238
+ return false unless (parent = node.parent)
239
+ return false unless parent.any_block_type? && parent.loc.begin&.begin_pos == range.end_pos
240
+
241
+ space_before_block_braces_config = config.for_cop('Layout/SpaceBeforeBlockBraces')
242
+ space_before_block_braces_config.fetch('Enabled', true) &&
243
+ space_before_block_braces_config['EnforcedStyle'] == 'no_space'
244
+ end
245
+
236
246
  def accepted_opening_delimiter?(range, char)
237
247
  return true unless char
238
248
 
@@ -49,8 +49,11 @@ module RuboCop
49
49
  MISSING_MSG = 'Space missing to the left of {.'
50
50
  DETECTED_MSG = 'Space detected to the left of {.'
51
51
 
52
+ # `Style/BlockDelimiters` decides whether to put a space before the `do`
53
+ # it writes by looking at the source before this cop removed the space,
54
+ # so the two corrections cannot be applied in the same pass.
52
55
  def self.autocorrect_incompatible_with
53
- [Style::SymbolProc]
56
+ [Style::BlockDelimiters, Style::SymbolProc]
54
57
  end
55
58
 
56
59
  def on_block(node)
@@ -117,7 +117,7 @@ module RuboCop
117
117
  elsif style == :space
118
118
  SpaceCorrector.add_space(processed_source, corrector, left, right)
119
119
  else
120
- compact_corrections(corrector, tokens, left, right)
120
+ compact_corrections(corrector, node, tokens, left, right)
121
121
  end
122
122
  end
123
123
 
@@ -178,6 +178,8 @@ module RuboCop
178
178
  space_offenses(node, left, nil, MSG, start_ok: start_ok, end_ok: true)
179
179
  end
180
180
 
181
+ return if closing_bracket_kept_apart?(node, tokens, left, right)
182
+
181
183
  if qualifies_for_compact?(tokens, right)
182
184
  compact_offense(node, right)
183
185
  elsif !multi_dimensional_array?(tokens, right)
@@ -212,13 +214,24 @@ module RuboCop
212
214
  end
213
215
  end
214
216
 
215
- def compact_corrections(corrector, tokens, left, right)
217
+ # A closing bracket on its own line is only collapsed when the opening
218
+ # bracket ends up on the first element's line, so that the brackets
219
+ # stay symmetrical (see `Layout/MultilineArrayBraceLayout`).
220
+ def closing_bracket_kept_apart?(node, tokens, left, right)
221
+ end_has_own_line?(right) &&
222
+ !multi_dimensional_array?(tokens, left, side: :left) &&
223
+ !same_line?(left, node.children.first)
224
+ end
225
+
226
+ def compact_corrections(corrector, node, tokens, left, right)
216
227
  if multi_dimensional_array?(tokens, left, side: :left)
217
228
  compact(corrector, left, :right)
218
229
  elsif !left.space_after?
219
230
  corrector.insert_after(left.pos, ' ')
220
231
  end
221
232
 
233
+ return if closing_bracket_kept_apart?(node, tokens, left, right)
234
+
222
235
  if multi_dimensional_array?(tokens, right)
223
236
  compact(corrector, right, :left)
224
237
  elsif !right.space_before?
@@ -153,7 +153,7 @@ module RuboCop
153
153
  else
154
154
  column = node.source_range.column
155
155
  return if multiline_block?(left_brace, right_brace) &&
156
- aligned_braces?(inner, right_brace, column)
156
+ aligned_braces?(inner, right_brace, column, left_brace)
157
157
 
158
158
  space_inside_right_brace(inner, right_brace, column)
159
159
  end
@@ -163,14 +163,21 @@ module RuboCop
163
163
  left_brace.first_line != right_brace.first_line
164
164
  end
165
165
 
166
- def aligned_braces?(inner, right_brace, column)
167
- column == right_brace.column || column == inner_last_space_count(inner)
166
+ def aligned_braces?(inner, right_brace, column, left_brace)
167
+ column == right_brace.column ||
168
+ column == inner_last_space_count(inner) ||
169
+ aligned_with_opening_brace_line?(left_brace, right_brace)
168
170
  end
169
171
 
170
172
  def inner_last_space_count(inner)
171
173
  inner.split("\n").last.count(' ')
172
174
  end
173
175
 
176
+ def aligned_with_opening_brace_line?(left_brace, right_brace)
177
+ begins_its_line?(right_brace) &&
178
+ right_brace.column == left_brace.source_line[/\A\s*/].length
179
+ end
180
+
174
181
  def no_space_inside_left_brace(left_brace, args_delimiter)
175
182
  if pipe?(args_delimiter)
176
183
  if left_brace.end_pos == args_delimiter.begin_pos &&
@@ -197,9 +204,7 @@ module RuboCop
197
204
  'Space between { and | detected.')
198
205
  end
199
206
  else
200
- brace_with_space = range_with_surrounding_space(left_brace, side: :right)
201
- space(brace_with_space.begin_pos + 1, brace_with_space.end_pos,
202
- 'Space inside { detected.')
207
+ space_after_left_brace(left_brace)
203
208
  end
204
209
  end
205
210
 
@@ -207,6 +212,20 @@ module RuboCop
207
212
  args_delimiter&.is?('|')
208
213
  end
209
214
 
215
+ def space_after_left_brace(left_brace)
216
+ brace_with_space = range_with_surrounding_space(left_brace, side: :right)
217
+ return if brace_with_space.source.match?(/\R/)
218
+ return if comment_at?(brace_with_space.end_pos)
219
+
220
+ space(
221
+ brace_with_space.begin_pos + 1, brace_with_space.end_pos, 'Space inside { detected.'
222
+ )
223
+ end
224
+
225
+ def comment_at?(pos)
226
+ processed_source.comments.any? { |comment| comment.source_range.begin_pos == pos }
227
+ end
228
+
210
229
  def space_inside_right_brace(inner, right_brace, column)
211
230
  brace_with_space = range_with_surrounding_space(right_brace, side: :left)
212
231
  begin_pos = brace_with_space.begin_pos
@@ -216,7 +235,7 @@ module RuboCop
216
235
  begin_pos = end_pos - (right_brace.column - column)
217
236
  end
218
237
 
219
- if inner.end_with?(']')
238
+ if closing_bracket_on_own_line?(inner)
220
239
  end_pos -= 1
221
240
  begin_pos = end_pos - (inner_last_space_count(inner) - column)
222
241
  end
@@ -224,6 +243,10 @@ module RuboCop
224
243
  space(begin_pos, end_pos, 'Space inside } detected.')
225
244
  end
226
245
 
246
+ def closing_bracket_on_own_line?(inner)
247
+ inner[/[^\n]*\z/].match?(/\A\s*\]\z/)
248
+ end
249
+
227
250
  def no_space(begin_pos, end_pos, msg)
228
251
  if style == :space
229
252
  offense(begin_pos, end_pos, msg)
@@ -241,7 +264,7 @@ module RuboCop
241
264
  end
242
265
 
243
266
  def offense(begin_pos, end_pos, msg, style_param = 'EnforcedStyle')
244
- return if begin_pos > end_pos
267
+ return if begin_pos >= end_pos
245
268
 
246
269
  range = range_between(begin_pos, end_pos)
247
270
  add_offense(range, message: msg) do |corrector|
@@ -37,7 +37,7 @@ module RuboCop
37
37
  'should be %<possible>s.'
38
38
 
39
39
  def self.autocorrect_incompatible_with
40
- [Naming::BlockForwarding]
40
+ [Naming::BlockForwarding, Style::ArgumentsForwarding]
41
41
  end
42
42
 
43
43
  def on_new_investigation
@@ -56,7 +56,8 @@ module RuboCop
56
56
  end
57
57
 
58
58
  def on_send(node)
59
- return if node.parenthesized?
59
+ # `foo.*` is a regular method call, not a binary operator.
60
+ return if node.parenthesized? || node.loc.dot
60
61
 
61
62
  return unless (parent = node.parent)
62
63
  return unless operator?(parent)
@@ -97,12 +97,14 @@ module RuboCop
97
97
  end
98
98
 
99
99
  def can_simplify_conditional?(node)
100
- node.else_branch && node.loc.else.source == 'else'
100
+ !node.elsif? && node.else_branch && node.loc.else.source == 'else'
101
101
  end
102
102
 
103
103
  def remove_empty_branch(corrector, node)
104
104
  range = if empty_if_branch?(node) && else_branch?(node)
105
105
  branch_range(node)
106
+ elsif node.condition.last_line == node.loc.else.line
107
+ node.source_range.with(end_pos: node.loc.else.begin_pos)
106
108
  else
107
109
  deletion_range(branch_range(node))
108
110
  end