Class: TreeSitter::Node

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/tree_sitter/node.rb

Overview

Node is a wrapper around a tree-sitter node.

Instance Method Summary collapse

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_name, *_args, &_block) ⇒ Object (private)

Allows access to child_by_field_name without using [].



75
76
77
78
79
80
81
# File 'lib/tree_sitter/node.rb', line 75

def method_missing(method_name, *_args, &_block)
  if fields.include?(method_name)
    child_by_field_name(method_name.to_s)
  else
    super
  end
end

Instance Method Details

#[](*keys) ⇒ Node | Array<Node>

Access node’s named children.

It’s similar to #fetch, but differs in input type, return values, and the internal implementation.

Both of these methods exist for separate use cases, but also because sometime tree-sitter does some monkey business and having both separate implementations can help.

Comparison with #fetch:

[]                            | fetch
------------------------------+----------------------

input types Integer, String, Symbol | Array<String, Symbol>

Array<Integer, String, Symbol>|
------------------------------+----------------------

returns 1-to-1 correspondance with | unique nodes

input                         |
------------------------------+----------------------

uses named_child | field_name_for_child

child_by_field_name           |   via each_node
------------------------------+----------------------

Parameters:

  • keys (Integer | String | Symbol | Array<Integer, String, Symbol>, #read)

Returns:



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

def [](*keys)
  case keys.length
  when 0 then raise ArgumentError, "#{self.class.name}##{__method__} requires a key."
  when 1
    case k = keys.first
    when Integer then named_child(k)
    when String, Symbol
      raise IndexError, "Cannot find field #{k.to_sym}. Available: #{fields.to_a}" unless fields.include?(k.to_sym)

      child_by_field_name(k.to_s)
    else raise ArgumentError, <<~ERR
      #{self.class.name}##{__method__} accepts Integer and returns named child at given index,
          or a (String | Symbol) and returns the child by given field name.
    ERR
    end
  else
    keys.map { |key| self[key] }
  end
end

#brk(out, vertical) ⇒ Object

Break helper

!@visibility private



248
249
250
251
252
253
254
# File 'lib/tree_sitter/node.rb', line 248

def brk(out, vertical)
  if vertical
    out.break
  else
    out.breakable
  end
end

#each {|child| ... } ⇒ Object

Iterate over a node’s children.

Yield Parameters:

  • child (Node)

    the child



92
93
94
95
96
97
98
# File 'lib/tree_sitter/node.rb', line 92

def each(&_block)
  return enum_for __method__ if !block_given?

  (0...child_count).each do |i|
    yield child(i)
  end
end

#each_field {|name, child| ... } ⇒ Object

Iterate over a node’s children assigned to a field.

Yield Parameters:

  • name (NilClass | String)

    field name.

  • child (Node)

    the child.



104
105
106
107
108
109
110
111
112
113
# File 'lib/tree_sitter/node.rb', line 104

def each_field
  return enum_for __method__ if !block_given?

  each.with_index do |c, i|
    f = field_name_for_child(i)
    next if f.nil? || f.empty?

    yield f, c
  end
end

#each_named {|child| ... } ⇒ Object

Iterate over a node’s named children

Yield Parameters:

  • child (Node)

    the child



118
119
120
121
122
123
124
# File 'lib/tree_sitter/node.rb', line 118

def each_named
  return enum_for __method__ if !block_given?

  (0...(named_child_count)).each do |i|
    yield named_child(i)
  end
end

#fetch(*keys) ⇒ Object

Access node’s named children.

It’s similar to #[], but differs in input type, return values, and the internal implementation.

Both of these methods exist for separate use cases, but also because sometime tree-sitter does some monkey business and having both separate implementations can help.

Comparison with #fetch:

[]                            | fetch
------------------------------+----------------------

input types Integer, String, Symbol | String, Symbol

Array<Integer, String, Symbol>| Array<String, Symbol>
------------------------------+----------------------

returns 1-to-1 correspondance with | unique nodes

input                         |
------------------------------+----------------------

uses named_child | field_name_for_child

child_by_field_name           |   via each_node
------------------------------+----------------------

See #[].



155
156
157
158
159
160
161
162
163
164
165
# File 'lib/tree_sitter/node.rb', line 155

def fetch(*keys)
  keys = keys.map(&:to_s)
  key_set = keys.to_set
  fields = {}
  each_field do |f, _c|
    fields[f] = self[f] if key_set.delete(f)

    break if key_set.empty?
  end
  fields.values_at(*keys)
end

#field?(field) ⇒ Boolean

Parameters:

  • field (String, Symbol)

Returns:

  • (Boolean)


22
23
24
# File 'lib/tree_sitter/node.rb', line 22

def field?(field)
  fields.include?(field.to_sym)
end

#fieldsArray<Symbol>

Returns the node’s named fields.

Returns:

  • (Array<Symbol>)

    the node’s named fields



9
10
11
12
13
14
15
16
17
18
19
# File 'lib/tree_sitter/node.rb', line 9

def fields
  return @fields if @fields

  @fields = Set.new
  child_count.times do |i|
    name = field_name_for_child(i)
    @fields << name.to_sym if name
  end

  @fields.to_a
end

#sexpr(indent: 2, width: 120, source: nil, vertical: nil) ⇒ String

Pretty-prints the node’s sexp.

The default call to to_s or to_string calls tree-sitter’s ‘ts_node_string`. It’s displayed on a single line, so reading a rich node becomes tiresome.

This provides a better sexpr where you can control the “screen” width to decide when to break.

Parameters:

  • indent (Integer) (defaults to: 2)

    indentation for nested nodes.

  • width (Integer) (defaults to: 120)

    the screen’s width.

  • source (Nil|String) (defaults to: nil)

    display source on the margin if not ‘nil`.

  • vertical (Nil|Boolean) (defaults to: nil)

    fit as much sexpr on a single line if ‘false`, else, go vertical. This is always `true` if `source` is not `nil`.

Returns:

  • (String)

    the pretty-printed sexpr.



192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/tree_sitter/node.rb', line 192

def sexpr(indent: 2, width: 120, source: nil, vertical: nil)
  res =
    sexpr_recur(
      indent: indent,
      width: width,
      source: source,
      vertical: !source.nil? || !!vertical,
    ).output
  return res if source.nil?

  max_width = 0
  res
    .lines
    .map { |line|
      extracted = line.scan(LINE_ANNOTATION).flatten.first || ''
      base = line.gsub(LINE_ANNOTATION, '').rstrip
      max_width = [max_width, base.length].max
      [base, extracted]
    }
    .map { |base, extracted|
      ("%-#{max_width}s | %s" % [base, extracted]).rstrip
    }
    .join("\n")
end

#to_aArray<TreeSitter::Node>

Returns all the node’s children.

Returns:



127
128
129
# File 'lib/tree_sitter/node.rb', line 127

def to_a
  each.to_a
end