rubocop 1.86.0 → 1.86.2

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 (64) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +14 -3
  3. data/lib/rubocop/cli/command/auto_generate_config.rb +27 -1
  4. data/lib/rubocop/cli/command/list_enabled_cops_for.rb +40 -0
  5. data/lib/rubocop/cli/command/show_docs_url.rb +3 -7
  6. data/lib/rubocop/cli/command/suggest_extensions.rb +1 -1
  7. data/lib/rubocop/cli.rb +4 -7
  8. data/lib/rubocop/comment_config.rb +12 -15
  9. data/lib/rubocop/config.rb +13 -9
  10. data/lib/rubocop/config_loader_resolver.rb +2 -1
  11. data/lib/rubocop/cop/autocorrect_logic.rb +2 -1
  12. data/lib/rubocop/cop/correctors/multiline_literal_brace_corrector.rb +1 -5
  13. data/lib/rubocop/cop/correctors.rb +28 -0
  14. data/lib/rubocop/cop/documentation.rb +2 -3
  15. data/lib/rubocop/cop/exclude_limit.rb +31 -5
  16. data/lib/rubocop/cop/gemspec/require_mfa.rb +3 -3
  17. data/lib/rubocop/cop/internal_affairs/location_line_equality_comparison.rb +1 -0
  18. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +1 -1
  19. data/lib/rubocop/cop/layout/end_alignment.rb +4 -2
  20. data/lib/rubocop/cop/layout/line_length.rb +5 -3
  21. data/lib/rubocop/cop/layout/multiline_method_call_brace_layout.rb +1 -1
  22. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +26 -1
  23. data/lib/rubocop/cop/lint/duplicate_methods.rb +10 -5
  24. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +3 -13
  25. data/lib/rubocop/cop/lint/require_relative_self_path.rb +2 -0
  26. data/lib/rubocop/cop/lint/syntax.rb +25 -1
  27. data/lib/rubocop/cop/lint/unused_method_argument.rb +10 -0
  28. data/lib/rubocop/cop/lint/useless_assignment.rb +3 -8
  29. data/lib/rubocop/cop/lint/utils/nil_receiver_checker.rb +33 -8
  30. data/lib/rubocop/cop/mixin/configurable_max.rb +6 -5
  31. data/lib/rubocop/cop/mixin.rb +85 -0
  32. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +1 -1
  33. data/lib/rubocop/cop/offense.rb +8 -0
  34. data/lib/rubocop/cop/registry.rb +19 -24
  35. data/lib/rubocop/cop/style/access_modifier_declarations.rb +14 -2
  36. data/lib/rubocop/cop/style/copyright.rb +21 -10
  37. data/lib/rubocop/cop/style/date_time.rb +2 -2
  38. data/lib/rubocop/cop/style/document_dynamic_eval_definition.rb +6 -1
  39. data/lib/rubocop/cop/style/guard_clause.rb +9 -6
  40. data/lib/rubocop/cop/style/hash_lookup_method.rb +12 -7
  41. data/lib/rubocop/cop/style/if_inside_else.rb +16 -7
  42. data/lib/rubocop/cop/style/module_member_existence_check.rb +7 -14
  43. data/lib/rubocop/cop/style/one_class_per_file.rb +24 -4
  44. data/lib/rubocop/cop/style/reduce_to_hash.rb +16 -0
  45. data/lib/rubocop/cop/style/redundant_line_continuation.rb +16 -0
  46. data/lib/rubocop/cop/style/redundant_parentheses.rb +4 -1
  47. data/lib/rubocop/cop/style/redundant_self.rb +2 -2
  48. data/lib/rubocop/cop/style/redundant_struct_keyword_init.rb +10 -0
  49. data/lib/rubocop/cop/style/regexp_literal.rb +29 -0
  50. data/lib/rubocop/cop/style/sole_nested_conditional.rb +4 -2
  51. data/lib/rubocop/cop/style/symbol_proc.rb +3 -3
  52. data/lib/rubocop/cop/style/while_until_modifier.rb +16 -0
  53. data/lib/rubocop/cop/team.rb +86 -35
  54. data/lib/rubocop/formatter/disabled_config_formatter.rb +4 -1
  55. data/lib/rubocop/lsp/runtime.rb +1 -2
  56. data/lib/rubocop/mcp/server.rb +2 -0
  57. data/lib/rubocop/options.rb +8 -4
  58. data/lib/rubocop/rspec/cop_helper.rb +8 -0
  59. data/lib/rubocop/rspec/shared_contexts.rb +21 -0
  60. data/lib/rubocop/runner.rb +77 -55
  61. data/lib/rubocop/target_finder.rb +13 -6
  62. data/lib/rubocop/version.rb +1 -1
  63. data/lib/rubocop.rb +7 -96
  64. metadata +7 -4
@@ -13,6 +13,12 @@ module RuboCop
13
13
  # array, while `method_defined?` will do direct method lookup, which is much
14
14
  # faster and consumes less memory.
15
15
  #
16
+ # NOTE: `constants.include?` is not handled by this cop because
17
+ # `Module#const_defined?` has different lookup behavior than
18
+ # `Module#constants` - `const_defined?` searches up to `Object`
19
+ # (top-level constants like `String`, `Integer`, etc.) while
20
+ # `constants` does not, which can cause behavior changes after autocorrection.
21
+ #
16
22
  # @example
17
23
  # # bad
18
24
  # Array.instance_methods.include?(:size)
@@ -28,27 +34,17 @@ module RuboCop
28
34
  #
29
35
  # # bad
30
36
  # Array.class_variables.include?(:foo)
31
- # Array.constants.include?(:foo)
32
37
  # Array.private_instance_methods.include?(:foo)
33
38
  # Array.protected_instance_methods.include?(:foo)
34
39
  # Array.public_instance_methods.include?(:foo)
35
- # Array.included_modules.include?(:foo)
36
40
  #
37
41
  # # good
38
42
  # Array.class_variable_defined?(:foo)
39
- # Array.const_defined?(:foo)
40
43
  # Array.private_method_defined?(:foo)
41
44
  # Array.protected_method_defined?(:foo)
42
45
  # Array.public_method_defined?(:foo)
43
- # Array.include?(:foo)
44
- #
45
- # @example AllowedMethods: [included_modules]
46
- #
47
- # # good
48
- # Array.included_modules.include?(:foo)
49
46
  #
50
47
  class ModuleMemberExistenceCheck < Base
51
- include AllowedMethods
52
48
  extend AutoCorrector
53
49
 
54
50
  MSG = 'Use `%<replacement>s` instead.'
@@ -63,15 +59,13 @@ module RuboCop
63
59
 
64
60
  METHOD_REPLACEMENTS = {
65
61
  class_variables: :class_variable_defined?,
66
- constants: :const_defined?,
67
- included_modules: :include?,
68
62
  instance_methods: :method_defined?,
69
63
  private_instance_methods: :private_method_defined?,
70
64
  protected_instance_methods: :protected_method_defined?,
71
65
  public_instance_methods: :public_method_defined?
72
66
  }.freeze
73
67
 
74
- METHODS_WITHOUT_INHERIT_PARAM = Set[:class_variables, :included_modules].freeze
68
+ METHODS_WITHOUT_INHERIT_PARAM = Set[:class_variables].freeze
75
69
  METHODS_WITH_INHERIT_PARAM =
76
70
  (METHOD_REPLACEMENTS.keys.to_set - METHODS_WITHOUT_INHERIT_PARAM).freeze
77
71
 
@@ -81,7 +75,6 @@ module RuboCop
81
75
  return unless (parent = node.parent)
82
76
  return unless module_member_inclusion?(parent)
83
77
  return unless simple_method_argument?(node) && simple_method_argument?(parent)
84
- return if allowed_method?(node.method_name)
85
78
 
86
79
  offense_range = node.location.selector.join(parent.source_range.end)
87
80
  replacement = replacement_for(node, parent)
@@ -13,30 +13,50 @@ module RuboCop
13
13
  # classes that logically belong with the main class.
