rubocop 1.55.1 → 1.56.1

Sign up to get free protection for your applications and to get access to all the features.
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: