rubocop 1.55.1 → 1.56.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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +10 -0
  4. data/lib/rubocop/config_finder.rb +2 -2
  5. data/lib/rubocop/cop/bundler/duplicated_gem.rb +1 -0
  6. data/lib/rubocop/cop/bundler/duplicated_group.rb +127 -0
  7. data/lib/rubocop/cop/correctors/lambda_literal_to_method_corrector.rb +7 -4
  8. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +1 -1
  9. data/lib/rubocop/cop/layout/indentation_width.rb +1 -1
  10. data/lib/rubocop/cop/layout/leading_comment_space.rb +1 -1
  11. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +1 -1
  12. data/lib/rubocop/cop/layout/redundant_line_break.rb +3 -1
  13. data/lib/rubocop/cop/layout/space_inside_parens.rb +1 -1
  14. data/lib/rubocop/cop/lint/non_atomic_file_operation.rb +2 -2
  15. data/lib/rubocop/cop/lint/struct_new_override.rb +12 -12
  16. data/lib/rubocop/cop/lint/suppressed_exception.rb +1 -1
  17. data/lib/rubocop/cop/lint/useless_assignment.rb +1 -2
  18. data/lib/rubocop/cop/mixin/string_help.rb +4 -2
  19. data/lib/rubocop/cop/naming/file_name.rb +1 -1
  20. data/lib/rubocop/cop/style/alias.rb +9 -8
  21. data/lib/rubocop/cop/style/arguments_forwarding.rb +72 -42
  22. data/lib/rubocop/cop/style/block_delimiters.rb +2 -1
  23. data/lib/rubocop/cop/style/class_equality_comparison.rb +2 -0
  24. data/lib/rubocop/cop/style/concat_array_literals.rb +1 -1
  25. data/lib/rubocop/cop/style/lambda_call.rb +5 -0
  26. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +3 -1
  27. data/lib/rubocop/cop/style/open_struct_use.rb +1 -1
  28. data/lib/rubocop/cop/style/redundant_parentheses.rb +3 -1
  29. data/lib/rubocop/cop/style/redundant_return.rb +7 -2
  30. data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +5 -0
  31. data/lib/rubocop/cop/style/string_literals_in_interpolation.rb +30 -5
  32. data/lib/rubocop/cop/style/symbol_array.rb +5 -3
  33. data/lib/rubocop/cop/utils/regexp_ranges.rb +26 -13
  34. data/lib/rubocop/lsp/routes.rb +23 -17
  35. data/lib/rubocop/lsp/runtime.rb +8 -2
  36. data/lib/rubocop/lsp/server.rb +2 -2
  37. data/lib/rubocop/runner.rb +5 -3
  38. data/lib/rubocop/target_ruby.rb +9 -5
  39. data/lib/rubocop/version.rb +1 -1
  40. data/lib/rubocop.rb +1 -0
  41. metadata +21 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4c1de4039df4f7edef2ab28f9623efa0f1d4d22e7f05d9cbd68bee76cb5216bf
4
- data.tar.gz: 3a0b84d52ba7fabd1980cfbb4d351a1d8ac2b5a1bd38608194529e34124ad603
3
+ metadata.gz: 33c4c853e4bf651034a9ac3faa2f221baa77104521504824e2b4e88b349f01e0
4
+ data.tar.gz: d49422193353fc79c86b1c8cdfc3465f4abebf56bc01cd95d2c0e150c3f9747a
5
5
  SHA512:
6
- metadata.gz: c4b5c017d22990f0f7627663d532d793cf193a97afc0de8dca538b86e35fa26210df1e871e705e3b4d9302ba0a46d5c1a7910368c083520ae62cd2facd21be0c
7
- data.tar.gz: 2e73d75f974b9f85a27e024e4c67bfc907a279f5fd07100996f04080361e9c1258b84eca28fe8352c594e5c815a6c748db786bbe67502bf887d86cfd201b8cc6
6
+ metadata.gz: 3bd1f56046aba375e89f62b8c954802636023539111238848c25b37a70ed95a41835198b2dd17ded64223b1f50e98f7e20f0c82aafed0381cad30f0be526b4fc
7
+ data.tar.gz: e02897c89f4869054ffea56efac2d9983f65bdce40c9254d5806effbda70561bf9c7c4161fa9a7d3269dc229d6d09b3e7c990eef10835f7ec5f2b3c55b32f6cf
data/README.md CHANGED
@@ -53,7 +53,7 @@ To prevent an unwanted RuboCop update you might want to use a conservative versi
53
53
  in your `Gemfile`:
54
54
 
55
55
  ```rb
56
- gem 'rubocop', '~> 1.55', require: false
56
+ gem 'rubocop', '~> 1.56', require: false
57
57
  ```
58
58
 
59
59
  See [our versioning policy](https://docs.rubocop.org/rubocop/versioning.html) for further details.
data/config/default.yml CHANGED
@@ -173,6 +173,16 @@ Bundler/DuplicatedGem:
173
173
  - '**/Gemfile'
174
174
  - '**/gems.rb'
175
175
 
176
+ Bundler/DuplicatedGroup:
177
+ Description: 'Checks for duplicate group entries in Gemfile.'
178
+ Enabled: true
179
+ Severity: warning
180
+ VersionAdded: '1.56'
181
+ Include:
182
+ - '**/*.gemfile'
183
+ - '**/Gemfile'
184
+ - '**/gems.rb'
185
+
176
186
  Bundler/GemComment:
177
187
  Description: 'Add a comment describing each gem.'
178
188
  Enabled: false
@@ -46,14 +46,14 @@ module RuboCop
46
46
 
47
47
  file = File.join(Dir.home, DOTFILE)
48
48
 
49
- return file if File.exist?(file)
49
+ file if File.exist?(file)
50
50
  end
51
51
 
52
52
  def find_user_xdg_config
53
53
  xdg_config_home = expand_path(ENV.fetch('XDG_CONFIG_HOME', '~/.config'))
54
54
  xdg_config = File.join(xdg_config_home, 'rubocop', XDG_CONFIG)
55
55
 
56
- return xdg_config if File.exist?(xdg_config)
56
+ xdg_config if File.exist?(xdg_config)
57
57
  end
58
58
 
59
59
  def expand_path(path)
@@ -4,6 +4,7 @@ module RuboCop
4
4
  module Cop
5
5
  module Bundler
6
6
  # A Gem's requirements should be listed only once in a Gemfile.
7
+ #
7
8
  # @example
8
9
  # # bad
9
10
  # gem 'rubocop'
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Bundler
6
+ # A Gem group, or a set of groups, should be listed only once in a Gemfile.
7
+ #
8
+ # For example, if the values of `source`, `git`, `platforms`, or `path`
9
+ # surrounding `group` are different, no offense will be registered:
10
+ #
11
+ # [source,ruby]
12
+ # -----
13
+ # platforms :ruby do
14
+ # group :default do
15
+ # gem 'openssl'
16
+ # end
17
+ # end
18
+ #
19
+ # platforms :jruby do
20
+ # group :default do
21
+ # gem 'jruby-openssl'
22
+ # end
23
+ # end
24
+ # -----
25
+ #
26
+ # @example
27
+ # # bad
28
+ # group :development do
29
+ # gem 'rubocop'
30
+ # end
31
+ #
32
+ # group :development do
33
+ # gem 'rubocop-rails'
34
+ # end
35
+ #
36
+ # # bad (same set of groups declared twice)
37
+ # group :development, :test do
38
+ # gem 'rubocop'
39
+ # end
40
+ #
41
+ # group :test, :development do
42
+ # gem 'rspec'
43
+ # end
44
+ #
45
+ # # good
46
+ # group :development do
47
+ # gem 'rubocop'
48
+ # end
49
+ #
50
+ # group :development, :test do
51
+ # gem 'rspec'
52
+ # end
53
+ #
54
+ # # good
55
+ # gem 'rubocop', groups: [:development, :test]
56
+ # gem 'rspec', groups: [:development, :test]
57
+ #
58
+ class DuplicatedGroup < Base
59
+ include RangeHelp
60
+
61
+ MSG = 'Gem group `%<group_name>s` already defined on line ' \
62
+ '%<line_of_first_occurrence>d of the Gemfile.'
63
+ SOURCE_BLOCK_NAMES = %i[source git platforms path].freeze
64
+
65
+ # @!method group_declarations(node)
66
+ def_node_search :group_declarations, '(send nil? :group ...)'
67
+
68
+ def on_new_investigation
69
+ return if processed_source.blank?
70
+
71
+ duplicated_group_nodes.each do |nodes|
72
+ nodes[1..].each do |node|
73
+ group_name = node.arguments.map(&:source).join(', ')
74
+
75
+ register_offense(node, group_name, nodes.first.first_line)
76
+ end
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ def duplicated_group_nodes
83
+ group_declarations = group_declarations(processed_source.ast)
84
+ group_keys = group_declarations.group_by do |node|
85
+ source_key = find_source_key(node)
86
+ group_attributes = group_attributes(node).sort.join
87
+
88
+ "#{source_key}#{group_attributes}"
89
+ end
90
+
91
+ group_keys.values.select { |nodes| nodes.size > 1 }
92
+ end
93
+
94
+ def register_offense(node, group_name, line_of_first_occurrence)
95
+ line_range = node.loc.column...node.loc.last_column
96
+ offense_location = source_range(processed_source.buffer, node.first_line, line_range)
97
+ message = format(
98
+ MSG,
99
+ group_name: group_name,
100
+ line_of_first_occurrence: line_of_first_occurrence
101
+ )
102
+ add_offense(offense_location, message: message)
103
+ end
104
+
105
+ def find_source_key(node)
106
+ source_block = node.each_ancestor(:block).find do |block_node|
107
+ SOURCE_BLOCK_NAMES.include?(block_node.method_name)
108
+ end
109
+
110
+ return unless source_block
111
+
112
+ "#{source_block.method_name}#{source_block.send_node.first_argument&.source}"
113
+ end
114
+
115
+ def group_attributes(node)
116
+ node.arguments.map do |argument|
117
+ if argument.hash_type?
118
+ argument.pairs.map(&:source).sort.join(', ')
119
+ else
120
+ argument.value.to_s
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
@@ -14,12 +14,15 @@ module RuboCop
14
14
  # Check for unparenthesized args' preceding and trailing whitespaces.
15
15
  remove_unparenthesized_whitespace(corrector)
16
16
 
17
- # Avoid correcting to `lambdado` by inserting whitespace
18
- # if none exists before or after the lambda arguments.
19
- insert_separating_space(corrector)
17
+ if block_node.block_type?
18
+ # Avoid correcting to `lambdado` by inserting whitespace
19
+ # if none exists before or after the lambda arguments.
20
+ insert_separating_space(corrector)
21
+
22
+ remove_arguments(corrector)
23
+ end
20
24
 
21
25
  replace_selector(corrector)
22
- remove_arguments(corrector)
23
26
 
24
27
  replace_delimiters(corrector)
25
28
 
@@ -137,7 +137,7 @@ module RuboCop
137
137
  return node if node
138
138
  end
139
139
 
140
- return last_heredoc_argument(n.receiver) if n.respond_to?(:receiver)
140
+ last_heredoc_argument(n.receiver) if n.respond_to?(:receiver)
141
141
  end
142
142
 
143
143
  def last_heredoc_argument_node(node)
@@ -354,7 +354,7 @@ module RuboCop
354
354
  # Don't check indentation if the line doesn't start with the body.
355
355
  # For example, lines like "else do_something".
356
356
  first_char_pos_on_line = body_node.source_range.source_line =~ /\S/
357
- return true unless body_node.loc.column == first_char_pos_on_line
357
+ true unless body_node.loc.column == first_char_pos_on_line
358
358
  end
359
359
 
360
360
  def offending_range(body_node, indentation)
@@ -57,7 +57,7 @@ module RuboCop
57
57
 
58
58
  def on_new_investigation
59
59
  processed_source.comments.each do |comment|
60
- next unless /\A#+[^#\s=+-]/.match?(comment.text)
60
+ next unless /\A(?!#\+\+|#--)(#+[^#\s=])/.match?(comment.text)
61
61
  next if comment.loc.line == 1 && allowed_on_first_line?(comment)
62
62
  next if doxygen_comment_style?(comment)
63
63
  next if gemfile_ruby_comment?(comment)
@@ -109,7 +109,7 @@ module RuboCop
109
109
  # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
110
110
 
111
111
  def comment_ranges(comments)
112
- comments.map(&:loc).map(&:expression)
112
+ comments.map(&:source_range)
113
113
  end
114
114
 
115
115
  def last_line(processed_source)
@@ -118,7 +118,9 @@ module RuboCop
118
118
  end
119
119
 
120
120
  def comment_within?(node)
121
- processed_source.comments.map(&:loc).map(&:line).any? do |comment_line_number|
121
+ comment_line_numbers = processed_source.comments.map { |comment| comment.loc.line }
122
+
123
+ comment_line_numbers.any? do |comment_line_number|
122
124
  comment_line_number >= node.first_line && comment_line_number <= node.last_line
123
125
  end
124
126
  end
@@ -168,7 +168,7 @@ module RuboCop
168
168
  # follows, and that the rules for space inside don't apply.
169
169
  return true if token2.comment?
170
170
 
171
- return true unless same_line?(token1, token2) && !token1.space_after?
171
+ true unless same_line?(token1, token2) && !token1.space_after?
172
172
  end
173
173
  end
174
174
  end
@@ -57,12 +57,12 @@ module RuboCop
57
57
  REMOVE_FORCE_METHODS).freeze
58
58
 
59
59
  # @!method send_exist_node(node)
60
- def_node_search :send_exist_node, <<-PATTERN
60
+ def_node_search :send_exist_node, <<~PATTERN
61
61
  $(send (const nil? {:FileTest :File :Dir :Shell}) {:exist? :exists?} ...)
62
62
  PATTERN
63
63
 
64
64
  # @!method receiver_and_method_name(node)
65
- def_node_matcher :receiver_and_method_name, <<-PATTERN
65
+ def_node_matcher :receiver_and_method_name, <<~PATTERN
66
66
  (send (const nil? $_) $_ ...)
67
67
  PATTERN
68
68
 
@@ -32,25 +32,25 @@ module RuboCop
32
32
  # @!method struct_new(node)
33
33
  def_node_matcher :struct_new, <<~PATTERN
34
34
  (send
35
- (const ${nil? cbase} :Struct) :new ...)
35
+ (const {nil? cbase} :Struct) :new ...)
36
36
  PATTERN
37
37
 
38
38
  def on_send(node)
39
- return unless struct_new(node) do
40
- node.arguments.each_with_index do |arg, index|
41
- # Ignore if the first argument is a class name
42
- next if index.zero? && arg.str_type?
39
+ return unless struct_new(node)
43
40
 
44
- # Ignore if the argument is not a member name
45
- next unless STRUCT_MEMBER_NAME_TYPES.include?(arg.type)
41
+ node.arguments.each_with_index do |arg, index|
42
+ # Ignore if the first argument is a class name
43
+ next if index.zero? && arg.str_type?
46
44
 
47
- member_name = arg.value
45
+ # Ignore if the argument is not a member name
46
+ next unless STRUCT_MEMBER_NAME_TYPES.include?(arg.type)
48
47
 
49
- next unless STRUCT_METHOD_NAMES.include?(member_name.to_sym)
48
+ member_name = arg.value
50
49
 
51
- message = format(MSG, member_name: member_name.inspect, method_name: member_name.to_s)
52
- add_offense(arg, message: message)
53
- end
50
+ next unless STRUCT_METHOD_NAMES.include?(member_name.to_sym)
51
+
52
+ message = format(MSG, member_name: member_name.inspect, method_name: member_name.to_s)
53
+ add_offense(arg, message: message)
54
54
  end
55
55
  end
56
56
  end
@@ -119,7 +119,7 @@ module RuboCop
119
119
  ancestor = node.each_ancestor(:kwbegin, :def, :defs, :block, :numblock).first
120
120
  return false unless ancestor
121
121
 
122
- end_line = ancestor.loc.end.line
122
+ end_line = ancestor.loc.end&.line || ancestor.loc.last_line
123
123
  processed_source[node.first_line...end_line].any? { |line| comment_line?(line) }
124
124
  end
125
125
 
@@ -96,8 +96,7 @@ module RuboCop
96
96
  return_value_node = return_value_node_of_scope(scope)
97
97
  return unless assignment.meta_assignment_node.equal?(return_value_node)
98
98
 
99
- " Use `#{assignment.operator.sub(/=$/, '')}` " \
100
- "instead of `#{assignment.operator}`."
99
+ " Use `#{assignment.operator.delete_suffix('=')}` instead of `#{assignment.operator}`."
101
100
  end
102
101
 
103
102
  def similar_name_message(variable)
@@ -30,8 +30,10 @@ module RuboCop
30
30
  private
31
31
 
32
32
  def inside_interpolation?(node)
33
- # A :begin node inside a :dstr or :dsym node is an interpolation.
34
- node.ancestors.drop_while { |a| !a.begin_type? }.any? { |a| a.dstr_type? || a.dsym_type? }
33
+ # A :begin node inside a :dstr, :dsym, or :regexp node is an interpolation.
34
+ node.ancestors
35
+ .drop_while { |a| !a.begin_type? }
36
+ .any? { |a| a.dstr_type? || a.dsym_type? || a.regexp_type? }
35
37
  end
36
38
  end
37
39
  end
@@ -136,7 +136,7 @@ module RuboCop
136
136
  end
137
137
 
138
138
  def filename_good?(basename)
139
- basename = basename.sub(/^\./, '')
139
+ basename = basename.delete_prefix('.')
140
140
  basename = basename.sub(/\.[^.]+$/, '')
141
141
  # special handling for Action Pack Variants file names like
142
142
  # some_file.xlsx+mobile.axlsx
@@ -122,7 +122,7 @@ module RuboCop
122
122
  end
123
123
 
124
124
  def bareword?(sym_node)
125
- !sym_node.source.start_with?(':')
125
+ !sym_node.source.start_with?(':') || sym_node.dsym_type?
126
126
  end
127
127
 
128
128
  def correct_alias_method_to_alias(corrector, send_node)
@@ -134,9 +134,7 @@ module RuboCop
134
134
 
135
135
  def correct_alias_to_alias_method(corrector, node)
136
136
  replacement =
137
- 'alias_method ' \
138
- ":#{identifier(node.new_identifier)}, " \
139
- ":#{identifier(node.old_identifier)}"
137
+ "alias_method #{identifier(node.new_identifier)}, #{identifier(node.old_identifier)}"
140
138
 
141
139
  corrector.replace(node, replacement)
142
140
  end
@@ -146,10 +144,13 @@ module RuboCop
146
144
  corrector.replace(node.old_identifier, node.old_identifier.source[1..])
147
145
  end
148
146
 
149
- # @!method identifier(node)
150
- def_node_matcher :identifier, <<~PATTERN
151
- (sym $_)
152
- PATTERN
147
+ def identifier(node)
148
+ if node.sym_type?
149
+ ":#{node.children.first}"
150
+ else
151
+ node.source
152
+ end
153
+ end
153
154
  end
154
155
  end
155
156
  end
@@ -80,6 +80,7 @@ module RuboCop
80
80
  minimum_target_ruby_version 2.7
81
81
 
82
82
  FORWARDING_LVAR_TYPES = %i[splat kwsplat block_pass].freeze
83
+ ADDITIONAL_ARG_TYPES = %i[lvar arg].freeze
83
84
 
84
85
  FORWARDING_MSG = 'Use shorthand syntax `...` for arguments forwarding.'
85
86
  ARGS_MSG = 'Use anonymous positional arguments forwarding (`*`).'
@@ -100,7 +101,7 @@ module RuboCop
100
101
  return if send_classifications.empty?
101
102
 
102
103
  if only_forwards_all?(send_classifications)
103
- add_forward_all_offenses(node, send_classifications)
104
+ add_forward_all_offenses(node, send_classifications, forwardable_args)
104
105
  elsif target_ruby_version >= 3.2
105
106
  add_post_ruby_32_offenses(node, send_classifications, forwardable_args)
106
107
  end
@@ -118,12 +119,13 @@ module RuboCop
118
119
  send_classifications.each_value.all? { |c, _, _| c == :all }
119
120
  end
120
121
 
121
- def add_forward_all_offenses(node, send_classifications)
122
- send_classifications.each_key do |send_node|
123
- register_forward_all_offense_on_forwarding_method(send_node)
122
+ def add_forward_all_offenses(node, send_classifications, forwardable_args)
123
+ send_classifications.each do |send_node, (_c, forward_rest, _forward_kwrest)|
124
+ register_forward_all_offense(send_node, send_node, forward_rest)
124
125
  end
125
126
 
126
- register_forward_all_offense_on_method_def(node)
127
+ rest_arg, _kwrest_arg, _block_arg = *forwardable_args
128
+ register_forward_all_offense(node, node.arguments, rest_arg)
127
129
  end
128
130
 
129
131
  def add_post_ruby_32_offenses(def_node, send_classifications, forwardable_args)
@@ -186,9 +188,7 @@ module RuboCop
186
188
 
187
189
  def register_forward_args_offense(def_arguments_or_send, rest_arg_or_splat)
188
190
  add_offense(rest_arg_or_splat, message: ARGS_MSG) do |corrector|
189
- unless parentheses?(def_arguments_or_send)
190
- add_parentheses(def_arguments_or_send, corrector)
191
- end
191
+ add_parens_if_missing(def_arguments_or_send, corrector)
192
192
 
193
193
  corrector.replace(rest_arg_or_splat, '*')
194
194
  end
@@ -196,36 +196,28 @@ module RuboCop
196
196
 
197
197
  def register_forward_kwargs_offense(add_parens, def_arguments_or_send, kwrest_arg_or_splat)
198
198
  add_offense(kwrest_arg_or_splat, message: KWARGS_MSG) do |corrector|
199
- if add_parens && !parentheses?(def_arguments_or_send)
200
- add_parentheses(def_arguments_or_send, corrector)
201
- end
199
+ add_parens_if_missing(def_arguments_or_send, corrector) if add_parens
202
200
 
203
201
  corrector.replace(kwrest_arg_or_splat, '**')
204
202
  end
205
203
  end
206
204
 
207
- def register_forward_all_offense_on_forwarding_method(forwarding_method)
208
- add_offense(arguments_range(forwarding_method), message: FORWARDING_MSG) do |corrector|
209
- begin_pos = forwarding_method.loc.selector&.end_pos || forwarding_method.loc.dot.end_pos
210
- range = range_between(begin_pos, forwarding_method.source_range.end_pos)
205
+ def register_forward_all_offense(def_or_send, send_or_arguments, rest_or_splat)
206
+ arg_range = arguments_range(def_or_send, rest_or_splat)
211
207
 
212
- corrector.replace(range, '(...)')
213
- end
214
- end
208
+ add_offense(arg_range, message: FORWARDING_MSG) do |corrector|
209
+ add_parens_if_missing(send_or_arguments, corrector)
215
210
 
216
- def register_forward_all_offense_on_method_def(method_definition)
217
- add_offense(arguments_range(method_definition), message: FORWARDING_MSG) do |corrector|
218
- arguments_range = range_with_surrounding_space(
219
- method_definition.arguments.source_range, side: :left
220
- )
221
- corrector.replace(arguments_range, '(...)')
211
+ corrector.replace(arg_range, '...')
222
212
  end
223
213
  end
224
214
 
225
- def arguments_range(node)
226
- arguments = node.arguments
215
+ def arguments_range(node, first_node)
216
+ arguments = node.arguments.reject { |arg| ADDITIONAL_ARG_TYPES.include?(arg.type) }
217
+
218
+ start_node = first_node || arguments.first
227
219
 
228
- range_between(arguments.first.source_range.begin_pos, arguments.last.source_range.end_pos)
220
+ range_between(start_node.source_range.begin_pos, arguments.last.source_range.end_pos)
229
221
  end
230
222
 
231
223
  def allow_only_rest_arguments?
@@ -236,6 +228,12 @@ module RuboCop
236
228
  cop_config.fetch('UseAnonymousForwarding', false)
237
229
  end
238
230
 
231
+ def add_parens_if_missing(node, corrector)
232
+ return if parentheses?(node)
233
+
234
+ add_parentheses(node, corrector)
235
+ end
236
+
239
237
  # Classifies send nodes for possible rest/kwrest/all (including block) forwarding.
240
238
  class SendNodeClassifier
241
239
  extend NodePattern::Macros
@@ -280,15 +278,36 @@ module RuboCop
280
278
  def classification
281
279
  return nil unless forwarded_rest_arg || forwarded_kwrest_arg
282
280
 
283
- if referenced_none? && (forwarded_exactly_all? || pre_ruby_32_allow_forward_all?)
281
+ if can_forward_all?
284
282
  :all
285
- elsif target_ruby_version >= 3.2
283
+ else
286
284
  :rest_or_kwrest
287
285
  end
288
286
  end
289
287
 
290
288
  private
291
289
 
290
+ def can_forward_all?
291
+ return false if any_arg_referenced?
292
+ return false if ruby_32_missing_rest_or_kwest?
293
+ return false unless offensive_block_forwarding?
294
+ return false if forward_additional_kwargs?
295
+
296
+ no_additional_args? || (target_ruby_version >= 3.0 && no_post_splat_args?)
297
+ end
298
+
299
+ def ruby_32_missing_rest_or_kwest?
300
+ target_ruby_version >= 3.2 && !forwarded_rest_and_kwrest_args
301
+ end
302
+
303
+ def offensive_block_forwarding?
304
+ @block_arg ? forwarded_block_arg : allow_offense_for_no_block?
305
+ end
306
+
307
+ def forwarded_rest_and_kwrest_args
308
+ forwarded_rest_arg && forwarded_kwrest_arg
309
+ end
310
+
292
311
  def arguments
293
312
  @send_node.arguments
294
313
  end
@@ -305,25 +324,36 @@ module RuboCop
305
324
  @referenced_lvars.include?(@block_arg_name)
306
325
  end
307
326
 
308
- def referenced_none?
309
- !(referenced_rest_arg? || referenced_kwrest_arg? || referenced_block_arg?)
310
- end
311
-
312
- def forwarded_exactly_all?
313
- @send_node.arguments.size == 3 &&
314
- forwarded_rest_arg &&
315
- forwarded_kwrest_arg &&
316
- forwarded_block_arg
327
+ def any_arg_referenced?
328
+ referenced_rest_arg? || referenced_kwrest_arg? || referenced_block_arg?
317
329
  end
318
330
 
319
331
  def target_ruby_version
320
332
  @config.fetch(:target_ruby_version)
321
333
  end
322
334
 
323
- def pre_ruby_32_allow_forward_all?
324
- target_ruby_version < 3.2 &&
325
- @def_node.arguments.none?(&:default?) &&
326
- (@block_arg ? forwarded_block_arg : !@config.fetch(:allow_only_rest_arguments))
335
+ def no_post_splat_args?
336
+ return true unless (splat_index = arguments.index(forwarded_rest_arg))
337
+
338
+ arg_after_splat = arguments[splat_index + 1]
339
+ [nil, :hash, :block_pass].include?(arg_after_splat&.type)
340
+ end
341
+
342
+ def forward_additional_kwargs?
343
+ return false unless forwarded_kwrest_arg
344
+
345
+ !forwarded_kwrest_arg.parent.children.one?
346
+ end
347
+
348
+ def allow_offense_for_no_block?
349
+ !@config.fetch(:allow_only_rest_arguments)
350
+ end
351
+
352
+ def no_additional_args?
353
+ forwardable_count = [@rest_arg, @kwrest_arg, @block_arg].compact.size
354
+
355
+ @def_node.arguments.size == forwardable_count &&
356
+ @send_node.arguments.size == forwardable_count
327
357
  end
328
358
  end
329
359
  end
@@ -370,7 +370,8 @@ module RuboCop
370
370
  def special_method_proper_block_style?(node)
371
371
  method_name = node.method_name
372
372
  return true if allowed_method?(method_name) || matches_allowed_pattern?(method_name)
373
- return node.braces? if braces_required_method?(method_name)
373
+
374
+ node.braces? if braces_required_method?(method_name)
374
375
  end
375
376
 
376
377
  def braces_required_method?(method_name)
@@ -69,6 +69,8 @@ module RuboCop
69
69
  matches_allowed_pattern?(def_node.method_name))
70
70
 
71
71
  class_comparison_candidate?(node) do |receiver_node, class_node|
72
+ return if class_node.dstr_type?
73
+
72
74
  range = offense_range(receiver_node, node)
73
75
  class_argument = (class_name = class_name(class_node, node)) ? "(#{class_name})" : ''
74
76
 
@@ -74,7 +74,7 @@ module RuboCop
74
74
  new_arguments =
75
75
  node.arguments.map do |arg|
76
76
  if arg.percent_literal?
77
- arg.children.map(&:value).map(&:inspect)
77
+ arg.children.map { |child| child.value.inspect }
78
78
  else
79
79
  arg.children.map(&:source)
80
80
  end
@@ -20,6 +20,7 @@ module RuboCop
20
20
  # lambda.(x, y)
21
21
  class LambdaCall < Base
22
22
  include ConfigurableEnforcedStyle
23
+ include IgnoredNode
23
24
  extend AutoCorrector
24
25
 
25
26
  MSG = 'Prefer the use of `%<prefer>s` over `%<current>s`.'
@@ -33,8 +34,12 @@ module RuboCop
33
34
  current = node.source
34
35
 
35
36
  add_offense(node, message: format(MSG, prefer: prefer, current: current)) do |corrector|
37
+ next if part_of_ignored_node?(node)
38
+
36
39
  opposite_style_detected
37
40
  corrector.replace(node, prefer)
41
+
42
+ ignore_node(node)
38
43
  end
39
44
  else
40
45
  correct_style_detected
@@ -146,7 +146,9 @@ module RuboCop
146
146
  end
147
147
 
148
148
  def call_in_match_pattern?(node)
149
- node.parent&.match_pattern_type?
149
+ return false unless (parent = node.parent)
150
+
151
+ parent.match_pattern_type? || parent.match_pattern_p_type?
150
152
  end
151
153
 
152
154
  def hash_literal_in_arguments?(node)
@@ -45,7 +45,7 @@ module RuboCop
45
45
  MSG = 'Avoid using `OpenStruct`; use `Struct`, `Hash`, a class or test doubles instead.'
46
46
 
47
47
  # @!method uses_open_struct?(node)
48
- def_node_matcher :uses_open_struct?, <<-PATTERN
48
+ def_node_matcher :uses_open_struct?, <<~PATTERN
49
49
  (const {nil? (cbase)} :OpenStruct)
50
50
  PATTERN
51
51
 
@@ -98,7 +98,9 @@ module RuboCop
98
98
  end
99
99
 
100
100
  def like_method_argument_parentheses?(node)
101
- node.send_type? && node.arguments.one? && !node.parenthesized? &&
101
+ return false if !node.send_type? && !node.super_type? && !node.yield_type?
102
+
103
+ node.arguments.one? && !node.parenthesized? &&
102
104
  !node.arithmetic_operation? && node.first_argument.begin_type?
103
105
  end
104
106
 
@@ -22,13 +22,18 @@ module RuboCop
22
22
  # return something
23
23
  # end
24
24
  #
25
- # # good
25
+ # # bad
26
26
  # def test
27
27
  # return something if something_else
28
28
  # end
29
29
  #
30
30
  # # good
31
31
  # def test
32
+ # something if something_else
33
+ # end
34
+ #
35
+ # # good
36
+ # def test
32
37
  # if x
33
38
  # elsif y
34
39
  # else
@@ -136,7 +141,7 @@ module RuboCop
136
141
  end
137
142
 
138
143
  def check_if_node(node)
139
- return if node.modifier_form? || node.ternary?
144
+ return if node.ternary?
140
145
 
141
146
  check_branch(node.if_branch)
142
147
  check_branch(node.else_branch)
@@ -75,6 +75,11 @@ module RuboCop
75
75
  add_offense(offense_branch) do |corrector|
76
76
  assignment_value = opposite_branch ? opposite_branch.source : 'nil'
77
77
  replacement = "#{assignment_value} #{keyword} #{if_node.condition.source}"
78
+ if opposite_branch.respond_to?(:heredoc?) && opposite_branch.heredoc?
79
+ replacement += opposite_branch.loc.heredoc_end.with(
80
+ begin_pos: opposite_branch.source_range.end_pos
81
+ ).source
82
+ end
78
83
 
79
84
  corrector.replace(if_node, replacement)
80
85
  end
@@ -3,22 +3,42 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Checks that quotes inside the string interpolation
6
+ # Checks that quotes inside string, symbol, and regexp interpolations
7
7
  # match the configured preference.
8
8
  #
9
9
  # @example EnforcedStyle: single_quotes (default)
10
10
  # # bad
11
- # result = "Tests #{success ? "PASS" : "FAIL"}"
11
+ # string = "Tests #{success ? "PASS" : "FAIL"}"
12
+ # symbol = :"Tests #{success ? "PASS" : "FAIL"}"
13
+ # heredoc = <<~TEXT
14
+ # Tests #{success ? "PASS" : "FAIL"}
15
+ # TEXT
16
+ # regexp = /Tests #{success ? "PASS" : "FAIL"}/
12
17
  #
13
18
  # # good
14
- # result = "Tests #{success ? 'PASS' : 'FAIL'}"
19
+ # string = "Tests #{success ? 'PASS' : 'FAIL'}"
20
+ # symbol = :"Tests #{success ? 'PASS' : 'FAIL'}"
21
+ # heredoc = <<~TEXT
22
+ # Tests #{success ? 'PASS' : 'FAIL'}
23
+ # TEXT
24
+ # regexp = /Tests #{success ? 'PASS' : 'FAIL'}/
15
25
  #
16
26
  # @example EnforcedStyle: double_quotes
17
27
  # # bad
18
- # result = "Tests #{success ? 'PASS' : 'FAIL'}"
28
+ # string = "Tests #{success ? 'PASS' : 'FAIL'}"
29
+ # symbol = :"Tests #{success ? 'PASS' : 'FAIL'}"
30
+ # heredoc = <<~TEXT
31
+ # Tests #{success ? 'PASS' : 'FAIL'}
32
+ # TEXT
33
+ # regexp = /Tests #{success ? 'PASS' : 'FAIL'}/
19
34
  #
20
35
  # # good
21
- # result = "Tests #{success ? "PASS" : "FAIL"}"
36
+ # string = "Tests #{success ? "PASS" : "FAIL"}"
37
+ # symbol = :"Tests #{success ? "PASS" : "FAIL"}"
38
+ # heredoc = <<~TEXT
39
+ # Tests #{success ? "PASS" : "FAIL"}
40
+ # TEXT
41
+ # regexp = /Tests #{success ? "PASS" : "FAIL"}/
22
42
  class StringLiteralsInInterpolation < Base
23
43
  include ConfigurableEnforcedStyle
24
44
  include StringLiteralsHelp
@@ -29,6 +49,11 @@ module RuboCop
29
49
  StringLiteralCorrector.correct(corrector, node, style)
30
50
  end
31
51
 
52
+ # Cop classes that include the StringHelp module usually ignore regexp
53
+ # nodes. Not so for this cop, which is why we override the on_regexp
54
+ # definition with an empty one.
55
+ def on_regexp(node); end
56
+
32
57
  private
33
58
 
34
59
  def message(_node)
@@ -69,9 +69,11 @@ module RuboCop
69
69
 
70
70
  def complex_content?(node)
71
71
  node.children.any? do |sym|
