rubocop 1.54.2 → 1.55.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: 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: