rubocop 1.71.1 → 1.72.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +30 -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/example_description.rb +4 -2
  10. data/lib/rubocop/cop/internal_affairs/location_exists.rb +116 -0
  11. data/lib/rubocop/cop/internal_affairs/node_pattern_groups/ast_walker.rb +1 -1
  12. data/lib/rubocop/cop/internal_affairs/plugin.rb +33 -0
  13. data/lib/rubocop/cop/internal_affairs/undefined_config.rb +7 -1
  14. data/lib/rubocop/cop/internal_affairs.rb +1 -16
  15. data/lib/rubocop/cop/layout/block_alignment.rb +2 -0
  16. data/lib/rubocop/cop/layout/else_alignment.rb +2 -2
  17. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +1 -1
  18. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +0 -6
  19. data/lib/rubocop/cop/layout/empty_lines_around_method_body.rb +22 -2
  20. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +1 -1
  21. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +2 -2
  22. data/lib/rubocop/cop/layout/space_around_method_call_operator.rb +1 -1
  23. data/lib/rubocop/cop/lint/cop_directive_syntax.rb +84 -0
  24. data/lib/rubocop/cop/lint/empty_expression.rb +0 -2
  25. data/lib/rubocop/cop/lint/format_parameter_mismatch.rb +1 -1
  26. data/lib/rubocop/cop/lint/redundant_type_conversion.rb +231 -0
  27. data/lib/rubocop/cop/lint/suppressed_exception_in_number_conversion.rb +111 -0
  28. data/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +1 -1
  29. data/lib/rubocop/cop/lint/useless_constant_scoping.rb +80 -0
  30. data/lib/rubocop/cop/lint/void.rb +1 -6
  31. data/lib/rubocop/cop/metrics/utils/repeated_attribute_discount.rb +7 -7
  32. data/lib/rubocop/cop/mixin/alignment.rb +2 -2
  33. data/lib/rubocop/cop/mixin/allowed_pattern.rb +4 -4
  34. data/lib/rubocop/cop/mixin/comments_help.rb +1 -1
  35. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +18 -18
  36. data/lib/rubocop/cop/mixin/hash_transform_method.rb +74 -74
  37. data/lib/rubocop/cop/mixin/percent_literal.rb +1 -1
  38. data/lib/rubocop/cop/mixin/range_help.rb +3 -3
  39. data/lib/rubocop/cop/mixin/string_help.rb +1 -1
  40. data/lib/rubocop/cop/naming/block_forwarding.rb +3 -3
  41. data/lib/rubocop/cop/naming/predicate_name.rb +44 -0
  42. data/lib/rubocop/cop/style/arguments_forwarding.rb +3 -3
  43. data/lib/rubocop/cop/style/each_with_object.rb +2 -3
  44. data/lib/rubocop/cop/style/explicit_block_argument.rb +14 -1
  45. data/lib/rubocop/cop/style/redundant_format.rb +238 -0
  46. data/lib/rubocop/cop/style/redundant_parentheses.rb +18 -4
  47. data/lib/rubocop/cop/style/while_until_modifier.rb +0 -1
  48. data/lib/rubocop/cop/util.rb +1 -1
  49. data/lib/rubocop/cop/utils/format_string.rb +7 -5
  50. data/lib/rubocop/cop/variable_force/variable.rb +13 -1
  51. data/lib/rubocop/directive_comment.rb +35 -2
  52. data/lib/rubocop/lsp/runtime.rb +2 -0
  53. data/lib/rubocop/lsp/server.rb +0 -2
  54. data/lib/rubocop/options.rb +26 -11
  55. data/lib/rubocop/path_util.rb +4 -0
  56. data/lib/rubocop/plugin/configuration_integrator.rb +143 -0
  57. data/lib/rubocop/plugin/load_error.rb +26 -0
  58. data/lib/rubocop/plugin/loader.rb +100 -0
  59. data/lib/rubocop/plugin/not_supported_error.rb +29 -0
  60. data/lib/rubocop/plugin.rb +39 -0
  61. data/lib/rubocop/rake_task.rb +4 -1
  62. data/lib/rubocop/rspec/cop_helper.rb +9 -0
  63. data/lib/rubocop/server/cache.rb +35 -2
  64. data/lib/rubocop/server/cli.rb +2 -2
  65. data/lib/rubocop/version.rb +17 -2
  66. data/lib/rubocop.rb +5 -0
  67. data/lib/ruby_lsp/rubocop/addon.rb +7 -10
  68. data/lib/ruby_lsp/rubocop/{wraps_built_in_lsp_runtime.rb → runtime_adapter.rb} +5 -8
  69. 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)
@@ -14,10 +14,10 @@ module RuboCop
14
14
  # autocorrected.
15
15
  #
16
16
  # [NOTE]
17
- # --
17
+ # ====
18
18
  # Because of a bug in Ruby 3.3.0, when a block is referenced inside of another block,
19
19
  # no offense will be registered until Ruby 3.4:
20
-
20
+ #
21
21
  # [source,ruby]
22
22
  # ----
23
23
  # def foo(&block)
@@ -25,7 +25,7 @@ module RuboCop
25
25
  # block_method { bar(&block) }
26
26
  # end
27
27
  # ----
28
- # --
28
+ # ====
29
29
  #
30
30
  # @example EnforcedStyle: anonymous (default)
31
31
  #
@@ -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
@@ -32,10 +32,10 @@ module RuboCop
32
32
  # This cop handles not only method forwarding but also forwarding to `super`.
33
33
  #
34
34
  # [NOTE]
35
- # --
35
+ # ====
36
36
  # Because of a bug in Ruby 3.3.0, when a block is referenced inside of another block,
37
37
  # no offense will be registered until Ruby 3.4:
38
-
38
+ #
39
39
  # [source,ruby]
40
40
  # ----
41
41
  # def foo(&block)
@@ -43,7 +43,7 @@ module RuboCop
43
43
  # block_method { bar(&block) }
44
44
  # end
45
45
  # ----
46
- # --
46
+ # ====
47
47
  #
48
48
  # @example
49
49
  # # bad
@@ -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,238 @@
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
+ # @safety
16
+ # This cop's autocorrection is unsafe because string object returned by
17
+ # `format` and `sprintf` are never frozen. If `format('string')` is autocorrected to
18
+ # `'string'`, `FrozenError` may occur when calling a destructive method like `String#<<`.
19
+ # Consider using `'string'.dup` instead of `format('string')`.
20
+ # Additionally, since the necessity of `dup` cannot be determined automatically,
21
+ # this autocorrection is inherently unsafe.
22
+ #
23
+ # [source,ruby]
24
+ # ----
25
+ # # frozen_string_literal: true
26
+ #
27
+ # format('template').frozen? # => false
28
+ # 'template'.frozen? # => true
29
+ # ----
30
+ #
31
+ # @example
32
+ #
33
+ # # bad
34
+ # format('the quick brown fox jumps over the lazy dog.')
35
+ # sprintf('the quick brown fox jumps over the lazy dog.')
36
+ #
37
+ # # good
38
+ # 'the quick brown fox jumps over the lazy dog.'
39
+ #
40
+ # # bad
41
+ # format('%s %s', 'foo', 'bar')
42
+ # sprintf('%s %s', 'foo', 'bar')
43
+ #
44
+ # # good
45
+ # 'foo bar'
46
+ #
47
+ class RedundantFormat < Base
48
+ extend AutoCorrector
49
+
50
+ MSG = 'Redundant `%<method_name>s` can be removed.'
51
+
52
+ RESTRICT_ON_SEND = %i[format sprintf].to_set.freeze
53
+ ACCEPTABLE_LITERAL_TYPES = %i[str dstr sym dsym numeric boolean nil].freeze
54
+
55
+ # @!method format_without_additional_args?(node)
56
+ def_node_matcher :format_without_additional_args?, <<~PATTERN
57
+ (send {(const {nil? cbase} :Kernel) nil?} %RESTRICT_ON_SEND ${str dstr})
58
+ PATTERN
59
+
60
+ # @!method rational_number?(node)
61
+ def_node_matcher :rational_number?, <<~PATTERN
62
+ {rational (send int :/ rational) (begin rational) (begin (send int :/ rational))}
63
+ PATTERN
64
+
65
+ # @!method complex_number?(node)
66
+ def_node_matcher :complex_number?, <<~PATTERN
67
+ {complex (send int :+ complex) (begin complex) (begin (send int :+ complex))}
68
+ PATTERN
69
+
70
+ # @!method find_hash_value_node(node, name)
71
+ def_node_search :find_hash_value_node, <<~PATTERN
72
+ (pair (sym %1) $_)
73
+ PATTERN
74
+
75
+ def on_send(node)
76
+ format_without_additional_args?(node) do |value|
77
+ add_offense(node, message: message(node)) do |corrector|
78
+ corrector.replace(node, value.source)
79
+ end
80
+ return
81
+ end
82
+
83
+ detect_unnecessary_fields(node)
84
+ end
85
+
86
+ private
87
+
88
+ def message(node)
89
+ format(MSG, method_name: node.method_name)
90
+ end
91
+
92
+ def detect_unnecessary_fields(node)
93
+ return unless node.first_argument&.str_type?
94
+
95
+ string = node.first_argument.value
96
+ arguments = node.arguments[1..]
97
+
98
+ return unless string && arguments.any?
99
+ return if arguments.any?(&:splat_type?)
100
+
101
+ register_all_fields_literal(node, string, arguments)
102
+ end
103
+
104
+ def register_all_fields_literal(node, string, arguments)
105
+ return unless all_fields_literal?(string, arguments.dup)
106
+
107
+ add_offense(node, message: message(node)) do |corrector|
108
+ replacement = format(string, *argument_values(arguments))
109
+ corrector.replace(node, quote(replacement, node))
110
+ end
111
+ end
112
+
113
+ def all_fields_literal?(string, arguments)
114
+ count = 0
115
+ sequences = RuboCop::Cop::Utils::FormatString.new(string).format_sequences
116
+ return false unless sequences.any?
117
+
118
+ sequences.each do |sequence|
119
+ next if sequence.percent?
120
+
121
+ hash = arguments.detect(&:hash_type?)
122
+ argument = find_argument(sequence, arguments, hash)
123
+ next unless matching_argument?(sequence, argument)
124
+
125
+ count += 1
126
+ end
127
+
128
+ sequences.size == count
129
+ end
130
+
131
+ def find_argument(sequence, arguments, hash)
132
+ if hash && (sequence.annotated? || sequence.template?)
133
+ find_hash_value_node(hash, sequence.name.to_sym).first
134
+ elsif sequence.arg_number
135
+ arguments[sequence.arg_number.to_i - 1]
136
+ else
137
+ # If the specifier contains `*`, the following arguments will be used
138
+ # to specify the width and can be ignored.
139
+ (sequence.arity - 1).times { arguments.shift }
140
+ arguments.shift
141
+ end
142
+ end
143
+
144
+ def matching_argument?(sequence, argument)
145
+ # Template specifiers don't give a type, any acceptable literal type is ok.
146
+ return argument.type?(*ACCEPTABLE_LITERAL_TYPES) if sequence.template?
147
+
148
+ # An argument matches a specifier if it can be easily converted
149
+ # to that type.
150
+ case sequence.type
151
+ when 's'
152
+ argument.type?(*ACCEPTABLE_LITERAL_TYPES)
153
+ when 'd', 'i', 'u'
154
+ integer?(argument)
155
+ when 'f'
156
+ float?(argument)
157
+ else
158
+ false
159
+ end
160
+ end
161
+
162
+ def numeric?(argument)
163
+ argument&.type?(:numeric, :str) ||
164
+ rational_number?(argument) ||
165
+ complex_number?(argument)
166
+ end
167
+
168
+ def integer?(argument)
169
+ numeric?(argument) && Integer(argument_value(argument), exception: false)
170
+ end
171
+
172
+ def float?(argument)
173
+ numeric?(argument) && Float(argument_value(argument), exception: false)
174
+ end
175
+
176
+ # Add correct quotes to the formatted string, preferring retaining the existing
177
+ # quotes if possible.
178
+ def quote(string, node)
179
+ str_node = node.first_argument
180
+ start_delimiter = str_node.loc.begin.source
181
+ end_delimiter = str_node.loc.end.source
182
+
183
+ # If there is any interpolation, the delimiters need to be changed potentially
184
+ if node.each_descendant(:dstr, :dsym).any?
185
+ case start_delimiter
186
+ when "'"
187
+ start_delimiter = end_delimiter = '"'
188
+ when /\A%q(.)/
189
+ start_delimiter = "%Q#{Regexp.last_match[1]}"
190
+ end
191
+ end
192
+
193
+ "#{start_delimiter}#{string}#{end_delimiter}"
194
+ end
195
+
196
+ def argument_values(arguments)
197
+ arguments.map { |argument| argument_value(argument) }
198
+ end
199
+
200
+ def argument_value(argument)
201
+ argument = argument.children.first if argument.begin_type?
202
+
203
+ if argument.dsym_type?
204
+ dsym_value(argument)
205
+ elsif argument.hash_type?
206
+ hash_value(argument)
207
+ elsif rational_number?(argument)
208
+ rational_value(argument)
209
+ elsif complex_number?(argument)
210
+ complex_value(argument)
211
+ elsif argument.respond_to?(:value)
212
+ argument.value
213
+ else
214
+ argument.source
215
+ end
216
+ end
217
+
218
+ def dsym_value(dsym_node)
219
+ dsym_node.children.first.source
220
+ end
221
+
222
+ def hash_value(hash_node)
223
+ hash_node.each_pair.with_object({}) do |pair, hash|
224
+ hash[pair.key.value] = argument_value(pair.value)
225
+ end
226
+ end
227
+
228
+ def rational_value(rational_node)
229
+ rational_node.source.to_r
230
+ end
231
+
232
+ def complex_value(complex_node)
233
+ Complex(complex_node.source)
234
+ end
235
+ end
236
+ end
237
+ end
238
+ end
@@ -13,14 +13,16 @@ module RuboCop
13
13
  # # good
14
14
  # x if y.z.nil?
15
15
  #
16
- class RedundantParentheses < Base
16
+ class RedundantParentheses < Base # rubocop:disable Metrics/ClassLength
17
17
  include Parentheses
18
18
  extend AutoCorrector
19
19
 
20
20
  ALLOWED_NODE_TYPES = %i[and or send splat kwsplat].freeze
21
21
 
22
22
  # @!method square_brackets?(node)
23
- def_node_matcher :square_brackets?, '(send {(send _recv _msg) str array hash} :[] ...)'
23
+ def_node_matcher :square_brackets?, <<~PATTERN
24
+ (send `{(send _recv _msg) str array hash const #variable?} :[] ...)
25
+ PATTERN
24
26
 
25
27
  # @!method method_node_and_args(node)
26
28
  def_node_matcher :method_node_and_args, '$(call _recv _msg $...)'
@@ -39,6 +41,10 @@ module RuboCop
39
41
 
40
42
  private
41
43
 
44
+ def variable?(node)
45
+ node.respond_to?(:variable?) && node.variable?
46
+ end
47
+
42
48
  def parens_allowed?(node)
43
49
  empty_parentheses?(node) ||
44
50
  first_arg_begins_with_hash_literal?(node) ||
@@ -128,6 +134,8 @@ module RuboCop
128
134
  node = begin_node.children.first
129
135
 
130
136
  if (message = find_offense_message(begin_node, node))
137
+ begin_node = begin_node.parent if node.range_type?
138
+
131
139
  return offense(begin_node, message)
132
140
  end
133
141
 
@@ -137,7 +145,7 @@ module RuboCop
137
145
  # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
138
146
  def find_offense_message(begin_node, node)
139
147
  return 'a keyword' if keyword_with_redundant_parentheses?(node)
140
- return 'a literal' if disallowed_literal?(begin_node, node)
148
+ return 'a literal' if node.literal? && disallowed_literal?(begin_node, node)
141
149
  return 'a variable' if node.variable?
142
150
  return 'a constant' if node.const_type?
143
151
  if node.assignment? && (begin_node.parent.nil? || begin_node.parent.begin_type?)
@@ -207,7 +215,13 @@ module RuboCop
207
215
  end
208
216
 
209
217
  def disallowed_literal?(begin_node, node)
210
- node.literal? && !node.range_type? && !raised_to_power_negative_numeric?(begin_node, node)
218
+ if node.range_type?
219
+ return false unless (parent = begin_node.parent)
220
+
221
+ parent.begin_type? && parent.children.one?
222
+ else
223
+ !raised_to_power_negative_numeric?(begin_node, node)
224
+ end
211
225
  end
212
226
 
213
227
  def raised_to_power_negative_numeric?(begin_node, node)
@@ -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