rubocop 1.54.2 → 1.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b5aa0f1796446e72fc2559c5e0ba132c8b8e0e2f244c649e37c475f309911cab
4
- data.tar.gz: ab9d4c1160f25912df7db584e67e4c2ce13f8372823a8f5f732ca6664ea63863
3
+ metadata.gz: 7bfd5d73e8097398116fc389fa55f96eca51b61c95a5e9e4654d7eeebaeef985
4
+ data.tar.gz: a2cf8206a9896953e85516ebdc00f673a5852eb9613e877fee6b43058f7ef080
5
5
  SHA512:
6
- metadata.gz: b314533429676735ccfbda5f7e891087c7c277fc84789aa4c82c6961ab966f8d7c6ce1c491f0b6033a7893c48348eebff092dc50f462dd5b3a3cbe77954adf99
7
- data.tar.gz: 91e2894b3931bed08640afc882785b5e18371d79fee3a1f4a9eddcb44faa9c4b2232fe798185fcb2b57c3b4561efab25ac019ae543fa4c9b0bdaf0f8a361522b
6
+ metadata.gz: 454b6be8363e0a91226348e2543587d5e6c3503bdaa9b2c0287f1a1e347f9a315c8f8337c5762e59f83f7406a797645ebef7e16a896694da29cb751a658e5579
7
+ data.tar.gz: 388fe4a1934bf7659148ce32a2837f5ff0822babc1e17abd1a2c4f8138e6a839a7c4f304202e7e712763dbc23fadee4c92412554cbe699a8f81810e602d62f49
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.54', require: false
56
+ gem 'rubocop', '~> 1.55', require: false
57
57
  ```
58
58
 
59
59
  See [our versioning policy](https://docs.rubocop.org/rubocop/versioning.html) for further details.
@@ -67,6 +67,8 @@ $ cd my/cool/ruby/project
67
67
  $ rubocop
68
68
  ```
69
69
 
70
+ You can also use this magic in your favorite editor with RuboCop's [built-in LSP](https://docs.rubocop.org/rubocop/usage/lsp.html).
71
+
70
72
  ## Documentation
71
73
 
72
74
  You can read a lot more about RuboCop in its [official docs](https://docs.rubocop.org).
data/config/default.yml CHANGED
@@ -3072,6 +3072,7 @@ Style/ArgumentsForwarding:
3072
3072
  StyleGuide: '#arguments-forwarding'
3073
3073
  Enabled: pending
3074
3074
  AllowOnlyRestArgument: true
3075
+ UseAnonymousForwarding: true
3075
3076
  VersionAdded: '1.1'
3076
3077
 
3077
3078
  Style/ArrayCoercion:
@@ -3916,8 +3917,9 @@ Style/HashConversion:
3916
3917
  Description: 'Avoid Hash[] in favor of ary.to_h or literal hashes.'
3917
3918
  StyleGuide: '#avoid-hash-constructor'
3918
3919
  Enabled: pending
3920
+ SafeAutoCorrect: false
3919
3921
  VersionAdded: '1.10'
3920
- VersionChanged: '1.11'
3922
+ VersionChanged: '1.55'
3921
3923
  AllowSplatArgument: true
3922
3924
 
3923
3925
  Style/HashEachMethods:
@@ -4810,12 +4812,16 @@ Style/RedundantArgument:
4810
4812
  Enabled: pending
4811
4813
  Safe: false
4812
4814
  VersionAdded: '1.4'
4813
- VersionChanged: '1.40'
4815
+ VersionChanged: '1.55'
4814
4816
  Methods:
4815
4817
  # Array#join
4816
4818
  join: ''
4817
4819
  # Array#sum
4818
4820
  sum: 0
4821
+ # Kernel.#exit
4822
+ exit: true
4823
+ # Kernel.#exit!
4824
+ exit!: false
4819
4825
  # String#split
4820
4826
  split: ' '
4821
4827
  # String#chomp
@@ -224,6 +224,11 @@ changed_parameters:
224
224
  - AllowedMethods
225
225
  - AllowedPatterns
226
226
  severity: warning
227
+ - cops: Style/ArgumentsForwarding
228
+ parameters: AllowOnlyRestArgument
229
+ reason: "`AllowOnlyRestArgument` has no effect with TargetRubyVersion >= 3.2."
230
+ severity: warning
231
+ minimum_ruby_version: 3.2
227
232
 
228
233
  # Enforced styles that have been removed or replaced
229
234
  changed_enforced_styles:
@@ -19,7 +19,7 @@ module RuboCop
19
19
  end
20
20
 
21
21
  def violated?
22
- config[cop]&.key?(parameter)
22
+ applies_to_current_ruby_version? && config[cop]&.key?(parameter)
23
23
  end
24
24
 
25
25
  def warning?
@@ -28,6 +28,14 @@ module RuboCop
28
28
 
29
29
  private
30
30
 
31
+ def applies_to_current_ruby_version?
32
+ minimum_ruby_version = metadata['minimum_ruby_version']
33
+
34
+ return true unless minimum_ruby_version
35
+
36
+ config.target_ruby_version >= minimum_ruby_version
37
+ end
38
+
31
39
  def alternative
32
40
  metadata['alternative']
33
41
  end
@@ -67,7 +67,7 @@ module RuboCop
67
67
 
68
68
  def require_path
69
69
  path = source_path.relative_path_from(root_file_path.dirname)
70
- path.to_s.sub('.rb', '')
70
+ path.to_s.delete_suffix('.rb')
71
71
  end
72
72
  end
73
73
  end
@@ -48,6 +48,10 @@ module RuboCop
48
48
 
49
49
  MSG = 'Redundant line break detected.'
50
50
 
51
+ def on_lvasgn(node)
52
+ super unless end_with_percent_blank_string?(processed_source)
53
+ end
54
+
51
55
  def on_send(node)
52
56
  # Include "the whole expression".
53
57
  node = node.parent while node.parent&.send_type? ||
@@ -61,6 +65,10 @@ module RuboCop
61
65
 
62
66
  private
63
67
 
68
+ def end_with_percent_blank_string?(processed_source)
69
+ processed_source.buffer.source.end_with?("%\n\n")
70
+ end
71
+
64
72
  def check_assignment(node, _rhs)
65
73
  return unless offense?(node)
66
74
 
@@ -68,7 +68,7 @@ module RuboCop
68
68
 
69
69
  def same_conditions_node_different_branch?(variable, outer_local_variable)
70
70
  variable_node = variable_node(variable)
71
- return false unless variable_node.conditional?
71
+ return false unless node_or_its_ascendant_conditional?(variable_node)
72
72
 
73
73
  outer_local_variable_node =
74
74
  find_conditional_node_from_ascendant(outer_local_variable.declaration_node)
@@ -96,6 +96,12 @@ module RuboCop
96
96
 
97
97
  find_conditional_node_from_ascendant(parent)
98
98
  end
99
+
100
+ def node_or_its_ascendant_conditional?(node)
101
+ return true if node.conditional?
102
+
103
+ !!find_conditional_node_from_ascendant(node)
104
+ end
99
105
  end
100
106
  end
101
107
  end
@@ -30,8 +30,8 @@ module RuboCop
30
30
  private
31
31
 
32
32
  def inside_interpolation?(node)
33
- # A :begin node inside a :dstr node is an interpolation.
34
- node.ancestors.drop_while { |a| !a.begin_type? }.any?(&:dstr_type?)
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? }
35
35
  end
36
36
  end
37
37
  end
@@ -8,6 +8,12 @@ module RuboCop
8
8
  # This cop identifies places where `do_something(*args, &block)`
9
9
  # can be replaced by `do_something(...)`.
10
10
  #
11
+ # In Ruby 3.2, anonymous args/kwargs forwarding has been added.
12
+ #
13
+ # This cop also identifies places where `use_args(*args)`/`use_kwargs(**kwargs)` can be
14
+ # replaced by `use_args(*)`/`use_kwargs(**)`; if desired, this functionality can be disabled
15
+ # by setting UseAnonymousForwarding: false.
16
+ #
11
17
  # @example
12
18
  # # bad
13
19
  # def foo(*args, &block)
@@ -24,7 +30,27 @@ module RuboCop
24
30
  # bar(...)
25
31
  # end
26
32
  #
27
- # @example AllowOnlyRestArgument: true (default)
33
+ # @example UseAnonymousForwarding: true (default, only relevant for Ruby >= 3.2)
34
+ # # bad
35
+ # def foo(*args, **kwargs)
36
+ # args_only(*args)
37
+ # kwargs_only(**kwargs)
38
+ # end
39
+ #
40
+ # # good
41
+ # def foo(*, **)
42
+ # args_only(*)
43
+ # kwargs_only(**)
44
+ # end
45
+ #
46
+ # @example UseAnonymousForwarding: false (only relevant for Ruby >= 3.2)
47
+ # # good
48
+ # def foo(*args, **kwargs)
49
+ # args_only(*args)
50
+ # kwargs_only(**kwargs)
51
+ # end
52
+ #
53
+ # @example AllowOnlyRestArgument: true (default, only relevant for Ruby < 3.2)
28
54
  # # good
