Skip to content

Commit 5ddb08a

Browse files
authored
Merge pull request #408 from Shopify/at-fix-heredoc
Fix constants with heredoc values
2 parents d67b82e + 86f77f3 commit 5ddb08a

5 files changed

Lines changed: 185 additions & 22 deletions

File tree

lib/rbi/parser.rb

Lines changed: 95 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,17 @@ def node_string(node)
138138
def node_string!(node)
139139
T.must(node_string(node))
140140
end
141+
142+
#: (Prism::Node node) -> Prism::Location
143+
def adjust_prism_location_for_heredoc(node)
144+
visitor = HeredocLocationVisitor.new(
145+
node.location.send(:source),
146+
node.location.start_offset,
147+
node.location.end_offset,
148+
)
149+
visitor.visit(node)
150+
visitor.location
151+
end
141152
end
142153

143154
class TreeBuilder < Visitor
@@ -217,30 +228,40 @@ def visit_constant_assign(node)
217228

218229
current_scope << if struct
219230
struct
220-
elsif type_variable_definition?(node.value)
221-
TypeMember.new(
222-
case node
223-
when Prism::ConstantWriteNode
224-
node.name.to_s
225-
when Prism::ConstantPathWriteNode
226-
node_string!(node.target)
227-
end,
228-
node_string!(node.value),
229-
loc: node_loc(node),
230-
comments: node_comments(node),
231-
)
232231
else
233-
Const.new(
234-
case node
235-
when Prism::ConstantWriteNode
236-
node.name.to_s
237-
when Prism::ConstantPathWriteNode
238-
node_string!(node.target)
239-
end,
240-
node_string!(node.value),
241-
loc: node_loc(node),
242-
comments: node_comments(node),
232+
adjusted_node_location = adjust_prism_location_for_heredoc(node)
233+
234+
adjusted_value_location = Prism::Location.new(
235+
node.value.location.send(:source),
236+
node.value.location.start_offset,
237+
adjusted_node_location.end_offset - node.value.location.start_offset,
243238
)
239+
240+
if type_variable_definition?(node.value)
241+
TypeMember.new(
242+
case node
243+
when Prism::ConstantWriteNode
244+
node.name.to_s
245+
when Prism::ConstantPathWriteNode
246+
node_string!(node.target)
247+
end,
248+
adjusted_value_location.slice,
249+
loc: Loc.from_prism(@file, adjusted_node_location),
250+
comments: node_comments(node),
251+
)
252+
else
253+
Const.new(
254+
case node
255+
when Prism::ConstantWriteNode
256+
node.name.to_s
257+
when Prism::ConstantPathWriteNode
258+
node_string!(node.target)
259+
end,
260+
adjusted_value_location.slice,
261+
loc: Loc.from_prism(@file, adjusted_node_location),
262+
comments: node_comments(node),
263+
)
264+
end
244265
end
245266
end
246267

@@ -903,5 +924,57 @@ def visit_assoc_node(node)
903924
)
904925
end
905926
end
927+
928+
class HeredocLocationVisitor < Prism::Visitor
929+
#: (Prism::Source source, Integer begin_offset, Integer end_offset) -> void
930+
def initialize(source, begin_offset, end_offset)
931+
super()
932+
@source = source
933+
@begin_offset = begin_offset
934+
@end_offset = end_offset
935+
@offset_last_newline = false #: bool
936+
end
937+
938+
#: (Prism::StringNode node) -> void
939+
def visit_string_node(node)
940+
return unless node.heredoc?
941+
942+
closing_loc = node.closing_loc
943+
return unless closing_loc
944+
945+
handle_string_node(node)
946+
end
947+
948+
#: (Prism::InterpolatedStringNode node) -> void
949+
def visit_interpolated_string_node(node)
950+
return super unless node.heredoc?
951+
952+
closing_loc = node.closing_loc
953+
return super unless closing_loc
954+
955+
handle_string_node(node)
956+
end
957+
958+
#: -> Prism::Location
959+
def location
960+
Prism::Location.new(
961+
@source,
962+
@begin_offset,
963+
@end_offset - @begin_offset - (@offset_last_newline ? 1 : 0),
964+
)
965+
end
966+
967+
private
968+
969+
#: (Prism::StringNode | Prism::InterpolatedStringNode node) -> void
970+
def handle_string_node(node)
971+
closing_loc = T.must(node.closing_loc)
972+
973+
if closing_loc.end_offset > @end_offset
974+
@end_offset = closing_loc.end_offset
975+
@offset_last_newline = true if node.closing_loc&.slice&.end_with?("\n")
976+
end
977+
end
978+
end
906979
end
907980
end

lib/rbi/printer.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -675,6 +675,13 @@ def oneline?(node)
675675
node.comments.empty? && node.empty?
676676
when Attr
677677
node.comments.empty? && node.sigs.empty?
678+
when Const
679+
return false unless node.comments.empty?
680+
681+
loc = node.loc
682+
return true unless loc
683+
684+
loc.begin_line == loc.end_line
678685
when Method
679686
node.comments.empty? && node.sigs.empty? && node.params.all? { |p| p.comments.empty? }
680687
when Sig

rbi/rbi.rbi

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,6 +1005,25 @@ class RBI::Parser
10051005
end
10061006
end
10071007

1008+
class RBI::Parser::HeredocLocationVisitor < ::Prism::Visitor
1009+
sig { params(source: ::Prism::Source, begin_offset: ::Integer, end_offset: ::Integer).void }
1010+
def initialize(source, begin_offset, end_offset); end
1011+
1012+
sig { returns(::Prism::Location) }
1013+
def location; end
1014+
1015+
sig { params(node: ::Prism::InterpolatedStringNode).void }
1016+
def visit_interpolated_string_node(node); end
1017+
1018+
sig { params(node: ::Prism::StringNode).void }
1019+
def visit_string_node(node); end
1020+
1021+
private
1022+
1023+
sig { params(node: T.any(::Prism::InterpolatedStringNode, ::Prism::StringNode)).void }
1024+
def handle_string_node(node); end
1025+
end
1026+
10081027
class RBI::Parser::SigBuilder < ::RBI::Parser::Visitor
10091028
sig { params(content: ::String, file: ::String).void }
10101029
def initialize(content, file:); end
@@ -1119,6 +1138,9 @@ class RBI::Parser::Visitor < ::Prism::Visitor
11191138

11201139
private
11211140

1141+
sig { params(node: ::Prism::Node).returns(::Prism::Location) }
1142+
def adjust_prism_location_for_heredoc(node); end
1143+
11221144
sig { params(node: ::Prism::Node).returns(::RBI::Loc) }
11231145
def node_loc(node); end
11241146

test/rbi/parser_test.rb

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,65 @@ def test_parse_constants
8888
assert_equal(rbi, tree.string)
8989
end
9090

91+
def test_parse_constants_with_heredoc
92+
rbi = <<~RBI
93+
A = <<-EOF
94+
foo
95+
foo
96+
EOF
97+
98+
B = <<~EOF.strip
99+
bar
100+
EOF
101+
102+
C = <<-'EOF'
103+
baz
104+
EOF
105+
106+
D = %Q(
107+
108+
qux
109+
110+
)
111+
112+
E = T.let(<<~EOF, String)
113+
foo
114+
EOF
115+
116+
F = "foo" \\
117+
"bar"
118+
119+
G = "\#{<<~begin}\#{<<~end}"
120+
begin
121+
foo
122+
foo
123+
end
124+
125+
H = foo(<<~begin, <<~end)
126+
begin
127+
foo
128+
foo
129+
end
130+
RBI
131+
132+
tree = parse_rbi(rbi)
133+
assert_equal(rbi, tree.string)
134+
end
135+
136+
def test_parse_constants_multiline_calls
137+
rbi = <<~RBI
138+
A = foo(
139+
<<~EOF,
140+
bar
141+
EOF
142+
baz,
143+
)
144+
RBI
145+
146+
tree = parse_rbi(rbi)
147+
assert_equal(rbi, tree.string)
148+
end
149+
91150
def test_parse_constants_with_newlines
92151
rbi = <<~RBI
93152
sig { returns(Foo::

test/rbi/printer_test.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1113,8 +1113,10 @@ class << self; end
11131113
class TE < T::Enum; end
11141114
# file.rbi:1:3-2:4
11151115
class TS < T::Struct; end
1116+
11161117
# file.rbi:1:3-2:4
11171118
C = 42
1119+
11181120
# file.rbi:1:3-2:4
11191121
extend E
11201122
# file.rbi:1:3-2:4

0 commit comments

Comments
 (0)