rubocop 0.78.0 → 0.79.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +2 -1
  4. data/lib/rubocop.rb +6 -0
  5. data/lib/rubocop/ast/builder.rb +43 -42
  6. data/lib/rubocop/ast/node/def_node.rb +11 -0
  7. data/lib/rubocop/ast/node/forward_args_node.rb +18 -0
  8. data/lib/rubocop/ast/traversal.rb +11 -3
  9. data/lib/rubocop/cli/command/show_cops.rb +11 -4
  10. data/lib/rubocop/config_loader.rb +19 -19
  11. data/lib/rubocop/config_validator.rb +49 -91
  12. data/lib/rubocop/cop/autocorrect_logic.rb +6 -3
  13. data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +1 -1
  14. data/lib/rubocop/cop/generator.rb +3 -4
  15. data/lib/rubocop/cop/generator/configuration_injector.rb +1 -1
  16. data/lib/rubocop/cop/layout/hash_alignment.rb +8 -4
  17. data/lib/rubocop/cop/layout/multiline_block_layout.rb +14 -5
  18. data/lib/rubocop/cop/layout/space_before_block_braces.rb +2 -2
  19. data/lib/rubocop/cop/lint/debugger.rb +2 -2
  20. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +1 -1
  21. data/lib/rubocop/cop/migration/department_name.rb +16 -1
  22. data/lib/rubocop/cop/mixin/frozen_string_literal.rb +1 -7
  23. data/lib/rubocop/cop/naming/method_parameter_name.rb +1 -1
  24. data/lib/rubocop/cop/registry.rb +7 -2
  25. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +4 -207
  26. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +168 -0
  27. data/lib/rubocop/cop/style/method_call_with_args_parentheses/require_parentheses.rb +54 -0
  28. data/lib/rubocop/cop/style/multiline_when_then.rb +5 -1
  29. data/lib/rubocop/cop/style/numeric_predicate.rb +4 -3
  30. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +7 -7
  31. data/lib/rubocop/cop/style/yoda_condition.rb +16 -1
  32. data/lib/rubocop/rspec/shared_contexts.rb +5 -0
  33. data/lib/rubocop/target_ruby.rb +151 -0
  34. data/lib/rubocop/version.rb +1 -1
  35. metadata +8 -4
@@ -24,15 +24,18 @@ module RuboCop
24
24
  @options[:disable_uncorrectable] == true
25
25
  end
26
26
 
27
+ def safe_autocorrect?
28
+ cop_config.fetch('Safe', true) &&
29
+ cop_config.fetch('SafeAutoCorrect', true)
30
+ end
31
+
27
32
  def autocorrect_enabled?
28
33
  # allow turning off autocorrect on a cop by cop basis
29
34
  return true unless cop_config
30
35
 
31
36
  return false if cop_config['AutoCorrect'] == false
32
37
 
33
- if @options.fetch(:safe_auto_correct, false)
34
- return cop_config.fetch('SafeAutoCorrect', true)
35
- end
38
+ return safe_autocorrect? if @options.fetch(:safe_auto_correct, false)
36
39
 
37
40
  true
38
41
  end
@@ -98,7 +98,7 @@ module RuboCop
98
98
  end
99
99
 
100
100
  def_node_search :dependency_declarations, <<~PATTERN
101
- (send (lvar _) {:add_dependency :add_runtime_dependency :add_development_dependency} ...)
101
+ (send (lvar _) {:add_dependency :add_runtime_dependency :add_development_dependency} (str _) ...)
102
102
  PATTERN
103
103
  end
104
104
  end
@@ -104,10 +104,9 @@ module RuboCop
104
104
  end
105
105
  SPEC
106
106
 
107
- CONFIGURATION_ADDED_MESSAGE = <<~MESSAGE
108
- [modify] A configuration for the cop is added into %<configuration_file_path>s.
109
- If you want to disable the cop by default, set `Enabled` option to false.
110
- MESSAGE
107
+ CONFIGURATION_ADDED_MESSAGE =
108
+ '[modify] A configuration for the cop is added into ' \
109
+ '%<configuration_file_path>s.'
111
110
 
112
111
  def initialize(name, github_user, output: $stdout)
113
112
  @badge = Badge.parse(name)
@@ -10,7 +10,7 @@ module RuboCop
10
10
  TEMPLATE = <<~YAML
11
11
  %<badge>s:
12
12
  Description: 'TODO: Write a description of the cop.'
13
- Enabled: true
13
+ Enabled: pending
14
14
  VersionAdded: '%<version_added>s'
15
15
  YAML
16
16
 
@@ -179,8 +179,12 @@ module RuboCop
179
179
  include HashAlignmentStyles
180
180
  include RangeHelp
181
181
 
182
- MSG = 'Align the elements of a hash literal if they span more than ' \
183
- 'one line.'
182
+ MESSAGES = { KeyAlignment => 'Align the keys of a hash literal if ' \
183
+ 'they span more than one line.',
184
+ SeparatorAlignment => 'Align the separators of a hash ' \
185
+ 'literal if they span more than one line.',
186
+ TableAlignment => 'Align the keys and values of a hash ' \
187
+ 'literal if they span more than one line.' }.freeze
184
188
 
185
189
  def on_send(node)
186
190
  return if double_splat?(node)
@@ -249,9 +253,9 @@ module RuboCop
249
253
  end
250
254
 
251
255
  def add_offences
252
- _format, offences = offences_by.min_by { |_, v| v.length }
256
+ format, offences = offences_by.min_by { |_, v| v.length }
253
257
  (offences || []).each do |offence|
254
- add_offense offence
258
+ add_offense(offence, message: MESSAGES[format])
255
259
  end
256
260
  end
257
261
 
@@ -97,7 +97,8 @@ module RuboCop
97
97
  def line_break_necessary_in_args?(node)
98
98
  needed_length = node.source_range.column +
99
99
  node.source.lines.first.length +
100
- block_arg_string(node.arguments).length + PIPE_SIZE
100
+ block_arg_string(node, node.arguments).length +
101
+ PIPE_SIZE
101
102
  needed_length > max_line_length
102
103
  end
103
104
 
@@ -115,7 +116,8 @@ module RuboCop
115
116
  newlines: false
116
117
  ).end_pos
117
118
  range = range_between(node.loc.begin.end.begin_pos, end_pos)
118
- corrector.replace(range, " |#{block_arg_string(node.arguments)}|")
119
+ corrector.replace(range,
120
+ " |#{block_arg_string(node, node.arguments)}|")
119
121
  end
120
122
 
121
123
  def autocorrect_body(corrector, node, block_body)
@@ -131,14 +133,21 @@ module RuboCop
131
133
  "\n #{' ' * block_start_col}")
132
134
  end
133
135
 
134
- def block_arg_string(args)
135
- args.children.map do |arg|
136
+ def block_arg_string(node, args)
137
+ arg_string = args.children.map do |arg|
136
138
  if arg.mlhs_type?
137
- "(#{block_arg_string(arg)})"
139
+ "(#{block_arg_string(node, arg)})"
138
140
  else
139
141
  arg.source
140
142
  end
141
143
  end.join(', ')
144
+ arg_string += ',' if include_trailing_comma?(node.arguments)
145
+ arg_string
146
+ end
147
+
148
+ def include_trailing_comma?(args)
149
+ arg_count = args.each_descendant(:arg).to_a.size
150
+ arg_count == 1 && args.source.include?(',')
142
151
  end
143
152
  end
144
153
  end
@@ -47,7 +47,7 @@ module RuboCop
47
47
  # That means preventing auto-correction to incorrect auto-corrected
48
48
  # code.
49
49
  # See: https://github.com/rubocop-hq/rubocop/issues/7534
50
- return if conflict_with_block_delimiters?
50
+ return if conflict_with_block_delimiters?(node)
51
51
 
52
52
  left_brace = node.loc.begin
53
53
  space_plus_brace = range_with_surrounding_space(range: left_brace)
@@ -118,7 +118,7 @@ module RuboCop
118
118
  end
119
119
  end
120
120
 
121
- def conflict_with_block_delimiters?
121
+ def conflict_with_block_delimiters?(node)
122
122
  block_delimiters_style == 'line_count_based' &&
123
123
  style == :no_space && node.multiline?
124
124
  end
@@ -43,9 +43,9 @@ module RuboCop
43
43
  PATTERN
44
44
 
45
45
  def_node_matcher :debugger_call?, <<~PATTERN
46
- {(send {nil? #kernel?} {:debugger :byebug :remote_byebug} ...)
46
+ {(send {nil? #kernel?} {:debugger :byebug :remote_byebug :console} ...)
47
47
  (send (send {#kernel? nil?} :binding)
48
- {:pry :remote_pry :pry_remote} ...)
48
+ {:pry :remote_pry :pry_remote :console} ...)
49
49
  (send (const {nil? (cbase)} :Pry) :rescue ...)
50
50
  (send nil? {:save_and_open_page
51
51
  :save_and_open_screenshot
@@ -213,7 +213,7 @@ module RuboCop
213
213
  end
214
214
 
215
215
  def matching_range(haystack, needle)
216
- offset = (haystack.source =~ Regexp.new(Regexp.escape(needle)))
216
+ offset = haystack.source.index(needle)
217
217
  return unless offset
218
218
 
219
219
  offset += haystack.begin_pos
@@ -10,13 +10,24 @@ module RuboCop
10
10
 
11
11
  MSG = 'Department name is missing.'
12
12
 
13
+ DISABLE_COMMENT_FORMAT =
14
+ /\A(# *rubocop *: *((dis|en)able|todo) +)(.*)/.freeze
15
+
16
+ # The token that makes up a disable comment.
17
+ # The token used after `# rubocop: disable` are `A-z`, `/`, and `,`.
18
+ # Also `A-z` includes `all`.
19
+ DISABLING_COPS_CONTENT_TOKEN = %r{[A-z/,]+}.freeze
20
+
13
21
  def investigate(processed_source)
14
22
  processed_source.each_comment do |comment|
15
- next if comment.text !~ /\A(# *rubocop:((dis|en)able|todo) +)(.*)/
23
+ next if comment.text !~ DISABLE_COMMENT_FORMAT
16
24
 
17
25
  offset = Regexp.last_match(1).length
18
26
  Regexp.last_match(4).scan(%r{[\w/]+|\W+}) do |name|
27
+ break unless valid_content_token?(name.strip)
28
+
19
29
  check_cop_name(name, comment, offset)
30
+
20
31
  offset += name.length
21
32
  end
22
33
  end
@@ -38,6 +49,10 @@ module RuboCop
38
49
  range = range_between(start, start + name.length)
39
50
  add_offense(range, location: range)
40
51
  end
52
+
53
+ def valid_content_token?(content_token)
54
+ !DISABLING_COPS_CONTENT_TOKEN.match(content_token).nil?
55
+ end
41
56
  end
42
57
  end
43
58
  end
@@ -40,13 +40,7 @@ module RuboCop
40
40
  end
41
41
 
42
42
  def leading_comment_lines
43
- comments = processed_source.comments
44
-
45
- comments.each_with_object([]) do |comment, leading_comments|
46
- next if comment.loc.line > 3
47
-
48
- leading_comments << comment.text
49
- end
43
+ processed_source.comments.first(3).map(&:text)
50
44
  end
51
45
  end
52
46
  end
@@ -49,7 +49,7 @@ module RuboCop
49
49
  def on_def(node)
50
50
  return unless node.arguments?
51
51
 
52
- check(node, node.arguments)
52
+ check(node, node.arguments.reject(&:forward_args_type?))
53
53
  end
54
54
  alias on_defs on_def
55
55
  end
@@ -146,10 +146,15 @@ module RuboCop
146
146
 
147
147
  def enabled?(cop, config, only_safe)
148
148
  cfg = config.for_cop(cop)
149
+
150
+ # cfg['Enabled'] might be a string `pending`, which is considered
151
+ # disabled
152
+ cop_enabled = cfg.fetch('Enabled') == true
153
+
149
154
  if only_safe
150
- cfg.fetch('Enabled') && cfg.fetch('Safe', true)
155
+ cop_enabled && cfg.fetch('Safe', true)
151
156
  else
152
- cfg.fetch('Enabled')
157
+ cop_enabled
153
158
  end
154
159
  end
155
160
 
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # rubocop:disable Metrics/ClassLength
4
3
  module RuboCop
5
4
  module Cop
6
5
  module Style
@@ -43,7 +42,6 @@ module RuboCop
43
42
  #
44
43
  # @example EnforcedStyle: require_parentheses (default)
45
44
  #
46
- #
47
45
  # # bad
48
46
  # array.delete e
49
47
  #
@@ -150,94 +148,18 @@ module RuboCop
150
148
  include IgnoredMethods
151
149
  include IgnoredPattern
152
150
 
153
- TRAILING_WHITESPACE_REGEX = /\s+\Z/.freeze
154
-
155
- def on_send(node)
156
- case style
157
- when :require_parentheses
158
- add_offense_for_require_parentheses(node)
159
- when :omit_parentheses
160
- add_offense_for_omit_parentheses(node)
161
- end
162
- end
163
- alias on_csend on_send
164
- alias on_super on_send
165
- alias on_yield on_send
166
-
167
- def autocorrect(node)
168
- case style
169
- when :require_parentheses
170
- autocorrect_for_require_parentheses(node)
171
- when :omit_parentheses
172
- autocorrect_for_omit_parentheses(node)
173
- end
174
- end
175
-
176
- def message(_node = nil)
151
+ def initialize(*)
152
+ super
177
153
  case style
178
154
  when :require_parentheses
179
- 'Use parentheses for method calls with arguments.'
155
+ extend RequireParentheses
180
156
  when :omit_parentheses
181
- 'Omit parentheses for method calls with arguments.'
157
+ extend OmitParentheses
182
158
  end
183
159
  end
184
160
 
185
161
  private
186
162
 
187
- def add_offense_for_require_parentheses(node)
188
- return if ignored_method?(node.method_name)
189
- return if matches_ignored_pattern?(node.method_name)
190
- return if eligible_for_parentheses_omission?(node)
191
- return unless node.arguments? && !node.parenthesized?
192
-
193
- add_offense(node)
194
- end
195
-
196
- def add_offense_for_omit_parentheses(node)
197
- return unless node.parenthesized?
198
- return if node.implicit_call?
199
- return if super_call_without_arguments?(node)
200
- return if allowed_camel_case_method_call?(node)
201
- return if legitimate_call_with_parentheses?(node)
202
-
203
- add_offense(node, location: node.loc.begin.join(node.loc.end))
204
- end
205
-
206
- def autocorrect_for_require_parentheses(node)
207
- lambda do |corrector|
208
- corrector.replace(args_begin(node), '(')
209
-
210
- unless args_parenthesized?(node)
211
- corrector.insert_after(args_end(node), ')')
212
- end
213
- end
214
- end
215
-
216
- def autocorrect_for_omit_parentheses(node)
217
- lambda do |corrector|
218
- if parentheses_at_the_end_of_multiline_call?(node)
219
- corrector.replace(args_begin(node), ' \\')
220
- else
221
- corrector.replace(args_begin(node), ' ')
222
- end
223
- corrector.remove(node.loc.end)
224
- end
225
- end
226
-
227
- def eligible_for_parentheses_omission?(node)
228
- node.operator_method? || node.setter_method? || ignored_macro?(node)
229
- end
230
-
231
- def included_macros_list
232
- cop_config.fetch('IncludedMacros', []).map(&:to_sym)
233
- end
234
-
235
- def ignored_macro?(node)
236
- cop_config['IgnoreMacros'] &&
237
- node.macro? &&
238
- !included_macros_list.include?(node.method_name)
239
- end
240
-
241
163
  def args_begin(node)
242
164
  loc = node.loc
243
165
  selector =
@@ -257,132 +179,7 @@ module RuboCop
257
179
  first_node = node.arguments.first
258
180
  first_node.begin_type? && first_node.parenthesized_call?
259
181
  end
260
-
261
- def parentheses_at_the_end_of_multiline_call?(node)
262
- node.multiline? &&
263
- node.loc.begin.source_line
264
- .gsub(TRAILING_WHITESPACE_REGEX, '')
265
- .end_with?('(')
266
- end
267
-
268
- def super_call_without_arguments?(node)
269
- node.super_type? && node.arguments.none?
270
- end
271
-
272
- def allowed_camel_case_method_call?(node)
273
- node.camel_case_method? &&
274
- (node.arguments.none? ||
275
- cop_config['AllowParenthesesInCamelCaseMethod'])
276
- end
277
-
278
- def legitimate_call_with_parentheses?(node)
279
- call_in_literals?(node) ||
280
- call_with_ambiguous_arguments?(node) ||
281
- call_in_logical_operators?(node) ||
282
- call_in_optional_arguments?(node) ||
283
- allowed_multiline_call_with_parentheses?(node) ||
284
- allowed_chained_call_with_parentheses?(node)
285
- end
286
-
287
- def call_in_literals?(node)
288
- node.parent &&
289
- (node.parent.pair_type? ||
290
- node.parent.array_type? ||
291
- node.parent.range_type? ||
292
- splat?(node.parent) ||
293
- ternary_if?(node.parent))
294
- end
295
-
296
- def call_in_logical_operators?(node)
297
- node.parent &&
298
- (logical_operator?(node.parent) ||
299
- node.parent.send_type? &&
300
- node.parent.arguments.any?(&method(:logical_operator?)))
301
- end
302
-
303
- def call_in_optional_arguments?(node)
304
- node.parent &&
305
- (node.parent.optarg_type? || node.parent.kwoptarg_type?)
306
- end
307
-
308
- def call_with_ambiguous_arguments?(node)
309
- call_with_braced_block?(node) ||
310
- call_as_argument_or_chain?(node) ||
311
- hash_literal_in_arguments?(node) ||
312
- node.descendants.any? do |n|
313
- ambigious_literal?(n) || logical_operator?(n) ||
314
- call_with_braced_block?(n)
315
- end
316
- end
317
-
318
- def call_with_braced_block?(node)
319
- (node.send_type? || node.super_type?) &&
320
- node.block_node && node.block_node.braces?
321
- end
322
-
323
- def call_as_argument_or_chain?(node)
324
- node.parent &&
325
- (node.parent.send_type? && !assigned_before?(node.parent, node) ||
326
- node.parent.csend_type? || node.parent.super_type?)
327
- end
328
-
329
- def hash_literal_in_arguments?(node)
330
- node.arguments.any? do |n|
331
- hash_literal?(n) ||
332
- n.send_type? && node.descendants.any?(&method(:hash_literal?))
333
- end
334
- end
335
-
336
- def allowed_multiline_call_with_parentheses?(node)
337
- cop_config['AllowParenthesesInMultilineCall'] && node.multiline?
338
- end
339
-
340
- def allowed_chained_call_with_parentheses?(node)
341
- return false unless cop_config['AllowParenthesesInChaining']
342
-
343
- previous = node.descendants.first
344
- return false unless previous&.send_type?
345
-
346
- previous.parenthesized? ||
347
- allowed_chained_call_with_parentheses?(previous)
348
- end
349
-
350
- def ambigious_literal?(node)
351
- splat?(node) || ternary_if?(node) || regexp_slash_literal?(node) ||
352
- unary_literal?(node)
353
- end
354
-
355
- def splat?(node)
356
- node.splat_type? || node.kwsplat_type? || node.block_pass_type?
357
- end
358
-
359
- def ternary_if?(node)
360
- node.if_type? && node.ternary?
361
- end
362
-
363
- def logical_operator?(node)
364
- (node.and_type? || node.or_type?) && node.logical_operator?
365
- end
366
-
367
- def hash_literal?(node)
368
- node.hash_type? && node.braces?
369
- end
370
-
371
- def regexp_slash_literal?(node)
372
- node.regexp_type? && node.loc.begin.source == '/'
373
- end
374
-
375
- def unary_literal?(node)
376
- node.numeric_type? && node.sign? ||
377
- node.parent&.send_type? && node.parent&.unary_operation?
378
- end
379
-
380
- def assigned_before?(node, target)
381
- node.assignment? &&
382
- node.loc.operator.begin < target.loc.begin
383
- end
384
182
  end
385
183
  end
386
184
  end
387
185
  end
388
- # rubocop:enable Metrics/ClassLength