-
-
Notifications
You must be signed in to change notification settings - Fork 3k
/
empty_line_between_defs.rb
150 lines (123 loc) · 4.61 KB
/
empty_line_between_defs.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
# frozen_string_literal: true
module RuboCop
module Cop
module Layout
# This cop checks whether method definitions are
# separated by one empty line.
#
# `NumberOfEmptyLines` can be an integer (default is 1) or
# an array (e.g. [1, 2]) to specify a minimum and maximum
# number of empty lines permitted.
#
# `AllowAdjacentOneLineDefs` configures whether adjacent
# one-line method definitions are considered an offense.
#
# @example
#
# # bad
# def a
# end
# def b
# end
#
# @example
#
# # good
# def a
# end
#
# def b
# end
class EmptyLineBetweenDefs < Base
include RangeHelp
extend AutoCorrector
MSG = 'Use empty lines between method definitions.'
def self.autocorrect_incompatible_with
[Layout::EmptyLines]
end
# We operate on `begin` nodes, instead of using `OnMethodDef`,
# so that we can walk over pairs of consecutive nodes and
# efficiently access a node's predecessor; #prev_node ends up
# doing a linear scan over siblings, so we don't want to call
# it on each def.
def on_begin(node)
node.children.each_cons(2) do |prev, n|
nodes = [prev, n]
check_defs(nodes) if nodes.all?(&method(:def_node?))
end
end
def check_defs(nodes)
return if blank_lines_between?(*nodes)
return if multiple_blank_lines_groups?(*nodes)
return if nodes.all?(&:single_line?) &&
cop_config['AllowAdjacentOneLineDefs']
location = nodes.last.loc.keyword.join(nodes.last.loc.name)
add_offense(location) do |corrector|
autocorrect(corrector, *nodes)
end
end
def autocorrect(corrector, prev_def, node)
# finds position of first newline
end_pos = prev_def.loc.end.end_pos
source_buffer = prev_def.loc.end.source_buffer
newline_pos = source_buffer.source.index("\n", end_pos)
# Handle the case when multiple one-liners are on the same line.
newline_pos = end_pos + 1 if newline_pos > node.source_range.begin_pos
count = blank_lines_count_between(prev_def, node)
if count > maximum_empty_lines
autocorrect_remove_lines(corrector, newline_pos, count)
else
autocorrect_insert_lines(corrector, newline_pos, count)
end
end
private
def def_node?(node)
return unless node
node.def_type? || node.defs_type?
end
def multiple_blank_lines_groups?(first_def_node, second_def_node)
lines = lines_between_defs(first_def_node, second_def_node)
blank_start = lines.each_index.select { |i| lines[i].blank? }.max
non_blank_end = lines.each_index.reject { |i| lines[i].blank? }.min
return false if blank_start.nil? || non_blank_end.nil?
blank_start > non_blank_end
end
def blank_lines_between?(first_def_node, second_def_node)
count = blank_lines_count_between(first_def_node, second_def_node)
(minimum_empty_lines..maximum_empty_lines).cover?(count)
end
def blank_lines_count_between(first_def_node, second_def_node)
lines_between_defs(first_def_node, second_def_node).count(&:blank?)
end
def minimum_empty_lines
Array(cop_config['NumberOfEmptyLines']).first
end
def maximum_empty_lines
Array(cop_config['NumberOfEmptyLines']).last
end
def lines_between_defs(first_def_node, second_def_node)
begin_line_num = def_end(first_def_node)
end_line_num = def_start(second_def_node) - 2
return [] if end_line_num.negative?
processed_source.lines[begin_line_num..end_line_num]
end
def def_start(node)
node.loc.keyword.line
end
def def_end(node)
node.loc.end.line
end
def autocorrect_remove_lines(corrector, newline_pos, count)
difference = count - maximum_empty_lines
range_to_remove = range_between(newline_pos, newline_pos + difference)
corrector.remove(range_to_remove)
end
def autocorrect_insert_lines(corrector, newline_pos, count)
difference = minimum_empty_lines - count
where_to_insert = range_between(newline_pos, newline_pos + 1)
corrector.insert_after(where_to_insert, "\n" * difference)
end
end
end
end
end