72
- content, = *sym
73
- content = content.to_s
74
- content_without_delimiter_pairs = content.gsub(/(\[\])|(\(\))/, '')
72
+ return false if DELIMITERS.include?(sym.source)
73
+
74
+ content = *sym
75
+ content = content.map { |c| c.is_a?(AST::Node) ? c.source : c }.join
76
+ content_without_delimiter_pairs = content.gsub(/(\[[^\s\[\]]*\])|(\([^\s\(\)]*\))/, '')
75
77
 
76
78
  content.include?(' ') || DELIMITERS.any? do |delimiter|
77
79
  content_without_delimiter_pairs.include?(delimiter)
@@ -6,27 +6,40 @@ module RuboCop
6
6
  # Helper to abstract complexity of building range pairs
7
7
  # with octal escape reconstruction (needed for regexp_parser < 2.7).
8
8
  class RegexpRanges
9
- attr_reader :compound_token, :root
9
+ attr_reader :root
10
10
 
11
11
  def initialize(root)
12
12
  @root = root
13
13
  @compound_token = []
14
+ @pairs = []
15
+ @populated = false
16
+ end
17
+
18
+ def compound_token
19
+ populate_all unless @populated
20
+
21
+ @compound_token
14
22
  end
15
23
 
16
24
  def pairs
17
- unless @pairs
18
- @pairs = []
19
- populate(root)
20
- end
25
+ populate_all unless @populated
26
+
27
+ @pairs
28
+ end
29
+
30
+ private
31
+
32
+ def populate_all
33
+ populate(@root)
21
34
 
22
35
  # If either bound is a compound the first one is an escape
23
36
  # and that's all we need to work with.
24
37
  # If there are any cops that wanted to operate on the compound
25
38
  # expression we could wrap it with a facade class.
26
- @pairs.map { |pair| pair.map(&:first) }
27
- end
39
+ @pairs.map! { |pair| pair.map(&:first) }
28
40
 
29
- private
41
+ @populated = true
42
+ end
30
43
 
31
44
  def populate(expr)
32
45
  expressions = expr.expressions.to_a
@@ -35,15 +48,15 @@ module RuboCop
35
48
  current = expressions.shift
36
49
 
37
50
  if escaped_octal?(current)
38
- compound_token << current
39
- compound_token.concat(pop_octal_digits(expressions))
51
+ @compound_token << current
52
+ @compound_token.concat(pop_octal_digits(expressions))
40
53
  # If we have all the digits we can discard.
41
54
  end
42
55
 
43
56
  next unless current.type == :set
44
57
 
45
58
  process_set(expressions, current)
46
- compound_token.clear
59
+ @compound_token.clear
47
60
  end
48
61
  end
49
62
 
@@ -64,8 +77,8 @@ module RuboCop
64
77
 
65
78
  def compose_range(expressions, current)
66
79
  range_start, range_end = current.expressions
67
- range_start = if compound_token.size.between?(1, 2) && octal_digit?(range_start.text)
68
- compound_token.dup << range_start
80
+ range_start = if @compound_token.size.between?(1, 2) && octal_digit?(range_start.text)
81
+ @compound_token.dup << range_start
69
82
  else
70
83
  [range_start]
71
84
  end
@@ -112,23 +112,29 @@ module RuboCop
112
112
  end
113
113
 
114
114
  handle 'workspace/executeCommand' do |request|
115
- if request[:params][:command] == 'rubocop.formatAutocorrects'
116
- uri = request[:params][:arguments][0][:uri]
117
- @server.write(
118
- id: request[:id],
119
- method: 'workspace/applyEdit',
120
- params: {
121
- label: 'Format with RuboCop autocorrects',
122
- edit: {
123
- changes: {
124
- uri => format_file(uri)
125
- }
126
- }
127
- }
128
- )
115
+ case (command = request[:params][:command])
116
+ when 'rubocop.formatAutocorrects'
117
+ label = 'Format with RuboCop autocorrects'
118
+ when 'rubocop.formatAutocorrectsAll'
119
+ label = 'Format all with RuboCop autocorrects'
129
120
  else
130
- handle_unsupported_method(request, request[:params][:command])
121
+ handle_unsupported_method(request, command)
122
+ return
131
123
  end
124
+
125
+ uri = request[:params][:arguments][0][:uri]
126
+ @server.write(
127
+ id: request[:id],
128
+ method: 'workspace/applyEdit',
129
+ params: {
130
+ label: label,
131
+ edit: {
132
+ changes: {
133
+ uri => format_file(uri, command: command)
134
+ }
135
+ }
136
+ }
137
+ )
132
138
  end
133
139
 
134
140
  handle 'textDocument/willSave' do |_request|
@@ -176,14 +182,14 @@ module RuboCop
176
182
  }
177
183
  end
178
184
 
179
- def format_file(file_uri)
185
+ def format_file(file_uri, command: nil)
180
186
  unless (text = @text_cache[file_uri])
181
187
  Logger.log("Format request arrived before text synchronized; skipping: `#{file_uri}'")
182
188
 
183
189
  return []
184
190
  end
185
191
 
186
- new_text = @server.format(remove_file_protocol_from(file_uri), text)
192
+ new_text = @server.format(remove_file_protocol_from(file_uri), text, command: command)
187
193
 
188
194
  return [] if new_text == text
189
195
 
@@ -35,9 +35,15 @@ module RuboCop
35
35
  # https://github.com/rubocop/rubocop/blob/v1.52.0/lib/rubocop/cli/command/execute_runner.rb#L95
36
36
  # Setting `parallel: true` would break this here:
37
37
  # https://github.com/rubocop/rubocop/blob/v1.52.0/lib/rubocop/runner.rb#L72
38
- def format(path, text)
38
+ def format(path, text, command:)
39
+ safe_autocorrect = if command
40
+ command == 'rubocop.formatAutocorrects'
41
+ else
42
+ @safe_autocorrect
43
+ end
44
+
39
45
  formatting_options = {
40
- stdin: text, force_exclusion: true, autocorrect: true, safe_autocorrect: @safe_autocorrect
46
+ stdin: text, force_exclusion: true, autocorrect: true, safe_autocorrect: safe_autocorrect
41
47
  }
42
48
  formatting_options[:only] = config_only_options if @lint_mode || @layout_mode
43
49
 
@@ -45,8 +45,8 @@ module RuboCop
45
45
  @writer.write(response)
46
46
  end
47
47
 
48
- def format(path, text)
49
- @runtime.format(path, text)
48
+ def format(path, text, command:)
49
+ @runtime.format(path, text, command: command)
50
50
  end
51
51
 
52
52
  def offenses(path, text)
@@ -421,10 +421,10 @@ module RuboCop
421
421
  end
422
422
 
423
423
  def considered_failure?(offense)
424
- # For :autocorrect level, any offense - corrected or not - is a failure.
425
424
  return false if offense.disabled?
426
425
 
427
- return true if @options[:fail_level] == :autocorrect
426
+ # For :autocorrect level, any correctable offense is a failure, regardless of severity
427
+ return true if @options[:fail_level] == :autocorrect && offense.correctable?
428
428
 
429
429
  !offense.corrected? && offense.severity >= minimum_severity_to_fail
430
430
  end
@@ -461,7 +461,9 @@ module RuboCop
461
461
  @minimum_severity_to_fail ||= begin
462
462
  # Unless given explicitly as `fail_level`, `:info` severity offenses do not fail
463
463
  name = @options[:fail_level] || :refactor
464
- RuboCop::Cop::Severity.new(name)
464
+
465
+ # autocorrect is a fake level - use the default
466
+ RuboCop::Cop::Severity.new(name == :autocorrect ? :refactor : name)
465
467
  end
466
468
  end
467
469
 
@@ -155,9 +155,11 @@ module RuboCop
155
155
  (send _ :required_ruby_version= $_)
156
156
  PATTERN
157
157
 
158
- # @!method gem_requirement?(node)
159
- def_node_matcher :gem_requirement?, <<~PATTERN
160
- (send (const(const _ :Gem):Requirement) :new $str)
158
+ # @!method gem_requirement_versions(node)
159
+ def_node_matcher :gem_requirement_versions, <<~PATTERN
160
+ (send (const(const _ :Gem):Requirement) :new
161
+ {$str+ | (send $str :freeze)+ | (array $str+) | (array (send $str :freeze)+)}
162
+ )
161
163
  PATTERN
162
164
 
163
165
  def name
@@ -194,10 +196,12 @@ module RuboCop
194
196
  end
195
197
 
196
198
  def version_from_right_hand_side(right_hand_side)
199
+ gem_requirement_versions = gem_requirement_versions(right_hand_side)
200
+
197
201
  if right_hand_side.array_type?
198
202
  version_from_array(right_hand_side)
199
- elsif gem_requirement?(right_hand_side)
200
- right_hand_side.children.last.value
203
+ elsif gem_requirement_versions
204
+ gem_requirement_versions.map(&:value)
201
205
  else
202
206
  right_hand_side.value
203
207
  end
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  # This module holds the RuboCop version information.
5
5
  module Version
6
- STRING = '1.55.1'
6
+ STRING = '1.56.1'
7
7
 
8
8
  MSG = '%<version>s (using Parser %<parser_version>s, ' \
9
9
  'rubocop-ast %<rubocop_ast_version>s, ' \
data/lib/rubocop.rb CHANGED
@@ -162,6 +162,7 @@ require_relative 'rubocop/cop/correctors/string_literal_corrector'
162
162
  require_relative 'rubocop/cop/correctors/unused_arg_corrector'
163
163
 
164
164
  require_relative 'rubocop/cop/bundler/duplicated_gem'
165
+ require_relative 'rubocop/cop/bundler/duplicated_group'
165
166
  require_relative 'rubocop/cop/bundler/gem_comment'
166
167
  require_relative 'rubocop/cop/bundler/gem_filename'
167
168
  require_relative 'rubocop/cop/bundler/gem_version'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.55.1
4
+ version: 1.56.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bozhidar Batsov
@@ -10,8 +10,22 @@ authors:
10
10
  autorequire:
11
11
  bindir: exe
12
12
  cert_chain: []
13
- date: 2023-07-31 00:00:00.000000000 Z
13
+ date: 2023-08-21 00:00:00.000000000 Z
14
14
  dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: base64
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - "~>"
20
+ - !ruby/object:Gem::Version
21
+ version: 0.1.1
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - "~>"
27
+ - !ruby/object:Gem::Version
28
+ version: 0.1.1
15
29
  - !ruby/object:Gem::Dependency
16
30
  name: json
17
31
  requirement: !ruby/object:Gem::Requirement
@@ -182,9 +196,9 @@ dependencies:
182
196
  - - "<"
183
197
  - !ruby/object:Gem::Version
184
198
  version: '3.0'
185
- description: |2
186
- RuboCop is a Ruby code style checking and code formatting tool.
187
- It aims to enforce the community-driven Ruby Style Guide.
199
+ description: |
200
+ RuboCop is a Ruby code style checking and code formatting tool.
201
+ It aims to enforce the community-driven Ruby Style Guide.
188
202
  email: rubocop@googlegroups.com
189
203
  executables:
190
204
  - rubocop
@@ -240,6 +254,7 @@ files:
240
254
  - lib/rubocop/cop/badge.rb
241
255
  - lib/rubocop/cop/base.rb
242
256
  - lib/rubocop/cop/bundler/duplicated_gem.rb
257
+ - lib/rubocop/cop/bundler/duplicated_group.rb
243
258
  - lib/rubocop/cop/bundler/gem_comment.rb
244
259
  - lib/rubocop/cop/bundler/gem_filename.rb
245
260
  - lib/rubocop/cop/bundler/gem_version.rb
@@ -1023,7 +1038,7 @@ metadata:
1023
1038
  homepage_uri: https://rubocop.org/
1024
1039
  changelog_uri: https://github.com/rubocop/rubocop/blob/master/CHANGELOG.md
1025
1040
  source_code_uri: https://github.com/rubocop/rubocop/
1026
- documentation_uri: https://docs.rubocop.org/rubocop/1.55/
1041
+ documentation_uri: https://docs.rubocop.org/rubocop/1.56/
1027
1042
  bug_tracker_uri: https://github.com/rubocop/rubocop/issues
1028
1043
  rubygems_mfa_required: 'true'
1029
1044
  post_install_message: