tree_stand 0.1.3 → 0.1.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|