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
@@ -68,6 +68,7 @@ module RuboCop
68
68
  # end
69
69
  #
70
70
  class BlockAlignment < Base
71
+ include Alignment
71
72
  include ConfigurableEnforcedStyle
72
73
  include RangeHelp
73
74
  extend AutoCorrector
@@ -154,6 +155,32 @@ module RuboCop
154
155
  else
155
156
  start_for_line_node(node)
156
157
  end
158
+
159
+ if using_tabs? && (indentation = anchor_indentation(ancestor_node, node))
160
+ replace_end_indentation(corrector, node, indentation)
161
+ else
162
+ shift_end_to_start_col(corrector, ancestor_node, node)
163
+ end
164
+ end
165
+
166
+ def anchor_indentation(ancestor_node, node)
167
+ if style == :start_of_block
168
+ do_line_anchor_loc(node, node.loc.begin).source_line[/\A\s*/]
169
+ else
170
+ anchor_loc = (ancestor_node || node).source_range
171
+ anchor_loc.source_line[/\A\s*/] if begins_its_line?(anchor_loc)
172
+ end
173
+ end
174
+
175
+ def replace_end_indentation(corrector, node, indentation)
176
+ loc_end = node.loc.end
177
+ whitespace = range_between(loc_end.begin_pos - loc_end.column, loc_end.begin_pos)
178
+ return if whitespace.source == indentation
179
+
180
+ corrector.replace(whitespace, indentation)
181
+ end
182
+
183
+ def shift_end_to_start_col(corrector, ancestor_node, node)
157
184
  start_col = compute_start_col(ancestor_node, node)
158
185
  loc_end = node.loc.end
159
186
  delta = start_col - loc_end.column
@@ -164,8 +164,12 @@ module RuboCop
164
164
  cop_config['IndentOneStep']
165
165
  end
166
166
 
167
+ # Under tabs one step is a single tab character, which counts as one column
168
+ # regardless of the configured indentation width.
167
169
  def indentation_width
168
- indent_one_step? ? configured_indentation_width : 0
170
+ return 0 unless indent_one_step?
171
+
172
+ using_tabs? ? 1 : configured_indentation_width
169
173
  end
170
174
 
171
175
  def incorrect_style(when_node, branch_type)
@@ -176,8 +180,10 @@ module RuboCop
176
180
  detect_incorrect_style(when_node)
177
181
 
178
182
  whitespace = whitespace_range(when_node)
183
+ next unless whitespace.source.strip.empty?
179
184
 
180
- corrector.replace(whitespace, replacement(when_node)) if whitespace.source.strip.empty?
185
+ indentation = replacement(when_node)
186
+ corrector.replace(whitespace, indentation) if indentation
181
187
  end
182
188
  end
183
189
 
@@ -193,10 +199,7 @@ module RuboCop
193
199
  end
194
200
 
195
201
  def base_column(case_node, base)
196
- case base
197
- when :case then case_node.location.keyword.column
198
- when :end then case_node.location.end.column
199
- end
202
+ base_location(case_node, base)&.column
200
203
  end
201
204
 
202
205
  def whitespace_range(node)
@@ -207,13 +210,26 @@ module RuboCop
207
210
  end
208
211
 
209
212
  def replacement(node)
213
+ base_loc = configured_base_location(node)
214
+
215
+ return ' ' * (base_loc.column + indentation_width) unless using_tabs?
216
+ return unless begins_its_line?(base_loc)
217
+
218
+ base_loc.source_line[/\A\s*/] + ("\t" * indentation_width)
219
+ end
220
+
221
+ def configured_base_location(node)
210
222
  case_node = node.each_ancestor(:case, :case_match).first
211
223
  base_type = cop_config[style_parameter_name] == 'end' ? :end : :case
212
224
 
213
- column = base_column(case_node, base_type)
214
- column += indentation_width
225
+ base_location(case_node, base_type)
226
+ end
215
227
 
216
- ' ' * column
228
+ def base_location(case_node, base)
229
+ case base
230
+ when :case then case_node.location.keyword
231
+ when :end then case_node.location.end
232
+ end
217
233
  end
218
234
  end
219
235
  end
@@ -188,6 +188,14 @@ module RuboCop
188
188
 
189
189
  MSG = '`%<category>s` is supposed to appear before `%<previous>s`.'
190
190
 
191
+ # A moved element is inserted before the line break that precedes its anchor.
192
+ # When the anchor follows a blank line, `Layout/EmptyLines`,
193
+ # `Layout/EmptyLinesAroundAccessModifier` or `Layout/EmptyLinesAroundClassBody`
194
+ # may remove that line break in the same pass.
195
+ def self.autocorrect_incompatible_with
196
+ [Layout::EmptyLines, Layout::EmptyLinesAroundAccessModifier, Layout::EmptyLinesAroundClassBody]
197
+ end
198
+
191
199
  # Validates code style on class declaration.
192
200
  # Add offense when find a node out of expected order.
193
201
  # A node is out of order when its category is expected earlier than
@@ -423,7 +431,7 @@ module RuboCop
423
431
  def end_position_for(node)
424
432
  if node.casgn_type?
425
433
  heredoc = find_heredoc(node)
426
- return heredoc.location.heredoc_end.end_pos + 1 if heredoc
434
+ return heredoc.location.heredoc_end.end_pos if heredoc
427
435
  end
428
436
 
429
437
  end_line = buffer.line_for_position(node.source_range.end_pos)
@@ -49,6 +49,10 @@ module RuboCop
49
49
  return unless node.def_modifier?
50
50
 
51
51
  method_def = node.each_descendant(:any_def).first
52
+ # A single-line `def` has nothing to align, even when a line
53
+ # continuation puts it on a later line than the modifier.
54
+ return if method_def.single_line?
55
+
52
56
  expr = node.source_range
53
57
 
54
58
  line_start = range_between(expr.begin_pos, method_def.loc.keyword.end_pos)
@@ -63,12 +63,14 @@ module RuboCop
63
63
  if node.modifier_form?
64
64
  check_condition(node.condition) if node.right_sibling
65
65
  else
66
- check_condition(node.condition)
66
+ check_condition(node.condition, node.if_branch)
67
67
  end
68
68
  end
69
69
 
70
70
  def on_while(node)
71
- check_condition(node.condition)
71
+ return if node.modifier_form? && !node.right_sibling
72
+
73
+ check_condition(node.condition, node.modifier_form? ? nil : node.body)
72
74
  end
73
75
  alias on_until on_while
74
76
 
@@ -84,7 +86,8 @@ module RuboCop
84
86
  last_condition = when_node.conditions.last
85
87
 
86
88
  next if !multiline_when_condition?(when_node) ||
87
- next_line_empty?(last_condition.last_line)
89
+ next_line_empty?(last_condition.last_line) ||
90
+ body_on_line?(when_node.body, last_condition.last_line)
88
91
 
89
92
  add_offense(when_node, &autocorrect(last_condition))
90
93
  end
@@ -94,7 +97,8 @@ module RuboCop
94
97
  node.resbody_branches.each do |resbody|
95
98
  rescued_exceptions = resbody.exceptions
96
99
  next if !multiline_rescue_exceptions?(rescued_exceptions) ||
97
- next_line_empty?(rescued_exceptions.last.last_line)
100
+ next_line_empty?(rescued_exceptions.last.last_line) ||
101
+ body_on_line?(resbody.body, rescued_exceptions.last.last_line)
98
102
 
99
103
  add_offense(resbody, &autocorrect(rescued_exceptions.last))
100
104
  end
@@ -102,9 +106,10 @@ module RuboCop
102
106
 
103
107
  private
104
108
 
105
- def check_condition(condition)
109
+ def check_condition(condition, body = nil)
106
110
  return unless condition.multiline?
107
111
  return if next_line_empty?(condition.last_line)
112
+ return if body_on_line?(body, condition.last_line)
108
113
 
109
114
  add_offense(condition, &autocorrect(condition))
110
115
  end
@@ -113,6 +118,10 @@ module RuboCop
113
118
  processed_source[line].blank?
114
119
  end
115
120
 
121
+ def body_on_line?(body, line)
122
+ body&.first_line == line
123
+ end
124
+
116
125
  def multiline_when_condition?(when_node)
117
126
  when_node.conditions.first.first_line != when_node.conditions.last.last_line
118
127
  end
@@ -27,8 +27,7 @@ module RuboCop
27
27
 
28
28
  def on_new_investigation
29
29
  return if processed_source.tokens.empty?
30
- # Quick check if we possibly have consecutive blank lines.
31
- return unless processed_source.raw_source.include?("\n\n\n")
30
+ return unless consecutive_blank_lines_possible?
32
31
 
33
32
  lines = Set.new
