rubocop 1.63.5 → 1.64.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1911c884de81dd524490fea5075e7605d3ccf82fad85ebc9d09adbe5d29637ca
4
- data.tar.gz: 8206c237dd18bbcd9769d55b1206d160ce766a21caa5c8f8ea956ae845371ea2
3
+ metadata.gz: b6dfe4310319b850aafab50e198152b52585d4e9da81d18b5a83dba9ecf73b62
4
+ data.tar.gz: a25d711605d517c0b1a4ed89eea3f5e65be8803a74f4c92d0b94d43ba9f7a4dd
5
5
  SHA512:
6
- metadata.gz: 429dfdfda489516f4856fa00ad50c062a172588ca4bb1006baa25213c2fb949b0781af446d689200f0a87017f47708ad49324a6644cce104f79dd42b129dd680
7
- data.tar.gz: ce8363fa68a56f9c5f36c40d1596932b1233854823c54bfb9dc36a669256dca64459186037fe10e9723a550cec0a6f11df2e1d802e4471d8c9d1e98db8c528f3
6
+ metadata.gz: 10d92f3f642368e7126f5066662c4e725c5743847efc8fa17c123390534c47ab528ce6ec986ff93800f569046c86988b4ffaa84c1ae89f2c44365f7867fda6f4
7
+ data.tar.gz: e27fdf438f351200f01a33fba1059d678c37d4759eba3600b9c0f7d1f9329a344ff82e23518d38a4959c06069a1a2693447d2a5df8b202f1b11dad2ff012bae5
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.63', require: false
56
+ gem 'rubocop', '~> 1.64', 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
@@ -156,7 +156,7 @@ AllCops:
156
156
  # included.
157
157
  SuggestExtensions:
158
158
  rubocop-rails: [rails]
159
- rubocop-rspec: [rspec]
159
+ rubocop-rspec: [rspec, rspec-rails]
160
160
  rubocop-minitest: [minitest]
161
161
  rubocop-sequel: [sequel]
162
162
  rubocop-rake: [rake]
@@ -3090,6 +3090,7 @@ Style/AccessModifierDeclarations:
3090
3090
  - inline
3091
3091
  - group
3092
3092
  AllowModifiersOnSymbols: true
3093
+ AllowModifiersOnAttrs: true
3093
3094
  SafeAutoCorrect: false
3094
3095
 
3095
3096
  Style/AccessorGrouping:
@@ -3687,6 +3688,7 @@ Style/DocumentationMethod:
3687
3688
  Description: 'Checks for missing documentation comment for public methods.'
3688
3689
  Enabled: false
3689
3690
  VersionAdded: '0.43'
3691
+ AllowedMethods: []
3690
3692
  Exclude:
3691
3693
  - 'spec/**/*'
3692
3694
  - 'test/**/*'
@@ -4068,6 +4070,8 @@ Style/HashSyntax:
4068
4070
  - either
4069
4071
  # forces use of the 3.1 syntax only if all values can be omitted in the hash.
4070
4072
  - consistent
4073
+ # allow either (implicit or explicit) syntax but enforce consistency within a single hash
4074
+ - either_consistent
4071
4075
  # Force hashes that have a symbol value to use hash rockets
4072
4076
  UseHashRocketsWithSymbolValues: false
4073
4077
  # Do not suggest { a?: 1 } over { :a? => 1 } in ruby19 style
@@ -5240,6 +5244,13 @@ Style/Send:
5240
5244
  Enabled: false
5241
5245
  VersionAdded: '0.33'
5242
5246
 
5247
+ Style/SendWithLiteralMethodName:
5248
+ Description: 'Detects the use of the `public_send` method with a static method name argument.'
5249
+ Enabled: pending
5250
+ Safe: false
5251
+ AllowSend: true
5252
+ VersionAdded: '1.64'
5253
+
5243
5254
  Style/SignalException:
5244
5255
  Description: 'Checks for proper usage of fail and raise.'
5245
5256
  StyleGuide: '#prefer-raise-over-fail'
@@ -5414,6 +5425,11 @@ Style/StructInheritance:
5414
5425
  VersionAdded: '0.29'
5415
5426
  VersionChanged: '1.20'
5416
5427
 
5428
+ Style/SuperArguments:
5429
+ Description: 'Call `super` without arguments and parentheses when the signature is identical.'
5430
+ Enabled: pending
5431
+ VersionAdded: '1.64'
5432
+
5417
5433
  Style/SuperWithArgsParentheses:
5418
5434
  Description: 'Use parentheses for `super` with arguments.'
5419
5435
  StyleGuide: '#super-with-args'
@@ -5449,7 +5465,7 @@ Style/SymbolProc:
5449
5465
  Enabled: true
5450
5466
  Safe: false
5451
5467
  VersionAdded: '0.26'
5452
- VersionChanged: '1.40'
5468
+ VersionChanged: '1.64'
5453
5469
  AllowMethodsWithArguments: false
5454
5470
  # A list of method names to be always allowed by the check.
5455
5471
  # The names should be fairly unique, otherwise you'll end up ignoring lots of code.
@@ -26,10 +26,10 @@ module RuboCop
26
26
 
27
27
  cops_array.each do |cop_name|
28
28
  cop = registry_hash[cop_name]
29
-
30
29
  next if cop.empty?
31
30
 
32
- puts Cop::Documentation.url_for(cop.first, @config)
31
+ url = Cop::Documentation.url_for(cop.first, @config)
32
+ puts url if url
33
33
  end
34
34
 
35
35
  puts
@@ -319,9 +319,8 @@ module RuboCop
319
319
  # @param [Gem::Version] gem_version an object like `Gem::Version.new("7.1.2.3")`
320
320
  # @return [Float] The major and minor version, like `7.1`
321
321
  def gem_version_to_major_minor_float(gem_version)
322
- segments = gem_version.canonical_segments
323
- # segments.fetch(0).to_f + (segments.fetch(1, 0.0).to_f / 10)
324
- Float("#{segments.fetch(0)}.#{segments.fetch(1, 0)}")
322
+ segments = gem_version.segments
323
+ Float("#{segments[0]}.#{segments[1]}")
325
324
  end
326
325
 
327
326
  # @returns [Hash{String => Gem::Version}] The locked gem versions, keyed by the gems' names.
@@ -60,12 +60,15 @@ module RuboCop
60
60
  []
61
61
  end
62
62
 
63
- # Cops (other than builtin) are encouraged to implement this
63
+ # Returns an url to view this cops documentation online.
64
+ # Requires 'DocumentationBaseURL' to be set for your department.
65
+ # Will follow the convention of RuboCops own documentation structure,
66
+ # overwrite this method to accommodate your custom layout.
64
67
  # @return [String, nil]
65
68
  #
66
69
  # @api public
67
- def self.documentation_url
68
- Documentation.url_for(self) if builtin?
70
+ def self.documentation_url(config = nil)
71
+ Documentation.url_for(self, config)
69
72
  end
70
73
 
71
74
  def self.inherited(subclass)
@@ -398,16 +401,6 @@ module RuboCop
398
401
 
399
402
  ### Actually private methods
400
403
 
401
- # rubocop:disable Layout/ClassStructure
402
- def self.builtin?
403
- return false unless (m = instance_methods(false).first) # any custom method will do
404
-
405
- path, _line = instance_method(m).source_location
406
- path.start_with?(__dir__)
407
- end
408
- private_class_method :builtin?
409
- # rubocop:enable Layout/ClassStructure
410
-
411
404
  def reset_investigation
412
405
  @currently_disabled_lines = @current_offenses = @processed_source = @current_corrector = nil
413
406
  end
@@ -90,13 +90,11 @@ module RuboCop
90
90
  Array(cop_config['AllowedGems'])
91
91
  end
92
92
 
93
- def message(range)
94
- gem_specification = range.source
95
-
93
+ def message(_range)
96
94
  if required_style?
97
- format(REQUIRED_MSG, gem_specification: gem_specification)
95
+ REQUIRED_MSG
98
96
  elsif forbidden_style?
99
- format(FORBIDDEN_MSG, gem_specification: gem_specification)
97
+ FORBIDDEN_MSG
100
98
  end
101
99
  end
102
100
 
@@ -17,23 +17,33 @@ module RuboCop
17
17
  fragment = cop_class.cop_name.downcase.gsub(/[^a-z]/, '')
18
18
  base_url = base_url_for(cop_class, config)
19
19
 
20
- "#{base_url}/#{base}.html##{fragment}"
20
+ "#{base_url}/#{base}.html##{fragment}" if base_url
21
21
  end
22
22
 
23
23
  # @api private
24
24
  def base_url_for(cop_class, config)
25
- return default_base_url unless config
25
+ if config
26
+ department_name = cop_class.department.to_s
27
+ url = config.for_department(department_name)['DocumentationBaseURL']
28
+ return url if url
29
+ end
26
30
 
27
- department_name = cop_class.department.to_s
28
-
29
- config.for_department(department_name)['DocumentationBaseURL'] ||
30
- config.for_all_cops['DocumentationBaseURL']
31
+ default_base_url if builtin?(cop_class)
31
32
  end
32
33
 
33
34
  # @api private
34
35
  def default_base_url
35
36
  'https://docs.rubocop.org/rubocop'
36
37
  end
38
+
39
+ # @api private
40
+ def builtin?(cop_class)
41
+ # any custom method will do
42
+ return false unless (m = cop_class.instance_methods(false).first)
43
+
44
+ path, _line = cop_class.instance_method(m).source_location
45
+ path.start_with?(__dir__)
46
+ end
37
47
  end
38
48
  end
39
49
  end
@@ -101,13 +101,11 @@ module RuboCop
101
101
  Array(cop_config['AllowedGems'])
102
102
  end
103
103
 
104
- def message(range)
105
- gem_specification = range.source
106
-
104
+ def message(_range)
107
105
  if required_style?
108
- format(REQUIRED_MSG, gem_specification: gem_specification)
106
+ REQUIRED_MSG
109
107
  elsif forbidden_style?
110
- format(FORBIDDEN_MSG, gem_specification: gem_specification)
108
+ FORBIDDEN_MSG
111
109
  end
112
110
  end
113
111
 
@@ -106,7 +106,9 @@ module RuboCop
106
106
  end
107
107
 
108
108
  def concat_consecutive_comments(comments)
109
- consecutive_comments = comments.chunk_while { |i, j| i.loc.line.succ == j.loc.line }
109
+ consecutive_comments = comments.chunk_while do |i, j|
110
+ i.loc.line.succ == j.loc.line && i.loc.column == j.loc.column
111
+ end
110
112
 
111
113
  consecutive_comments.map do |chunk|
112
114
  joined_text = chunk.map { |c| comment_text(c) }.join
@@ -43,13 +43,13 @@ module RuboCop
43
43
  types.map do |type|
44
44
  case type
45
45
  when :array
46
- ->(node) { node.array_type? }
46
+ lambda(&:array_type?)
47
47
  when :hash
48
- ->(node) { node.hash_type? }
48
+ lambda(&:hash_type?)
49
49
  when :heredoc
50
50
  ->(node) { heredoc_node?(node) }
51
51
  when :method_call
52
- ->(node) { node.call_type? }
52
+ lambda(&:call_type?)
53
53
  else
54
54
  raise Warning, "Unknown foldable type: #{type.inspect}. " \
55
55
  "Valid foldable types are: #{FOLDABLE_TYPES.join(', ')}."
@@ -67,13 +67,14 @@ module RuboCop
67
67
  end
68
68
 
69
69
  def ignore_mixed_hash_shorthand_syntax?(hash_node)
70
- target_ruby_version <= 3.0 || enforced_shorthand_syntax != 'consistent' ||
70
+ target_ruby_version <= 3.0 ||
71
+ !%w[consistent either_consistent].include?(enforced_shorthand_syntax) ||
71
72
  !hash_node.hash_type?
72
73
  end
73
74
 
74
75
  def ignore_hash_shorthand_syntax?(pair_node)
75
76
  target_ruby_version <= 3.0 || enforced_shorthand_syntax == 'either' ||
76
- enforced_shorthand_syntax == 'consistent' ||
77
+ %w[consistent either_consistent].include?(enforced_shorthand_syntax) ||
77
78
  !pair_node.parent.hash_type?
78
79
  end
79
80
 
@@ -172,6 +173,11 @@ module RuboCop
172
173
  hash_value_type_breakdown[:value_needed]&.any?
173
174
  end
174
175
 
176
+ def ignore_explicit_omissible_hash_shorthand_syntax?(hash_value_type_breakdown)
177
+ hash_value_type_breakdown.keys == [:value_omittable] &&
178
+ enforced_shorthand_syntax == 'either_consistent'
179
+ end
180
+
175
181
  def each_omitted_value_pair(hash_value_type_breakdown, &block)
176
182
  hash_value_type_breakdown[:value_omitted]&.each(&block)
177
183
  end
@@ -198,6 +204,7 @@ module RuboCop
198
204
 
199
205
  def no_mixed_shorthand_syntax_check(hash_value_type_breakdown)
200
206
  return if hash_with_values_that_cant_be_omitted?(hash_value_type_breakdown)
207
+ return if ignore_explicit_omissible_hash_shorthand_syntax?(hash_value_type_breakdown)
201
208
 
202
209
  each_omittable_value_pair(hash_value_type_breakdown) do |pair_node|
203
210
  hash_key_source = pair_node.key.source
@@ -8,6 +8,17 @@ module RuboCop
8
8
  # EnforcedStyle config covers only method definitions.
9
9
  # Applications of visibility methods to symbols can be controlled
10
10
  # using AllowModifiersOnSymbols config.
11
+ # Also, the visibility of `attr*` methods can be controlled using
12
+ # AllowModifiersOnAttrs config.
13
+ #
14
+ # In Ruby 3.0, `attr*` methods now return an array of defined method names
15
+ # as symbols. So we can write the modifier and `attr*` in inline style.
16
+ # AllowModifiersOnAttrs config allows `attr*` methods to be written in
17
+ # inline style without modifying applications that have been maintained
18
+ # for a long time in group style. Furthermore, developers who are not very
19
+ # familiar with Ruby may know that the modifier applies to `def`, but they
20
+ # may not know that it also applies to `attr*` methods. It would be easier
21
+ # to understand if we could write `attr*` methods in inline style.
11
22
  #
12
23
  # @safety
13
24
  # Autocorrection is not safe, because the visibility of dynamically
@@ -67,6 +78,34 @@ module RuboCop
67
78
  # private :bar, :baz
68
79
  #
69
80
  # end
81
+ #
82
+ # @example AllowModifiersOnAttrs: true (default)
83
+ # # good
84
+ # class Foo
85
+ #
86
+ # public attr_reader :bar
87
+ # protected attr_writer :baz
88
+ # private attr_accessor :qux
89
+ # private attr :quux
90
+ #
91
+ # def public_method; end
92
+ #
93
+ # private
94
+ #
95
+ # def private_method; end
96
+ #
97
+ # end
98
+ #
99
+ # @example AllowModifiersOnAttrs: false
100
+ # # bad
101
+ # class Foo
102
+ #
103
+ # public attr_reader :bar
104
+ # protected attr_writer :baz
105
+ # private attr_accessor :qux
106
+ # private attr :quux
107
+ #
108
+ # end
70
109
  class AccessModifierDeclarations < Base
71
110
  extend AutoCorrector
72
111
 
@@ -92,10 +131,17 @@ module RuboCop
92
131
  (send nil? {:private :protected :public :module_function} (sym _))
93
132
  PATTERN
94
133
 
134
+ # @!method access_modifier_with_attr?(node)
135
+ def_node_matcher :access_modifier_with_attr?, <<~PATTERN
136
+ (send nil? {:private :protected :public :module_function}
137
+ (send nil? {:attr :attr_reader :attr_writer :attr_accessor} _))
138
+ PATTERN
139
+
95
140
  def on_send(node)
96
141
  return unless node.access_modifier?
97
142
  return if ALLOWED_NODE_TYPES.include?(node.parent&.type)
98
143
  return if allow_modifiers_on_symbols?(node)
144
+ return if allow_modifiers_on_attrs?(node)
99
145
 
100
146
  if offense?(node)
101
147
  add_offense(node.loc.selector) do |corrector|
@@ -128,6 +174,10 @@ module RuboCop
128
174
  cop_config['AllowModifiersOnSymbols'] && access_modifier_with_symbol?(node)
129
175
  end
130
176
 
177
+ def allow_modifiers_on_attrs?(node)
178
+ cop_config['AllowModifiersOnAttrs'] && access_modifier_with_attr?(node)
179
+ end
180
+
131
181
  def offense?(node)
132
182
  (group_style? && access_modifier_is_inlined?(node) &&
133
183
  !right_siblings_same_inline_method?(node)) ||
@@ -29,6 +29,8 @@ module RuboCop
29
29
  #
30
30
  # Names not on this list are likely to be meaningful and are allowed by default.
31
31
  #
32
+ # This cop handles not only method forwarding but also forwarding to `super`.
33
+ #
32
34
  # @example
33
35
  # # bad
34
36
  # def foo(*args, &block)
@@ -146,7 +148,7 @@ module RuboCop
146
148
 
147
149
  restarg, kwrestarg, blockarg = extract_forwardable_args(node.arguments)
148
150
  forwardable_args = redundant_forwardable_named_args(restarg, kwrestarg, blockarg)
149
- send_nodes = node.each_descendant(:send).to_a
151
+ send_nodes = node.each_descendant(:send, :csend, :super).to_a
150
152
 
151
153
  send_classifications = classify_send_nodes(
152
154
  node, send_nodes, non_splat_or_block_pass_lvar_references(node.body), forwardable_args
@@ -61,7 +61,7 @@ module RuboCop
61
61
  raise Warning, AUTOCORRECT_EMPTY_WARNING if autocorrect_notice.empty?
62
62
 
63
63
  regex = Regexp.new(notice)
64
- return if autocorrect_notice&.match?(regex)
64
+ return if autocorrect_notice.gsub(/^# */, '').match?(regex)
65
65
 
66
66
  raise Warning, "AutocorrectNotice '#{autocorrect_notice}' must match Notice /#{notice}/"
67
67
  end
@@ -77,26 +77,28 @@ module RuboCop
77
77
  return false if token_index >= processed_source.tokens.size
78
78
 
79
79
  token = processed_source.tokens[token_index]
80
- token.comment? && /^#!.*$/.match?(token.text)
80
+ token.comment? && /\A#!.*\z/.match?(token.text)
81
81
  end
82
82
 
83
83
  def encoding_token?(processed_source, token_index)
84
84
  return false if token_index >= processed_source.tokens.size
85
85
 
86
86
  token = processed_source.tokens[token_index]
87
- token.comment? && /^#.*coding\s?[:=]\s?(?:UTF|utf)-8/.match?(token.text)
87
+ token.comment? && /\A#.*coding\s?[:=]\s?(?:UTF|utf)-8/.match?(token.text)
88
88
  end
89
89
 
90
90
  def notice_found?(processed_source)
91
- notice_found = false
92
- notice_regexp = Regexp.new(notice)
91
+ notice_regexp = Regexp.new(notice.lines.map(&:strip).join)
92
+ multiline_notice = +''
93
93
  processed_source.tokens.each do |token|
94
94
  break unless token.comment?
95
95
 
96
- notice_found = notice_regexp.match?(token.text)
97
- break if notice_found
96
+ multiline_notice << token.text.sub(/\A# */, '')
97
+
98
+ break if notice_regexp.match?(token.text)
98
99
  end
99
- notice_found
100
+
101
+ multiline_notice.match?(notice_regexp)
100
102
  end
101
103
  end
102
104
  end
@@ -95,6 +95,17 @@ module RuboCop
95
95
  # end
96
96
  # end
97
97
  #
98
+ # @example AllowedMethods: ['method_missing', 'respond_to_missing?']
99
+ #
100
+ # # good
101
+ # class Foo
102
+ # def method_missing(name, *args)
103
+ # end
104
+ #
105
+ # def respond_to_missing?(symbol, include_private)
106
+ # end
107
+ # end
108
+ #
98
109
  class DocumentationMethod < Base
99
110
  include DocumentationComment
100
111
  include DefNode
@@ -119,6 +130,7 @@ module RuboCop
119
130
  def check(node)
120
131
  return if non_public?(node) && !require_for_non_public_methods?
121
132
  return if documentation_comment?(node)
133
+ return if method_allowed?(node)
122
134
 
123
135
  add_offense(node)
124
136
  end
@@ -126,6 +138,14 @@ module RuboCop
126
138
  def require_for_non_public_methods?
127
139
  cop_config['RequireForNonPublicMethods']
128
140
  end
141
+
142
+ def method_allowed?(node)
143
+ allowed_methods.include?(node.method_name)
144
+ end
145
+
146
+ def allowed_methods
147
+ @allowed_methods ||= cop_config.fetch('AllowedMethods', []).map(&:to_sym)
148
+ end
129
149
  end
130
150
  end
131
151
  end
@@ -29,6 +29,8 @@ module RuboCop
29
29
  # * never - forces use of explicit hash literal value
30
30
  # * either - accepts both shorthand and explicit use of hash literal value
31
31
  # * consistent - forces use of the 3.1 syntax only if all values can be omitted in the hash
32
+ # * either_consistent - accepts both shorthand and explicit use of hash literal value,
33
+ # but they must be consistent
32
34
  #
33
35
  # @example EnforcedStyle: ruby19 (default)
34
36
  # # bad
@@ -110,6 +112,22 @@ module RuboCop
110
112
  # # good - can't omit `baz`
111
113
  # {foo: foo, bar: baz}
112
114
  #
115
+ # @example EnforcedShorthandSyntax: either_consistent
116
+ #
117
+ # # good - `foo` and `bar` values can be omitted, but they are consistent, so it's accepted
118
+ # {foo: foo, bar: bar}
119
+ #
120
+ # # bad - `bar` value can be omitted
121
+ # {foo:, bar: bar}
122
+ #
123
+ # # bad - mixed syntaxes
124
+ # {foo:, bar: baz}
125
+ #
126
+ # # good
127
+ # {foo:, bar:}
128
+ #
129
+ # # good - can't omit `baz`
130
+ # {foo: foo, bar: baz}
113
131
  class HashSyntax < Base
114
132
  include ConfigurableEnforcedStyle
115
133
  include HashShorthandSyntax
@@ -112,9 +112,11 @@ module RuboCop
112
112
  end
113
113
 
114
114
  def message(node, keyword)
115
- message_template = node.elsif? ? MSG_FOR_ELSIF : MSG
116
-
117
- format(message_template, keyword: keyword)
115
+ if node.elsif?
116
+ MSG_FOR_ELSIF
117
+ else
118
+ format(MSG, keyword: keyword)
119
+ end
118
120
  end
119
121
 
120
122
  def return_boolean_value?(condition)
@@ -57,7 +57,7 @@ module RuboCop
57
57
  def_node_matcher :each_block_with_push?, <<-PATTERN
58
58
  [
59
59
  ^({begin kwbegin} ...)
60
- ({block numblock} (send _ :each) _
60
+ ({block numblock} (send !{nil? self} :each) _
61
61
  (send (lvar _) {:<< :push :append} _))
62
62
  ]
63
63
  PATTERN
@@ -75,7 +75,7 @@ module RuboCop
75
75
  end
76
76
 
77
77
  def always_multiline?
78
- @config.for_cop('Style/OneLineConditional')['AlwaysCorrectToMultiline']
78
+ cop_config['AlwaysCorrectToMultiline']
79
79
  end
80
80
 
81
81
  def cannot_replace_to_ternary?(node)
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Detects the use of the `public_send` method with a literal method name argument.
7
+ # Since the `send` method can be used to call private methods, by default,
8
+ # only the `public_send` method is detected.
9
+ #
10
+ # @safety
11
+ # This cop is not safe because it can incorrectly detect based on the receiver.
12
+ # Additionally, when `AllowSend` is set to `true`, it cannot determine whether
13
+ # the `send` method being detected is calling a private method.
14
+ #
15
+ # @example
16
+ # # bad
17
+ # obj.public_send(:method_name)
18
+ # obj.public_send('method_name')
19
+ #
20
+ # # good
21
+ # obj.method_name
22
+ #
23
+ # @example AllowSend: true (default)
24
+ # # good
25
+ # obj.send(:method_name)
26
+ # obj.send('method_name')
27
+ # obj.__send__(:method_name)
28
+ # obj.__send__('method_name')
29
+ #
30
+ # @example AllowSend: false
31
+ # # bad
32
+ # obj.send(:method_name)
33
+ # obj.send('method_name')
34
+ # obj.__send__(:method_name)
35
+ # obj.__send__('method_name')
36
+ #
37
+ # # good
38
+ # obj.method_name
39
+ #
40
+ class SendWithLiteralMethodName < Base
41
+ extend AutoCorrector
42
+
43
+ MSG = 'Use `%<method_name>s` method call directly instead.'
44
+ RESTRICT_ON_SEND = %i[public_send send __send__].freeze
45
+ STATIC_METHOD_NAME_NODE_TYPES = %i[sym str].freeze
46
+
47
+ # rubocop:disable Metrics/AbcSize
48
+ def on_send(node)
49
+ return if allow_send? && !node.method?(:public_send)
50
+ return unless (first_argument = node.first_argument)
51
+ return unless STATIC_METHOD_NAME_NODE_TYPES.include?(first_argument.type)
52
+
53
+ offense_range = offense_range(node)
54
+ method_name = first_argument.value
55
+
56
+ add_offense(offense_range, message: format(MSG, method_name: method_name)) do |corrector|
57
+ if node.arguments.one?
58
+ corrector.replace(offense_range, method_name)
59
+ else
60
+ corrector.replace(node.loc.selector, method_name)
61
+ corrector.remove(removal_argument_range(first_argument, node.arguments[1]))
62
+ end
63
+ end
64
+ end
65
+ # rubocop:enable Metrics/AbcSize
66
+
67
+ private
68
+
69
+ def allow_send?
70
+ !!cop_config['AllowSend']
71
+ end
72
+
73
+ def offense_range(node)
74
+ node.loc.selector.join(node.source_range.end)
75
+ end
76
+
77
+ def removal_argument_range(first_argument, second_argument)
78
+ first_argument.source_range.begin.join(second_argument.source_range.begin)
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Checks for redundant argument forwarding when calling super
7
+ # with arguments identical to the method definition.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # def method(*args, **kwargs)
12
+ # super(*args, **kwargs)
13
+ # end
14
+ #
15
+ # # good - implicitly passing all arguments
16
+ # def method(*args, **kwargs)
17
+ # super
18
+ # end
19
+ #
20
+ # # good - forwarding a subset of the arguments
21
+ # def method(*args, **kwargs)
22
+ # super(*args)
23
+ # end
24
+ #
25
+ # # good - forwarding no arguments
26
+ # def method(*args, **kwargs)
27
+ # super()
28
+ # end
29
+ class SuperArguments < Base
30
+ extend AutoCorrector
31
+
32
+ DEF_TYPES = %i[def defs].freeze
33
+
34
+ MSG = 'Call `super` without arguments and parentheses when the signature is identical.'
35
+
36
+ def on_super(super_node)
37
+ def_node = super_node.ancestors.find do |node|
38
+ # You can't implicitly call super when dynamically defining methods
39
+ break if define_method?(node)
40
+
41
+ break node if DEF_TYPES.include?(node.type)
42
+ end
43
+ return unless def_node
44
+ return unless arguments_identical?(def_node.arguments.argument_list, super_node.arguments)
45
+
46
+ add_offense(super_node) { |corrector| corrector.replace(super_node, 'super') }
47
+ end
48
+
49
+ private
50
+
51
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
52
+ def arguments_identical?(def_args, super_args)
53
+ super_args = preprocess_super_args(super_args)
54
+ return false if def_args.size != super_args.size
55
+
56
+ def_args.zip(super_args).each do |def_arg, super_arg|
57
+ next if positional_arg_same?(def_arg, super_arg)
58
+ next if positional_rest_arg_same(def_arg, super_arg)
59
+ next if keyword_arg_same?(def_arg, super_arg)
60
+ next if keyword_rest_arg_same?(def_arg, super_arg)
61
+ next if block_arg_same?(def_arg, super_arg)
62
+ next if forward_arg_same?(def_arg, super_arg)
63
+
64
+ return false
65
+ end
66
+ true
67
+ end
68
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
69
+
70
+ def positional_arg_same?(def_arg, super_arg)
71
+ return false unless def_arg.arg_type? || def_arg.optarg_type?
72
+ return false unless super_arg.lvar_type?
73
+
74
+ def_arg.name == super_arg.children.first
75
+ end
76
+
77
+ def positional_rest_arg_same(def_arg, super_arg)
78
+ return false unless def_arg.restarg_type?
79
+ # anonymous forwarding
80
+ return true if def_arg.name.nil? && super_arg.forwarded_restarg_type?
81
+ return false unless super_arg.splat_type?
82
+ return false unless (lvar_node = super_arg.children.first).lvar_type?
83
+
84
+ def_arg.name == lvar_node.children.first
85
+ end
86
+
87
+ def keyword_arg_same?(def_arg, super_arg)
88
+ return false unless def_arg.kwarg_type? || def_arg.kwoptarg_type?
89
+ return false unless (pair_node = super_arg).pair_type?
90
+ return false unless (sym_node = pair_node.key).sym_type?
91
+ return false unless (lvar_node = pair_node.value).lvar_type?
92
+ return false unless sym_node.source == lvar_node.source
93
+
94
+ def_arg.name == sym_node.value
95
+ end
96
+
97
+ def keyword_rest_arg_same?(def_arg, super_arg)
98
+ return false unless def_arg.kwrestarg_type?
99
+ # anonymous forwarding
100
+ return true if def_arg.name.nil? && super_arg.forwarded_kwrestarg_type?
101
+ return false unless super_arg.kwsplat_type?
102
+ return false unless (lvar_node = super_arg.children.first).lvar_type?
103
+
104
+ def_arg.name == lvar_node.children.first
105
+ end
106
+
107
+ def block_arg_same?(def_arg, super_arg)
108
+ return false unless def_arg.blockarg_type? && super_arg.block_pass_type?
109
+ # anonymous forwarding
110
+ return true if (block_pass_child = super_arg.children.first).nil? && def_arg.name.nil?
111
+
112
+ def_arg.name == block_pass_child.children.first
113
+ end
114
+
115
+ def forward_arg_same?(def_arg, super_arg)
116
+ def_arg.forward_arg_type? && super_arg.forwarded_args_type?
117
+ end
118
+
119
+ def define_method?(node)
120
+ return false unless node.block_type?
121
+
122
+ node.method?(:define_method) || node.method?(:define_singleton_method)
123
+ end
124
+
125
+ def preprocess_super_args(super_args)
126
+ super_args.flat_map do |node|
127
+ if node.hash_type? && !node.braces?
128
+ node.children
129
+ else
130
+ node
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
@@ -120,6 +120,23 @@ module RuboCop
120
120
  # # good
121
121
  # something.map { |s| s.upcase }
122
122
  #
123
+ # @example AllCops:ActiveSupportExtensionsEnabled: false (default)
124
+ # # bad
125
+ # ->(x) { x.foo }
126
+ # proc { |x| x.foo }
127
+ # Proc.new { |x| x.foo }
128
+ #
129
+ # # good
130
+ # lambda(&:foo)
131
+ # proc(&:foo)
132
+ # Proc.new(&:foo)
133
+ #
134
+ # @example AllCops:ActiveSupportExtensionsEnabled: true
135
+ # # good
136
+ # ->(x) { x.foo }
137
+ # proc { |x| x.foo }
138
+ # Proc.new { |x| x.foo }
139
+ #
123
140
  class SymbolProc < Base
124
141
  include CommentsHelp
125
142
  include RangeHelp
@@ -129,6 +146,7 @@ module RuboCop
129
146
 
130
147
  MSG = 'Pass `&:%<method>s` as an argument to `%<block_method>s` instead of a block.'
131
148
  SUPER_TYPES = %i[super zsuper].freeze
149
+ LAMBDA_OR_PROC = %i[lambda proc].freeze
132
150
 
133
151
  # @!method proc_node?(node)
134
152
  def_node_matcher :proc_node?, '(send (const {nil? cbase} :Proc) :new)'
@@ -151,13 +169,12 @@ module RuboCop
151
169
  # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
152
170
  def on_block(node)
153
171
  symbol_proc?(node) do |dispatch_node, arguments_node, method_name|
154
- # TODO: Rails-specific handling that we should probably make
155
- # configurable - https://github.com/rubocop/rubocop/issues/1485
156
- # we should allow lambdas & procs
157
- return if proc_node?(dispatch_node)
172
+ if active_support_extensions_enabled?
173
+ return if proc_node?(dispatch_node)
174
+ return if LAMBDA_OR_PROC.include?(dispatch_node.method_name)
175
+ end
158
176
  return if unsafe_hash_usage?(dispatch_node)
159
177
  return if unsafe_array_usage?(dispatch_node)
160
- return if %i[lambda proc].include?(dispatch_node.method_name)
161
178
  return if allowed_method_name?(dispatch_node.method_name)
162
179
  return if allow_if_method_has_argument?(node.send_node)
163
180
  return if node.block_type? && destructuring_block_argument?(arguments_node)
@@ -206,6 +223,8 @@ module RuboCop
206
223
  end
207
224
 
208
225
  def autocorrect_without_args(corrector, node)
226
+ autocorrect_lambda_block(corrector, node) if node.send_node.lambda_literal?
227
+
209
228
  corrector.replace(block_range_with_space(node), "(&:#{node.body.method_name})")
210
229
  end
211
230
 
@@ -218,6 +237,14 @@ module RuboCop
218
237
  corrector.remove(block_range_with_space(node))
219
238
  end
220
239
 
240
+ def autocorrect_lambda_block(corrector, node)
241
+ send_node_loc = node.send_node.loc
242
+ corrector.replace(send_node_loc.selector, 'lambda')
243
+
244
+ range = range_between(send_node_loc.selector.end_pos, node.loc.begin.end_pos - 2)
245
+ corrector.remove(range)
246
+ end
247
+
221
248
  def block_range_with_space(node)
222
249
  block_range = range_between(begin_pos_for_replacement(node), node.loc.end.end_pos)
223
250
  range_with_surrounding_space(block_range, side: :left)
@@ -116,20 +116,24 @@ module RuboCop
116
116
  def set_max(cfg, cop_name)
117
117
  return unless cfg[:exclude_limit]
118
118
 
119
- max_set_in_user_config =
120
- @config_for_pwd.for_cop(cop_name)['Max'] != default_config(cop_name)['Max']
121
- if !max_set_in_user_config &&
122
- # In case auto_gen_only_exclude is set, only modify the maximum if the files are not
123
- # excluded one by one.
124
- (!@options[:auto_gen_only_exclude] ||
125
- @files_with_offenses[cop_name].size > @exclude_limit)
126
- cfg.merge!(cfg[:exclude_limit])
127
- end
119
+ cfg.merge!(cfg[:exclude_limit]) if should_set_max?(cop_name)
128
120
 
129
121
  # Remove already used exclude_limit.
130
122
  cfg.reject! { |key| key == :exclude_limit }
131
123
  end
132
124
 
125
+ def should_set_max?(cop_name)
126
+ max_set_in_user_config =
127
+ @config_for_pwd.for_cop(cop_name)['Max'] != default_config(cop_name)['Max']
128
+
129
+ max_allowed = !max_set_in_user_config && !no_exclude_limit?
130
+ return false unless max_allowed
131
+
132
+ # In case auto_gen_only_exclude is set, only modify the maximum if the files are not
133
+ # excluded one by one.
134
+ !@options[:auto_gen_only_exclude] || @files_with_offenses[cop_name].size > @exclude_limit
135
+ end
136
+
133
137
  def output_cop_comments(output_buffer, cfg, cop_name, offense_count)
134
138
  output_buffer.puts "# Offense count: #{offense_count}" if show_offense_counts?
135
139
 
@@ -27,6 +27,7 @@ module RuboCop
27
27
  '[t]ap' => 'TapFormatter',
28
28
  '[w]orst' => 'WorstOffendersFormatter'
29
29
  }.freeze
30
+ BUILTIN_FORMATTER_NAMES = BUILTIN_FORMATTERS_FOR_KEYS.keys.map { |key| key.delete('[]') }
30
31
 
31
32
  FORMATTER_APIS = %i[started finished].freeze
32
33
 
@@ -88,7 +89,12 @@ module RuboCop
88
89
  /^\[#{specified_key}\]/.match?(key) || specified_key == key.delete('[]')
89
90
  end
90
91
 
91
- raise %(No formatter for "#{specified_key}") if matching_keys.empty?
92
+ if matching_keys.empty?
93
+ similar_name = NameSimilarity.find_similar_name(specified_key, BUILTIN_FORMATTER_NAMES)
94
+ suggestion = %( Did you mean? "#{similar_name}") if similar_name
95
+
96
+ raise Rainbow(%(Formatter "#{specified_key}" not found.#{suggestion})).red
97
+ end
92
98
 
93
99
  raise %(Cannot determine formatter for "#{specified_key}") if matching_keys.size > 1
94
100
 
@@ -72,7 +72,7 @@ module RuboCop
72
72
  def parser
73
73
  return @parser if defined?(@parser)
74
74
 
75
- @parser = if @lockfile_path && bundler_lock_parser_defined?
75
+ @parser = if @lockfile_path && File.exist?(@lockfile_path) && bundler_lock_parser_defined?
76
76
  begin
77
77
  lockfile = ::Bundler.read_file(@lockfile_path)
78
78
  ::Bundler::LockfileParser.new(lockfile) if lockfile
@@ -45,10 +45,6 @@ module RuboCop
45
45
  result: LanguageServer::Protocol::Interface::InitializeResult.new(
46
46
  capabilities: LanguageServer::Protocol::Interface::ServerCapabilities.new(
47
47
  document_formatting_provider: true,
48
- diagnostic_provider: LanguageServer::Protocol::Interface::DiagnosticOptions.new(
49
- inter_file_dependencies: false,
50
- workspace_diagnostics: false
51
- ),
52
48
  text_document_sync: LanguageServer::Protocol::Interface::TextDocumentSyncOptions.new(
53
49
  change: LanguageServer::Protocol::Constant::TextDocumentSyncKind::FULL,
54
50
  open_close: true
@@ -73,10 +69,6 @@ module RuboCop
73
69
  end
74
70
  end
75
71
 
76
- handle 'textDocument/diagnostic' do |request|
77
- # no-op, diagnostics are handled in textDocument/didChange
78
- end
79
-
80
72
  handle 'textDocument/didChange' do |request|
81
73
  params = request[:params]
82
74
  result = diagnostic(params[:textDocument][:uri], params[:contentChanges][0][:text])
@@ -125,6 +117,12 @@ module RuboCop
125
117
  end
126
118
 
127
119
  uri = request[:params][:arguments][0][:uri]
120
+ formatted = nil
121
+
122
+ # The `workspace/executeCommand` is an LSP method triggered by intentional user actions,
123
+ # so the user's intention for autocorrection is respected.
124
+ LSP.disable { formatted = format_file(uri, command: command) }
125
+
128
126
  @server.write(
129
127
  id: request[:id],
130
128
  method: 'workspace/applyEdit',
@@ -132,7 +130,7 @@ module RuboCop
132
130
  label: label,
133
131
  edit: {
134
132
  changes: {
135
- uri => format_file(uri, command: command)
133
+ uri => formatted
136
134
  }
137
135
  }
138
136
  }
data/lib/rubocop/lsp.rb CHANGED
@@ -22,8 +22,15 @@ module RuboCop
22
22
  # Disable LSP.
23
23
  #
24
24
  # @return [void]
25
- def disable
26
- @enabled = false
25
+ def disable(&block)
26
+ if block
27
+ original = @enabled
28
+ @enabled = false
29
+ yield
30
+ @enabled = original
31
+ else
32
+ @enabled = false
33
+ end
27
34
  end
28
35
  end
29
36
  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.63.5'
6
+ STRING = '1.64.0'
7
7
 
8
8
  MSG = '%<version>s (using %<parser_version>s, ' \
9
9
  'rubocop-ast %<rubocop_ast_version>s, ' \
data/lib/rubocop.rb CHANGED
@@ -666,6 +666,7 @@ require_relative 'rubocop/cop/style/select_by_regexp'
666
666
  require_relative 'rubocop/cop/style/self_assignment'
667
667
  require_relative 'rubocop/cop/style/semicolon'
668
668
  require_relative 'rubocop/cop/style/send'
669
+ require_relative 'rubocop/cop/style/send_with_literal_method_name'
669
670
  require_relative 'rubocop/cop/style/signal_exception'
670
671
  require_relative 'rubocop/cop/style/single_argument_dig'
671
672
  require_relative 'rubocop/cop/style/single_line_block_params'
@@ -682,6 +683,7 @@ require_relative 'rubocop/cop/style/string_literals_in_interpolation'
682
683
  require_relative 'rubocop/cop/style/string_methods'
683
684
  require_relative 'rubocop/cop/style/strip'
684
685
  require_relative 'rubocop/cop/style/struct_inheritance'
686
+ require_relative 'rubocop/cop/style/super_arguments'
685
687
  require_relative 'rubocop/cop/style/super_with_args_parentheses'
686
688
  require_relative 'rubocop/cop/style/swap_values'
687
689
  require_relative 'rubocop/cop/style/symbol_array'
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.63.5
4
+ version: 1.64.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bozhidar Batsov
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: exe
12
12
  cert_chain: []
13
- date: 2024-05-09 00:00:00.000000000 Z
13
+ date: 2024-05-23 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: json
@@ -885,6 +885,7 @@ files:
885
885
  - lib/rubocop/cop/style/self_assignment.rb
886
886
  - lib/rubocop/cop/style/semicolon.rb
887
887
  - lib/rubocop/cop/style/send.rb
888
+ - lib/rubocop/cop/style/send_with_literal_method_name.rb
888
889
  - lib/rubocop/cop/style/signal_exception.rb
889
890
  - lib/rubocop/cop/style/single_argument_dig.rb
890
891
  - lib/rubocop/cop/style/single_line_block_params.rb
@@ -904,6 +905,7 @@ files:
904
905
  - lib/rubocop/cop/style/string_methods.rb
905
906
  - lib/rubocop/cop/style/strip.rb
906
907
  - lib/rubocop/cop/style/struct_inheritance.rb
908
+ - lib/rubocop/cop/style/super_arguments.rb
907
909
  - lib/rubocop/cop/style/super_with_args_parentheses.rb
908
910
  - lib/rubocop/cop/style/swap_values.rb
909
911
  - lib/rubocop/cop/style/symbol_array.rb
@@ -1032,9 +1034,9 @@ licenses:
1032
1034
  - MIT
1033
1035
  metadata:
1034
1036
  homepage_uri: https://rubocop.org/
1035
- changelog_uri: https://github.com/rubocop/rubocop/releases/tag/v1.63.5
1037
+ changelog_uri: https://github.com/rubocop/rubocop/releases/tag/v1.64.0
1036
1038
  source_code_uri: https://github.com/rubocop/rubocop/
1037
- documentation_uri: https://docs.rubocop.org/rubocop/1.63/
1039
+ documentation_uri: https://docs.rubocop.org/rubocop/1.64/
1038
1040
  bug_tracker_uri: https://github.com/rubocop/rubocop/issues
1039
1041
  rubygems_mfa_required: 'true'
1040
1042
  post_install_message: