Skip to content

Commit

Permalink
Merge pull request #2769 from ruby/tunnel
Browse files Browse the repository at this point in the history
Prism::Node#tunnel
  • Loading branch information
kddnewton committed May 3, 2024
2 parents 624fdf7 + 0bf5d65 commit 5d9dff4
Show file tree
Hide file tree
Showing 7 changed files with 67 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
1 change: 1 addition & 0 deletions sig/prism/_private/pattern.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ module Prism
def compile_alternation_pattern_node: (AlternationPatternNode) -> Proc
def compile_constant_path_node: (ConstantPathNode) -> Proc
def compile_constant_read_node: (ConstantReadNode) -> Proc
def compile_constant_name: (Prism::node, Symbol) -> Proc
def compile_hash_pattern_node: (HashPatternNode) -> Proc
def compile_nil_node: (NilNode) -> Proc
def compile_regular_expression_node: (RegularExpressionNode) -> Proc
Expand Down
37 changes: 37 additions & 0 deletions 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
3 changes: 3 additions & 0 deletions templates/rbi/prism/node.rbi.erb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ class Prism::Node
sig { returns(String) }
def to_dot; end

sig { params(line: Integer, column: Integer).returns(T::Array[Prism::Node]) }
def tunnel(line, column); end

sig { abstract.params(visitor: Prism::Visitor).returns(T.untyped) }
def accept(visitor); end

Expand Down
1 change: 1 addition & 0 deletions templates/sig/prism/node.rbs.erb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ module Prism
def slice_lines: () -> String
def pretty_print: (untyped q) -> untyped
def to_dot: () -> String
def tunnel: (Integer line, Integer column) -> Array[Prism::node]
end

type node_singleton = singleton(Node) & _NodeSingleton
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 5d9dff4

Please sign in to comment.