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 +4 -4
- data/CONTRIBUTING.md +65 -0
- data/bin/console +15 -7
- data/bin/setup +5 -4
- data/lib/tree_stand/node.rb +116 -19
- data/lib/tree_stand/parser.rb +16 -0
- data/lib/tree_stand/tree.rb +40 -9
- data/lib/tree_stand/utils/printer.rb +70 -0
- data/lib/tree_stand/version.rb +1 -1
- data/lib/tree_stand/visitor.rb +1 -1
- data/lib/tree_stand/visitors/tree_walker.rb +30 -0
- data/lib/tree_stand.rb +5 -0
- metadata +5 -4
- data/lib/tree_stand/capture.rb +0 -46
- data/lib/tree_stand/match.rb +0 -58
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 14e5005eed3acbb6399f68c0edf358ba15f4565eb4d99e20652158dbd1c2594e
|
4
|
+
data.tar.gz: 8fee6e5e40b9db6adcd72cf72e2705124b9a7e6fcc90ad4673600c1f5074cd69
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
7
|
-
|
6
|
+
TreeStand.configure do
|
7
|
+
config.parser_path = File.expand_path(
|
8
|
+
File.join(__dir__, "..", "parsers")
|
9
|
+
)
|
10
|
+
end
|
8
11
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
+
ivars_to_add = <<~RUBY
|
13
|
+
@parser = TreeStand::Parser.new("math")
|
14
|
+
@tree = @parser.parse_string(nil, "1 + x")
|
15
|
+
RUBY
|
12
16
|
|
13
|
-
|
14
|
-
|
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-
|
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-
|
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-
|
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-
|
21
|
+
cp tmp/tree-sitter-math/target/parser.so parsers/math.so
|
data/lib/tree_stand/node.rb
CHANGED
@@ -7,13 +7,29 @@ module TreeStand
|
|
7
7
|
extend Forwardable
|
8
8
|
include Enumerable
|
9
9
|
|
10
|
-
|
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
|
-
#
|
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::
|
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
|
51
|
-
|
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
|
-
|
71
|
-
|
72
|
-
|
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
|
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.
|
89
|
-
#
|
90
|
-
#
|
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]
|
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
|
data/lib/tree_stand/parser.rb
CHANGED
@@ -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
|
data/lib/tree_stand/tree.rb
CHANGED
@@ -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
|
data/lib/tree_stand/version.rb
CHANGED
data/lib/tree_stand/visitor.rb
CHANGED
@@ -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.
|
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:
|
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
|
data/lib/tree_stand/capture.rb
DELETED
@@ -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
|
data/lib/tree_stand/match.rb
DELETED
@@ -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
|