34
33
  processed_source.tokens.each { |token| lines << token.line }
@@ -42,6 +41,10 @@ module RuboCop
42
41
 
43
42
  private
44
43
 
44
+ def consecutive_blank_lines_possible?
45
+ processed_source.buffer.source.include?("\n\n\n")
46
+ end
47
+
45
48
  def each_extra_empty_line(lines)
46
49
  prev_line = 1
47
50
 
@@ -111,6 +111,7 @@ module RuboCop
111
111
 
112
112
  def require_empty_line?(node)
113
113
  return false unless node.respond_to?(:type)
114
+ return false if node.left_sibling.last_line == node.first_line
114
115
 
115
116
  !allow_alias?(node) && !attribute_or_allowed_method?(node)
116
117
  end
@@ -38,7 +38,7 @@ module RuboCop
38
38
  KIND = 'block'
39
39
 
40
40
  def on_block(node)
41
- first_line = node.send_node.last_line
41
+ first_line = node.loc.begin.line
42
42
 
43
43
  check(node, node.body, adjusted_first_line: first_line)
44
44
  end
@@ -85,8 +85,12 @@ module RuboCop
85
85
  next if line == line_of_def_or_kwbegin || last_body_and_end_on_same_line?(body)
86
86
 
87
87
  keyword = loc.source
88
+
88
89
  # below the keyword
89
- check_line(style, line, message('after', keyword), &:empty?)
90
+ unless left_to_block_body_cop?(body, line)
91
+ check_line(style, line, message('after', keyword), &:empty?)
92
+ end
93
+
90
94
  # above the keyword
91
95
  check_line(style, line - 2, message('before', keyword), &:empty?)
92
96
  end
@@ -122,6 +126,15 @@ module RuboCop
122
126
  end
123
127
  end
124
128
 
129
+ def left_to_block_body_cop?(body, keyword_line)
130
+ return false unless body.parent.any_block_type?
131
+
132
+ cop_config = config.for_enabled_cop('Layout/EmptyLinesAroundBlockBody')
133
+ return false if cop_config['EnforcedStyle'] != 'empty_lines'
134
+
135
+ only_empty_lines_until_end?(body, keyword_line)
136
+ end
137
+
125
138
  def keyword_locations_in_rescue(node)
126
139
  [node.loc.else, *node.resbody_branches.map { |body| body.loc.keyword }].compact
127
140
  end
@@ -133,6 +146,15 @@ module RuboCop
133
146
  *keyword_locations(rescue_body_without_ensure)
134
147
  ]
135
148
  end
149
+
150
+ def only_empty_lines_until_end?(body, keyword_line)
151
+ end_line = body.parent.loc.end.line
152
+ next_line = (keyword_line + 1).upto(end_line).find do |line_number|
153
+ !processed_source.lines[line_number - 1].strip.empty?
154
+ end
155
+
156
+ next_line == end_line
157
+ end
136
158
  end
137
159
  end
138
160
  end
@@ -123,16 +123,8 @@ module RuboCop
123
123
  AlignmentCorrector.align_end(corrector, processed_source, node, alignment_node(node))
124
124
  end
125
125
 
126
- # rubocop:disable-next Metrics/CyclomaticComplexity
127
126
  def check_assignment(node, rhs)
128
- # If there are method calls chained to the right hand side of the
129
- # assignment, we let rhs be the receiver of those method calls before
130
- # we check if it's an if/unless/while/until.
131
- return unless (rhs = first_part_of_call_chain(rhs))
132
-
133
- # If `rhs` is a `begin` node or a logical operator,
134
- # unwrap to find the leading conditional.
135
- rhs = rhs.child_nodes.first while rhs&.type?(:begin, :or, :and)
127
+ rhs = leading_node(rhs)
136
128
 
137
129
  return unless rhs&.conditional?
138
130
  return if rhs.if_type? && rhs.ternary?
@@ -151,6 +143,16 @@ module RuboCop
151
143
  ignore_node(inner_node)
152
144
  end
153
145
 
146
+ def leading_node(rhs)
147
+ # If there are method calls chained to the right hand side of the assignment,
148
+ # we let rhs be the receiver of those method calls.
149
+ rhs = first_part_of_call_chain(rhs)
150
+
151
+ # If `rhs` is a `begin` node or a logical operator, unwrap to find the leading node.
152
+ rhs = rhs.child_nodes.first while rhs&.type?(:begin, :or, :and)
153
+ rhs
154
+ end
155
+
154
156
  def asgn_variable_align_with(outer_node, inner_node)
155
157
  expr = outer_node.source_range
156
158
 
@@ -207,7 +209,11 @@ module RuboCop
207
209
 
208
210
  def assignment_or_operator_method(node)
209
211
  node.ancestors.find do |ancestor|
210
- ancestor.assignment_or_similar? || (ancestor.send_type? && ancestor.operator_method?)
212
+ if ancestor.assignment_or_similar?
213
+ leading_node(extract_rhs(ancestor)).equal?(node)
214
+ elsif ancestor.send_type? && ancestor.operator_method?
215
+ !leading_node(ancestor).equal?(node)
216
+ end
211
217
  end
212
218
  end
213
219
  end
@@ -36,6 +36,10 @@ module RuboCop
36
36
  MSG_UNNECESSARY = 'Unnecessary spacing detected.'
37
37
  MSG_UNALIGNED_ASGN = '`=` is not aligned with the %<location>s assignment.'
38
38
 
39
+ def self.autocorrect_incompatible_with
40
+ [Style::SelfAssignment]
41
+ end
42
+
39
43
  def on_new_investigation
40
44
  return if processed_source.blank?
41
45
 
@@ -189,6 +189,7 @@ module RuboCop
189
189
  return false unless style == :special_for_inner_method_call_in_parentheses
190
190
  return false unless inner_call?(top_level_send)
191
191
  return false unless display_column(send_node.source_range) < column_delta.abs
192
+ return false if column_delta.positive?
192
193
 
193
194
  top_level_send != send_node || begins_its_line?(top_level_send.loc.end)
194
195
  end
@@ -187,9 +187,11 @@ module RuboCop
187
187
  end
188
188
 
189
189
  def check_based_on_longest_key(hash_node, left_brace, left_parenthesis)
190
- key_lengths = hash_node.keys.map { |key| key.source_range.length }
191
- check_first(hash_node.pairs.first, left_brace, left_parenthesis,
192
- key_lengths.max - key_lengths.first)
190
+ first_key = hash_node.keys.first
191
+ key_lengths = hash_node.keys.select(&:single_line?).map { |key| key.source_range.length }
192
+ offset = first_key.single_line? ? key_lengths.max - first_key.source_range.length : 0
193
+
194
+ check_first(hash_node.pairs.first, left_brace, left_parenthesis, offset)
193
195
  end
194
196
 
195
197
  # Returns the description of what the correct indentation is based on.
@@ -75,6 +75,12 @@ module RuboCop
75
75
 
76
76
  MSG = 'Add a line break before the first argument of a multi-line method argument list.'
77
77
 
78
+ # A line break inserted after `(` while `Style/MethodCallWithArgsParentheses` removes
79
+ # the parentheses in the same pass leaves the method name alone on its line.
80
+ def self.autocorrect_incompatible_with
81
+ [Style::MethodCallWithArgsParentheses]
82
+ end
83
+
78
84
  def on_send(node)
79
85
  return if allowed_method?(node.method_name)
80
86
 
@@ -59,6 +59,10 @@ module RuboCop
59
59
 
60
60
  MSG = 'Add a line break before the first parameter of a multi-line method parameter list.'
61
61
 
62
+ def self.autocorrect_incompatible_with
63
+ [Style::MethodDefParentheses]
64
+ end
65
+
62
66
  def on_def(node)
63
67
  check_method_line_break(node, node.arguments, ignore_last: ignore_last_element?)
64
68
  end
@@ -215,7 +215,7 @@ module RuboCop
215
215
  check_pairs(node)
216
216
  end
217
217
 
218
- attr_accessor :offenses_by, :column_deltas
218
+ attr_accessor :offenses_by, :column_deltas, :unalignable_styles
219
219
 
220
220
  private
221
221
 
@@ -240,6 +240,7 @@ module RuboCop
240
240
  def reset!
241
241
  self.offenses_by = {}
242
242
  self.column_deltas = Hash.new { |hash, key| hash[key] = {} }
243
+ self.unalignable_styles = []
243
244
  end
244
245
 
245
246
  def check_pairs(node)
@@ -265,7 +266,9 @@ module RuboCop
265
266
  kwsplat_offenses = offenses_by.delete(KeywordSplatAlignment)
266
267
  register_offenses_with_format(kwsplat_offenses, KeywordSplatAlignment)
267
268
 
268
- format, offenses = offenses_by.min_by { |_, v| v.length }
269
+ candidates = offenses_by.reject { |style, _| unalignable_styles.include?(style) }
270
+ candidates = offenses_by if candidates.empty?
271
+ format, offenses = candidates.min_by { |_, v| v.length }
269
272
  register_offenses_with_format(offenses, format)
270
273
  end
271
274
 
@@ -283,10 +286,19 @@ module RuboCop
283
286
  offenses_by[alignment.class] ||= []
284
287
  return if good_alignment? delta
285
288
 
289
+ if key_out_of_line?(delta, node)
290
+ unalignable_styles << alignment.class
291
+ return
292
+ end
293
+
286
294
  column_deltas[alignment.class][node] = delta
287
295
  offenses_by[alignment.class].push(node)
288
296
  end
289
297
 
298
+ def key_out_of_line?(delta, node)
299
+ (delta[:key] || 0) < -node.source_range.column
300
+ end
301
+
290
302
  def ignore_hash_argument?(node)
291
303
  case cop_config['EnforcedLastArgumentHashStyle']
292
304
  when 'always_inspect' then false
@@ -325,18 +337,10 @@ module RuboCop
325
337
  node.loc.operator)
326
338
  else
327
339
  delta_value = delta[:key] || 0
328
- correct_no_value(corrector, delta_value, node.source_range)
340
+ adjust(corrector, delta_value, node.source_range)
329
341
  end
330
342
  end
331
343
 
332
- def correct_no_value(corrector, key_delta, key)
333
- adjust(corrector, clamped_key_delta(key_delta, key), key)
334
- end
335
-
336
- def clamped_key_delta(key_delta, key)
337
- [key_delta, -key.column].max
338
- end
339
-
340
344
  def correct_key_value(corrector, delta, key, value, separator)
341
345
  # We can't use the instance variable inside the lambda. That would
342
346
  # just give each lambda the same reference and they would all get the
@@ -345,7 +349,7 @@ module RuboCop
345
349
  value_delta = delta[:value] || 0
346
350
  key_delta = delta[:key] || 0
347
351
 
348
- adjust(corrector, clamped_key_delta(key_delta, key), key)
352
+ adjust(corrector, key_delta, key)
349
353
  adjust(corrector, separator_delta, separator)
350
354
  adjust(corrector, value_delta, value)
351
355
  end
@@ -117,6 +117,8 @@ module RuboCop
117
117
  previous = heredoc
118
118
  current = previous.parent
119
119
  until send_missing_closing_parens?(current, previous, heredoc)
120
+ return if current&.any_block_type? && !current.send_node.equal?(previous)
121
+
120
122
  previous = current
121
123
  current = current.parent
122
124
  return unless previous && current
@@ -55,6 +55,10 @@ module RuboCop
55
55
  (send _ {:squish :squish!})
56
56
  PATTERN
57
57
 
58
+ def on_new_investigation
59
+ @offending_heredoc_bodies = nil
60
+ end
61
+
58
62
  def on_heredoc(node)
59
63
  body = heredoc_body(node)
60
64
  return if body.strip.empty?
@@ -77,6 +81,9 @@ module RuboCop
77
81
  private
78
82
 
79
83
  def register_offense(node, heredoc_indent_type)
84
+ return if within_offending_heredoc?(node)
85
+
86
+ offending_heredoc_bodies << node.loc.heredoc_body
80
87
  message = message(heredoc_indent_type)
81
88
 
82
89
  add_offense(node.loc.heredoc_body, message: message) do |corrector|
@@ -90,6 +97,14 @@ module RuboCop
90
97
  end
91
98
  end
92
99
 
100
+ def within_offending_heredoc?(node)
101
+ offending_heredoc_bodies.any? { |body| body.contains?(node.source_range) }
102
+ end
103
+
104
+ def offending_heredoc_bodies
105
+ @offending_heredoc_bodies ||= []
106
+ end
107
+
93
108
  def message(heredoc_indent_type)
94
109
  current_indent_type = "<<#{heredoc_indent_type}"
95
110
 
@@ -6,6 +6,9 @@ module RuboCop
6
6
  # Checks that the indentation method is consistent.
7
7
  # Either tabs only or spaces only are used for indentation.
8
8
  #
9
+ # With `EnforcedStyle: tabs`, spaces that follow the indenting tabs are
10
+ # taken to be alignment rather than indentation, and are left alone.
11
+ #
9
12
  # @example EnforcedStyle: spaces (default)
10
13
  # # bad
11
14
  # # This example uses a tab to indent bar.
@@ -67,7 +70,7 @@ module RuboCop
67
70
  match = if style == :spaces
68
71
  line.match(/\A\s*\t+/)
69
72
  else
70
- line.match(/\A\s* +/)
73
+ line.match(/\A +/)
71
74
  end
72
75
  return unless match
73
76
 
@@ -82,7 +82,7 @@ module RuboCop
82
82
 
83
83
  # @!method access_modifier?(node)
84
84
  def_node_matcher :access_modifier?, <<~PATTERN
85
- [(send ...) access_modifier?]
85
+ [(send ...) bare_access_modifier?]
86
86
  PATTERN
87
87
 
88
88
  def on_rescue(node)
@@ -250,7 +250,7 @@ module RuboCop
250
250
 
251
251
  def check_members_for_normal_style(base, members)
252
252
  members.first.children.each do |member|
253
- next if member.send_type? && member.access_modifier?
253
+ next if member.send_type? && member.bare_access_modifier?
254
254
 
255
255
  check_indentation(base, member)
256
256
  end
@@ -111,6 +111,12 @@ module RuboCop
111
111
  comments.map(&:source_range)
112
112
  end
113
113
 
114
+ def output_record_separator_ranges(ast)
115
+ return [] if ast.nil?
116
+
117
+ ast.each_node(:gvar).select { |gvar| gvar.source == '$\\' }.map(&:source_range)
118
+ end
119
+
114
120
  def last_line(processed_source)
115
121
  last_token = processed_source.tokens.last
116
122
 
@@ -123,7 +129,8 @@ module RuboCop
123
129
 
124
130
  def ignored_ranges
125
131
  @ignored_ranges ||= ignored_literal_ranges(processed_source.ast) +
126
- comment_ranges(processed_source.comments)
132
+ comment_ranges(processed_source.comments) +
133
+ output_record_separator_ranges(processed_source.ast)
127
134
  end
128
135
 
129
136
  def ignored_parent?(node)
@@ -87,10 +87,10 @@ module RuboCop
87
87
  return if children.empty?
88
88
 
89
89
  if style == :aligned && !always_indented?(node)
90
- check_aligned(children, 1)
90
+ check_aligned(children, 1, children[0].loc.column)
91
91
  else
92
92
  check_indented(children)
93
- check_aligned(children, 2)
93
+ check_aligned(children, 2, indented_column(children))
94
94
  end
95
95
  end
96
96
 
@@ -110,21 +110,37 @@ module RuboCop
110
110
  PARENT_TYPES_FOR_INDENTED.include?(dstr_node.parent&.type)
111
111
  end
112
112
 
113
- def check_aligned(children, start_index)
113
+ def check_aligned(children, start_index, target_column)
114
114
  base_column = children[start_index - 1].loc.column
115
- children[start_index..].each do |child|
116
- @column_delta = base_column - child.loc.column
117
- add_offense_and_correction(child, MSG_ALIGN) if @column_delta != 0
118
- base_column = child.loc.column
115
+ same_column_runs(children[start_index..]).each do |run|
116
+ column = run.first.loc.column
117
+ if column != base_column && column != target_column
118
+ register_misaligned_run(run, target_column - column)
119
+ end
120
+ base_column = column
121
+ end
122
+ end
123
+
124
+ def same_column_runs(parts)
125
+ parts.chunk_while { |a, b| a.loc.column == b.loc.column }
126
+ end
127
+
128
+ def register_misaligned_run(run, column_delta)
129
+ @column_delta = column_delta
130
+ add_offense(run.first, message: MSG_ALIGN) do |corrector|
131
+ run.each { |child| autocorrect(corrector, child) }
119
132
  end
120
133
  end
121
134
 
122
135
  def check_indented(children)
123
- @column_delta = base_column(children[0]) + configured_indentation_width -
124
- children[1].loc.column
136
+ @column_delta = indented_column(children) - children[1].loc.column
125
137
  add_offense_and_correction(children[1], MSG_INDENT) if @column_delta != 0
126
138
  end
127
139
 
140
+ def indented_column(children)
141
+ base_column(children[0]) + configured_indentation_width
142
+ end
143
+
128
144
  def base_column(child)
129
145
  grandparent = child.parent.parent
130
146
  if grandparent&.pair_type?