rubocop 1.71.2 → 1.72.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +28 -0
  4. data/lib/rubocop/cli/command/suggest_extensions.rb +7 -1
  5. data/lib/rubocop/comment_config.rb +1 -1
  6. data/lib/rubocop/config.rb +4 -0
  7. data/lib/rubocop/config_loader.rb +40 -8
  8. data/lib/rubocop/config_loader_resolver.rb +21 -7
  9. data/lib/rubocop/cop/internal_affairs/location_exists.rb +116 -0
  10. data/lib/rubocop/cop/internal_affairs/node_pattern_groups/ast_walker.rb +1 -1
  11. data/lib/rubocop/cop/internal_affairs/plugin.rb +33 -0
  12. data/lib/rubocop/cop/internal_affairs/undefined_config.rb +7 -1
  13. data/lib/rubocop/cop/internal_affairs.rb +1 -16
  14. data/lib/rubocop/cop/layout/block_alignment.rb +2 -0
  15. data/lib/rubocop/cop/layout/else_alignment.rb +1 -1
  16. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +1 -1
  17. data/lib/rubocop/cop/layout/empty_lines_around_method_body.rb +22 -2
  18. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +1 -1
  19. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +2 -2
  20. data/lib/rubocop/cop/layout/space_around_method_call_operator.rb +1 -1
  21. data/lib/rubocop/cop/lint/cop_directive_syntax.rb +84 -0
  22. data/lib/rubocop/cop/lint/format_parameter_mismatch.rb +1 -1
  23. data/lib/rubocop/cop/lint/redundant_type_conversion.rb +221 -0
  24. data/lib/rubocop/cop/lint/suppressed_exception_in_number_conversion.rb +111 -0
  25. data/lib/rubocop/cop/lint/useless_constant_scoping.rb +74 -0
  26. data/lib/rubocop/cop/metrics/utils/repeated_attribute_discount.rb +7 -7
  27. data/lib/rubocop/cop/mixin/alignment.rb +2 -2
  28. data/lib/rubocop/cop/mixin/comments_help.rb +1 -1
  29. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +18 -18
  30. data/lib/rubocop/cop/mixin/hash_transform_method.rb +74 -74
  31. data/lib/rubocop/cop/mixin/percent_literal.rb +1 -1
  32. data/lib/rubocop/cop/mixin/range_help.rb +3 -3
  33. data/lib/rubocop/cop/mixin/string_help.rb +1 -1
  34. data/lib/rubocop/cop/naming/predicate_name.rb +44 -0
  35. data/lib/rubocop/cop/style/redundant_format.rb +222 -0
  36. data/lib/rubocop/cop/style/redundant_parentheses.rb +17 -3
  37. data/lib/rubocop/cop/util.rb +1 -1
  38. data/lib/rubocop/cop/utils/format_string.rb +7 -5
  39. data/lib/rubocop/directive_comment.rb +35 -2
  40. data/lib/rubocop/lsp/runtime.rb +2 -0
  41. data/lib/rubocop/lsp/server.rb +0 -2
  42. data/lib/rubocop/options.rb +26 -11
  43. data/lib/rubocop/path_util.rb +4 -0
  44. data/lib/rubocop/plugin/configuration_integrator.rb +141 -0
  45. data/lib/rubocop/plugin/load_error.rb +35 -0
  46. data/lib/rubocop/plugin/loader.rb +100 -0
  47. data/lib/rubocop/plugin/not_supported_error.rb +29 -0
  48. data/lib/rubocop/plugin.rb +39 -0
  49. data/lib/rubocop/rake_task.rb +4 -1
  50. data/lib/rubocop/server/cache.rb +35 -2
  51. data/lib/rubocop/server/cli.rb +2 -2
  52. data/lib/rubocop/version.rb +17 -2
  53. data/lib/rubocop.rb +5 -0
  54. data/lib/ruby_lsp/rubocop/addon.rb +7 -10
  55. data/lib/ruby_lsp/rubocop/{wraps_built_in_lsp_runtime.rb → runtime_adapter.rb} +5 -8
  56. 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
@@ -21,7 +21,7 @@ module RuboCop
21
21
  end
22
22
 
23
23
  def begin_source(node)
24
- node.loc.begin.source if node.loc.respond_to?(:begin) && node.loc.begin
24
+ node.loc.begin.source if node.loc?(:begin)
25
25
  end
26
26
 
27
27
  def type(node)
@@ -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.respond_to?(:begin) && node.loc.begin
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?, '(send {(send _recv _msg) str array hash} :[] ...)'
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
- node.literal? && !node.range_type? && !raised_to_power_negative_numeric?(begin_node, node)
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
@@ -32,7 +32,7 @@ module RuboCop
32
32
  end
33
33
 
34
34
  def parentheses?(node)
35
- node.loc.respond_to?(:end) && node.loc.end&.is?(')')
35
+ node.loc_is?(:end, ')')
36
36
  end
37
37
 
38
38
  # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
@@ -5,15 +5,16 @@ module RuboCop
5
5
  module Utils
6
6
  # Parses {Kernel#sprintf} format strings.
7
7
  class FormatString
8
- DIGIT_DOLLAR = /(\d+)\$/.freeze
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 = /\{(?<name>\w+)\}/.freeze
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
- "# rubocop : ((?:disable|enable|todo))\\b #{COPS_PATTERN}"
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 ||= comment.text.match(DIRECTIVE_COMMENT_REGEXP)&.captures
91
+ @match_captures ||= @match_data&.captures
59
92
  end
60
93
 
61
94
  # Checks if this directive disables cops
@@ -20,6 +20,8 @@ module RuboCop
20
20
  attr_writer :safe_autocorrect, :lint_mode, :layout_mode
21
21
 
22
22
  def initialize(config_store)
23
+ RuboCop::LSP.enable
24
+
23
25
  @runner = RuboCop::Lsp::StdinRunner.new(config_store)
24
26
  @cop_registry = RuboCop::Cop::Registry.global.to_h
25
27