rubocop 1.59.0 → 1.60.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +3 -3
  4. data/config/default.yml +3 -1
  5. data/lib/rubocop/config.rb +0 -2
  6. data/lib/rubocop/config_loader.rb +0 -1
  7. data/lib/rubocop/config_validator.rb +0 -2
  8. data/lib/rubocop/cop/base.rb +6 -0
  9. data/lib/rubocop/cop/exclude_limit.rb +1 -1
  10. data/lib/rubocop/cop/layout/end_alignment.rb +5 -1
  11. data/lib/rubocop/cop/layout/first_array_element_indentation.rb +16 -1
  12. data/lib/rubocop/cop/layout/line_continuation_leading_space.rb +1 -1
  13. data/lib/rubocop/cop/lint/literal_assignment_in_condition.rb +12 -5
  14. data/lib/rubocop/cop/lint/shadowed_argument.rb +1 -0
  15. data/lib/rubocop/cop/lint/syntax.rb +6 -3
  16. data/lib/rubocop/cop/mixin/configurable_formatting.rb +1 -0
  17. data/lib/rubocop/cop/naming/block_forwarding.rb +10 -2
  18. data/lib/rubocop/cop/security/open.rb +2 -2
  19. data/lib/rubocop/cop/style/arguments_forwarding.rb +52 -11
  20. data/lib/rubocop/cop/style/collection_compact.rb +11 -2
  21. data/lib/rubocop/cop/style/conditional_assignment.rb +1 -1
  22. data/lib/rubocop/cop/style/each_for_simple_loop.rb +7 -7
  23. data/lib/rubocop/cop/style/eval_with_location.rb +0 -11
  24. data/lib/rubocop/cop/style/hash_each_methods.rb +3 -3
  25. data/lib/rubocop/cop/style/identical_conditional_branches.rb +4 -1
  26. data/lib/rubocop/cop/style/invertible_unless_condition.rb +39 -2
  27. data/lib/rubocop/cop/style/map_to_hash.rb +9 -5
  28. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +13 -5
  29. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +1 -3
  30. data/lib/rubocop/cop/style/multiline_ternary_operator.rb +1 -3
  31. data/lib/rubocop/cop/style/numeric_literal_prefix.rb +1 -1
  32. data/lib/rubocop/cop/style/parallel_assignment.rb +2 -2
  33. data/lib/rubocop/cop/style/parentheses_around_condition.rb +8 -0
  34. data/lib/rubocop/cop/style/redundant_each.rb +7 -4
  35. data/lib/rubocop/cop/style/redundant_line_continuation.rb +8 -1
  36. data/lib/rubocop/cop/style/redundant_parentheses.rb +18 -2
  37. data/lib/rubocop/cop/style/slicing_with_range.rb +76 -10
  38. data/lib/rubocop/cop/style/symbol_proc.rb +36 -0
  39. data/lib/rubocop/cops_documentation_generator.rb +11 -1
  40. data/lib/rubocop/ext/regexp_node.rb +9 -4
  41. data/lib/rubocop/formatter/disabled_config_formatter.rb +17 -6
  42. data/lib/rubocop/formatter/json_formatter.rb +0 -1
  43. data/lib/rubocop/formatter.rb +1 -1
  44. data/lib/rubocop/lsp/routes.rb +1 -1
  45. data/lib/rubocop/options.rb +0 -8
  46. data/lib/rubocop/rspec/shared_contexts.rb +6 -0
  47. data/lib/rubocop/rspec/support.rb +1 -0
  48. data/lib/rubocop/server/cache.rb +1 -2
  49. data/lib/rubocop/version.rb +1 -1
  50. metadata +11 -11
  51. /data/lib/rubocop/formatter/{git_hub_actions_formatter.rb → github_actions_formatter.rb} +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 236fd46f4a118eb34ff38fce1a147a116b2d30a7bf849ca809ece40aecabc694
