Class: TreeSitter::Node

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

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) ⇒ 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, &)
  if fields.include?(method_name)
    child_by_field_name(method_name.to_s)
  else
    super
  end
end

Instance Method Details

#==Object

#[](*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



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

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

#changed?Boolean

Check if a syntax node has been edited.

Returns:

  • (Boolean)


71
72
73
# File 'ext/tree_sitter/node.c', line 71

static VALUE node_has_changes(VALUE self) {
  return ts_node_has_changes(SELF) ? Qtrue : Qfalse;
}

#child(idx) ⇒ Node

Get the node’s child at the given index, where zero represents the first child.

Returns:

Raises:

  • (IndexError)

    if out of range.



142
143
144
145
146
147
148
149
150
151
152
153
# File 'ext/tree_sitter/node.c', line 142

static VALUE node_child(VALUE self, VALUE idx) {
  TSNode node = SELF;
  uint32_t index = NUM2UINT(idx);
  uint32_t range = ts_node_child_count(node);

  if (index < range) {
    return new_node_by_val(ts_node_child(node, index));
  } else {
    rb_raise(rb_eIndexError, "Index %d is out of range (len = %d)", index,
             range);
  }
}

#child_by_field_id(field_id) ⇒ Node

Get the node’s child with the given numerical field id.

You can convert a field name to an id using Language#field_id_for_name.

Returns:



162
163
164
# File 'ext/tree_sitter/node.c', line 162

static VALUE node_child_by_field_id(VALUE self, VALUE field_id) {
  return new_node_by_val(ts_node_child_by_field_id(SELF, NUM2UINT(field_id)));
}

#child_by_field_name(field_name) ⇒ Node

Get the node’s child with the given field name.

Parameters:

  • field_name (String, Symbol)

Returns:



173
174
175
176
177
178
179
180
181
182
# File 'ext/tree_sitter/node.c', line 173

static VALUE node_child_by_field_name(VALUE self, VALUE field_name) {
  if (Qtrue == rb_funcall(self, rb_intern("field?"), 1, field_name)) {
    VALUE field_str = rb_funcall(field_name, rb_intern("to_s"), 0);
    const char *name = StringValuePtr(field_str);
    uint32_t length = (uint32_t)RSTRING_LEN(field_str);
    return new_node_by_val(ts_node_child_by_field_name(SELF, name, length));
  } else {
    return Qnil;
  }
}

#child_countInteger

Get the node’s number of children.

Returns:

  • (Integer)


189
190
191
192
193
194
195
196
197
# File 'ext/tree_sitter/node.c', line 189

static VALUE node_child_count(VALUE self) {
  TSNode node = SELF;
  const char *type = ts_node_type(node);
  if (strcmp(type, "end") == 0) {
    return UINT2NUM(0);
  } else {
    return UINT2NUM(ts_node_child_count(SELF));
  }
}

#descendant_countInteger

Get the node’s number of descendants, including one for the node itself.

Returns:

  • (Integer)


204
205
206
# File 'ext/tree_sitter/node.c', line 204

VALUE node_descendant_count(VALUE self) {
  return UINT2NUM(ts_node_descendant_count(SELF));
}

#descendant_for_byte_range(from, to) ⇒ Node

Get the smallest node within this node that spans the given range of byte positions.

Parameters:

  • from (Integer)
  • to (Integer)

Returns:

Raises:

  • (IndexError)

    if out of range.



219
220
221
222
223
224
225
226
227
228
229
# File 'ext/tree_sitter/node.c', line 219

static VALUE node_descendant_for_byte_range(VALUE self, VALUE from, VALUE to) {
  uint32_t from_b = NUM2UINT(from);
  uint32_t to_b = NUM2UINT(to);

  if (from_b > to_b) {
    rb_raise(rb_eIndexError, "From > To: %d > %d", from_b, to_b);
  } else {
    return new_node_by_val(
        ts_node_descendant_for_byte_range(SELF, from_b, to_b));
  }
}

#descendant_for_point_range(from, to) ⇒ Node

Get the smallest node within this node that spans the given range of (row, column) positions.

Parameters:

Returns:

Raises:

  • (IndexError)

    if out of range.



242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
# File 'ext/tree_sitter/node.c', line 242

static VALUE node_descendant_for_point_range(VALUE self, VALUE from, VALUE to) {
  TSNode node = SELF;
  TSPoint start = ts_node_start_point(node);
  TSPoint end = ts_node_end_point(node);
  TSPoint f = value_to_point(from);
  TSPoint t = value_to_point(to);

  if ((f.row < start.row) || (t.row > end.row) ||
      (f.row == start.row && (f.column < start.column)) ||
      (t.row == end.row && (t.column > end.column))) {
    rb_raise(rb_eIndexError,
             "Invalid point range: [%+" PRIsVALUE ", %+" PRIsVALUE
             "] is not in [%+" PRIsVALUE ", %+" PRIsVALUE "].",
             from, to, new_point(&start), new_point(&end));
  } else {
    return new_node_by_val(ts_node_descendant_for_point_range(node, f, t));
  }
}

#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(&)
  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

#edit(input_edit) ⇒ nil

Edit the node to keep it in-sync with source code that has been edited.

This function is only rarely needed. When you edit a syntax tree with the Tree#edit function, all of the nodes that you retrieve from the tree afterward will already reflect the edit. You only need to use #edit when you have a TreeSitter::Node instance that you want to keep and continue to use after an edit.

Parameters:

Returns:

  • (nil)


274
275
276
277
278
279
280
# File 'ext/tree_sitter/node.c', line 274

static VALUE node_edit(VALUE self, VALUE input_edit) {
  TSNode node = SELF;
  TSInputEdit edit = value_to_input_edit(input_edit);
  ts_node_edit(&node, &edit);

  return Qnil;
}

#end_byteInteger

Get the node’s end byte.

Returns:

  • (Integer)


287
288
289
# File 'ext/tree_sitter/node.c', line 287

static VALUE node_end_byte(VALUE self) {
  return UINT2NUM(ts_node_end_byte(SELF));
}

#end_pointPoint

Get the node’s end position in terms of rows and columns.

Returns:



296
297
298
# File 'ext/tree_sitter/node.c', line 296

static VALUE node_end_point(VALUE self) {
  return new_point_by_val(ts_node_end_point(SELF));
}

#eq?Boolean

Builtins

Returns:

  • (Boolean)

#error?Boolean

Check if the node is a syntax error.

Returns:

  • (Boolean)


89
90
91
# File 'ext/tree_sitter/node.c', line 89

VALUE node_is_error(VALUE self) {
  return ts_node_is_error(SELF) ? Qtrue : Qfalse;
}

#extra?Boolean

Check if the node is extra. Extra nodes represent things like comments, which are not required the grammar, but can appear anywhere.

Returns:

  • (Boolean)


121
122
123
# File 'ext/tree_sitter/node.c', line 121

static VALUE node_is_extra(VALUE self) {
  return ts_node_is_extra(SELF) ? Qtrue : Qfalse;
}

#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

#field_name_for_child(idx) ⇒ String

Get the field name for node’s child at the given index, where zero represents the first child.

Returns:

  • (String)

Raises:

  • (IndexError)

    if out of range.



308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
# File 'ext/tree_sitter/node.c', line 308

static VALUE node_field_name_for_child(VALUE self, VALUE idx) {
  // FIXME: the original API returns nil if no name was found, but I made it
  // raise an exception like `node_child` for consistency. The latter was made
  // this way to avoid segfault. Should we absolutely stick to the original API?
  TSNode node = SELF;
  uint32_t index = NUM2UINT(idx);
  uint32_t range = ts_node_child_count(node);

  if (index < range) {
    return safe_str(ts_node_field_name_for_child(node, index));
  } else {
    rb_raise(rb_eIndexError, "Index %d is out of range (len = %d)", index,
             range);
  }
}

#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

#first_child_for_byte(byte) ⇒ Node

Get the node’s first child that extends beyond the given byte offset.

Parameters:

  • byte (Integer)

Returns:



331
332
333
# File 'ext/tree_sitter/node.c', line 331

static VALUE node_first_child_for_byte(VALUE self, VALUE byte) {
  return new_node_by_val(ts_node_first_child_for_byte(SELF, NUM2UINT(byte)));
}

#first_named_child_for_byte(byte) ⇒ Node

Get the node’s first named child that extends beyond the given byte offset.

Parameters:

  • byte (Integer)

Returns:



342
343
344
345
# File 'ext/tree_sitter/node.c', line 342

static VALUE node_first_named_child_for_byte(VALUE self, VALUE byte) {
  return new_node_by_val(
      ts_node_first_named_child_for_byte(SELF, NUM2UINT(byte)));
}

#grammar_symbolInteger

Get the node’s type as a numerical id as it appears in the grammar ignoring aliases. This should be used in Language#next_state instead of #symbol.

Returns:

  • (Integer)


354
355
356
# File 'ext/tree_sitter/node.c', line 354

VALUE node_grammar_symbol(VALUE self) {
  return UINT2NUM(ts_node_grammar_symbol(SELF));
}

#grammar_typeObject

