rubocop 1.89.0 → 1.91.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 (248) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +309 -14
  3. data/config/obsoletion.yml +9 -0
  4. data/exe/rubocop +1 -1
  5. data/lib/rubocop/cached_data.rb +7 -2
  6. data/lib/rubocop/changed_files.rb +112 -0
  7. data/lib/rubocop/cli/command/execute_runner.rb +52 -1
  8. data/lib/rubocop/cli.rb +11 -7
  9. data/lib/rubocop/comment_config/directive_range.rb +34 -0
  10. data/lib/rubocop/comment_config/disable_next.rb +143 -0
  11. data/lib/rubocop/comment_config/push_pop.rb +55 -0
  12. data/lib/rubocop/comment_config.rb +50 -64
  13. data/lib/rubocop/config.rb +33 -5
  14. data/lib/rubocop/config_loader.rb +13 -6
  15. data/lib/rubocop/config_loader_resolver.rb +50 -9
  16. data/lib/rubocop/config_obsoletion/cop_rule.rb +2 -1
  17. data/lib/rubocop/config_obsoletion/removed_cop.rb +8 -0
  18. data/lib/rubocop/config_obsoletion.rb +1 -1
  19. data/lib/rubocop/config_validator.rb +19 -5
  20. data/lib/rubocop/cop/autocorrect_logic.rb +8 -0
  21. data/lib/rubocop/cop/badge.rb +1 -1
  22. data/lib/rubocop/cop/base.rb +49 -5
  23. data/lib/rubocop/cop/corrector.rb +3 -3
  24. data/lib/rubocop/cop/correctors.rb +2 -2
  25. data/lib/rubocop/cop/gemspec/require_mfa.rb +1 -1
  26. data/lib/rubocop/cop/generator.rb +1 -1
  27. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +1 -2
  28. data/lib/rubocop/cop/internal_affairs/node_pattern_groups/ast_walker.rb +1 -2
  29. data/lib/rubocop/cop/internal_affairs/on_send_without_on_csend.rb +1 -1
  30. data/lib/rubocop/cop/layout/argument_alignment.rb +2 -0
  31. data/lib/rubocop/cop/layout/block_alignment.rb +1 -2
  32. data/lib/rubocop/cop/layout/class_structure.rb +102 -26
  33. data/lib/rubocop/cop/layout/comment_indentation.rb +8 -5
  34. data/lib/rubocop/cop/layout/else_alignment.rb +1 -0
  35. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +1 -2
  36. data/lib/rubocop/cop/layout/empty_line_after_magic_comment.rb +63 -5
  37. data/lib/rubocop/cop/layout/empty_lines_after_module_inclusion.rb +1 -1
  38. data/lib/rubocop/cop/layout/end_alignment.rb +1 -2
  39. data/lib/rubocop/cop/layout/extra_spacing.rb +105 -3
  40. data/lib/rubocop/cop/layout/first_argument_indentation.rb +21 -1
  41. data/lib/rubocop/cop/layout/first_array_element_indentation.rb +17 -4
  42. data/lib/rubocop/cop/layout/first_parameter_indentation.rb +11 -0
  43. data/lib/rubocop/cop/layout/hash_alignment.rb +6 -5
  44. data/lib/rubocop/cop/layout/indentation_width.rb +2 -2
  45. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +1 -2
  46. data/lib/rubocop/cop/layout/line_length.rb +20 -3
  47. data/lib/rubocop/cop/layout/multiline_assignment_layout.rb +1 -2
  48. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +5 -5
  49. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +1 -2
  50. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +14 -3
  51. data/lib/rubocop/cop/layout/space_around_operators.rb +12 -1
  52. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +2 -4
  53. data/lib/rubocop/cop/lint/ambiguous_range.rb +1 -2
  54. data/lib/rubocop/cop/lint/argument_mismatch.rb +99 -0
  55. data/lib/rubocop/cop/lint/assignment_in_condition.rb +2 -0
  56. data/lib/rubocop/cop/lint/constant_reassignment.rb +17 -11
  57. data/lib/rubocop/cop/lint/cop_directive_syntax.rb +165 -15
  58. data/lib/rubocop/cop/lint/deprecated_reference.rb +11 -2
  59. data/lib/rubocop/cop/lint/duplicate_methods.rb +113 -15
  60. data/lib/rubocop/cop/lint/empty_block.rb +1 -1
  61. data/lib/rubocop/cop/lint/implicit_string_concatenation.rb +1 -2
  62. data/lib/rubocop/cop/lint/ineffective_access_modifier.rb +1 -2
  63. data/lib/rubocop/cop/lint/interpolation_check.rb +1 -2
  64. data/lib/rubocop/cop/lint/literal_as_condition.rb +11 -8
  65. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +3 -6
  66. data/lib/rubocop/cop/lint/misplaced_magic_comment.rb +185 -0
  67. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +25 -12
  68. data/lib/rubocop/cop/lint/name_typo.rb +53 -6
  69. data/lib/rubocop/cop/lint/nested_method_definition.rb +1 -1
  70. data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +1 -1
  71. data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +15 -0
  72. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +17 -1
  73. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +122 -29
  74. data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +47 -7
  75. data/lib/rubocop/cop/lint/redundant_require_statement.rb +1 -2
  76. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +1 -2
  77. data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +1 -2
  78. data/lib/rubocop/cop/lint/redundant_type_conversion.rb +1 -2
  79. data/lib/rubocop/cop/lint/redundant_with_index.rb +1 -2
  80. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +1 -2
  81. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +2 -4
  82. data/lib/rubocop/cop/lint/super_argument_mismatch.rb +179 -0
  83. data/lib/rubocop/cop/lint/suppressed_exception_in_number_conversion.rb +1 -2
  84. data/lib/rubocop/cop/lint/to_enum_arguments.rb +1 -2
  85. data/lib/rubocop/cop/lint/unreachable_code.rb +1 -2
  86. data/lib/rubocop/cop/lint/unused_private_method.rb +94 -17
  87. data/lib/rubocop/cop/lint/useless_access_modifier.rb +1 -2
  88. data/lib/rubocop/cop/lint/useless_assignment.rb +1 -2
  89. data/lib/rubocop/cop/lint/useless_constant_scoping.rb +36 -0
  90. data/lib/rubocop/cop/lint/useless_method_definition.rb +1 -1
  91. data/lib/rubocop/cop/lint/useless_rescue.rb +1 -2
  92. data/lib/rubocop/cop/lint/utils/nil_receiver_checker.rb +61 -7
  93. data/lib/rubocop/cop/lint/void.rb +1 -2
  94. data/lib/rubocop/cop/lint.rb +3 -0
  95. data/lib/rubocop/cop/metrics/perceived_complexity.rb +1 -2
  96. data/lib/rubocop/cop/migration/department_name.rb +1 -2
  97. data/lib/rubocop/cop/mixin/alignment.rb +1 -1
  98. data/lib/rubocop/cop/mixin/annotation_comment.rb +1 -1
  99. data/lib/rubocop/cop/mixin/comments_help.rb +1 -2
  100. data/lib/rubocop/cop/mixin/configurable_enforced_style.rb +1 -2
  101. data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +11 -1
  102. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +7 -4
  103. data/lib/rubocop/cop/mixin/hash_subset.rb +1 -2
  104. data/lib/rubocop/cop/mixin/hash_transform_method.rb +1 -1
  105. data/lib/rubocop/cop/mixin/indexed_method_arity.rb +94 -0
  106. data/lib/rubocop/cop/mixin/line_length_help.rb +7 -1
  107. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +62 -6
  108. data/lib/rubocop/cop/mixin/range_help.rb +1 -2
  109. data/lib/rubocop/cop/mixin/statement_modifier.rb +13 -7
  110. data/lib/rubocop/cop/mixin/trailing_comma.rb +2 -4
  111. data/lib/rubocop/cop/mixin.rb +6 -2
  112. data/lib/rubocop/cop/naming/binary_operator_parameter_name.rb +16 -0
  113. data/lib/rubocop/cop/naming/block_forwarding.rb +4 -1
  114. data/lib/rubocop/cop/naming/block_parameter_name.rb +1 -1
  115. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +2 -4
  116. data/lib/rubocop/cop/naming/method_name.rb +5 -2
  117. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +1 -2
  118. data/lib/rubocop/cop/offense.rb +27 -4
  119. data/lib/rubocop/cop/registry.rb +43 -11
  120. data/lib/rubocop/cop/severity.rb +12 -0
  121. data/lib/rubocop/cop/style/access_modifier_declarations.rb +3 -2
  122. data/lib/rubocop/cop/style/accessor_grouping.rb +1 -2
  123. data/lib/rubocop/cop/style/arguments_forwarding.rb +5 -8
  124. data/lib/rubocop/cop/style/array_first_last.rb +1 -2
  125. data/lib/rubocop/cop/style/bitwise_predicate.rb +1 -2
  126. data/lib/rubocop/cop/style/block_delimiters.rb +3 -6
  127. data/lib/rubocop/cop/style/case_like_if.rb +5 -7
  128. data/lib/rubocop/cop/style/class_and_module_children.rb +1 -2
  129. data/lib/rubocop/cop/style/combinable_loops.rb +1 -2
  130. data/lib/rubocop/cop/style/concat_array_literals.rb +1 -2
  131. data/lib/rubocop/cop/style/conditional_assignment.rb +1 -2
  132. data/lib/rubocop/cop/style/constant_visibility.rb +6 -3
  133. data/lib/rubocop/cop/style/copyright.rb +1 -2
  134. data/lib/rubocop/cop/style/data_inheritance.rb +14 -0
  135. data/lib/rubocop/cop/style/directive_scope.rb +304 -0
  136. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +83 -13
  137. data/lib/rubocop/cop/style/documentation_method.rb +1 -9
  138. data/lib/rubocop/cop/style/each_for_simple_loop.rb +1 -1
  139. data/lib/rubocop/cop/style/empty_block_parameter.rb +1 -1
  140. data/lib/rubocop/cop/style/empty_case_condition.rb +1 -2
  141. data/lib/rubocop/cop/style/empty_lambda_parameter.rb +1 -1
  142. data/lib/rubocop/cop/style/empty_method.rb +12 -2
  143. data/lib/rubocop/cop/style/endless_method.rb +31 -12
  144. data/lib/rubocop/cop/style/file_null.rb +2 -2
  145. data/lib/rubocop/cop/style/float_division.rb +10 -16
  146. data/lib/rubocop/cop/style/for.rb +5 -0
  147. data/lib/rubocop/cop/style/format_string_token.rb +1 -2
  148. data/lib/rubocop/cop/style/guard_clause.rb +5 -2
  149. data/lib/rubocop/cop/style/hash_each_methods.rb +1 -2
  150. data/lib/rubocop/cop/style/hash_lookup_method.rb +3 -0
  151. data/lib/rubocop/cop/style/hash_syntax.rb +1 -2
  152. data/lib/rubocop/cop/style/identical_conditional_branches.rb +2 -4
  153. data/lib/rubocop/cop/style/if_inside_else.rb +1 -2
  154. data/lib/rubocop/cop/style/if_unless_modifier.rb +1 -2
  155. data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +1 -2
  156. data/lib/rubocop/cop/style/magic_comment_format.rb +1 -1
  157. data/lib/rubocop/cop/style/map_to_hash.rb +1 -2
  158. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +2 -4
  159. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +1 -1
  160. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +1 -2
  161. data/lib/rubocop/cop/style/multiline_method_signature.rb +1 -2
  162. data/lib/rubocop/cop/style/negated_if_else_condition.rb +1 -2
  163. data/lib/rubocop/cop/style/negative_array_index.rb +2 -4
  164. data/lib/rubocop/cop/style/nested_modifier.rb +9 -4
  165. data/lib/rubocop/cop/style/nil_comparison.rb +36 -9
  166. data/lib/rubocop/cop/style/nil_lambda.rb +1 -1
  167. data/lib/rubocop/cop/style/non_nil_check.rb +18 -2
  168. data/lib/rubocop/cop/style/not.rb +3 -1
  169. data/lib/rubocop/cop/style/numeric_literal_prefix.rb +11 -1
  170. data/lib/rubocop/cop/style/one_line_conditional.rb +1 -1
  171. data/lib/rubocop/cop/style/operator_method_call.rb +1 -2
  172. data/lib/rubocop/cop/style/parallel_assignment.rb +1 -2
  173. data/lib/rubocop/cop/style/parentheses_around_condition.rb +6 -1
  174. data/lib/rubocop/cop/style/redundant_assignment.rb +1 -2
  175. data/lib/rubocop/cop/style/redundant_condition.rb +6 -8
  176. data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +11 -3
  177. data/lib/rubocop/cop/style/redundant_each.rb +1 -2
  178. data/lib/rubocop/cop/style/redundant_fetch_block.rb +1 -1
  179. data/lib/rubocop/cop/style/redundant_format.rb +2 -4
  180. data/lib/rubocop/cop/style/redundant_parentheses.rb +13 -5
  181. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +1 -2
  182. data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +6 -5
  183. data/lib/rubocop/cop/style/redundant_regexp_constructor.rb +6 -6
  184. data/lib/rubocop/cop/style/redundant_return.rb +1 -2
  185. data/lib/rubocop/cop/style/redundant_self_assignment.rb +1 -2
  186. data/lib/rubocop/cop/style/redundant_string_escape.rb +1 -2
  187. data/lib/rubocop/cop/style/rescue_modifier.rb +1 -2
  188. data/lib/rubocop/cop/style/safe_navigation.rb +1 -2
  189. data/lib/rubocop/cop/style/sample.rb +11 -6
  190. data/lib/rubocop/cop/style/select_by_regexp.rb +1 -2
  191. data/lib/rubocop/cop/style/semicolon.rb +2 -4
  192. data/lib/rubocop/cop/style/single_line_block_params.rb +1 -1
  193. data/lib/rubocop/cop/style/single_line_do_end_block.rb +1 -2
  194. data/lib/rubocop/cop/style/sole_nested_conditional.rb +1 -2
  195. data/lib/rubocop/cop/style/special_global_vars.rb +1 -1
  196. data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +2 -0
  197. data/lib/rubocop/cop/style/string_concatenation.rb +18 -5
  198. data/lib/rubocop/cop/style/struct_inheritance.rb +14 -0
  199. data/lib/rubocop/cop/style/super_arguments.rb +1 -2
  200. data/lib/rubocop/cop/style/symbol_proc.rb +1 -2
  201. data/lib/rubocop/cop/style/time_now.rb +41 -0
  202. data/lib/rubocop/cop/style/trailing_comma_in_block_args.rb +1 -1
  203. data/lib/rubocop/cop/style/yoda_condition.rb +1 -2
  204. data/lib/rubocop/cop/style.rb +2 -1
  205. data/lib/rubocop/cop/team.rb +11 -3
  206. data/lib/rubocop/cop/util.rb +3 -5
  207. data/lib/rubocop/cop/variable_force/variable.rb +1 -2
  208. data/lib/rubocop/cops_documentation_generator.rb +26 -8
  209. data/lib/rubocop/directive_comment.rb +73 -10
  210. data/lib/rubocop/formatter/disabled_config_formatter.rb +19 -1
  211. data/lib/rubocop/formatter/emacs_style_formatter.rb +3 -1
  212. data/lib/rubocop/formatter/formatter_set.rb +2 -1
  213. data/lib/rubocop/formatter/github_actions_formatter.rb +6 -5
  214. data/lib/rubocop/formatter/html_formatter.rb +2 -4
  215. data/lib/rubocop/formatter/json_formatter.rb +11 -6
  216. data/lib/rubocop/formatter/junit_formatter.rb +26 -9
  217. data/lib/rubocop/formatter/offense_count_formatter.rb +1 -2
  218. data/lib/rubocop/formatter/sarif_formatter.rb +181 -0
  219. data/lib/rubocop/formatter/simple_text_formatter.rb +4 -3
  220. data/lib/rubocop/formatter/tap_formatter.rb +3 -1
  221. data/lib/rubocop/formatter/worst_offenders_formatter.rb +1 -2
  222. data/lib/rubocop/formatter.rb +1 -0
  223. data/lib/rubocop/lockfile.rb +18 -0
  224. data/lib/rubocop/lsp/diagnostic.rb +4 -8
  225. data/lib/rubocop/lsp/routes.rb +48 -19
  226. data/lib/rubocop/magic_comment.rb +27 -0
  227. data/lib/rubocop/mcp/server.rb +1 -2
  228. data/lib/rubocop/options.rb +69 -7
  229. data/lib/rubocop/path_util.rb +2 -3
  230. data/lib/rubocop/plugin/configuration_integrator.rb +6 -3
  231. data/lib/rubocop/plugin/loader.rb +1 -1
  232. data/lib/rubocop/rake_task.rb +1 -2
  233. data/lib/rubocop/remote_config.rb +4 -4
  234. data/lib/rubocop/result_cache.rb +16 -5
  235. data/lib/rubocop/rspec/expect_offense.rb +2 -4
  236. data/lib/rubocop/rspec/shared_contexts.rb +4 -2
  237. data/lib/rubocop/runner.rb +90 -44
  238. data/lib/rubocop/server/cache.rb +1 -2
  239. data/lib/rubocop/server/cli.rb +2 -4
  240. data/lib/rubocop/server.rb +14 -0
  241. data/lib/rubocop/target_finder.rb +4 -1
  242. data/lib/rubocop/todo_audit.rb +111 -0
  243. data/lib/rubocop/unified_diff.rb +257 -0
  244. data/lib/rubocop/version.rb +3 -5
  245. data/lib/rubocop.rb +7 -1
  246. data/lib/ruby_lsp/rubocop/addon.rb +1 -2
  247. metadata +18 -6
  248. data/lib/rubocop/cop/style/double_cop_disable_directive.rb +0 -46
@@ -49,7 +49,12 @@ module RuboCop
49
49
  InvestigationReport = Struct.new(:cop, :processed_source, :offenses, :corrector)
50
50
 
51
51
  # List of methods names to restrict calls for `on_send` / `on_csend`
52
- RESTRICT_ON_SEND = Set[].freeze # rubocop:disable InternalAffairs/UselessRestrictOnSend
52
+ RESTRICT_ON_SEND = Set[].freeze # rubocop:disable InternalAffairs/UselessRestrictOnSend -- the base class default, which cops override
53
+
54
+ # Departments whose cops report above `convention` unless a cop says
55
+ # otherwise. The exceptions live in `config/default.yml` as an explicit
56
+ # `Severity`, such as the lint-like cops in `Bundler` and `Gemspec`.
57
+ DEPARTMENT_SEVERITIES = { Lint: :warning, Security: :warning, Metrics: :refactor }.freeze
53
58
 
54
59
  # List of cops that should not try to autocorrect at the same
55
60
  # time as this cop
@@ -127,6 +132,8 @@ module RuboCop
127
132
  # in the `on_new_investigation` callback.
128
133
  # If your cop does autocorrections, be aware that your instance may be called
129
134
  # multiple times with the same `processed_source.path` but different content.
135
+ # Note that under `--parallel` each worker process has its own cop
136
+ # instances, so state persists only within a worker's share of the files.
130
137
  def self.support_multiple_source?
131
138
  false
132
139
  end
@@ -208,13 +215,15 @@ module RuboCop
208
215
  severity = find_severity(range_to_pass, severity)
209
216
  message = find_message(range_to_pass, message)
210
217
 
211
- status, corrector = enabled_line?(range.line) ? correct(range, &block) : :disabled
218
+ status, corrector = enabled_lines?(range) ? correct(range, &block) : :disabled
219
+ justification = suppression_reason(range) if status == :disabled
212
220
 
213
221
  # Since this range may be generated from Ruby code embedded in some
214
222
  # template file, we convert it to location info in the original file.
215
223
  range = range_for_original(range)
216
224
 
217
- current_offenses << Offense.new(severity, range, message, name, status, corrector)
225
+ current_offenses << Offense.new(severity, range, message, name, status, corrector,
226
+ justification: justification)
218
227
  end
219
228
 
220
229
  # This method should be overridden when a cop's behavior depends
@@ -284,6 +293,13 @@ module RuboCop
284
293
  @config.string_literals_frozen_by_default?
285
294
  end
286
295
 
296
+ # Whether the user opted in to unstable behavior, with `--preview` or
297
+ # `AllCops: Preview`. Cops branch on this to ship a change that is not
298
+ # ready to be the default yet.
299
+ def preview?
300
+ @config.preview?(@options)
301
+ end
302
+
287
303
  def relevant_file?(file)
288
304
  return false unless target_satisfies_all_gem_version_requirements?
289
305
  return true unless @config.clusivity_config_for_badge?(self.class.badge)
@@ -322,7 +338,7 @@ module RuboCop
322
338
 
323
339
  ### Reserved for Commissioner
324
340
 
325
- # rubocop:disable Layout/ClassStructure
341
+ # rubocop:disable Layout/ClassStructure -- grouped under the Commissioner heading above
326
342
  # @api private
327
343
  def callbacks_needed
328
344
  self.class.callbacks_needed
@@ -445,6 +461,10 @@ module RuboCop
445
461
  def use_corrector(range, corrector)
446
462
  if autocorrect?
447
463
  attempt_correction(range, corrector)
464
+ elsif skipped_unsafe_correction_with_disable_uncorrectable?
465
+ # The unsafe correction will not run, so the offense is
466
+ # uncorrectable for this run and gets a todo comment.
467
+ attempt_correction(range, nil)
448
468
  elsif corrector && (always_autocorrect? || (contextual_autocorrect? && !LSP.enabled?))
449
469
  :uncorrected
450
470
  else
@@ -524,12 +544,36 @@ module RuboCop
524
544
  @processed_source.comment_config.cop_enabled_at_line?(self, line_number)
525
545
  end
526
546
 
547
+ # A multi-line offense is suppressed by a directive on any line of its
548
+ # range, not only its first line, matching the intuition that the
549
+ # directive is attached to the offending code.
550
+ def enabled_lines?(range)
551
+ return true if @options[:ignore_disable_comments] || !@processed_source
552
+
553
+ comment_config = @processed_source.comment_config
554
+ comment_config.cop_enabled_at_lines?(self, range.first_line, range.last_line)
555
+ end
556
+
557
+ # The `--` reason on the directive that suppresses offenses on this
558
+ # range, or `nil` when the directive carries none.
559
+ def suppression_reason(range)
560
+ covering = covering_disabled_range(range)
561
+ covering.directive.reason if covering.respond_to?(:directive)
562
+ end
563
+
564
+ def covering_disabled_range(range)
565
+ disabled_ranges = @processed_source.comment_config.cop_disabled_line_ranges[cop_name]
566
+ disabled_ranges&.find do |disabled_range|
567
+ disabled_range.end >= range.first_line && disabled_range.begin <= range.last_line
568
+ end
569
+ end
570
+
527
571
  def find_severity(_range, severity)
528
572
  custom_severity || severity || default_severity
529
573
  end
530
574
 
531
575
  def default_severity
532
- self.class.lint? ? :warning : :convention
576
+ DEPARTMENT_SEVERITIES.fetch(self.class.department, :convention)
533
577
  end
534
578
 
535
579
  def custom_severity
@@ -46,7 +46,7 @@ module RuboCop
46
46
 
47
47
  # Removes `size` characters prior to the source range.
48
48
  #
49
- # @param [Parser::Source::Range, RuboCop::AST::Node] range or node
49
+ # @param [Parser::Source::Range, RuboCop::AST::Node] node_or_range
50
50
  # @param [Integer] size
51
51
  def remove_preceding(node_or_range, size)
52
52
  range = to_range(node_or_range)
@@ -58,7 +58,7 @@ module RuboCop
58
58
  # If `size` is greater than the size of `range`, the removed region can
59
59
  # overrun the end of `range`.
60
60
  #
61
- # @param [Parser::Source::Range, RuboCop::AST::Node] range or node
61
+ # @param [Parser::Source::Range, RuboCop::AST::Node] node_or_range
62
62
  # @param [Integer] size
63
63
  def remove_leading(node_or_range, size)
64
64
  range = to_range(node_or_range)
@@ -70,7 +70,7 @@ module RuboCop
70
70
  # If `size` is greater than the size of `range`, the removed region can
71
71
  # overrun the beginning of `range`.
72
72
  #
73
- # @param [Parser::Source::Range, RuboCop::AST::Node] range or node
73
+ # @param [Parser::Source::Range, RuboCop::AST::Node] node_or_range
74
74
  # @param [Integer] size
75
75
  def remove_trailing(node_or_range, size)
76
76
  range = to_range(node_or_range)
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RuboCop
4
- module Cop # rubocop:disable Style/Documentation
4
+ module Cop # rubocop:disable Style/Documentation -- a namespace that only autoloads the correctors below
5
5
  # Autoloads corrector classes used by cops. Classes are autoloaded to reduce the number of
6
6
  # required classes because they're referenced only when autocorrection is performed.
7
7
 
8
- # rubocop:disable Layout/LineLength
8
+ # rubocop:disable Layout/LineLength -- the autoload paths do not wrap
9
9
  autoload :AlignmentCorrector, 'rubocop/cop/correctors/alignment_corrector'
10
10
  autoload :ConditionCorrector, 'rubocop/cop/correctors/condition_corrector'
11
11
  autoload :EachToForCorrector, 'rubocop/cop/correctors/each_to_for_corrector'
@@ -92,7 +92,7 @@ module RuboCop
92
92
  (str "true")
93
93
  PATTERN
94
94
 
95
- def on_block(node) # rubocop:disable Metrics/MethodLength, InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
95
+ def on_block(node) # rubocop:disable Metrics/MethodLength, InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler -- the gemspec block is matched by its named block variable
96
96
  gem_specification(node) do |block_var|
97
97
  metadata_value = metadata(node)
98
98
  mfa_value = mfa_value(metadata_value)
@@ -147,7 +147,7 @@ module RuboCop
147
147
  badge: badge,
148
148
  version_added: version_added)
149
149
 
150
- injector.inject do # rubocop:disable Lint/UnexpectedBlockArity
150
+ injector.inject do # rubocop:disable Lint/UnexpectedBlockArity -- this `inject` is the injector API, not `Enumerable#inject`
151
151
  output.puts(format(CONFIGURATION_ADDED_MESSAGE,
152
152
  configuration_file_path: config_file_path))
153
153
  end
@@ -146,7 +146,7 @@ module RuboCop
146
146
  end
147
147
  end
148
148
 
149
- # rubocop:disable Metrics/MethodLength
149
+ # rubocop:disable-next Metrics/MethodLength
150
150
  def formatted_message(offense_type, directive, actual_name, method_name)
151
151
  case offense_type
152
152
  when :wrong_name
@@ -166,7 +166,6 @@ module RuboCop
166
166
  format(MSG, method: method_name)
167
167
  end
168
168
  end
169
- # rubocop:enable Metrics/MethodLength
170
169
 
171
170
  def remove_receiver(current)
172
171
  current.delete_prefix('self.')
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module InternalAffairs
6
- # rubocop:disable InternalAffairs/RedundantSourceRange -- node here is a `NodePattern::Node`
6
+ # rubocop:disable-next InternalAffairs/RedundantSourceRange -- node here is a `NodePattern::Node`
7
7
  class NodePatternGroups
8
8
  # Walks an AST that has been processed by `InternalAffairs::NodePatternGroups::Processor`
9
9
  # in order to find `node_type` and `node_sequence` nodes that can be replaced with a node
@@ -125,7 +125,6 @@ module RuboCop
125
125
  end
126
126
  end
127
127
  end
128
- # rubocop:enable InternalAffairs/RedundantSourceRange
129
128
  end
130
129
  end
131
130
  end
@@ -75,7 +75,7 @@ module RuboCop
75
75
  @on_csend_definition = node if node.new_identifier.value == :on_csend
76
76
  end
77
77
 
78
- def on_send(node) # rubocop:disable InternalAffairs/OnSendWithoutOnCSend
78
+ def on_send(node) # rubocop:disable InternalAffairs/OnSendWithoutOnCSend -- this cop is what enforces the rule, and does not apply to itself
79
79
  return unless (new_identifier = node.first_argument)
80
80
  return unless new_identifier.basic_literal?
81
81
 
@@ -81,6 +81,8 @@ module RuboCop
81
81
  last_arg = node.last_argument
82
82
 
83
83
  if last_arg.hash_type? && !last_arg.braces?
84
+ return items if enforce_hash_argument_with_separator?
85
+
84
86
  items += last_arg.pairs
85
87
  else
86
88
  items << last_arg
@@ -281,7 +281,7 @@ module RuboCop
281
281
  end
282
282
  end
283
283
 
284
- # rubocop:disable Metrics/AbcSize
284
+ # rubocop:disable-next Metrics/AbcSize
285
285
  def do_line_begins_inside_argument?(node, do_loc)
286
286
  line_begin_pos = do_loc.begin_pos - do_loc.column
287
287
  first_char_pos = line_begin_pos + (do_loc.source_line =~ /\S/)
@@ -292,7 +292,6 @@ module RuboCop
292
292
  first_char_pos < argument.source_range.end_pos
293
293
  end
294
294
  end
295
- # rubocop:enable Metrics/AbcSize
296
295
 
297
296
  # The continuation line indentation is only an unmeaningful alignment target when
298
297
  # it is dictated by an opening delimiter, i.e. the line begins inside `(` or `[`.
@@ -190,33 +190,58 @@ module RuboCop
190
190
 
191
191
  # Validates code style on class declaration.
192
192
  # Add offense when find a node out of expected order.
193
+ # A node is out of order when its category is expected earlier than
194
+ # the highest-priority category seen so far, so that a low-priority element
195
+ # (even an unmovable one) cannot mask disorder among the elements that follow it.
196
+ # Consecutive elements of the same category are reported only once,
197
+ # on the first element of the group.
193
198
  def on_class(class_node)
194
- previous = -1
195
- walk_over_nested_class_definition(class_node) do |node, category|
196
- index = expected_order.index(category)
197
- if index < previous
198
- message = format(MSG, category: category, previous: expected_order[previous])
199
- add_offense(node, message: message) { |corrector| autocorrect(corrector, node) }
199
+ # Corrections are registered in reverse source order because an insertion at
200
+ # a given position lands before any insertion already made there;
201
+ # this keeps the source order of nodes moved before the same anchor.
202
+ out_of_order_elements(class_node).reverse_each do |node, category, previous|
203
+ message = format(MSG, category: category, previous: previous)
204
+
205
+ add_offense(node, message: message) do |corrector|
206
+ autocorrect(corrector, node)
200
207
  end
201
- previous = index
202
208
  end
203
209
  end
204
210
  alias on_sclass on_class
205
211
 
206
212
  private
207
213
 
208
- # Autocorrect by swapping between two nodes autocorrecting them
209
- def autocorrect(corrector, node)
210
- previous = node.left_siblings.reverse.find do |sibling|
211
- !ignore_for_autocorrect?(node, sibling)
214
+ def out_of_order_elements(class_node)
215
+ out_of_order = []
216
+ max_index = -1
217
+ previous_category = nil
218
+ walk_over_nested_class_definition(class_node) do |node, category|
219
+ index = expected_order.index(category)
220
+ if index < max_index && category != previous_category
221
+ out_of_order << [node, category, expected_order[max_index]]
222
+ end
223
+ max_index = index if index > max_index
224
+ previous_category = category
212
225
  end
213
- return unless previous
214
-
215
- current_range = source_range_with_comment(node)
216
- previous_range = source_range_with_comment(previous)
226
+ out_of_order
227
+ end
217
228
 
218
- corrector.insert_before(previous_range, current_range.source)
219
- corrector.remove(current_range)
229
+ # Autocorrect by moving the node, together with the contiguous group of
230
+ # same-category elements that follows it, to its expected position.
231
+ def autocorrect(corrector, node)
232
+ return if dynamic_constant?(node)
233
+
234
+ anchor = insertion_anchor(node)
235
+ return unless anchor
236
+
237
+ anchor_range = source_range_with_comment(anchor)
238
+ # Reversed for the same reason offenses are registered in reverse source order:
239
+ # the last insertion at a position comes first.
240
+ movable_group(node).reverse_each do |group_node|
241
+ current_range = source_range_with_comment(group_node)
242
+ corrector.insert_before(anchor_range, current_range.source)
243
+ corrector.remove(current_range)
244
+ end
220
245
  end
221
246
 
222
247
  # Classifies a node to match with something in the {expected_order}
@@ -302,15 +327,6 @@ module RuboCop
302
327
  end
303
328
  end
304
329
 
305
- def ignore_for_autocorrect?(node, sibling)
306
- classification = classify(node)
307
- sibling_class = classify(sibling)
308
-
309
- ignore?(sibling, sibling_class) ||
310
- classification == sibling_class ||
311
- dynamic_constant?(node)
312
- end
313
-
314
330
  def humanize_node(node)
315
331
  if node.def_type?
316
332
  return :initializer if node.method?(:initialize)
@@ -328,6 +344,66 @@ module RuboCop
328
344
  !(expression.method?(:freeze) && expression.receiver&.recursive_basic_literal?)
329
345
  end
330
346
 
347
+ # The expected position of the node: the first left sibling within its movable span
348
+ # whose category is expected to appear after the node's.
349
+ # Requiring a strictly later category keeps the order of elements sharing a category stable.
350
+ def insertion_anchor(node)
351
+ index = expected_order.index(classify(node))
352
+
353
+ movable_span(node).find do |sibling|
354
+ classification = classify(sibling)
355
+
356
+ !ignore?(sibling, classification) && expected_order.index(classification) > index
357
+ end
358
+ end
359
+
360
+ # The node together with the contiguous same-category right siblings,
361
+ # so that the whole group moves in a single pass while the offense is
362
+ # reported only on its first element.
363
+ def movable_group(node)
364
+ classification = classify(node)
365
+ group = [node]
366
+
367
+ node.right_siblings.each do |sibling|
368
+ break unless classify(sibling) == classification
369
+ break if ignore?(sibling, classification) || dynamic_constant?(sibling)
370
+
371
+ group << sibling
372
+ end
373
+
374
+ group
375
+ end
376
+
377
+ # Left siblings the node may be reordered with: those after the last barrier.
378
+ # Ignored elements within the span are simply jumped over.
379
+ def movable_span(node)
380
+ left_siblings = node.left_siblings
381
+ barrier_index = left_siblings.rindex { |sibling| barrier?(node, sibling) }
382
+
383
+ barrier_index ? left_siblings[(barrier_index + 1)..] : left_siblings
384
+ end
385
+
386
+ # A dynamic constant blocks every element: the cop does not move such constants,
387
+ # and letting other elements jump over one would change execution order just the same.
388
+ # A bare visibility modifier blocks only the elements whose meaning depends on
389
+ # the visibility section they appear in.
390
+ def barrier?(node, sibling)
391
+ dynamic_constant?(sibling) || (visibility_dependent?(node) && visibility_block?(sibling))
392
+ end
393
+
394
+ # Whether moving the node across a bare visibility modifier would change its meaning.
395
+ # This is the case for `def` nodes and for macros whose category is classified by visibility
396
+ # (e.g. `attr_accessor` when the expected order lists `private_attribute_macros`).
397
+ # Inline visibility (`private def foo`, `def self.foo`) travels with the node.
398
+ def visibility_dependent?(node)
399
+ return true if node.def_type?
400
+ return false if !node.send_type? || node.def_modifier?
401
+
402
+ key = find_category(node.method_name) || node.method_name.to_s
403
+
404
+ VISIBILITY_SCOPES.any? { |visibility| expected_order.include?("#{visibility}_#{key}") }
405
+ end
406
+
331
407
  def private_constant?(node)
332
408
  return false unless node.casgn_type? && node.namespace.nil?
333
409
  return false unless (parent = node.parent)
@@ -64,10 +64,10 @@ module RuboCop
64
64
  autocorrect_one(corrector, comment)
65
65
  end
66
66
 
67
- # Corrects all comment lines that occur immediately before the given
68
- # comment and have the same indentation. This is to avoid a long chain
69
- # of correcting, saving the file, parsing and inspecting again, and
70
- # then correcting one more line, and so on.
67
+ # Corrects all preceding comment lines that have the same indentation
68
+ # and are separated from the given comment by nothing but blank lines.
69
+ # This is to avoid a long chain of correcting, saving the file, parsing
70
+ # and inspecting again, and then correcting one more line, and so on.
71
71
  def autocorrect_preceding_comments(corrector, comment)
72
72
  comments = processed_source.comments
73
73
  index = comments.index(comment)
@@ -82,7 +82,10 @@ module RuboCop
82
82
  def should_correct?(preceding_comment, reference_comment)
83
83
  loc = preceding_comment.loc
84
84
  ref_loc = reference_comment.loc
85
- loc.line == ref_loc.line - 1 && loc.column == ref_loc.column
85
+ return false unless loc.column == ref_loc.column
86
+ return false unless own_line_comment?(preceding_comment)
87
+
88
+ ((loc.line + 1)...ref_loc.line).all? { |line| processed_source[line - 1].blank? }
86
89
  end
87
90
 
88
91
  def autocorrect_one(corrector, comment)
@@ -93,6 +93,7 @@ module RuboCop
93
93
  when :def, :defs then base_for_method_definition(parent)
94
94
  when :kwbegin then parent.loc.begin
95
95
  when :block, :numblock, :itblock then start_line_range(parent)
96
+ when :class, :module, :sclass then parent.loc.keyword
96
97
  else node.loc.keyword
97
98
  end
98
99
  end
@@ -161,7 +161,7 @@ module RuboCop
161
161
  next_sibling.if_type? && contains_guard_clause?(next_sibling)
162
162
  end
163
163
 
164
- # rubocop:disable Metrics/CyclomaticComplexity
164
+ # rubocop:disable-next Metrics/CyclomaticComplexity
165
165
  def last_heredoc_argument(node)
166
166
  n = last_heredoc_argument_node(node)
167
167
  n = n.children.first while n.respond_to?(:begin_type?) && n.begin_type?