4
- data.tar.gz: 550ecc4682d191a6a429a6af299212a42aa459e0aee04d5f7cb6a20ded419ff4
3
+ metadata.gz: e1a3d23f54a884c339ea9572297b0c8327493b96f2285cdb0db4585d1b76f9e3
4
+ data.tar.gz: c0df01858756b5b0c18a786c576ce2a21b6f36ffa99947111b8a03d5296c24eb
5
5
  SHA512:
6
- metadata.gz: 5c7cfaa0f6573e566bbbac89a993889cd0f0944ab5c012e0286da61021e2d3da6ed51f68fbbf6c71afd4b6d81c577d15228b7a6a2d93aed59d2dfcba61a9be63
7
- data.tar.gz: 5cbb4256ff218ab5744fc3065f56061e5b0eab787d21dc30f3ded5972b789ffc2c24cd977fbaf3acbcc84abf36c5e3537a2eeb328dabb3becf29268eccaa81f8
6
+ metadata.gz: 22ae311355c3b238f17df4d3e078920e531d706630ee41e2bd58543f082714da1b19948a81a377c83a34b2a7845b2eb2024cc64f7df97f9df5b5111b6be39541
7
+ data.tar.gz: a60635fbaec51410dd057156d80f07e533c4ce18a7ff83cf1a10d448e80fecbb33fbf3f074ee8172bce997c46c0c0b3019d4d21e19aede283cf5ea7e1f21c95d
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012-23 Bozhidar Batsov
1
+ Copyright (c) 2012-24 Bozhidar Batsov
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <img src="https://raw.githubusercontent.com/rubocop/rubocop/master/logo/rubo-logo-horizontal.png" alt="RuboCop Logo"/>
2
+ <img src="https://raw.githubusercontent.com/rubocop/rubocop/master/logo/rubo-logo-horizontal-white.png" alt="RuboCop Logo"/>
3
3
  </p>
4
4
 
5
5
  ----------
