Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prism::Node#tunnel #2769

Merged
merged 1 commit into from
May 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading