rubocop 1.88.2 → 1.90.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 (227) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +139 -16
  3. data/exe/rubocop +1 -1
  4. data/lib/rubocop/cached_data.rb +7 -2
  5. data/lib/rubocop/cli/command/execute_runner.rb +31 -0
  6. data/lib/rubocop/cli.rb +2 -4
  7. data/lib/rubocop/comment_config/directive_range.rb +32 -0
  8. data/lib/rubocop/comment_config/disable_next.rb +97 -0
  9. data/lib/rubocop/comment_config.rb +81 -49
  10. data/lib/rubocop/config.rb +76 -5
  11. data/lib/rubocop/config_loader.rb +1 -2
  12. data/lib/rubocop/config_loader_resolver.rb +1 -2
  13. data/lib/rubocop/config_validator.rb +39 -3
  14. data/lib/rubocop/cop/autocorrect_logic.rb +8 -0
  15. data/lib/rubocop/cop/badge.rb +8 -0
  16. data/lib/rubocop/cop/base.rb +40 -6
  17. data/lib/rubocop/cop/bundler.rb +19 -0
  18. data/lib/rubocop/cop/corrector.rb +4 -4
  19. data/lib/rubocop/cop/correctors/alignment_corrector.rb +55 -5
  20. data/lib/rubocop/cop/gemspec.rb +22 -0
  21. data/lib/rubocop/cop/generator/registration_injector.rb +188 -0
  22. data/lib/rubocop/cop/generator.rb +4 -0
  23. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +1 -2
  24. data/lib/rubocop/cop/internal_affairs/node_pattern_groups/ast_walker.rb +1 -2
  25. data/lib/rubocop/cop/layout/block_alignment.rb +1 -2
  26. data/lib/rubocop/cop/layout/class_structure.rb +108 -26
  27. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +1 -2
  28. data/lib/rubocop/cop/layout/empty_line_after_magic_comment.rb +63 -5
  29. data/lib/rubocop/cop/layout/end_alignment.rb +1 -2
  30. data/lib/rubocop/cop/layout/extra_spacing.rb +105 -3
  31. data/lib/rubocop/cop/layout/first_argument_indentation.rb +21 -1
  32. data/lib/rubocop/cop/layout/first_array_element_indentation.rb +1 -1
  33. data/lib/rubocop/cop/layout/indentation_width.rb +3 -1
  34. data/lib/rubocop/cop/layout/leading_comment_space.rb +27 -0
  35. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +1 -2
  36. data/lib/rubocop/cop/layout/line_length.rb +84 -12
  37. data/lib/rubocop/cop/layout/multiline_assignment_layout.rb +1 -2
  38. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +12 -2
  39. data/lib/rubocop/cop/layout/redundant_line_break.rb +77 -0
  40. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +1 -2
  41. data/lib/rubocop/cop/layout/space_around_operators.rb +7 -0
  42. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +31 -34
  43. data/lib/rubocop/cop/layout.rb +114 -0
  44. data/lib/rubocop/cop/lazy_loader.rb +41 -0
  45. data/lib/rubocop/cop/lint/ambiguous_assignment.rb +9 -7
  46. data/lib/rubocop/cop/lint/ambiguous_block_association.rb +71 -0
  47. data/lib/rubocop/cop/lint/ambiguous_range.rb +1 -2
  48. data/lib/rubocop/cop/lint/argument_mismatch.rb +89 -0
  49. data/lib/rubocop/cop/lint/constant_reassignment.rb +18 -21
  50. data/lib/rubocop/cop/lint/constant_resolution.rb +25 -0
  51. data/lib/rubocop/cop/lint/cop_directive_syntax.rb +84 -8
  52. data/lib/rubocop/cop/lint/deprecated_reference.rb +194 -0
  53. data/lib/rubocop/cop/lint/duplicate_methods.rb +300 -22
  54. data/lib/rubocop/cop/lint/implicit_string_concatenation.rb +1 -2
  55. data/lib/rubocop/cop/lint/ineffective_access_modifier.rb +1 -2
  56. data/lib/rubocop/cop/lint/inherit_exception.rb +53 -7
  57. data/lib/rubocop/cop/lint/interpolation_check.rb +1 -2
  58. data/lib/rubocop/cop/lint/literal_as_condition.rb +8 -6
  59. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +4 -7
  60. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +25 -12
  61. data/lib/rubocop/cop/lint/missing_super.rb +31 -1
  62. data/lib/rubocop/cop/lint/name_typo.rb +270 -0
  63. data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +15 -4
  64. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +98 -19
  65. data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +47 -7
  66. data/lib/rubocop/cop/lint/redundant_require_statement.rb +1 -2
  67. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +1 -2
  68. data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +1 -2
  69. data/lib/rubocop/cop/lint/redundant_type_conversion.rb +1 -2
  70. data/lib/rubocop/cop/lint/redundant_with_index.rb +1 -2
  71. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +8 -2
  72. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +2 -4
  73. data/lib/rubocop/cop/lint/super_argument_mismatch.rb +179 -0
  74. data/lib/rubocop/cop/lint/suppressed_exception_in_number_conversion.rb +1 -2
  75. data/lib/rubocop/cop/lint/to_enum_arguments.rb +38 -3
  76. data/lib/rubocop/cop/lint/unreachable_code.rb +1 -2
  77. data/lib/rubocop/cop/lint/unused_private_method.rb +234 -0
  78. data/lib/rubocop/cop/lint/useless_access_modifier.rb +1 -2
  79. data/lib/rubocop/cop/lint/useless_assignment.rb +1 -2
  80. data/lib/rubocop/cop/lint/useless_constant_scoping.rb +36 -0
  81. data/lib/rubocop/cop/lint/useless_method_definition.rb +1 -1
  82. data/lib/rubocop/cop/lint/useless_rescue.rb +1 -2
  83. data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +2 -0
  84. data/lib/rubocop/cop/lint/utils/nil_receiver_checker.rb +61 -7
  85. data/lib/rubocop/cop/lint/void.rb +18 -8
  86. data/lib/rubocop/cop/lint.rb +178 -0
  87. data/lib/rubocop/cop/metrics/perceived_complexity.rb +1 -2
  88. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +1 -1
  89. data/lib/rubocop/cop/metrics.rb +31 -0
  90. data/lib/rubocop/cop/migration/department_name.rb +1 -6
  91. data/lib/rubocop/cop/migration.rb +13 -0
  92. data/lib/rubocop/cop/mixin/check_line_breakable.rb +23 -5
  93. data/lib/rubocop/cop/mixin/comments_help.rb +1 -2
  94. data/lib/rubocop/cop/mixin/configurable_enforced_style.rb +1 -2
  95. data/lib/rubocop/cop/mixin/endless_method_rewriter.rb +13 -5
  96. data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +23 -6
  97. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +1 -2
  98. data/lib/rubocop/cop/mixin/hash_subset.rb +1 -2
  99. data/lib/rubocop/cop/mixin/indexed_method_arity.rb +94 -0
  100. data/lib/rubocop/cop/mixin/line_length_help.rb +7 -1
  101. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +51 -6
  102. data/lib/rubocop/cop/mixin/project_index_help.rb +140 -0
  103. data/lib/rubocop/cop/mixin/range_help.rb +1 -2
  104. data/lib/rubocop/cop/mixin/reparsed_equivalence.rb +171 -0
  105. data/lib/rubocop/cop/mixin/statement_modifier.rb +46 -4
  106. data/lib/rubocop/cop/mixin/trailing_comma.rb +2 -4
  107. data/lib/rubocop/cop/mixin.rb +82 -77
  108. data/lib/rubocop/cop/naming/accessor_method_name.rb +24 -0
  109. data/lib/rubocop/cop/naming/binary_operator_parameter_name.rb +16 -0
  110. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +2 -4
  111. data/lib/rubocop/cop/naming/method_name.rb +5 -2
  112. data/lib/rubocop/cop/naming/predicate_prefix.rb +33 -5
  113. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +1 -2
  114. data/lib/rubocop/cop/naming/variable_number.rb +3 -1
  115. data/lib/rubocop/cop/naming.rb +31 -0
  116. data/lib/rubocop/cop/offense.rb +37 -10
  117. data/lib/rubocop/cop/registry.rb +180 -46
  118. data/lib/rubocop/cop/security.rb +19 -0
  119. data/lib/rubocop/cop/style/accessor_grouping.rb +1 -2
  120. data/lib/rubocop/cop/style/arguments_forwarding.rb +3 -6
  121. data/lib/rubocop/cop/style/array_first_last.rb +1 -2
  122. data/lib/rubocop/cop/style/array_intersect.rb +24 -5
  123. data/lib/rubocop/cop/style/bitwise_predicate.rb +1 -2
  124. data/lib/rubocop/cop/style/block_delimiters.rb +3 -6
  125. data/lib/rubocop/cop/style/case_like_if.rb +2 -4
  126. data/lib/rubocop/cop/style/class_and_module_children.rb +79 -20
  127. data/lib/rubocop/cop/style/combinable_loops.rb +1 -2
  128. data/lib/rubocop/cop/style/concat_array_literals.rb +1 -2
  129. data/lib/rubocop/cop/style/conditional_assignment.rb +23 -2
  130. data/lib/rubocop/cop/style/constant_visibility.rb +1 -2
  131. data/lib/rubocop/cop/style/copyright.rb +5 -1
  132. data/lib/rubocop/cop/style/directive_scope.rb +171 -0
  133. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +76 -12
  134. data/lib/rubocop/cop/style/documentation.rb +47 -0
  135. data/lib/rubocop/cop/style/empty_case_condition.rb +1 -2
  136. data/lib/rubocop/cop/style/endless_method.rb +0 -10
  137. data/lib/rubocop/cop/style/format_string_token.rb +1 -2
  138. data/lib/rubocop/cop/style/guard_clause.rb +1 -2
  139. data/lib/rubocop/cop/style/hash_each_methods.rb +1 -2
  140. data/lib/rubocop/cop/style/hash_syntax.rb +1 -2
  141. data/lib/rubocop/cop/style/identical_conditional_branches.rb +2 -4
  142. data/lib/rubocop/cop/style/if_inside_else.rb +1 -2
  143. data/lib/rubocop/cop/style/if_unless_modifier.rb +8 -50
  144. data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +1 -2
  145. data/lib/rubocop/cop/style/lambda.rb +4 -0
  146. data/lib/rubocop/cop/style/map_to_hash.rb +1 -2
  147. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +22 -7
  148. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +1 -2
  149. data/lib/rubocop/cop/style/missing_respond_to_missing.rb +30 -0
  150. data/lib/rubocop/cop/style/multiline_if_then.rb +7 -1
  151. data/lib/rubocop/cop/style/multiline_method_signature.rb +1 -2
  152. data/lib/rubocop/cop/style/multiple_comparison.rb +33 -22
  153. data/lib/rubocop/cop/style/mutable_constant.rb +5 -0
  154. data/lib/rubocop/cop/style/negated_if_else_condition.rb +1 -2
  155. data/lib/rubocop/cop/style/negative_array_index.rb +3 -3
  156. data/lib/rubocop/cop/style/nil_comparison.rb +1 -2
  157. data/lib/rubocop/cop/style/operator_method_call.rb +1 -2
  158. data/lib/rubocop/cop/style/parallel_assignment.rb +1 -2
  159. data/lib/rubocop/cop/style/redundant_assignment.rb +12 -2
  160. data/lib/rubocop/cop/style/redundant_condition.rb +2 -4
  161. data/lib/rubocop/cop/style/redundant_constant_base.rb +37 -1
  162. data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +1 -2
  163. data/lib/rubocop/cop/style/redundant_each.rb +1 -2
  164. data/lib/rubocop/cop/style/redundant_file_extension_in_require.rb +9 -0
  165. data/lib/rubocop/cop/style/redundant_format.rb +14 -7
  166. data/lib/rubocop/cop/style/redundant_line_continuation.rb +78 -135
  167. data/lib/rubocop/cop/style/redundant_parentheses.rb +65 -67
  168. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +1 -2
  169. data/lib/rubocop/cop/style/redundant_return.rb +1 -2
  170. data/lib/rubocop/cop/style/redundant_self_assignment.rb +1 -2
  171. data/lib/rubocop/cop/style/redundant_string_escape.rb +1 -2
  172. data/lib/rubocop/cop/style/rescue_modifier.rb +1 -2
  173. data/lib/rubocop/cop/style/safe_navigation.rb +11 -2
  174. data/lib/rubocop/cop/style/sample.rb +11 -6
  175. data/lib/rubocop/cop/style/select_by_regexp.rb +1 -2
  176. data/lib/rubocop/cop/style/semicolon.rb +2 -4
  177. data/lib/rubocop/cop/style/single_line_do_end_block.rb +1 -2
  178. data/lib/rubocop/cop/style/sole_nested_conditional.rb +21 -5
  179. data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +2 -0
  180. data/lib/rubocop/cop/style/static_class.rb +21 -0
  181. data/lib/rubocop/cop/style/string_concatenation.rb +18 -5
  182. data/lib/rubocop/cop/style/super_arguments.rb +1 -2
  183. data/lib/rubocop/cop/style/symbol_proc.rb +1 -2
  184. data/lib/rubocop/cop/style/time_now.rb +41 -0
  185. data/lib/rubocop/cop/style/yoda_condition.rb +1 -2
  186. data/lib/rubocop/cop/style.rb +312 -0
  187. data/lib/rubocop/cop/team.rb +59 -6
  188. data/lib/rubocop/cop/util.rb +2 -4
  189. data/lib/rubocop/cop/variable_force/variable.rb +19 -8
  190. data/lib/rubocop/cops_documentation_generator.rb +4 -7
  191. data/lib/rubocop/directive_comment.rb +39 -3
  192. data/lib/rubocop/feature_loader.rb +0 -6
  193. data/lib/rubocop/formatter/emacs_style_formatter.rb +3 -1
  194. data/lib/rubocop/formatter/html_formatter.rb +2 -4
  195. data/lib/rubocop/formatter/json_formatter.rb +11 -6
  196. data/lib/rubocop/formatter/junit_formatter.rb +26 -9
  197. data/lib/rubocop/formatter/offense_count_formatter.rb +1 -2
  198. data/lib/rubocop/formatter/simple_text_formatter.rb +4 -3
  199. data/lib/rubocop/formatter/tap_formatter.rb +3 -1
  200. data/lib/rubocop/formatter/worst_offenders_formatter.rb +1 -2
  201. data/lib/rubocop/lockfile.rb +18 -0
  202. data/lib/rubocop/lsp/diagnostic.rb +4 -8
  203. data/lib/rubocop/lsp/routes.rb +89 -22
  204. data/lib/rubocop/lsp/runtime.rb +4 -0
  205. data/lib/rubocop/lsp/server.rb +4 -0
  206. data/lib/rubocop/lsp/stdin_runner.rb +20 -0
  207. data/lib/rubocop/mcp/server.rb +1 -2
  208. data/lib/rubocop/options.rb +10 -6
  209. data/lib/rubocop/path_util.rb +1 -2
  210. data/lib/rubocop/pending_cops_reporter.rb +1 -6
  211. data/lib/rubocop/plugin/configuration_integrator.rb +6 -3
  212. data/lib/rubocop/project_index_loader.rb +16 -0
  213. data/lib/rubocop/rake_task.rb +1 -2
  214. data/lib/rubocop/result_cache.rb +6 -4
  215. data/lib/rubocop/rspec/expect_offense.rb +2 -4
  216. data/lib/rubocop/rspec/shared_contexts.rb +16 -2
  217. data/lib/rubocop/runner.rb +71 -43
  218. data/lib/rubocop/server/cache.rb +11 -7
  219. data/lib/rubocop/server/cli.rb +2 -4
  220. data/lib/rubocop/server.rb +14 -0
  221. data/lib/rubocop/target_finder.rb +4 -1
  222. data/lib/rubocop/target_ruby.rb +1 -1
  223. data/lib/rubocop/todo_audit.rb +111 -0
  224. data/lib/rubocop/version.rb +3 -5
  225. data/lib/rubocop.rb +20 -626
  226. data/lib/ruby_lsp/rubocop/addon.rb +1 -2
  227. metadata +28 -5
