ruby_tree_sitter 1.1.0-arm64-darwin-22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +22 -0
  3. data/README.md +199 -0
  4. data/ext/tree_sitter/encoding.c +29 -0
  5. data/ext/tree_sitter/extconf.rb +149 -0
  6. data/ext/tree_sitter/input.c +127 -0
  7. data/ext/tree_sitter/input_edit.c +42 -0
  8. data/ext/tree_sitter/language.c +219 -0
  9. data/ext/tree_sitter/logger.c +228 -0
  10. data/ext/tree_sitter/macros.h +163 -0
  11. data/ext/tree_sitter/node.c +618 -0
  12. data/ext/tree_sitter/parser.c +398 -0
  13. data/ext/tree_sitter/point.c +26 -0
  14. data/ext/tree_sitter/quantifier.c +43 -0
  15. data/ext/tree_sitter/query.c +282 -0
  16. data/ext/tree_sitter/query_capture.c +28 -0
  17. data/ext/tree_sitter/query_cursor.c +215 -0
  18. data/ext/tree_sitter/query_error.c +41 -0
  19. data/ext/tree_sitter/query_match.c +44 -0
  20. data/ext/tree_sitter/query_predicate_step.c +83 -0
  21. data/ext/tree_sitter/range.c +35 -0
  22. data/ext/tree_sitter/repo.rb +121 -0
  23. data/ext/tree_sitter/symbol_type.c +46 -0
  24. data/ext/tree_sitter/tree.c +234 -0
  25. data/ext/tree_sitter/tree_cursor.c +269 -0
  26. data/ext/tree_sitter/tree_sitter.c +44 -0
  27. data/ext/tree_sitter/tree_sitter.h +107 -0
  28. data/lib/tree_sitter/node.rb +197 -0
  29. data/lib/tree_sitter/tree_sitter.bundle +0 -0
  30. data/lib/tree_sitter/version.rb +8 -0
  31. data/lib/tree_sitter.rb +14 -0
  32. data/lib/tree_stand/ast_modifier.rb +30 -0
  33. data/lib/tree_stand/breadth_first_visitor.rb +54 -0
  34. data/lib/tree_stand/config.rb +13 -0
  35. data/lib/tree_stand/node.rb +224 -0
  36. data/lib/tree_stand/parser.rb +67 -0
  37. data/lib/tree_stand/range.rb +55 -0
  38. data/lib/tree_stand/tree.rb +123 -0
  39. data/lib/tree_stand/utils/printer.rb +73 -0
  40. data/lib/tree_stand/version.rb +7 -0
  41. data/lib/tree_stand/visitor.rb +127 -0
  42. data/lib/tree_stand/visitors/tree_walker.rb +37 -0
  43. data/lib/tree_stand.rb +48 -0
  44. data/tree_sitter.gemspec +35 -0
  45. metadata +124 -0
@@ -0,0 +1,44 @@
1
+ #include "tree_sitter.h"
2
+
3
+ VALUE mTreeSitter;
4
+
5
+ void Init_tree_sitter() {
6
+ mTreeSitter = rb_define_module("TreeSitter");
7
+
8
+ /**
9
+ * The latest ABI version that is supported by the current version of the
10
+ * library. When Languages are generated by the Tree-sitter CLI, they are
11
+ * assigned an ABI version number that corresponds to the current CLI version.
12
+ * The Tree-sitter library is generally backwards-compatible with languages
13
+ * generated using older CLI versions, but is not forwards-compatible.
14
+ */
15
+ rb_define_const(mTreeSitter, "LANGUAGE_VERSION",
16
+ INT2NUM(TREE_SITTER_LANGUAGE_VERSION));
17
+
18
+ /**
19
+ * The earliest ABI version that is supported by the current version of the
20
+ * library.
21
+ */
22
+ rb_define_const(mTreeSitter, "MIN_COMPATIBLE_LANGUAGE_VERSION",
23
+ TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION);
24
+
25
+ init_encoding();
26
+ init_input();
27
+ init_input_edit();
28
+ init_language();
29
+ init_logger();
30
+ init_node();
31
+ init_parser();
32
+ init_point();
33
+ init_quantifier();
34
+ init_query();
35
+ init_query_capture();
36
+ init_query_cursor();
37
+ init_query_error();
38
+ init_query_match();
39
+ init_query_predicate_step();
40
+ init_range();
41
+ init_symbol_type();
42
+ init_tree();
43
+ init_tree_cursor();
44
+ }
@@ -0,0 +1,107 @@
1
+ #ifndef _RB_TREE_SITTER_H
2
+ #define _RB_TREE_SITTER_H
3
+
4
+ #include "macros.h"
5
+ #include <dlfcn.h>
6
+ #include <fcntl.h>
7
+ #include <ruby.h>
8
+ #include <stdio.h>
9
+ #include <string.h>
10
+ #include <tree_sitter/api.h>
11
+
12
+ static inline VALUE safe_str(const char *str) {
13
+ if (str == NULL) {
14
+ return Qnil;
15
+ } else {
16
+ return rb_utf8_str_new_cstr(str);
17
+ }
18
+ }
19
+
20
+ static inline VALUE safe_str2(const char *str, uint32_t len) {
21
+ if (str == NULL) {
22
+ return Qnil;
23
+ } else if (len == 0) {
24
+ return rb_utf8_str_new_cstr("");
25
+ } else {
26
+ return rb_utf8_str_new(str, len);
27
+ }
28
+ }
29
+
30
+ static inline VALUE safe_symbol(const char *str) {
31
+ if (str == NULL) {
32
+ return Qnil;
33
+ } else {
34
+ return ID2SYM(rb_intern(str));
35
+ }
36
+ }
37
+
38
+ // VALUE to TS* converters
39
+
40
+ TSInput value_to_input(VALUE);
41
+ TSInputEdit value_to_input_edit(VALUE);
42
+ TSInputEncoding value_to_encoding(VALUE);
43
+ TSLanguage *value_to_language(VALUE);
44
+ TSLogger value_to_logger(VALUE);
45
+ TSNode value_to_node(VALUE);
46
+ TSPoint value_to_point(VALUE);
47
+ TSQuantifier value_to_quantifier(VALUE);
48
+ TSQuery *value_to_query(VALUE);
49
+ TSQueryCursor *value_to_query_cursor(VALUE);
50
+ TSQueryError value_to_query_error(VALUE);
51
+ TSQueryMatch value_to_query_match(VALUE);
52
+ TSQueryPredicateStep value_to_query_predicate_step(VALUE);
53
+ TSQueryPredicateStepType value_to_query_predicate_step_type(VALUE);
54
+ TSRange value_to_range(VALUE);
55
+ TSSymbolType value_to_symbol_type(VALUE);
56
+ TSTree *value_to_tree(VALUE);
57
+ TSTreeCursor value_to_tree_cursor(VALUE);
58
+
59
+ // TS* to VALUE converters
60
+ VALUE new_input(const TSInput *);
61
+ VALUE new_language(const TSLanguage *);
62
+ VALUE new_logger(const TSLogger *);
63
+ VALUE new_logger_by_val(TSLogger);
64
+ VALUE new_node(const TSNode *);
65
+ VALUE new_node_by_val(TSNode);
66
+ VALUE new_point(const TSPoint *);
67
+ VALUE new_point_by_val(TSPoint);
68
+ VALUE new_query_capture(const TSQueryCapture *);
69
+ VALUE new_query_match(const TSQueryMatch *);
70
+ VALUE new_query_predicate_step(const TSQueryPredicateStep *);
71
+ VALUE new_range(const TSRange *);
72
+ VALUE new_symbol_type(TSSymbolType);
73
+ VALUE new_tree(TSTree *);
74
+
75
+ // All init_* functions are called from Init_tree_sitter
76
+ void init_encoding(void);
77
+ void init_input(void);
78
+ void init_input_edit(void);
79
+ void init_language(void);
80
+ void init_logger(void);
81
+ void init_node(void);
82
+ void init_parser(void);
83
+ void init_point(void);
84
+ void init_quantifier(void);
85
+ void init_query(void);
86
+ void init_query_capture(void);
87
+ void init_query_cursor(void);
88
+ void init_query_error(void);
89
+ void init_query_match(void);
90
+ void init_query_predicate_step(void);
91
+ void init_range(void);
92
+ void init_symbol_type(void);
93
+ void init_tree(void);
94
+ void init_tree_cursor(void);
95
+
96
+ // Other helpers
97
+ const char *quantifier_str(TSQuantifier);
98
+ const char *query_error_str(TSQueryError);
99
+
100
+ // TSTree reference counting
101
+ int tree_rc_free(const TSTree *);
102
+ void tree_rc_new(const TSTree *);
103
+
104
+ // This is a special entry-point for the extension
105
+ void Init_tree_sitter(void);
106
+
107
+ #endif
@@ -0,0 +1,197 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TreeSitter
4
+ # Node is a wrapper around a tree-sitter node.
5
+ class Node
6
+ # @return [Array<Symbol>] the node's named fields
7
+ def fields
8
+ return @fields if @fields
9
+
10
+ @fields = Set.new
11
+ child_count.times do |i|
12
+ name = field_name_for_child(i)
13
+ @fields << name.to_sym if name
14
+ end
15
+
16
+ @fields
17
+ end
18
+
19
+ def field?(field)
20
+ fields.include?(field)
21
+ end
22
+
23
+ # FIXME: These APIs (`[]` and `fetch`) need absolute fixing.
24
+ # 1. The documentation with the table doesn't work.
25
+ # 1. The APIs are very confusing! Make them act similarly to Hash's
26
+ # `fetch` and `[]`.
27
+ # 1. `[]` should take a single input and return nil if nothing found
28
+ # (no exceptions).
29
+ # 1. `fetch` should should accept a single argument, potentially a
30
+ # default, and raise exception if no default was provided.
31
+ # Also allow for the `all:` kwarg.
32
+ # 1. `values_at` takes many arguments.
33
+ # And I don't think we can move to 1.0 without adressing them.
34
+ #
35
+ # Access node's named children.
36
+ #
37
+ # It's similar to {#fetch}, but differes in input type, return values, and
38
+ # the internal implementation.
39
+ #
40
+ # Both of these methods exist for separate use cases, but also because
41
+ # sometime tree-sitter does some monkey business and having both separate
42
+ # implementations can help.
43
+ #
44
+ # Comparison with {#fetch}:
45
+ #
46
+ # [] | fetch
47
+ # ------------------------------+----------------------
48
+ # input types Integer, String, Symbol | Array<String, Symbol>
49
+ # Array<Integer, String, Symbol>|
50
+ # ------------------------------+----------------------
51
+ # returns 1-to-1 correspondance with | unique nodes
52
+ # input |
53
+ # ------------------------------+----------------------
54
+ # uses named_child | field_name_for_child
55
+ # child_by_field_name | via each_node
56
+ # ------------------------------+----------------------
57
+ #
58
+ # @param keys [Integer | String | Symbol | Array<Integer, String, Symbol>, #read]
59
+ #
60
+ # @return [Node | Array<Node>]
61
+ def [](*keys)
62
+ case keys.length
63
+ when 0 then raise "#{self.class.name}##{__method__} requires a key."
64
+ when 1
65
+ case k = keys.first
66
+ when Numeric then named_child(k)
67
+ when String, Symbol
68
+ raise "Cannot find field #{k}" unless fields.include?(k.to_sym)
69
+
70
+ child_by_field_name(k.to_s)
71
+ else raise <<~ERR
72
+ #{self.class.name}##{__method__} accepts Integer and returns named child at given index,
73
+ or a (String | Symbol) and returns the child by given field name.
74
+ ERR
75
+ end
76
+ else
77
+ keys.map { |key| self[key] }
78
+ end
79
+ end
80
+
81
+ # @!visibility private
82
+ #
83
+ # Allows access to child_by_field_name without using [].
84
+ def method_missing(method_name, *_args, &_block)
85
+ if fields.include?(method_name)
86
+ child_by_field_name(method_name.to_s)
87
+ else
88
+ super
89
+ end
90
+ end
91
+
92
+ # @!visibility private
93
+ #
94
+ def respond_to_missing?(*args)
95
+ args.length == 1 && fields.include?(args[0])
96
+ end
97
+
98
+ # Iterate over a node's children.
99
+ #
100
+ # @yieldparam child [Node] the child
101
+ def each
102
+ return enum_for __method__ if !block_given?
103
+
104
+ (0...(child_count)).each do |i|
105
+ yield child(i)
106
+ end
107
+ end
108
+
109
+ # Iterate over a node's children assigned to a field.
110
+ #
111
+ # @yieldparam name [NilClass | String] field name.
112
+ # @yieldparam child [Node] the child.
113
+ def each_field
114
+ return enum_for __method__ if !block_given?
115
+
116
+ each.with_index do |c, i|
117
+ f = field_name_for_child(i)
118
+ next if f.nil? || f.empty?
119
+
120
+ yield f, c
121
+ end
122
+ end
123
+
124
+ # Iterate over a node's named children
125
+ #
126
+ # @yieldparam child [Node] the child
127
+ def each_named
128
+ return enum_for __method__ if !block_given?
129
+
130
+ (0...(named_child_count)).each do |i|
131
+ yield named_child(i)
132
+ end
133
+ end
134
+
135
+ # @return [Array<TreeSitter::Node>] all the node's children
136
+ def to_a
137
+ each.to_a
138
+ end
139
+
140
+ # Access node's named children.
141
+ #
142
+ # It's similar to {#fetch}, but differes in input type, return values, and
143
+ # the internal implementation.
144
+ #
145
+ # Both of these methods exist for separate use cases, but also because
146
+ # sometime tree-sitter does some monkey business and having both separate
147
+ # implementations can help.
148
+ #
149
+ # Comparison with {#fetch}:
150
+ #
151
+ # [] | fetch
152
+ # ------------------------------+----------------------
153
+ # input types Integer, String, Symbol | String, Symbol
154
+ # Array<Integer, String, Symbol>| Array<String, Symbol>
155
+ # ------------------------------+----------------------
156
+ # returns 1-to-1 correspondance with | unique nodes
157
+ # input |
158
+ # ------------------------------+----------------------
159
+ # uses named_child | field_name_for_child
160
+ # child_by_field_name | via each_node
161
+ # ------------------------------+----------------------
162
+ # @param all [Boolean] If `true`, return an array of nodes for all the
163
+ # demanded keys, putting `nil` for missing ones. If `false`, return the
164
+ # same array after calling `compact`. Defaults to `false`.
165
+ #
166
+ # See {#fetch_all}.
167
+ def fetch(*keys, all: false, **_kwargs)
168
+ dict = {}
169
+ keys.each.with_index do |k, i|
170
+ dict[k.to_s] = i
171
+ end
172
+
173
+ res = {}
174
+ each_field do |f, c|
175
+ if dict.key?(f)
176
+ res[f] = c
177
+ dict.delete(f)
178
+ end
179
+ break if dict.empty?
180
+ end
181
+
182
+ res = keys.uniq.map { |k| res[k.to_s] }
183
+ res = res.compact if !all
184
+ res
185
+ end
186
+
187
+ # Access all named children of a node, returning `nil` for missing ones.
188
+ #
189
+ # Equivalent to `fetch(…, all: true)`.
190
+ #
191
+ # See {#fetch}.
192
+ def fetch_all(*keys, **kwargs)
193
+ kwargs[:all] = true
194
+ fetch(*keys, **kwargs)
195
+ end
196
+ end
197
+ end
Binary file
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TreeSitter
4
+ # The version of the tree-sitter library.
5
+ TREESITTER_VERSION = '0.22.6'
6
+ # The current version of the gem.
7
+ VERSION = '1.1.0'
8
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ # TreeSitter is a Ruby interface to the tree-sitter parsing library.
4
+ module TreeSitter
5
+ end
6
+
7
+ require 'set'
8
+
9
+ require 'tree_sitter/version'
10
+
11
+ require 'tree_sitter/tree_sitter'
12
+ require 'tree_sitter/node'
13
+
14
+ ObjectSpace.define_finalizer(TreeSitter::Tree.class, proc { TreeSitter::Tree.finalizer })
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ module TreeStand
5
+ # An experimental class to modify the AST. It re-runs the query on the
6
+ # modified document every loop to ensure that the match is still valid.
7
+ # @see TreeStand::Tree
8
+ # @api experimental
9
+ class AstModifier
10
+ extend T::Sig
11
+
12
+ sig { params(tree: TreeStand::Tree).void }
13
+ def initialize(tree)
14
+ @tree = tree
15
+ end
16
+
17
+ # @param query [String]
18
+ # @yieldparam self [self]
19
+ # @yieldparam match [TreeStand::Match]
20
+ # @return [void]
21
+ def on_match(query)
22
+ matches = @tree.query(query)
23
+
24
+ while !matches.empty?
25
+ yield self, matches.first
26
+ matches = @tree.query(query)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ module TreeStand
5
+ # Breadth-first traversal through the tree, calling hooks at each stop.
6
+ class BreadthFirstVisitor
7
+ extend T::Sig
8
+
9
+ sig { params(node: TreeStand::Node).void }
10
+ def initialize(node)
11
+ @node = node
12
+ end
13
+
14
+ # Run the visitor on the document and return self. Allows chaining create and visit.
15
+ # @example
16
+ # visitor = CountingVisitor.new(node, :predicate).visit
17
+ sig { returns(T.self_type) }
18
+ def visit
19
+ queue = [@node]
20
+ visit_node(queue) while queue.any?
21
+ self
22
+ end
23
+
24
+ # @abstract The default implementation does nothing.
25
+ sig { overridable.params(node: TreeStand::Node).void }
26
+ def on(node) = nil
27
+
28
+ # @abstract The default implementation yields to visit all children.
29
+ sig { overridable.params(node: TreeStand::Node, block: T.proc.void).void }
30
+ def around(node, &block) = yield
31
+
32
+ private
33
+
34
+ def visit_node(queue)
35
+ node = queue.shift
36
+
37
+ if respond_to?("on_#{node.type}")
38
+ public_send("on_#{node.type}", node)
39
+ else
40
+ on(node)
41
+ end
42
+
43
+ if respond_to?("around_#{node.type}")
44
+ public_send("around_#{node.type}", node) do
45
+ node.each { |child| queue << child }
46
+ end
47
+ else
48
+ around(node) do
49
+ node.each { |child| queue << child }
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ module TreeStand
5
+ # Global configuration for the gem.
6
+ # @api private
7
+ class Config
8
+ extend T::Sig
9
+
10
+ sig { returns(String) }
11
+ attr_accessor :parser_path
12
+ end
13
+ end
@@ -0,0 +1,224 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ module TreeStand
5
+ # Wrapper around a TreeSitter node and provides convient
6
+ # methods that are missing on the original node. This class
7
+ # overrides the `method_missing` method to delegate to a nodes
8
+ # named children.
9
+ class Node
10
+ extend T::Sig
11
+ extend Forwardable
12
+ include Enumerable
13
+
14
+ # @!method type
15
+ # @return [Symbol] the type of the node in the tree-sitter grammar.
16
+ # @!method error?
17
+ # @return [bool] true if the node is an error node.
18
+ def_delegators(
19
+ :@ts_node,
20
+ :type,
21
+ :error?,
22
+ )
23
+
24
+ sig { returns(TreeStand::Tree) }
25
+ attr_reader :tree
26
+
27
+ sig { returns(TreeSitter::Node) }
28
+ attr_reader :ts_node
29
+
30
+ # @api private
31
+ sig { params(tree: TreeStand::Tree, ts_node: TreeSitter::Node).void }
32
+ def initialize(tree, ts_node)
33
+ @tree = tree
34
+ @ts_node = ts_node
35
+ @fields = @ts_node.each_field.to_a.map(&:first)
36
+ end
37
+
38
+ # TreeSitter uses a `TreeSitter::Cursor` to iterate over matches by calling
39
+ # `curser#next_match` repeatedly until it returns `nil`.
40
+ #
41
+ # This method does all of that for you and collects all of the matches into
42
+ # an array and each corresponding capture into a hash.
43
+ #
44
+ # @example
45
+ # # This will return a match for each identifier nodes in the tree.
46
+ # tree_matches = tree.query(<<~QUERY)
47
+ # (identifier) @identifier
48
+ # QUERY
49
+ #
50
+ # # It is equivalent to:
51
+ # tree.root_node.query(<<~QUERY)
52
+ # (identifier) @identifier
53
+ # QUERY
54
+ sig { params(query_string: String).returns(T::Array[T::Hash[String, TreeStand::Node]]) }
55
+ def query(query_string)
56
+ ts_query = TreeSitter::Query.new(@tree.parser.ts_language, query_string)
57
+ ts_cursor = TreeSitter::QueryCursor.exec(ts_query, ts_node)
58
+ matches = []
59
+ while ts_match = ts_cursor.next_match
60
+ captures = {}
61
+
62
+ ts_match.captures.each do |ts_capture|
63
+ capture_name = ts_query.capture_name_for_id(ts_capture.index)
64
+ captures[capture_name] = TreeStand::Node.new(@tree, ts_capture.node)
65
+ end
66
+
67
+ matches << captures
68
+ end
69
+ matches
70
+ end
71
+
72
+ # Returns the first captured node that matches the query string or nil if
73
+ # there was no captured node.
74
+ #
75
+ # @example Find the first identifier node.
76
+ # identifier_node = tree.root_node.find_node("(identifier) @identifier")
77
+ #
78
+ # @see #find_node!
79
+ # @see #query
80
+ sig { params(query_string: String).returns(T.nilable(TreeStand::Node)) }
81
+ def find_node(query_string)
82
+ query(query_string).first&.values&.first
83
+ end
84
+
85
+ # Like {#find_node}, except that if no node is found, raises an
86
+ # {TreeStand::NodeNotFound} error.
87
+ #
88
+ # @see #find_node
89
+ # @raise [TreeStand::NodeNotFound]
90
+ sig { params(query_string: String).returns(TreeStand::Node) }
91
+ def find_node!(query_string)
92
+ find_node(query_string) || raise(TreeStand::NodeNotFound)
93
+ end
94
+
95
+ sig { returns(TreeStand::Range) }
96
+ def range
97
+ TreeStand::Range.new(
98
+ start_byte: @ts_node.start_byte,
99
+ end_byte: @ts_node.end_byte,
100
+ start_point: @ts_node.start_point,
101
+ end_point: @ts_node.end_point,
102
+ )
103
+ end
104
+
105
+ # Node includes enumerable so that you can iterate over the child nodes.
106
+ # @example
107
+ # node.text # => "3 * 4"
108
+ #
109
+ # @example Iterate over the child nodes
110
+ # node.each do |child|
111
+ # print child.text
112
+ # end
113
+ # # prints: 3*4
114
+ #
115
+ # @example Enumerable methods
116
+ # node.map(&:text) # => ["3", "*", "4"]
117
+ #
118
+ # @yieldparam child [TreeStand::Node]
119
+ sig do
120
+ override
121
+ .params(block: T.nilable(T.proc.params(node: TreeStand::Node).returns(BasicObject)))
122
+ .returns(T::Enumerator[TreeStand::Node])
123
+ end
124
+ def each(&block)
125
+ enumerator = Enumerator.new do |yielder|
126
+ @ts_node.each do |child|
127
+ yielder << TreeStand::Node.new(@tree, child)
128
+ end
129
+ end
130
+ enumerator.each(&block) if block_given?
131
+ enumerator
132
+ end
133
+
134
+ # (see TreeStand::Visitors::TreeWalker)
135
+ # Backed by {TreeStand::Visitors::TreeWalker}.
136
+ #
137
+ # @example Check the subtree for error nodes
138
+ # node.walk.any? { |node| node.type == :error }
139
+ #
140
+ # @see TreeStand::Visitors::TreeWalker
141
+ #
142
+ # @yieldparam node [TreeStand::Node]
143
+ sig do
144
+ params(block: T.nilable(T.proc.params(node: TreeStand::Node).returns(BasicObject)))
145
+ .returns(T::Enumerator[TreeStand::Node])
146
+ end
147
+ def walk(&block)
148
+ enumerator = Enumerator.new do |yielder|
149
+ Visitors::TreeWalker.new(self) do |child|
150
+ yielder << child
151
+ end.visit
152
+ end
153
+ enumerator.each(&block) if block_given?
154
+ enumerator
155
+ end
156
+
157
+ # @example
158
+ # node.text # => "4"
159
+ # node.parent.text # => "3 * 4"
160
+ # node.parent.parent.text # => "1 + 3 * 4"
161
+ sig { returns(TreeStand::Node) }
162
+ def parent
163
+ TreeStand::Node.new(@tree, @ts_node.parent)
164
+ end
165
+
166
+ # @example
167
+ # node.text # => "3 * 4"
168
+ # node.to_a.map(&:text) # => ["3", "*", "4"]
169
+ # node.children.map(&:text) # => ["3", "*", "4"]
170
+ sig { returns(T::Array[TreeStand::Node]) }
171
+ def children = to_a
172
+
173
+ # A convenience method for getting the text of the node. Each {TreeStand::Node}
174
+ # wraps the parent {TreeStand::Tree #tree} and has access to the source document.
175
+ sig { returns(String) }
176
+ def text
177
+ T.must(@tree.document[@ts_node.start_byte...@ts_node.end_byte])
178
+ end
179
+
180
+ # This class overrides the `method_missing` method to delegate to the
181
+ # node's named children.
182
+ # @example
183
+ # node.text # => "3 * 4"
184
+ #
185
+ # node.left.text # => "3"
186
+ # node.operator.text # => "*"
187
+ # node.right.text # => "4"
188
+ # node.operand # => NoMethodError
189
+ # @overload method_missing(field_name)
190
+ # @param name [Symbol, String]
191
+ # @return [TreeStand::Node] child node for the given field name
192
+ # @raise [NoMethodError] Raised if the node does not have child with name `field_name`
193
+ #
194
+ # @overload method_missing(method_name, *args, &block)
195
+ # @raise [NoMethodError]
196
+ def method_missing(method, *args, &block)
197
+ return super unless @fields.include?(method.to_s)
198
+
199
+ TreeStand::Node.new(@tree, T.unsafe(@ts_node).public_send(method, *args, &block))
200
+ end
201
+
202
+ sig { params(other: Object).returns(T::Boolean) }
203
+ def ==(other)
204
+ return false unless other.is_a?(TreeStand::Node)
205
+
206
+ T.must(range == other.range && type == other.type && text == other.text)
207
+ end
208
+
209
+ # (see TreeStand::Utils::Printer)
210
+ # Backed by {TreeStand::Utils::Printer}.
211
+ #
212
+ # @see TreeStand::Utils::Printer
213
+ sig { params(pp: PP).void }
214
+ def pretty_print(pp)
215
+ Utils::Printer.new(ralign: 80).print(self, io: pp.output)
216
+ end
217
+
218
+ private
219
+
220
+ def respond_to_missing?(method, *)
221
+ @fields.include?(method.to_s) || super
222
+ end
223
+ end
224
+ end