rubocop 1.5.1 → 1.8.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 (115) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +2 -2
  4. data/config/default.yml +111 -14
  5. data/config/obsoletion.yml +196 -0
  6. data/lib/rubocop.rb +20 -1
  7. data/lib/rubocop/cli/command/suggest_extensions.rb +19 -19
  8. data/lib/rubocop/comment_config.rb +6 -6
  9. data/lib/rubocop/config.rb +8 -5
  10. data/lib/rubocop/config_loader.rb +10 -6
  11. data/lib/rubocop/config_loader_resolver.rb +21 -4
  12. data/lib/rubocop/config_obsoletion.rb +64 -262
  13. data/lib/rubocop/config_obsoletion/changed_enforced_styles.rb +33 -0
  14. data/lib/rubocop/config_obsoletion/changed_parameter.rb +21 -0
  15. data/lib/rubocop/config_obsoletion/cop_rule.rb +34 -0
  16. data/lib/rubocop/config_obsoletion/extracted_cop.rb +44 -0
  17. data/lib/rubocop/config_obsoletion/parameter_rule.rb +44 -0
  18. data/lib/rubocop/config_obsoletion/removed_cop.rb +41 -0
  19. data/lib/rubocop/config_obsoletion/renamed_cop.rb +34 -0
  20. data/lib/rubocop/config_obsoletion/rule.rb +41 -0
  21. data/lib/rubocop/config_obsoletion/split_cop.rb +27 -0
  22. data/lib/rubocop/config_validator.rb +11 -4
  23. data/lib/rubocop/cop/base.rb +17 -15
  24. data/lib/rubocop/cop/cop.rb +2 -2
  25. data/lib/rubocop/cop/correctors/string_literal_corrector.rb +6 -8
  26. data/lib/rubocop/cop/gemspec/required_ruby_version.rb +3 -2
  27. data/lib/rubocop/cop/internal_affairs.rb +1 -0
  28. data/lib/rubocop/cop/internal_affairs/style_detected_api_use.rb +145 -0
  29. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +19 -3
  30. data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +1 -1
  31. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +26 -0
  32. data/lib/rubocop/cop/layout/line_length.rb +6 -16
  33. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +7 -3
  34. data/lib/rubocop/cop/layout/multiline_operation_indentation.rb +2 -2
  35. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +3 -10
  36. data/lib/rubocop/cop/layout/space_around_equals_in_parameter_default.rb +1 -0
  37. data/lib/rubocop/cop/layout/space_before_block_braces.rb +2 -0
  38. data/lib/rubocop/cop/layout/space_before_brackets.rb +62 -0
  39. data/lib/rubocop/cop/layout/space_inside_block_braces.rb +13 -10
  40. data/lib/rubocop/cop/layout/space_inside_hash_literal_braces.rb +2 -2
  41. data/lib/rubocop/cop/lint/ambiguous_assignment.rb +59 -0
  42. data/lib/rubocop/cop/lint/binary_operator_with_identical_operands.rb +7 -2
  43. data/lib/rubocop/cop/lint/deprecated_constants.rb +75 -0
  44. data/lib/rubocop/cop/lint/duplicate_branch.rb +64 -2
  45. data/lib/rubocop/cop/lint/lambda_without_literal_block.rb +44 -0
  46. data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +10 -6
  47. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +2 -1
  48. data/lib/rubocop/cop/lint/redundant_dir_glob_sort.rb +48 -0
  49. data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +50 -17
  50. data/lib/rubocop/cop/lint/shadowed_exception.rb +1 -11
  51. data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +13 -0
  52. data/lib/rubocop/cop/lint/unreachable_loop.rb +17 -0
  53. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +1 -1
  54. data/lib/rubocop/cop/migration/department_name.rb +1 -1
  55. data/lib/rubocop/cop/mixin/allowed_identifiers.rb +18 -0
  56. data/lib/rubocop/cop/mixin/comments_help.rb +1 -10
  57. data/lib/rubocop/cop/mixin/first_element_line_break.rb +1 -1
  58. data/lib/rubocop/cop/mixin/string_help.rb +4 -1
  59. data/lib/rubocop/cop/naming/accessor_method_name.rb +15 -1
  60. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +59 -5
  61. data/lib/rubocop/cop/naming/variable_name.rb +2 -0
  62. data/lib/rubocop/cop/naming/variable_number.rb +1 -8
  63. data/lib/rubocop/cop/registry.rb +10 -0
  64. data/lib/rubocop/cop/style/access_modifier_declarations.rb +3 -1
  65. data/lib/rubocop/cop/style/character_literal.rb +10 -11
  66. data/lib/rubocop/cop/style/collection_methods.rb +14 -1
  67. data/lib/rubocop/cop/style/commented_keyword.rb +22 -5
  68. data/lib/rubocop/cop/style/empty_literal.rb +6 -2
  69. data/lib/rubocop/cop/style/endless_method.rb +102 -0
  70. data/lib/rubocop/cop/style/float_division.rb +44 -1
  71. data/lib/rubocop/cop/style/for.rb +2 -0
  72. data/lib/rubocop/cop/style/hash_except.rb +95 -0
  73. data/lib/rubocop/cop/style/hash_like_case.rb +2 -1
  74. data/lib/rubocop/cop/style/if_inside_else.rb +8 -3
  75. data/lib/rubocop/cop/style/if_unless_modifier.rb +4 -0
  76. data/lib/rubocop/cop/style/ip_addresses.rb +1 -1
  77. data/lib/rubocop/cop/style/keyword_parameters_order.rb +12 -2
  78. data/lib/rubocop/cop/style/lambda_call.rb +2 -1
  79. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +7 -0
  80. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +16 -6
  81. data/lib/rubocop/cop/style/method_def_parentheses.rb +7 -0
  82. data/lib/rubocop/cop/style/multiline_method_signature.rb +26 -1
  83. data/lib/rubocop/cop/style/multiline_when_then.rb +3 -1
  84. data/lib/rubocop/cop/style/mutable_constant.rb +13 -3
  85. data/lib/rubocop/cop/style/nested_parenthesized_calls.rb +4 -0
  86. data/lib/rubocop/cop/style/perl_backrefs.rb +86 -9
  87. data/lib/rubocop/cop/style/raise_args.rb +5 -2
  88. data/lib/rubocop/cop/style/redundant_argument.rb +21 -2
  89. data/lib/rubocop/cop/style/redundant_freeze.rb +8 -4
  90. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +24 -8
  91. data/lib/rubocop/cop/style/redundant_return.rb +1 -1
  92. data/lib/rubocop/cop/style/single_line_block_params.rb +30 -7
  93. data/lib/rubocop/cop/style/single_line_methods.rb +33 -2
  94. data/lib/rubocop/cop/style/sole_nested_conditional.rb +25 -9
  95. data/lib/rubocop/cop/style/special_global_vars.rb +1 -13
  96. data/lib/rubocop/cop/style/string_concatenation.rb +26 -1
  97. data/lib/rubocop/cop/style/string_literals.rb +14 -8
  98. data/lib/rubocop/cop/style/string_literals_in_interpolation.rb +4 -3
  99. data/lib/rubocop/cop/style/symbol_proc.rb +5 -4
  100. data/lib/rubocop/cop/util.rb +3 -1
  101. data/lib/rubocop/ext/regexp_node.rb +31 -9
  102. data/lib/rubocop/ext/regexp_parser.rb +21 -3
  103. data/lib/rubocop/formatter/emacs_style_formatter.rb +2 -0
  104. data/lib/rubocop/formatter/simple_text_formatter.rb +2 -0
  105. data/lib/rubocop/formatter/tap_formatter.rb +2 -0
  106. data/lib/rubocop/lockfile.rb +40 -0
  107. data/lib/rubocop/options.rb +9 -9
  108. data/lib/rubocop/rspec/cop_helper.rb +0 -4
  109. data/lib/rubocop/rspec/expect_offense.rb +34 -22
  110. data/lib/rubocop/runner.rb +16 -1
  111. data/lib/rubocop/target_finder.rb +4 -2
  112. data/lib/rubocop/target_ruby.rb +47 -11
  113. data/lib/rubocop/util.rb +16 -0
  114. data/lib/rubocop/version.rb +8 -2
  115. metadata +42 -9