@@ -51,7 +51,7 @@ module RuboCop
51
51
 
52
52
  private
53
53
 
54
- # rubocop:disable Metrics/AbcSize
54
+ # rubocop:disable-next Metrics/AbcSize
55
55
  def define_options
56
56
  OptionParser.new do |opts|
57
57
  opts.banner = rainbow.wrap('Usage: rubocop [options] [file1, file2, ...]').bright
@@ -71,7 +71,6 @@ module RuboCop
71
71
  add_profile_options(opts) if RUBY_ENGINE == 'ruby' && !Platform.windows?
72
72
  end
73
73
  end
74
- # rubocop:enable Metrics/AbcSize
75
74
 
76
75
  def add_check_options(opts) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
77
76
  section(opts, 'Basic Options') do # rubocop:disable Metrics/BlockLength
@@ -132,6 +131,7 @@ module RuboCop
132
131
  option(opts, '--display-only-fail-level-offenses')
133
132
  option(opts, '--display-only-correctable')
134
133
  option(opts, '--display-only-safe-correctable')
134
+ option(opts, '--display-suppressed')
135
135
  end
136
136
  end
137
137
 
@@ -173,6 +173,8 @@ module RuboCop
173
173
  @options.replace(ConfigRegeneration.new.options.merge(@options))
174
174
  end
175
175
 
176
+ option(opts, '--report-unused-todo-entries')
177
+
176
178
  option(opts, '--exclude-limit COUNT') { @validator.validate_exclude_limit_option }
177
179
  option(opts, '--no-exclude-limit')
178
180
 
@@ -382,7 +384,7 @@ module RuboCop
382
384
  %i[only except].each { |opt| OptionsValidator.validate_cop_list(@options[opt]) }
383
385
  end
384
386
 
385
- # rubocop:disable Metrics/AbcSize
387
+ # rubocop:disable-next Metrics/AbcSize
386
388
  def validate_compatibility # rubocop:disable Metrics/MethodLength
387
389
  if only_includes_redundant_disable?
388
390
  raise OptionArgumentError, 'Lint/RedundantCopDisableDirective cannot be used with --only.'
@@ -405,7 +407,6 @@ module RuboCop
405
407
 
406
408
  raise OptionArgumentError, "Incompatible cli options: #{incompatible_options.inspect}"
407
409
  end
408
- # rubocop:enable Metrics/AbcSize
409
410
 
410
411
  def validate_auto_gen_config
411
412
  return if @options.key?(:auto_gen_config)
@@ -527,7 +528,7 @@ module RuboCop
527
528
 
528
529
  # This module contains help texts for command line options.
