Skip to content

Commit

Permalink
[ruby/prism] Prism::Node#tunnel
Browse files Browse the repository at this point in the history
  • Loading branch information
kddnewton authored and matzbot committed May 3, 2024
1 parent c631679 commit 7a49edc
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 3 deletions.
4 changes: 2 additions & 2 deletions lib/prism/node_ext.rb
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ def child
DEPRECATED: ConstantPathNode#child is deprecated and will be removed \
in the next major version. Use \
ConstantPathNode#name/ConstantPathNode#name_loc instead. Called from \
#{caller(1..1).first}.
#{caller(1, 1)&.first}.
MSG

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

name ? ConstantReadNode.new(source, name, name_loc) : MissingNode.new(source, location)
Expand Down
5 changes: 4 additions & 1 deletion lib/prism/pattern.rb
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,10 @@ def compile_constant_path_node(node)
parent = node.parent

if parent.is_a?(ConstantReadNode) && parent.slice == "Prism"
compile_constant_name(node, node.name)
name = node.name
raise CompilationError, node.inspect if name.nil?

compile_constant_name(node, name)
else
compile_error(node)
end
Expand Down
37 changes: 37 additions & 0 deletions prism/templates/lib/prism/node.rb.erb
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,43 @@ module Prism
DotVisitor.new.tap { |visitor| accept(visitor) }.to_dot
end

# Returns a list of nodes that are descendants of this node that contain the
# given line and column. This is useful for locating a node that is selected
# based on the line and column of the source code.
#
# Important to note is that the column given to this method should be in
# bytes, as opposed to characters or code units.
def tunnel(line, column)
queue = [self]
result = []

while (node = queue.shift)
result << node

node.compact_child_nodes.each do |child_node|
child_location = child_node.location

start_line = child_location.start_line
end_line = child_location.end_line

if start_line == end_line
if line == start_line && column >= child_location.start_column && column < child_location.end_column
queue << child_node
break
end
elsif (line == start_line && column >= child_location.start_column) || (line == end_line && column < child_location.end_column)
queue << child_node
break
elsif line > start_line && line < end_line
queue << child_node
break
end
end
end

result
end

# Returns a list of the fields that exist for this node class. Fields
# describe the structure of the node. This kind of reflection is useful for
# things like recursively visiting each node _and_ field in the tree.
Expand Down
19 changes: 19 additions & 0 deletions test/prism/ruby_api_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,25 @@ def test_node_equality
refute_operator parse_expression(complex_source_1), :===, parse_expression(complex_source_2)
end

def test_node_tunnel
program = Prism.parse("foo(1) +\n bar(2, 3) +\n baz(3, 4, 5)").value

tunnel = program.tunnel(1, 4).last
assert_kind_of IntegerNode, tunnel
assert_equal 1, tunnel.value

tunnel = program.tunnel(2, 6).last
assert_kind_of IntegerNode, tunnel
assert_equal 2, tunnel.value

tunnel = program.tunnel(3, 9).last
assert_kind_of IntegerNode, tunnel
assert_equal 4, tunnel.value

tunnel = program.tunnel(3, 8)
assert_equal [ProgramNode, StatementsNode, CallNode, ArgumentsNode, CallNode, ArgumentsNode], tunnel.map(&:class)
end

private

def parse_expression(source)
Expand Down

0 comments on commit 7a49edc

Please sign in to comment.