29
55
  # def foo(*args)
30
56
  # bar(*args)
@@ -34,7 +60,7 @@ module RuboCop
34
60
  # bar(**kwargs)
35
61
  # end
36
62
  #
37
- # @example AllowOnlyRestArgument: false
63
+ # @example AllowOnlyRestArgument: false (only relevant for Ruby < 3.2)
38
64
  # # bad
39
65
  # # The following code can replace the arguments with `...`,
40
66
  # # but it will change the behavior. Because `...` forwards block also.
@@ -53,77 +79,133 @@ module RuboCop
53
79
 
54
80
  minimum_target_ruby_version 2.7
55
81
 
56
- MSG = 'Use arguments forwarding.'
57
-
58
- # @!method use_rest_arguments?(node)
59
- def_node_matcher :use_rest_arguments?, <<~PATTERN
60
- (args ({restarg kwrestarg} $_) $...)
61
- PATTERN
62
-
63
- # @!method only_rest_arguments?(node, name)
64
- def_node_matcher :only_rest_arguments?, <<~PATTERN
65
- {
66
- (send _ _ (splat (lvar %1)))
67
- (send _ _ (hash (kwsplat (lvar %1))))
68
- }
69
- PATTERN
70
-
71
- # @!method forwarding_method_arguments?(node, rest_name, block_name, kwargs_name)
72
- def_node_matcher :forwarding_method_arguments?, <<~PATTERN
73
- {
74
- (send _ _
75
- (splat (lvar %1))
76
- (block-pass {(lvar %2) nil?}))
77
- (send _ _
78
- (splat (lvar %1))
79
- (hash (kwsplat (lvar %3)))
80
- (block-pass {(lvar %2) nil?}))
81
- }
82
- PATTERN
82
+ FORWARDING_LVAR_TYPES = %i[splat kwsplat block_pass].freeze
83
+
84
+ FORWARDING_MSG = 'Use shorthand syntax `...` for arguments forwarding.'
85
+ ARGS_MSG = 'Use anonymous positional arguments forwarding (`*`).'
86
+ KWARGS_MSG = 'Use anonymous keyword arguments forwarding (`**`).'
83
87
 
84
88
  def on_def(node)
85
89
  return unless node.body
86
- return unless (rest_args_name, args = use_rest_arguments?(node.arguments))
87
- return if args.any?(&:default?)
88
90
 
89
- node.each_descendant(:send) do |send_node|
90
- kwargs_name, block_name = extract_argument_names_from(args)
91
+ forwardable_args = extract_forwardable_args(node.arguments)
92
+
93
+ send_classifications = classify_send_nodes(
94
+ node,
95
+ node.each_descendant(:send).to_a,
96
+ non_splat_or_block_pass_lvar_references(node.body),
97
+ forwardable_args
98
+ )
91
99
 
92
- next unless forwarding_method?(send_node, rest_args_name, kwargs_name, block_name) &&
93
- all_lvars_as_forwarding_method_arguments?(node, send_node)
100
+ return if send_classifications.empty?
94
101
 
95
- register_offense_to_forwarding_method_arguments(send_node)
96
- register_offense_to_method_definition_arguments(node)
102
+ if only_forwards_all?(send_classifications)
103
+ add_forward_all_offenses(node, send_classifications)
104
+ elsif target_ruby_version >= 3.2
105
+ add_post_ruby_32_offenses(node, send_classifications, forwardable_args)
97
106
  end
98
107
  end
108
+
99
109
  alias on_defs on_def
100
110
 
101
111
  private
102
112
 
103
- def extract_argument_names_from(args)
104
- kwargs_name = args.first.source.delete('**') if args.first&.kwrestarg_type?
105
- block_arg_name = args.last.source.delete('&') if args.last&.blockarg_type?
113
+ def extract_forwardable_args(args)
114
+ [args.find(&:restarg_type?), args.find(&:kwrestarg_type?), args.find(&:blockarg_type?)]
115
+ end
116
+
117
+ def only_forwards_all?(send_classifications)
118
+ send_classifications.each_value.all? { |c, _, _| c == :all }
119
+ end
120
+
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)
124
+ end
125
+
126
+ register_forward_all_offense_on_method_def(node)
127
+ end
128
+
129
+ def add_post_ruby_32_offenses(def_node, send_classifications, forwardable_args)
130
+ return unless use_anonymous_forwarding?
131
+
132
+ rest_arg, kwrest_arg, _block_arg = *forwardable_args
133
+
134
+ send_classifications.each do |send_node, (_c, forward_rest, forward_kwrest)|
135
+ if forward_rest
136
+ register_forward_args_offense(def_node.arguments, rest_arg)
137
+ register_forward_args_offense(send_node, forward_rest)
138
+ end
106
139
 
107
- [kwargs_name, block_arg_name].map { |name| name&.to_sym }
140
+ if forward_kwrest
141
+ register_forward_kwargs_offense(!forward_rest, def_node.arguments, kwrest_arg)
142
+ register_forward_kwargs_offense(!forward_rest, send_node, forward_kwrest)
143
+ end
144
+ end
108
145
  end
109
146
 
110
- def forwarding_method?(node, rest_arg, kwargs, block_arg)
111
- return only_rest_arguments?(node, rest_arg) unless allow_only_rest_arguments?
147
+ def non_splat_or_block_pass_lvar_references(body)
148
+ body.each_descendant(:lvar, :lvasgn).filter_map do |lvar|
149
+ parent = lvar.parent
150
+
151
+ next if lvar.lvar_type? && FORWARDING_LVAR_TYPES.include?(parent.type)
112
152
 
113
- forwarding_method_arguments?(node, rest_arg, block_arg, kwargs)
153
+ lvar.children.first
154
+ end.uniq
114
155
  end
115
156
 
116
- def all_lvars_as_forwarding_method_arguments?(def_node, forwarding_method)
117
- lvars = def_node.body.each_descendant(:lvar, :lvasgn)
157
+ def classify_send_nodes(def_node, send_nodes, referenced_lvars, forwardable_args)
158
+ send_nodes.to_h do |send_node|
159
+ classification_and_forwards = classification_and_forwards(
160
+ def_node,
161
+ send_node,
162
+ referenced_lvars,
163
+ forwardable_args
164
+ )
165
+
166
+ [send_node, classification_and_forwards]
167
+ end.compact
168
+ end
118
169
 
119
- begin_pos = forwarding_method.source_range.begin_pos
120
- end_pos = forwarding_method.source_range.end_pos
170
+ def classification_and_forwards(def_node, send_node, referenced_lvars, forwardable_args)
171
+ classifier = SendNodeClassifier.new(
172
+ def_node,
173
+ send_node,
174
+ referenced_lvars,
175
+ forwardable_args,
176
+ target_ruby_version: target_ruby_version,
177
+ allow_only_rest_arguments: allow_only_rest_arguments?
178
+ )
121
179
 
122
- lvars.all? { |lvar| lvar.source_range.begin_pos.between?(begin_pos, end_pos) }
180
+ classification = classifier.classification
181
+
182
+ return unless classification
183
+
184
+ [classification, classifier.forwarded_rest_arg, classifier.forwarded_kwrest_arg]
123
185
  end
124
186
 
125
- def register_offense_to_forwarding_method_arguments(forwarding_method)
126
- add_offense(arguments_range(forwarding_method)) do |corrector|
187
+ def register_forward_args_offense(def_arguments_or_send, rest_arg_or_splat)
188
+ 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
192
+
193
+ corrector.replace(rest_arg_or_splat, '*')
194
+ end
195
+ end
196
+
197
+ def register_forward_kwargs_offense(add_parens, def_arguments_or_send, kwrest_arg_or_splat)
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
202
+
203
+ corrector.replace(kwrest_arg_or_splat, '**')
204
+ end
205
+ end
206
+
207
+ def register_forward_all_offense_on_forwarding_method(forwarding_method)
208
+ add_offense(arguments_range(forwarding_method), message: FORWARDING_MSG) do |corrector|
127
209
  begin_pos = forwarding_method.loc.selector&.end_pos || forwarding_method.loc.dot.end_pos
128
210
  range = range_between(begin_pos, forwarding_method.source_range.end_pos)
129
211
 
@@ -131,8 +213,8 @@ module RuboCop
131
213
  end
132
214
  end
133
215
 
134
- def register_offense_to_method_definition_arguments(method_definition)
135
- add_offense(arguments_range(method_definition)) do |corrector|
216
+ def register_forward_all_offense_on_method_def(method_definition)
217
+ add_offense(arguments_range(method_definition), message: FORWARDING_MSG) do |corrector|
136
218
  arguments_range = range_with_surrounding_space(
137
219
  method_definition.arguments.source_range, side: :left
138
220
  )
@@ -149,6 +231,97 @@ module RuboCop
149
231
  def allow_only_rest_arguments?
150
232
  cop_config.fetch('AllowOnlyRestArgument', true)
151
233
  end
234
+
235
+ def use_anonymous_forwarding?
236
+ cop_config.fetch('UseAnonymousForwarding', false)
237
+ end
238
+
239
+ # Classifies send nodes for possible rest/kwrest/all (including block) forwarding.
240
+ class SendNodeClassifier
241
+ extend NodePattern::Macros
242
+
243
+ # @!method find_forwarded_rest_arg(node, rest_name)
244
+ def_node_search :find_forwarded_rest_arg, '(splat (lvar %1))'
245
+
246
+ # @!method find_forwarded_kwrest_arg(node, kwrest_name)
247
+ def_node_search :find_forwarded_kwrest_arg, '(kwsplat (lvar %1))'
248
+
249
+ # @!method find_forwarded_block_arg(node, block_name)
250
+ def_node_search :find_forwarded_block_arg, '(block_pass {(lvar %1) nil?})'
251
+
252
+ def initialize(def_node, send_node, referenced_lvars, forwardable_args, **config)
253
+ @def_node = def_node
254
+ @send_node = send_node
255
+ @referenced_lvars = referenced_lvars
256
+ @rest_arg, @kwrest_arg, @block_arg = *forwardable_args
257
+ @rest_arg_name, @kwrest_arg_name, @block_arg_name =
258
+ *forwardable_args.map { |a| a&.name }
259
+ @config = config
260
+ end
261
+
262
+ def forwarded_rest_arg
263
+ return nil if referenced_rest_arg?
264
+
265
+ find_forwarded_rest_arg(@send_node, @rest_arg_name).first
266
+ end
267
+
268
+ def forwarded_kwrest_arg
269
+ return nil if referenced_kwrest_arg?
270
+
271
+ find_forwarded_kwrest_arg(@send_node, @kwrest_arg_name).first
272
+ end
273
+
274
+ def forwarded_block_arg
275
+ return nil if referenced_block_arg?
276
+
277
+ find_forwarded_block_arg(@send_node, @block_arg_name).first
278
+ end
279
+
280
+ def classification
281
+ return nil unless forwarded_rest_arg || forwarded_kwrest_arg
282
+
283
+ if referenced_none? && (forwarded_exactly_all? || pre_ruby_32_allow_forward_all?)
284
+ :all
285
+ elsif target_ruby_version >= 3.2
286
+ :rest_or_kwrest
287
+ end
288
+ end
289
+
290
+ private
291
+
292
+ def referenced_rest_arg?
293
+ @referenced_lvars.include?(@rest_arg_name)
294
+ end
295
+
296
+ def referenced_kwrest_arg?
297
+ @referenced_lvars.include?(@kwrest_arg_name)
298
+ end
299
+
300
+ def referenced_block_arg?
301
+ @referenced_lvars.include?(@block_arg_name)
302
+ end
303
+
304
+ def referenced_none?
305
+ !(referenced_rest_arg? || referenced_kwrest_arg? || referenced_block_arg?)
306
+ end
307
+
308
+ def forwarded_exactly_all?
309
+ @send_node.arguments.size == 3 &&
310
+ forwarded_rest_arg &&
311
+ forwarded_kwrest_arg &&
312
+ forwarded_block_arg
313
+ end
314
+
315
+ def target_ruby_version
316
+ @config.fetch(:target_ruby_version)
317
+ end
318
+
319
+ def pre_ruby_32_allow_forward_all?
320
+ target_ruby_version < 3.2 &&
321
+ @def_node.arguments.none?(&:default?) &&
322
+ (@block_arg ? forwarded_block_arg : !@config.fetch(:allow_only_rest_arguments))
323
+ end
324
+ end
152
325
  end
153
326
  end
154
327
  end
@@ -67,6 +67,7 @@ module RuboCop
67
67
  return unless node.parent&.begin_type?
68
68
  return unless collection_looping_method?(node)
69
69
  return unless same_collection_looping_block?(node, node.left_sibling)
70
+ return unless node.body && node.left_sibling.body
70
71
 
71
72
  add_offense(node) do |corrector|
72
73
  combine_with_left_sibling(corrector, node)
@@ -10,6 +10,16 @@ module RuboCop
10
10
  # `Hash[*ary]` can be replaced with `ary.each_slice(2).to_h` but it will be complicated.
11
11
  # So, `AllowSplatArgument` option is true by default to allow splat argument for simple code.
12
12
  #
13
+ # @safety
14
+ # This cop's autocorrection is unsafe because `ArgumentError` occurs
15
+ # if the number of elements is odd:
16
+ #
17
+ # [source,ruby]
18
+ # ----
19
+ # Hash[[[1, 2], [3]]] #=> {1=>2, 3=>nil}
20
+ # [[1, 2], [5]].to_h #=> wrong array length at 1 (expected 2, was 1) (ArgumentError)
21
+ # ----
22
+ #
13
23
  # @example
14
24
  # # bad
15
25
  # Hash[ary]
@@ -124,9 +124,10 @@ module RuboCop
124
124
  node.parent&.class_type? && node.parent&.single_line?
125
125
  end
126
126
 
127
- def call_with_ambiguous_arguments?(node)
127
+ def call_with_ambiguous_arguments?(node) # rubocop:disable Metrics/PerceivedComplexity
128
128
  call_with_braced_block?(node) ||
129
129
  call_as_argument_or_chain?(node) ||
130
+ call_in_match_pattern?(node) ||
130
131
  hash_literal_in_arguments?(node) ||
131
132
  node.descendants.any? do |n|
132
133
  n.forwarded_args_type? || ambiguous_literal?(n) || logical_operator?(n) ||
@@ -144,6 +145,10 @@ module RuboCop
144
145
  node.parent.csend_type? || node.parent.super_type? || node.parent.yield_type?)
145
146
  end
146
147
 
148
+ def call_in_match_pattern?(node)
149
+ node.parent&.match_pattern_type?
150
+ end
151
+
147
152
  def hash_literal_in_arguments?(node)
148
153
  node.arguments.any? do |n|
149
154
  hash_literal?(n) ||
@@ -35,6 +35,8 @@ module RuboCop
35
35
  # array.join('')
36
36
  # [1, 2, 3].join("")
37
37
  # array.sum(0)
38
+ # exit(true)
39
+ # exit!(false)
38
40
  # string.split(" ")
39
41
  # "first\nsecond".split(" ")
40
42
  # string.chomp("\n")
@@ -45,6 +47,8 @@ module RuboCop
45
47
  # array.join
46
48
  # [1, 2, 3].join
47
49
  # array.sum
50
+ # exit
51
+ # exit!
48
52
  # string.split
49
53
  # "first second".split
50
54
  # string.chomp
@@ -55,9 +59,10 @@ module RuboCop
55
59
  extend AutoCorrector
56
60
 
57
61
  MSG = 'Argument %<arg>s is redundant because it is implied by default.'
62
+ NO_RECEIVER_METHODS = %i[exit exit!].freeze
58
63
 
59
64
  def on_send(node)
60
- return if node.receiver.nil?
65
+ return if !NO_RECEIVER_METHODS.include?(node.method_name) && node.receiver.nil?
61
66
  return if node.arguments.count != 1
62
67
  return unless redundant_argument?(node)
63
68
 
@@ -52,7 +52,7 @@ module RuboCop
52
52
  include AllowedMethods
53
53
  include AllowedPattern
54
54
 
55
- MSG = 'Use `return false` instead of `%<prefer>s` in the predicate method.'
55
+ MSG = 'Return `false` instead of `nil` in predicate methods.'
56
56
 
57
57
  # @!method return_nil?(node)
58
58
  def_node_matcher :return_nil?, <<~PATTERN
@@ -65,16 +65,28 @@ module RuboCop
65
65
  return unless (body = node.body)
66
66
 
67
67
  body.each_descendant(:return) do |return_node|
68
- next unless return_nil?(return_node)
68
+ register_offense(return_node, 'return false') if return_nil?(return_node)
69
+ end
69
70
 
70
- message = format(MSG, prefer: return_node.source)
71
+ return unless (nil_node = nil_node_at_the_end_of_method_body(body))
71
72
 
72
- add_offense(return_node, message: message) do |corrector|
73
- corrector.replace(return_node, 'return false')
74
- end
75
- end
73
+ register_offense(nil_node, 'false')
76
74
  end
77
75
  alias on_defs on_def
76
+
77
+ private
78
+
79
+ def nil_node_at_the_end_of_method_body(body)
80
+ return unless (last_child = body.children.last)
81
+
82
+ last_child if last_child.is_a?(AST::Node) && last_child.nil_type?
83
+ end
84
+
85
+ def register_offense(offense_node, replacement)
86
+ add_offense(offense_node) do |corrector|
87
+ corrector.replace(offense_node, replacement)
88
+ end
89
+ end
78
90
  end
79
91
  end
80
92
  end
@@ -22,6 +22,15 @@ module RuboCop
22
22
  # # bad
23
23
  # [:foo, :bar, :baz]
24
24
  #
25
+ # # bad (contains spaces)
26
+ # %i[foo\ bar baz\ quux]
27
+ #
28
+ # # bad (contains [] with spaces)
29
+ # %i[foo \[ \]]
30
+ #
31
+ # # bad (contains () with spaces)
32
+ # %i(foo \( \))
33
+ #
25
34
  # @example EnforcedStyle: brackets