529
530
  # @api private
530
- # rubocop:disable Metrics/ModuleLength
531
+ # rubocop:disable-next Metrics/ModuleLength
531
532
  module OptionsHelp
532
533
  MAX_EXCL = RuboCop::Options::DEFAULT_MAXIMUM_EXCLUSION_ITEMS.to_s
533
534
  FORMATTER_OPTION_LIST = RuboCop::Formatter::FormatterSet::BUILTIN_FORMATTERS_FOR_KEYS.keys
@@ -545,6 +546,8 @@ module RuboCop
545
546
  regenerate_todo: ['Regenerate the TODO configuration file using',
546
547
  'the last configuration. If there is no existing',
547
548
  'TODO file, acts like --auto-gen-config.'],
549
+ report_unused_todo_entries: ['Also report TODO configuration file entries that',
550
+ 'are no longer needed, and fail if any are found.'],
548
551
  offense_counts: ['Include offense counts in configuration',
549
552
  'file generated by --auto-gen-config.',
550
553
  'Default is true.'],
@@ -611,6 +614,8 @@ module RuboCop
611
614
  display_only_correctable: ['Only output correctable offense messages.'],
612
615
  display_only_safe_correctable: ['Only output safe-correctable offense messages',
613
616
  'when combined with --display-only-correctable.'],
617
+ display_suppressed: ['Also output offenses suppressed by directive',
618
+ 'comments. They do not affect the exit code.'],
614
619
  show_cops: ['Show the given cops, or all cops by',
615
620
  'default, and their configurations for the',
616
621
  'current directory.',
@@ -689,5 +694,4 @@ module RuboCop
689
694
  memory: 'Profile rubocop memory usage.'
690
695
  }.freeze
691
696
  end
692
- # rubocop:enable Metrics/ModuleLength
693
697
  end
@@ -63,7 +63,7 @@ module RuboCop
63
63
  end
64
64
  end
65
65
 
66
- # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
66
+ # rubocop:disable-next Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
67
67
  def match_path?(pattern, path)
68
68
  case pattern
69
69
  when String
@@ -89,7 +89,6 @@ module RuboCop
89
89
  end
90
90
  end
91
91
  end
92
- # rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
93
92
 
94
93
  # Returns true for an absolute Unix or Windows path.
95
94
  def absolute?(path)
@@ -20,7 +20,7 @@ module RuboCop
20
20
  attr_accessor :disable_pending_cops, :enable_pending_cops
21
21
 
22
22
  def warn_if_needed(config)
23
- return if possible_new_cops?(config)
23
+ return if disable_pending_cops || enable_pending_cops
24
24
 
25
25
  pending_cops = pending_cops_only_qualified(config.pending_cops)
26
26
  warn_on_pending_cops(pending_cops) unless pending_cops.empty?
@@ -32,11 +32,6 @@ module RuboCop
32
32
  pending_cops.select { |cop| Cop::Registry.qualified_cop?(cop.name) }
33
33
  end
34
34
 
35
- def possible_new_cops?(config)
36
- disable_pending_cops || enable_pending_cops ||
37
- config.disabled_new_cops? || config.enabled_new_cops?
38
- end
39
-
40
35
  def warn_on_pending_cops(pending_cops)
41
36
  warn Rainbow(PENDING_BANNER).yellow
42
37
 
@@ -59,7 +59,11 @@ module RuboCop
59
59
 
60
60
  plugin_config.make_excludes_absolute
61
61
 
62
- ConfigLoader.merge_with_default(plugin_config, plugin_config_path)
62
+ # `unset_nil: false` keeps `AllCops` keys declared with a nil value by a plugin
63
+ # (e.g. `TargetRailsVersion: ~` of rubocop-rails); `merge_all_cop_settings` above
64
+ # carries such keys into each subsequent plugin's config, where `unset_nil: true`
65
+ # would delete them from the combined configuration.
66
+ ConfigLoader.merge_with_default(plugin_config, plugin_config_path, unset_nil: false)
63
67
  end
64
68
  end
65
69
  end
@@ -88,7 +92,7 @@ module RuboCop
88
92
  result
89
93
  end
90
94
 
91
- # rubocop:disable Metrics/AbcSize
95
+ # rubocop:disable-next Metrics/AbcSize
92
96
  def load_plugin_rubocop_config(plugin, runner_context)
93
97
  rules = plugin.rules(runner_context)
94
98
 
@@ -105,7 +109,6 @@ module RuboCop
105
109
  raise "Plugin `#{plugin_name}' failed to load with error: #{error_message}"
106
110
  end
107
111
  end
108
- # rubocop:enable Metrics/AbcSize
109
112
 
110
113
  # This is how we ensure "first-in wins": plugins can override AllCops settings that are
111
114
  # set by RuboCop's default configuration, but once a plugin sets an AllCop setting, they
@@ -62,5 +62,21 @@ module RuboCop
62
62
  rescue StandardError => e
63
63
  warn Rainbow("rubydex index build failed: #{e.message}. Continuing without it.").yellow
64
64
  end
65
+
66
+ # The Ruby source files of every gem in the project's bundle, for
67
+ # `AllCops/ProjectIndexIncludesGems`. Returns an empty array when RuboCop
68
+ # does not run inside Bundler, so the option silently degrades to
69
+ # project-only indexing.
70
+ def bundled_gem_source_files
71
+ return [] unless defined?(Bundler) && Bundler::SharedHelpers.in_bundle?
72
+
73
+ Bundler.load.specs.flat_map do |spec|
74
+ Dir.glob(File.join(spec.full_gem_path, 'lib', '**', '*.rb'))
75
+ end
76
+ rescue StandardError => e
77
+ message = "rubydex bundle indexing failed: #{e.message}. Indexing the project only."
78
+ warn Rainbow(message).yellow
79
+ []
80
+ end
65
81
  end
66
82
  end
@@ -74,7 +74,7 @@ module RuboCop
74
74
 
75
75
  def setup_subtasks(name, *args, &task_block) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
76
76
  namespace(name) do
77
- # rubocop:todo Naming/InclusiveLanguage
77
+ # rubocop:todo-next Naming/InclusiveLanguage
78
78
  task(:auto_correct, *args) do |_, task_args|
79
79
  require 'rainbow'
80
80
  warn Rainbow(
@@ -86,7 +86,6 @@ module RuboCop
86
86
  perform('--autocorrect')
87
87
  end
88
88
  end
89
- # rubocop:enable Naming/InclusiveLanguage
90
89
 
91
90
  desc "Autocorrect RuboCop offenses (only when it's safe)."
92
91
  task(:autocorrect, *args) do |_, task_args|
@@ -237,10 +237,12 @@ module RuboCop
237
237
  # when traversing the relative paths with symlinks.
238
238
  exe_root = File.absolute_path(exe_root)
239
239
 
240
- # These are all the files we have `require`d plus everything in the
241
- # exe directory. A change to any of them could affect the cop output
242
- # so we include them in the cache hash.
243
- source_files = $LOADED_FEATURES + Find.find(exe_root).to_a
240
+ # These are all the files we have `require`d, all of RuboCop's own files whether loaded
241
+ # or not (cops are loaded lazily, so which of them are in `$LOADED_FEATURES` varies from
242
+ # run to run), plus everything in the exe directory. A change to any of them could affect
243
+ # the cop output so we include them in the cache hash.
244
+ rubocop_lib_files = Dir[File.join(File.absolute_path(lib_root), 'rubocop', '**', '*.rb')]
245
+ source_files = $LOADED_FEATURES | rubocop_lib_files | Find.find(exe_root).to_a
244
246
  source_files -= ResultCache.rubocop_required_features # Rely on gem versions
245
247
 
246
248
  source_files
@@ -117,7 +117,7 @@ module RuboCop
117
117
  source
118
118
  end
119
119
 
120
- # rubocop:disable Metrics/AbcSize
120
+ # rubocop:disable-next Metrics/AbcSize
121
121
  def expect_offense(source, file = nil, severity: nil, chomp: false, **replacements)
122
122
  expected_annotations = parse_annotations(source, **replacements)
123
123
  source = expected_annotations.plain_source
@@ -138,9 +138,8 @@ module RuboCop
138
138
 
139
139
  @offenses
140
140
  end
141
- # rubocop:enable Metrics/AbcSize
142
141
 
143
- # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity
142
+ # rubocop:disable-next Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity
144
143
  def expect_correction(correction, loop: true, source: nil)
145
144
  if source
146
145
  expected_annotations = parse_annotations(source, raise_error: false)
@@ -177,7 +176,6 @@ module RuboCop
177
176
  expect(new_source).to eq(correction)
178
177
  expect(@processed_source).to be_valid_syntax, 'Expected correction to be valid syntax'
179
178
  end
180
- # rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity
181
179
 
182
180
  def expect_no_corrections
183
181
  raise '`expect_no_corrections` must follow `expect_offense`' unless @processed_source
@@ -6,11 +6,14 @@ require 'tmpdir'
6
6
  # or stubbing Dir.pwd get a fresh value.
7
7
  RSpec.configure { |c| c.before { RuboCop::PathUtil.reset_pwd } }
8
8
 
9
- RSpec.shared_context 'isolated environment' do # rubocop:disable Metrics/BlockLength
10
- around do |example| # rubocop:disable Metrics/BlockLength
9
+ # rubocop:disable-next Metrics/BlockLength
10
+ RSpec.shared_context 'isolated environment' do
11
+ around do |example|
11
12
  Dir.mktmpdir do |tmpdir|
12
13
  original_home = Dir.home
13
14
  original_xdg_config_home = ENV.fetch('XDG_CONFIG_HOME', nil)
15
+ original_xdg_cache_home = ENV.fetch('XDG_CACHE_HOME', nil)
16
+ original_rubocop_cache_root = ENV.fetch('RUBOCOP_CACHE_ROOT', nil)
14
17
 
15
18
  # Make sure to expand all symlinks in the path first. Otherwise we may
16
19
  # get mismatched pathnames when loading config files later on.
@@ -23,6 +26,12 @@ RSpec.shared_context 'isolated environment' do # rubocop:disable Metrics/BlockLe
23
26
  ENV['HOME'] = virtual_home
24
27
  ENV.delete('XDG_CONFIG_HOME')
25
28
 
29
+ # `XDG_CACHE_HOME` and `RUBOCOP_CACHE_ROOT` are set on some CI runners and
30
+ # would otherwise point the result cache at a fixed directory shared across examples
31
+ # and parallel test-queue workers, letting one example's cache leak into another.
32
+ ENV.delete('XDG_CACHE_HOME')
33
+ ENV.delete('RUBOCOP_CACHE_ROOT')
34
+
26
35
  base_dir = example.metadata[:project_inside_home] ? virtual_home : tmpdir
27
36
  root = example.metadata[:root]
28
37
  working_dir = root ? File.join(base_dir, 'work', root) : File.join(base_dir, 'work')
@@ -38,6 +47,8 @@ RSpec.shared_context 'isolated environment' do # rubocop:disable Metrics/BlockLe
38
47
  ensure
39
48
  ENV['HOME'] = original_home
40
49
  ENV['XDG_CONFIG_HOME'] = original_xdg_config_home
50
+ ENV['XDG_CACHE_HOME'] = original_xdg_cache_home
51
+ ENV['RUBOCOP_CACHE_ROOT'] = original_rubocop_cache_root
41
52
 
42
53
  RuboCop::ResultCache.reset_config_cache
43
54
  RuboCop::ConfigLoader.clear_options # This also resets RuboCop::FileFinder.root_level
@@ -124,6 +135,8 @@ RSpec.shared_context 'config' do # rubocop:disable Metrics/BlockLength
124
135
 
125
136
  let(:gem_versions) { {} }
126
137
 
138
+ let(:path_sourced_gems) { [] }
139
+
127
140
  ### Utilities
128
141
 
129
142
  def source_range(range, buffer: source_buffer)
@@ -168,6 +181,7 @@ RSpec.shared_context 'config' do # rubocop:disable Metrics/BlockLength
168
181
  **gem_versions.transform_values { |value| Gem::Version.new(value) }
169
182
  }
170
183
  )
184
+ allow(config).to receive(:path_sourced_gems_in_target).and_return(path_sourced_gems)
171
185
 
172
186
  config
173
187
  end
@@ -67,12 +67,14 @@ module RuboCop
67
67
  end
68
68
 
69
69
  def run(paths)
70
+ @inspection_team_by_config = {}.compare_by_identity
71
+
70
72
  # Compute the cache source checksum once to avoid potential
71
73
  # inconsistencies between workers.
72
74
  ResultCache.source_checksum
73
75
 
74
76
  target_files = find_target_files(paths)
75
- @project_index = ProjectIndexLoader.build_index(target_files) if project_index_enabled?
77
+ build_project_index(target_files)
76
78
 
77
79
  if @options[:list_target_files]
78
80
  list_files(target_files)
@@ -104,6 +106,34 @@ module RuboCop
104
106
  target_files.each(&:freeze).freeze
105
107
  end
106
108
 
109
+ def build_project_index(target_files)
110
+ return unless project_index_enabled?
111
+
112
+ @project_index = ProjectIndexLoader.build_index(project_index_files(target_files))
113
+ end
114
+
115
+ # The index always covers the whole project, not just the files being
116
+ # inspected: cross-file offenses must not depend on which files a
117
+ # particular run happens to include, or single-file runs (editors, CI
118
+ # sharding) would see different offenses than full runs. The project is
119
+ # rooted at the directory of the configuration that enabled the index.
120
+ def project_index_files(target_files)
121
+ root = @config_store.for_pwd.base_dir_for_path_parameters
122
+ project_files = begin
123
+ find_target_files([root]) | target_files
124
+ rescue Errno::ENOENT
125
+ target_files
126
+ end
127
+
128
+ (project_files | bundled_gem_files).sort
129
+ end
130
+
131
+ def bundled_gem_files
132
+ return [] unless @config_store.for_pwd.for_all_cops['ProjectIndexIncludesGems']
133
+
134
+ ProjectIndexLoader.bundled_gem_source_files
135
+ end
136
+
107
137
  def project_index_enabled?
108
138
  return false unless @config_store.for_pwd.for_all_cops['UseProjectIndex']
109
139
 
@@ -236,7 +266,8 @@ module RuboCop
236
266
  file_offense_cache(file) do