@@ -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.59', require: false
56
+ gem 'rubocop', '~> 1.60', require: false
57
57
  ```
58
58
 
59
59
  See [our versioning policy](https://docs.rubocop.org/rubocop/versioning.html) for further details.
@@ -248,5 +248,5 @@ RuboCop's changelog is available [here](CHANGELOG.md).
248
248
 
249
249
  ## Copyright
250
250
 
251
- Copyright (c) 2012-2023 Bozhidar Batsov. See [LICENSE.txt](LICENSE.txt) for
251
+ Copyright (c) 2012-2024 Bozhidar Batsov. See [LICENSE.txt](LICENSE.txt) for
252
252
  further details.
data/config/default.yml CHANGED
@@ -4712,6 +4712,7 @@ Style/OperatorMethodCall:
4712
4712
 
4713
4713
  Style/OptionHash:
4714
4714
  Description: "Don't use option hashes when you can use keyword arguments."
4715
+ StyleGuide: '#keyword-arguments-vs-option-hashes'
4715
4716
  Enabled: false
4716
4717
  VersionAdded: '0.33'
4717
4718
  VersionChanged: '0.34'
@@ -5237,7 +5238,8 @@ Style/SingleLineMethods:
5237
5238
  AllowIfMethodIsEmpty: true
5238
5239
 
5239
5240
  Style/SlicingWithRange:
5240
- Description: 'Checks array slicing is done with endless ranges when suitable.'
5241
+ Description: 'Checks array slicing is done with redundant, endless, and beginless ranges when suitable.'
5242
+ StyleGuide: '#slicing-with-ranges'
5241
5243
  Enabled: true
5242
5244
  VersionAdded: '0.83'
5243
5245
  Safe: false
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'pathname'
4
-
5
3
  # FIXME: Moving Rails department code to RuboCop Rails will remove
6
4
  # the following rubocop:disable comment.
7
5
  # rubocop:disable Metrics/ClassLength
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'erb'
4
- require 'pathname'
5
4
  require 'yaml'
6
5
  require_relative 'config_finder'
7
6
 
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'pathname'
4
-
5
3
  module RuboCop
6
4
  # Handles validation of configuration, for example cop names, parameter
7
5
  # names, and Ruby versions.
@@ -481,6 +481,12 @@ module RuboCop
481
481
  range.end_pos + @current_offset
482
482
  )
483
483
  end
484
+
485
+ # This experimental feature has been under consideration for a while.
486
+ # @api private
487
+ def lsp_mode?
488
+ ARGV.include?('--lsp')
489
+ end
484
490
  end
485
491
  end
486
492
  end
@@ -8,7 +8,7 @@ module RuboCop
8
8
  # The parameter name given is transformed into a method name (eg. `Max`
9
9
  # becomes `self.max=` and `MinDigits` becomes `self.min_digits=`).
10
10
  def exclude_limit(parameter_name, method_name: transform(parameter_name))
11
- define_method("#{method_name}=") do |value|
11
+ define_method(:"#{method_name}=") do |value|
12
12
  cfg = config_to_allow_offenses
13
13
  cfg[:exclude_limit] ||= {}
14
14
  current_max = cfg[:exclude_limit][parameter_name]
@@ -83,7 +83,11 @@ module RuboCop
83
83
  end
84
84
 
85
85
  def on_sclass(node)
86
- check_other_alignment(node)
86
+ if node.parent&.assignment?
87
+ check_asgn_alignment(node.parent, node)
88
+ else
89
+ check_other_alignment(node)
90
+ end
87
91
  end
88
92
 
89
93
  def on_module(node)
@@ -5,7 +5,10 @@ module RuboCop
5
5
  module Layout
6
6
  # Checks the indentation of the first element in an array literal
7
7
  # where the opening bracket and the first element are on separate lines.
8
- # The other elements' indentations are handled by the ArrayAlignment cop.
8
+ # The other elements' indentations are handled by `Layout/ArrayAlignment` cop.
9
+ #
10
+ # This cop will respect `Layout/ArrayAlignment` and will not work when
11
+ # `EnforcedStyle: with_fixed_indentation` is specified for `Layout/ArrayAlignment`.
9
12
  #
10
13
  # By default, array literals that are arguments in a method call with
11
14
  # parentheses, and where the opening square bracket of the array is on the
@@ -93,6 +96,8 @@ module RuboCop
93
96
  end
94
97
 
95
98
  def on_send(node)
99
+ return if style != :consistent && enforce_first_argument_with_fixed_indentation?
100
+
96
101
  each_argument_node(node, :array) do |array_node, left_parenthesis|
97
102
  check(array_node, left_parenthesis)
98
103
  end
@@ -174,6 +179,16 @@ module RuboCop
174
179
  'where the left bracket is.'
175
180
  end
176
181
  end
182
+
183
+ def enforce_first_argument_with_fixed_indentation?
184
+ return false unless array_alignment_config['Enabled']
185
+
186
+ array_alignment_config['EnforcedStyle'] == 'with_fixed_indentation'
187
+ end
188
+
189
+ def array_alignment_config
190
+ config.for_cop('Layout/ArrayAlignment')
191
+ end
177
192
  end
178
193
  end
179
194
  end
@@ -107,7 +107,7 @@ module RuboCop
107
107
  return false unless line.end_with?("\\\n")
108
108
 
109
109
  # Ensure backslash isn't part of a token spanning to the next line.
110
- node.children.none? { |c| c.first_line == line_num && c.multiline? }
110
+ node.children.none? { |c| (c.first_line...c.last_line).cover?(line_num) && c.multiline? }
111
111
  end
112
112
 
113
113
  def autocorrect(corrector, offense_range, insert_pos, spaces)
@@ -41,7 +41,7 @@ module RuboCop
41
41
  next unless asgn_node.loc.operator
42
42
 
43
43
  rhs = asgn_node.to_a.last
44
- next if !forbidden_literal?(rhs) || parallel_assignment_with_splat_operator?(rhs)
44
+ next if !all_literals?(rhs) || parallel_assignment_with_splat_operator?(rhs)
45
45
 
46
46
  range = offense_range(asgn_node, rhs)
47
47
 
@@ -59,10 +59,17 @@ module RuboCop
59
59
  node.each_child_node { |child| traverse_node(child, &block) }
60
60
  end
61
61
 
62
- def forbidden_literal?(node)
63
- return false if node.dstr_type? || node.xstr_type?
64
-
65
- node.respond_to?(:literal?) && node.literal?
62
+ def all_literals?(node)
63
+ case node.type
64
+ when :dstr, :xstr
65
+ false
66
+ when :array
67
+ node.values.all? { |value| all_literals?(value) }
68
+ when :hash
69
+ (node.values + node.keys).all? { |item| all_literals?(item) }
70
+ else
71
+ node.respond_to?(:literal?) && node.literal?
72
+ end
66
73
  end
67
74
 
68
75
  def parallel_assignment_with_splat_operator?(node)
@@ -123,6 +123,7 @@ module RuboCop
123
123
 
124
124
  # Shorthand assignments always use their arguments
125
125
  next false if assignment_node.shorthand_asgn?
126
+ next false unless assignment_node.parent
126
127
 
127
128
  node_within_block_or_conditional =
128
129
  node_within_block_or_conditional?(assignment_node.parent, argument.scope.node)
@@ -17,9 +17,12 @@ module RuboCop
17
17
  private
18
18
 
19
19
  def add_offense_from_diagnostic(diagnostic, ruby_version)
20
- message =
21
- "#{diagnostic.message}\n(Using Ruby #{ruby_version} parser; " \
22
- 'configure using `TargetRubyVersion` parameter, under `AllCops`)'
20
+ message = if lsp_mode?
21
+ diagnostic.message
22
+ else
23
+ "#{diagnostic.message}\n(Using Ruby #{ruby_version} parser; " \
24
+ 'configure using `TargetRubyVersion` parameter, under `AllCops`)'
25
+ end
23
26
  add_offense(diagnostic.location, message: message, severity: diagnostic.level)
24
27
  end
25
28
 
@@ -18,6 +18,7 @@ module RuboCop
18
18
  alternative_styles.each do |alternative|
19
19
  return unexpected_style_detected(alternative) if valid_name?(node, name, alternative)
20
20
  end
21
+ unrecognized_style_detected
21
22
  end
22
23
 
23
24
  def valid_name?(node, name, given_style = style)
@@ -51,21 +51,29 @@ module RuboCop
51
51
  [Lint::AmbiguousOperator, Style::ArgumentsForwarding]
52
52
  end
53
53
 
54
+ # rubocop:disable Metrics/CyclomaticComplexity
54
55
  def on_def(node)
55
56
  return if node.arguments.empty?
56
57
 
57
58
  last_argument = node.last_argument
58
59
  return if expected_block_forwarding_style?(node, last_argument)
59
60
 
60
- register_offense(last_argument, node)
61
-
61
+ invalid_syntax = false
62
62
  node.each_descendant(:block_pass) do |block_pass_node|
63
63
  next if block_pass_node.children.first&.sym_type? ||
64
64
  last_argument.source != block_pass_node.source
65
65
 
66
+ if block_pass_node.each_ancestor(:block, :numblock).any?
67
+ invalid_syntax = true
68
+ next
69
+ end
70
+
66
71
  register_offense(block_pass_node, node)
67
72
  end
73
+
74
+ register_offense(last_argument, node) unless invalid_syntax
68
75
  end
76
+ # rubocop:enable Metrics/CyclomaticComplexity
69
77
  alias on_defs on_def
70
78
 
71
79
  private
@@ -23,6 +23,7 @@ module RuboCop
23
23
  # # bad
24
24
  # open(something)
25
25
  # open("| #{something}")
26
+ # open("| foo")
26
27
  # URI.open(something)
27
28
  #
28
29
  # # good
@@ -32,7 +33,6 @@ module RuboCop
32
33
  #
33
34
  # # good (literal strings)
34
35
  # open("foo.text")
35
- # open("| foo")
36
36
  # URI.open("http://example.com")
37
37
  class Open < Base
38
38
  MSG = 'The use of `%<receiver>sopen` is a serious security risk.'
@@ -40,7 +40,7 @@ module RuboCop
40
40
 
41
41
  # @!method open?(node)
42
42
  def_node_matcher :open?, <<~PATTERN
43
- (send ${nil? (const {nil? cbase} :URI)} :open $!str ...)
43
+ (send ${nil? (const {nil? cbase} :URI)} :open $_ ...)
44
44
  PATTERN
45
45
 
46
46
  def on_send(node)
@@ -126,6 +126,7 @@ module RuboCop
126
126
  FORWARDING_MSG = 'Use shorthand syntax `...` for arguments forwarding.'
127
127
  ARGS_MSG = 'Use anonymous positional arguments forwarding (`*`).'
128
128
  KWARGS_MSG = 'Use anonymous keyword arguments forwarding (`**`).'
129
+ BLOCK_MSG = 'Use anonymous block arguments forwarding (`&`).'
129
130
 
130
131
  def self.autocorrect_incompatible_with
131
132
  [Naming::BlockForwarding]
@@ -171,27 +172,42 @@ module RuboCop
171
172
  send_classifications.all? { |_, c, _, _| c == :all }
172
173
  end
173
174
 
175
+ # rubocop:disable Metrics/MethodLength
174
176
  def add_forward_all_offenses(node, send_classifications, forwardable_args)
175
- send_classifications.each do |send_node, _c, forward_rest, _forward_kwrest|
176
- register_forward_all_offense(send_node, send_node, forward_rest)
177
+ _rest_arg, _kwrest_arg, block_arg = *forwardable_args
178
+ registered_block_arg_offense = false
179
+
180
+ send_classifications.each do |send_node, _c, forward_rest, forward_kwrest, forward_block_arg| # rubocop:disable Layout/LineLength
181
+ if !forward_rest && !forward_kwrest && outside_block?(forward_block_arg)
182
+ register_forward_block_arg_offense(!forward_rest, node.arguments, block_arg)
183
+ register_forward_block_arg_offense(!forward_rest, send_node, forward_block_arg)
184
+
185
+ registered_block_arg_offense = true
186
+ break
187
+ else
188
+ register_forward_all_offense(send_node, send_node, forward_rest)
189
+ end
177
190
  end
178
191
 
192
+ return if registered_block_arg_offense
193
+
179
194
  rest_arg, _kwrest_arg, _block_arg = *forwardable_args
180
195
  register_forward_all_offense(node, node.arguments, rest_arg)
181
196
  end
197
+ # rubocop:enable Metrics/MethodLength
182
198
 
183
199
  def add_post_ruby_32_offenses(def_node, send_classifications, forwardable_args)
184
200
  return unless use_anonymous_forwarding?
185
201
 
186
202
  rest_arg, kwrest_arg, _block_arg = *forwardable_args
187
203
 
188
- send_classifications.each do |send_node, _c, forward_rest, forward_kwrest|
189
- if forward_rest
204
+ send_classifications.each do |send_node, _c, forward_rest, forward_kwrest, _forward_block_arg| # rubocop:disable Layout/LineLength
205
+ if outside_block?(forward_rest)
190
206
  register_forward_args_offense(def_node.arguments, rest_arg)
191
207
  register_forward_args_offense(send_node, forward_rest)
192
208
  end
193
209
 
194
- if forward_kwrest
210
+ if outside_block?(forward_kwrest)
195
211
  register_forward_kwargs_offense(!forward_rest, def_node.arguments, kwrest_arg)
196
212
  register_forward_kwargs_offense(!forward_rest, send_node, forward_kwrest)
197
213
  end
@@ -225,10 +241,7 @@ module RuboCop
225
241
 
226
242
  def classification_and_forwards(def_node, send_node, referenced_lvars, forwardable_args)
227
243
  classifier = SendNodeClassifier.new(
228
- def_node,
229
- send_node,
230
- referenced_lvars,
231
- forwardable_args,
244
+ def_node, send_node, referenced_lvars, forwardable_args,
232
245
  target_ruby_version: target_ruby_version,
233
246
  allow_only_rest_arguments: allow_only_rest_arguments?
234
247
  )
@@ -237,7 +250,12 @@ module RuboCop
237
250
 
238
251
  return unless classification
239
252
 
240
- [classification, classifier.forwarded_rest_arg, classifier.forwarded_kwrest_arg]
253
+ [
254
+ classification,
255
+ classifier.forwarded_rest_arg,
256
+ classifier.forwarded_kwrest_arg,
257
+ classifier.forwarded_block_arg
258
+ ]
241
259
  end
242
260
 
243
261
  def redundant_named_arg(arg, config_name, keyword)
@@ -250,6 +268,12 @@ module RuboCop
250
268
  redundant_arg_names.include?(arg.source) ? arg : nil
251
269
  end
252
270
 
271
+ def outside_block?(node)
272
+ return false unless node
273
+
274
+ node.each_ancestor(:block, :numblock).none?
275
+ end
276
+
253
277
  def register_forward_args_offense(def_arguments_or_send, rest_arg_or_splat)
254
278
  add_offense(rest_arg_or_splat, message: ARGS_MSG) do |corrector|
255
279
  add_parens_if_missing(def_arguments_or_send, corrector)
@@ -266,6 +290,16 @@ module RuboCop
266
290
  end
267
291
  end
268
292
 
293
+ def register_forward_block_arg_offense(add_parens, def_arguments_or_send, block_arg)
294
+ return if target_ruby_version <= 3.0
295
+
296
+ add_offense(block_arg, message: BLOCK_MSG) do |corrector|
297
+ add_parens_if_missing(def_arguments_or_send, corrector) if add_parens
298
+
299
+ corrector.replace(block_arg, '&')
300
+ end
301
+ end
302
+
269
303
  def register_forward_all_offense(def_or_send, send_or_arguments, rest_or_splat)
270
304
  arg_range = arguments_range(def_or_send, rest_or_splat)
271
305
 
@@ -340,7 +374,7 @@ module RuboCop
340
374
  end
341
375
 
342
376
  def classification
343
- return nil unless forwarded_rest_arg || forwarded_kwrest_arg
377
+ return nil unless forwarded_rest_arg || forwarded_kwrest_arg || forwarded_block_arg
344
378
 
345
379
  if can_forward_all?
346
380
  :all
@@ -424,9 +458,16 @@ module RuboCop
424
458
  def no_additional_args?
425
459
  forwardable_count = [@rest_arg, @kwrest_arg, @block_arg].compact.size
426
460
 
461
+ return false if missing_rest_arg_or_kwrest_arg?
462
+
427
463
  @def_node.arguments.size == forwardable_count &&
428
464
  @send_node.arguments.size == forwardable_count
429
465
  end
466
+
467
+ def missing_rest_arg_or_kwrest_arg?
468
+ (@rest_arg_name && !forwarded_rest_arg) ||
469
+ (@kwrest_arg_name && !forwarded_kwrest_arg)
470
+ end
430
471
  end
431
472
  end
432
473
  end
@@ -23,6 +23,8 @@ module RuboCop
23
23
  # array.reject { |e| e.nil? }
24
24
  # array.delete_if { |e| e.nil? }
25
25
  # array.select { |e| !e.nil? }
26
+ # array.grep_v(nil)
27
+ # array.grep_v(NilClass)
26
28
  #
27
29
  # # good
28
30
  # array.compact
@@ -46,7 +48,7 @@ module RuboCop
46
48
  extend TargetRubyVersion
47
49
 
48
50
  MSG = 'Use `%<good>s` instead of `%<bad>s`.'
49
- RESTRICT_ON_SEND = %i[reject delete_if reject! select select!].freeze
51
+ RESTRICT_ON_SEND = %i[reject delete_if reject! select select! grep_v].freeze
50
52
  TO_ENUM_METHODS = %i[to_enum lazy].freeze
51
53
 
52
54
  minimum_target_ruby_version 2.4
@@ -79,6 +81,11 @@ module RuboCop
79
81
  $(lvar _) :nil?) :!))
80
82
  PATTERN
81
83
 
84
+ # @!method grep_v_with_nil?(node)
85
+ def_node_matcher :grep_v_with_nil?, <<~PATTERN
86
+ (send _ :grep_v {(nil) (const {nil? cbase} :NilClass)})
87
+ PATTERN
88
+
82
89
  def on_send(node)
83
90
  return unless (range = offense_range(node))
84
91
  return if allowed_receiver?(node.receiver)
@@ -95,8 +102,9 @@ module RuboCop
95
102
 
96
103
  private
97
104
 
105
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
98
106
  def offense_range(node)
99
- if reject_method_with_block_pass?(node)
107
+ if reject_method_with_block_pass?(node) || grep_v_with_nil?(node)
100
108
  range(node, node)
101
109
  else
102
110
  block_node = node.parent
@@ -110,6 +118,7 @@ module RuboCop
110
118
  range(node, block_node)
111
119
  end
112
120
  end
121
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
113
122
 
114
123
  def to_enum_method?(node)
115
124
  return false unless node.receiver.send_type?
@@ -233,7 +233,7 @@ module RuboCop
233
233
  PATTERN
234
234
 
235
235
  ASSIGNMENT_TYPES.each do |type|
236
- define_method "on_#{type}" do |node|
236
+ define_method :"on_#{type}" do |node|
237
237
  return if part_of_ignored_node?(node)
238
238
  return if node.parent&.shorthand_asgn?
239
239
 
@@ -32,27 +32,27 @@ module RuboCop
32
32
 
33
33
  send_node = node.send_node
34
34
 
35
- range = send_node.receiver.source_range.join(send_node.loc.selector)
36
-
37
- add_offense(range) do |corrector|
35
+ add_offense(send_node) do |corrector|
38
36
  range_type, min, max = each_range(node)
39
37
 
40
38
  max += 1 if range_type == :irange
41
39
 
42
- corrector.replace(node.send_node, "#{max - min}.times")
40
+ corrector.replace(send_node, "#{max - min}.times")
43
41
  end
44
42
  end
45
43
 
46
44
  private
47
45
 
48
46
  def offending?(node)
47
+ return false unless node.arguments.empty?
48
+
49
49
  each_range_with_zero_origin?(node) || each_range_without_block_argument?(node)
50
50
  end
51
51
 
52
52
  # @!method each_range(node)
53
53
  def_node_matcher :each_range, <<~PATTERN
54
54
  (block
55
- (send
55
+ (call
56
56
  (begin
57
57
  (${irange erange}
58
58
  (int $_) (int $_)))
@@ -64,7 +64,7 @@ module RuboCop
64
64
  # @!method each_range_with_zero_origin?(node)
65
65
  def_node_matcher :each_range_with_zero_origin?, <<~PATTERN
66
66
  (block
67
- (send
67
+ (call
68
68
  (begin
69
69
  ({irange erange}
70
70
  (int 0) (int _)))
@@ -76,7 +76,7 @@ module RuboCop
76
76
  # @!method each_range_without_block_argument?(node)
77
77
  def_node_matcher :each_range_without_block_argument?, <<~PATTERN
78
78
  (block
79
- (send
79
+ (call
80
80
  (begin
81
81
  ({irange erange}
82
82
  (int _) (int _)))
@@ -128,17 +128,6 @@ module RuboCop
128
128
  node.method?(:eval) ? node.arguments.size >= 2 : true
129
129
  end
130
130
 
131
- # FIXME: It's a Style/ConditionalAssignment's false positive.
132
- # rubocop:disable Style/ConditionalAssignment
133
- def with_lineno?(node)
134
- if node.method?(:eval)
135
- node.arguments.size == 4
136
- else
137
- node.arguments.size == 3
138
- end
139
- end
140
- # rubocop:enable Style/ConditionalAssignment
141
-
142
131
  def add_offense_for_incorrect_line(method_name, line_node, sign, line_diff)
143
132
  expected = expected_line(sign, line_diff)
144
133
  message = format(MSG_INCORRECT_LINE,
@@ -111,11 +111,11 @@ module RuboCop
111
111
  lvar_sources = node.body.each_descendant(:lvar).map(&:source)
112
112
 
113
113
  if block_arg.mlhs_type?
114
- block_arg.each_descendant(:arg).all? do |block_arg|
115
- lvar_sources.none?(block_arg.source)
114
+ block_arg.each_descendant(:arg, :restarg).all? do |block_arg|
115
+ lvar_sources.none?(block_arg.source.delete_prefix('*'))
116
116
  end
117
117
  else
118
- lvar_sources.none?(block_arg.source)
118
+ lvar_sources.none?(block_arg.source.delete_prefix('*'))
119
119
  end
120
120
  end
121
121
 
@@ -158,7 +158,10 @@ module RuboCop
158
158
  if head.assignment?
159
159
  # The `send` node is used instead of the `indexasgn` node, so `name` cannot be used.
160
160
  # https://github.com/rubocop/rubocop-ast/blob/v1.29.0/lib/rubocop/ast/node/indexasgn_node.rb
161
- assigned_value = head.send_type? ? head.receiver.source : head.name.to_s
161
+ #
162
+ # FIXME: It would be better to update `RuboCop::AST::OpAsgnNode` or its subclasses to
163
+ # handle `self.foo ||= value` as a solution, instead of using `head.node_parts[0].to_s`.
164
+ assigned_value = head.send_type? ? head.receiver.source : head.node_parts[0].to_s
162
165
 
163
166
  return if condition_variable == assigned_value
164
167
  end
@@ -51,7 +51,7 @@ module RuboCop
51
51
  class InvertibleUnlessCondition < Base
52
52
  extend AutoCorrector
53
53
 
54
- MSG = 'Favor `if` with inverted condition over `unless`.'
54
+ MSG = 'Prefer `%<prefer>s` over `%<current>s`.'
55
55
 
56
56
  def on_if(node)
57
57
  return unless node.unless?
@@ -59,7 +59,10 @@ module RuboCop
59
59
  condition = node.condition
60
60
  return unless invertible?(condition)
61
61
 
62
- add_offense(node) do |corrector|
62
+ message = format(MSG, prefer: "#{node.inverse_keyword} #{preferred_condition(condition)}",
63
+ current: "#{node.keyword} #{condition.source}")
64
+
65
+ add_offense(node, message: message) do |corrector|
63
66
  corrector.replace(node.loc.keyword, node.inverse_keyword)
64
67
  autocorrect(corrector, condition)
65
68
  end
@@ -88,6 +91,40 @@ module RuboCop
88
91
  (argument.const_type? && argument.short_name.to_s.upcase != argument.short_name.to_s)
89
92
  end
90
93
 
94
+ def preferred_condition(node)
95
+ case node.type
96
+ when :begin then "(#{preferred_condition(node.children.first)})"
97
+ when :send then preferred_send_condition(node)
98
+ when :or, :and then preferred_logical_condition(node)
99
+ end
100
+ end
101
+
102
+ def preferred_send_condition(node)
103
+ receiver_source = node.receiver.source
104
+ return receiver_source if node.method?(:!)
105
+
106
+ inverse_method_name = inverse_methods[node.method_name]
107
+ return "#{receiver_source}.#{inverse_method_name}" unless node.arguments?
108
+
109
+ argument_list = node.arguments.map(&:source).join(', ')
110
+ if node.operator_method?
111
+ return "#{receiver_source} #{inverse_method_name} #{argument_list}"
112
+ end
113
+
114
+ if node.parenthesized?
115
+ return "#{receiver_source}.#{inverse_method_name}(#{argument_list})"
116
+ end
117
+
118
+ "#{receiver_source}.#{inverse_method_name} #{argument_list}"
119
+ end
120
+
121
+ def preferred_logical_condition(node)
122
+ preferred_lhs = preferred_condition(node.lhs)
123
+ preferred_rhs = preferred_condition(node.rhs)
124
+
125
+ "#{preferred_lhs} #{node.inverse_operator} #{preferred_rhs}"
126
+ end
127
+
91
128
  def autocorrect(corrector, node)
92
129
  case node.type
93
130
  when :begin