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.
- checksums.yaml +4 -4
- data/config/default.yml +14 -3
- data/lib/rubocop/cli/command/auto_generate_config.rb +27 -1
- data/lib/rubocop/cli/command/list_enabled_cops_for.rb +40 -0
- data/lib/rubocop/cli/command/show_docs_url.rb +3 -7
- data/lib/rubocop/cli/command/suggest_extensions.rb +1 -1
- data/lib/rubocop/cli.rb +4 -7
- data/lib/rubocop/comment_config.rb +12 -15
- data/lib/rubocop/config.rb +13 -9
- data/lib/rubocop/config_loader_resolver.rb +2 -1
- data/lib/rubocop/cop/autocorrect_logic.rb +2 -1
- data/lib/rubocop/cop/correctors/multiline_literal_brace_corrector.rb +1 -5
- data/lib/rubocop/cop/correctors.rb +28 -0
- data/lib/rubocop/cop/documentation.rb +2 -3
- data/lib/rubocop/cop/exclude_limit.rb +31 -5
- data/lib/rubocop/cop/gemspec/require_mfa.rb +3 -3
- data/lib/rubocop/cop/internal_affairs/location_line_equality_comparison.rb +1 -0
- data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +1 -1
- data/lib/rubocop/cop/layout/end_alignment.rb +4 -2
- data/lib/rubocop/cop/layout/line_length.rb +5 -3
- data/lib/rubocop/cop/layout/multiline_method_call_brace_layout.rb +1 -1
- data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +26 -1
- data/lib/rubocop/cop/lint/duplicate_methods.rb +10 -5
- data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +3 -13
- data/lib/rubocop/cop/lint/require_relative_self_path.rb +2 -0
- data/lib/rubocop/cop/lint/syntax.rb +25 -1
- data/lib/rubocop/cop/lint/unused_method_argument.rb +10 -0
- data/lib/rubocop/cop/lint/useless_assignment.rb +3 -8
- data/lib/rubocop/cop/lint/utils/nil_receiver_checker.rb +33 -8
- data/lib/rubocop/cop/mixin/configurable_max.rb +6 -5
- data/lib/rubocop/cop/mixin.rb +85 -0
- data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +1 -1
- data/lib/rubocop/cop/offense.rb +8 -0
- data/lib/rubocop/cop/registry.rb +19 -24
- data/lib/rubocop/cop/style/access_modifier_declarations.rb +14 -2
- data/lib/rubocop/cop/style/copyright.rb +21 -10
- data/lib/rubocop/cop/style/date_time.rb +2 -2
- data/lib/rubocop/cop/style/document_dynamic_eval_definition.rb +6 -1
- data/lib/rubocop/cop/style/guard_clause.rb +9 -6
- data/lib/rubocop/cop/style/hash_lookup_method.rb +12 -7
- data/lib/rubocop/cop/style/if_inside_else.rb +16 -7
- data/lib/rubocop/cop/style/module_member_existence_check.rb +7 -14
- data/lib/rubocop/cop/style/one_class_per_file.rb +24 -4
- data/lib/rubocop/cop/style/reduce_to_hash.rb +16 -0
- data/lib/rubocop/cop/style/redundant_line_continuation.rb +16 -0
- data/lib/rubocop/cop/style/redundant_parentheses.rb +4 -1
- data/lib/rubocop/cop/style/redundant_self.rb +2 -2
- data/lib/rubocop/cop/style/redundant_struct_keyword_init.rb +10 -0
- data/lib/rubocop/cop/style/regexp_literal.rb +29 -0
- data/lib/rubocop/cop/style/sole_nested_conditional.rb +4 -2
- data/lib/rubocop/cop/style/symbol_proc.rb +3 -3
- data/lib/rubocop/cop/style/while_until_modifier.rb +16 -0
- data/lib/rubocop/cop/team.rb +86 -35
- data/lib/rubocop/formatter/disabled_config_formatter.rb +4 -1
- data/lib/rubocop/lsp/runtime.rb +1 -2
- data/lib/rubocop/mcp/server.rb +2 -0
- data/lib/rubocop/options.rb +8 -4
- data/lib/rubocop/rspec/cop_helper.rb +8 -0
- data/lib/rubocop/rspec/shared_contexts.rb +21 -0
- data/lib/rubocop/runner.rb +77 -55
- data/lib/rubocop/target_finder.rb +13 -6
- data/lib/rubocop/version.rb +1 -1
- data/lib/rubocop.rb +7 -96
- 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
|
|
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
|
|
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
|
|
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?
|
|
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
|
-
|
|
263
|
+
send_node = node.send_node
|
|
264
264
|
|
|
265
|
-
if
|
|
266
|
-
|
|
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
|
data/lib/rubocop/cop/team.rb
CHANGED
|
@@ -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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
# @return [Array<Offense>]
|
|
101
|
+
def investigate_fragments(fragments, original:)
|
|
102
|
+
@updated_source_file = false
|
|
103
103
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
109
|
+
autocorrect(original, corrector)
|
|
110
|
+
@errors = errors
|
|
111
|
+
@warnings = warnings
|
|
113
112
|
|
|
114
|
-
|
|
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,
|
|
136
|
+
def autocorrect(processed_source, corrector)
|
|
140
137
|
@updated_source_file = false
|
|
141
138
|
return unless autocorrect?
|
|
142
|
-
return
|
|
139
|
+
return unless corrector
|
|
140
|
+
return if corrector.empty?
|
|
143
141
|
|
|
144
|
-
new_source =
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
data/lib/rubocop/lsp/runtime.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
data/lib/rubocop/mcp/server.rb
CHANGED
|
@@ -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
|
data/lib/rubocop/options.rb
CHANGED
|
@@ -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[
|
|
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
|
-
[('
|
|
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: ['
|
|
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)
|