forked from rubocop/rubocop
-
Notifications
You must be signed in to change notification settings - Fork 0
/
heredoc_method_call_position.rb
156 lines (125 loc) · 4.11 KB
/
heredoc_method_call_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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# frozen_string_literal: true
module RuboCop
module Cop
module Lint
# This cop checks for the ordering of a method call where
# the receiver of the call is a HEREDOC.
#
# @example
# # bad
#
# <<-SQL
# bar
# SQL
# .strip_indent
#
# <<-SQL
# bar
# SQL
# .strip_indent
# .trim
#
# # good
#
# <<~SQL
# bar
# SQL
#
# <<~SQL.trim
# bar
# SQL
#
class HeredocMethodCallPosition < Cop
include RangeHelp
MSG = 'Put a method call with a HEREDOC receiver on the ' \
'same line as the HEREDOC opening.'
def on_send(node)
heredoc = heredoc_node_descendent_receiver(node)
return unless heredoc
return if correctly_positioned?(node, heredoc)
add_offense(node, location: call_after_heredoc_range(heredoc))
end
alias on_csend on_send
def autocorrect(node)
heredoc = heredoc_node_descendent_receiver(node)
lambda do |corrector|
call_range = call_range_to_safely_reposition(node, heredoc)
return if call_range.nil?
call_source = call_range.source.strip
corrector.remove(call_range)
corrector.insert_after(heredoc_begin_line_range(node), call_source)
end
end
private
def heredoc_node_descendent_receiver(node)
while send_node?(node)
return node.receiver if heredoc_node?(node.receiver)
node = node.receiver
end
end
def send_node?(node)
return nil unless node
node.call_type?
end
def heredoc_node?(node)
node.respond_to?(:heredoc?) && node.heredoc?
end
def call_after_heredoc_range(heredoc)
pos = heredoc_end_pos(heredoc)
range_between(pos + 1, pos + 2)
end
def correctly_positioned?(node, heredoc)
heredoc_end_pos(heredoc) > call_end_pos(node)
end
def calls_on_multiple_lines?(node, _heredoc)
last_line = node.last_line
while send_node?(node)
return true unless last_line == node.last_line
return true unless all_on_same_line?(node.arguments)
node = node.receiver
end
false
end
def all_on_same_line?(nodes)
return true if nodes.empty?
nodes.first.first_line == nodes.last.last_line
end
def heredoc_end_pos(heredoc)
heredoc.location.heredoc_end.end_pos
end
def call_end_pos(node)
node.source_range.end_pos
end
def heredoc_begin_line_range(heredoc)
pos = heredoc.source_range.begin_pos
range_by_whole_lines(range_between(pos, pos))
end
def call_line_range(node)
pos = node.source_range.end_pos
range_by_whole_lines(range_between(pos, pos))
end
# Returns nil if no range can be safely repositioned.
def call_range_to_safely_reposition(node, heredoc)
return nil if calls_on_multiple_lines?(node, heredoc)
heredoc_end_pos = heredoc_end_pos(heredoc)
call_end_pos = call_end_pos(node)
call_range = range_between(heredoc_end_pos, call_end_pos)
call_line_range = call_line_range(node)
call_source = call_range.source.strip
call_line_source = call_line_range.source.strip
return call_range if call_source == call_line_source
if trailing_comma?(call_source, call_line_source)
# If there's some on the last line other than the call, e.g.
# a trailing comma, then we leave the "\n" following the
# heredoc_end in place.
return range_between(heredoc_end_pos, call_end_pos + 1)
end
nil
end
def trailing_comma?(call_source, call_line_source)
"#{call_source}," == call_line_source
end
end
end
end
end