rubocop 1.71.1 → 1.72.1

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 (65) 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 +44 -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 +2 -2
  16. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +1 -1
  17. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +0 -6
  18. data/lib/rubocop/cop/layout/empty_lines_around_method_body.rb +22 -2
  19. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +1 -1
  20. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +2 -2
  21. data/lib/rubocop/cop/layout/space_around_method_call_operator.rb +1 -1
  22. data/lib/rubocop/cop/lint/cop_directive_syntax.rb +84 -0
  23. data/lib/rubocop/cop/lint/empty_expression.rb +0 -2
  24. data/lib/rubocop/cop/lint/format_parameter_mismatch.rb +1 -1
  25. data/lib/rubocop/cop/lint/redundant_type_conversion.rb +231 -0
  26. data/lib/rubocop/cop/lint/suppressed_exception_in_number_conversion.rb +111 -0
  27. data/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +1 -1
  28. data/lib/rubocop/cop/lint/useless_constant_scoping.rb +74 -0
  29. data/lib/rubocop/cop/lint/void.rb +1 -6
  30. data/lib/rubocop/cop/metrics/utils/repeated_attribute_discount.rb +7 -7
  31. data/lib/rubocop/cop/mixin/alignment.rb +2 -2
  32. data/lib/rubocop/cop/mixin/comments_help.rb +1 -1
  33. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +18 -18
  34. data/lib/rubocop/cop/mixin/hash_transform_method.rb +74 -74
  35. data/lib/rubocop/cop/mixin/percent_literal.rb +1 -1
  36. data/lib/rubocop/cop/mixin/range_help.rb +3 -3
  37. data/lib/rubocop/cop/mixin/string_help.rb +1 -1
  38. data/lib/rubocop/cop/naming/predicate_name.rb +44 -0
  39. data/lib/rubocop/cop/style/each_with_object.rb +2 -3
  40. data/lib/rubocop/cop/style/explicit_block_argument.rb +14 -1
  41. data/lib/rubocop/cop/style/redundant_format.rb +222 -0
  42. data/lib/rubocop/cop/style/redundant_parentheses.rb +19 -3
  43. data/lib/rubocop/cop/style/while_until_modifier.rb +0 -1
  44. data/lib/rubocop/cop/util.rb +1 -1
  45. data/lib/rubocop/cop/utils/format_string.rb +7 -5
  46. data/lib/rubocop/cop/variable_force/variable.rb +13 -1
  47. data/lib/rubocop/directive_comment.rb +35 -2
  48. data/lib/rubocop/lsp/runtime.rb +2 -0
  49. data/lib/rubocop/lsp/server.rb +0 -2
  50. data/lib/rubocop/options.rb +26 -11
  51. data/lib/rubocop/path_util.rb +4 -0
  52. data/lib/rubocop/plugin/configuration_integrator.rb +141 -0
  53. data/lib/rubocop/plugin/load_error.rb +26 -0
  54. data/lib/rubocop/plugin/loader.rb +100 -0
  55. data/lib/rubocop/plugin/not_supported_error.rb +29 -0
  56. data/lib/rubocop/plugin.rb +39 -0
  57. data/lib/rubocop/rake_task.rb +4 -1
  58. data/lib/rubocop/rspec/cop_helper.rb +7 -0
  59. data/lib/rubocop/server/cache.rb +35 -2
  60. data/lib/rubocop/server/cli.rb +2 -2
  61. data/lib/rubocop/version.rb +17 -2
  62. data/lib/rubocop.rb +5 -0
  63. data/lib/ruby_lsp/rubocop/addon.rb +7 -10
  64. data/lib/ruby_lsp/rubocop/{wraps_built_in_lsp_runtime.rb → runtime_adapter.rb} +5 -8
  65. 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
@@ -58,7 +58,7 @@ module RuboCop
58
58
 
59
59
  # @!method each_with_object_block_candidate?(node)
60
60
  def_node_matcher :each_with_object_block_candidate?, <<~PATTERN
61
- (block $(call _ {:inject :reduce} _) $_ $_)
61
+ (block $(call _ {:inject :reduce} _) $(args _ _) $_)
62
62
  PATTERN
63
63
 
64
64
  # @!method each_with_object_numblock_candidate?(node)
@@ -71,8 +71,7 @@ module RuboCop
71
71
 
72
72
  first_arg, second_arg = *node.arguments
73
73
 
74
- corrector.replace(first_arg, second_arg.source)
75
- corrector.replace(second_arg, first_arg.source)
74
+ corrector.swap(first_arg, second_arg)
76
75
 
77
76
  if return_value_occupies_whole_line?(return_value)
78
77
  corrector.remove(whole_line_expression(return_value))
@@ -135,7 +135,13 @@ module RuboCop
135
135
  end
136
136
 
137
137
  def correct_call_node(node, corrector, block_name)
138
- corrector.insert_after(node, "(&#{block_name})")
138
+ new_arguments = if node.zsuper_type?
139
+ args = build_new_arguments_for_zsuper(node) << "&#{block_name}"
140
+ args.join(', ')
141
+ else
142
+ "&#{block_name}"
143
+ end
144
+ corrector.insert_after(node, "(#{new_arguments})")
139
145
  return unless node.parenthesized?
140
146
 
141
147
  args_begin = Util.args_begin(node)
@@ -144,6 +150,13 @@ module RuboCop
144
150
  corrector.remove(range)
145
151
  end
146
152
 
153
+ def build_new_arguments_for_zsuper(node)
154
+ def_node = node.each_ancestor(:def, :defs).first
155
+ def_node.arguments.map do |arg|
156
+ arg.optarg_type? ? arg.node_parts[0] : arg.source
157
+ end
158
+ end
159
+
147
160
  def block_body_range(block_node, send_node)
148
161
  range_between(send_node.source_range.end_pos, block_node.loc.end.end_pos)
149
162
  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,13 @@ 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
+ return false unless (parent = begin_node.parent)
221
+
222
+ parent.begin_type? && parent.children.one?
223
+ else
224
+ !raised_to_power_negative_numeric?(begin_node, node)
225
+ end
211
226
  end
212
227
 
213
228
  def raised_to_power_negative_numeric?(begin_node, node)
@@ -284,6 +299,7 @@ module RuboCop
284
299
  block.keywords? && begin_node.each_ancestor(:call).any?
285
300
  end
286
301
  end
302
+ # rubocop:enable Metrics/ClassLength
287
303
  end
288
304
  end
289
305
  end
@@ -24,7 +24,6 @@ module RuboCop
24
24
  # # good
25
25
  # x += 1 until x > 10
26
26
  #
27
- # @example
28
27
  # # bad
29
28
  # x += 100 while x < 500 # a long comment that makes code too long if it were a single line
30
29
  #
@@ -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?