Get the node’s type as it appears in the grammar ignoring aliases as a null-terminated string.

Returns:

  • String



364
365
366
# File 'ext/tree_sitter/node.c', line 364

VALUE node_grammar_type(VALUE self) {
  return safe_str(ts_node_grammar_type(SELF));
}

#has_error?Boolean

Check if the node is a syntax error or contains any syntax errors.

Returns:

  • (Boolean)


80
81
82
# File 'ext/tree_sitter/node.c', line 80

static VALUE node_has_error(VALUE self) {
  return ts_node_has_error(SELF) ? Qtrue : Qfalse;
}

#inspectString

Get an S-expression representing the node as a string.

Returns:

  • (String)


57
58
59
60
61
62
63
64
# File 'ext/tree_sitter/node.c', line 57

static VALUE node_string(VALUE self) {
  char *str = ts_node_string(SELF);
  VALUE res = safe_str(str);
  if (str) {
    free(str);
  }
  return res;
}

#languageLanguage

Get the node’s language.

Returns:



373
374
375
# File 'ext/tree_sitter/node.c', line 373

static VALUE node_language(VALUE self) {
  return new_language(ts_node_language(SELF));
}

#missing?Boolean

Check if the node is missing. Missing nodes are inserted by the parser in order to recover from certain kinds of syntax errors.

Returns:

  • (Boolean)


130
131
132
# File 'ext/tree_sitter/node.c', line 130

static VALUE node_is_missing(VALUE self) {
  return ts_node_is_missing(SELF) ? Qtrue : Qfalse;
}

#named?Boolean

Check if the node is named. Named nodes correspond to named rules in the grammar, whereas anonymous nodes correspond to string literals in the grammar.

Returns:

  • (Boolean)


100
101
102
# File 'ext/tree_sitter/node.c', line 100

static VALUE node_is_named(VALUE self) {
  return ts_node_is_named(SELF) ? Qtrue : Qfalse;
}

#named_child(idx) ⇒ Node

Get the node’s named child at the given index.

Parameters:

  • idx (Integer)

Returns:

Raises:

  • (IndexError)

    if out of range.

See Also:



444
445
446
447
448
449
450
451
452
453
454
455
456
# File 'ext/tree_sitter/node.c', line 444

static VALUE node_named_child(VALUE self, VALUE idx) {
  // FIXME: see notes in `node_field_name_for_child`
  TSNode node = SELF;
  uint32_t index = NUM2UINT(idx);
  uint32_t range = ts_node_named_child_count(node);

  if (index < range) {
    return new_node_by_val(ts_node_named_child(node, index));
  } else {
    rb_raise(rb_eIndexError, "Index %d is out of range (len = %d)", index,
             range);
  }
}

#named_child_countInteger

Get the node’s number of named children.

Returns:

  • (Integer)

See Also:



465
466
467
# File 'ext/tree_sitter/node.c', line 465

static VALUE node_named_child_count(VALUE self) {
  return UINT2NUM(ts_node_named_child_count(SELF));
}

#named_descendant_for_byte_range(from, to) ⇒ Node

Get the smallest named node within this node that spans the given range of byte positions.

Parameters:

  • from (Integer)
  • to (Integer)

Returns:

Raises:

  • (IndexError)

    if out of range.



388
389
390
391
392
393
394
395
396
397
398
399
# File 'ext/tree_sitter/node.c', line 388

static VALUE node_named_descendant_for_byte_range(VALUE self, VALUE from,
                                                  VALUE to) {
  uint32_t from_b = NUM2UINT(from);
  uint32_t to_b = NUM2UINT(to);

  if (from_b > to_b) {
    rb_raise(rb_eIndexError, "From > To: %d > %d", from_b, to_b);
  } else {
    return new_node_by_val(
        ts_node_named_descendant_for_byte_range(SELF, from_b, to_b));
  }
}

#named_descendant_for_point_range(from, to) ⇒ Node

Get the smallest named node within this node that spans the given range of (row, column) positions.

Parameters:

Returns:

Raises:

  • (IndexError)

    if out of range.



412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
# File 'ext/tree_sitter/node.c', line 412

static VALUE node_named_descendant_for_point_range(VALUE self, VALUE from,
                                                   VALUE to) {
  TSNode node = SELF;
  TSPoint start = ts_node_start_point(node);
  TSPoint end = ts_node_end_point(node);
  TSPoint f = value_to_point(from);
  TSPoint t = value_to_point(to);

  if ((f.row < start.row) || (t.row > end.row) ||
      (f.row == start.row && (f.column < start.column)) ||
      (t.row == end.row && (t.column > end.column))) {
    rb_raise(rb_eIndexError,
             "Invalid point range: [%+" PRIsVALUE ", %+" PRIsVALUE
             "] is not in [%+" PRIsVALUE ", %+" PRIsVALUE "].",
             from, to, new_point(&start), new_point(&end));
  } else {
    return new_node_by_val(
        ts_node_named_descendant_for_point_range(node, f, t));
  }
}

#next_named_siblingNode

Get the node’s next named sibling.

Returns:



474
475
476
# File 'ext/tree_sitter/node.c', line 474

static VALUE node_next_named_sibling(VALUE self) {
  return new_node_by_val(ts_node_next_named_sibling(SELF));
}

#next_parse_stateInteger

Get the parse state after this node.

Returns:

  • (Integer)


492
493
494
# File 'ext/tree_sitter/node.c', line 492

VALUE node_next_parse_state(VALUE self) {
  return UINT2NUM(ts_node_next_parse_state(SELF));
}

#next_siblingNode

Get the node’s next sibling.

Returns:



483
484
485
# File 'ext/tree_sitter/node.c', line 483

static VALUE node_next_sibling(VALUE self) {
  return new_node_by_val(ts_node_next_sibling(SELF));
}

#null?Boolean

Check if the node is null. Functions like #child and #next_sibling will return a null node to indicate that no such node was found.

Returns:

  • (Boolean)


111
112
113
# File 'ext/tree_sitter/node.c', line 111

static VALUE node_is_null(VALUE self) {
  return ts_node_is_null(SELF) ? Qtrue : Qfalse;
}

#parentNode

Get the node’s immediate parent.

Returns:



501
502
503
# File 'ext/tree_sitter/node.c', line 501

static VALUE node_parent(VALUE self) {
  return new_node_by_val(ts_node_parent(SELF));
}

#parse_stateInteger

Get this node’s parse state.

Returns:

  • (Integer)


546
547
548
# File 'ext/tree_sitter/node.c', line 546

VALUE node_parse_state(VALUE self) {
  return UINT2NUM(ts_node_parse_state(SELF));
}

#prev_named_siblingNode

Get the node’s previous named sibling.

Returns:



510
511
512
# File 'ext/tree_sitter/node.c', line 510

static VALUE node_prev_named_sibling(VALUE self) {
  return new_node_by_val(ts_node_prev_named_sibling(SELF));
}

#prev_siblingNode

Get the node’s previous sibling.

Returns:



519
520
521
# File 'ext/tree_sitter/node.c', line 519

static VALUE node_prev_sibling(VALUE self) {
  return new_node_by_val(ts_node_prev_sibling(SELF));
}

#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
# File 'lib/tree_sitter/node.rb', line 192

def sexpr(indent: 2, width: 120, source: nil, vertical: nil)
  res =
    sexpr_recur(
      indent:,
      width:,
      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

#start_byteInteger

Get the node’s start byte.

Returns:

  • (Integer)


528
529
530
# File 'ext/tree_sitter/node.c', line 528

static VALUE node_start_byte(VALUE self) {
  return UINT2NUM(ts_node_start_byte(SELF));
}

#start_pointPoint

Get the node’s start position in terms of rows and columns.

Returns:



537
538
539
# File 'ext/tree_sitter/node.c', line 537

static VALUE node_start_point(VALUE self) {
  return new_point_by_val(ts_node_start_point(SELF));
}

#symbolInteger

Get the node’s type as a numerical id.

Returns:

  • (Integer)


555
# File 'ext/tree_sitter/node.c', line 555

static VALUE node_symbol(VALUE self) { return UINT2NUM(ts_node_symbol(SELF)); }

#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

#to_sString

Get an S-expression representing the node as a string.

Returns:

  • (String)


57
58
59
60
61
62
63
64
# File 'ext/tree_sitter/node.c', line 57

static VALUE node_string(VALUE self) {
  char *str = ts_node_string(SELF);
  VALUE res = safe_str(str);
  if (str) {
    free(str);
  }
  return res;
}

#to_strString

Get an S-expression representing the node as a string.

Returns:

  • (String)


57
58
59
60
61
62
63
64
# File 'ext/tree_sitter/node.c', line 57

static VALUE node_string(VALUE self) {
  char *str = ts_node_string(SELF);
  VALUE res = safe_str(str);
  if (str) {
    free(str);
  }
  return res;
}

#typeSymbol

Get the node’s type as a null-terminated string.

Returns:

  • (Symbol)


562
# File 'ext/tree_sitter/node.c', line 562

static VALUE node_type(VALUE self) { return safe_symbol(ts_node_type(SELF)); }