/
double_negation.rb
159 lines (138 loc) · 4.66 KB
/
double_negation.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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# frozen_string_literal: true
module RuboCop
module Cop
module Style
# Checks for uses of double negation (`!!`) to convert something to a boolean value.
#
# When using `EnforcedStyle: allowed_in_returns`, allow double negation in contexts
# that use boolean as a return value. When using `EnforcedStyle: forbidden`, double negation
# should be forbidden always.
#
# NOTE: when `something` is a boolean value
# `!!something` and `!something.nil?` are not the same thing.
# As you're unlikely to write code that can accept values of any type
# this is rarely a problem in practice.
#
# @safety
# Autocorrection is unsafe when the value is `false`, because the result
# of the expression will change.
#
# [source,ruby]
# ----
# !!false #=> false
# !false.nil? #=> true
# ----
#
# @example
# # bad
# !!something
#
# # good
# !something.nil?
#
# @example EnforcedStyle: allowed_in_returns (default)
# # good
# def foo?
# !!return_value
# end
#
# define_method :foo? do
# !!return_value
# end
#
# define_singleton_method :foo? do
# !!return_value
# end
#
# @example EnforcedStyle: forbidden
# # bad
# def foo?
# !!return_value
# end
#
# define_method :foo? do
# !!return_value
# end
#
# define_singleton_method :foo? do
# !!return_value
# end
class DoubleNegation < Base
include ConfigurableEnforcedStyle
extend AutoCorrector
MSG = 'Avoid the use of double negation (`!!`).'
RESTRICT_ON_SEND = %i[!].freeze
# @!method double_negative?(node)
def_node_matcher :double_negative?, '(send (send _ :!) :!)'
def on_send(node)
return unless double_negative?(node) && node.prefix_bang?
return if style == :allowed_in_returns && allowed_in_returns?(node)
location = node.loc.selector
add_offense(location) do |corrector|
corrector.remove(location)
corrector.insert_after(node, '.nil?')
end
end
private
def allowed_in_returns?(node)
node.parent&.return_type? || end_of_method_definition?(node)
end
def end_of_method_definition?(node)
return false unless (def_node = find_def_node_from_ascendant(node))
conditional_node = find_conditional_node_from_ascendant(node)
last_child = find_last_child(def_node.send_type? ? def_node : def_node.body)
if conditional_node
double_negative_condition_return_value?(node, last_child, conditional_node)
elsif last_child.pair_type? || last_child.hash_type? || last_child.parent.array_type?
false
else
last_child.last_line <= node.last_line
end
end
def find_def_node_from_ascendant(node)
return unless (parent = node.parent)
return parent if parent.def_type? || parent.defs_type?
return node.parent.child_nodes.first if define_mehod?(parent)
find_def_node_from_ascendant(node.parent)
end
def define_mehod?(node)
return false unless node.block_type?
child = node.child_nodes.first
return false unless child.send_type?
child.method?(:define_method) || child.method?(:define_singleton_method)
end
def find_conditional_node_from_ascendant(node)
return unless (parent = node.parent)
return parent if parent.conditional?
find_conditional_node_from_ascendant(parent)
end
def find_last_child(node)
case node.type
when :rescue
find_last_child(node.body)
when :ensure
find_last_child(node.child_nodes.first)
else
node.child_nodes.last
end
end
def double_negative_condition_return_value?(node, last_child, conditional_node)
parent = find_parent_not_enumerable(node)
if parent.begin_type?
node.loc.line == parent.loc.last_line
else
last_child.last_line <= conditional_node.last_line
end
end
def find_parent_not_enumerable(node)
return unless (parent = node.parent)
if parent.pair_type? || parent.hash_type? || parent.array_type?
find_parent_not_enumerable(parent)
else
parent
end
end
end
end
end
end