14
14
  #
15
15
  # @example
16
- # # bad
16
+ # # bad - Multiple top-level classes
17
17
  # class Foo
18
18
  # end
19
19
  #
20
20
  # class Bar
21
21
  # end
22
22
  #
23
- # # bad
23
+ # # bad - Multiple top-level modules
24
+ # module Foo
25
+ # end
26
+ #
27
+ # module Bar
28
+ # end
29
+ #
30
+ # # bad - A top-level class and a top-level module
24
31
  # class Foo
25
32
  # end
26
33
  #
27
34
  # module Bar
28
35
  # end
29
36
  #
30
- # # good
37
+ # # good - A single top-level class
31
38
  # class Foo
32
39
  # end
33
40
  #
34
- # # good
41
+ # # good - A single top-level module
42
+ # module Foo
43
+ # end
44
+ #
45
+ # # good - Nested classes within a single top-level class
35
46
  # class Foo
36
47
  # class Bar
37
48
  # end
38
49
  # end
39
50
  #
51
+ # # good - Multiple classes within a single top-level module
52
+ # module Foo
53
+ # class Bar
54
+ # end
55
+ #
56
+ # class Baz
57
+ # end
58
+ # end
59
+ #
40
60
  # @example AllowedClasses: ['AllowedClass']
41
61
  # # good
42
62
  # class Foo
@@ -99,6 +99,7 @@ module RuboCop
99
99
  end
100
100
  return unless key
101
101
  return if accumulator_used_in_expressions?(block_node, key, value)
102
+ return if nested_match?(key) || nested_match?(value)
102
103
 
103
104
  register_offense(node, block_node, key, value)
104
105
  end
@@ -108,6 +109,21 @@ module RuboCop
108
109
  references_variable?(key, acc_name) || references_variable?(value, acc_name)
109
110
  end
110
111
 
112
+ def nested_match?(node)
113
+ node.each_node(:call).any? do |send_node|
114
+ next false unless RESTRICT_ON_SEND.include?(send_node.method_name)
115
+
116
+ inner_block = send_node.block_node
117
+ next false unless inner_block
118
+
119
+ if send_node.method?(:each_with_object)
120
+ each_with_object_to_hash?(inner_block)
121
+ else
122
+ inject_to_hash?(inner_block)
123
+ end
124
+ end
125
+ end
126
+
111
127
  def accumulator_name(block_node)
112
128
  index = block_node.method?(:each_with_object) ? 1 : 0
113
129
  block_node.argument_list[index].name
@@ -82,6 +82,7 @@ module RuboCop
82
82
  tIDENTIFIER kBREAK kNEXT kRETURN kSUPER kYIELD
83
83
  ].freeze
84
84
  ARITHMETIC_OPERATOR_TOKENS = %i[tDIVIDE tDSTAR tMINUS tPERCENT tPLUS tSTAR2].freeze
85
+ STRING_LITERAL_BEGIN_TOKENS = %i[tSTRING_BEG tXSTRING_BEG tREGEXP_BEG tSYMBEG].freeze
85
86
 
86
87
  def on_new_investigation
87
88
  return unless processed_source.ast
@@ -105,6 +106,7 @@ module RuboCop
105
106
  string_concatenation?(range.source_line) ||
106
107
  start_with_arithmetic_operator?(range) ||
107
108
  inside_string_literal_or_method_with_argument?(range) ||
109
+ inside_string_literal_with_interpolation?(range) ||
108
110
  leading_dot_method_chain_with_blank_line?(range)
109
111
  end
110
112
 
@@ -132,6 +134,20 @@ module RuboCop
132
134
  end
133
135
  end
134
136
 
137
+ def inside_string_literal_with_interpolation?(range)
138
+ string_depth = 0
139
+ processed_source.tokens.each do |token|
140
+ break if token.pos.begin_pos >= range.begin_pos
141
+
142
+ if STRING_LITERAL_BEGIN_TOKENS.include?(token.type)
143
+ string_depth += 1
144
+ elsif token.type == :tSTRING_END
145
+ string_depth -= 1
146
+ end
147
+ end
148
+ string_depth.positive?
149
+ end
150
+
135
151
  def leading_dot_method_chain_with_blank_line?(range)
136
152
  return false unless range.source_line.strip.start_with?('.', '&.')
137
153
 
@@ -270,14 +270,17 @@ module RuboCop
270
270
  end
271
271
  end
272
272
 
273
+ # rubocop:disable Metrics/CyclomaticComplexity
273
274
  def body_range?(begin_node, node)
275
+ return false if begin_node.chained?
274
276
  return false unless node.range_type?
275
277
  return false unless (parent = begin_node.parent)
276
- return false if parent.pair_type?
278
+ return false unless parent.begin_type?
277
279
 
278
280
  (node.begin.nil? && begin_node == parent.children.first) ||
279
281
  (node.end.nil? && begin_node == parent.children.last)
280
282
  end
283
+ # rubocop:enable Metrics/CyclomaticComplexity
281
284
 
282
285
  def disallowed_one_line_pattern_matching?(begin_node, node)
283
286
  if (parent = begin_node.parent)
@@ -59,7 +59,7 @@ module RuboCop
59
59
 
60
60
  def initialize(config = nil, options = nil)
61
61
  super
62
- @allowed_send_nodes = []
62
+ @allowed_send_nodes = Set.new.compare_by_identity
63
63
  @local_variables_scopes = Hash.new { |hash, key| hash[key] = [] }.compare_by_identity
64
64
  end
65
65
 
@@ -187,7 +187,7 @@ module RuboCop
187
187
  def allow_self(node)
188
188
  return unless node.send_type? && node.self_receiver?
189
189
 
190
- @allowed_send_nodes << node
190
+ @allowed_send_nodes.add(node)
191
191
  end
192
192
 
193
193
  def add_lhs_to_local_variables_scopes(rhs, lhs)
@@ -9,6 +9,16 @@ module RuboCop
9
9
  # Therefore, this cop detects and autocorrects redundant `keyword_init: nil`
10
10
  # and `keyword_init: true` in `Struct.new`.
11
11
  #
12
+ # This cop is disabled by default because `keyword_init: true` is not purely
13
+ # redundant. It changes behavior in the following ways:
14
+ #
15
+ # - `Struct#keyword_init?` returns `true` instead of `nil`.
16
+ # - A `Struct` with `keyword_init: true` accepts a `Hash` argument and
17
+ # expands it as keyword arguments, whereas without it the `Hash` is
18
+ # treated as a positional argument.
19
+ # - `keyword_init: true` raises an `ArgumentError` for positional arguments,
20
+ # enforcing keyword-only initialization.
21
+ #
12
22
  # @safety
13
23
  # This autocorrect is unsafe because when the value of `keyword_init` changes
14
24
  # from `true` to `nil`, the return value of `Struct#keyword_init?` changes.
@@ -98,7 +98,16 @@ module RuboCop
98
98
  MSG_USE_SLASHES = 'Use `//` around regular expression.'
99
99
  MSG_USE_PERCENT_R = 'Use `%r` around regular expression.'
100
100
 
101
+ PAIR_DELIMITER_PATTERNS = {
102
+ ['(', ')'] => /\\.|[()]/,
103
+ ['[', ']'] => /\\.|[\[\]]/,
104
+ ['{', '}'] => /\\.|[{}]/,
105
+ ['<', '>'] => /\\.|[<>]/
106
+ }.freeze
107
+
101
108
  def on_regexp(node)
109
+ return if slash_literal?(node) && percent_r_delimiters_conflict?(node)
110
+
102
111
  message = if slash_literal?(node)
103
112
  MSG_USE_PERCENT_R unless allowed_slash_literal?(node)
104
113
  else
@@ -115,6 +124,26 @@ module RuboCop
115
124
 
116
125
  private
117
126
 
127
+ def percent_r_delimiters_conflict?(node)
128
+ opening, closing = preferred_delimiters
129
+ return false unless (pattern = PAIR_DELIMITER_PATTERNS[[opening, closing]])
130
+
131
+ !balanced_delimiters?(node_body(node), opening, closing, pattern)
132
+ end
133
+
134
+ def balanced_delimiters?(text, opening, closing, pattern)
135
+ depth = 0
136
+ text.scan(pattern) do |match|
137
+ if match == opening
138
+ depth += 1
139
+ elsif match == closing
140
+ depth -= 1
141
+ return false if depth.negative?
142
+ end
143
+ end
144
+ depth.zero?
145
+ end
146
+
118
147
  def allowed_slash_literal?(node)
119
148
  (style == :slashes && !contains_disallowed_slash?(node)) || allowed_mixed_slash?(node)
120
149
  end
@@ -65,7 +65,10 @@ module RuboCop
65
65
 
66
66
  message = format(MSG, conditional_type: node.keyword)
67
67
  add_offense(if_branch.loc.keyword, message: message) do |corrector|
68
+ next if ignored_node?(node)
69
+
68
70
  autocorrect(corrector, node, if_branch)
71
+ ignore_node(if_branch)
69
72
  end
70
73
  end
71
74
 
@@ -115,9 +118,8 @@ module RuboCop
115
118
  end
116
119
 
117
120
  def correct_node(corrector, node)
118
- corrector.replace(node.loc.keyword, 'if') if node.unless? && !part_of_ignored_node?(node)
121
+ corrector.replace(node.loc.keyword, 'if') if node.unless?
119
122
  corrector.replace(node.condition, chainable_condition(node))
120
- ignore_node(node)
121
123
  end
122
124
 
123
125
  def correct_for_guard_condition_style(corrector, node, if_branch)
@@ -260,10 +260,10 @@ module RuboCop
260
260
  end
261
261
 
262
262
  def begin_pos_for_replacement(node)
263
- expr = node.send_node.source_range
263
+ send_node = node.send_node
264
264
 
265
- if (paren_pos = (expr.source =~ /\(\s*\)$/))
266
- expr.begin_pos + paren_pos
265
+ if send_node.parenthesized? && send_node.arguments.empty?
266
+ send_node.loc.begin.begin_pos
267
267
  else
268
268
  node.loc.begin.begin_pos
269
269
  end
@@ -16,6 +16,11 @@ module RuboCop
16
16
  # # good
17
17
  # x += 1 while x < 10
18
18
  #
19
+ # # good
20
+ # while x < 10
21
+ # y += 1 if x.odd?
22
+ # end
23
+ #
19
24
  # # bad
20
25
  # until x > 10
21
26
  # x += 1
@@ -24,6 +29,11 @@ module RuboCop
24
29
  # # good
25
30
  # x += 1 until x > 10
26
31
  #
32
+ # # good
33
+ # until x > 10
34
+ # y += 1 unless x.even?
35
+ # end
36
+ #
27
37
  # # bad
28
38
  # x += 100 while x < 500 # a long comment that makes code too long if it were a single line
29
39
  #
@@ -45,6 +55,12 @@ module RuboCop
45
55
  end
46
56
  end
47
57
  alias on_until on_while
58
+
59
+ private
60
+
61
+ def non_eligible_body?(body)
62
+ body&.conditional? || super
63
+ end
48
64
  end
49
65
  end
50
66
  end
@@ -11,6 +11,9 @@ module RuboCop
11
11
  # (unless autocorrections happened).
12
12
  # rubocop:disable Metrics/ClassLength
13
13
  class Team
14
+ InvestigationResult = Struct.new(:report, :corrector)
15
+ private_constant :InvestigationResult
16
+
14
17
  # @return [Team]
15
18
  def self.new(cop_or_classes, config, options = {})
16
19
  # Support v0 api:
@@ -89,31 +92,25 @@ module RuboCop
89
92
 
90
93
  # @return [Commissioner::InvestigationReport]
91
94
  def investigate(processed_source, offset: 0, original: processed_source)
