tree_stand 0.1.2 → 0.1.5

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c800b4009161889917105434b7f93ec8f75e678edf643cc4705e9a4e98da8aef
4
- data.tar.gz: 6313e2c9035433aae6023ffa1d49fff987ae4d13addcfe097684e4a73c2ceab5
3
+ metadata.gz: 14e5005eed3acbb6399f68c0edf358ba15f4565eb4d99e20652158dbd1c2594e
4
+ data.tar.gz: 8fee6e5e40b9db6adcd72cf72e2705124b9a7e6fcc90ad4673600c1f5074cd69
5
5
  SHA512:
6
- metadata.gz: d4c2690b25d485b36e0e1d9718295dd7d5e941fa40cb127fc908ab8b11b2fd5bab94d0d396f0d0db6955dd9299c7750364d747246fcd514466a2b3570bff4632
7
- data.tar.gz: acc8c9ef231e3c4a05f64c33c26871a9ca7cbb41b54319ab43db2f99dd9bc3fc3c263c038315df751cbf7595528fd814340ad6e26f149eee3354586c77bd072a
6
+ metadata.gz: 1801ffc4ecfa3e7693009267a8a22225f53d04668ca5ddf4c59ba7e0bd2fc5c9e5b5ecfde771aa9631911bd19a0d0e198abcfd18cfe476d3c64ffb7df4480662
7
+ data.tar.gz: 1fec3247f8836c5bf8175530efe0cb51e692e922c431d2f7d81b4f7dd22d895849cfe1af57996c4dff4a18a29bfdecbd30d0bdba24a94dffc4540609f24c3830
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,65 @@
1
+ # Contributing to TreeStand
2
+
3
+ ## Getting Started
4
+
5
+ To work on this gem you'll need the tree-sitter CLI tool. See the [offical
6
+ documentation](https://github.com/tree-sitter/tree-sitter/blob/master/cli/README.md#tree-sitter-cli) for installation
7
+ instructions.
8
+
9
+ Clone the repository and run the setup script.
10
+
11
+ ```
12
+ git clone https://github.com/Shopify/tree_stand.git
13
+ bin/setup
14
+ ```
15
+
16
+ ### Testing
17
+
18
+ ```
19
+ bundle exec rake test
20
+ ```
21
+
22
+ ### Documentation
23
+
24
+ To run the documentation server, execute the following command and open [localhost:8808](http://localhost:8808).
25
+
26
+ ```
27
+ $ bundle exec yard server --reload
28
+ ```
29
+
30
+ To get statistics about documentation coverage and which items are missing documentation run the following command.
31
+
32
+ ```
33
+ $ bundle exec yard stats --list-undoc
34
+ Files: 10
35
+ Modules: 2 ( 0 undocumented)
36
+ Classes: 11 ( 0 undocumented)
37
+ Constants: 1 ( 0 undocumented)
38
+ Attributes: 14 ( 0 undocumented)
39
+ Methods: 34 ( 0 undocumented)
40
+ 100.00% documented
41
+ ```
42
+
43
+ ## Pushing a new Version
44
+
45
+ Create a new PR to bump the version number in `lib/tree_stand/version.rb`. See
46
+ [github://Shopify/tree_stand#18](https://github.com/Shopify/tree_stand/pull/18) for an example.
47
+
48
+ ```ruby
49
+ $ cat lib/tree_stand/version.rb
50
+ module TreeStand
51
+ # The current version of the gem.
52
+ VERSION = "0.1.5"
53
+ end
54
+ ```
55
+
56
+ Once that PR is merged, tag the latest commit with the format `v#{TreeStand::VERSION}` and push the new tag.
57
+
58
+ ```
59
+ git tag v0.1.5
60
+ git push --tags
61
+ ```
62
+
63
+ Draft a new Release [on Github](https://github.com/Shopify/tree_stand/releases).
64
+
65
+ Finally, we use [shipit](https://github.com/Shopify/shipit-engine) to push gems to rubygems.
data/bin/console CHANGED
@@ -3,12 +3,20 @@
3
3
  require "bundler/setup"
4
4
  require "tree_stand"
5
5
 
6
- # You can add fixtures and/or initialization code here to make experimenting
7
- # with your gem easier. You can also use a different console, if you like.
6
+ TreeStand.configure do
7
+ config.parser_path = File.expand_path(
8
+ File.join(__dir__, "..", "parsers")
9
+ )
10
+ end
8
11
 
9
- # (If you use this, don't forget to add pry to your Gemfile!)
10
- # require "pry"
11
- # Pry.start
12
+ ivars_to_add = <<~RUBY
13
+ @parser = TreeStand::Parser.new("math")
14
+ @tree = @parser.parse_string(nil, "1 + x")
15
+ RUBY
12
16
 
13
- require "irb"
14
- IRB.start(__FILE__)
17
+ eval(ivars_to_add)
18
+ puts("available ivars:")
19
+ puts(ivars_to_add)
20
+
21
+ require "pry"
22
+ Pry.start
data/bin/setup CHANGED
@@ -6,15 +6,16 @@ set -vx
6
6
  bundle install
7
7
 
8
8
  # Do any other automated setup that you need to do here
9
- if [[ ! -d tmp/tree-sitter-sql ]]; then
9
+ if [[ ! -d tmp/tree-sitter-math ]]; then
10
10
  mkdir -p tmp
11
- git -C tmp/ clone --depth=1 https://github.com/DerekStride/tree-sitter-sql.git
11
+ git -C tmp/ clone --depth=1 https://github.com/DerekStride/tree-sitter-math.git
12
12
  fi
13
13
 
14
- cd tmp/tree-sitter-sql
14
+ cd tmp/tree-sitter-math
15
15
 
16
16
  npm install
17
+ mkdir -p target
17
18
  gcc -shared -o target/parser.so -fPIC src/parser.c -I./src
18
19
 
19
20
  cd ../..
20
- cp tmp/tree-sitter-sql/target/parser.so parsers/sql.so
21
+ cp tmp/tree-sitter-math/target/parser.so parsers/math.so
@@ -7,13 +7,29 @@ module TreeStand
7
7
  extend Forwardable
8
8
  include Enumerable
9
9
 
10
- def_delegators :@ts_node, :type, :start_byte, :end_byte, :start_point, :end_point
10
+ # @!method type
11
+ # @return [Symbol] the type of the node in the tree-sitter grammar.
12
+ # @!method error?
13
+ # @return [bool] true if the node is an error node.
14
+ def_delegators(
15
+ :@ts_node,
16
+ :type,
17
+ :error?,
18
+ )
11
19
 
12
20
  # @return [TreeStand::Tree]
13
21
  attr_reader :tree
14
22
  # @return [TreeSitter::Node]
15
23
  attr_reader :ts_node
16
24
 
25
+ # @!method to_a
26
+ # @example
27
+ # node.text # => "3 * 4"
28
+ # node.to_a.map(&:text) # => ["3", "*", "4"]
29
+ # node.children.map(&:text) # => ["3", "*", "4"]
30
+ # @return [Array<TreeStand::Node>]
31
+ alias_method :children, :to_a
32
+
17
33
  # @api private
18
34
  def initialize(tree, ts_node)
19
35
  @tree = tree
@@ -21,6 +37,67 @@ module TreeStand
21
37
  @fields = @ts_node.each_field.to_a.map(&:first)
22
38
  end
23
39
 
40
+ # TreeSitter uses a `TreeSitter::Cursor` to iterate over matches by calling
41
+ # `curser#next_match` repeatedly until it returns `nil`.
42
+ #
43
+ # This method does all of that for you and collects all of the matches into
44
+ # an array and each corresponding capture into a hash.
45
+ #
46
+ # @example
47
+ # # This will return a match for each identifier nodes in the tree.
48
+ # tree_matches = tree.query(<<~QUERY)
49
+ # (identifier) @identifier
50
+ # QUERY
51
+ #
52
+ # # It is equivalent to:
53
+ # tree.root_node.query(<<~QUERY)
54
+ # (identifier) @identifier
55
+ # QUERY
56
+ #
57
+ # @param query_string [String]
58
+ # @return [Array<Hash<String, TreeStand::Node>>]
59
+ def query(query_string)
60
+ ts_query = TreeSitter::Query.new(@tree.parser.ts_language, query_string)
61
+ ts_cursor = TreeSitter::QueryCursor.exec(ts_query, ts_node)
62
+ matches = []
63
+ while ts_match = ts_cursor.next_match
64
+ captures = {}
65
+
66
+ ts_match.captures.each do |ts_capture|
67
+ capture_name = ts_query.capture_name_for_id(ts_capture.index)
68
+ captures[capture_name] = TreeStand::Node.new(@tree, ts_capture.node)
69
+ end
70
+
71
+ matches << captures
72
+ end
73
+ matches
74
+ end
75
+
76
+ # Returns the first captured node that matches the query string or nil if
77
+ # there was no captured node.
78
+ #
79
+ # @example Find the first identifier node.
80
+ # identifier_node = tree.root_node.find_node("(identifier) @identifier")
81
+ #
82
+ # @see #find_node!
83
+ # @see #query
84
+ # @param query_string [String]
85
+ # @return [TreeStand::Node, nil]
86
+ def find_node(query_string)
87
+ query(query_string).first&.values&.first
88
+ end
89
+
90
+ # Like {#find_node}, except that if no node is found, raises an
91
+ # {TreeStand::NodeNotFound} error.
92
+ #
93
+ # @see #find_node
94
+ # @param query_string [String]
95
+ # @return [TreeStand::Node]
96
+ # @raise [TreeStand::NodeNotFound]
97
+ def find_node!(query_string)
98
+ find_node(query_string) || raise(TreeStand::NodeNotFound)
99
+ end
100
+
24
101
  # @return [TreeStand::Range]
25
102
  def range
26
103
  TreeStand::Range.new(
@@ -32,33 +109,74 @@ module TreeStand
32
109
  end
33
110
 
34
111
  # Node includes enumerable so that you can iterate over the child nodes.
112
+ # @example
113
+ # node.text # => "3 * 4"
114
+ #
115
+ # @example Iterate over the child nodes
116
+ # node.each do |child|
117
+ # print child.text
118
+ # end
119
+ # # prints: 3*4
120
+ #
121
+ # @example Enumerable methods
122
+ # node.map(&:text) # => ["3", "*", "4"]
123
+ #
35
124
  # @yieldparam child [TreeStand::Node]
36
125
  # @return [Enumerator]
37
- def each
38
- @ts_node.each do |child|
39
- yield TreeStand::Node.new(@tree, child)
40
- end
126
+ def each(&block)
127
+ Enumerator.new do |yielder|
128
+ @ts_node.each do |child|
129
+ yielder << TreeStand::Node.new(@tree, child)
130
+ end
131
+ end.each(&block)
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
+ # @yieldparam node [TreeStand::Node]
141
+ # @return [Enumerator]
142
+ #
143
+ # @see TreeStand::Visitors::TreeWalker
144
+ def walk(&block)
145
+ Enumerator.new do |yielder|
146
+ Visitors::TreeWalker.new(self) do |child|
147
+ yielder << child
148
+ end.visit
149
+ end.each(&block)
41
150
  end
42
151
 
152
+ # @example
153
+ # node.text # => "4"
154
+ # node.parent.text # => "3 * 4"
155
+ # node.parent.parent.text # => "1 + 3 * 4"
43
156
  # @return [TreeStand::Node]
44
157
  def parent
45
158
  TreeStand::Node.new(@tree, @ts_node.parent)
46
159
  end
47
160
 
48
- # A convience method for getting the text of the node. Each TreeStand Node
49
- # wraps the parent tree and has access to the source document.
161
+ # A convience method for getting the text of the node. Each {TreeStand::Node}
162
+ # wraps the parent {#tree} and has access to the source document.
50
163
  # @return [String]
51
164
  def text
52
165
  @tree.document[@ts_node.start_byte...@ts_node.end_byte]
53
166
  end
54
167
 
55
168
  # This class overrides the `method_missing` method to delegate to the
56
- # node's named children. This allows you to write code like this:
57
- # root = tree.root_node
58
- # child = root.expression
169
+ # node's named children.
170
+ # @example
171
+ # node.text # => "3 * 4"
172
+ #
173
+ # node.left.text # => "3"
174
+ # node.operator.text # => "*"
175
+ # node.right.text # => "4"
176
+ # node.operand # => NoMethodError
59
177
  # @overload method_missing(field_name)
60
178
  # @param name [Symbol, String]
61
- # @return [TreeStand::Node] Child node for the given field name
179
+ # @return [TreeStand::Node] child node for the given field name
62
180
  # @raise [NoMethodError] Raised if the node does not have child with name `field_name`
63
181
  #
64
182
  # @overload method_missing(method_name, *args, &block)
@@ -77,5 +195,16 @@ module TreeStand
77
195
  type == other.type &&
78
196
  text == other.text
79
197
  end
198
+
199
+ # (see TreeStand::Utils::Printer)
200
+ # Backed by {TreeStand::Utils::Printer}.
201
+ #
202
+ # @param pp [PP]
203
+ # @return [void]
204
+ #
205
+ # @see TreeStand::Utils::Printer
206
+ def pretty_print(pp)
207
+ Utils::Printer.new(ralign: 80).print(self, io: pp.output)
208
+ end
80
209
  end
81
210
  end
@@ -38,5 +38,21 @@ module TreeStand
38
38
  ts_tree = @ts_parser.parse_string(nil, document)
39
39
  TreeStand::Tree.new(self, ts_tree, document)
40
40
  end
41
+
42
+ # (see #parse_string)
43
+ # @note Like {#parse_string}, except that if the tree contains any parse
44
+ # errors, raises an {TreeStand::InvalidDocument} error.
45
+ #
46
+ # @see #parse_string
47
+ # @raise [TreeStand::InvalidDocument]
48
+ def parse_string!(tree, document)
49
+ tree = parse_string(tree, document)
50
+ return tree unless tree.any?(&:error?)
51
+
52
+ raise(InvalidDocument, <<~ERROR)
53
+ Encountered errors in the document. Check the tree for more details.
54
+ #{tree}
55
+ ERROR
56
+ end
41
57
  end
42
58
  end
@@ -22,10 +22,51 @@ module TreeStand
22
22
  # This is not always possible and depends on the edits you make, beware that
23
23
  # the tree will be different after each edit and this approach may cause bugs.
24
24
  class Tree
25
+ extend Forwardable
26
+ include Enumerable
27
+
25
28
  # @return [String]
26
29
  attr_reader :document
27
30
  # @return [TreeSitter::Tree]
28
31
  attr_reader :ts_tree
32
+ # @return [TreeStand::Parser]
33
+ attr_reader :parser
34
+
35
+ # @!method query(query_string)
36
+ # @note This is a convenience method that calls {TreeStand::Node#query} on
37
+ # {#root_node}.
38
+ #
39
+ # @!method find_node(query_string)
40
+ # @note This is a convenience method that calls {TreeStand::Node#find_node} on
41
+ # {#root_node}.
42
+ #
43
+ # @!method find_node!(query_string)
44
+ # @note This is a convenience method that calls {TreeStand::Node#find_node!} on
45
+ # {#root_node}.
46
+ #
47
+ # @!method walk(&block)
48
+ # (see TreeStand::Node#walk)
49
+ #
50
+ # @note This is a convenience method that calls {TreeStand::Node#walk} on
51
+ # {#root_node}.
52
+ #
53
+ # @example Tree includes Enumerable
54
+ # tree.any? { |node| node.type == :error }
55
+ #
56
+ # @!method text
57
+ # (see TreeStand::Node#text)
58
+ # @note This is a convenience method that calls {TreeStand::Node#text} on
59
+ # {#root_node}.
60
+ def_delegators(
61
+ :root_node,
62
+ :query,
63
+ :find_node,
64
+ :find_node!,
65
+ :walk,
66
+ :text,
67
+ )
68
+
69
+ alias_method :each, :walk
29
70
 
30
71
  # @api private
31
72
  def initialize(parser, tree, document)
@@ -39,26 +80,6 @@ module TreeStand
39
80
  TreeStand::Node.new(self, @ts_tree.root_node)
40
81
  end
41
82
 
42
- # TreeSitter uses a `TreeSitter::Cursor` to iterate over matches by calling
43
- # `curser#next_match` repeatedly until it returns `nil`.
44
- #
45
- # This method does all of that for you and collects them into an array.
46
- #
47
- # @see TreeStand::Match
48
- # @see TreeStand::Capture
49
- #
50
- # @param query_string [String]
51
- # @return [Array<TreeStand::Match>]
52
- def query(query_string)
53
- ts_query = TreeSitter::Query.new(@parser.ts_language, query_string)
54
- ts_cursor = TreeSitter::QueryCursor.exec(ts_query, @ts_tree.root_node)
55
- matches = []
56
- while match = ts_cursor.next_match
57
- matches << TreeStand::Match.new(self, ts_query, match)
58
- end
59
- matches
60
- end
61
-
62
83
  # This method replaces the section of the document specified by range and
63
84
  # replaces it with the provided text. Then it will reparse the document and
64
85
  # update the tree!
@@ -73,7 +94,7 @@ module TreeStand
73
94
  replace_with_new_doc(new_document)
74
95
  end
75
96
 
76
- # This method deletes the section of the document specified by range Then
97
+ # This method deletes the section of the document specified by range. Then
77
98
  # it will reparse the document and update the tree!
78
99
  # @param range [TreeStand::Range]
79
100
  # @return [void]
@@ -0,0 +1,70 @@
1
+ module TreeStand
2
+ # A collection of useful methods for working with syntax trees.
3
+ module Utils
4
+ # Used to {TreeStand::Node#pretty_print pretty-print} the node.
5
+ #
6
+ # @example
7
+ # pp node
8
+ # # (expression
9
+ # # (sum
10
+ # # left: (number) | 1
11
+ # # ("+") | +
12
+ # # right: (variable))) | x
13
+ class Printer
14
+ # @param ralign [Integer] the right alignment for the text column.
15
+ def initialize(ralign:)
16
+ @ralign = ralign
17
+ end
18
+
19
+ # (see TreeStand::Utils::Printer)
20
+ #
21
+ # @param node [TreeStand::Node]
22
+ # @param io [IO]
23
+ # @return [IO]
24
+ def print(node, io: StringIO.new)
25
+ lines = pretty_output_lines(node)
26
+
27
+ lines.each do |line|
28
+ if line.text.empty?
29
+ io.puts line.sexpr
30
+ next
31
+ end
32
+
33
+ io.puts "#{line.sexpr}#{" " * (@ralign - line.sexpr.size)}| #{line.text}"
34
+ end
35
+
36
+ io
37
+ end
38
+
39
+ private
40
+
41
+ Line = Struct.new(:sexpr, :text)
42
+ private_constant :Line
43
+
44
+ def pretty_output_lines(node, prefix: "", depth: 0)
45
+ indent = " " * depth
46
+ ts_node = node.ts_node
47
+ if indent.size + prefix.size + ts_node.to_s.size < @ralign || ts_node.child_count == 0
48
+ return [Line.new("#{indent}#{prefix}#{ts_node}", node.text)]
49
+ end
50
+
51
+ lines = [Line.new("#{indent}#{prefix}(#{ts_node.type}", "")]
52
+
53
+ node.each.with_index do |child, index|
54
+ lines += if field_name = ts_node.field_name_for_child(index)
55
+ pretty_output_lines(
56
+ child,
57
+ prefix: "#{field_name}: ",
58
+ depth: depth + 1,
59
+ )
60
+ else
61
+ pretty_output_lines(child, depth: depth + 1)
62
+ end
63
+ end
64
+
65
+ lines.last.sexpr << ")"
66
+ lines
67
+ end
68
+ end
69
+ end
70
+ end
@@ -1,4 +1,4 @@
1
1
  module TreeStand
2
2
  # The current version of the gem.
3
- VERSION = "0.1.2"
3
+ VERSION = "0.1.5"
4
4
  end
@@ -47,7 +47,7 @@ module TreeStand
47
47
  def visit_node(node)
48
48
  if respond_to?("on_#{node.type}")
49
49
  public_send("on_#{node.type}", node)
50
- elsif respond_to?(:_on_default)
50
+ elsif respond_to?(:_on_default, true)
51
51
  _on_default(node)
52
52
  end
53
53
 
@@ -0,0 +1,30 @@
1
+ module TreeStand
2
+ # A collection of useful visitors for traversing trees.
3
+ module Visitors
4
+ # Walks the tree depth-first and yields each node to the provided block.
5
+ #
6
+ # @example Create a list of all the nodes in the tree.
7
+ # list = []
8
+ # TreeStand::Visitors::TreeWalker.new(root) do |node|
9
+ # list << node
10
+ # end.visit
11
+ #
12
+ # @see TreeStand::Node#walk
13
+ # @see TreeStand::Tree#walk
14
+ class TreeWalker < Visitor
15
+ # @param node [TreeStand::Node]
16
+ # @param block [Proc] A block that will be called for
17
+ # each node in the tree.
18
+ def initialize(node, &block)
19
+ super(node)
20
+ @block = block
21
+ end
22
+
23
+ private
24
+
25
+ def _on_default(node)
26
+ @block.call(node)
27
+ end
28
+ end
29
+ end
30
+ end
data/lib/tree_stand.rb CHANGED
@@ -9,6 +9,11 @@ loader.setup
9
9
  module TreeStand
10
10
  # Common Ancestor for all TreeStand errors.
11
11
  class Error < StandardError; end
12
+ # Raised when the parsed document contains errors.
13
+ class InvalidDocument < Error; end
14
+ # Raised when performing a search on a tree where a return value is expected,
15
+ # but no match is found.
16
+ class NodeNotFound < Error; end
12
17
 
13
18
  class << self
14
19
  # Easy configuration of the gem.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tree_stand
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - derekstride
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-12-15 00:00:00.000000000 Z
11
+ date: 2023-01-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: zeitwerk
@@ -137,6 +137,7 @@ files:
137
137
  - ".shopify-build/VERSION"
138
138
  - ".shopify-build/tree-stand-publish-package.yml"
139
139
  - CODE_OF_CONDUCT.md
140
+ - CONTRIBUTING.md
140
141
  - Gemfile
141
142
  - LICENSE.txt
142
143
  - README.md
@@ -146,15 +147,15 @@ files:
146
147
  - deploy/install-treesitter
147
148
  - lib/tree_stand.rb
148
149
  - lib/tree_stand/ast_modifier.rb
149
- - lib/tree_stand/capture.rb
150
150
  - lib/tree_stand/config.rb
151
- - lib/tree_stand/match.rb
152
151
  - lib/tree_stand/node.rb
153
152
  - lib/tree_stand/parser.rb
154
153
  - lib/tree_stand/range.rb
155
154
  - lib/tree_stand/tree.rb
155
+ - lib/tree_stand/utils/printer.rb
156
156
  - lib/tree_stand/version.rb
157
157
  - lib/tree_stand/visitor.rb
158
+ - lib/tree_stand/visitors/tree_walker.rb
158
159
  - parsers/.gitkeep
159
160
  - service.yml
160
161
  - shipit.production.yml
@@ -1,45 +0,0 @@
1
- module TreeStand
2
- # Wrapper around a TreeSitter capture.
3
- # @see TreeStand::Tree#query
4
- # @see TreeStand::Match
5
- class Capture
6
- # @return [TreeStand::Match]
7
- attr_reader :match
8
- # @return [TreeSitter::Capture]
9
- attr_reader :ts_capture
10
-
11
- # @api private
12
- def initialize(match, ts_capture)
13
- @match = match
14
- @ts_capture = ts_capture
15
- end
16
-
17
- # The name of the capture. TreeSitter strips the `@` from the capture name.
18
- # @example
19
- # match = @tree.query(<<~QUERY).first
20
- # (identifier) @identifier.name
21
- # QUERY
22
- #
23
- # capture = match.captures.first
24
- #
25
- # assert_equal("identifier.name", capture.name)
26
- # @return [String]
27
- def name
28
- @match.ts_query.capture_name_for_id(@ts_capture.index)
29
- end
30
-
31
- # @return [TreeStand::Node]
32
- def node
33
- TreeStand::Node.new(@match.tree, @ts_capture.node)
34
- end
35
-
36
- # @param other [Object]
37
- # @return [bool]
38
- def ==(other)
39
- return false unless other.is_a?(TreeStand::Capture)
40
-
41
- name == other.name &&
42
- node == other.node
43
- end
44
- end
45
- end
@@ -1,57 +0,0 @@
1
- module TreeStand
2
- # Wrapper around a TreeSitter match.
3
- # @see TreeStand::Tree#query
4
- # @see TreeStand::Capture
5
- class Match
6
- # @return [TreeStand::Tree]
7
- attr_reader :tree
8
- # @return [TreeSitter::Query]
9
- attr_reader :ts_query
10
- # @return [TreeSitter::Match]
11
- attr_reader :ts_match
12
-
13
- # @api private
14
- def initialize(tree, ts_query, ts_match)
15
- @tree = tree
16
- @ts_query = ts_query
17
- @ts_match = ts_match
18
-
19
- # TODO: This is a hack to get the captures to be populated.
20
- # See: https://github.com/Faveod/ruby-tree-sitter/pull/16
21
- captures
22
- end
23
-
24
- # Looks up a capture by name. TreeSitter strips the `@` from the capture name.
25
- # @example
26
- # match = @tree.query(<<~QUERY).first
27
- # (identifier) @identifier.name
28
- # QUERY
29
- #
30
- # refute_nil(match["identifier.name"])
31
- # @param capture_name [String] The name of the capture from the query.
32
- # @return [TreeStand::Capture, nil]
33
- def [](capture_name)
34
- captures.find { |capture| capture.name == capture_name }
35
- end
36
-
37
- # @return [Array<TreeStand::Capture>]
38
- def captures
39
- @captures ||= @ts_match.captures.map do |capture|
40
- TreeStand::Capture.new(self, capture)
41
- end
42
- end
43
-
44
- # @param other [Object]
45
- # @return [bool]
46
- def ==(other)
47
- return false unless other.is_a?(TreeStand::Match)
48
-
49
- captures == other.captures
50
- end
51
-
52
- # @return [TreeStand::Capture, nil]
53
- def dig(name, *)
54
- self[name]
55
- end
56
- end
57
- end