forked from rubocop/rubocop-performance
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathredundant_equality_comparison_block.rb
105 lines (87 loc) · 3.64 KB
/
redundant_equality_comparison_block.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# frozen_string_literal: true
module RuboCop
module Cop
module Performance
# Checks for uses `Enumerable#all?`, `Enumerable#any?`, `Enumerable#one?`,
# and `Enumerable#none?` are compared with `===` or similar methods in block.
#
# By default, `Object#===` behaves the same as `Object#==`, but this
# behavior is appropriately overridden in subclass. For example,
# `Range#===` returns `true` when argument is within the range.
#
# @safety
# This cop is unsafe because `===` and `==` do not always behave the same.
#
# @example
# # bad
# items.all? { |item| pattern === item }
# items.all? { |item| item == other }
# items.all? { |item| item.is_a?(Klass) }
# items.all? { |item| item.kind_of?(Klass) }
#
# # good
# items.all?(pattern)
#
class RedundantEqualityComparisonBlock < Base
extend AutoCorrector
extend TargetRubyVersion
minimum_target_ruby_version 2.5
MSG = 'Use `%<prefer>s` instead of block.'
TARGET_METHODS = %i[all? any? one? none?].freeze
COMPARISON_METHODS = %i[== === is_a? kind_of?].freeze
IS_A_METHODS = %i[is_a? kind_of?].freeze
def on_block(node)
return unless TARGET_METHODS.include?(node.method_name)
return unless one_block_argument?(node.arguments)
block_argument = node.arguments.first
block_body = node.body
return unless use_equality_comparison_block?(block_body)
return if same_block_argument_and_is_a_argument?(block_body, block_argument)
return unless (new_argument = new_argument(block_argument, block_body))
range = offense_range(node)
prefer = "#{node.method_name}(#{new_argument})"
add_offense(range, message: format(MSG, prefer: prefer)) do |corrector|
corrector.replace(range, prefer)
end
end
private
def one_block_argument?(block_arguments)
block_arguments.one? && !block_arguments.source.include?(',')
end
def use_equality_comparison_block?(block_body)
block_body.send_type? && COMPARISON_METHODS.include?(block_body.method_name)
end
def same_block_argument_and_is_a_argument?(block_body, block_argument)
if block_body.method?(:===)
block_argument.source != block_body.children[2].source
elsif IS_A_METHODS.include?(block_body.method_name)
block_argument.source == block_body.first_argument.source
else
false
end
end
def new_argument(block_argument, block_body)
if block_argument.source == block_body.receiver.source
rhs = block_body.first_argument
return if use_block_argument_in_method_argument_of_operand?(block_argument, rhs)
rhs.source
elsif block_argument.source == block_body.first_argument.source
lhs = block_body.receiver
return if use_block_argument_in_method_argument_of_operand?(block_argument, lhs)
lhs.source
end
end
def use_block_argument_in_method_argument_of_operand?(block_argument, operand)
return false unless operand.send_type?
arguments = operand.arguments
arguments.inject(arguments.map(&:source)) do |operand_sources, argument|
operand_sources + argument.each_descendant(:lvar).map(&:source)
end.any?(block_argument.source)
end
def offense_range(node)
node.send_node.loc.selector.join(node.source_range.end)
end
end
end
end
end