forked from rubocop/rubocop-performance
-
Notifications
You must be signed in to change notification settings - Fork 0
/
casecmp.rb
109 lines (94 loc) · 3.31 KB
/
casecmp.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
106
107
108
109
# frozen_string_literal: true
module RuboCop
module Cop
module Performance
# Identifies places where a case-insensitive string comparison
# can better be implemented using `casecmp`.
#
# This cop is disabled by default because `String#casecmp` only works with
# ASCII characters. See https://github.com/rubocop/rubocop/issues/9753.
#
# If you are working only with ASCII characters, then this cop can be
# safely enabled.
#
# @safety
# This cop is unsafe because `String#casecmp` and `String#casecmp?` behave
# differently when using Non-ASCII characters.
#
# @example
# # bad
# str.downcase == 'abc'
# str.upcase.eql? 'ABC'
# 'abc' == str.downcase
# 'ABC'.eql? str.upcase
# str.downcase == str.downcase
#
# # good
# str.casecmp('ABC').zero?
# 'abc'.casecmp(str).zero?
class Casecmp < Base
extend AutoCorrector
MSG = 'Use `%<good>s` instead of `%<bad>s`.'
RESTRICT_ON_SEND = %i[== eql? !=].freeze
CASE_METHODS = %i[downcase upcase].freeze
def_node_matcher :downcase_eq, <<~PATTERN
(send
$(send _ ${:downcase :upcase})
${:== :eql? :!=}
${str (send _ {:downcase :upcase} ...) (begin str)})
PATTERN
def_node_matcher :eq_downcase, <<~PATTERN
(send
{str (send _ {:downcase :upcase} ...) (begin str)}
${:== :eql? :!=}
$(send _ ${:downcase :upcase}))
PATTERN
def_node_matcher :downcase_downcase, <<~PATTERN
(send
$(send _ ${:downcase :upcase})
${:== :eql? :!=}
$(send _ ${:downcase :upcase}))
PATTERN
def on_send(node)
return unless downcase_eq(node) || eq_downcase(node)
return unless (parts = take_method_apart(node))
_receiver, method, arg, variable = parts
good_method = build_good_method(method, arg, variable)
message = format(MSG, good: good_method, bad: node.source)
add_offense(node, message: message) do |corrector|
autocorrect(corrector, node, good_method)
end
end
private
def take_method_apart(node)
if downcase_downcase(node)
receiver, method, rhs = *node
arg, = *rhs
elsif downcase_eq(node)
receiver, method, arg = *node
elsif eq_downcase(node)
arg, method, receiver = *node
else
return
end
variable, = *receiver
[receiver, method, arg, variable]
end
def autocorrect(corrector, node, replacement)
corrector.replace(node, replacement)
end
def build_good_method(method, arg, variable)
bang = method == :!= ? '!' : ''
# We want resulting call to be parenthesized
# if arg already includes one or more sets of parens, don't add more
# or if method call already used parens, again, don't add more
if arg.send_type? || !parentheses?(arg)
"#{bang}#{variable.source}.casecmp(#{arg.source}).zero?"
else
"#{bang}#{variable.source}.casecmp#{arg.source}.zero?"
end
end
end
end
end
end