rubocop 1.71.2 → 1.72.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/config/default.yml +28 -0
- data/lib/rubocop/cli/command/suggest_extensions.rb +7 -1
- data/lib/rubocop/comment_config.rb +1 -1
- data/lib/rubocop/config.rb +4 -0
- data/lib/rubocop/config_loader.rb +40 -8
- data/lib/rubocop/config_loader_resolver.rb +21 -7
- data/lib/rubocop/cop/internal_affairs/location_exists.rb +116 -0
- data/lib/rubocop/cop/internal_affairs/node_pattern_groups/ast_walker.rb +1 -1
- data/lib/rubocop/cop/internal_affairs/plugin.rb +33 -0
- data/lib/rubocop/cop/internal_affairs/undefined_config.rb +7 -1
- data/lib/rubocop/cop/internal_affairs.rb +1 -16
- data/lib/rubocop/cop/layout/block_alignment.rb +2 -0
- data/lib/rubocop/cop/layout/else_alignment.rb +1 -1
- data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +1 -1
- data/lib/rubocop/cop/layout/empty_lines_around_method_body.rb +22 -2
- data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +1 -1
- data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +2 -2
- data/lib/rubocop/cop/layout/space_around_method_call_operator.rb +1 -1
- data/lib/rubocop/cop/lint/cop_directive_syntax.rb +84 -0
- data/lib/rubocop/cop/lint/format_parameter_mismatch.rb +1 -1
- data/lib/rubocop/cop/lint/redundant_type_conversion.rb +221 -0
- data/lib/rubocop/cop/lint/suppressed_exception_in_number_conversion.rb +111 -0
- data/lib/rubocop/cop/lint/useless_constant_scoping.rb +74 -0
- data/lib/rubocop/cop/metrics/utils/repeated_attribute_discount.rb +7 -7
- data/lib/rubocop/cop/mixin/alignment.rb +2 -2
- data/lib/rubocop/cop/mixin/comments_help.rb +1 -1
- data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +18 -18
- data/lib/rubocop/cop/mixin/hash_transform_method.rb +74 -74
- data/lib/rubocop/cop/mixin/percent_literal.rb +1 -1
- data/lib/rubocop/cop/mixin/range_help.rb +3 -3
- data/lib/rubocop/cop/mixin/string_help.rb +1 -1
- data/lib/rubocop/cop/naming/predicate_name.rb +44 -0
- data/lib/rubocop/cop/style/redundant_format.rb +222 -0
- data/lib/rubocop/cop/style/redundant_parentheses.rb +17 -3
- data/lib/rubocop/cop/util.rb +1 -1
- data/lib/rubocop/cop/utils/format_string.rb +7 -5
- data/lib/rubocop/directive_comment.rb +35 -2
- data/lib/rubocop/lsp/runtime.rb +2 -0
- data/lib/rubocop/lsp/server.rb +0 -2
- data/lib/rubocop/options.rb +26 -11
- data/lib/rubocop/path_util.rb +4 -0
- data/lib/rubocop/plugin/configuration_integrator.rb +141 -0
- data/lib/rubocop/plugin/load_error.rb +35 -0
- data/lib/rubocop/plugin/loader.rb +100 -0
- data/lib/rubocop/plugin/not_supported_error.rb +29 -0
- data/lib/rubocop/plugin.rb +39 -0
- data/lib/rubocop/rake_task.rb +4 -1
- data/lib/rubocop/server/cache.rb +35 -2
- data/lib/rubocop/server/cli.rb +2 -2
- data/lib/rubocop/version.rb +17 -2
- data/lib/rubocop.rb +5 -0
- data/lib/ruby_lsp/rubocop/addon.rb +7 -10
- data/lib/ruby_lsp/rubocop/{wraps_built_in_lsp_runtime.rb → runtime_adapter.rb} +5 -8
- metadata +35 -9
@@ -9,6 +9,80 @@ module RuboCop
|
|
9
9
|
|
10
10
|
RESTRICT_ON_SEND = %i[[] to_h].freeze
|
11
11
|
|
12
|
+
# Internal helper class to hold match data
|
13
|
+
Captures = Struct.new(:transformed_argname, :transforming_body_expr, :unchanged_body_expr) do
|
14
|
+
def noop_transformation?
|
15
|
+
transforming_body_expr.lvar_type? &&
|
16
|
+
transforming_body_expr.children == [transformed_argname]
|
17
|
+
end
|
18
|
+
|
19
|
+
def transformation_uses_both_args?
|
20
|
+
transforming_body_expr.descendants.include?(unchanged_body_expr)
|
21
|
+
end
|
22
|
+
|
23
|
+
def use_transformed_argname?
|
24
|
+
transforming_body_expr.each_descendant(:lvar).any? do |node|
|
25
|
+
node.source == transformed_argname.to_s
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Internal helper class to hold autocorrect data
|
31
|
+
Autocorrection = Struct.new(:match, :block_node, :leading, :trailing) do
|
32
|
+
def self.from_each_with_object(node, match)
|
33
|
+
new(match, node, 0, 0)
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.from_hash_brackets_map(node, match)
|
37
|
+
new(match, node.children.last, 'Hash['.length, ']'.length)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.from_map_to_h(node, match)
|
41
|
+
if node.parent&.block_type? && node.parent.send_node == node
|
42
|
+
strip_trailing_chars = 0
|
43
|
+
else
|
44
|
+
map_range = node.children.first.source_range
|
45
|
+
node_range = node.source_range
|
46
|
+
strip_trailing_chars = node_range.end_pos - map_range.end_pos
|
47
|
+
end
|
48
|
+
|
49
|
+
new(match, node.children.first, 0, strip_trailing_chars)
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.from_to_h(node, match)
|
53
|
+
new(match, node, 0, 0)
|
54
|
+
end
|
55
|
+
|
56
|
+
def strip_prefix_and_suffix(node, corrector)
|
57
|
+
expression = node.source_range
|
58
|
+
corrector.remove_leading(expression, leading)
|
59
|
+
corrector.remove_trailing(expression, trailing)
|
60
|
+
end
|
61
|
+
|
62
|
+
def set_new_method_name(new_method_name, corrector)
|
63
|
+
range = block_node.send_node.loc.selector
|
64
|
+
if (send_end = block_node.send_node.loc.end)
|
65
|
+
# If there are arguments (only true in the `each_with_object`
|
66
|
+
# case)
|
67
|
+
range = range.begin.join(send_end)
|
68
|
+
end
|
69
|
+
corrector.replace(range, new_method_name)
|
70
|
+
end
|
71
|
+
|
72
|
+
def set_new_arg_name(transformed_argname, corrector)
|
73
|
+
corrector.replace(block_node.arguments, "|#{transformed_argname}|")
|
74
|
+
end
|
75
|
+
|
76
|
+
def set_new_body_expression(transforming_body_expr, corrector)
|
77
|
+
body_source = transforming_body_expr.source
|
78
|
+
if transforming_body_expr.hash_type? && !transforming_body_expr.braces?
|
79
|
+
body_source = "{ #{body_source} }"
|
80
|
+
end
|
81
|
+
|
82
|
+
corrector.replace(block_node.body, body_source)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
12
86
|
# @!method array_receiver?(node)
|
13
87
|
def_node_matcher :array_receiver?, <<~PATTERN
|
14
88
|
{(array ...) (send _ :each_with_index) (send _ :with_index _ ?) (send _ :zip ...)}
|
@@ -113,80 +187,6 @@ module RuboCop
|
|
113
187
|
correction.set_new_arg_name(captures.transformed_argname, corrector)
|
114
188
|
correction.set_new_body_expression(captures.transforming_body_expr, corrector)
|
115
189
|
end
|
116
|
-
|
117
|
-
# Internal helper class to hold match data
|
118
|
-
Captures = Struct.new(:transformed_argname, :transforming_body_expr, :unchanged_body_expr) do
|
119
|
-
def noop_transformation?
|
120
|
-
transforming_body_expr.lvar_type? &&
|
121
|
-
transforming_body_expr.children == [transformed_argname]
|
122
|
-
end
|
123
|
-
|
124
|
-
def transformation_uses_both_args?
|
125
|
-
transforming_body_expr.descendants.include?(unchanged_body_expr)
|
126
|
-
end
|
127
|
-
|
128
|
-
def use_transformed_argname?
|
129
|
-
transforming_body_expr.each_descendant(:lvar).any? do |node|
|
130
|
-
node.source == transformed_argname.to_s
|
131
|
-
end
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
# Internal helper class to hold autocorrect data
|
136
|
-
Autocorrection = Struct.new(:match, :block_node, :leading, :trailing) do
|
137
|
-
def self.from_each_with_object(node, match)
|
138
|
-
new(match, node, 0, 0)
|
139
|
-
end
|
140
|
-
|
141
|
-
def self.from_hash_brackets_map(node, match)
|
142
|
-
new(match, node.children.last, 'Hash['.length, ']'.length)
|
143
|
-
end
|
144
|
-
|
145
|
-
def self.from_map_to_h(node, match)
|
146
|
-
if node.parent&.block_type? && node.parent.send_node == node
|
147
|
-
strip_trailing_chars = 0
|
148
|
-
else
|
149
|
-
map_range = node.children.first.source_range
|
150
|
-
node_range = node.source_range
|
151
|
-
strip_trailing_chars = node_range.end_pos - map_range.end_pos
|
152
|
-
end
|
153
|
-
|
154
|
-
new(match, node.children.first, 0, strip_trailing_chars)
|
155
|
-
end
|
156
|
-
|
157
|
-
def self.from_to_h(node, match)
|
158
|
-
new(match, node, 0, 0)
|
159
|
-
end
|
160
|
-
|
161
|
-
def strip_prefix_and_suffix(node, corrector)
|
162
|
-
expression = node.source_range
|
163
|
-
corrector.remove_leading(expression, leading)
|
164
|
-
corrector.remove_trailing(expression, trailing)
|
165
|
-
end
|
166
|
-
|
167
|
-
def set_new_method_name(new_method_name, corrector)
|
168
|
-
range = block_node.send_node.loc.selector
|
169
|
-
if (send_end = block_node.send_node.loc.end)
|
170
|
-
# If there are arguments (only true in the `each_with_object`
|
171
|
-
# case)
|
172
|
-
range = range.begin.join(send_end)
|
173
|
-
end
|
174
|
-
corrector.replace(range, new_method_name)
|
175
|
-
end
|
176
|
-
|
177
|
-
def set_new_arg_name(transformed_argname, corrector)
|
178
|
-
corrector.replace(block_node.arguments, "|#{transformed_argname}|")
|
179
|
-
end
|
180
|
-
|
181
|
-
def set_new_body_expression(transforming_body_expr, corrector)
|
182
|
-
body_source = transforming_body_expr.source
|
183
|
-
if transforming_body_expr.hash_type? && !transforming_body_expr.braces?
|
184
|
-
body_source = "{ #{body_source} }"
|
185
|
-
end
|
186
|
-
|
187
|
-
corrector.replace(block_node.body, body_source)
|
188
|
-
end
|
189
|
-
end
|
190
190
|
end
|
191
191
|
end
|
192
192
|
end
|
@@ -4,9 +4,10 @@ module RuboCop
|
|
4
4
|
module Cop
|
5
5
|
# Methods that calculate and return Parser::Source::Ranges
|
6
6
|
module RangeHelp
|
7
|
-
private
|
8
|
-
|
9
7
|
BYTE_ORDER_MARK = 0xfeff # The Unicode codepoint
|
8
|
+
NOT_GIVEN = Module.new
|
9
|
+
|
10
|
+
private
|
10
11
|
|
11
12
|
def source_range(source_buffer, line_number, column, length = 1)
|
12
13
|
if column.is_a?(Range)
|
@@ -51,7 +52,6 @@ module RuboCop
|
|
51
52
|
Parser::Source::Range.new(buffer, begin_pos, end_pos)
|
52
53
|
end
|
53
54
|
|
54
|
-
NOT_GIVEN = Module.new
|
55
55
|
def range_with_surrounding_space(range_positional = NOT_GIVEN, # rubocop:disable Metrics/ParameterLists
|
56
56
|
range: NOT_GIVEN, side: :both, newlines: true,
|
57
57
|
whitespace: false, continuations: false,
|
@@ -10,7 +10,7 @@ module RuboCop
|
|
10
10
|
def on_str(node)
|
11
11
|
# Constants like __FILE__ are handled as strings,
|
12
12
|
# but don't respond to begin.
|
13
|
-
return unless node.loc
|
13
|
+
return unless node.loc?(:begin)
|
14
14
|
return if part_of_ignored_node?(node)
|
15
15
|
|
16
16
|
if offense?(node)
|
@@ -17,6 +17,10 @@ module RuboCop
|
|
17
17
|
# they end with a `?`. These methods should be changed to remove the
|
18
18
|
# prefix.
|
19
19
|
#
|
20
|
+
# When `UseSorbetSigs` set to true (optional), the cop will only report
|
21
|
+
# offenses if the method has a Sorbet `sig` with a return type of
|
22
|
+
# `T::Boolean`. Dynamic methods are not supported with this configuration.
|
23
|
+
#
|
20
24
|
# @example NamePrefix: ['is_', 'has_', 'have_'] (default)
|
21
25
|
# # bad
|
22
26
|
# def is_even(value)
|
@@ -58,6 +62,30 @@ module RuboCop
|
|
58
62
|
# def is_even?(value)
|
59
63
|
# end
|
60
64
|
#
|
65
|
+
# @example UseSorbetSigs: false (default)
|
66
|
+
# # bad
|
67
|
+
# sig { returns(String) }
|
68
|
+
# def is_this_thing_on
|
69
|
+
# "yes"
|
70
|
+
# end
|
71
|
+
#
|
72
|
+
# # good - Sorbet signature is not evaluated
|
73
|
+
# sig { returns(String) }
|
74
|
+
# def is_this_thing_on?
|
75
|
+
# "yes"
|
76
|
+
# end
|
77
|
+
#
|
78
|
+
# @example UseSorbetSigs: true
|
79
|
+
# # bad
|
80
|
+
# sig { returns(T::Boolean) }
|
81
|
+
# def odd(value)
|
82
|
+
# end
|
83
|
+
#
|
84
|
+
# # good
|
85
|
+
# sig { returns(T::Boolean) }
|
86
|
+
# def odd?(value)
|
87
|
+
# end
|
88
|
+
#
|
61
89
|
# @example MethodDefinitionMacros: ['define_method', 'define_singleton_method'] (default)
|
62
90
|
# # bad
|
63
91
|
# define_method(:is_even) { |value| }
|
@@ -100,6 +128,7 @@ module RuboCop
|
|
100
128
|
method_name = node.method_name.to_s
|
101
129
|
|
102
130
|
next if allowed_method_name?(method_name, prefix)
|
131
|
+
next if use_sorbet_sigs? && !sorbet_sig?(node, return_type: 'T::Boolean')
|
103
132
|
|
104
133
|
add_offense(
|
105
134
|
node.loc.name,
|
@@ -121,6 +150,17 @@ module RuboCop
|
|
121
150
|
|
122
151
|
private
|
123
152
|
|
153
|
+
# @!method sorbet_return_type(node)
|
154
|
+
def_node_matcher :sorbet_return_type, <<~PATTERN
|
155
|
+
(block (send nil? :sig) args (send _ :returns $_type))
|
156
|
+
PATTERN
|
157
|
+
|
158
|
+
def sorbet_sig?(node, return_type: nil)
|
159
|
+
return false unless (type = sorbet_return_type(node.left_sibling))
|
160
|
+
|
161
|
+
type.source == return_type
|
162
|
+
end
|
163
|
+
|
124
164
|
def allowed_method_name?(method_name, prefix)
|
125
165
|
!(method_name.start_with?(prefix) && # cheap check to avoid allocating Regexp
|
126
166
|
method_name.match?(/^#{prefix}[^0-9]/)) ||
|
@@ -151,6 +191,10 @@ module RuboCop
|
|
151
191
|
cop_config['NamePrefix']
|
152
192
|
end
|
153
193
|
|
194
|
+
def use_sorbet_sigs?
|
195
|
+
cop_config['UseSorbetSigs']
|
196
|
+
end
|
197
|
+
|
154
198
|
def method_definition_macros(macro_name)
|
155
199
|
cop_config['MethodDefinitionMacros'].include?(macro_name.to_s)
|
156
200
|
end
|
@@ -0,0 +1,222 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Style
|
6
|
+
# Checks for calls to `Kernel#format` or `Kernel#sprintf` that are redundant.
|
7
|
+
#
|
8
|
+
# Calling `format` with only a single string argument is redundant, as it can be
|
9
|
+
# replaced by the string itself.
|
10
|
+
#
|
11
|
+
# Also looks for `format` calls where the arguments are literals that can be
|
12
|
+
# inlined into a string easily. This applies to the `%s`, `%d`, `%i`, `%u`, and
|
13
|
+
# `%f` format specifiers.
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
#
|
17
|
+
# # bad
|
18
|
+
# format('the quick brown fox jumps over the lazy dog.')
|
19
|
+
# sprintf('the quick brown fox jumps over the lazy dog.')
|
20
|
+
#
|
21
|
+
# # good
|
22
|
+
# 'the quick brown fox jumps over the lazy dog.'
|
23
|
+
#
|
24
|
+
# # bad
|
25
|
+
# format('%s %s', 'foo', 'bar')
|
26
|
+
# sprintf('%s %s', 'foo', 'bar')
|
27
|
+
#
|
28
|
+
# # good
|
29
|
+
# 'foo bar'
|
30
|
+
#
|
31
|
+
class RedundantFormat < Base
|
32
|
+
extend AutoCorrector
|
33
|
+
|
34
|
+
MSG = 'Redundant `%<method_name>s` can be removed.'
|
35
|
+
|
36
|
+
RESTRICT_ON_SEND = %i[format sprintf].to_set.freeze
|
37
|
+
ACCEPTABLE_LITERAL_TYPES = %i[str dstr sym dsym numeric boolean nil].freeze
|
38
|
+
|
39
|
+
# @!method format_without_additional_args?(node)
|
40
|
+
def_node_matcher :format_without_additional_args?, <<~PATTERN
|
41
|
+
(send {(const {nil? cbase} :Kernel) nil?} %RESTRICT_ON_SEND ${str dstr})
|
42
|
+
PATTERN
|
43
|
+
|
44
|
+
# @!method rational_number?(node)
|
45
|
+
def_node_matcher :rational_number?, <<~PATTERN
|
46
|
+
{rational (send int :/ rational) (begin rational) (begin (send int :/ rational))}
|
47
|
+
PATTERN
|
48
|
+
|
49
|
+
# @!method complex_number?(node)
|
50
|
+
def_node_matcher :complex_number?, <<~PATTERN
|
51
|
+
{complex (send int :+ complex) (begin complex) (begin (send int :+ complex))}
|
52
|
+
PATTERN
|
53
|
+
|
54
|
+
# @!method find_hash_value_node(node, name)
|
55
|
+
def_node_search :find_hash_value_node, <<~PATTERN
|
56
|
+
(pair (sym %1) $_)
|
57
|
+
PATTERN
|
58
|
+
|
59
|
+
def on_send(node)
|
60
|
+
format_without_additional_args?(node) do |value|
|
61
|
+
add_offense(node, message: message(node)) do |corrector|
|
62
|
+
corrector.replace(node, value.source)
|
63
|
+
end
|
64
|
+
return
|
65
|
+
end
|
66
|
+
|
67
|
+
detect_unnecessary_fields(node)
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def message(node)
|
73
|
+
format(MSG, method_name: node.method_name)
|
74
|
+
end
|
75
|
+
|
76
|
+
def detect_unnecessary_fields(node)
|
77
|
+
return unless node.first_argument&.str_type?
|
78
|
+
|
79
|
+
string = node.first_argument.value
|
80
|
+
arguments = node.arguments[1..]
|
81
|
+
|
82
|
+
return unless string && arguments.any?
|
83
|
+
return if arguments.any?(&:splat_type?)
|
84
|
+
|
85
|
+
register_all_fields_literal(node, string, arguments)
|
86
|
+
end
|
87
|
+
|
88
|
+
def register_all_fields_literal(node, string, arguments)
|
89
|
+
return unless all_fields_literal?(string, arguments.dup)
|
90
|
+
|
91
|
+
add_offense(node, message: message(node)) do |corrector|
|
92
|
+
replacement = format(string, *argument_values(arguments))
|
93
|
+
corrector.replace(node, quote(replacement, node))
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def all_fields_literal?(string, arguments)
|
98
|
+
count = 0
|
99
|
+
sequences = RuboCop::Cop::Utils::FormatString.new(string).format_sequences
|
100
|
+
return false unless sequences.any?
|
101
|
+
|
102
|
+
sequences.each do |sequence|
|
103
|
+
next if sequence.percent?
|
104
|
+
|
105
|
+
hash = arguments.detect(&:hash_type?)
|
106
|
+
argument = find_argument(sequence, arguments, hash)
|
107
|
+
next unless matching_argument?(sequence, argument)
|
108
|
+
|
109
|
+
count += 1
|
110
|
+
end
|
111
|
+
|
112
|
+
sequences.size == count
|
113
|
+
end
|
114
|
+
|
115
|
+
def find_argument(sequence, arguments, hash)
|
116
|
+
if sequence.annotated? || sequence.template?
|
117
|
+
find_hash_value_node(hash, sequence.name.to_sym).first
|
118
|
+
elsif sequence.arg_number
|
119
|
+
arguments[sequence.arg_number.to_i - 1]
|
120
|
+
else
|
121
|
+
# If the specifier contains `*`, the following arguments will be used
|
122
|
+
# to specify the width and can be ignored.
|
123
|
+
(sequence.arity - 1).times { arguments.shift }
|
124
|
+
arguments.shift
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def matching_argument?(sequence, argument)
|
129
|
+
# Template specifiers don't give a type, any acceptable literal type is ok.
|
130
|
+
return argument.type?(*ACCEPTABLE_LITERAL_TYPES) if sequence.template?
|
131
|
+
|
132
|
+
# An argument matches a specifier if it can be easily converted
|
133
|
+
# to that type.
|
134
|
+
case sequence.type
|
135
|
+
when 's'
|
136
|
+
argument.type?(*ACCEPTABLE_LITERAL_TYPES)
|
137
|
+
when 'd', 'i', 'u'
|
138
|
+
integer?(argument)
|
139
|
+
when 'f'
|
140
|
+
float?(argument)
|
141
|
+
else
|
142
|
+
false
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def numeric?(argument)
|
147
|
+
argument.type?(:numeric, :str) ||
|
148
|
+
rational_number?(argument) ||
|
149
|
+
complex_number?(argument)
|
150
|
+
end
|
151
|
+
|
152
|
+
def integer?(argument)
|
153
|
+
numeric?(argument) && Integer(argument_value(argument), exception: false)
|
154
|
+
end
|
155
|
+
|
156
|
+
def float?(argument)
|
157
|
+
numeric?(argument) && Float(argument_value(argument), exception: false)
|
158
|
+
end
|
159
|
+
|
160
|
+
# Add correct quotes to the formatted string, preferring retaining the existing
|
161
|
+
# quotes if possible.
|
162
|
+
def quote(string, node)
|
163
|
+
str_node = node.first_argument
|
164
|
+
start_delimiter = str_node.loc.begin.source
|
165
|
+
end_delimiter = str_node.loc.end.source
|
166
|
+
|
167
|
+
# If there is any interpolation, the delimiters need to be changed potentially
|
168
|
+
if node.each_descendant(:dstr, :dsym).any?
|
169
|
+
case start_delimiter
|
170
|
+
when "'"
|
171
|
+
start_delimiter = end_delimiter = '"'
|
172
|
+
when /\A%q(.)/
|
173
|
+
start_delimiter = "%Q#{Regexp.last_match[1]}"
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
"#{start_delimiter}#{string}#{end_delimiter}"
|
178
|
+
end
|
179
|
+
|
180
|
+
def argument_values(arguments)
|
181
|
+
arguments.map { |argument| argument_value(argument) }
|
182
|
+
end
|
183
|
+
|
184
|
+
def argument_value(argument)
|
185
|
+
argument = argument.children.first if argument.begin_type?
|
186
|
+
|
187
|
+
if argument.dsym_type?
|
188
|
+
dsym_value(argument)
|
189
|
+
elsif argument.hash_type?
|
190
|
+
hash_value(argument)
|
191
|
+
elsif rational_number?(argument)
|
192
|
+
rational_value(argument)
|
193
|
+
elsif complex_number?(argument)
|
194
|
+
complex_value(argument)
|
195
|
+
elsif argument.respond_to?(:value)
|
196
|
+
argument.value
|
197
|
+
else
|
198
|
+
argument.source
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def dsym_value(dsym_node)
|
203
|
+
dsym_node.children.first.source
|
204
|
+
end
|
205
|
+
|
206
|
+
def hash_value(hash_node)
|
207
|
+
hash_node.each_pair.with_object({}) do |pair, hash|
|
208
|
+
hash[pair.key.value] = argument_value(pair.value)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def rational_value(rational_node)
|
213
|
+
rational_node.source.to_r
|
214
|
+
end
|
215
|
+
|
216
|
+
def complex_value(complex_node)
|
217
|
+
Complex(complex_node.source)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
@@ -13,6 +13,7 @@ module RuboCop
|
|
13
13
|
# # good
|
14
14
|
# x if y.z.nil?
|
15
15
|
#
|
16
|
+
# rubocop:disable Metrics/ClassLength
|
16
17
|
class RedundantParentheses < Base
|
17
18
|
include Parentheses
|
18
19
|
extend AutoCorrector
|
@@ -20,7 +21,9 @@ module RuboCop
|
|
20
21
|
ALLOWED_NODE_TYPES = %i[and or send splat kwsplat].freeze
|
21
22
|
|
22
23
|
# @!method square_brackets?(node)
|
23
|
-
def_node_matcher :square_brackets?,
|
24
|
+
def_node_matcher :square_brackets?, <<~PATTERN
|
25
|
+
(send `{(send _recv _msg) str array hash const #variable?} :[] ...)
|
26
|
+
PATTERN
|
24
27
|
|
25
28
|
# @!method method_node_and_args(node)
|
26
29
|
def_node_matcher :method_node_and_args, '$(call _recv _msg $...)'
|
@@ -39,6 +42,10 @@ module RuboCop
|
|
39
42
|
|
40
43
|
private
|
41
44
|
|
45
|
+
def variable?(node)
|
46
|
+
node.respond_to?(:variable?) && node.variable?
|
47
|
+
end
|
48
|
+
|
42
49
|
def parens_allowed?(node)
|
43
50
|
empty_parentheses?(node) ||
|
44
51
|
first_arg_begins_with_hash_literal?(node) ||
|
@@ -128,6 +135,8 @@ module RuboCop
|
|
128
135
|
node = begin_node.children.first
|
129
136
|
|
130
137
|
if (message = find_offense_message(begin_node, node))
|
138
|
+
begin_node = begin_node.parent if node.range_type?
|
139
|
+
|
131
140
|
return offense(begin_node, message)
|
132
141
|
end
|
133
142
|
|
@@ -137,7 +146,7 @@ module RuboCop
|
|
137
146
|
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
138
147
|
def find_offense_message(begin_node, node)
|
139
148
|
return 'a keyword' if keyword_with_redundant_parentheses?(node)
|
140
|
-
return 'a literal' if disallowed_literal?(begin_node, node)
|
149
|
+
return 'a literal' if node.literal? && disallowed_literal?(begin_node, node)
|
141
150
|
return 'a variable' if node.variable?
|
142
151
|
return 'a constant' if node.const_type?
|
143
152
|
if node.assignment? && (begin_node.parent.nil? || begin_node.parent.begin_type?)
|
@@ -207,7 +216,11 @@ module RuboCop
|
|
207
216
|
end
|
208
217
|
|
209
218
|
def disallowed_literal?(begin_node, node)
|
210
|
-
|
219
|
+
if node.range_type?
|
220
|
+
begin_node.parent&.begin_type?
|
221
|
+
else
|
222
|
+
!raised_to_power_negative_numeric?(begin_node, node)
|
223
|
+
end
|
211
224
|
end
|
212
225
|
|
213
226
|
def raised_to_power_negative_numeric?(begin_node, node)
|
@@ -284,6 +297,7 @@ module RuboCop
|
|
284
297
|
block.keywords? && begin_node.each_ancestor(:call).any?
|
285
298
|
end
|
286
299
|
end
|
300
|
+
# rubocop:enable Metrics/ClassLength
|
287
301
|
end
|
288
302
|
end
|
289
303
|
end
|
data/lib/rubocop/cop/util.rb
CHANGED
@@ -5,15 +5,16 @@ module RuboCop
|
|
5
5
|
module Utils
|
6
6
|
# Parses {Kernel#sprintf} format strings.
|
7
7
|
class FormatString
|
8
|
-
DIGIT_DOLLAR = /(
|
8
|
+
DIGIT_DOLLAR = /(?<arg_number>\d+)\$/.freeze
|
9
|
+
INTERPOLATION = /#\{.*?\}/.freeze
|
9
10
|
FLAG = /[ #0+-]|#{DIGIT_DOLLAR}/.freeze
|
10
11
|
NUMBER_ARG = /\*#{DIGIT_DOLLAR}?/.freeze
|
11
|
-
NUMBER = /\d+|#{NUMBER_ARG}/.freeze
|
12
|
+
NUMBER = /\d+|#{NUMBER_ARG}|#{INTERPOLATION}/.freeze
|
12
13
|
WIDTH = /(?<width>#{NUMBER})/.freeze
|
13
|
-
PRECISION = /\.(?<precision>#{NUMBER})/.freeze
|
14
|
+
PRECISION = /\.(?<precision>#{NUMBER}?)/.freeze
|
14
15
|
TYPE = /(?<type>[bBdiouxXeEfgGaAcps])/.freeze
|
15
16
|
NAME = /<(?<name>\w+)>/.freeze
|
16
|
-
TEMPLATE_NAME =
|
17
|
+
TEMPLATE_NAME = /(?<!#)\{(?<name>\w+)\}/.freeze
|
17
18
|
|
18
19
|
SEQUENCE = /
|
19
20
|
% (?<type>%)
|
@@ -41,7 +42,7 @@ module RuboCop
|
|
41
42
|
#
|
42
43
|
# @see https://ruby-doc.org/core-2.6.3/Kernel.html#method-i-format
|
43
44
|
class FormatSequence
|
44
|
-
attr_reader :begin_pos, :end_pos, :flags, :width, :precision, :name, :type
|
45
|
+
attr_reader :begin_pos, :end_pos, :flags, :width, :precision, :name, :type, :arg_number
|
45
46
|
|
46
47
|
def initialize(match)
|
47
48
|
@source = match[0]
|
@@ -52,6 +53,7 @@ module RuboCop
|
|
52
53
|
@precision = match[:precision]
|
53
54
|
@name = match[:name]
|
54
55
|
@type = match[:type]
|
56
|
+
@arg_number = match[:arg_number]
|
55
57
|
end
|
56
58
|
|
57
59
|
def percent?
|
@@ -18,10 +18,24 @@ module RuboCop
|
|
18
18
|
# @api private
|
19
19
|
COPS_PATTERN = "(all|#{COP_NAMES_PATTERN})"
|
20
20
|
# @api private
|
21
|
+
AVAILABLE_MODES = %w[disable enable todo].freeze
|
22
|
+
# @api private
|
23
|
+
DIRECTIVE_MARKER_PATTERN = '# rubocop : '
|
24
|
+
# @api private
|
25
|
+
DIRECTIVE_MARKER_REGEXP = Regexp.new(DIRECTIVE_MARKER_PATTERN.gsub(' ', '\s*'))
|
26
|
+
# @api private
|
27
|
+
DIRECTIVE_HEADER_PATTERN = "#{DIRECTIVE_MARKER_PATTERN}((?:#{AVAILABLE_MODES.join('|')}))\\b"
|
28
|
+
# @api private
|
21
29
|
DIRECTIVE_COMMENT_REGEXP = Regexp.new(
|
22
|
-
"#
|
30
|
+
"#{DIRECTIVE_HEADER_PATTERN} #{COPS_PATTERN}"
|
23
31
|
.gsub(' ', '\s*')
|
24
32
|
)
|
33
|
+
# @api private
|
34
|
+
TRAILING_COMMENT_MARKER = '--'
|
35
|
+
# @api private
|
36
|
+
MALFORMED_DIRECTIVE_WITHOUT_COP_NAME_REGEXP = Regexp.new(
|
37
|
+
"\\A#{DIRECTIVE_HEADER_PATTERN}\\s*\\z".gsub(' ', '\s*')
|
38
|
+
)
|
25
39
|
|
26
40
|
def self.before_comment(line)
|
27
41
|
line.split(DIRECTIVE_COMMENT_REGEXP).first
|
@@ -32,9 +46,28 @@ module RuboCop
|
|
32
46
|
def initialize(comment, cop_registry = Cop::Registry.global)
|
33
47
|
@comment = comment
|
34
48
|
@cop_registry = cop_registry
|
49
|
+
@match_data = comment.text.match(DIRECTIVE_COMMENT_REGEXP)
|
35
50
|
@mode, @cops = match_captures
|
36
51
|
end
|
37
52
|
|
53
|
+
# Checks if the comment starts with `# rubocop:` marker
|
54
|
+
def start_with_marker?
|
55
|
+
comment.text.start_with?(DIRECTIVE_MARKER_REGEXP)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Checks if the comment is malformed as a `# rubocop:` directive
|
59
|
+
def malformed?
|
60
|
+
return true if !start_with_marker? || @match_data.nil?
|
61
|
+
|
62
|
+
tail = @match_data.post_match.lstrip
|
63
|
+
!(tail.empty? || tail.start_with?(TRAILING_COMMENT_MARKER))
|
64
|
+
end
|
65
|
+
|
66
|
+
# Checks if the directive comment is missing a cop name
|
67
|
+
def missing_cop_name?
|
68
|
+
MALFORMED_DIRECTIVE_WITHOUT_COP_NAME_REGEXP.match?(comment.text)
|
69
|
+
end
|
70
|
+
|
38
71
|
# Checks if this directive relates to single line
|
39
72
|
def single_line?
|
40
73
|
!comment.text.start_with?(DIRECTIVE_COMMENT_REGEXP)
|
@@ -55,7 +88,7 @@ module RuboCop
|
|
55
88
|
|
56
89
|
# Returns match captures to directive comment pattern
|
57
90
|
def match_captures
|
58
|
-
@match_captures ||=
|
91
|
+
@match_captures ||= @match_data&.captures
|
59
92
|
end
|
60
93
|
|
61
94
|
# Checks if this directive disables cops
|
data/lib/rubocop/lsp/runtime.rb
CHANGED