/
empty_conditional_body.rb
177 lines (153 loc) · 5.17 KB
/
empty_conditional_body.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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# frozen_string_literal: true
module RuboCop
module Cop
module Lint
# Checks for the presence of `if`, `elsif` and `unless` branches without a body.
#
# NOTE: empty `else` branches are handled by `Style/EmptyElse`.
#
# @safety
# Autocorrection for this cop is not safe. The conditions for empty branches that
# the autocorrection removes may have side effects, or the logic in subsequent
# branches may change due to the removal of a previous condition.
#
# @example
# # bad
# if condition
# end
#
# # bad
# unless condition
# end
#
# # bad
# if condition
# do_something
# elsif other_condition
# end
#
# # good
# if condition
# do_something
# end
#
# # good
# unless condition
# do_something
# end
#
# # good
# if condition
# do_something
# elsif other_condition
# do_something_else
# end
#
# @example AllowComments: true (default)
# # good
# if condition
# do_something
# elsif other_condition
# # noop
# end
#
# @example AllowComments: false
# # bad
# if condition
# do_something
# elsif other_condition
# # noop
# end
#
class EmptyConditionalBody < Base
extend AutoCorrector
include CommentsHelp
include RangeHelp
MSG = 'Avoid `%<keyword>s` branches without a body.'
def on_if(node)
return if node.body || same_line?(node.loc.begin, node.loc.end)
return if cop_config['AllowComments'] && contains_comments?(node)
add_offense(node, message: format(MSG, keyword: node.keyword)) do |corrector|
next if node.parent&.call_type?
autocorrect(corrector, node)
end
end
private
def autocorrect(corrector, node)
remove_comments(corrector, node)
remove_empty_branch(corrector, node)
correct_other_branches(corrector, node)
end
def remove_comments(corrector, node)
comments_in_range(node).each do |comment|
range = range_by_whole_lines(comment.source_range, include_final_newline: true)
corrector.remove(range)
end
end
def remove_empty_branch(corrector, node)
if empty_if_branch?(node) && else_branch?(node)
corrector.remove(branch_range(node))
else
corrector.remove(deletion_range(branch_range(node)))
end
end
def correct_other_branches(corrector, node)
return unless require_other_branches_correction?(node)
if node.else_branch&.if_type? && !node.else_branch.modifier_form?
# Replace an orphaned `elsif` with `if`
corrector.replace(node.else_branch.loc.keyword, 'if')
else
# Flip orphaned `else`
corrector.replace(node.loc.else, "#{node.inverse_keyword} #{node.condition.source}")
end
end
def require_other_branches_correction?(node)
return false unless node.if_type? && node.else?
return false if !empty_if_branch?(node) && node.elsif?
!empty_elsif_branch?(node)
end
def empty_if_branch?(node)
return false unless (parent = node.parent)
return true unless parent.if_type?
return true unless (if_branch = parent.if_branch)
if_branch.if_type? && !if_branch.body
end
def empty_elsif_branch?(node)
return false unless (else_branch = node.else_branch)
else_branch.if_type? && !else_branch.body
end
def else_branch?(node)
node.else_branch && !node.else_branch.if_type?
end
# rubocop:disable Metrics/AbcSize
def branch_range(node)
if empty_if_branch?(node) && else_branch?(node)
node.source_range.with(end_pos: node.loc.else.begin_pos)
elsif node.loc.else
node.source_range.with(end_pos: node.condition.source_range.end_pos)
elsif all_branches_body_missing?(node)
if_node = node.ancestors.detect(&:if?)
node.source_range.join(if_node.loc.end.end)
else
node.source_range
end
end
# rubocop:enable Metrics/AbcSize
def all_branches_body_missing?(node)
return false unless node.parent&.if_type?
node.parent.branches.compact.empty?
end
def deletion_range(range)
# Collect a range between the start of the `if` node and the next relevant node,
# including final new line.
# Based on `RangeHelp#range_by_whole_lines` but allows the `if` to not start
# on the first column.
buffer = @processed_source.buffer
last_line = buffer.source_line(range.last_line)
end_offset = last_line.length - range.last_column + 1
range.adjust(end_pos: end_offset).intersect(buffer.source_range)
end
end
end
end
end