Skip to content

Commit 0bf5d65

Browse files
committed
Prism::Node#tunnel
1 parent 624fdf7 commit 0bf5d65

File tree

7 files changed

+67
-3
lines changed

7 files changed

+67
-3
lines changed

lib/prism/node_ext.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ def child
172172
DEPRECATED: ConstantPathNode#child is deprecated and will be removed \
173173
in the next major version. Use \
174174
ConstantPathNode#name/ConstantPathNode#name_loc instead. Called from \
175-
#{caller(1..1).first}.
175+
#{caller(1, 1)&.first}.
176176
MSG
177177

178178
name ? ConstantReadNode.new(source, name, name_loc) : MissingNode.new(source, location)
@@ -214,7 +214,7 @@ def child
214214
DEPRECATED: ConstantPathTargetNode#child is deprecated and will be \
215215
removed in the next major version. Use \
216216
ConstantPathTargetNode#name/ConstantPathTargetNode#name_loc instead. \
217-
Called from #{caller(1..1).first}.
217+
Called from #{caller(1, 1)&.first}.
218218
MSG
219219

220220
name ? ConstantReadNode.new(source, name, name_loc) : MissingNode.new(source, location)

lib/prism/pattern.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,10 @@ def compile_constant_path_node(node)
149149
parent = node.parent
150150

151151
if parent.is_a?(ConstantReadNode) && parent.slice == "Prism"
152-
compile_constant_name(node, node.name)
152+
name = node.name
153+
raise CompilationError, node.inspect if name.nil?
154+
155+
compile_constant_name(node, name)
153156
else
154157
compile_error(node)
155158
end

sig/prism/_private/pattern.rbs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ module Prism
1515
def compile_alternation_pattern_node: (AlternationPatternNode) -> Proc
1616
def compile_constant_path_node: (ConstantPathNode) -> Proc
1717
def compile_constant_read_node: (ConstantReadNode) -> Proc
18+
def compile_constant_name: (Prism::node, Symbol) -> Proc
1819
def compile_hash_pattern_node: (HashPatternNode) -> Proc
1920
def compile_nil_node: (NilNode) -> Proc
2021
def compile_regular_expression_node: (RegularExpressionNode) -> Proc

templates/lib/prism/node.rb.erb

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,43 @@ module Prism
7676
DotVisitor.new.tap { |visitor| accept(visitor) }.to_dot
7777
end
7878

79+
# Returns a list of nodes that are descendants of this node that contain the
80+
# given line and column. This is useful for locating a node that is selected
81+
# based on the line and column of the source code.
82+
#
83+
# Important to note is that the column given to this method should be in
84+
# bytes, as opposed to characters or code units.
85+
def tunnel(line, column)
86+
queue = [self]
87+
result = []
88+
89+
while (node = queue.shift)
90+
result << node
91+
92+
node.compact_child_nodes.each do |child_node|
93+
child_location = child_node.location
94+
95+
start_line = child_location.start_line
96+
end_line = child_location.end_line
97+
98+
if start_line == end_line
99+
if line == start_line && column >= child_location.start_column && column < child_location.end_column
100+
queue << child_node
101+
break
102+
end
103+
elsif (line == start_line && column >= child_location.start_column) || (line == end_line && column < child_location.end_column)
104+
queue << child_node
105+
break
106+
elsif line > start_line && line < end_line
107+
queue << child_node
108+
break
109+
end
110+
end
111+
end
112+
113+
result
114+
end
115+
79116
# Returns a list of the fields that exist for this node class. Fields
80117
# describe the structure of the node. This kind of reflection is useful for
81118
# things like recursively visiting each node _and_ field in the tree.

templates/rbi/prism/node.rbi.erb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ class Prism::Node
3131
sig { returns(String) }
3232
def to_dot; end
3333

34+
sig { params(line: Integer, column: Integer).returns(T::Array[Prism::Node]) }
35+
def tunnel(line, column); end
36+
3437
sig { abstract.params(visitor: Prism::Visitor).returns(T.untyped) }
3538
def accept(visitor); end
3639

templates/sig/prism/node.rbs.erb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ module Prism
1818
def slice_lines: () -> String
1919
def pretty_print: (untyped q) -> untyped
2020
def to_dot: () -> String
21+
def tunnel: (Integer line, Integer column) -> Array[Prism::node]
2122
end
2223

2324
type node_singleton = singleton(Node) & _NodeSingleton

test/prism/ruby_api_test.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,25 @@ def test_node_equality
266266
refute_operator parse_expression(complex_source_1), :===, parse_expression(complex_source_2)
267267
end
268268

269+
def test_node_tunnel
270+
program = Prism.parse("foo(1) +\n bar(2, 3) +\n baz(3, 4, 5)").value
271+
272+
tunnel = program.tunnel(1, 4).last
273+
assert_kind_of IntegerNode, tunnel
274+
assert_equal 1, tunnel.value
275+
276+
tunnel = program.tunnel(2, 6).last
277+
assert_kind_of IntegerNode, tunnel
278+
assert_equal 2, tunnel.value
279+
280+
tunnel = program.tunnel(3, 9).last
281+
assert_kind_of IntegerNode, tunnel
282+
assert_equal 4, tunnel.value
283+
284+
tunnel = program.tunnel(3, 8)
285+
assert_equal [ProgramNode, StatementsNode, CallNode, ArgumentsNode, CallNode, ArgumentsNode], tunnel.map(&:class)
286+
end
287+
269288
private
270289

271290
def parse_expression(source)

0 commit comments

Comments
 (0)