tree_stand 0.1.3 → 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: 0dec13e65fde2c739a206944e34805122f065982b6a87bf5720baca17f0fb8a5
4
- data.tar.gz: b00f3d2b463196d0336ec45ca38294d5f41bab931f546ce5224e1f695268448b
3
+ metadata.gz: 14e5005eed3acbb6399f68c0edf358ba15f4565eb4d99e20652158dbd1c2594e
4
+ data.tar.gz: 8fee6e5e40b9db6adcd72cf72e2705124b9a7e6fcc90ad4673600c1f5074cd69
5
5
  SHA512:
6
- metadata.gz: e00d9f5b809a264bb19672dc0a0b28b58ea37944004fd2c63ca76042eed15f1c1b9f0d539f627d0565a16492522a51cd69e30574cc540c60d66c66fc1484bb3b
7
- data.tar.gz: b28e29fd57802ef79b0a9e88ff2fdaa9f0e79e33ef49173c56f109cdaf2737438f6bb2c23aa5a75fc6af2b4b2c96831a973e921bbcbcfd2f11c2735b525b4085
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
@@ -24,8 +40,8 @@ module TreeStand
24
40
  # TreeSitter uses a `TreeSitter::Cursor` to iterate over matches by calling
25
41
  # `curser#next_match` repeatedly until it returns `nil`.
26
42
  #
27
- # This method does all of that for you and collects all of the
28
- # {TreeStand::Match matches} into an array.
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.
29
45
  #
30
46
  # @example
31
47
  # # This will return a match for each identifier nodes in the tree.
@@ -38,21 +54,50 @@ module TreeStand
38
54
  # (identifier) @identifier
39
55
  # QUERY
40
56
  #
41
- # @see TreeStand::Match
42
- # @see TreeStand::Capture
43
- #
44
57
  # @param query_string [String]
45
- # @return [Array<TreeStand::Match>]
58
+ # @return [Array<Hash<String, TreeStand::Node>>]
46
59
  def query(query_string)
47
60
  ts_query = TreeSitter::Query.new(@tree.parser.ts_language, query_string)
48
61
  ts_cursor = TreeSitter::QueryCursor.exec(ts_query, ts_node)
49
62
  matches = []
50
- while match = ts_cursor.next_match
51
- matches << TreeStand::Match.new(@tree, ts_query, match)
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
52
72
  end
53
73
  matches
54
74
  end
55
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
+
56
101
  # @return [TreeStand::Range]
57
102
  def range
58
103
  TreeStand::Range.new(
@@ -64,33 +109,74 @@ module TreeStand
64
109
  end
65
110
 
66
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
+ #
67
124
  # @yieldparam child [TreeStand::Node]
68
125
  # @return [Enumerator]
69
- def each
70
- @ts_node.each do |child|
71
- yield TreeStand::Node.new(@tree, child)
72
- 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)
73
150
  end
74
151
 
152
+ # @example
153
+ # node.text # => "4"
154
+ # node.parent.text # => "3 * 4"
155
+ # node.parent.parent.text # => "1 + 3 * 4"
75
156
  # @return [TreeStand::Node]
76
157
  def parent
77
158
  TreeStand::Node.new(@tree, @ts_node.parent)
78
159
  end
79
160
 
80
- # A convience method for getting the text of the node. Each TreeStand Node
81
- # 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.
82
163
  # @return [String]
83
164
  def text
84
165
  @tree.document[@ts_node.start_byte...@ts_node.end_byte]
85
166
  end
86
167
 
87
168
  # This class overrides the `method_missing` method to delegate to the
88
- # node's named children. This allows you to write code like this:
89
- # root = tree.root_node
90
- # 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
91
177
  # @overload method_missing(field_name)
92
178
  # @param name [Symbol, String]
93
- # @return [TreeStand::Node] Child node for the given field name
179
+ # @return [TreeStand::Node] child node for the given field name
94
180
  # @raise [NoMethodError] Raised if the node does not have child with name `field_name`
95
181
  #
96
182
  # @overload method_missing(method_name, *args, &block)
@@ -109,5 +195,16 @@ module TreeStand
109
195
  type == other.type &&
110
196
  text == other.text
111
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
112
209
  end
113
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,6 +22,9 @@ 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]
@@ -29,6 +32,42 @@ module TreeStand
29
32
  # @return [TreeStand::Parser]
30
33
  attr_reader :parser
31
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
70
+
32
71
  # @api private
33
72
  def initialize(parser, tree, document)
34
73
  @parser = parser
@@ -41,14 +80,6 @@ module TreeStand
41
80
  TreeStand::Node.new(self, @ts_tree.root_node)
42
81
  end
43
82
 
44
- # (see TreeStand::Node#query)
45
- # @note This is a convenience method that calls {TreeStand::Node#query} on
46
- # {#root_node}.
47
- # @see TreeStand::Node#query
48
- def query(query_string)
49
- root_node.query(query_string)
50
- end
51
-
52
83
  # This method replaces the section of the document specified by range and
53
84
  # replaces it with the provided text. Then it will reparse the document and
54
85
  # update the tree!
@@ -63,7 +94,7 @@ module TreeStand
63
94
  replace_with_new_doc(new_document)
64
95
  end
65
96
 
66
- # 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
67
98
  # it will reparse the document and update the tree!
68
99
  # @param range [TreeStand::Range]
69
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.3"
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.3
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,46 +0,0 @@
1
- module TreeStand
2
- # Wrapper around a TreeSitter capture.
3
- # @see TreeStand::Tree#query
4
- # @see TreeStand::Node#query
5
- # @see TreeStand::Match
6
- class Capture
7
- # @return [TreeStand::Match]
8
- attr_reader :match
9
- # @return [TreeSitter::Capture]
10
- attr_reader :ts_capture
11
-
12
- # @api private
13
- def initialize(match, ts_capture)
14
- @match = match
15
- @ts_capture = ts_capture
16
- end
17
-
18
- # The name of the capture. TreeSitter strips the `@` from the capture name.
19
- # @example
20
- # match = @tree.query(<<~QUERY).first
21
- # (identifier) @identifier.name
22
- # QUERY
23
- #
24
- # capture = match.captures.first
25
- #
26
- # assert_equal("identifier.name", capture.name)
27
- # @return [String]
28
- def name
29
- @match.ts_query.capture_name_for_id(@ts_capture.index)
30
- end
31
-
32
- # @return [TreeStand::Node]
33
- def node
34
- TreeStand::Node.new(@match.tree, @ts_capture.node)
35
- end
36
-
37
- # @param other [Object]
38
- # @return [bool]
39
- def ==(other)
40
- return false unless other.is_a?(TreeStand::Capture)
41
-
42
- name == other.name &&
43
- node == other.node
44
- end
45
- end
46
- end
@@ -1,58 +0,0 @@
1
- module TreeStand
2
- # Wrapper around a TreeSitter match.
3
- # @see TreeStand::Tree#query
4
- # @see TreeStand::Node#query
5
- # @see TreeStand::Capture
6
- class Match
7
- # @return [TreeStand::Tree]
8
- attr_reader :tree
9
- # @return [TreeSitter::Query]
10
- attr_reader :ts_query
11
- # @return [TreeSitter::Match]
12
- attr_reader :ts_match
13
- # @return [Array<TreeStand::Capture>]
14
- attr_reader :captures
15
-
16
- # @api private
17
- def initialize(tree, ts_query, ts_match)
18
- @tree = tree
19
- @ts_query = ts_query
20
- @ts_match = ts_match
21
-
22
- # It's important to load all of the captures when a Match is
23
- # instantiated, otherwise the ts_match will be invalid after
24
- # TreeSitter::Cursor#next_match is called.
25
- #
26
- # See: https://github.com/Faveod/ruby-tree-sitter/pull/16
27
- @captures = @ts_match.captures.map do |capture|
28
- TreeStand::Capture.new(self, capture)
29
- end
30
- end
31
-
32
- # Looks up a capture by name. TreeSitter strips the `@` from the capture name.
33
- # @example
34
- # match = @tree.query(<<~QUERY).first
35
- # (identifier) @identifier.name
36
- # QUERY
37
- #
38
- # refute_nil(match["identifier.name"])
39
- # @param capture_name [String] The name of the capture from the query.
40
- # @return [TreeStand::Capture, nil]
41
- def [](capture_name)
42
- captures.find { |capture| capture.name == capture_name }
43
- end
44
-
45
- # @param other [Object]
46
- # @return [bool]
47
- def ==(other)
48
- return false unless other.is_a?(TreeStand::Match)
49
-
50
- captures == other.captures
51
- end
52
-
53
- # @return [TreeStand::Capture, nil]
54
- def dig(name, *)
55
- self[name]
56
- end
57
- end
58
- end