rubocop 1.91.0 → 1.92.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +4 -1
  4. data/lib/rubocop/cached_data.rb +47 -7
  5. data/lib/rubocop/changed_files.rb +35 -13
  6. data/lib/rubocop/cli/command/cop_names.rb +57 -0
  7. data/lib/rubocop/cli/command/execute_runner.rb +2 -2
  8. data/lib/rubocop/cli/command/explain.rb +34 -0
  9. data/lib/rubocop/cli/command/show_cops.rb +6 -0
  10. data/lib/rubocop/cli/command/show_docs_url.rb +6 -4
  11. data/lib/rubocop/cli.rb +3 -2
  12. data/lib/rubocop/comment_config.rb +17 -8
  13. data/lib/rubocop/config_loader.rb +6 -3
  14. data/lib/rubocop/config_loader_resolver.rb +37 -12
  15. data/lib/rubocop/config_obsoletion/removed_cop.rb +1 -1
  16. data/lib/rubocop/config_validator.rb +11 -0
  17. data/lib/rubocop/cop/autocorrect_logic.rb +10 -5
  18. data/lib/rubocop/cop/badge.rb +1 -1
  19. data/lib/rubocop/cop/base.rb +42 -21
  20. data/lib/rubocop/cop/bundler/gem_comment.rb +0 -1
  21. data/lib/rubocop/cop/bundler/ordered_gems.rb +0 -8
  22. data/lib/rubocop/cop/cop_documentation.rb +81 -0
  23. data/lib/rubocop/cop/cop_explanation.rb +140 -0
  24. data/lib/rubocop/cop/correctors/alignment_corrector.rb +13 -1
  25. data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +37 -7
  26. data/lib/rubocop/cop/correctors/empty_line_corrector.rb +1 -1
  27. data/lib/rubocop/cop/correctors/lambda_literal_to_method_corrector.rb +25 -4
  28. data/lib/rubocop/cop/correctors/multiline_literal_brace_corrector.rb +34 -0
  29. data/lib/rubocop/cop/correctors/parentheses_corrector.rb +45 -8
  30. data/lib/rubocop/cop/documentation.rb +1 -1
  31. data/lib/rubocop/cop/gemspec/dependency_version.rb +0 -6
  32. data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +0 -6
  33. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +2 -5
  34. data/lib/rubocop/cop/internal_affairs/node_type_group.rb +0 -7
  35. data/lib/rubocop/cop/internal_affairs/redundant_context_config_parameter.rb +1 -7
  36. data/lib/rubocop/cop/internal_affairs/style_detected_api_use.rb +1 -7
  37. data/lib/rubocop/cop/layout/argument_alignment.rb +5 -1
  38. data/lib/rubocop/cop/layout/block_alignment.rb +27 -0
  39. data/lib/rubocop/cop/layout/case_indentation.rb +25 -9
  40. data/lib/rubocop/cop/layout/class_structure.rb +29 -7
  41. data/lib/rubocop/cop/layout/def_end_alignment.rb +4 -0
  42. data/lib/rubocop/cop/layout/dot_position.rb +25 -15
  43. data/lib/rubocop/cop/layout/empty_line_after_multiline_condition.rb +21 -8
  44. data/lib/rubocop/cop/layout/empty_lines.rb +5 -2
  45. data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +1 -0
  46. data/lib/rubocop/cop/layout/empty_lines_around_block_body.rb +9 -3
  47. data/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb +23 -1
  48. data/lib/rubocop/cop/layout/end_alignment.rb +19 -17
  49. data/lib/rubocop/cop/layout/extra_spacing.rb +4 -0
  50. data/lib/rubocop/cop/layout/first_argument_indentation.rb +27 -1
  51. data/lib/rubocop/cop/layout/first_array_element_line_break.rb +15 -0
  52. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +16 -8
  53. data/lib/rubocop/cop/layout/first_method_argument_line_break.rb +6 -0
  54. data/lib/rubocop/cop/layout/first_method_parameter_line_break.rb +4 -0
  55. data/lib/rubocop/cop/layout/hash_alignment.rb +16 -12
  56. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +7 -3
  57. data/lib/rubocop/cop/layout/heredoc_indentation.rb +15 -0
  58. data/lib/rubocop/cop/layout/indentation_consistency.rb +22 -5
  59. data/lib/rubocop/cop/layout/indentation_style.rb +4 -1
  60. data/lib/rubocop/cop/layout/indentation_width.rb +2 -2
  61. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +8 -1
  62. data/lib/rubocop/cop/layout/line_end_string_concatenation_indentation.rb +25 -9
  63. data/lib/rubocop/cop/layout/line_length.rb +153 -21
  64. data/lib/rubocop/cop/layout/multiline_array_brace_layout.rb +12 -0
  65. data/lib/rubocop/cop/layout/multiline_block_layout.rb +8 -1
  66. data/lib/rubocop/cop/layout/multiline_hash_brace_layout.rb +9 -0
  67. data/lib/rubocop/cop/layout/multiline_method_call_brace_layout.rb +6 -0
  68. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +30 -5
  69. data/lib/rubocop/cop/layout/multiline_method_definition_brace_layout.rb +4 -0
  70. data/lib/rubocop/cop/layout/redundant_line_break.rb +22 -1
  71. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +15 -2
  72. data/lib/rubocop/cop/layout/single_line_block_chain.rb +10 -2
  73. data/lib/rubocop/cop/layout/space_after_not.rb +5 -0
  74. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +6 -11
  75. data/lib/rubocop/cop/layout/space_around_keyword.rb +10 -0
  76. data/lib/rubocop/cop/layout/space_before_block_braces.rb +4 -1
  77. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +15 -2
  78. data/lib/rubocop/cop/layout/space_inside_block_braces.rb +31 -8
  79. data/lib/rubocop/cop/layout/space_inside_reference_brackets.rb +2 -12
  80. data/lib/rubocop/cop/lint/ambiguous_operator.rb +1 -1
  81. data/lib/rubocop/cop/lint/ambiguous_operator_precedence.rb +2 -1
  82. data/lib/rubocop/cop/lint/ambiguous_regexp_literal.rb +6 -1
  83. data/lib/rubocop/cop/lint/empty_conditional_body.rb +3 -1
  84. data/lib/rubocop/cop/lint/heredoc_method_call_position.rb +4 -0
  85. data/lib/rubocop/cop/lint/literal_as_condition.rb +15 -0
  86. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +63 -19
  87. data/lib/rubocop/cop/lint/loop.rb +24 -2
  88. data/lib/rubocop/cop/lint/misplaced_magic_comment.rb +71 -11
  89. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +18 -3
  90. data/lib/rubocop/cop/lint/number_conversion.rb +3 -1
  91. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +12 -1
  92. data/lib/rubocop/cop/lint/redundant_dir_glob_sort.rb +13 -2
  93. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +47 -1
  94. data/lib/rubocop/cop/lint/symbol_conversion.rb +1 -0
  95. data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +18 -1
  96. data/lib/rubocop/cop/lint/useless_assignment.rb +14 -7
  97. data/lib/rubocop/cop/lint/useless_setter_call.rb +7 -4
  98. data/lib/rubocop/cop/lint/void.rb +8 -1
  99. data/lib/rubocop/cop/metrics/class_length.rb +4 -2
  100. data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +0 -4
  101. data/lib/rubocop/cop/metrics/module_length.rb +4 -2
  102. data/lib/rubocop/cop/mixin/alignment.rb +4 -0
  103. data/lib/rubocop/cop/mixin/check_line_breakable.rb +23 -19
  104. data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +8 -2
  105. data/lib/rubocop/cop/mixin/code_length.rb +10 -2
  106. data/lib/rubocop/cop/mixin/empty_lines_around_body.rb +12 -1
  107. data/lib/rubocop/cop/mixin/endless_method_body_help.rb +105 -0
  108. data/lib/rubocop/cop/mixin/forbidden_identifiers.rb +2 -2
  109. data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +24 -5
  110. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +26 -2
  111. data/lib/rubocop/cop/mixin/min_branches_count.rb +3 -1
  112. data/lib/rubocop/cop/mixin/multiline_element_line_breaks.rb +11 -1
  113. data/lib/rubocop/cop/mixin/multiline_literal_brace_layout.rb +9 -0
  114. data/lib/rubocop/cop/mixin/ordered_gem_node.rb +1 -1
  115. data/lib/rubocop/cop/mixin/pattern_matching_help.rb +35 -0
  116. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +17 -2
  117. data/lib/rubocop/cop/mixin/project_index_help.rb +15 -0
  118. data/lib/rubocop/cop/mixin/reparsed_equivalence.rb +13 -3
  119. data/lib/rubocop/cop/mixin/statement_modifier.rb +25 -4
  120. data/lib/rubocop/cop/mixin/visibility_help.rb +2 -1
  121. data/lib/rubocop/cop/mixin.rb +2 -0
  122. data/lib/rubocop/cop/naming/accessor_method_name.rb +0 -16
  123. data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
  124. data/lib/rubocop/cop/naming/heredoc_delimiter_case.rb +9 -2
  125. data/lib/rubocop/cop/naming/heredoc_delimiter_naming.rb +5 -3
  126. data/lib/rubocop/cop/naming/predicate_prefix.rb +7 -22
  127. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +1 -1
  128. data/lib/rubocop/cop/offense.rb +59 -3
  129. data/lib/rubocop/cop/registry.rb +1 -1
  130. data/lib/rubocop/cop/security/json_load.rb +16 -4
  131. data/lib/rubocop/cop/style/access_modifier_declarations.rb +37 -2
  132. data/lib/rubocop/cop/style/alias.rb +18 -5
  133. data/lib/rubocop/cop/style/ambiguous_endless_method_definition.rb +4 -3
  134. data/lib/rubocop/cop/style/and_or.rb +6 -2
  135. data/lib/rubocop/cop/style/arguments_forwarding.rb +32 -3
  136. data/lib/rubocop/cop/style/attr.rb +3 -4
  137. data/lib/rubocop/cop/style/bitwise_predicate.rb +6 -4
  138. data/lib/rubocop/cop/style/block_delimiters.rb +35 -3
  139. data/lib/rubocop/cop/style/case_like_if.rb +6 -3
  140. data/lib/rubocop/cop/style/class_and_module_children.rb +56 -16
  141. data/lib/rubocop/cop/style/class_methods_definitions.rb +15 -0
  142. data/lib/rubocop/cop/style/collection_querying.rb +79 -46
  143. data/lib/rubocop/cop/style/combinable_loops.rb +89 -11
  144. data/lib/rubocop/cop/style/concat_array_literals.rb +58 -7
  145. data/lib/rubocop/cop/style/conditional_assignment.rb +4 -0
  146. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +65 -9
  147. data/lib/rubocop/cop/style/double_negation.rb +11 -0
  148. data/lib/rubocop/cop/style/empty_literal.rb +1 -1
  149. data/lib/rubocop/cop/style/empty_method.rb +3 -1
  150. data/lib/rubocop/cop/style/empty_string_inside_interpolation.rb +53 -11
  151. data/lib/rubocop/cop/style/endless_method.rb +71 -6
  152. data/lib/rubocop/cop/style/exact_regexp_match.rb +45 -8
  153. data/lib/rubocop/cop/style/explicit_block_argument.rb +50 -10
  154. data/lib/rubocop/cop/style/float_division.rb +22 -2
  155. data/lib/rubocop/cop/style/for.rb +53 -2
  156. data/lib/rubocop/cop/style/format_string.rb +28 -6
  157. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +2 -1
  158. data/lib/rubocop/cop/style/guard_clause.rb +31 -22
  159. data/lib/rubocop/cop/style/hash_as_last_array_item.rb +4 -0
  160. data/lib/rubocop/cop/style/hash_conversion.rb +28 -8
  161. data/lib/rubocop/cop/style/hash_lookup_method.rb +32 -9
  162. data/lib/rubocop/cop/style/hash_syntax.rb +16 -3
  163. data/lib/rubocop/cop/style/identical_conditional_branches.rb +22 -3
  164. data/lib/rubocop/cop/style/if_inside_else.rb +21 -1
  165. data/lib/rubocop/cop/style/if_unless_modifier.rb +32 -26
  166. data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +4 -0
  167. data/lib/rubocop/cop/style/if_with_semicolon.rb +47 -4
  168. data/lib/rubocop/cop/style/inverse_methods.rb +24 -2
  169. data/lib/rubocop/cop/style/it_block_parameter.rb +94 -4
  170. data/lib/rubocop/cop/style/keyword_parameters_order.rb +3 -1
  171. data/lib/rubocop/cop/style/lambda.rb +34 -1
  172. data/lib/rubocop/cop/style/lambda_call.rb +7 -2
  173. data/lib/rubocop/cop/style/map_into_array.rb +1 -2
  174. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +80 -13
  175. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +25 -2
  176. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +26 -3
  177. data/lib/rubocop/cop/style/method_def_parentheses.rb +29 -3
  178. data/lib/rubocop/cop/style/missing_else.rb +7 -1
  179. data/lib/rubocop/cop/style/mixin_grouping.rb +12 -3
  180. data/lib/rubocop/cop/style/multiline_if_modifier.rb +45 -5
  181. data/lib/rubocop/cop/style/multiline_in_pattern_then.rb +7 -0
  182. data/lib/rubocop/cop/style/multiline_method_signature.rb +2 -5
  183. data/lib/rubocop/cop/style/multiline_ternary_operator.rb +7 -3
  184. data/lib/rubocop/cop/style/multiline_when_then.rb +7 -0
  185. data/lib/rubocop/cop/style/mutable_constant.rb +52 -7
  186. data/lib/rubocop/cop/style/negated_if_else_condition.rb +2 -1
  187. data/lib/rubocop/cop/style/negative_array_index.rb +9 -1
  188. data/lib/rubocop/cop/style/nested_modifier.rb +27 -8
  189. data/lib/rubocop/cop/style/nested_ternary_operator.rb +26 -8
  190. data/lib/rubocop/cop/style/next.rb +24 -10
  191. data/lib/rubocop/cop/style/nil_comparison.rb +16 -4
  192. data/lib/rubocop/cop/style/non_nil_check.rb +1 -1
  193. data/lib/rubocop/cop/style/not.rb +10 -5
  194. data/lib/rubocop/cop/style/numeric_predicate.rb +21 -5
  195. data/lib/rubocop/cop/style/one_line_conditional.rb +13 -2
  196. data/lib/rubocop/cop/style/parentheses_around_condition.rb +1 -1
  197. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +14 -3
  198. data/lib/rubocop/cop/style/raise_args.rb +19 -9
  199. data/lib/rubocop/cop/style/reduce_to_hash.rb +17 -1
  200. data/lib/rubocop/cop/style/redundant_begin.rb +22 -4
  201. data/lib/rubocop/cop/style/redundant_condition.rb +30 -13
  202. data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +6 -0
  203. data/lib/rubocop/cop/style/redundant_exception.rb +15 -3
  204. data/lib/rubocop/cop/style/redundant_fetch_block.rb +9 -1
  205. data/lib/rubocop/cop/style/redundant_freeze.rb +11 -3
  206. data/lib/rubocop/cop/style/redundant_interpolation_unfreeze.rb +24 -1
  207. data/lib/rubocop/cop/style/redundant_parentheses.rb +12 -1
  208. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +7 -0
  209. data/lib/rubocop/cop/style/redundant_regexp_constructor.rb +20 -0
  210. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +4 -0
  211. data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +28 -0
  212. data/lib/rubocop/cop/style/redundant_sort.rb +38 -4
  213. data/lib/rubocop/cop/style/regexp_literal.rb +75 -26
  214. data/lib/rubocop/cop/style/require_order.rb +25 -8
  215. data/lib/rubocop/cop/style/rescue_modifier.rb +11 -9
  216. data/lib/rubocop/cop/style/return_nil.rb +17 -2
  217. data/lib/rubocop/cop/style/return_nil_in_predicate_method_definition.rb +11 -1
  218. data/lib/rubocop/cop/style/sample.rb +9 -8
  219. data/lib/rubocop/cop/style/self_assignment.rb +12 -15
  220. data/lib/rubocop/cop/style/semicolon.rb +16 -5
  221. data/lib/rubocop/cop/style/send_with_literal_method_name.rb +4 -0
  222. data/lib/rubocop/cop/style/single_line_methods.rb +21 -4
  223. data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +29 -5
  224. data/lib/rubocop/cop/style/string_concatenation.rb +9 -3
  225. data/lib/rubocop/cop/style/struct_inheritance.rb +6 -0
  226. data/lib/rubocop/cop/style/super_arguments.rb +3 -0
  227. data/lib/rubocop/cop/style/symbol_proc.rb +24 -6
  228. data/lib/rubocop/cop/style/ternary_parentheses.rb +23 -1
  229. data/lib/rubocop/cop/style/trailing_underscore_variable.rb +23 -19
  230. data/lib/rubocop/cop/style/while_until_modifier.rb +1 -1
  231. data/lib/rubocop/cop/style/yoda_condition.rb +24 -9
  232. data/lib/rubocop/cop/style/yoda_expression.rb +10 -1
  233. data/lib/rubocop/cop/team.rb +10 -5
  234. data/lib/rubocop/cop/variable_force/assignment.rb +1 -1
  235. data/lib/rubocop/cop/variable_force/variable.rb +14 -1
  236. data/lib/rubocop/formatter/base_formatter.rb +22 -0
  237. data/lib/rubocop/formatter/formatter_set.rb +4 -0
  238. data/lib/rubocop/formatter/json_formatter.rb +71 -1
  239. data/lib/rubocop/formatter/offense_count_formatter.rb +3 -1
  240. data/lib/rubocop/formatter/pacman_formatter.rb +3 -1
  241. data/lib/rubocop/formatter/sarif_formatter.rb +12 -7
  242. data/lib/rubocop/formatter/worst_offenders_formatter.rb +3 -1
  243. data/lib/rubocop/lsp/routes.rb +23 -23
  244. data/lib/rubocop/lsp/runtime.rb +47 -11
  245. data/lib/rubocop/lsp/stdin_runner.rb +11 -5
  246. data/lib/rubocop/lsp.rb +5 -2
  247. data/lib/rubocop/mcp/autocorrection_request.rb +52 -0
  248. data/lib/rubocop/mcp/explain_tool.rb +68 -0
  249. data/lib/rubocop/mcp/scope.rb +65 -0
  250. data/lib/rubocop/mcp/server.rb +184 -76
  251. data/lib/rubocop/mcp/source_files.rb +35 -0
  252. data/lib/rubocop/offense_limit.rb +30 -0
  253. data/lib/rubocop/options.rb +45 -10
  254. data/lib/rubocop/plugin/configuration_integrator.rb +12 -5
  255. data/lib/rubocop/remote_config.rb +4 -1
  256. data/lib/rubocop/result_cache.rb +20 -13
  257. data/lib/rubocop/runner.rb +109 -41
  258. data/lib/rubocop/server/cache.rb +6 -6
  259. data/lib/rubocop/target_ruby.rb +21 -9
  260. data/lib/rubocop/util.rb +69 -0
  261. data/lib/rubocop/version.rb +1 -1
  262. data/lib/rubocop.rb +5 -0
  263. metadata +16 -5