92
- be_ready
93
-
94
- # The autocorrection process may have to be repeated multiple times
95
- # until there are no corrections left to perform
96
- # To speed things up, run autocorrecting cops by themselves, and only
97
- # run the other cops when no corrections are left
98
- on_duty = roundup_relevant_cops(processed_source)
95
+ result = investigate_with_corrector(processed_source, offset: offset, original: original)
96
+ autocorrect(processed_source, result.corrector)
97
+ result.report
98
+ end
99
99
 
100
- autocorrect_cops, other_cops = on_duty.partition(&:autocorrect?)
101
- report = investigate_partial(autocorrect_cops, processed_source,
102
- offset: offset, original: original)
100
+ # @return [Array<Offense>]
101
+ def investigate_fragments(fragments, original:)
102
+ @updated_source_file = false
103
103
 
104
- unless autocorrect(processed_source, report, offset: offset, original: original)
105
- # If we corrected some errors, another round of inspection will be
106
- # done, and any other offenses will be caught then, so only need
107
- # to check other_cops if no correction was done
108
- report = report.merge(investigate_partial(other_cops, processed_source,
109
- offset: offset, original: original))
110
- end
104
+ offenses, errors, warnings, corrector =
105
+ fragments.each_with_object([[], [], [], nil]) do |fragment, data|
106
+ investigate_fragment(fragment, original, data)
107
+ end
111
108
 
112
- process_errors(processed_source.path, report.errors)
109
+ autocorrect(original, corrector)
110
+ @errors = errors
111
+ @warnings = warnings
113
112
 
114
- report
115
- ensure
116
- @ready = false
113
+ offenses
117
114
  end
118
115
 
119
116
  # @deprecated
@@ -136,14 +133,13 @@ module RuboCop
136
133
 
137
134
  private
138
135
 
139
- def autocorrect(processed_source, report, original:, offset:)
136
+ def autocorrect(processed_source, corrector)
140
137
  @updated_source_file = false
141
138
  return unless autocorrect?
142
- return if report.processed_source.parser_error
139
+ return unless corrector
140
+ return if corrector.empty?
143
141
 
144
- new_source = autocorrect_report(report, original: original, offset: offset)
145
-
146
- return unless new_source
142
+ new_source = corrector.rewrite
147
143
 
148
144
  if @options[:stdin]
149
145
  # holds source read in from stdin, when --stdin option is used
@@ -174,6 +170,54 @@ module RuboCop
174
170
  commissioner.investigate(processed_source, offset: offset, original: original)
175
171
  end
176
172
 
173
+ def investigate_with_corrector(processed_source, offset:, original:)
174
+ be_ready
175
+
176
+ # The autocorrection process may have to be repeated multiple times
177
+ # until there are no corrections left to perform
178
+ # To speed things up, run autocorrecting cops by themselves, and only
179
+ # run the other cops when no corrections are left
180
+ on_duty = roundup_relevant_cops(processed_source)
181
+
182
+ autocorrect_cops, other_cops = on_duty.partition(&:autocorrect?)
183
+ report = investigate_partial(autocorrect_cops, processed_source,
184
+ offset: offset, original: original)
185
+
186
+ corrector = collated_corrector(report, offset: offset, original: original)
187
+
188
+ unless corrector
189
+ # If we corrected some errors, another round of inspection will be
190
+ # done, and any other offenses will be caught then, so only need
191
+ # to check other_cops if no correction was done
192
+ report = report.merge(investigate_partial(other_cops, processed_source,
193
+ offset: offset, original: original))
194
+ end
195
+
196
+ process_errors(processed_source.path, report.errors)
197
+
198
+ InvestigationResult.new(report, corrector)
199
+ ensure
200
+ @ready = false
201
+ end
202
+
203
+ def investigate_fragment(fragment, original, data)
204
+ offenses, errors, warnings, corrector = data
205
+ result = investigate_with_corrector(
206
+ fragment[:processed_source],
207
+ offset: fragment[:offset],
208
+ original: original
209
+ )
210
+
211
+ offenses.concat(result.report.offenses)
212
+ if result.corrector
213
+ corrector ||= Corrector.new(original)
214
+ merge_corrector!(corrector, result.corrector, offset: 0)
215
+ data[3] = corrector
216
+ end
217
+ errors.concat(@errors)
218
+ warnings.concat(@warnings)
219
+ end
220
+
177
221
  # @return [Array<cop>]
178
222
  def roundup_relevant_cops(processed_source)
179
223
  cops.select do |cop|
@@ -200,28 +244,35 @@ module RuboCop
200
244
  cop.class.support_target_rails_version?(cop.target_rails_version)
201
245
  end
202
246
 
203
- def autocorrect_report(report, offset:, original:)
247
+ def collated_corrector(report, offset:, original:)
248
+ return unless autocorrect?
249
+ return if report.processed_source.parser_error
250
+
204
251
  corrector = collate_corrections(report, offset: offset, original: original)
205
252
 
206
- corrector.rewrite unless corrector.empty?
253
+ corrector unless corrector.empty?
207
254
  end
208
255
 
209
256
  def collate_corrections(report, offset:, original:)
210
257
  corrector = Corrector.new(original)
211
258
 
212
259
  each_corrector(report) do |to_merge|
213
- suppress_clobbering do
214
- if corrector.source_buffer == to_merge.source_buffer
215
- corrector.merge!(to_merge)
216
- else
217
- corrector.import!(to_merge, offset: offset)
218
- end
219
- end
260
+ merge_corrector!(corrector, to_merge, offset: offset)
220
261
  end
221
262
 
222
263
  corrector
223
264
  end
224
265
 
266
+ def merge_corrector!(corrector, to_merge, offset:)
267
+ suppress_clobbering do
268
+ if corrector.source_buffer == to_merge.source_buffer
269
+ corrector.merge!(to_merge)
270
+ else
271
+ corrector.import!(to_merge, offset: offset)
272
+ end
273
+ end
274
+ end
275
+
225
276
  def each_corrector(report)
226
277
  skips = Set.new
227
278
  report.cop_reports.each do |cop_report|
@@ -131,6 +131,9 @@ module RuboCop
131
131
  end
132
132
 
133
133
  def set_max(cfg, cop_name)
134
+ exclude_limits = RuboCop::ExcludeLimit.read_limits(cop_name)
135
+ cfg[:exclude_limit] = exclude_limits unless exclude_limits.empty?
136
+
134
137
  return unless cfg[:exclude_limit]
135
138
 
136
139
  cfg.merge!(cfg[:exclude_limit]) if should_set_max?(cop_name)
@@ -192,7 +195,7 @@ module RuboCop
192
195
  next unless value.is_a?(Array)
193
196
  next if value.empty?
194
197
 
195
- value.map! { |v| v.nil? ? '~' : v } # Change nil back to ~ as in the YAML file.
198
+ value = value.map { |v| v.nil? ? '~' : v } # Change nil back to ~ as in the YAML file.
196
199
  output_buffer.puts "# #{param}: #{value.uniq.join(', ')}"
197
200
  end
198
201
  end
@@ -23,7 +23,6 @@ module RuboCop
23
23
  RuboCop::LSP.enable
24
24
 
25
25
  @runner = RuboCop::Lsp::StdinRunner.new(config_store)
26
- @cop_registry = RuboCop::Cop::Registry.global.to_h
27
26
 
28
27
  @safe_autocorrect = true
29
28
  @lint_mode = false
@@ -63,7 +62,7 @@ module RuboCop
63
62
  document_encoding,
64
63
  offense,
65
64
  path,
66
- @cop_registry[offense.cop_name]&.first,
65
+ RuboCop::Cop::Registry.global.find_by_cop_name(offense.cop_name),
67
66
  processed_source
68
67
  ).to_lsp_diagnostic(config)
69
68
  end
@@ -190,6 +190,8 @@ module RuboCop
190
190
  result = yield(path, source_code, safety)
191
191
 
192
192
  ::MCP::Tool::Response.new([{ type: 'text', text: result }])
193
+ rescue RuboCop::Error => e
194
+ ::MCP::Tool::Response.new([{ type: 'text', text: e.message }], error: true)
193
195
  end
194
196
  end
195
197
  # rubocop:enable Metrics/MethodLength, Metrics/ParameterLists
