-
-
Notifications
You must be signed in to change notification settings - Fork 3k
/
dot_position.rb
137 lines (116 loc) · 4.1 KB
/
dot_position.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
# frozen_string_literal: true
module RuboCop
module Cop
module Layout
# This cop checks the . position in multi-line method calls.
#
# @example EnforcedStyle: leading (default)
# # bad
# something.
# method
#
# # good
# something
# .method
#
# @example EnforcedStyle: trailing
# # bad
# something
# .method
#
# # good
# something.
# method
class DotPosition < Base
include ConfigurableEnforcedStyle
include RangeHelp
extend AutoCorrector
def on_send(node)
return unless node.dot? || ampersand_dot?(node)
return correct_style_detected if proper_dot_position?(node)
opposite_style_detected
dot = node.loc.dot
message = message(dot)
add_offense(dot, message: message) { |corrector| autocorrect(corrector, dot, node) }
end
alias on_csend on_send
private
def autocorrect(corrector, dot, node)
dot_range = if processed_source[dot.line - 1].strip == '.'
range_by_whole_lines(dot, include_final_newline: true)
else
dot
end
corrector.remove(dot_range)
case style
when :leading
corrector.insert_before(selector_range(node), dot.source)
when :trailing
corrector.insert_after(node.receiver, dot.source)
end
end
def message(dot)
"Place the #{dot.source} on the " +
case style
when :leading
'next line, together with the method name.'
when :trailing
'previous line, together with the method call receiver.'
end
end
def proper_dot_position?(node)
selector_line = selector_range(node).line
# If the receiver is a HEREDOC and the selector is on the same line
# then there is nothing to do
return true if heredoc?(node.receiver) && node.receiver.loc.first_line == selector_line
receiver_line = receiver_end_line(node.receiver)
dot_line = node.loc.dot.line
# receiver and selector are on the same line
return true if selector_line == receiver_line
# don't register an offense if there is a line comment between the
# dot and the selector otherwise, we might break the code while
# "correcting" it (even if there is just an extra blank line, treat
# it the same)
# Also, in the case of a heredoc, the receiver will end after the dot,
# because the heredoc body is on subsequent lines, so use the highest
# line to compare to.
return true if line_between?(selector_line, [receiver_line, dot_line].max)
correct_dot_position_style?(dot_line, selector_line)
end
def line_between?(first_line, second_line)
(first_line - second_line) > 1
end
def correct_dot_position_style?(dot_line, selector_line)
case style
when :leading then dot_line == selector_line
when :trailing then dot_line != selector_line
end
end
def receiver_end_line(node)
if (line = last_heredoc_line(node))
line
else
node.source_range.end.line
end
end
def last_heredoc_line(node)
if node.send_type?
node.arguments.select { |arg| heredoc?(arg) }.map { |arg| arg.loc.heredoc_end.line }.max
elsif heredoc?(node)
node.loc.heredoc_end.line
end
end
def heredoc?(node)
(node.str_type? || node.dstr_type?) && node.heredoc?
end
def selector_range(node)
# l.(1) has no selector, so we use the opening parenthesis instead
node.loc.selector || node.loc.begin
end
def ampersand_dot?(node)
node.loc.respond_to?(:dot) && node.loc.dot && node.loc.dot.is?('&.')
end
end
end
end
end