-
-
Notifications
You must be signed in to change notification settings - Fork 3k
/
empty_lines_around_body.rb
173 lines (145 loc) · 5.51 KB
/
empty_lines_around_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
# frozen_string_literal: true
module RuboCop
module Cop
module Layout
# Common functionality for checking if presence/absence of empty lines
# around some kind of body matches the configuration.
module EmptyLinesAroundBody
extend NodePattern::Macros
include ConfigurableEnforcedStyle
include RangeHelp
MSG_EXTRA = 'Extra empty line detected at %<kind>s body ' \
'%<location>s.'
MSG_MISSING = 'Empty line missing at %<kind>s body %<location>s.'
MSG_DEFERRED = 'Empty line missing before first %<type>s ' \
'definition'
private
def_node_matcher :constant_definition?, '{class module}'
def_node_matcher :empty_line_required?,
'{def defs class module (send nil? {:private :protected :public})}'
def check(node, body, adjusted_first_line: nil)
return if valid_body_style?(body)
return if node.single_line?
first_line = adjusted_first_line || node.source_range.first_line
last_line = node.source_range.last_line
case style
when :empty_lines_except_namespace
check_empty_lines_except_namespace(body, first_line, last_line)
when :empty_lines_special
check_empty_lines_special(body, first_line, last_line)
else
check_both(style, first_line, last_line)
end
end
def check_empty_lines_except_namespace(body, first_line, last_line)
if namespace?(body, with_one_child: true)
check_both(:no_empty_lines, first_line, last_line)
else
check_both(:empty_lines, first_line, last_line)
end
end
def check_empty_lines_special(body, first_line, last_line)
return unless body
if namespace?(body, with_one_child: true)
check_both(:no_empty_lines, first_line, last_line)
else
if first_child_requires_empty_line?(body)
check_beginning(:empty_lines, first_line)
else
check_beginning(:no_empty_lines, first_line)
check_deferred_empty_line(body)
end
check_ending(:empty_lines, last_line)
end
end
def check_both(style, first_line, last_line)
case style
when :beginning_only
check_beginning(:empty_lines, first_line)
check_ending(:no_empty_lines, last_line)
when :ending_only
check_beginning(:no_empty_lines, first_line)
check_ending(:empty_lines, last_line)
else
check_beginning(style, first_line)
check_ending(style, last_line)
end
end
def check_beginning(style, first_line)
check_source(style, first_line, 'beginning')
end
def check_ending(style, last_line)
check_source(style, last_line - 2, 'end')
end
def check_source(style, line_no, desc)
case style
when :no_empty_lines
check_line(style, line_no, message(MSG_EXTRA, desc), &:empty?)
when :empty_lines
check_line(style, line_no, message(MSG_MISSING, desc)) do |line|
!line.empty?
end
end
end
def check_line(style, line, msg)
return unless yield(processed_source.lines[line])
offset = style == :empty_lines && msg.include?('end.') ? 2 : 1
range = source_range(processed_source.buffer, line + offset, 0)
add_offense([style, range], location: range, message: msg)
end
def check_deferred_empty_line(body)
node = first_empty_line_required_child(body)
return unless node
line = previous_line_ignoring_comments(node.first_line)
return if processed_source[line].empty?
range = source_range(processed_source.buffer, line + 2, 0)
add_offense(
[:empty_lines, range],
location: range,
message: deferred_message(node)
)
end
def namespace?(body, with_one_child: false)
if body.begin_type?
return false if with_one_child
body.children.all? { |child| constant_definition?(child) }
else
constant_definition?(body)
end
end
def first_child_requires_empty_line?(body)
if body.begin_type?
empty_line_required?(body.children.first)
else
empty_line_required?(body)
end
end
def first_empty_line_required_child(body)
if body.begin_type?
body.children.find { |child| empty_line_required?(child) }
elsif empty_line_required?(body)
body
end
end
def previous_line_ignoring_comments(send_line)
(send_line - 2).downto(0) do |line|
return line unless comment_line?(processed_source[line])
end
0
end
def message(type, desc)
format(type, kind: self.class::KIND, location: desc)
end
def deferred_message(node)
format(MSG_DEFERRED, type: node.type)
end
def valid_body_style?(body)
# When style is `empty_lines`, if the body is empty, we don't enforce
# the presence OR absence of an empty line
# But if style is `no_empty_lines`, there must not be an empty line
body.nil? && style != :no_empty_lines
end
end
end
end
end