tree_stand 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/ci.yml +44 -0
- data/.github/workflows/cla.yml +22 -0
- data/.gitignore +10 -0
- data/.shopify-build/VERSION +1 -0
- data/.shopify-build/tree-stand-publish-package.yml +23 -0
- data/CODE_OF_CONDUCT.md +134 -0
- data/Gemfile +14 -0
- data/LICENSE.txt +21 -0
- data/README.md +54 -0
- data/Rakefile +9 -0
- data/bin/console +14 -0
- data/bin/setup +20 -0
- data/deploy/install-treesitter +22 -0
- data/lib/tree_stand/ast_modifier.rb +25 -0
- data/lib/tree_stand/capture.rb +45 -0
- data/lib/tree_stand/config.rb +8 -0
- data/lib/tree_stand/match.rb +57 -0
- data/lib/tree_stand/node.rb +76 -0
- data/lib/tree_stand/parser.rb +42 -0
- data/lib/tree_stand/range.rb +40 -0
- data/lib/tree_stand/tree.rb +95 -0
- data/lib/tree_stand/version.rb +4 -0
- data/lib/tree_stand/visitor.rb +59 -0
- data/lib/tree_stand.rb +33 -0
- data/parsers/.gitkeep +0 -0
- data/service.yml +1 -0
- data/shipit.production.yml +3 -0
- data/tree_stand.gemspec +36 -0
- metadata +146 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 736381046f8ec987fd106632c34cfbcf79055801d19c519d59980da52d9ebf6a
|
4
|
+
data.tar.gz: 7e1563ffb2af294542d3ac35fd287738bae0ebb69f7312907c18b5ff07fafd64
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 36c90d3233c617abb88bb364cd79bc8fc0465301ef91dd0f9043cbc390d7e7ea5bf7f6ddf07ce4d82f715fd6dc2f76a3c3886a601610c29ddd6679effe2aa3f9
|
7
|
+
data.tar.gz: 3ffc48adac74e5623244e0024534c867f074715d03640f7a428b077556e71f5d5b9b9394ad6f02c586b76768878f13511f3772fa70a5871d9fedea537eca6f67
|
@@ -0,0 +1,44 @@
|
|
1
|
+
name: TreeStand
|
2
|
+
on:
|
3
|
+
push:
|
4
|
+
branches:
|
5
|
+
- "main"
|
6
|
+
pull_request:
|
7
|
+
types: [opened, synchronize, reopened, ready_for_review]
|
8
|
+
branches:
|
9
|
+
- 'main'
|
10
|
+
jobs:
|
11
|
+
test:
|
12
|
+
runs-on: ubuntu-latest
|
13
|
+
strategy:
|
14
|
+
matrix:
|
15
|
+
entry:
|
16
|
+
- { ruby: 3.0 }
|
17
|
+
- { ruby: 3.1 }
|
18
|
+
name: test (${{ matrix.entry.ruby }})
|
19
|
+
steps:
|
20
|
+
- uses: actions/checkout@v3
|
21
|
+
|
22
|
+
- uses: actions/checkout@v3
|
23
|
+
with:
|
24
|
+
repository: DerekStride/tree-sitter-sql
|
25
|
+
path: tmp/tree-sitter-sql
|
26
|
+
|
27
|
+
- uses: ruby/setup-ruby@v1
|
28
|
+
with:
|
29
|
+
ruby-version: ${{ matrix.entry.ruby }}
|
30
|
+
- uses: actions/cache@v1
|
31
|
+
with:
|
32
|
+
path: vendor/bundle
|
33
|
+
key: ${{ runner.os }}-gems-${{ hashFiles('Gemfile') }}
|
34
|
+
restore-keys: ${{ runner.os }}-gems-
|
35
|
+
|
36
|
+
- uses: actions/setup-node@v3
|
37
|
+
with:
|
38
|
+
node-version: 16
|
39
|
+
- run: npm install tree-sitter-cli
|
40
|
+
|
41
|
+
- run: sudo apt-get install -y libtree-sitter-dev make gcc
|
42
|
+
- run: bundle install --jobs=3 --retry=3 --path=vendor/bundle
|
43
|
+
- run: bin/setup
|
44
|
+
- run: bundle exec rake
|
@@ -0,0 +1,22 @@
|
|
1
|
+
name: Contributor License Agreement (CLA)
|
2
|
+
|
3
|
+
on:
|
4
|
+
pull_request_target:
|
5
|
+
types: [opened, synchronize]
|
6
|
+
issue_comment:
|
7
|
+
types: [created]
|
8
|
+
|
9
|
+
jobs:
|
10
|
+
cla:
|
11
|
+
runs-on: ubuntu-latest
|
12
|
+
if: |
|
13
|
+
(github.event.issue.pull_request
|
14
|
+
&& !github.event.issue.pull_request.merged_at
|
15
|
+
&& contains(github.event.comment.body, 'signed')
|
16
|
+
)
|
17
|
+
|| (github.event.pull_request && !github.event.pull_request.merged)
|
18
|
+
steps:
|
19
|
+
- uses: Shopify/shopify-cla-action@v1
|
20
|
+
with:
|
21
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
22
|
+
cla-token: ${{ secrets.CLA_TOKEN }}
|
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
v1
|
@@ -0,0 +1,23 @@
|
|
1
|
+
containers:
|
2
|
+
default:
|
3
|
+
build:
|
4
|
+
from: ubuntu-latest
|
5
|
+
type: ci
|
6
|
+
ruby: 3.1
|
7
|
+
rust: stable
|
8
|
+
|
9
|
+
steps:
|
10
|
+
- label: Publish Gem
|
11
|
+
timeout: 30m
|
12
|
+
run:
|
13
|
+
- cargo install tree-sitter-cli
|
14
|
+
- tree-sitter -V
|
15
|
+
- mkdir -p tmp
|
16
|
+
- cd tmp
|
17
|
+
- git clone --depth=1 https://github.com/tree-sitter/tree-sitter.git
|
18
|
+
- cd tree-sitter
|
19
|
+
- make
|
20
|
+
- make install
|
21
|
+
- cd ../..
|
22
|
+
- publish:
|
23
|
+
type: gem
|
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
Contact: opensource@shopify.com
|
4
|
+
|
5
|
+
## Our Pledge
|
6
|
+
|
7
|
+
We as members, contributors, and leaders pledge to make participation in our
|
8
|
+
community a harassment-free experience for everyone, regardless of age, body
|
9
|
+
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
10
|
+
identity and expression, level of experience, education, socio-economic status,
|
11
|
+
nationality, personal appearance, race, caste, color, religion, or sexual
|
12
|
+
identity and orientation.
|
13
|
+
|
14
|
+
We pledge to act and interact in ways that contribute to an open, welcoming,
|
15
|
+
diverse, inclusive, and healthy community.
|
16
|
+
|
17
|
+
## Our Standards
|
18
|
+
|
19
|
+
Examples of behavior that contributes to a positive environment for our
|
20
|
+
community include:
|
21
|
+
|
22
|
+
* Demonstrating empathy and kindness toward other people
|
23
|
+
* Being respectful of differing opinions, viewpoints, and experiences
|
24
|
+
* Giving and gracefully accepting constructive feedback
|
25
|
+
* Accepting responsibility and apologizing to those affected by our mistakes,
|
26
|
+
and learning from the experience
|
27
|
+
* Focusing on what is best not just for us as individuals, but for the overall
|
28
|
+
community
|
29
|
+
|
30
|
+
Examples of unacceptable behavior include:
|
31
|
+
|
32
|
+
* The use of sexualized language or imagery, and sexual attention or advances of
|
33
|
+
any kind
|
34
|
+
* Trolling, insulting or derogatory comments, and personal or political attacks
|
35
|
+
* Public or private harassment
|
36
|
+
* Publishing others' private information, such as a physical or email address,
|
37
|
+
without their explicit permission
|
38
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
39
|
+
professional setting
|
40
|
+
|
41
|
+
## Enforcement Responsibilities
|
42
|
+
|
43
|
+
Community leaders are responsible for clarifying and enforcing our standards of
|
44
|
+
acceptable behavior and will take appropriate and fair corrective action in
|
45
|
+
response to any behavior that they deem inappropriate, threatening, offensive,
|
46
|
+
or harmful.
|
47
|
+
|
48
|
+
Community leaders have the right and responsibility to remove, edit, or reject
|
49
|
+
comments, commits, code, wiki edits, issues, and other contributions that are
|
50
|
+
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
51
|
+
decisions when appropriate.
|
52
|
+
|
53
|
+
## Scope
|
54
|
+
|
55
|
+
This Code of Conduct applies within all community spaces, and also applies when
|
56
|
+
an individual is officially representing the community in public spaces.
|
57
|
+
Examples of representing our community include using an official e-mail address,
|
58
|
+
posting via an official social media account, or acting as an appointed
|
59
|
+
representative at an online or offline event.
|
60
|
+
|
61
|
+
## Enforcement
|
62
|
+
|
63
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
64
|
+
reported to the community leaders responsible for enforcement at
|
65
|
+
[INSERT CONTACT METHOD].
|
66
|
+
All complaints will be reviewed and investigated promptly and fairly.
|
67
|
+
|
68
|
+
All community leaders are obligated to respect the privacy and security of the
|
69
|
+
reporter of any incident.
|
70
|
+
|
71
|
+
## Enforcement Guidelines
|
72
|
+
|
73
|
+
Community leaders will follow these Community Impact Guidelines in determining
|
74
|
+
the consequences for any action they deem in violation of this Code of Conduct:
|
75
|
+
|
76
|
+
### 1. Correction
|
77
|
+
|
78
|
+
**Community Impact**: Use of inappropriate language or other behavior deemed
|
79
|
+
unprofessional or unwelcome in the community.
|
80
|
+
|
81
|
+
**Consequence**: A private, written warning from community leaders, providing
|
82
|
+
clarity around the nature of the violation and an explanation of why the
|
83
|
+
behavior was inappropriate. A public apology may be requested.
|
84
|
+
|
85
|
+
### 2. Warning
|
86
|
+
|
87
|
+
**Community Impact**: A violation through a single incident or series of
|
88
|
+
actions.
|
89
|
+
|
90
|
+
**Consequence**: A warning with consequences for continued behavior. No
|
91
|
+
interaction with the people involved, including unsolicited interaction with
|
92
|
+
those enforcing the Code of Conduct, for a specified period of time. This
|
93
|
+
includes avoiding interactions in community spaces as well as external channels
|
94
|
+
like social media. Violating these terms may lead to a temporary or permanent
|
95
|
+
ban.
|
96
|
+
|
97
|
+
### 3. Temporary Ban
|
98
|
+
|
99
|
+
**Community Impact**: A serious violation of community standards, including
|
100
|
+
sustained inappropriate behavior.
|
101
|
+
|
102
|
+
**Consequence**: A temporary ban from any sort of interaction or public
|
103
|
+
communication with the community for a specified period of time. No public or
|
104
|
+
private interaction with the people involved, including unsolicited interaction
|
105
|
+
with those enforcing the Code of Conduct, is allowed during this period.
|
106
|
+
Violating these terms may lead to a permanent ban.
|
107
|
+
|
108
|
+
### 4. Permanent Ban
|
109
|
+
|
110
|
+
**Community Impact**: Demonstrating a pattern of violation of community
|
111
|
+
standards, including sustained inappropriate behavior, harassment of an
|
112
|
+
individual, or aggression toward or disparagement of classes of individuals.
|
113
|
+
|
114
|
+
**Consequence**: A permanent ban from any sort of public interaction within the
|
115
|
+
community.
|
116
|
+
|
117
|
+
## Attribution
|
118
|
+
|
119
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
120
|
+
version 2.1, available at
|
121
|
+
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
122
|
+
|
123
|
+
Community Impact Guidelines were inspired by
|
124
|
+
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
125
|
+
|
126
|
+
For answers to common questions about this code of conduct, see the FAQ at
|
127
|
+
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
128
|
+
[https://www.contributor-covenant.org/translations][translations].
|
129
|
+
|
130
|
+
[homepage]: https://www.contributor-covenant.org
|
131
|
+
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
132
|
+
[Mozilla CoC]: https://github.com/mozilla/diversity
|
133
|
+
[FAQ]: https://www.contributor-covenant.org/faq
|
134
|
+
[translations]: https://www.contributor-covenant.org/translations
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2022 derekstride
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# TreeStand
|
2
|
+
|
3
|
+
[![TreeStand](https://github.com/Shopify/tree_stand/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/Shopify/tree_stand/actions/workflows/ci.yml)
|
4
|
+
|
5
|
+
|
6
|
+
TreeStand is a high-level Ruby wrapper for the [Tree-sitter](https://tree-sitter.github.io/tree-sitter/) bindings. It
|
7
|
+
makes it easier to configure the parsers, and work with the underlying syntax tree.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
gem "tree_stand"
|
15
|
+
```
|
16
|
+
|
17
|
+
## Usage
|
18
|
+
|
19
|
+
### Setting Up a Parser
|
20
|
+
|
21
|
+
TreeStand do not help with compiling individual parsers. However, once you compile a parser and generate a shared
|
22
|
+
object (`.so`) or a dynamic library (`.dylib`) you can tell TreeStand where to find them and pass the parser filename
|
23
|
+
to `TreeStand::Parser::new`.
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
TreeStand.configure do
|
27
|
+
config.parser_path = "path/to/parser/folder/"
|
28
|
+
end
|
29
|
+
|
30
|
+
sql_parser = TreeStand::Parser.new("sql")
|
31
|
+
ruby_parser = TreeStand::Parser.new("ruby")
|
32
|
+
```
|
33
|
+
|
34
|
+
|
35
|
+
### API Conventions
|
36
|
+
|
37
|
+
TreeStand aims to provide APIs similar to TreeSitter when possible. For example, the TreeSitter parser exposes a
|
38
|
+
`#parse_string(tree, document)` method. TreeStand replicates this behaviour but augments it to return a
|
39
|
+
`TreeStand::Tree` instead of the underlying `TreeSitter::Tree`. Similarly, `TreeStand::Tree#root_node` returns a
|
40
|
+
`TreeStand::Node` & `TreeSitter::Tree#root_node` returns a `TreeSitter::Node`.
|
41
|
+
|
42
|
+
The underlying objects are accessible via a `ts_` prefixed attribute, e.g. `ts_parser`, `ts_tree`, `ts_node`, etc.
|
43
|
+
|
44
|
+
## Contributing
|
45
|
+
|
46
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/Shopify/tree_stand. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
47
|
+
|
48
|
+
## License
|
49
|
+
|
50
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
51
|
+
|
52
|
+
## Code of Conduct
|
53
|
+
|
54
|
+
Everyone interacting in the TreeStand project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/Shopify/tree_stand/blob/master/CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "tree_stand"
|
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.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
set -euo pipefail
|
3
|
+
IFS=$'\n\t'
|
4
|
+
set -vx
|
5
|
+
|
6
|
+
bundle install
|
7
|
+
|
8
|
+
# Do any other automated setup that you need to do here
|
9
|
+
if [[ ! -d tmp/tree-sitter-sql ]]; then
|
10
|
+
mkdir -p tmp
|
11
|
+
git -C tmp/ clone --depth=1 https://github.com/DerekStride/tree-sitter-sql.git
|
12
|
+
fi
|
13
|
+
|
14
|
+
cd tmp/tree-sitter-sql
|
15
|
+
|
16
|
+
npm install
|
17
|
+
gcc -shared -o target/parser.so -fPIC src/parser.c -I./src
|
18
|
+
|
19
|
+
cd ../..
|
20
|
+
cp tmp/tree-sitter-sql/target/parser.so parsers/sql.so
|
@@ -0,0 +1,22 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
set -ex
|
4
|
+
|
5
|
+
PREFIX="$(pwd)/tmp"
|
6
|
+
|
7
|
+
mkdir -p tmp/lib tmp/lib
|
8
|
+
cd tmp
|
9
|
+
|
10
|
+
git clone --depth=1 https://github.com/tree-sitter/tree-sitter.git 2> /dev/null
|
11
|
+
cd tree-sitter
|
12
|
+
|
13
|
+
make
|
14
|
+
PREFIX=$PREFIX make install
|
15
|
+
cd ../..
|
16
|
+
|
17
|
+
bundle config set build.tree_sitter \
|
18
|
+
--with-tree-sitter-dir=$PREFIX \
|
19
|
+
--with-tree-sitter-lib=$PREFIX/lib \
|
20
|
+
--with-tree-sitter-include=$PREFIX/include \
|
21
|
+
--with-opt-include=$PREFIX/include \
|
22
|
+
--with-opt-lib=$PREFIX/lib
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module TreeStand
|
2
|
+
# An experimental class to modify the AST. I re-runs the query on the
|
3
|
+
# modified document every loop to ensure that the match is still valid.
|
4
|
+
# @see TreeStand::Tree
|
5
|
+
# @api experimental
|
6
|
+
class AstModifier
|
7
|
+
# @param tree [TreeStand::Tree]
|
8
|
+
def initialize(tree)
|
9
|
+
@tree = tree
|
10
|
+
end
|
11
|
+
|
12
|
+
# @param query [String]
|
13
|
+
# @yieldparam self [self]
|
14
|
+
# @yieldparam match [TreeStand::Match]
|
15
|
+
# @return [void]
|
16
|
+
def on_match(query)
|
17
|
+
matches = @tree.query(query)
|
18
|
+
|
19
|
+
while !matches.empty?
|
20
|
+
yield self, matches.first
|
21
|
+
matches = @tree.query(query)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module TreeStand
|
2
|
+
# Wrapper around a TreeSitter capture.
|
3
|
+
# @see TreeStand::Tree#query
|
4
|
+
# @see TreeStand::Match
|
5
|
+
class Capture
|
6
|
+
# @return [TreeStand::Match]
|
7
|
+
attr_reader :match
|
8
|
+
# @return [TreeSitter::Capture]
|
9
|
+
attr_reader :ts_capture
|
10
|
+
|
11
|
+
# @api private
|
12
|
+
def initialize(match, ts_capture)
|
13
|
+
@match = match
|
14
|
+
@ts_capture = ts_capture
|
15
|
+
end
|
16
|
+
|
17
|
+
# The name of the capture. TreeSitter strips the `@` from the capture name.
|
18
|
+
# @example
|
19
|
+
# match = @tree.query(<<~QUERY).first
|
20
|
+
# (identifier) @identifier.name
|
21
|
+
# QUERY
|
22
|
+
#
|
23
|
+
# capture = match.captures.first
|
24
|
+
#
|
25
|
+
# assert_equal("identifier.name", capture.name)
|
26
|
+
# @return [String]
|
27
|
+
def name
|
28
|
+
@match.ts_query.capture_name_for_id(@ts_capture.index)
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [TreeStand::Node]
|
32
|
+
def node
|
33
|
+
TreeStand::Node.new(@match.tree, @ts_capture.node)
|
34
|
+
end
|
35
|
+
|
36
|
+
# @param other [Object]
|
37
|
+
# @return [bool]
|
38
|
+
def ==(other)
|
39
|
+
return false unless other.is_a?(TreeStand::Capture)
|
40
|
+
|
41
|
+
name == other.name &&
|
42
|
+
node == other.node
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module TreeStand
|
2
|
+
# Wrapper around a TreeSitter match.
|
3
|
+
# @see TreeStand::Tree#query
|
4
|
+
# @see TreeStand::Capture
|
5
|
+
class Match
|
6
|
+
# @return [TreeStand::Tree]
|
7
|
+
attr_reader :tree
|
8
|
+
# @return [TreeSitter::Query]
|
9
|
+
attr_reader :ts_query
|
10
|
+
# @return [TreeSitter::Match]
|
11
|
+
attr_reader :ts_match
|
12
|
+
|
13
|
+
# @api private
|
14
|
+
def initialize(tree, ts_query, ts_match)
|
15
|
+
@tree = tree
|
16
|
+
@ts_query = ts_query
|
17
|
+
@ts_match = ts_match
|
18
|
+
|
19
|
+
# TODO: This is a hack to get the captures to be populated.
|
20
|
+
# See: https://github.com/Faveod/ruby-tree-sitter/pull/16
|
21
|
+
captures
|
22
|
+
end
|
23
|
+
|
24
|
+
# Looks up a capture by name. TreeSitter strips the `@` from the capture name.
|
25
|
+
# @example
|
26
|
+
# match = @tree.query(<<~QUERY).first
|
27
|
+
# (identifier) @identifier.name
|
28
|
+
# QUERY
|
29
|
+
#
|
30
|
+
# refute_nil(match["identifier.name"])
|
31
|
+
# @param capture_name [String] The name of the capture from the query.
|
32
|
+
# @return [TreeStand::Capture, nil]
|
33
|
+
def [](capture_name)
|
34
|
+
captures.find { |capture| capture.name == capture_name }
|
35
|
+
end
|
36
|
+
|
37
|
+
# @return [Array<TreeStand::Capture>]
|
38
|
+
def captures
|
39
|
+
@captures ||= @ts_match.captures.map do |capture|
|
40
|
+
TreeStand::Capture.new(self, capture)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# @param other [Object]
|
45
|
+
# @return [bool]
|
46
|
+
def ==(other)
|
47
|
+
return false unless other.is_a?(TreeStand::Match)
|
48
|
+
|
49
|
+
captures == other.captures
|
50
|
+
end
|
51
|
+
|
52
|
+
# @return [TreeStand::Capture, nil]
|
53
|
+
def dig(name, *)
|
54
|
+
self[name]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module TreeStand
|
2
|
+
# Wrapper around a TreeSitter node and provides convient
|
3
|
+
# methods that are missing on the original node. This class
|
4
|
+
# overrides the `method_missing` method to delegate to a nodes
|
5
|
+
# named children.
|
6
|
+
class Node
|
7
|
+
extend Forwardable
|
8
|
+
include Enumerable
|
9
|
+
|
10
|
+
def_delegators :@ts_node, :type, :start_byte, :end_byte, :start_point, :end_point
|
11
|
+
|
12
|
+
# @api private
|
13
|
+
def initialize(tree, ts_node)
|
14
|
+
@tree = tree
|
15
|
+
@ts_node = ts_node
|
16
|
+
@fields = @ts_node.each_field.to_a.map(&:first)
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [TreeStand::Range]
|
20
|
+
def range
|
21
|
+
TreeStand::Range.new(
|
22
|
+
start_byte: @ts_node.start_byte,
|
23
|
+
end_byte: @ts_node.end_byte,
|
24
|
+
start_point: @ts_node.start_point,
|
25
|
+
end_point: @ts_node.end_point,
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Node includes enumerable so that you can iterate over the child nodes.
|
30
|
+
# @yieldparam child [TreeStand::Node]
|
31
|
+
# @return [Enumerator]
|
32
|
+
def each
|
33
|
+
@ts_node.each do |child|
|
34
|
+
yield TreeStand::Node.new(@tree, child)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return [TreeStand::Node]
|
39
|
+
def parent
|
40
|
+
TreeStand::Node.new(@tree, @ts_node.parent)
|
41
|
+
end
|
42
|
+
|
43
|
+
# A convience method for getting the text of the node. Each TreeStand Node
|
44
|
+
# wraps the parent tree and has access to the source document.
|
45
|
+
# @return [String]
|
46
|
+
def text
|
47
|
+
@tree.document[@ts_node.start_byte...@ts_node.end_byte]
|
48
|
+
end
|
49
|
+
|
50
|
+
# This class overrides the `method_missing` method to delegate to the
|
51
|
+
# node's named children. This allows you to write code like this:
|
52
|
+
# root = tree.root_node
|
53
|
+
# child = root.expression
|
54
|
+
# @overload method_missing(field_name)
|
55
|
+
# @param name [Symbol, String]
|
56
|
+
# @return [TreeStand::Node] Child node for the given field name
|
57
|
+
# @raise [NoMethodError] Raised if the node does not have child with name `field_name`
|
58
|
+
#
|
59
|
+
# @overload method_missing(method_name, *args, &block)
|
60
|
+
# @raise [NoMethodError]
|
61
|
+
def method_missing(method, *args, &block)
|
62
|
+
return super unless @fields.include?(method.to_s)
|
63
|
+
TreeStand::Node.new(@tree, @ts_node.public_send(method, *args, &block))
|
64
|
+
end
|
65
|
+
|
66
|
+
# @param other [Object]
|
67
|
+
# @return [bool]
|
68
|
+
def ==(other)
|
69
|
+
return false unless other.is_a?(TreeStand::Node)
|
70
|
+
|
71
|
+
range == other.range &&
|
72
|
+
type == other.type &&
|
73
|
+
text == other.text
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module TreeStand
|
2
|
+
# Wrapper around the TreeSitter parser. It looks up the parser by filename in
|
3
|
+
# the configured parsers directory.
|
4
|
+
# @example
|
5
|
+
# TreeStand.configure do
|
6
|
+
# config.parser_path = "path/to/parser/folder/"
|
7
|
+
# end
|
8
|
+
#
|
9
|
+
# # Looks for a parser in `path/to/parser/folder/sql.{so,dylib}`
|
10
|
+
# sql_parser = TreeStand::Parser.new("sql")
|
11
|
+
#
|
12
|
+
# # Looks for a parser in `path/to/parser/folder/ruby.{so,dylib}`
|
13
|
+
# ruby_parser = TreeStand::Parser.new("ruby")
|
14
|
+
class Parser
|
15
|
+
# @return [TreeSitter::Language]
|
16
|
+
attr_reader :ts_language
|
17
|
+
# @return [TreeSitter::Parser]
|
18
|
+
attr_reader :ts_parser
|
19
|
+
|
20
|
+
# @param language [String]
|
21
|
+
def initialize(language)
|
22
|
+
@language_string = language
|
23
|
+
@ts_language = TreeSitter::Language.load(
|
24
|
+
language,
|
25
|
+
"#{TreeStand.config.parser_path}/#{language}.so"
|
26
|
+
)
|
27
|
+
@ts_parser = TreeSitter::Parser.new.tap do |parser|
|
28
|
+
parser.language = @ts_language
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Parse the provided document with the TreeSitter parser.
|
33
|
+
# @param tree [TreeStand::Tree]
|
34
|
+
# @param document [String]
|
35
|
+
# @return [TreeStand::Tree]
|
36
|
+
def parse_string(tree, document)
|
37
|
+
# There's a bug with passing a non-nil tree
|
38
|
+
ts_tree = @ts_parser.parse_string(nil, document)
|
39
|
+
TreeStand::Tree.new(self, ts_tree, document)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module TreeStand
|
2
|
+
# Wrapper around a TreeSitter range. This is mainly used to compare ranges.
|
3
|
+
class Range
|
4
|
+
# Point is a Struct containing the row and column from a TreeSitter point.
|
5
|
+
# TreeStand uses this to compare points.
|
6
|
+
# @!attribute [rw] row
|
7
|
+
# @return [Integer]
|
8
|
+
# @!attribute [rw] column
|
9
|
+
# @return [Integer]
|
10
|
+
Point = Struct.new(:row, :column)
|
11
|
+
|
12
|
+
# @return [Integer]
|
13
|
+
attr_reader :start_byte
|
14
|
+
# @return [Integer]
|
15
|
+
attr_reader :end_byte
|
16
|
+
# @return [TreeStand::Range::Point]
|
17
|
+
attr_reader :start_point
|
18
|
+
# @return [TreeStand::Range::Point]
|
19
|
+
attr_reader :end_point
|
20
|
+
|
21
|
+
# @api private
|
22
|
+
def initialize(start_byte:, end_byte:, start_point:, end_point:)
|
23
|
+
@start_byte = start_byte
|
24
|
+
@end_byte = end_byte
|
25
|
+
@start_point = Point.new(start_point.row, start_point.column)
|
26
|
+
@end_point = Point.new(end_point.row, end_point.column)
|
27
|
+
end
|
28
|
+
|
29
|
+
# @param other [Object]
|
30
|
+
# @return [bool]
|
31
|
+
def ==(other)
|
32
|
+
return false unless other.is_a?(TreeStand::Range)
|
33
|
+
|
34
|
+
@start_byte == other.start_byte &&
|
35
|
+
@end_byte == other.end_byte &&
|
36
|
+
@start_point == other.start_point &&
|
37
|
+
@end_point == other.end_point
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module TreeStand
|
2
|
+
# Wrapper around a TreeSitter tree.
|
3
|
+
#
|
4
|
+
# This class exposes a convient API for working with the tree. There are
|
5
|
+
# dangers in using this class. The tree is mutable and the document can be
|
6
|
+
# changed. This class does not protect against that.
|
7
|
+
#
|
8
|
+
# Some of the moetods on this class edit and re-parse the document updating
|
9
|
+
# the tree. Because the document is re-parsed, the tree will be different. Which
|
10
|
+
# means all outstanding nodes & ranges will be invalid.
|
11
|
+
#
|
12
|
+
# Methods that edit the document are suffixed with `!`, e.g. `#edit!`.
|
13
|
+
#
|
14
|
+
# It's often the case that you will want perfrom multiple edits. One such
|
15
|
+
# pattern is to call #query & #edit on all matches in a loop. It's important
|
16
|
+
# to keep the destructive nature of #edit in mind and re-issue the query
|
17
|
+
# after each edit.
|
18
|
+
#
|
19
|
+
# Another thing to keep in mind is that edits done later in the document will
|
20
|
+
# likely not affect the ranges that occur earlier in the document. This can
|
21
|
+
# be a convient property that could allow you to apply edits in a reverse order.
|
22
|
+
# This is not always possible and depends on the edits you make, beware that
|
23
|
+
# the tree will be different after each edit and this approach may cause bugs.
|
24
|
+
class Tree
|
25
|
+
# @return [String]
|
26
|
+
attr_reader :document
|
27
|
+
# @return [TreeSitter::Tree]
|
28
|
+
attr_reader :ts_tree
|
29
|
+
|
30
|
+
# @api private
|
31
|
+
def initialize(parser, tree, document)
|
32
|
+
@parser = parser
|
33
|
+
@ts_tree = tree
|
34
|
+
@document = document
|
35
|
+
end
|
36
|
+
|
37
|
+
# @return [TreeStand::Node]
|
38
|
+
def root_node
|
39
|
+
TreeStand::Node.new(self, @ts_tree.root_node)
|
40
|
+
end
|
41
|
+
|
42
|
+
# TreeSitter uses a `TreeSitter::Cursor` to iterate over matches by calling
|
43
|
+
# `curser#next_match` repeatedly until it returns `nil`.
|
44
|
+
#
|
45
|
+
# This method does all of that for you and collects them into an array.
|
46
|
+
#
|
47
|
+
# @see TreeStand::Match
|
48
|
+
# @see TreeStand::Capture
|
49
|
+
#
|
50
|
+
# @param query_string [String]
|
51
|
+
# @return [Array<TreeStand::Match>]
|
52
|
+
def query(query_string)
|
53
|
+
ts_query = TreeSitter::Query.new(@parser.ts_language, query_string)
|
54
|
+
ts_cursor = TreeSitter::QueryCursor.exec(ts_query, @ts_tree.root_node)
|
55
|
+
matches = []
|
56
|
+
while match = ts_cursor.next_match
|
57
|
+
matches << TreeStand::Match.new(self, ts_query, match)
|
58
|
+
end
|
59
|
+
matches
|
60
|
+
end
|
61
|
+
|
62
|
+
# This method replaces the section of the document specified by range and
|
63
|
+
# replaces it with the provided text. Then it will reparse the document and
|
64
|
+
# update the tree!
|
65
|
+
# @param range [TreeStand::Range]
|
66
|
+
# @param replacement [String]
|
67
|
+
# @return [void]
|
68
|
+
def edit!(range, replacement)
|
69
|
+
new_document = +""
|
70
|
+
new_document << @document[0...range.start_byte]
|
71
|
+
new_document << replacement
|
72
|
+
new_document << @document[range.end_byte..-1]
|
73
|
+
replace_with_new_doc(new_document)
|
74
|
+
end
|
75
|
+
|
76
|
+
# This method deletes the section of the document specified by range Then
|
77
|
+
# it will reparse the document and update the tree!
|
78
|
+
# @param range [TreeStand::Range]
|
79
|
+
# @return [void]
|
80
|
+
def delete!(range)
|
81
|
+
new_document = +""
|
82
|
+
new_document << @document[0...range.start_byte]
|
83
|
+
new_document << @document[range.end_byte..-1]
|
84
|
+
replace_with_new_doc(new_document)
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def replace_with_new_doc(new_document)
|
90
|
+
@document = new_document
|
91
|
+
new_tree = @parser.parse_string(@ts_tree, @document)
|
92
|
+
@ts_tree = new_tree.ts_tree
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module TreeStand
|
2
|
+
# Depth-first traversal through the tree, calling hooks at each stop.
|
3
|
+
#
|
4
|
+
# Hooks are language depended so are defined by creating methods on the
|
5
|
+
# visitor with the form `on_#{node.type}`.
|
6
|
+
#
|
7
|
+
# You can also define an `_on_default` method to run on all nodes.
|
8
|
+
#
|
9
|
+
# @example Create a visitor counting certain nodes
|
10
|
+
# class CountingVisitor < TreeStand::Visitor
|
11
|
+
# attr_reader :count
|
12
|
+
#
|
13
|
+
# def initialize(document, type:)
|
14
|
+
# super(document)
|
15
|
+
# @type = type
|
16
|
+
# @count = 0
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# def on_predicate(node)
|
20
|
+
# # if this node matches our search, increment the counter
|
21
|
+
# @count += 1 if node.type == @type
|
22
|
+
# end
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# # Initialize a visitor
|
26
|
+
# visitor = CountingVisitor.new(document, :predicate).visit
|
27
|
+
# # Check the result
|
28
|
+
# visitor.count
|
29
|
+
# # => 3
|
30
|
+
class Visitor
|
31
|
+
# @param node [TreeStand::Node]
|
32
|
+
def initialize(node)
|
33
|
+
@node = node
|
34
|
+
end
|
35
|
+
|
36
|
+
# Run the visitor on the document and return self. Allows chaining create and visit.
|
37
|
+
# @example
|
38
|
+
# visitor = CountingVisitor.new(node, :predicate).visit
|
39
|
+
# @return [self]
|
40
|
+
def visit
|
41
|
+
visit_node(@node)
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def visit_node(node)
|
48
|
+
if respond_to?("on_#{node.type}")
|
49
|
+
public_send("on_#{node.type}", node)
|
50
|
+
elsif respond_to?(:_on_default)
|
51
|
+
_on_default(node)
|
52
|
+
end
|
53
|
+
|
54
|
+
node.each do |child|
|
55
|
+
visit_node(child)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/tree_stand.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require "tree_sitter"
|
2
|
+
require "zeitwerk"
|
3
|
+
|
4
|
+
loader = Zeitwerk::Loader.for_gem
|
5
|
+
loader.setup
|
6
|
+
|
7
|
+
# TreeStand is a high-level Ruby wrapper for {https://tree-sitter.github.io/tree-sitter tree-sitter} bindings. It makes
|
8
|
+
# it easier to configure the parsers, and work with the underlying syntax tree.
|
9
|
+
module TreeStand
|
10
|
+
# Common Ancestor for all TreeStand errors.
|
11
|
+
class Error < StandardError; end
|
12
|
+
|
13
|
+
class << self
|
14
|
+
# Easy configuration of the gem.
|
15
|
+
#
|
16
|
+
# @example
|
17
|
+
# TreeStand.configure do
|
18
|
+
# config.parser_path = "path/to/parser/folder/"
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# sql_parser = TreeStand::Parser.new("sql")
|
22
|
+
# ruby_parser = TreeStand::Parser.new("ruby")
|
23
|
+
# @return [void]
|
24
|
+
def configure(&block)
|
25
|
+
instance_eval(&block)
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return [TreeStand::Config]
|
29
|
+
def config
|
30
|
+
@config ||= Config.new
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/parsers/.gitkeep
ADDED
File without changes
|
data/service.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
classification: library
|
data/tree_stand.gemspec
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
lib = File.expand_path("../lib", __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require "tree_stand/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "tree_stand"
|
7
|
+
spec.version = TreeStand::VERSION
|
8
|
+
spec.authors = ["derekstride"]
|
9
|
+
spec.email = ["derek@stride.host", "opensource@shopify.com"]
|
10
|
+
|
11
|
+
spec.summary = "A high-level Ruby wrapper for the Tree-sitter bindings"
|
12
|
+
spec.homepage = "https://github.com/Shopify/tree_stand"
|
13
|
+
spec.license = "MIT"
|
14
|
+
|
15
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
16
|
+
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
18
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
19
|
+
spec.metadata["changelog_uri"] = spec.homepage
|
20
|
+
|
21
|
+
# Specify which files should be added to the gem when it is released.
|
22
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
23
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
24
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
25
|
+
end
|
26
|
+
spec.bindir = "exe"
|
27
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
|
+
spec.require_paths = ["lib"]
|
29
|
+
|
30
|
+
spec.add_dependency "zeitwerk"
|
31
|
+
|
32
|
+
spec.add_development_dependency "bundler", "~> 2.3"
|
33
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
34
|
+
spec.add_development_dependency "minitest"
|
35
|
+
spec.add_development_dependency "pry-byebug"
|
36
|
+
end
|
metadata
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tree_stand
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- derekstride
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-12-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: zeitwerk
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.3'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: minitest
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: pry-byebug
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description:
|
84
|
+
email:
|
85
|
+
- derek@stride.host
|
86
|
+
- opensource@shopify.com
|
87
|
+
executables: []
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- ".github/workflows/ci.yml"
|
92
|
+
- ".github/workflows/cla.yml"
|
93
|
+
- ".gitignore"
|
94
|
+
- ".shopify-build/VERSION"
|
95
|
+
- ".shopify-build/tree-stand-publish-package.yml"
|
96
|
+
- CODE_OF_CONDUCT.md
|
97
|
+
- Gemfile
|
98
|
+
- LICENSE.txt
|
99
|
+
- README.md
|
100
|
+
- Rakefile
|
101
|
+
- bin/console
|
102
|
+
- bin/setup
|
103
|
+
- deploy/install-treesitter
|
104
|
+
- lib/tree_stand.rb
|
105
|
+
- lib/tree_stand/ast_modifier.rb
|
106
|
+
- lib/tree_stand/capture.rb
|
107
|
+
- lib/tree_stand/config.rb
|
108
|
+
- lib/tree_stand/match.rb
|
109
|
+
- lib/tree_stand/node.rb
|
110
|
+
- lib/tree_stand/parser.rb
|
111
|
+
- lib/tree_stand/range.rb
|
112
|
+
- lib/tree_stand/tree.rb
|
113
|
+
- lib/tree_stand/version.rb
|
114
|
+
- lib/tree_stand/visitor.rb
|
115
|
+
- parsers/.gitkeep
|
116
|
+
- service.yml
|
117
|
+
- shipit.production.yml
|
118
|
+
- tree_stand.gemspec
|
119
|
+
homepage: https://github.com/Shopify/tree_stand
|
120
|
+
licenses:
|
121
|
+
- MIT
|
122
|
+
metadata:
|
123
|
+
allowed_push_host: https://rubygems.org
|
124
|
+
homepage_uri: https://github.com/Shopify/tree_stand
|
125
|
+
source_code_uri: https://github.com/Shopify/tree_stand
|
126
|
+
changelog_uri: https://github.com/Shopify/tree_stand
|
127
|
+
post_install_message:
|
128
|
+
rdoc_options: []
|
129
|
+
require_paths:
|
130
|
+
- lib
|
131
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
132
|
+
requirements:
|
133
|
+
- - ">="
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: '0'
|
136
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
137
|
+
requirements:
|
138
|
+
- - ">="
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
version: '0'
|
141
|
+
requirements: []
|
142
|
+
rubygems_version: 3.3.3
|
143
|
+
signing_key:
|
144
|
+
specification_version: 4
|
145
|
+
summary: A high-level Ruby wrapper for the Tree-sitter bindings
|
146
|
+
test_files: []
|