@@ -20,6 +20,7 @@ module RuboCop
20
20
  # # good
21
21
  # fooBar = 1
22
22
  class VariableName < Base
23
+ include AllowedIdentifiers
23
24
  include ConfigurableNaming
24
25
 
25
26
  MSG = 'Use %<style>s for variable names.'
@@ -27,6 +28,7 @@ module RuboCop
27
28
  def on_lvasgn(node)
28
29
  name, = *node
29
30
  return unless name
31
+ return if allowed_identifier?(name)
30
32
 
31
33
  check_name(node, name, node.loc.name)
32
34
  end
@@ -97,6 +97,7 @@ module RuboCop
97
97
  # expect(Open3).to receive(:capture3)
98
98
  #
99
99
  class VariableNumber < Base
100
+ include AllowedIdentifiers
100
101
  include ConfigurableNumbering
101
102
 
102
103
  MSG = 'Use %<style>s for %<identifier_type>s numbers.'
@@ -139,14 +140,6 @@ module RuboCop
139
140
 
140
141
  format(MSG, style: style, identifier_type: identifier_type)
141
142
  end
142
-
143
- def allowed_identifier?(name)
144
- allowed_identifiers.include?(name.to_s.delete('@'))
145
- end
146
-
147
- def allowed_identifiers
148
- cop_config.fetch('AllowedIdentifiers', [])
149
- end
150
143
  end
151
144
  end
152
145
  end
@@ -204,6 +204,12 @@ module RuboCop
204
204
  to_h[cop_name].first
205
205
  end
206
206
 
207
+ def freeze
208
+ clear_enrollment_queue
209
+ unqualified_cop_names # build cache
210
+ super
211
+ end
212
+
207
213
  @global = new
208
214
 
209
215
  class << self
@@ -228,6 +234,10 @@ module RuboCop
228
234
  @global = previous
229
235
  end
230
236
 
237
+ def self.reset!
238
+ @global = new
239
+ end
240
+
231
241
  private
232
242
 
233
243
  def initialize_copy(reg)
@@ -87,7 +87,9 @@ module RuboCop
87
87
  return if allow_modifiers_on_symbols?(node)
88
88
 
89
89
  if offense?(node)
90
- add_offense(node.loc.selector) if opposite_style_detected
90
+ add_offense(node.loc.selector) do
91
+ opposite_style_detected
92
+ end
91
93
  else
92
94
  correct_style_detected
93
95
  end
@@ -14,8 +14,9 @@ module RuboCop
14
14
  #
15
15
  # # good
16
16
  # ?\C-\M-d
17
- class CharacterLiteral < Cop
17
+ class CharacterLiteral < Base
18
18
  include StringHelp
19
+ extend AutoCorrector
19
20
 
20
21
  MSG = 'Do not use the character literal - ' \
21
22
  'use string literal instead.'
@@ -26,17 +27,15 @@ module RuboCop
26
27
  node.source.size.between?(2, 3)
27
28
  end
28
29
 
29
- def autocorrect(node)
30
- lambda do |corrector|
31
- string = node.source[1..-1]
30
+ def autocorrect(corrector, node)
31
+ string = node.source[1..-1]
32
32
 
33
- # special character like \n
34
- # or ' which needs to use "" or be escaped.
35
- if string.length == 2 || string == "'"
36
- corrector.replace(node, %("#{string}"))
37
- elsif string.length == 1 # normal character
38
- corrector.replace(node, "'#{string}'")
39
- end
33
+ # special character like \n
34
+ # or ' which needs to use "" or be escaped.
35
+ if string.length == 2 || string == "'"
36
+ corrector.replace(node, %("#{string}"))
37
+ elsif string.length == 1 # normal character
38
+ corrector.replace(node, "'#{string}'")
40
39
  end
41
40
  end
42
41
 
@@ -48,7 +48,7 @@ module RuboCop
48
48
  end
49
49
 
50
50
  def on_send(node)
51
- return unless node.arguments.one? && node.first_argument.block_pass_type?
51
+ return unless implicit_block?(node)
52
52
 
53
53
  check_method_node(node)
54
54
  end
@@ -64,9 +64,22 @@ module RuboCop
64
64
  end
65
65
  end
66
66
 
67
+ def implicit_block?(node)
68
+ return false unless node.arguments.any?
69
+
70
+ node.last_argument.block_pass_type? ||
71
+ node.last_argument.sym_type? && methods_accepting_symbol.include?(node.method_name.to_s)
72
+ end
73
+
67
74
  def message(node)
68
75
  format(MSG, prefer: preferred_method(node.method_name), current: node.method_name)
69
76
  end
77
+
78
+ # Some enumerable methods accept a bare symbol (ie. _not_ Symbol#to_proc) instead
79
+ # of a block.
80
+ def methods_accepting_symbol
81
+ Array(cop_config['MethodsAcceptingSymbol'])
82
+ end
70
83
  end
71
84
  end
72
85
  end
@@ -4,12 +4,15 @@ module RuboCop
4
4
  module Cop
5
5
  module Style
6
6
  # This cop checks for comments put on the same line as some keywords.
7
- # These keywords are: `begin`, `class`, `def`, `end`, `module`.
7
+ # These keywords are: `class`, `module`, `def`, `begin`, `end`.
8
8
  #
9
9
  # Note that some comments
10
10
  # (`:nodoc:`, `:yields:`, `rubocop:disable` and `rubocop:todo`)
11
11
  # are allowed.
12
12
  #
13
+ # Auto-correction removes comments from `end` keyword and keeps comments
14
+ # for `class`, `module`, `def` and `begin` above the keyword.
15
+ #
13
16
  # @example
14
17
  # # bad
15
18
  # if condition
@@ -34,16 +37,17 @@ module RuboCop
34
37
  # y
35
38
  # end
36
39
  class CommentedKeyword < Base
40
+ include RangeHelp
41
+ extend AutoCorrector
42
+
37
43
  MSG = 'Do not place comments on the same line as the ' \
38
44
  '`%<keyword>s` keyword.'
39
45
 
40
46
  def on_new_investigation
41
47
  processed_source.comments.each do |comment|
42
- next unless (match = line(comment).match(/(?<keyword>\S+).*#/))
48
+ next unless (match = line(comment).match(/(?<keyword>\S+).*#/)) && offensive?(comment)
43
49
 
44
- if offensive?(comment)
45
- add_offense(comment, message: format(MSG, keyword: match[:keyword]))
46
- end
50
+ register_offense(comment, match[:keyword])
47
51
  end
48
52
  end
49
53
 
@@ -60,6 +64,19 @@ module RuboCop
60
64
  ].freeze
61
65
  ALLOWED_COMMENT_REGEXES = ALLOWED_COMMENTS.map { |c| /#\s*#{c}/ }.freeze
62
66
 
67
+ def register_offense(comment, matched_keyword)
68
+ add_offense(comment, message: format(MSG, keyword: matched_keyword)) do |corrector|
69
+ range = range_with_surrounding_space(range: comment.loc.expression, newlines: false)
70
+ corrector.remove(range)
71
+
72
+ unless matched_keyword == 'end'
73
+ corrector.insert_before(
74
+ range.source_buffer.line_range(comment.loc.line), "#{comment.text}\n"
75
+ )
76
+ end
77
+ end
78
+ end
79
+
63
80
  def offensive?(comment)
64
81
  line = line(comment)
65
82
  KEYWORD_REGEXES.any? { |r| r.match?(line) } &&
@@ -32,8 +32,12 @@ module RuboCop
32
32
  def_node_matcher :str_node, '(send (const {nil? cbase} :String) :new)'
33
33
  def_node_matcher :array_with_block,
34
34
  '(block (send (const {nil? cbase} :Array) :new) args _)'
35
- def_node_matcher :hash_with_block,
36
- '(block (send (const {nil? cbase} :Hash) :new) args _)'
35
+ def_node_matcher :hash_with_block, <<~PATTERN
36
+ {
37
+ (block (send (const {nil? cbase} :Hash) :new) args _)
38
+ (numblock (send (const {nil? cbase} :Hash) :new) ...)
39
+ }
40
+ PATTERN
37
41
 
38
42
  def on_send(node)
39
43
  return unless (message = offense_message(node))
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # This cop checks for endless methods.
7
+ #
8
+ # It can enforce either the use of endless methods definitions
9
+ # for single-lined method bodies, or disallow endless methods.
10
+ #
11
+ # Other method definition types are not considered by this cop.
12
+ #
13
+ # The supported styles are:
14
+ # * allow_single_line (default) - only single line endless method definitions are allowed.
15
+ # * allow_always - all endless method definitions are allowed.
16
+ # * disallow - all endless method definitions are disallowed.
17
+ #
18
+ # NOTE: Incorrect endless method definitions will always be
19
+ # corrected to a multi-line definition.
20
+ #
21
+ # @example EnforcedStyle: allow_single_line (default)
22
+ # # good
23
+ # def my_method() = x
24
+ #
25
+ # # bad, multi-line endless method
26
+ # def my_method() = x.foo
27
+ # .bar
28
+ # .baz
29
+ #
30
+ # @example EnforcedStyle: allow_always
31
+ # # good
32
+ # def my_method() = x
33
+ #
34
+ # # good
35
+ # def my_method() = x.foo
36
+ # .bar
37
+ # .baz
38
+ #
39
+ # @example EnforcedStyle: disallow
40
+ # # bad
41
+ # def my_method; x end
42
+ #
43
+ # # bad
44
+ # def my_method() = x.foo
45
+ # .bar
46
+ # .baz
47
+ #
48
+ class EndlessMethod < Base
49
+ include ConfigurableEnforcedStyle
50
+ extend TargetRubyVersion
51
+ extend AutoCorrector
52
+
53
+ minimum_target_ruby_version 3.0
54
+
55
+ CORRECTION_STYLES = %w[multiline single_line].freeze
56
+ MSG = 'Avoid endless method definitions.'
57
+ MSG_MULTI_LINE = 'Avoid endless method definitions with multiple lines.'
58
+
59
+ def on_def(node)
60
+ if style == :disallow
61
+ handle_disallow_style(node)
62
+ else
63
+ handle_allow_style(node)
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ def handle_allow_style(node)
70
+ return unless node.endless?
71
+ return if node.single_line? || style == :allow_always
72
+
73
+ add_offense(node, message: MSG_MULTI_LINE) do |corrector|
74
+ correct_to_multiline(corrector, node)
75
+ end
76
+ end
77
+
78
+ def handle_disallow_style(node)
79
+ return unless node.endless?
80
+
81
+ add_offense(node) do |corrector|
82
+ correct_to_multiline(corrector, node)
83
+ end
84
+ end
85
+
86
+ def correct_to_multiline(corrector, node)
87
+ replacement = <<~RUBY.strip
88
+ def #{node.method_name}#{arguments(node)}
89
+ #{node.body.source}
90
+ end
91
+ RUBY
92
+
93
+ corrector.replace(node, replacement)
94
+ end
95
+
96
+ def arguments(node, missing = '')
97
+ node.arguments.any? ? node.arguments.source : missing
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -41,6 +41,8 @@ module RuboCop
41
41
  # a.fdiv(b)
42
42
  class FloatDivision < Base
43
43
  include ConfigurableEnforcedStyle
44
+ extend AutoCorrector
45
+
44
46
  MESSAGES = {
45
47
  left_coerce: 'Prefer using `.to_f` on the left side.',
46
48
  right_coerce: 'Prefer using `.to_f` on the right side.',
@@ -64,7 +66,20 @@ module RuboCop
64
66
  PATTERN
65
67
 
66
68
  def on_send(node)
67
- add_offense(node) if offense_condition?(node)
69
+ return unless offense_condition?(node)
70
+
71
+ add_offense(node) do |corrector|
72
+ case style
73
+ when :left_coerce, :single_coerce
74
+ add_to_f_method(corrector, node.receiver)
75
+ remove_to_f_method(corrector, node.first_argument)
76
+ when :right_coerce
77
+ remove_to_f_method(corrector, node.receiver)
78
+ add_to_f_method(corrector, node.first_argument)
79
+ when :fdiv
80
+ correct_from_slash_to_fdiv(corrector, node, node.receiver, node.first_argument)
81
+ end
82
+ end
68
83
  end
69
84
 
70
85
  private
@@ -87,6 +102,34 @@ module RuboCop
87
102
  def message(_node)
88
103
  MESSAGES[style]
89
104
  end
105
+
106
+ def add_to_f_method(corrector, node)
107
+ corrector.insert_after(node, '.to_f') unless node.send_type? && node.method?(:to_f)
108
+ end
109
+
110
+ def remove_to_f_method(corrector, send_node)
111
+ corrector.remove(send_node.loc.dot)
112
+ corrector.remove(send_node.loc.selector)
113
+ end
114
+
115
+ def correct_from_slash_to_fdiv(corrector, node, receiver, argument)
116
+ receiver_source = extract_receiver_source(receiver)
117
+ argument_source = extract_receiver_source(argument)
118
+
119
+ if argument.respond_to?(:parenthesized?) && !argument.parenthesized?
120
+ argument_source = "(#{argument_source})"
121
+ end
122
+
123
+ corrector.replace(node, "#{receiver_source}.fdiv#{argument_source}")
124
+ end
125
+
126
+ def extract_receiver_source(node)
127
+ if node.send_type? && node.method?(:to_f)
128
+ node.receiver.source
129
+ else
130
+ node.source
131
+ end
132
+ end
90
133
  end
91
134
  end
92
135
  end
@@ -51,6 +51,7 @@ module RuboCop
51
51
  if style == :each
52
52
  add_offense(node, message: PREFER_EACH) do |corrector|
53
53
  ForToEachCorrector.new(node).call(corrector)
54
+ opposite_style_detected
54
55
  end
55
56
  else
56
57
  correct_style_detected
@@ -63,6 +64,7 @@ module RuboCop
63
64
  if style == :for
64
65
  add_offense(node, message: PREFER_FOR) do |corrector|
65
66
  EachToForCorrector.new(node).call(corrector)
67
+ opposite_style_detected
66
68
  end
67
69
  else
68
70
  correct_style_detected
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # This cop checks for usages of `Hash#reject`, `Hash#select`, and `Hash#filter` methods
7
+ # that can be replaced with `Hash#except` method.
8
+ #
9
+ # This cop should only be enabled on Ruby version 3.0 or higher.
10
+ # (`Hash#except` was added in Ruby 3.0.)
11
+ #
12
+ # For safe detection, it is limited to commonly used string and symbol comparisons
13
+ # when used `==`.
14
+ # And do not check `Hash#delete_if` and `Hash#keep_if` to change receiver object.
15
+ #
16
+ # @example
17
+ #
18
+ # # bad
19
+ # {foo: 1, bar: 2, baz: 3}.reject {|k, v| k == :bar }
20
+ # {foo: 1, bar: 2, baz: 3}.select {|k, v| k != :bar }
21
+ # {foo: 1, bar: 2, baz: 3}.filter {|k, v| k != :bar }
22
+ #
23
+ # # good
24
+ # {foo: 1, bar: 2, baz: 3}.except(:bar)
25
+ #
26
+ class HashExcept < Base
27
+ include RangeHelp
28
+ extend TargetRubyVersion
29
+ extend AutoCorrector
30
+
31
+ minimum_target_ruby_version 3.0
32
+
33
+ MSG = 'Use `%<prefer>s` instead.'
34
+ RESTRICT_ON_SEND = %i[reject select filter].freeze
35
+
36
+ def_node_matcher :bad_method?, <<~PATTERN
37
+ (block
38
+ (send _ _)
39
+ (args
40
+ (arg _)
41
+ (arg _))
42
+ (send
43
+ _ {:== :!= :eql?} _))
44
+ PATTERN
45
+
46
+ def on_send(node)
47
+ block = node.parent
48
+ return unless bad_method?(block) && semantically_except_method?(node, block)
49
+
50
+ except_key = except_key(block)
51
+ return unless safe_to_register_offense?(block, except_key)
52
+
53
+ range = offense_range(node)
54
+ preferred_method = "except(#{except_key.source})"
55
+
56
+ add_offense(range, message: format(MSG, prefer: preferred_method)) do |corrector|
57
+ corrector.replace(range, preferred_method)
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ def semantically_except_method?(send, block)
64
+ body = block.body
65
+
66
+ case send.method_name
67
+ when :reject
68
+ body.method?('==') || body.method?('eql?')
69
+ when :select, :filter
70
+ body.method?('!=')
71
+ else
72
+ false
73
+ end
74
+ end
75
+
76
+ def safe_to_register_offense?(block, except_key)
77
+ return true if block.body.method?('eql?')
78
+
79
+ except_key.sym_type? || except_key.str_type?
80
+ end
81
+
82
+ def except_key(node)
83
+ key_argument = node.argument_list.first
84
+ lhs, _method_name, rhs = *node.body
85
+
86
+ [lhs, rhs].find { |operand| operand.source != key_argument.source }
87
+ end
88
+
89
+ def offense_range(node)
90
+ range_between(node.loc.selector.begin_pos, node.parent.loc.end.end_pos)
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end