@@ -16,7 +16,9 @@ module RuboCop
16
16
  'root of the project. RuboCop will use this path to determine which ' \
17
17
  'cops are enabled (via eg. Include/Exclude), and so that certain cops ' \
18
18
  'like Naming/FileName can be checked.'
19
- EXITING_OPTIONS = %i[version verbose_version show_cops show_docs_url lsp mcp].freeze
19
+ EXITING_OPTIONS = %i[
20
+ version verbose_version show_cops list_enabled_cops_for show_docs_url lsp mcp
21
+ ].freeze
20
22
  DEFAULT_MAXIMUM_EXCLUSION_ITEMS = 15
21
23
 
22
24
  def initialize
@@ -236,6 +238,7 @@ module RuboCop
236
238
  def add_additional_modes(opts)
237
239
  section(opts, 'Additional Modes') do
238
240
  option(opts, '-L', '--list-target-files')
241
+ option(opts, '--list-enabled-cops-for PATH')
239
242
  option(opts, '--show-cops [COP1,COP2,...]') do |list|
240
243
  @options[:show_cops] = list.nil? ? [] : list.split(',')
241
244
  end
@@ -474,8 +477,7 @@ module RuboCop
474
477
  end
475
478
 
476
479
  def invalid_arguments_for_parallel
477
- [('--auto-gen-config' if @options.key?(:auto_gen_config)),
478
- ('-F/--fail-fast' if @options.key?(:fail_fast)),
480
+ [('-F/--fail-fast' if @options.key?(:fail_fast)),
479
481
  ('--profile' if @options[:profile]),
480
482
  ('--memory' if @options[:memory]),
481
483
  ('--cache false' if @options > { cache: 'false' })].compact
@@ -599,7 +601,7 @@ module RuboCop
599
601
  display_only_correctable: ['Only output correctable offense messages.'],
600
602
  display_only_safe_correctable: ['Only output safe-correctable offense messages',
601
603
  'when combined with --display-only-correctable.'],
602
- show_cops: ['Shows the given cops, or all cops by',
604
+ show_cops: ['Show the given cops, or all cops by',
603
605
  'default, and their configurations for the',
604
606
  'current directory.',
605
607
  'You can use `*` as a wildcard.'],
@@ -628,6 +630,8 @@ module RuboCop
628
630
  'autocorrected source. This is especially useful',
629
631
  'when combined with --autocorrect and --stdin.'],
630
632
  list_target_files: 'List all files RuboCop will inspect.',
633
+ list_enabled_cops_for: ['List which cops will inspect a given file or',
634
+ 'directory.'],
631
635
  autocorrect: 'Autocorrect offenses (only when it\'s safe).',
632
636
  auto_correct: '(same, deprecated)',
633
637
  safe_auto_correct: '(same, deprecated)',
@@ -6,6 +6,12 @@ require 'tempfile'
6
6
  module CopHelper
7
7
  extend RSpec::SharedContext
8
8
 
9
+ @integrated_plugins = false
10
+
11
+ class << self
12
+ attr_accessor :integrated_plugins
13
+ end
14
+
9
15
  let(:ruby_version) do
10
16
  # The minimum version Prism can parse is 3.3.
11
17
  ENV['PARSER_ENGINE'] == 'parser_prism' ? 3.3 : RuboCop::TargetRuby::DEFAULT_VERSION
@@ -18,11 +24,13 @@ module CopHelper
18
24
 
19
25
  before(:all) do
20
26
  next if ENV['RUBOCOP_CORE_DEVELOPMENT']
27
+ next if CopHelper.integrated_plugins
21
28
 
22
29
  plugins = Gem.loaded_specs.filter_map do |feature_name, feature_specification|
23
30
  feature_name if feature_specification.metadata['default_lint_roller_plugin']
24
31
  end
25
32
  RuboCop::Plugin.integrate_plugins(RuboCop::Config.new, plugins)
33
+ CopHelper.integrated_plugins = true
26
34
  end
27
35
 
28
36
  def inspect_source(source, file = nil)