@@ -66,16 +66,18 @@ module RuboCop
66
66
 
67
67
  # @!method bit_operation?(node)
68
68
  def_node_matcher :bit_operation?, <<~PATTERN
69
- (begin
70
- (send _ :& _))
69
+ {
70
+ (begin (send _ :& _))
71
+ (send _ :& _)
72
+ }
71
73
  PATTERN
72
74
 
73
75
  # rubocop:disable-next Metrics/AbcSize
74
76
  def on_send(node)
75
- return unless node.receiver&.begin_type?
77
+ return unless bit_operation?(node.receiver)
76
78
  return unless (preferred_method = preferred_method(node))
77
79
 
78
- bit_operation = node.receiver.children.first
80
+ bit_operation = node.receiver.begin_type? ? node.receiver.children.first : node.receiver
79
81
  lhs, _operator, rhs = *bit_operation
80
82
 
81
83
  preferred = if preferred_method == 'allbits?' && lhs.source == node.first_argument.source
@@ -177,7 +177,7 @@ module RuboCop
177
177
  BRACES_REQUIRED_MESSAGE = "Brace delimiters `{...}` required for '%<method_name>s' method."
178
178
 
179
179
  def self.autocorrect_incompatible_with
180
- [Style::RedundantBegin]
180
+ [Layout::SpaceBeforeBlockBraces, Style::MethodCallWithArgsParentheses, Style::RedundantBegin]
181
181
  end
