tree_stand 0.1.3 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0dec13e65fde2c739a206944e34805122f065982b6a87bf5720baca17f0fb8a5
4
- data.tar.gz: b00f3d2b463196d0336ec45ca38294d5f41bab931f546ce5224e1f695268448b
3
+ metadata.gz: 7d9ecce5098c654a026b3f08bd8e5dc0a87b696d5e56102f11a40540a7b45d72
4
+ data.tar.gz: 41b5bc8b68fb82183e1d641e735fa9083365c5077402193cbf2128c026b1140e
5
5
  SHA512:
6
- metadata.gz: e00d9f5b809a264bb19672dc0a0b28b58ea37944004fd2c63ca76042eed15f1c1b9f0d539f627d0565a16492522a51cd69e30574cc540c60d66c66fc1484bb3b
7
- data.tar.gz: b28e29fd57802ef79b0a9e88ff2fdaa9f0e79e33ef49173c56f109cdaf2737438f6bb2c23aa5a75fc6af2b4b2c96831a973e921bbcbcfd2f11c2735b525b4085
6
+ metadata.gz: e1a91dcfa898f9341ed7d8493204cec0ff1170b157e8598226a41858faa96de9f4df45b6e1e449c5c817b3edc7b44efa9bfb7bbfae6a1f4a031580ff0663a0e3
7
+ data.tar.gz: 23cc338e725309b833f1b9200101f191772798e3a85d1610c41237522096a7454013fcf6252de8b161c62f7831a263f2ec94603aff4be47739b4a78cc4fdcedc
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/README.md CHANGED
@@ -37,9 +37,10 @@ ruby_parser = TreeStand::Parser.new("ruby")
37
37
  ### API Conventions
38
38
 
39
39
  TreeStand aims to provide APIs similar to TreeSitter when possible. For example, the TreeSitter parser exposes a
40
- `#parse_string(tree, document)` method. TreeStand replicates this behaviour but augments it to return a
41
- `TreeStand::Tree` instead of the underlying `TreeSitter::Tree`. Similarly, `TreeStand::Tree#root_node` returns a
42
- `TreeStand::Node` & `TreeSitter::Tree#root_node` returns a `TreeSitter::Node`.
40
+ `#parse_string(tree, document)` method. TreeStand replicates this behaviour closely with it's `#parse_string(document,
41
+ tree: nil)` method but augments it to return a `TreeStand::Tree` instead of the underlying `TreeSitter::Tree`.
42
+ Similarly, `TreeStand::Tree#root_node` returns a `TreeStand::Node` & `TreeSitter::Tree#root_node` returns a
43
+ `TreeSitter::Node`.
43
44
 
44
45
  The underlying objects are accessible via a `ts_` prefixed attribute, e.g. `ts_parser`, `ts_tree`, `ts_node`, etc.
45
46
 
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 {TreeStand::Tree #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
@@ -30,13 +30,31 @@ module TreeStand
30
30
  end
31
31
 
32
32
  # Parse the provided document with the TreeSitter parser.
33
- # @param tree [TreeStand::Tree]
33
+ # @param tree [TreeStand::Tree, nil] providing the old tree will allow the
34
+ # parser to take advantage of incremental parsing and improve performance
35
+ # by re-useing nodes from the old tree.
34
36
  # @param document [String]
35
37
  # @return [TreeStand::Tree]
36
- def parse_string(tree, document)
37
- # There's a bug with passing a non-nil tree
38
+ def parse_string(document, tree: nil)
39
+ # @todo There's a bug with passing a non-nil tree
38
40
  ts_tree = @ts_parser.parse_string(nil, document)
39
41
  TreeStand::Tree.new(self, ts_tree, document)
40
42
  end
43
+
44
+ # (see #parse_string)
45
+ # @note Like {#parse_string}, except that if the tree contains any parse
46
+ # errors, raises an {TreeStand::InvalidDocument} error.
47
+ #
48
+ # @see #parse_string
49
+ # @raise [TreeStand::InvalidDocument]
50
+ def parse_string!(document, tree: nil)
51
+ tree = parse_string(document, tree: tree)
52
+ return tree unless tree.any?(&:error?)
53
+
54
+ raise(InvalidDocument, <<~ERROR)
55
+ Encountered errors in the document. Check the tree for more details.
56
+ #{tree}
57
+ ERROR
58
+ end
41
59
  end
42
60
  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]
@@ -78,7 +109,7 @@ module TreeStand
78
109
 
79
110
  def replace_with_new_doc(new_document)
80
111
  @document = new_document
81
- new_tree = @parser.parse_string(@ts_tree, @document)
112
+ new_tree = @parser.parse_string(@document, tree: self)
82
113
  @ts_tree = new_tree.ts_tree
83
114
  end
84
115
  end
@@ -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.6"
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.6
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-26 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