forked from rubocop/rubocop
-
Notifications
You must be signed in to change notification settings - Fork 2
/
if_with_boolean_literal_branches.rb
158 lines (136 loc) · 4.75 KB
/
if_with_boolean_literal_branches.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
# frozen_string_literal: true
module RuboCop
module Cop
module Style
# Checks for redundant `if` with boolean literal branches.
# It checks only conditions to return boolean value (`true` or `false`) for safe detection.
# The conditions to be checked are comparison methods, predicate methods, and double negative.
# `nonzero?` method is allowed by default.
# These are customizable with `AllowedMethods` option.
# This cop targets only `if`s with a single `elsif` or `else` branch.
#
# [source,ruby]
# ----
# if foo
# true
# elsif bar > baz
# true
# elsif qux > quux # Single `elsif` is warned, but two or more `elsif`s are not.
# true
# else
# false
# end
# ----
#
# @safety
# Autocorrection is unsafe because there is no guarantee that all predicate methods
# will return a boolean value. Those methods can be allowed with `AllowedMethods` config.
#
# @example
# # bad
# if foo == bar
# true
# else
# false
# end
#
# # bad
# foo == bar ? true : false
#
# # good
# foo == bar
#
# @example
# # bad
# if foo.do_something?
# true
# else
# false
# end
#
# # good (but potentially an unsafe correction)
# foo.do_something?
#
# @example AllowedMethods: ['nonzero?'] (default)
# # good
# num.nonzero? ? true : false
#
class IfWithBooleanLiteralBranches < Base
include AllowedMethods
extend AutoCorrector
MSG = 'Remove redundant %<keyword>s with boolean literal branches.'
MSG_FOR_ELSIF = 'Use `else` instead of redundant `elsif` with boolean literal branches.'
# @!method if_with_boolean_literal_branches?(node)
def_node_matcher :if_with_boolean_literal_branches?, <<~PATTERN
(if #return_boolean_value? {(true) (false) | (false) (true)})
PATTERN
# @!method double_negative?(node)
def_node_matcher :double_negative?, '(send (send _ :!) :!)'
def on_if(node)
return if !if_with_boolean_literal_branches?(node) || multiple_elsif?(node)
condition = node.condition
range, keyword = offense_range_with_keyword(node, condition)
add_offense(range, message: message(node, keyword)) do |corrector|
replacement = replacement_condition(node, condition)
if node.elsif?
corrector.insert_before(node, "else\n")
corrector.replace(node, "#{indent(node.if_branch)}#{replacement}")
else
corrector.replace(node, replacement)
end
end
end
private
def multiple_elsif?(node)
return false unless (parent = node.parent)
parent.if_type? && parent.elsif?
end
def offense_range_with_keyword(node, condition)
if node.ternary?
range = condition.source_range.end.join(node.source_range.end)
[range, 'ternary operator']
else
keyword = node.loc.keyword
[keyword, "`#{keyword.source}`"]
end
end
def message(node, keyword)
message_template = node.elsif? ? MSG_FOR_ELSIF : MSG
format(message_template, keyword: keyword)
end
def return_boolean_value?(condition)
if condition.begin_type?
return_boolean_value?(condition.children.first)
elsif condition.or_type?
return_boolean_value?(condition.lhs) && return_boolean_value?(condition.rhs)
elsif condition.and_type?
return_boolean_value?(condition.rhs)
else
assume_boolean_value?(condition)
end
end
def assume_boolean_value?(condition)
return false unless condition.send_type?
return false if allowed_method?(condition.method_name)
condition.comparison_method? || condition.predicate_method? || double_negative?(condition)
end
def replacement_condition(node, condition)
bang = '!' if opposite_condition?(node)
if bang && require_parentheses?(condition)
"#{bang}(#{condition.source})"
else
"#{bang}#{condition.source}"
end
end
def opposite_condition?(node)
(!node.unless? && node.if_branch.false_type?) ||
(node.unless? && node.if_branch.true_type?)
end
def require_parentheses?(condition)
condition.and_type? || condition.or_type? ||
(condition.send_type? && condition.comparison_method?)
end
end
end
end
end