182
182
 
183
183
  def on_send(node)
@@ -196,6 +196,8 @@ module RuboCop
196
196
  end
197
197
  end
198
198
  alias on_csend on_send
199
+ alias on_super on_send
200
+ alias on_yield on_send
199
201
 
200
202
  def on_block(node)
201
203
  return if part_of_ignored_node?(node)
@@ -292,7 +294,7 @@ module RuboCop
292
294
  corrector.replace(b, '{')
293
295
  corrector.replace(e, '}')
294
296
 
295
- corrector.wrap(node.body, "begin\n", "\nend") if begin_required?(node)
297
+ wrap_body_with_begin(corrector, node.body) if begin_required?(node)
296
298
  end
297
299
 
298
300
  def whitespace_before?(range)
@@ -304,6 +306,8 @@ module RuboCop
304
306
  end
305
307
 
306
308
  def move_comment_before_block(corrector, comment, block_node, closing_brace)
309
+ return if directive_comment?(comment)
310
+
307
311
  range = block_node.chained? ? end_of_chain(block_node.parent).source_range : closing_brace
308
312
 
309
313
  # It is possible that there is code between the block and the comment
@@ -317,6 +321,10 @@ module RuboCop
317
321
  corrector.insert_before(block_node, "#{comment.text}\n")
318
322
  end
319
323
 
324
+ def directive_comment?(comment)
325
+ comment.text.match?(DirectiveComment::DIRECTIVE_COMMENT_REGEXP)
326
+ end
327
+
320
328
  def source_range_before_comment(range, comment)
321
329
  range = range.end.join(comment.source_range.begin)
322
330
 
@@ -372,6 +380,7 @@ module RuboCop
372
380
  # rubocop:disable-next Metrics/CyclomaticComplexity -- inlined special_method checks to avoid double evaluation
373
381
  def proper_block_style?(node)
374
382
  return true if require_do_end?(node)
383
+ return true if collection_of_for_loop?(node)
375
384
 
376
385
  method_name = node.method_name
377
386
  return true if allowed_method?(method_name) || matches_allowed_pattern?(method_name)
@@ -398,6 +407,10 @@ module RuboCop
398
407
  !modifier_rescue?(body)
399
408
  end
400
409
 
410
+ def collection_of_for_loop?(node)
411
+ node.parent&.for_type? && node.parent.collection == node
412
+ end
413
+
401
414
  def modifier_rescue?(rescue_node)
402
415
  return false if rescue_node.body.nil? || rescue_node.else_branch
403
416
  return false unless rescue_node.resbody_branches.one?
@@ -438,11 +451,20 @@ module RuboCop
438
451
  end
439
452
 
440
453
  def correction_would_break_code?(node)
441
- return false unless node.keywords?
454
+ return loop_condition?(node) if node.braces?
442
455
 
443
456
  node.send_node.arguments? && !node.send_node.parenthesized?
444
457
  end
445
458
 
459
+ # A `do` in the condition of `while` or `until` is taken as the start of
460
+ # the loop body. Inside the body of another block it is unambiguous again.
461
+ def loop_condition?(node)
462
+ ancestors = node.each_ancestor.take_while { |ancestor| !ancestor.any_block_type? }
463
+ [node, *ancestors].each_cons(2).any? do |child, parent|
464
+ parent.type?(:while, :until) && parent.condition.equal?(child)
465
+ end
466
+ end
467
+
446
468
  def functional_method?(method_name)
447
469
  functional_methods.include?(method_name)
448
470
  end
@@ -490,6 +512,16 @@ module RuboCop
490
512
  node.type?(:array, :range)
491
513
  end
492
514
 
515
+ def wrap_body_with_begin(corrector, body)
516
+ heredoc_ends = body.each_node(:any_str).select(&:heredoc?).map do |str|
517
+ str.loc.heredoc_end
518
+ end
519
+ body_end = [body.source_range, *heredoc_ends].max_by(&:end_pos)
520
+
521
+ corrector.insert_before(body, "begin\n")
522
+ corrector.insert_after(body_end, "\nend")
523
+ end
524
+
493
525
  def begin_required?(block_node)
494
526
  # If the block contains `rescue` or `ensure`, it needs to be wrapped in
495
527
  # `begin`...`end` when changing `do-end` to `{}`.
@@ -54,6 +54,10 @@ module RuboCop
54
54
 
55
55
  MSG = 'Convert `if-elsif` to `case-when`.'
56
56
 
57
+ def self.autocorrect_incompatible_with
58
+ [Style::IfInsideElse]
59
+ end
60
+
57
61
  def on_if(node)
58
62
  return unless should_check?(node)
59
63
 
@@ -224,10 +228,9 @@ module RuboCop
224
228
  end
225
229
 
226
230
  def branch_conditions(node)
227
- conditions = []
228
- while node&.if_type? && !node.ternary?
231
+ conditions = [node.condition]
232
+ while (node = node.else_branch)&.if_type? && node.elsif?
229
233
  conditions << node.condition
230
- node = node.else_branch
231
234
  end
232
235
  conditions
233
236
  end
@@ -65,6 +65,12 @@ module RuboCop
65
65
  NESTED_MSG = 'Use nested module/class definitions instead of compact style.'
66
66
  COMPACT_MSG = 'Use compact module/class definition instead of nested style.'
67
67
 
68
+ # `Style/StructInheritance` turns a class definition into a constant assignment in
69
+ # the same pass, which the compacted `class` keyword would then precede.
70
+ def self.autocorrect_incompatible_with
71
+ [Style::StructInheritance]
72
+ end
73
+
68
74
  def on_class(node)
69
75
  return if node.parent_class && style_for_classes != :nested
70
76
 
@@ -117,7 +123,10 @@ module RuboCop
117
123
  end
118
124
 
119
125
  def heuristic_namespace_keyword(node)
120
- class_definition = node.left_sibling&.each_node(:class)&.find do |class_node|
126
+ left_sibling = node.left_sibling
127
+ return 'module' unless left_sibling.is_a?(AST::Node)
128
+
129
+ class_definition = left_sibling.each_node(:class).find do |class_node|
121
130
  class_node.identifier == node.identifier.namespace
122
131
  end
123
132
 
@@ -143,7 +152,9 @@ module RuboCop
143
152
  # making autocorrection ping-pong between the two forms.
144
153
  return if style_for_kind(node.body.type) == :nested
145
154
  return unless compactible_namespace?(node)
155
+ return if node.each_ancestor(:class, :module).any? { |ancestor| ignored_node?(ancestor) }
146
156
 
157
+ ignore_node(node)
147
158
  compact_node(corrector, node)
148
159
  remove_end(corrector, node.body)
149
160
  unindent(corrector, node)
@@ -194,33 +205,62 @@ module RuboCop
194
205
  "#{node.body.children.first.const_name}"
195
206
  end
196
207
 
197
- # rubocop:disable-next Metrics/AbcSize
198
208
  def remove_end(corrector, body)
199
- remove_begin_pos = if same_line?(body.loc.name, body.loc.end)
200
- body.loc.name.end_pos
201
- else
202
- body.loc.end.begin_pos - leading_spaces(body).size
203
- end
204
- adjustment = processed_source.raw_source[remove_begin_pos] == ';' ? 0 : 1
205
- range = range_between(remove_begin_pos, body.loc.end.end_pos + adjustment)
209
+ range = if same_line?(body.loc.name, body.loc.end)
210
+ single_line_end_range(body)
211
+ else
212
+ multiline_end_range(body)
213
+ end
206
214
 
207
215
  corrector.remove(range)
208
216
  end
209
217
 
210
- def unindent(corrector, node)
211
- return unless node.body.children.last
218
+ def single_line_end_range(body)
219
+ raw = processed_source.raw_source
220
+ pos = body.loc.end.begin_pos
221
+ pos -= 1 while raw[pos - 1] == ' '
222
+ pos -= 1 if raw[pos - 1] == ';'
223
+ range_between(pos, body.loc.end.end_pos)
224
+ end
212
225
 
213
- last_child_leading_spaces = leading_spaces(node.body.children.last)
214
- return if spaces_size(leading_spaces(node)) == spaces_size(last_child_leading_spaces)
226
+ def multiline_end_range(body)
227
+ remove_begin_pos = body.loc.end.begin_pos - leading_spaces(body).size
228
+ adjustment = processed_source.raw_source[remove_begin_pos] == ';' ? 0 : 1
229
+ range_between(remove_begin_pos, body.loc.end.end_pos + adjustment)
230
+ end
215
231
 
216
- column_delta = configured_indentation_width - spaces_size(last_child_leading_spaces)
217
- return if column_delta.zero?
232
+ def unindent(corrector, node)
233
+ column_delta = unindent_column_delta(node)
234
+ return unless column_delta&.negative?
235
+
236
+ range = unindentable_range(node.body)
237
+ return unless range
218
238
 
219
239
  AlignmentCorrector.correct(
220
- corrector, processed_source, node, column_delta, tab_indentation: true
240
+ corrector, processed_source, range, column_delta, tab_indentation: true
221
241
  )
222
242
  end
223
243
 
244
+ def unindent_column_delta(node)
245
+ body = node.body.children.last
246
+ return unless body
247
+
248
+ node_indentation = spaces_size(leading_spaces(node))
249
+ body_indentation = spaces_size(leading_spaces(body))
250
+
251
+ node_indentation + configured_indentation_width - body_indentation
252
+ end
253
+
254
+ def unindentable_range(body)
255
+ first_line = body.loc.name.line + 1
256
+ last_line = body.loc.end.line - 1
257
+ return if first_line > last_line
258
+
259
+ buffer = processed_source.buffer
260
+ range_between(buffer.line_range(first_line).begin_pos,
261
+ buffer.line_range(last_line).end_pos)
262
+ end
263
+
224
264
  def leading_spaces(node)
225
265
  node.source_range.source_line[/\A\s*/]
226
266
  end
@@ -72,9 +72,13 @@ module RuboCop
72
72
  return unless def_self_style?
73
73
  return unless node.identifier.self_type?
74
74
  return unless all_methods_public?(node)
75
+ return unless def_nodes(node).all? { |def_node| no_code_after?(def_node.source_range) }
75
76
 
76
77
  add_offense(node, message: MSG_SCLASS) do |corrector|
78
+ next if inside_corrected_sclass?(node)
79
+
77
80
  autocorrect_sclass(node, corrector)
81
+ ignore_node(node)
78
82
  end
79
83
  end
80
84
 
@@ -112,6 +116,17 @@ module RuboCop
112
116
  end
113
117
  end
114
118
 
119
+ def no_code_after?(range)
120
+ trailing = processed_source.lines[range.last_line - 1][range.last_column..].to_s.strip
121
+ trailing = trailing.sub(/\A;+\s*/, '')
122
+
123
+ trailing.empty? || trailing.start_with?('#')
124
+ end
125
+
126
+ def inside_corrected_sclass?(node)
127
+ node.each_ancestor(:sclass).any? { |ancestor| ignored_node?(ancestor) }
128
+ end
129
+
115
130
  def autocorrect_sclass(node, corrector)
116
131
  rewritten_defs = []
117
132
 
@@ -15,31 +15,24 @@ module RuboCop
15
15
  # would yield false positives. For example, `String` implements `length`
16
16
  # and `size`, but it doesn't include `Enumerable`.
17
17
  #
18
+ # When `count` is used without a block, it measures the collection's
19
+ # size, so the offense is corrected to `empty?` or `!empty?`. The
20
+ # predicate methods `any?`, `none?` and `one?` test element truthiness
21
+ # instead and would return different results for collections with falsey
22
+ # elements (e.g. `[false].count.zero?` is `false`, but `[false].none?`
23
+ # is `true`). For the same reason, `count == 1` and `count > 1` without
24
+ # a block are not flagged.
25
+ #
18
26
  # @safety
19
27
  # The cop is unsafe because receiver might not include `Enumerable`, or
20
28
  # it has nonstandard implementation of `count` or any replacement
21
29
  # methods.
22
30
  #
23
- # It's also unsafe because for collections with falsey values, expressions
24
- # with `count` without a block return a different result than methods `any?`,
25
- # `none?` and `one?`:
26
- #
27
- # [source,ruby]
28
- # ----
29
- # [nil, false].count.positive?
30
- # [nil].count == 1
31
- # # => true
32
- #
33
- # [nil, false].any?
34
- # [nil].one?
35
- # # => false
36
- #
37
- # [nil].count == 0
38
- # # => false
39
- #
40
- # [nil].none?
41
- # # => true
42
- # ----
31
+ # Blockless `count` is corrected to `empty?`, which `Enumerable` doesn't
32
+ # define. `Range`, `Enumerator` (including lazy ones), `Struct`, `StringIO`
33
+ # and any class that just includes `Enumerable` respond to `count` but not
34
+ # to `empty?`, so `(1...1).count.zero?` is `true` while `(1...1).empty?`
35
+ # raises `NoMethodError`.
43
36
  #
44
37
  # Autocorrection is unsafe when replacement methods don't iterate over
45
38
  # every element in collection and the given block runs side effects:
@@ -60,12 +53,14 @@ module RuboCop
60
53
  # x.count > 0
61
54
  # x.count != 0
62
55
  #
56
+ # # good
57
+ # !x.empty?
58
+ #
59
+ # # bad
63
60
  # x.count(&:foo?).positive?
64
61
  # x.count { |item| item.foo? }.positive?
65
62
  #
66
63
  # # good
67
- # x.any?
68
- #
69
64
  # x.any?(&:foo?)
70
65
  # x.any? { |item| item.foo? }
71
66
  #
@@ -74,24 +69,33 @@ module RuboCop
74
69
  # x.count == 0
75
70
  #
76
71
  # # good
77
- # x.none?
72
+ # x.empty?
78
73
  #
79
74
  # # bad
80
- # x.count == 1
81
- # x.one?
75
+ # x.count(&:foo?).zero?
76
+ # x.count(&:foo?) == 0
77
+ #
78
+ # # good
79
+ # x.none?(&:foo?)
80
+ #
81
+ # # bad
82
+ # x.count(&:foo?) == 1
83
+ #
84
+ # # good
85
+ # x.one?(&:foo?)
82
86
  #
83
87
  # @example AllCops:ActiveSupportExtensionsEnabled: false (default)
84
88
  #
85
89
  # # good
86
- # x.count > 1
90
+ # x.count(&:foo?) > 1
87
91
  #
88
92
  # @example AllCops:ActiveSupportExtensionsEnabled: true
89
93
  #
90
94
  # # bad
91
- # x.count > 1
95
+ # x.count(&:foo?) > 1
92
96
  #
93
97
  # # good
94
- # x.many?
98
+ # x.many?(&:foo?)
95
99
  #
96
100
  class CollectionQuerying < Base
97
101
  include RangeHelp
@@ -102,13 +106,21 @@ module RuboCop
102
106
  RESTRICT_ON_SEND = %i[positive? > != zero? ==].freeze
103
107
 
104
108
  REPLACEMENTS = {
105
- [:positive?, nil] => :any?,
106
- [:>, 0] => :any?,
107
- [:!=, 0] => :any?,
108
- [:zero?, nil] => :none?,
109
- [:==, 0] => :none?,
110
- [:==, 1] => :one?,
111
- [:>, 1] => :many?
109
+ [:positive?, nil] => 'any?',
110
+ [:>, 0] => 'any?',
111
+ [:!=, 0] => 'any?',
112
+ [:zero?, nil] => 'none?',
113
+ [:==, 0] => 'none?',
114
+ [:==, 1] => 'one?',
115
+ [:>, 1] => 'many?'
116
+ }.freeze
117
+
118
+ NO_BLOCK_REPLACEMENTS = {
119
+ [:positive?, nil] => '!empty?',
120
+ [:>, 0] => '!empty?',
121
+ [:!=, 0] => '!empty?',
122
+ [:zero?, nil] => 'empty?',
123
+ [:==, 0] => 'empty?'
112
124
  }.freeze
113
125
 
114
126
  # @!method count_predicate(node)
@@ -132,28 +144,49 @@ module RuboCop
132
144
  def on_send(node)
133
145
  return unless (count_node = count_predicate(node))
134
146
 
135
- replacement_method = replacement_method(node)
147
+ replacement = replacement_method(node, count_node)
136
148
 
137
- return unless replacement_supported?(replacement_method)
149
+ return unless replacement_supported?(node, replacement)
138
150
 
139
151
  offense_range = count_node.loc.selector.join(node.source_range.end)
140
- add_offense(offense_range,
141
- message: format(MSG, prefer: replacement_method)) do |corrector|
142
- corrector.replace(count_node.loc.selector, replacement_method)
143
- corrector.remove(removal_range(node))
152
+ add_offense(offense_range, message: format(MSG, prefer: replacement)) do |corrector|
153
+ autocorrect(corrector, node, count_node, replacement)
144
154
  end
145
155
  end
146
156
 
147
157
  private
148
158
 
149
- def replacement_method(node)
150
- REPLACEMENTS.fetch([node.method_name, node.first_argument&.value])
159
+ def replacement_method(node, count_node)
160
+ key = [node.method_name, node.first_argument&.value]
161
+ return REPLACEMENTS.fetch(key) if block_given_to_count?(count_node)
162
+
163
+ NO_BLOCK_REPLACEMENTS[key]
164
+ end
165
+
166
+ def block_given_to_count?(count_node)
167
+ count_node.parent&.any_block_type? || count_node.last_argument&.block_pass_type?
168
+ end
169
+
170
+ def replacement_supported?(node, replacement)
171
+ return false unless replacement
172
+ return false if replacement == 'many?' && !active_support_extensions_enabled?
173
+
174
+ !(replacement.start_with?('!') && receiver_of_call?(node))
151
175
  end
152
176
 
153
- def replacement_supported?(method_name)
154
- return true if active_support_extensions_enabled?
177
+ def receiver_of_call?(node)
178
+ node.parent&.call_type? && node.parent.receiver == node && node.parent.loc.dot
179
+ end
180
+
181
+ def autocorrect(corrector, node, count_node, replacement)
182
+ if replacement.start_with?('!')
183
+ corrector.insert_before(count_node, '!')
184
+ corrector.replace(count_node.loc.selector, 'empty?')
185
+ else
186
+ corrector.replace(count_node.loc.selector, replacement)
187
+ end
155
188
 
156
- method_name != :many?
189
+ corrector.remove(removal_range(node))
157
190
  end
158
191
 
159
192
  def removal_range(node)
@@ -60,19 +60,22 @@ module RuboCop
60
60
  # end
61
61
  #
62
62
  class CombinableLoops < Base
63
+ include RangeHelp
63
64
  extend AutoCorrector
64
65
 
65
66
  MSG = 'Combine this loop with the previous loop.'
66
67
 
67
- # rubocop:disable-next Metrics/CyclomaticComplexity
68
+ def self.autocorrect_incompatible_with
69
+ [Layout::BlockEndNewline, Layout::RedundantLineBreak]
70
+ end
71
+
68
72
  def on_block(node)
69
73
  return unless node.parent&.begin_type?
70
74
  return unless collection_looping_method?(node)
71
- return unless same_collection_looping_block?(node, node.left_sibling)
72
- return unless node.body && node.left_sibling.body
75
+ return unless combinable_looping_blocks?(node, node.left_sibling)
73
76
 
74
77
  add_offense(node) do |corrector|
75
- next unless node.arguments == node.left_sibling.arguments
78
+ next unless combinable_blocks?(node, node.left_sibling)
76
79
 
77
80
  combine_with_left_sibling(corrector, node)
78
81
  end
@@ -87,9 +90,7 @@ module RuboCop
87
90
  return unless node.body && node.left_sibling.body
88
91
 
89
92
  add_offense(node) do |corrector|
90
- # Combining loops with different iteration variables would leave the second
91
- # body referencing an undefined variable, so only autocorrect when they match.
92
- next unless node.variable == node.left_sibling.variable
93
+ next unless combinable_fors?(node, node.left_sibling)
93
94
 
94
95
  combine_with_left_sibling(corrector, node)
95
96
  end
@@ -110,25 +111,102 @@ module RuboCop
110
111
  sibling.send_node.arguments == node.send_node.arguments
111
112
  end
112
113
 
114
+ def combinable_looping_blocks?(node, sibling)
115
+ same_collection_looping_block?(node, sibling) && node.body && sibling.body
116
+ end
117
+
113
118
  def same_collection_looping_for?(node, sibling)
114
119
  sibling&.for_type? && node.collection == sibling.collection
115
120
  end
116
121
 
122
+ def combinable_blocks?(node, sibling)
123
+ return false unless node.arguments == sibling.arguments
124
+ # Numbered and `it` parameters are implicit, so `arguments` is empty for both.
125
+ return false unless node.argument_list == sibling.argument_list
126
+ return false if directive_after_opening?(node)
127
+
128
+ !node.body.type?(:rescue, :ensure) && !sibling.body.type?(:rescue, :ensure)
129
+ end
130
+
131
+ # Combining loops with different iteration variables would leave the second
132
+ # body referencing an undefined variable, so only autocorrect when they match.
133
+ def combinable_fors?(node, sibling)
134
+ node.variable == sibling.variable && !directive_after_opening?(node)
135
+ end
136
+
137
+ # The opening is removed, so a directive after it would end up on a line of
138
+ # its own, where it covers the rest of the file instead of a single line.
139
+ def directive_after_opening?(node)
140
+ opening_line = loop_opening(node).line
141
+ return false if opening_line == node.body.first_line
142
+
143
+ comment = processed_source.comment_at_line(opening_line)
144
+ comment && DirectiveComment.new(comment).start_with_marker?
145
+ end
146
+
147
+ # The block arguments or `{`/`do` of a block, or the `do` or collection of a `for` loop.
148
+ def loop_opening(node)
149
+ if node.for_type?
150
+ node.loc.begin || node.collection.source_range
151
+ else
152
+ (node.block_type? && node.arguments.source_range) || node.loc.begin
153
+ end
154
+ end
155
+
117
156
  def combine_with_left_sibling(corrector, node)
118
- corrector.remove(node.left_sibling.body.source_range.end.join(node.left_sibling.loc.end))
119
- corrector.remove(node.source_range.begin.join(node.body.source_range.begin))
157
+ corrector.remove(closing_with_preceding_space(node.left_sibling, node))
158
+ corrector.remove(opening_with_following_space(node))
120
159
 
121
160
  correct_end_of_block(corrector, node)
122
161
  end
123
162
 
163
+ # Comments and heredoc bodies before the closing delimiter are kept. When
164
+ # the next loop starts on the delimiter's line, the line break before the
165
+ # delimiter is kept too, so that a comment doesn't swallow the next loop.
166
+ def closing_with_preceding_space(loop, next_loop)
167
+ separate_lines = !same_line?(loop.loc.end, next_loop)
168
+
169
+ range_with_surrounding_space(
170
+ loop.loc.end, side: :left, newlines: separate_lines, whitespace: separate_lines
171
+ )
172
+ end
173
+
174
+ # Comments between the opening and the body are kept.
175
+ def opening_with_following_space(node)
176
+ opening = node.source_range.begin.join(loop_opening(node))
177
+
178
+ range_with_surrounding_space(opening, side: :right, whitespace: true)
179
+ end
180
+
124
181
  def correct_end_of_block(corrector, node)
125
182
  return unless node.left_sibling.respond_to?(:braces?)
126
- return if node.right_sibling&.any_block_type?
183
+ return if combined_with_right_sibling?(node)
127
184
 
128
- end_of_block = node.left_sibling.braces? ? '}' : ' end'
185
+ end_of_block = first_combined_block(node).braces? ? '}' : ' end'
129
186
  corrector.remove(node.loc.end)
130
187
  corrector.insert_before(node.source_range.end, end_of_block)
131
188
  end
189
+
190
+ def combined_with_right_sibling?(node)
191
+ sibling = node.right_sibling
192
+ return false unless sibling&.any_block_type?
193
+ # A disabled loop isn't corrected, so it doesn't get merged into this one.
194
+ return false unless enabled_lines?(sibling.source_range)
195
+
196
+ same_collection_looping_block?(sibling, node) && sibling.body &&
197
+ combinable_blocks?(sibling, node)
198
+ end
199
+
200
+ def first_combined_block(node)
201
+ first = node.left_sibling
202
+
203
+ while (previous = first.left_sibling) && enabled_lines?(first.source_range) &&
204
+ combinable_looping_blocks?(first, previous) && combinable_blocks?(first, previous)
205
+ first = previous
206
+ end
207
+
208
+ first
209
+ end
132
210
  end
133
211
  end
134
212
  end