26
35
  # # good
27
36
  # [:foo, :bar, :baz]
@@ -40,6 +49,7 @@ module RuboCop
40
49
 
41
50
  PERCENT_MSG = 'Use `%i` or `%I` for an array of symbols.'
42
51
  ARRAY_MSG = 'Use %<prefer>s for an array of symbols.'
52
+ DELIMITERS = ['[', ']', '(', ')'].freeze
43
53
 
44
54
  class << self
45
55
  attr_accessor :largest_brackets
@@ -47,7 +57,7 @@ module RuboCop
47
57
 
48
58
  def on_array(node)
49
59
  if bracketed_array_of?(:sym, node)
50
- return if symbols_contain_spaces?(node)
60
+ return if complex_content?(node)
51
61
 
52
62
  check_bracketed_array(node, 'i')
53
63
  elsif node.percent_literal?(:symbol)
@@ -57,13 +67,22 @@ module RuboCop
57
67
 
58
68
  private
59
69
 
60
- def symbols_contain_spaces?(node)
70
+ def complex_content?(node)
61
71
  node.children.any? do |sym|
62
72
  content, = *sym
63
- content.to_s.include?(' ')
73
+ content = content.to_s
74
+ content_without_delimiter_pairs = content.gsub(/(\[\])|(\(\))/, '')
75
+
76
+ content.include?(' ') || DELIMITERS.any? do |delimiter|
77
+ content_without_delimiter_pairs.include?(delimiter)
78
+ end
64
79
  end
65
80
  end
66
81
 
82
+ def invalid_percent_array_contents?(node)
83
+ complex_content?(node)
84
+ end
85
+
67
86
  def build_bracketed_array(node)
68
87
  return '[]' if node.children.empty?
69
88
 
@@ -63,7 +63,7 @@ module RuboCop
63
63
 
64
64
  def classname_attribute_value(file)
65
65
  @classname_attribute_value_cache ||= Hash.new do |hash, key|
66
- hash[key] = key.gsub(/\.rb\Z/, '').gsub("#{Dir.pwd}/", '').tr('/', '.')
66
+ hash[key] = key.delete_suffix('.rb').gsub("#{Dir.pwd}/", '').tr('/', '.')
67
67
  end
68
68
  @classname_attribute_value_cache[file]
69
69
  end
@@ -36,7 +36,9 @@ module RuboCop
36
36
  end
37
37
 
38
38
  handle 'initialize' do |request|
39
- @server.configure(safe_autocorrect: safe_autocorrect?(request))
39
+ initialization_options = extract_initialization_options_from(request)
40
+
41
+ @server.configure(initialization_options)
40
42
 
41
43
  @server.write(
42
44
  id: request[:id],
@@ -164,10 +166,14 @@ module RuboCop
164
166
 
165
167
  private
166
168
 
167
- def safe_autocorrect?(request)
169
+ def extract_initialization_options_from(request)
168
170
  safe_autocorrect = request.dig(:params, :initializationOptions, :safeAutocorrect)
169
171
 
170
- safe_autocorrect.nil? || safe_autocorrect == true
172
+ {
173
+ safe_autocorrect: safe_autocorrect.nil? || safe_autocorrect == true,
174
+ lint_mode: request.dig(:params, :initializationOptions, :lintMode) == true,
175
+ layout_mode: request.dig(:params, :initializationOptions, :layoutMode) == true
176
+ }
171
177
  end
172
178
 
173
179
  def format_file(file_uri)
@@ -14,12 +14,14 @@ module RuboCop
14
14
  # Runtime for Language Server Protocol of RuboCop.
15
15
  # @api private
16
16
  class Runtime
17
- attr_writer :safe_autocorrect
17
+ attr_writer :safe_autocorrect, :lint_mode, :layout_mode
18
18
 
19
19
  def initialize(config_store)
20
20
  @config_store = config_store
21
21
  @logged_paths = []
22
22
  @safe_autocorrect = true
23
+ @lint_mode = false
24
+ @layout_mode = false
23
25
  end
24
26
 
25
27
  # This abuses the `--stdin` option of rubocop and reads the formatted text
@@ -37,6 +39,7 @@ module RuboCop
37
39
  formatting_options = {
38
40
  stdin: text, force_exclusion: true, autocorrect: true, safe_autocorrect: @safe_autocorrect
39
41
  }
42
+ formatting_options[:only] = config_only_options if @lint_mode || @layout_mode
40
43
 
41
44
  redirect_stdout { run_rubocop(formatting_options, path) }
42
45
 
@@ -47,6 +50,7 @@ module RuboCop
47
50
  diagnostic_options = {
48
51
  stdin: text, force_exclusion: true, formatters: ['json'], format: 'json'
49
52
  }
53
+ diagnostic_options[:only] = config_only_options if @lint_mode || @layout_mode
50
54
 
51
55
  json = redirect_stdout { run_rubocop(diagnostic_options, path) }
52
56
  results = JSON.parse(json, symbolize_names: true)
@@ -64,6 +68,13 @@ module RuboCop
64
68
 
65
69
  private
66
70
 
71
+ def config_only_options
72
+ only_options = []
73
+ only_options << 'Lint' if @lint_mode
74
+ only_options << 'Layout' if @layout_mode
75
+ only_options
76
+ end
77
+
67
78
  def redirect_stdout(&block)
68
79
  stdout = StringIO.new
69
80
 
@@ -53,8 +53,10 @@ module RuboCop
53
53
  @runtime.offenses(path, text)
54
54
  end
55
55
 
56
- def configure(safe_autocorrect: true)
57
- @runtime.safe_autocorrect = safe_autocorrect
56
+ def configure(options)
57
+ @runtime.safe_autocorrect = options[:safe_autocorrect]
58
+ @runtime.lint_mode = options[:lint_mode]
59
+ @runtime.layout_mode = options[:layout_mode]
58
60
  end
59
61
 
60
62
  def stop(&block)
@@ -202,6 +202,10 @@ module RuboCop
202
202
  lib_root = File.join(File.dirname(__FILE__), '..')
203
203
  exe_root = File.join(lib_root, '..', 'exe')
204
204
 
205
+ # Make sure to use an absolute path to prevent errors on Windows
206
+ # when traversing the relative paths with symlinks.
207
+ exe_root = File.absolute_path(exe_root)
208
+
205
209
  # These are all the files we have `require`d plus everything in the
206
210
  # exe directory. A change to any of them could affect the cop output
207
211
  # so we include them in the cache hash.
@@ -94,8 +94,12 @@ module RuboCop
94
94
  end
95
95
 
96
96
  def wanted_dir_patterns(base_dir, exclude_pattern, flags)
97
- base_dir = base_dir.gsub('/{}/', '/\{}/')
98
- dirs = Dir.glob(File.join(base_dir.gsub('/*/', '/\*/').gsub('/**/', '/\**/'), '*/'), flags)
97
+ # Escape glob characters in base_dir to avoid unwanted behavior.
98
+ base_dir = base_dir.gsub(/[\\\{\}\[\]\*\?]/) do |reserved_glob_character|
99
+ "\\#{reserved_glob_character}"
100
+ end
101
+
102
+ dirs = Dir.glob(File.join(base_dir, '*/'), flags)
99
103
  .reject do |dir|
100
104
  next true if dir.end_with?('/./', '/../')
101
105
  next true if File.fnmatch?(exclude_pattern, dir, flags)
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  # This module holds the RuboCop version information.
5
5
  module Version
6
- STRING = '1.54.2'
6
+ STRING = '1.55.0'
7
7
 
8
8
  MSG = '%<version>s (using Parser %<parser_version>s, ' \
9
9
  'rubocop-ast %<rubocop_ast_version>s, ' \
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.54.2
4
+ version: 1.55.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: 2023-07-13 00:00:00.000000000 Z
13
+ date: 2023-07-25 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: json
@@ -134,7 +134,7 @@ dependencies:
134
134
  requirements:
135
135
  - - ">="
136
136
  - !ruby/object:Gem::Version
137
- version: 1.28.0
137
+ version: 1.28.1
138
138
  - - "<"
139
139
  - !ruby/object:Gem::Version
140
140
  version: '2.0'
@@ -144,7 +144,7 @@ dependencies:
144
144
  requirements:
145
145
  - - ">="
146
146
  - !ruby/object:Gem::Version
147
- version: 1.28.0
147
+ version: 1.28.1
148
148
  - - "<"
149
149
  - !ruby/object:Gem::Version
150
150
  version: '2.0'
@@ -1023,7 +1023,7 @@ metadata:
1023
1023
  homepage_uri: https://rubocop.org/
1024
1024
  changelog_uri: https://github.com/rubocop/rubocop/blob/master/CHANGELOG.md
1025
1025
  source_code_uri: https://github.com/rubocop/rubocop/
1026
- documentation_uri: https://docs.rubocop.org/rubocop/1.54/
1026
+ documentation_uri: https://docs.rubocop.org/rubocop/1.55/
1027
1027
  bug_tracker_uri: https://github.com/rubocop/rubocop/issues
1028
1028
  rubygems_mfa_required: 'true'
1029
1029
  post_install_message: