Class: TreeStand::Node

Inherits:
Object
  • Object
show all
Extended by:
Forwardable, T::Sig
Includes:
Enumerable
Defined in:
lib/tree_stand/node.rb

Overview

Wrapper around a TreeSitter node and provides convient methods that are missing on the original node. This class overrides the ‘method_missing` method to delegate to a nodes named children.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(tree, ts_node) ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Parameters:



31
32
33
34
35
# File 'lib/tree_stand/node.rb', line 31

def initialize(tree, ts_node)
  @tree = tree
  @ts_node = ts_node
  @fields = @ts_node.each_field.to_a.map(&:first)
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(field_name) ⇒ TreeStand::Node #method_missing(method_name, *args, &block) ⇒ Object

This class overrides the ‘method_missing` method to delegate to the node’s named children.

Examples:

node.text          # => "3 * 4"

node.left.text     # => "3"
node.operator.text # => "*"
node.right.text    # => "4"
node.operand       # => NoMethodError

Overloads:

  • #method_missing(field_name) ⇒ TreeStand::Node

    Returns child node for the given field name.

    Parameters:

    • name (Symbol, String)

    Returns:

    Raises:

    • (NoMethodError)

      Raised if the node does not have child with name ‘field_name`

  • #method_missing(method_name, *args, &block) ⇒ Object

    Raises:

    • (NoMethodError)


195
196
197
198
# File 'lib/tree_stand/node.rb', line 195

def method_missing(method, *args, &block)
  return super unless @fields.include?(method.to_s)
  TreeStand::Node.new(@tree, T.unsafe(@ts_node).public_send(method, *args, &block))
end

Instance Attribute Details

#treeTreeStand::Tree (readonly)

Returns:



25
26
27
# File 'lib/tree_stand/node.rb', line 25

def tree
  @tree
end

#ts_nodeTreeSitter::Node (readonly)

Returns:

  • (TreeSitter::Node)


27
28
29
# File 'lib/tree_stand/node.rb', line 27

def ts_node
  @ts_node
end

Instance Method Details

#==(other) ⇒ Boolean

Parameters:

  • other (Object)

Returns:

  • (Boolean)


201
202
203
204
205
# File 'lib/tree_stand/node.rb', line 201

def ==(other)
  return false unless other.is_a?(TreeStand::Node)

  T.must(range == other.range && type == other.type && text == other.text)
end

#childrenArray<TreeStand::Node>

Examples:

node.text # => "3 * 4"
node.to_a.map(&:text) # => ["3", "*", "4"]
node.children.map(&:text) # => ["3", "*", "4"]

Returns:



170
# File 'lib/tree_stand/node.rb', line 170

def children = to_a

#each(&block) {|child| ... } ⇒ T::Enumerator[TreeStand::Node]

Node includes enumerable so that you can iterate over the child nodes.

Examples:

node.text # => "3 * 4"

Iterate over the child nodes

node.each do |child|
  print child.text
end
# prints: 3*4

Enumerable methods

node.map(&:text) # => ["3", "*", "4"]

Parameters:

Yield Parameters:

Returns:



123
124
125
126
127
128
129
130
131
# File 'lib/tree_stand/node.rb', line 123

def each(&block)
  enumerator = Enumerator.new do |yielder|
    @ts_node.each do |child|
      yielder << TreeStand::Node.new(@tree, child)
    end
  end
  enumerator.each(&block) if block_given?
  enumerator
end

#error?bool

Returns true if the node is an error node.

Returns:

  • (bool)

    true if the node is an error node.



18
19
20
21
22
# File 'lib/tree_stand/node.rb', line 18

def_delegators(
  :@ts_node,
  :type,
  :error?,
)

#find_node(query_string) ⇒ TreeStand::Node?

Returns the first captured node that matches the query string or nil if there was no captured node.

Examples:

Find the first identifier node.

identifier_node = tree.root_node.find_node("(identifier) @identifier")

Parameters:

  • query_string (String)

Returns:

See Also:



80
81
82
# File 'lib/tree_stand/node.rb', line 80

def find_node(query_string)
  query(query_string).first&.values&.first
end

#find_node!(query_string) ⇒ TreeStand::Node

Like #find_node, except that if no node is found, raises an TreeStand::NodeNotFound error.

Parameters:

  • query_string (String)

Returns:

Raises:

See Also:



90
91
92
# File 'lib/tree_stand/node.rb', line 90

def find_node!(query_string)
  find_node(query_string) || raise(TreeStand::NodeNotFound)
end

#parentTreeStand::Node

Examples:

node.text # => "4"
node.parent.text # => "3 * 4"
node.parent.parent.text # => "1 + 3 * 4"

Returns:



161
162
163
# File 'lib/tree_stand/node.rb', line 161

def parent
  TreeStand::Node.new(@tree, @ts_node.parent)
end

#pretty_print(pp) ⇒ void

This method returns an undefined value.

Backed by Utils::Printer.

Parameters:

  • pp (PP)

See Also:



212
213
214
# File 'lib/tree_stand/node.rb', line 212

def pretty_print(pp)
  Utils::Printer.new(ralign: 80).print(self, io: pp.output)
end

#query(query_string) ⇒ Array<Hash{String => TreeStand::Node}>

TreeSitter uses a ‘TreeSitter::Cursor` to iterate over matches by calling `curser#next_match` repeatedly until it returns `nil`.

This method does all of that for you and collects all of the matches into an array and each corresponding capture into a hash.

Examples:

# This will return a match for each identifier nodes in the tree.
tree_matches = tree.query(<<~QUERY)
  (identifier) @identifier
QUERY

# It is equivalent to:
tree.root_node.query(<<~QUERY)
  (identifier) @identifier
QUERY

Parameters:

  • query_string (String)

Returns:



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/tree_stand/node.rb', line 54

def query(query_string)
  ts_query = TreeSitter::Query.new(@tree.parser.ts_language, query_string)
  ts_cursor = TreeSitter::QueryCursor.exec(ts_query, ts_node)
  matches = []
  while ts_match = ts_cursor.next_match
    captures = {}

    ts_match.captures.each do |ts_capture|
      capture_name = ts_query.capture_name_for_id(ts_capture.index)
      captures[capture_name] = TreeStand::Node.new(@tree, ts_capture.node)
    end

    matches << captures
  end
  matches
end

#rangeTreeStand::Range

Returns:



95
96
97
98
99
100
101
102
# File 'lib/tree_stand/node.rb', line 95

def range
  TreeStand::Range.new(
    start_byte: @ts_node.start_byte,
    end_byte: @ts_node.end_byte,
    start_point: @ts_node.start_point,
    end_point: @ts_node.end_point,
  )
end

#textString

A convenience method for getting the text of the node. Each TreeStand::Node wraps the parent #tree and has access to the source document.

Returns:

  • (String)


175
176
177
# File 'lib/tree_stand/node.rb', line 175

def text
  T.must(@tree.document[@ts_node.start_byte...@ts_node.end_byte])
end

#typeSymbol

Returns the type of the node in the tree-sitter grammar.

Returns:

  • (Symbol)

    the type of the node in the tree-sitter grammar.



18
19
20
21
22
# File 'lib/tree_stand/node.rb', line 18

def_delegators(
  :@ts_node,
  :type,
  :error?,
)

#walk(&block) {|node| ... } ⇒ T::Enumerator[TreeStand::Node]

Examples:

Check the subtree for error nodes

node.walk.any? { |node| node.type == :error }

Parameters:

Yield Parameters:

Returns:

See Also:



146
147
148
149
150
151
152
153
154
# File 'lib/tree_stand/node.rb', line 146

def walk(&block)
  enumerator = Enumerator.new do |yielder|
    Visitors::TreeWalker.new(self) do |child|
      yielder << child
    end.visit
  end
  enumerator.each(&block) if block_given?
  enumerator
end