237
267
  source, offenses = do_inspection_loop(file)
238
268
  offenses = add_redundant_disables(file, offenses.compact.sort, source)
239
- offenses.sort.reject(&:disabled?).freeze
269
+ offenses = offenses.reject(&:disabled?) unless @options[:display_suppressed]
270
+ offenses.sort.freeze
240
271
  end
241
272
  end
242
273
 
@@ -293,7 +324,13 @@ module RuboCop
293
324
  end
294
325
 
295
326
  def check_for_redundant_disables?(source)
296
- return false if source.disabled_line_ranges.empty? || except_redundant_cop_disable_directive?
327
+ return false if except_redundant_cop_disable_directive?
328
+ # Detached `disable-next` directives produce no disabled ranges but must
329
+ # still be reported as redundant.
330
+ if source.disabled_line_ranges.empty? &&
331
+ source.comment_config.detached_next_directives.empty?
332
+ return false
333
+ end
297
334
 
298
335
  !@options[:only]
299
336
  end
@@ -468,38 +505,35 @@ module RuboCop
468
505
 
469
506
  def mobilize_team(processed_source)
470
507
  config = @config_store.for_file(processed_source.path)
471
- team = Cop::Team.mobilize(mobilized_cop_classes(config), config, @options)
472
-
473
- if @project_index
474
- team.cops.each do |cop|
475
- cop.project_index = @project_index
476
- end
477
- end
478
-
479
- team
508
+ @inspection_team_by_config ||= {}.compare_by_identity
509
+ @inspection_team_by_config[config] ||= assemble_team(config)
480
510
  end
481
511
 
482
- def mobilized_cop_classes(config) # rubocop:disable Metrics/AbcSize
512
+ def mobilized_cop_classes(config)
483
513
  @mobilized_cop_classes ||= {}.compare_by_identity
484
514
  @mobilized_cop_classes[config] ||= begin
485
- cop_classes = Cop::Registry.all
486
-
487
515
  # `@options[:only]` and `@options[:except]` are not qualified until
488
- # needed so that the Registry can be fully loaded, including any
489
- # cops added by `require`s.
516
+ # needed so that the registry contains any cops added by `require`s,
517
+ # which register eagerly when their files are loaded.
490
518
  qualify_option_cop_names
491
519
 
492
520
  OptionsValidator.new(@options).validate_cop_options
493
521
 
494
- if @options[:only]
495
- cop_classes.select! { |c| c.match?(@options[:only]) }
496
- else
497
- filter_cop_classes(cop_classes, config)
498
- end
522
+ # Filtering by badge keeps lazy-loaded cops unloaded; only the cops
523
+ # that survive the filter and are enabled will be loaded.
524
+ Cop::Registry.global.filter_by_badge(@options) { |badge| mobilize_cop_badge?(badge, config) }
525
+ end
526
+ end
499
527
 
500
- cop_classes.reject! { |c| c.match?(@options[:except]) }
528
+ def mobilize_cop_badge?(badge, config)
529
+ return false if badge.department == :Test
530
+ return false if badge.match_name?(@options[:except])
501
531
 
502
- Cop::Registry.new(cop_classes, @options)
532
+ if @options[:only]
533
+ badge.match_name?(@options[:only])
534
+ else
535
+ # use only cops that link to a style guide if requested.
536
+ !style_guide_cops_only?(config) || config.for_cop(badge.to_s)['StyleGuide']
503
537
  end
504
538
  end
505
539
 
@@ -513,13 +547,6 @@ module RuboCop
513
547
  end
514
548
  end
515
549
 
516
- def filter_cop_classes(cop_classes, config)
517
- # use only cops that link to a style guide if requested
518
- return unless style_guide_cops_only?(config)
519
-
520
- cop_classes.select! { |cop| config.for_cop(cop)['StyleGuide'] }
521
- end
522
-
523
550
  def style_guide_cops_only?(config)
524
551
  @options[:only_guide_cops] || config.for_all_cops['StyleGuideCopsOnly']
525
552
  end
@@ -584,7 +611,7 @@ module RuboCop
584
611
  end
585
612
  end
586
613
 
587
- # rubocop:disable Metrics/MethodLength
614
+ # rubocop:disable-next Metrics/MethodLength
588
615
  def get_processed_source(file, prism_result, source: nil)
589
616
  config = @config_store.for_file(file)
590
617
  ruby_version = config.target_ruby_version
@@ -618,25 +645,26 @@ module RuboCop
618
645
  processed_source.registry = mobilized_cop_classes(config)
619
646
  processed_source
620
647
  end
621
- # rubocop:enable Metrics/MethodLength
622
648
 
623
649
  # A Cop::Team instance is stateful and may change when inspecting.
624
650
  # The "standby" team for a given config is an initialized but
625
651
  # otherwise dormant team that can be used for config- and option-
626
652
  # level caching in ResultCache.
627
653
  def standby_team(config)
628
- @team_by_config ||= {}.compare_by_identity
629
- @team_by_config[config] ||= begin
630
- team = Cop::Team.mobilize(mobilized_cop_classes(config), config, @options)
631
-
632
- if @project_index
633
- team.cops.each do |cop|
634
- cop.project_index = @project_index
635
- end
636
- end
654
+ @standby_team_by_config ||= {}.compare_by_identity
655
+ @standby_team_by_config[config] ||= assemble_team(config)
656
+ end
637
657
 
638
- team
658
+ def assemble_team(config)
659
+ team = Cop::Team.mobilize(mobilized_cop_classes(config), config, @options)
660
+
661
+ if @project_index
662
+ team.cops.each do |cop|
663
+ cop.project_index = @project_index
664
+ end
639
665
  end
666
+
667
+ team
640
668
  end
641
669
  end
642
670
  end
@@ -45,7 +45,7 @@ module RuboCop
45
45
  @project_dir_cache_key ||= project_dir[1..].tr('/', '+')
46
46
  end
47
47
 
48
- # rubocop:disable Metrics/AbcSize
48
+ # rubocop:disable-next Metrics/AbcSize
49
49
  def restart_key(args_config_file_path: nil)
50
50
  lockfile_path = LOCKFILE_NAMES.map do |lockfile_name|
51
51
  Pathname(project_dir).join(lockfile_name)
@@ -61,14 +61,17 @@ module RuboCop
61
61
 
62
62
  Digest::SHA1.hexdigest(version_data + config_data + inherit_from_data + require_data)
63
63
  end
64
- # rubocop:enable Metrics/AbcSize
65
64
 
66
65
  def dir
67
- Pathname.new(File.join(cache_path, project_dir_cache_key)).tap do |d|
66
+ dir_path.tap do |d|
68
67
  d.mkpath unless d.exist?
69
68
  end
70
69
  end
71
70
 
71
+ def dir_path
72
+ Pathname.new(File.join(cache_path, project_dir_cache_key))
73
+ end
74
+
72
75
  def cache_path
73
76
  cache_root_dir = if cache_root_path
74
77
  File.join(cache_root_path, 'rubocop_cache')
@@ -87,8 +90,8 @@ module RuboCop
87
90
  dir.join('token')
88
91
  end
89
92
 
90
- def pid_path
91
- dir.join('pid')
93
+ def pid_path(create_dir: true)
94
+ (create_dir ? dir : dir_path).join('pid')
92
95
  end
93
96
 
94
97
  def lock_path
@@ -108,8 +111,9 @@ module RuboCop
108
111
  end
109
112
 
110
113
  def pid_running?
111
- Process.kill(0, pid_path.read.to_i) == 1
112
- rescue Errno::ESRCH, Errno::ENOENT, Errno::EACCES, Errno::EROFS, Errno::ENAMETOOLONG
114
+ Process.kill(0, pid_path(create_dir: false).read.to_i) == 1
115
+ rescue Errno::ESRCH, Errno::ENOENT, Errno::EACCES, Errno::EROFS, Errno::ENAMETOOLONG,
116
+ Errno::ENOTDIR
113
117
  false
114
118
  end
115
119
 
@@ -55,7 +55,7 @@ module RuboCop
55
55
 
56
56
  private
57
57
 
58
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
58
+ # rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
59
59
  def process_arguments(argv)
60
60
  server_arguments = delete_server_argument_from(argv)
61
61
 
@@ -84,9 +84,8 @@ module RuboCop
84
84
 
85
85
  STATUS_SUCCESS
86
86
  end
87
- # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
88
87
 
89
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
88
+ # rubocop:disable-next Metrics/CyclomaticComplexity, Metrics/MethodLength
90
89
  def run_command(server_command, detach:)
91
90
  case server_command
92
91
  when '--server'
@@ -107,7 +106,6 @@ module RuboCop
107
106
  Server::ClientCommand::Status.new.run
108
107
  end
109
108
  end
110
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength
111
109
 
112
110
  def fetch_cache_root_path_from(arguments)
113
111
  cache_root = arguments.detect { |argument| argument.start_with?('--cache-root') }
@@ -17,6 +17,11 @@ module RuboCop
17
17
  module Server
18
18
  TIMEOUT = 20
19
19
 
20
+ # Options that start a long-lived protocol server on stdio in the current process.
21
+ # They must never be forwarded to the server process, which would run them as
22
+ # a lint request and silently produce no protocol response.
23
+ IN_PROCESS_PROTOCOL_OPTIONS = %w[--lsp --mcp].freeze
24
+
20
25
  autoload :CLI, 'rubocop/server/cli'
21
26
  autoload :Cache, 'rubocop/server/cache'
22
27
  autoload :ClientCommand, 'rubocop/server/client_command'
@@ -36,6 +41,15 @@ module RuboCop
36
41
  Cache.pid_running?
37
42
  end
38
43
 
44
+ # Whether the invocation should be forwarded to the running server process.
45
+ # `--lsp` and `--mcp` always run in the current process even when the server is running:
46
+ # they serve a protocol on stdio, which the server's `exec` command cannot do.
47
+ def forward_to_server?(argv = ARGV)
48
+ return false unless running?
49
+
50
+ (IN_PROCESS_PROTOCOL_OPTIONS & argv).empty?
51
+ end
52
+
39
53
  def wait_for_running_status!(expected)
40
54
  start_time = Time.now
41
55
  while Server.running? != expected
@@ -21,7 +21,10 @@ module RuboCop
21
21
 
22
22
  args.uniq.each do |arg|
23
23
  files += if File.directory?(arg)
24
- target_files_in_dir(arg.chomp(File::SEPARATOR))
24
+ # Expand the directory so that the traversal can prune directories the
25
+ # config excludes - the exclude patterns are absolute paths, and glued
26
+ # onto a relative base (`rubocop .`) they never match anything.
27
+ target_files_in_dir(File.expand_path(arg.chomp(File::SEPARATOR)))
25
28
  else
26
29
  process_explicit_path(arg, mode)
27
30
  end
@@ -212,7 +212,7 @@ module RuboCop
212
212
  end
213
213
 
214
214
  def pattern
215
- /^ruby = "(?<version>\d+\.\d+)/.freeze
215
+ /^ruby = ["'](?<version>\d+\.\d+)/.freeze
216
216
  end
217
217
  end
218
218