rubocop 1.66.1 → 1.68.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.
Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +55 -6
  4. data/config/internal_affairs.yml +11 -0
  5. data/lib/rubocop/cached_data.rb +12 -4
  6. data/lib/rubocop/cli/command/auto_generate_config.rb +6 -7
  7. data/lib/rubocop/cli/command/execute_runner.rb +1 -1
  8. data/lib/rubocop/cli/command/lsp.rb +2 -2
  9. data/lib/rubocop/cli/command/version.rb +2 -2
  10. data/lib/rubocop/config_loader_resolver.rb +3 -3
  11. data/lib/rubocop/config_validator.rb +2 -1
  12. data/lib/rubocop/cop/autocorrect_logic.rb +22 -2
  13. data/lib/rubocop/cop/base.rb +6 -2
  14. data/lib/rubocop/cop/bundler/gem_version.rb +1 -0
  15. data/lib/rubocop/cop/cop.rb +8 -0
  16. data/lib/rubocop/cop/correctors/alignment_corrector.rb +1 -12
  17. data/lib/rubocop/cop/correctors/parentheses_corrector.rb +1 -1
  18. data/lib/rubocop/cop/correctors/percent_literal_corrector.rb +10 -0
  19. data/lib/rubocop/cop/internal_affairs/cop_description.rb +0 -4
  20. data/lib/rubocop/cop/internal_affairs/redundant_message_argument.rb +6 -21
  21. data/lib/rubocop/cop/internal_affairs/redundant_source_range.rb +8 -1
  22. data/lib/rubocop/cop/internal_affairs/useless_message_assertion.rb +0 -5
  23. data/lib/rubocop/cop/internal_affairs.rb +16 -0
  24. data/lib/rubocop/cop/layout/access_modifier_indentation.rb +5 -1
  25. data/lib/rubocop/cop/layout/def_end_alignment.rb +1 -1
  26. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +1 -1
  27. data/lib/rubocop/cop/layout/first_method_argument_line_break.rb +8 -0
  28. data/lib/rubocop/cop/layout/indentation_width.rb +4 -5
  29. data/lib/rubocop/cop/layout/leading_comment_space.rb +56 -1
  30. data/lib/rubocop/cop/layout/space_before_brackets.rb +5 -5
  31. data/lib/rubocop/cop/layout/space_inside_block_braces.rb +4 -0
  32. data/lib/rubocop/cop/lint/ambiguous_range.rb +4 -1
  33. data/lib/rubocop/cop/lint/big_decimal_new.rb +4 -7
  34. data/lib/rubocop/cop/lint/boolean_symbol.rb +1 -1
  35. data/lib/rubocop/cop/lint/duplicate_branch.rb +39 -4
  36. data/lib/rubocop/cop/lint/duplicate_set_element.rb +74 -0
  37. data/lib/rubocop/cop/lint/ensure_return.rb +0 -3
  38. data/lib/rubocop/cop/lint/erb_new_arguments.rb +1 -1
  39. data/lib/rubocop/cop/lint/float_comparison.rb +1 -1
  40. data/lib/rubocop/cop/lint/implicit_string_concatenation.rb +10 -4
  41. data/lib/rubocop/cop/lint/it_without_arguments_in_block.rb +5 -14
  42. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +25 -2
  43. data/lib/rubocop/cop/lint/non_atomic_file_operation.rb +7 -0
  44. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +5 -6
  45. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +1 -1
  46. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +9 -0
  47. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +107 -41
  48. data/lib/rubocop/cop/lint/symbol_conversion.rb +1 -1
  49. data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +88 -0
  50. data/lib/rubocop/cop/lint/uri_regexp.rb +25 -7
  51. data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +4 -1
  52. data/lib/rubocop/cop/mixin/check_line_breakable.rb +10 -0
  53. data/lib/rubocop/cop/mixin/endless_method_rewriter.rb +24 -0
  54. data/lib/rubocop/cop/mixin/frozen_string_literal.rb +3 -1
  55. data/lib/rubocop/cop/mixin/percent_array.rb +1 -1
  56. data/lib/rubocop/cop/mixin/statement_modifier.rb +3 -2
  57. data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
  58. data/lib/rubocop/cop/naming/inclusive_language.rb +12 -3
  59. data/lib/rubocop/cop/naming/predicate_name.rb +1 -1
  60. data/lib/rubocop/cop/offense.rb +4 -5
  61. data/lib/rubocop/cop/style/access_modifier_declarations.rb +12 -2
  62. data/lib/rubocop/cop/style/accessor_grouping.rb +10 -2
  63. data/lib/rubocop/cop/style/ambiguous_endless_method_definition.rb +79 -0
  64. data/lib/rubocop/cop/style/arguments_forwarding.rb +46 -6
  65. data/lib/rubocop/cop/style/bitwise_predicate.rb +100 -0
  66. data/lib/rubocop/cop/style/block_delimiters.rb +31 -3
  67. data/lib/rubocop/cop/style/collection_compact.rb +10 -10
  68. data/lib/rubocop/cop/style/combinable_defined.rb +115 -0
  69. data/lib/rubocop/cop/style/combinable_loops.rb +7 -0
  70. data/lib/rubocop/cop/style/commented_keyword.rb +7 -1
  71. data/lib/rubocop/cop/style/conditional_assignment.rb +1 -1
  72. data/lib/rubocop/cop/style/data_inheritance.rb +1 -1
  73. data/lib/rubocop/cop/style/endless_method.rb +1 -14
  74. data/lib/rubocop/cop/style/eval_with_location.rb +1 -1
  75. data/lib/rubocop/cop/style/guard_clause.rb +15 -2
  76. data/lib/rubocop/cop/style/hash_each_methods.rb +6 -0
  77. data/lib/rubocop/cop/style/hash_syntax.rb +2 -2
  78. data/lib/rubocop/cop/style/if_inside_else.rb +1 -1
  79. data/lib/rubocop/cop/style/if_with_semicolon.rb +7 -3
  80. data/lib/rubocop/cop/style/keyword_arguments_merging.rb +67 -0
  81. data/lib/rubocop/cop/style/lambda.rb +1 -1
  82. data/lib/rubocop/cop/style/map_into_array.rb +59 -8
  83. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +12 -7
  84. data/lib/rubocop/cop/style/multiline_memoization.rb +1 -1
  85. data/lib/rubocop/cop/style/multiple_comparison.rb +28 -39
  86. data/lib/rubocop/cop/style/nested_modifier.rb +1 -1
  87. data/lib/rubocop/cop/style/nested_parenthesized_calls.rb +1 -1
  88. data/lib/rubocop/cop/style/one_line_conditional.rb +4 -0
  89. data/lib/rubocop/cop/style/operator_method_call.rb +25 -6
  90. data/lib/rubocop/cop/style/redundant_begin.rb +4 -0
  91. data/lib/rubocop/cop/style/redundant_condition.rb +1 -1
  92. data/lib/rubocop/cop/style/redundant_line_continuation.rb +23 -5
  93. data/lib/rubocop/cop/style/redundant_parentheses.rb +9 -11
  94. data/lib/rubocop/cop/style/require_order.rb +1 -1
  95. data/lib/rubocop/cop/style/rescue_modifier.rb +13 -1
  96. data/lib/rubocop/cop/style/return_nil_in_predicate_method_definition.rb +54 -12
  97. data/lib/rubocop/cop/style/safe_navigation.rb +104 -50
  98. data/lib/rubocop/cop/style/safe_navigation_chain_length.rb +52 -0
  99. data/lib/rubocop/cop/style/select_by_regexp.rb +9 -6
  100. data/lib/rubocop/cop/style/semicolon.rb +1 -1
  101. data/lib/rubocop/cop/style/struct_inheritance.rb +1 -1
  102. data/lib/rubocop/cop/style/ternary_parentheses.rb +26 -5
  103. data/lib/rubocop/cop/style/trivial_accessors.rb +1 -1
  104. data/lib/rubocop/cop/team.rb +8 -1
  105. data/lib/rubocop/cop/util.rb +1 -1
  106. data/lib/rubocop/cop/variable_force/assignment.rb +18 -3
  107. data/lib/rubocop/cop/variable_force/branch.rb +1 -1
  108. data/lib/rubocop/cop/variable_force/variable.rb +5 -1
  109. data/lib/rubocop/cop/variable_force/variable_table.rb +2 -2
  110. data/lib/rubocop/cops_documentation_generator.rb +81 -40
  111. data/lib/rubocop/file_finder.rb +9 -4
  112. data/lib/rubocop/formatter/disabled_config_formatter.rb +1 -1
  113. data/lib/rubocop/lsp/runtime.rb +2 -0
  114. data/lib/rubocop/lsp/server.rb +0 -1
  115. data/lib/rubocop/rspec/expect_offense.rb +1 -0
  116. data/lib/rubocop/runner.rb +17 -6
  117. data/lib/rubocop/server/cache.rb +6 -1
  118. data/lib/rubocop/server/core.rb +1 -0
  119. data/lib/rubocop/target_ruby.rb +13 -13
  120. data/lib/rubocop/version.rb +28 -7
  121. data/lib/rubocop/yaml_duplication_checker.rb +20 -27
  122. data/lib/rubocop.rb +10 -0
  123. metadata +13 -4
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Looks for endless methods inside operations of lower precedence (`and`, `or`, and
7
+ # modifier forms of `if`, `unless`, `while`, `until`) that are ambiguous due to
8
+ # lack of parentheses. This may lead to unexpected behavior as the code may appear
9
+ # to use these keywords as part of the method but in fact they modify
10
+ # the method definition itself.
11
+ #
12
+ # In these cases, using a normal method definition is more clear.
13
+ #
14
+ # @example
15
+ #
16
+ # # bad
17
+ # def foo = true if bar
18
+ #
19
+ # # good - using a non-endless method is more explicit
20
+ # def foo
21
+ # true
22
+ # end if bar
23
+ #
24
+ # # ok - method body is explicit
25
+ # def foo = (true if bar)
26
+ #
27
+ # # ok - method definition is explicit
28
+ # (def foo = true) if bar
29
+ class AmbiguousEndlessMethodDefinition < Base
30
+ extend TargetRubyVersion
31
+ extend AutoCorrector
32
+ include EndlessMethodRewriter
33
+ include RangeHelp
34
+
35
+ minimum_target_ruby_version 3.0
36
+
37
+ MSG = 'Avoid using `%<keyword>s` statements with endless methods.'
38
+
39
+ # @!method ambiguous_endless_method_body(node)
40
+ def_node_matcher :ambiguous_endless_method_body, <<~PATTERN
41
+ ^${
42
+ (if _ <def _>)
43
+ ({and or} def _)
44
+ ({while until} _ def)
45
+ }
46
+ PATTERN
47
+
48
+ def on_def(node)
49
+ return unless node.endless?
50
+
51
+ operation = ambiguous_endless_method_body(node)
52
+ return unless operation
53
+
54
+ return unless modifier_form?(operation)
55
+
56
+ add_offense(operation, message: format(MSG, keyword: keyword(operation))) do |corrector|
57
+ correct_to_multiline(corrector, node)
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ def modifier_form?(operation)
64
+ return true if operation.and_type? || operation.or_type?
65
+
66
+ operation.modifier_form?
67
+ end
68
+
69
+ def keyword(operation)
70
+ if operation.respond_to?(:keyword)
71
+ operation.keyword
72
+ else
73
+ operation.operator
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -180,7 +180,8 @@ module RuboCop
180
180
  end
181
181
 
182
182
  def only_forwards_all?(send_classifications)
183
- send_classifications.all? { |_, c, _, _| c == :all }
183
+ all_classifications = %i[all all_anonymous].freeze
184
+ send_classifications.all? { |_, c, _, _| all_classifications.include?(c) }
184
185
  end
185
186
 
186
187
  # rubocop:disable Metrics/MethodLength
@@ -188,8 +189,8 @@ module RuboCop
188
189
  _rest_arg, _kwrest_arg, block_arg = *forwardable_args
189
190
  registered_block_arg_offense = false
190
191
 
191
- send_classifications.each do |send_node, _c, forward_rest, forward_kwrest, forward_block_arg| # rubocop:disable Layout/LineLength
192
- if !forward_rest && !forward_kwrest
192
+ send_classifications.each do |send_node, c, forward_rest, forward_kwrest, forward_block_arg| # rubocop:disable Layout/LineLength
193
+ if !forward_rest && !forward_kwrest && c != :all_anonymous
193
194
  # Prevents `anonymous block parameter is also used within block (SyntaxError)` occurs
194
195
  # in Ruby 3.3.0.
195
196
  if outside_block?(forward_block_arg)
@@ -213,6 +214,7 @@ module RuboCop
213
214
  # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
214
215
  def add_post_ruby_32_offenses(def_node, send_classifications, forwardable_args)
215
216
  return unless use_anonymous_forwarding?
217
+ return if send_inside_block?(send_classifications)
216
218
 
217
219
  rest_arg, kwrest_arg, block_arg = *forwardable_args
218
220
 
@@ -355,8 +357,15 @@ module RuboCop
355
357
  cop_config.fetch('UseAnonymousForwarding', false)
356
358
  end
357
359
 
360
+ def send_inside_block?(send_classifications)
361
+ send_classifications.any? do |send_node, *|
362
+ send_node.each_ancestor(:block, :numblock).any?
363
+ end
364
+ end
365
+
358
366
  def add_parens_if_missing(node, corrector)
359
367
  return if parentheses?(node)
368
+ return if node.send_type? && node.method?(:[])
360
369
 
361
370
  add_parentheses(node, corrector)
362
371
  end
@@ -374,6 +383,23 @@ module RuboCop
374
383
  # @!method forwarded_block_arg?(node, block_name)
375
384
  def_node_matcher :forwarded_block_arg?, '(block_pass {(lvar %1) nil?})'
376
385
 
386
+ # @!method def_all_anonymous_args?(node)
387
+ def_node_matcher :def_all_anonymous_args?, <<~PATTERN
388
+ (
389
+ def _
390
+ (args ... (restarg) (kwrestarg) (blockarg nil?))
391
+ _
392
+ )
393
+ PATTERN
394
+
395
+ # @!method send_all_anonymous_args?(node)
396
+ def_node_matcher :send_all_anonymous_args?, <<~PATTERN
397
+ (
398
+ send _ _
399
+ ... (forwarded_restarg) (hash (forwarded_kwrestarg)) (block_pass nil?)
400
+ )
401
+ PATTERN
402
+
377
403
  def initialize(def_node, send_node, referenced_lvars, forwardable_args, **config)
378
404
  @def_node = def_node
379
405
  @send_node = send_node
@@ -405,7 +431,9 @@ module RuboCop
405
431
  def classification
406
432
  return nil unless forwarded_rest_arg || forwarded_kwrest_arg || forwarded_block_arg
407
433
 
408
- if can_forward_all?
434
+ if ruby_32_only_anonymous_forwarding?
435
+ :all_anonymous
436
+ elsif can_forward_all?
409
437
  :all
410
438
  else
411
439
  :rest_or_kwrest
@@ -414,16 +442,28 @@ module RuboCop
414
442
 
415
443
  private
416
444
 
445
+ # rubocop:disable Metrics/CyclomaticComplexity
417
446
  def can_forward_all?
418
447
  return false if any_arg_referenced?
419
- return false if ruby_32_missing_rest_or_kwest?
448
+ return false if ruby_30_or_lower_optarg?
449
+ return false if ruby_32_or_higher_missing_rest_or_kwest?
420
450
  return false unless offensive_block_forwarding?
421
451
  return false if additional_kwargs_or_forwarded_kwargs?
422
452
 
423
453
  no_additional_args? || (target_ruby_version >= 3.0 && no_post_splat_args?)
424
454
  end
455
+ # rubocop:enable Metrics/CyclomaticComplexity
456
+
457
+ # def foo(a = 41, ...) is a syntax error in 3.0.
458
+ def ruby_30_or_lower_optarg?
459
+ target_ruby_version <= 3.0 && @def_node.arguments.any?(&:optarg_type?)
460
+ end
461
+
462
+ def ruby_32_only_anonymous_forwarding?
463
+ def_all_anonymous_args?(@def_node) && send_all_anonymous_args?(@send_node)
464
+ end
425
465
 
426
- def ruby_32_missing_rest_or_kwest?
466
+ def ruby_32_or_higher_missing_rest_or_kwest?
427
467
  target_ruby_version >= 3.2 && !forwarded_rest_and_kwrest_args
428
468
  end
429
469
 
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Prefer bitwise predicate methods over direct comparison operations.
7
+ #
8
+ # @safety
9
+ # This cop is unsafe, as it can produce false positives if the receiver
10
+ # is not an `Integer` object.
11
+ #
12
+ # @example
13
+ #
14
+ # # bad - checks any set bits
15
+ # (variable & flags).positive?
16
+ #
17
+ # # good
18
+ # variable.anybits?(flags)
19
+ #
20
+ # # bad - checks all set bits
21
+ # (variable & flags) == flags
22
+ #
23
+ # # good
24
+ # variable.allbits?(flags)
25
+ #
26
+ # # bad - checks no set bits
27
+ # (variable & flags).zero?
28
+ #
29
+ # # good
30
+ # variable.nobits?(flags)
31
+ #
32
+ class BitwisePredicate < Base
33
+ extend AutoCorrector
34
+ extend TargetRubyVersion
35
+
36
+ MSG = 'Replace with `%<preferred>s` for comparison with bit flags.'
37
+ RESTRICT_ON_SEND = %i[!= == > >= positive? zero?].freeze
38
+
39
+ minimum_target_ruby_version 2.5
40
+
41
+ # @!method anybits?(node)
42
+ def_node_matcher :anybits?, <<~PATTERN
43
+ {
44
+ (send #bit_operation? :positive?)
45
+ (send #bit_operation? :> (int 0))
46
+ (send #bit_operation? :>= (int 1))
47
+ (send #bit_operation? :!= (int 0))
48
+ }
49
+ PATTERN
50
+
51
+ # @!method allbits?(node)
52
+ def_node_matcher :allbits?, <<~PATTERN
53
+ {
54
+ (send (begin (send _ :& _flags)) :== _flags)
55
+ (send (begin (send _flags :& _)) :== _flags)
56
+ }
57
+ PATTERN
58
+
59
+ # @!method nobits?(node)
60
+ def_node_matcher :nobits?, <<~PATTERN
61
+ {
62
+ (send #bit_operation? :zero?)
63
+ (send #bit_operation? :== (int 0))
64
+ }
65
+ PATTERN
66
+
67
+ # @!method bit_operation?(node)
68
+ def_node_matcher :bit_operation?, <<~PATTERN
69
+ (begin
70
+ (send _ :& _))
71
+ PATTERN
72
+
73
+ def on_send(node)
74
+ return unless node.receiver.begin_type?
75
+ return unless (preferred_method = preferred_method(node))
76
+
77
+ bit_operation = node.receiver.children.first
78
+ lhs, _operator, rhs = *bit_operation
79
+ preferred = "#{lhs.source}.#{preferred_method}(#{rhs.source})"
80
+
81
+ add_offense(node, message: format(MSG, preferred: preferred)) do |corrector|
82
+ corrector.replace(node, preferred)
83
+ end
84
+ end
85
+
86
+ private
87
+
88
+ def preferred_method(node)
89
+ if anybits?(node)
90
+ 'anybits?'
91
+ elsif allbits?(node)
92
+ 'allbits?'
93
+ elsif nobits?(node)
94
+ 'nobits?'
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -176,6 +176,10 @@ module RuboCop
176
176
 
177
177
  BRACES_REQUIRED_MESSAGE = "Brace delimiters `{...}` required for '%<method_name>s' method."
178
178
 
179
+ def self.autocorrect_incompatible_with
180
+ [Style::RedundantBegin]
181
+ end
182
+
179
183
  def on_send(node)
180
184
  return unless node.arguments?
181
185
  return if node.parenthesized?
@@ -299,13 +303,28 @@ module RuboCop
299
303
 
300
304
  def move_comment_before_block(corrector, comment, block_node, closing_brace)
301
305
  range = block_node.chained? ? end_of_chain(block_node.parent).source_range : closing_brace
306
+
307
+ # It is possible that there is code between the block and the comment
308
+ # which needs to be preserved and trimmed.
309
+ pre_comment_range = source_range_before_comment(range, comment)
310
+
302
311
  corrector.remove(range_with_surrounding_space(comment.source_range, side: :right))
303
- remove_trailing_whitespace(corrector, range, comment)
304
- corrector.insert_after(range, "\n")
312
+ remove_trailing_whitespace(corrector, pre_comment_range, comment)
313
+ corrector.insert_after(pre_comment_range, "\n")
305
314
 
306
315
  corrector.insert_before(block_node, "#{comment.text}\n")
307
316
  end
308
317
 
318
+ def source_range_before_comment(range, comment)
319
+ range = range.end.join(comment.source_range.begin)
320
+
321
+ # End the range before any whitespace that precedes the comment
322
+ trailing_whitespace_count = range.source[/\s+\z/]&.length
323
+ range = range.adjust(end_pos: -trailing_whitespace_count) if trailing_whitespace_count
324
+
325
+ range
326
+ end
327
+
309
328
  def end_of_chain(node)
310
329
  return end_of_chain(node.block_node) if with_block?(node)
311
330
  return node unless node.chained?
@@ -341,8 +360,9 @@ module RuboCop
341
360
  end
342
361
  end
343
362
 
363
+ # rubocop:disable Metrics/CyclomaticComplexity
344
364
  def proper_block_style?(node)
345
- return true if require_braces?(node)
365
+ return true if require_braces?(node) || require_do_end?(node)
346
366
  return special_method_proper_block_style?(node) if special_method?(node.method_name)
347
367
 
348
368
  case style
@@ -352,6 +372,7 @@ module RuboCop
352
372
  when :always_braces then braces_style?(node)
353
373
  end
354
374
  end
375
+ # rubocop:enable Metrics/CyclomaticComplexity
355
376
 
356
377
  def require_braces?(node)
357
378
  return false unless node.braces?
@@ -361,6 +382,13 @@ module RuboCop
361
382
  end
362
383
  end
363
384
 
385
+ def require_do_end?(node)
386
+ return false if node.braces? || node.multiline?
387
+ return false unless (resbody = node.each_descendant(:resbody).first)
388
+
389
+ resbody.children.first&.array_type?
390
+ end
391
+
364
392
  def special_method?(method_name)
365
393
  allowed_method?(method_name) ||
366
394
  matches_allowed_pattern?(method_name) ||
@@ -21,6 +21,7 @@ module RuboCop
21
21
  # array.reject(&:nil?)
22
22
  # array.reject { |e| e.nil? }
23
23
  # array.select { |e| !e.nil? }
24
+ # array.filter { |e| !e.nil? }
24
25
  # array.grep_v(nil)
25
26
  # array.grep_v(NilClass)
26
27
  #
@@ -29,10 +30,9 @@ module RuboCop
29
30
  #
30
31
  # # bad
31
32
  # hash.reject!(&:nil?)
32
- # array.delete_if(&:nil?)
33
33
  # hash.reject! { |k, v| v.nil? }
34
- # array.delete_if { |e| e.nil? }
35
34
  # hash.select! { |k, v| !v.nil? }
35
+ # hash.filter! { |k, v| !v.nil? }
36
36
  #
37
37
  # # good
38
38
  # hash.compact!
@@ -48,14 +48,15 @@ module RuboCop
48
48
  extend TargetRubyVersion
49
49
 
50
50
  MSG = 'Use `%<good>s` instead of `%<bad>s`.'
51
- RESTRICT_ON_SEND = %i[reject delete_if reject! select select! grep_v].freeze
51
+ RESTRICT_ON_SEND = %i[reject reject! select select! filter filter! grep_v].freeze
52
52
  TO_ENUM_METHODS = %i[to_enum lazy].freeze
53
+ FILTER_METHODS = %i[filter filter!].freeze
53
54
 
54
55
  minimum_target_ruby_version 2.4
55
56
 
56
57
  # @!method reject_method_with_block_pass?(node)
57
58
  def_node_matcher :reject_method_with_block_pass?, <<~PATTERN
58
- (call !nil? {:reject :delete_if :reject!}
59
+ (call !nil? {:reject :reject!}
59
60
  (block_pass
60
61
  (sym :nil?)))
61
62
  PATTERN
@@ -64,7 +65,7 @@ module RuboCop
64
65
  def_node_matcher :reject_method?, <<~PATTERN
65
66
  (block
66
67
  (call
67
- !nil? {:reject :delete_if :reject!})
68
+ !nil? {:reject :reject!})
68
69
  $(args ...)
69
70
  (call
70
71
  $(lvar _) :nil?))
@@ -74,7 +75,7 @@ module RuboCop
74
75
  def_node_matcher :select_method?, <<~PATTERN
75
76
  (block
76
77
  (call
77
- !nil? {:select :select!})
78
+ !nil? {:select :select! :filter :filter!})
78
79
  $(args ...)
79
80
  (call
80
81
  (call
@@ -87,11 +88,10 @@ module RuboCop
87
88
  PATTERN
88
89
 
89
90
  def on_send(node)
91
+ return if target_ruby_version < 2.6 && FILTER_METHODS.include?(node.method_name)
90
92
  return unless (range = offense_range(node))
91
93
  return if allowed_receiver?(node.receiver)
92
- if (target_ruby_version <= 3.0 || node.method?(:delete_if)) && to_enum_method?(node)
93
- return
94
- end
94
+ return if target_ruby_version <= 3.0 && to_enum_method?(node)
95
95
 
96
96
  good = good_method_name(node)
97
97
  message = format(MSG, good: good, bad: range.source)
@@ -127,7 +127,7 @@ module RuboCop
127
127
  end
128
128
 
129
129
  def good_method_name(node)
130
- if node.bang_method? || node.method?(:delete_if)
130
+ if node.bang_method?
131
131
  'compact!'
132
132
  else
133
133
  'compact'
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Checks for multiple `defined?` calls joined by `&&` that can be combined
7
+ # into a single `defined?`.
8
+ #
9
+ # When checking that a nested constant or chained method is defined, it is
10
+ # not necessary to check each ancestor or component of the chain.
11
+ #
12
+ # @example
13
+ # # bad
14
+ # defined?(Foo) && defined?(Foo::Bar) && defined?(Foo::Bar::Baz)
15
+ #
16
+ # # good
17
+ # defined?(Foo::Bar::Baz)
18
+ #
19
+ # # bad
20
+ # defined?(foo) && defined?(foo.bar) && defined?(foo.bar.baz)
21
+ #
22
+ # # good
23
+ # defined?(foo.bar.baz)
24
+ class CombinableDefined < Base
25
+ extend AutoCorrector
26
+ include RangeHelp
27
+
28
+ MSG = 'Combine nested `defined?` calls.'
29
+ OPERATORS = %w[&& and].freeze
30
+
31
+ def on_and(node)
32
+ # Only register an offense if all `&&` terms are `defined?` calls
33
+ return unless (terms = terms(node)).all?(&:defined_type?)
34
+
35
+ calls = defined_calls(terms)
36
+ namespaces = namespaces(calls)
37
+
38
+ calls.each do |call|
39
+ next unless namespaces.any?(call)
40
+
41
+ add_offense(node) do |corrector|
42
+ remove_term(corrector, call)
43
+ end
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def terms(node)
50
+ node.each_descendant.select do |descendant|
51
+ descendant.parent.and_type? && !descendant.and_type?
52
+ end
53
+ end
54
+
55
+ def defined_calls(nodes)
56
+ nodes.filter_map do |defined_node|
57
+ subject = defined_node.first_argument
58
+ subject if subject.const_type? || subject.call_type?
59
+ end
60
+ end
61
+
62
+ def namespaces(nodes)
63
+ nodes.filter_map do |node|
64
+ if node.respond_to?(:namespace)
65
+ node.namespace
66
+ elsif node.respond_to?(:receiver)
67
+ node.receiver
68
+ end
69
+ end
70
+ end
71
+
72
+ def remove_term(corrector, term)
73
+ term = term.parent until term.parent.and_type?
74
+ range = if term == term.parent.children.last
75
+ rhs_range_to_remove(term)
76
+ else
77
+ lhs_range_to_remove(term)
78
+ end
79
+
80
+ corrector.remove(range)
81
+ end
82
+
83
+ # If the redundant `defined?` node is the LHS of an `and` node,
84
+ # the term as well as the subsequent `&&`/`and` operator will be removed.
85
+ def lhs_range_to_remove(term)
86
+ source = @processed_source.buffer.source
87
+
88
+ pos = term.source_range.end_pos
89
+ pos += 1 until source[..pos].end_with?(*OPERATORS)
90
+
91
+ range_with_surrounding_space(
92
+ range: term.source_range.with(end_pos: pos + 1),
93
+ side: :right,
94
+ newlines: false
95
+ )
96
+ end
97
+
98
+ # If the redundant `defined?` node is the RHS of an `and` node,
99
+ # the term as well as the preceding `&&`/`and` operator will be removed.
100
+ def rhs_range_to_remove(term)
101
+ source = @processed_source.buffer.source
102
+
103
+ pos = term.source_range.begin_pos
104
+ pos -= 1 until source[pos, 3].start_with?(*OPERATORS)
105
+
106
+ range_with_surrounding_space(
107
+ range: term.source_range.with(begin_pos: pos - 1),
108
+ side: :right,
109
+ newlines: false
110
+ )
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -7,6 +7,9 @@ module RuboCop
7
7
  # can be combined into a single loop. It is very likely that combining them
8
8
  # will make the code more efficient and more concise.
9
9
  #
10
+ # NOTE: Autocorrection is not applied when the block variable names differ in separate loops,
11
+ # as it is impossible to determine which variable name should be prioritized.
12
+ #
10
13
  # @safety
11
14
  # The cop is unsafe, because the first loop might modify state that the
12
15
  # second loop depends on; these two aren't combinable.
@@ -61,6 +64,7 @@ module RuboCop
61
64
 
62
65
  MSG = 'Combine this loop with the previous loop.'
63
66
 
67
+ # rubocop:disable Metrics/CyclomaticComplexity
64
68
  def on_block(node)
65
69
  return unless node.parent&.begin_type?
66
70
  return unless collection_looping_method?(node)
@@ -68,9 +72,12 @@ module RuboCop
68
72
  return unless node.body && node.left_sibling.body
69
73
 
70
74
  add_offense(node) do |corrector|
75
+ next unless node.arguments == node.left_sibling.arguments
76
+
71
77
  combine_with_left_sibling(corrector, node)
72
78
  end
73
79
  end
80
+ # rubocop:enable Metrics/CyclomaticComplexity
74
81
 
75
82
  alias on_numblock on_block
76
83
 
@@ -10,7 +10,7 @@ module RuboCop
10
10
  #
11
11
  # Note that some comments
12
12
  # (`:nodoc:`, `:yields:`, `rubocop:disable` and `rubocop:todo`)
13
- # are allowed.
13
+ # and RBS::Inline annotation comments are allowed.
14
14
  #
15
15
  # Autocorrection removes comments from `end` keyword and keeps comments
16
16
  # for `class`, `module`, `def` and `begin` above the keyword.
@@ -82,6 +82,8 @@ module RuboCop
82
82
 
83
83
  def offensive?(comment)
84
84
  line = source_line(comment)
85
+ return false if rbs_inline_annotation?(line, comment)
86
+
85
87
  KEYWORD_REGEXES.any? { |r| r.match?(line) } &&
86
88
  ALLOWED_COMMENT_REGEXES.none? { |r| r.match?(line) }
87
89
  end
@@ -89,6 +91,10 @@ module RuboCop
89
91
  def source_line(comment)
90
92
  comment.source_range.source_line
91
93
  end
94
+
95
+ def rbs_inline_annotation?(line, comment)
96
+ comment.text.start_with?('#:') && line.start_with?(/\A\s*def\s/)
97
+ end
92
98
  end
93
99
  end
94
100
  end
@@ -73,7 +73,7 @@ module RuboCop
73
73
  elsif_branches << node.if_branch
74
74
 
75
75
  else_branch = node.else_branch
76
- if else_branch&.if_type? && else_branch&.elsif?
76
+ if else_branch&.if_type? && else_branch.elsif?
77
77
  expand_elsif(else_branch, elsif_branches)
78
78
  else
79
79
  elsif_branches << else_branch
@@ -36,7 +36,7 @@ module RuboCop
36
36
  def on_class(node)
37
37
  return unless data_define?(node.parent_class)
38
38
 
39
- add_offense(node.parent_class.source_range) do |corrector|
39
+ add_offense(node.parent_class) do |corrector|
40
40
  corrector.remove(range_with_surrounding_space(node.loc.keyword, newlines: false))
41
41
  corrector.replace(node.loc.operator, '=')
42
42
 
@@ -48,6 +48,7 @@ module RuboCop
48
48
  #
49
49
  class EndlessMethod < Base
50
50
  include ConfigurableEnforcedStyle
51
+ include EndlessMethodRewriter
51
52
  extend TargetRubyVersion
52
53
  extend AutoCorrector
53
54
 
@@ -81,20 +82,6 @@ module RuboCop
81
82
 
82
83
  add_offense(node) { |corrector| correct_to_multiline(corrector, node) }
83
84
  end
84
-
85
- def correct_to_multiline(corrector, node)
86
- replacement = <<~RUBY.strip
87
- def #{node.method_name}#{arguments(node)}
88
- #{node.body.source}
89
- end
90
- RUBY
91
-
92
- corrector.replace(node, replacement)
93
- end
94
-
95
- def arguments(node, missing = '')
96
- node.arguments.any? ? node.arguments.source : missing
97
- end
98
85
  end
99
86
  end
100
87
  end
@@ -136,7 +136,7 @@ module RuboCop
136
136
  actual: line_node.source,
137
137
  expected: expected)
138
138
 
139
- add_offense(line_node.source_range, message: message) do |corrector|
139
+ add_offense(line_node, message: message) do |corrector|
140
140
  corrector.replace(line_node, expected)
141
141
  end
142
142
  end