@@ -176,7 +176,6 @@ module RuboCop
176
176
 
177
177
  last_heredoc_argument(n.receiver) if n.respond_to?(:receiver)
178
178
  end
179
- # rubocop:enable Metrics/CyclomaticComplexity
180
179
 
181
180
  def last_heredoc_argument_node(node)
182
181
  return node unless node.respond_to?(:if_branch)
@@ -5,6 +5,14 @@ module RuboCop
5
5
  module Layout
6
6
  # Checks for a newline after the final magic comment.
7
7
  #
8
+ # `NumberOfEmptyLines` configures the minimum number of empty lines required. Set it
9
+ # to `2` when using YARD, which otherwise treats the magic comments as documentation
10
+ # for the first module or class in the file.
11
+ #
12
+ # NOTE: `Layout/EmptyLines` has to be disabled for values greater than `1`, as it
13
+ # removes the extra empty lines this cop adds, and autocorrecting with both enabled
14
+ # loops between them.
15
+ #
8
16
  # @example
9
17
  # # good
10
18
  # # frozen_string_literal: true
@@ -20,30 +28,80 @@ module RuboCop
20
28
  # class Person
21
29
  # # Some code
22
30
  # end
31
+ #
32
+ # @example NumberOfEmptyLines: 1 (default)
33
+ # # good
34
+ # # frozen_string_literal: true
35
+ #
36
+ # class Person
37
+ # end
38
+ #
39
+ # @example NumberOfEmptyLines: 2
40
+ # # bad
41
+ # # frozen_string_literal: true
42
+ #
43
+ # class Person
44
+ # end
45
+ #
46
+ # # good
47
+ # # frozen_string_literal: true
48
+ #
49
+ #
50
+ # class Person
51
+ # end
23
52
  class EmptyLineAfterMagicComment < Base
24
53
  include RangeHelp
25
54
  extend AutoCorrector
26
55
 
27
- MSG = 'Add an empty line after magic comments.'
56
+ MSG = 'Expected at least %<expected>d empty %<lines>s after magic comments; found ' \
57
+ '%<actual>d.'
58
+
59
+ def validate_config
60
+ return if number_of_empty_lines.is_a?(Integer) && number_of_empty_lines.positive?
61
+
62
+ raise ValidationError,
63
+ 'The `Layout/EmptyLineAfterMagicComment` cop only accepts a positive ' \
64
+ 'integer for its `NumberOfEmptyLines` configuration parameter, but ' \
65
+ "`#{number_of_empty_lines.inspect}` was given."
66
+ end
28
67
 
29
68
  def on_new_investigation
30
69
  return unless (last_magic_comment = last_magic_comment(processed_source))
31
- return unless (next_line = processed_source[last_magic_comment.loc.line])
32
- return if next_line.strip.empty?
70
+
71
+ actual = empty_lines_after(last_magic_comment)
72
+ # Trailing empty lines are `Layout/TrailingEmptyLines`' responsibility.
73
+ return unless processed_source[last_magic_comment.loc.line + actual]
74
+ return if actual >= number_of_empty_lines
33
75
 
34
76
  offending_range = offending_range(last_magic_comment)
35
77
 
36
- add_offense(offending_range) do |corrector|
37
- corrector.insert_before(offending_range, "\n")
78
+ add_offense(offending_range, message: message(actual)) do |corrector|
79
+ corrector.insert_before(offending_range, "\n" * (number_of_empty_lines - actual))
38
80
  end
39
81
  end
40
82
 
41
83
  private
42
84
 
85
+ def message(actual)
86
+ format(MSG, expected: number_of_empty_lines, actual: actual,
87
+ lines: number_of_empty_lines == 1 ? 'line' : 'lines')
88
+ end
89
+
90
+ def empty_lines_after(last_magic_comment)
91
+ first_line = last_magic_comment.loc.line
92
+ count = 0
93
+ count += 1 while (line = processed_source[first_line + count]) && line.strip.empty?
94
+ count
95
+ end
96
+
43
97
  def offending_range(last_magic_comment)
44
98
  source_range(processed_source.buffer, last_magic_comment.loc.line + 1, 0)
45
99
  end
46
100
 
101
+ def number_of_empty_lines
102
+ cop_config.fetch('NumberOfEmptyLines', 1)
103
+ end
104
+
47
105
  # Find the last magic comment in the source file.
48
106
  #
49
107
  # Take all comments that precede the first line of code (or just take
@@ -91,7 +91,7 @@ module RuboCop
91
91
  end
92
92
 
93
93
  def next_line_node(node)
94
- return if node.parent.if_type?
94
+ return if node.parent.type?(:if, :rescue, :ensure)
95
95
 
96
96
  node.right_sibling
97
97
  end
@@ -123,7 +123,7 @@ module RuboCop
123
123
  AlignmentCorrector.align_end(corrector, processed_source, node, alignment_node(node))
124
124
  end
125
125
 
126
- # rubocop:disable Metrics/CyclomaticComplexity
126
+ # rubocop:disable-next Metrics/CyclomaticComplexity
127
127
  def check_assignment(node, rhs)
128
128
  # If there are method calls chained to the right hand side of the
129
129
  # assignment, we let rhs be the receiver of those method calls before
@@ -139,7 +139,6 @@ module RuboCop
139
139
 
140
140
  check_asgn_alignment(node, rhs)
141
141
  end
142
- # rubocop:enable Metrics/CyclomaticComplexity
143
142
 
144
143
  def check_asgn_alignment(outer_node, inner_node)
145
144
  align_with = {