/
negated_if_else_condition.rb
135 lines (113 loc) · 3.77 KB
/
negated_if_else_condition.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
# frozen_string_literal: true
module RuboCop
module Cop
module Style
# Checks for uses of `if-else` and ternary operators with a negated condition
# which can be simplified by inverting condition and swapping branches.
#
# @example
# # bad
# if !x
# do_something
# else
# do_something_else
# end
#
# # good
# if x
# do_something_else
# else
# do_something
# end
#
# # bad
# !x ? do_something : do_something_else
#
# # good
# x ? do_something_else : do_something
#
class NegatedIfElseCondition < Base
include RangeHelp
extend AutoCorrector
MSG = 'Invert the negated condition and swap the %<type>s branches.'
NEGATED_EQUALITY_METHODS = %i[!= !~].freeze
# @!method double_negation?(node)
def_node_matcher :double_negation?, '(send (send _ :!) :!)'
def self.autocorrect_incompatible_with
[Style::InverseMethods, Style::Not]
end
def on_new_investigation
@corrected_nodes = nil
end
def on_if(node)
return unless if_else?(node)
return unless (condition = unwrap_begin_nodes(node.condition))
return if double_negation?(condition) || !negated_condition?(condition)
message = message(node)
add_offense(node, message: message) do |corrector|
unless corrected_ancestor?(node)
correct_negated_condition(corrector, condition)
swap_branches(corrector, node)
@corrected_nodes ||= Set.new.compare_by_identity
@corrected_nodes.add(node)
end
end
end
private
def if_else?(node)
else_branch = node.else_branch
!node.elsif? && else_branch && (!else_branch.if_type? || !else_branch.elsif?)
end
def unwrap_begin_nodes(node)
node = node.children.first while node && (node.begin_type? || node.kwbegin_type?)
node
end
def negated_condition?(node)
node.send_type? &&
(node.negation_method? || NEGATED_EQUALITY_METHODS.include?(node.method_name))
end
def message(node)
type = node.ternary? ? 'ternary' : 'if-else'
format(MSG, type: type)
end
def corrected_ancestor?(node)
node.each_ancestor(:if).any? { |ancestor| @corrected_nodes&.include?(ancestor) }
end
def correct_negated_condition(corrector, node)
receiver, method_name, rhs = *node
replacement =
if node.negation_method?
receiver.source
else
inverted_method = method_name.to_s.sub('!', '=')
"#{receiver.source} #{inverted_method} #{rhs.source}"
end
corrector.replace(node, replacement)
end
def swap_branches(corrector, node)
if node.if_branch.nil?
corrector.remove(range_by_whole_lines(node.loc.else, include_final_newline: true))
else
corrector.swap(if_range(node), else_range(node))
end
end
# Collect the entire if branch, including whitespace and comments
def if_range(node)
if node.ternary?
node.if_branch
else
range_between(node.condition.source_range.end_pos, node.loc.else.begin_pos)
end
end
# Collect the entire else branch, including whitespace and comments
def else_range(node)
if node.ternary?
node.else_branch
else
range_between(node.loc.else.end_pos, node.loc.end.begin_pos)
end
end
end
end
end
end