tree_sitter 0.1.0-x86_64-linux
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 +7 -0
- data/CHANGELOG.md +12 -0
- data/LICENSE.txt +21 -0
- data/Makefile +116 -0
- data/README.md +466 -0
- data/lib/tree_sitter/3.2/tree_sitter.so +0 -0
- data/lib/tree_sitter/3.3/tree_sitter.so +0 -0
- data/lib/tree_sitter/3.4/tree_sitter.so +0 -0
- data/lib/tree_sitter/4.0/tree_sitter.so +0 -0
- data/lib/tree_sitter/formatting.rb +236 -0
- data/lib/tree_sitter/inserter.rb +306 -0
- data/lib/tree_sitter/query_rewriter.rb +314 -0
- data/lib/tree_sitter/refactor.rb +214 -0
- data/lib/tree_sitter/rewriter.rb +155 -0
- data/lib/tree_sitter/transformer.rb +324 -0
- data/lib/tree_sitter/version.rb +5 -0
- data/lib/tree_sitter.rb +25 -0
- metadata +96 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: d8c782c0496a1303ac908be404481b5a5c3689952689e0d5039ff75fad6d2dd5
|
|
4
|
+
data.tar.gz: 3ed5e9d23d35958672860377a90a2227bb316c475d8a8c7ec8aaea6d22c2be74
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: ecfbc952e455d3034e6c66808245218f907a68a4c6868d80a3efc11eb6f5e91ae317e578be991babee2172d962fdd8effc73d6da6d28259044130811496a6620
|
|
7
|
+
data.tar.gz: 115d5a2b323b9841d6d90d27af3a6e34abb2bce91194fe9af62e6f3b87bdabd393c21e19c6b12d86712f5be3be9f6664f9f7c8e45de87096bcb675e84b8aebcd
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# [v0.1.0] - 13-01-2026
|
|
2
|
+
## What's Changed
|
|
3
|
+
* Bump libloading from 0.8.9 to 0.9.0 by @dependabot[bot] in https://github.com/gjtorikian/tree_sitter/pull/2
|
|
4
|
+
* Bump tree-sitter from 0.25.10 to 0.26.3 by @dependabot[bot] in https://github.com/gjtorikian/tree_sitter/pull/3
|
|
5
|
+
* Bump actions/checkout from 4 to 6 by @dependabot[bot] in https://github.com/gjtorikian/tree_sitter/pull/1
|
|
6
|
+
* Test build bump by @gjtorikian in https://github.com/gjtorikian/tree_sitter/pull/4
|
|
7
|
+
|
|
8
|
+
## New Contributors
|
|
9
|
+
* @dependabot[bot] made their first contribution in https://github.com/gjtorikian/tree_sitter/pull/2
|
|
10
|
+
* @gjtorikian made their first contribution in https://github.com/gjtorikian/tree_sitter/pull/4
|
|
11
|
+
|
|
12
|
+
**Full Changelog**: https://github.com/gjtorikian/tree_sitter/commits/v0.1.0
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Garen J. Torikian
|
|
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/Makefile
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Makefile for building tree-sitter grammars
|
|
2
|
+
|
|
3
|
+
GRAMMAR_DIR := .tree-sitter-grammars
|
|
4
|
+
|
|
5
|
+
# Platform detection
|
|
6
|
+
UNAME_S := $(shell uname -s)
|
|
7
|
+
ifeq ($(UNAME_S),Darwin)
|
|
8
|
+
EXT := dylib
|
|
9
|
+
CC_FLAGS := -shared -fPIC
|
|
10
|
+
else ifeq ($(UNAME_S),Linux)
|
|
11
|
+
EXT := so
|
|
12
|
+
CC_FLAGS := -shared -fPIC
|
|
13
|
+
else ifeq ($(OS),Windows_NT)
|
|
14
|
+
EXT := dll
|
|
15
|
+
CC_FLAGS := -shared
|
|
16
|
+
else
|
|
17
|
+
EXT := so
|
|
18
|
+
CC_FLAGS := -shared -fPIC
|
|
19
|
+
endif
|
|
20
|
+
|
|
21
|
+
GRAMMARS := rust ruby python javascript go php java c_sharp
|
|
22
|
+
|
|
23
|
+
.PHONY: all grammars clean $(GRAMMARS)
|
|
24
|
+
|
|
25
|
+
all: grammars
|
|
26
|
+
|
|
27
|
+
grammars: $(GRAMMARS)
|
|
28
|
+
|
|
29
|
+
rust: $(GRAMMAR_DIR)/rust/libtree-sitter-rust.$(EXT)
|
|
30
|
+
$(GRAMMAR_DIR)/rust/libtree-sitter-rust.$(EXT):
|
|
31
|
+
@mkdir -p $(GRAMMAR_DIR)
|
|
32
|
+
@if [ ! -d "$(GRAMMAR_DIR)/rust" ]; then \
|
|
33
|
+
echo "Cloning tree-sitter-rust..."; \
|
|
34
|
+
git clone --depth 1 https://github.com/tree-sitter/tree-sitter-rust.git $(GRAMMAR_DIR)/rust; \
|
|
35
|
+
fi
|
|
36
|
+
@echo "Building tree-sitter-rust..."
|
|
37
|
+
@cd $(GRAMMAR_DIR)/rust && $(CC) $(CC_FLAGS) -I src src/parser.c src/scanner.c -o libtree-sitter-rust.$(EXT)
|
|
38
|
+
|
|
39
|
+
ruby: $(GRAMMAR_DIR)/ruby/libtree-sitter-ruby.$(EXT)
|
|
40
|
+
$(GRAMMAR_DIR)/ruby/libtree-sitter-ruby.$(EXT):
|
|
41
|
+
@mkdir -p $(GRAMMAR_DIR)
|
|
42
|
+
@if [ ! -d "$(GRAMMAR_DIR)/ruby" ]; then \
|
|
43
|
+
echo "Cloning tree-sitter-ruby..."; \
|
|
44
|
+
git clone --depth 1 https://github.com/tree-sitter/tree-sitter-ruby.git $(GRAMMAR_DIR)/ruby; \
|
|
45
|
+
fi
|
|
46
|
+
@echo "Building tree-sitter-ruby..."
|
|
47
|
+
@cd $(GRAMMAR_DIR)/ruby && $(CC) $(CC_FLAGS) -I src src/parser.c src/scanner.c -o libtree-sitter-ruby.$(EXT)
|
|
48
|
+
|
|
49
|
+
python: $(GRAMMAR_DIR)/python/libtree-sitter-python.$(EXT)
|
|
50
|
+
$(GRAMMAR_DIR)/python/libtree-sitter-python.$(EXT):
|
|
51
|
+
@mkdir -p $(GRAMMAR_DIR)
|
|
52
|
+
@if [ ! -d "$(GRAMMAR_DIR)/python" ]; then \
|
|
53
|
+
echo "Cloning tree-sitter-python..."; \
|
|
54
|
+
git clone --depth 1 https://github.com/tree-sitter/tree-sitter-python.git $(GRAMMAR_DIR)/python; \
|
|
55
|
+
fi
|
|
56
|
+
@echo "Building tree-sitter-python..."
|
|
57
|
+
@cd $(GRAMMAR_DIR)/python && $(CC) $(CC_FLAGS) -I src src/parser.c src/scanner.c -o libtree-sitter-python.$(EXT)
|
|
58
|
+
|
|
59
|
+
javascript: $(GRAMMAR_DIR)/javascript/libtree-sitter-javascript.$(EXT)
|
|
60
|
+
$(GRAMMAR_DIR)/javascript/libtree-sitter-javascript.$(EXT):
|
|
61
|
+
@mkdir -p $(GRAMMAR_DIR)
|
|
62
|
+
@if [ ! -d "$(GRAMMAR_DIR)/javascript" ]; then \
|
|
63
|
+
echo "Cloning tree-sitter-javascript..."; \
|
|
64
|
+
git clone --depth 1 https://github.com/tree-sitter/tree-sitter-javascript.git $(GRAMMAR_DIR)/javascript; \
|
|
65
|
+
fi
|
|
66
|
+
@echo "Building tree-sitter-javascript..."
|
|
67
|
+
@cd $(GRAMMAR_DIR)/javascript && $(CC) $(CC_FLAGS) -I src src/parser.c src/scanner.c -o libtree-sitter-javascript.$(EXT)
|
|
68
|
+
|
|
69
|
+
go: $(GRAMMAR_DIR)/go/libtree-sitter-go.$(EXT)
|
|
70
|
+
$(GRAMMAR_DIR)/go/libtree-sitter-go.$(EXT):
|
|
71
|
+
@mkdir -p $(GRAMMAR_DIR)
|
|
72
|
+
@if [ ! -d "$(GRAMMAR_DIR)/go" ]; then \
|
|
73
|
+
echo "Cloning tree-sitter-go..."; \
|
|
74
|
+
git clone --depth 1 https://github.com/tree-sitter/tree-sitter-go.git $(GRAMMAR_DIR)/go; \
|
|
75
|
+
fi
|
|
76
|
+
@echo "Building tree-sitter-go..."
|
|
77
|
+
@cd $(GRAMMAR_DIR)/go && $(CC) $(CC_FLAGS) -I src src/parser.c -o libtree-sitter-go.$(EXT)
|
|
78
|
+
|
|
79
|
+
php: $(GRAMMAR_DIR)/php/libtree-sitter-php.$(EXT)
|
|
80
|
+
$(GRAMMAR_DIR)/php/libtree-sitter-php.$(EXT):
|
|
81
|
+
@mkdir -p $(GRAMMAR_DIR)
|
|
82
|
+
@if [ ! -d "$(GRAMMAR_DIR)/php" ]; then \
|
|
83
|
+
echo "Cloning tree-sitter-php..."; \
|
|
84
|
+
git clone --depth 1 https://github.com/tree-sitter/tree-sitter-php.git $(GRAMMAR_DIR)/php; \
|
|
85
|
+
fi
|
|
86
|
+
@echo "Building tree-sitter-php..."
|
|
87
|
+
@cd $(GRAMMAR_DIR)/php/php && $(CC) $(CC_FLAGS) -I src src/parser.c src/scanner.c -o ../libtree-sitter-php.$(EXT)
|
|
88
|
+
|
|
89
|
+
java: $(GRAMMAR_DIR)/java/libtree-sitter-java.$(EXT)
|
|
90
|
+
$(GRAMMAR_DIR)/java/libtree-sitter-java.$(EXT):
|
|
91
|
+
@mkdir -p $(GRAMMAR_DIR)
|
|
92
|
+
@if [ ! -d "$(GRAMMAR_DIR)/java" ]; then \
|
|
93
|
+
echo "Cloning tree-sitter-java..."; \
|
|
94
|
+
git clone --depth 1 https://github.com/tree-sitter/tree-sitter-java.git $(GRAMMAR_DIR)/java; \
|
|
95
|
+
fi
|
|
96
|
+
@echo "Building tree-sitter-java..."
|
|
97
|
+
@cd $(GRAMMAR_DIR)/java && $(CC) $(CC_FLAGS) -I src src/parser.c -o libtree-sitter-java.$(EXT)
|
|
98
|
+
|
|
99
|
+
c_sharp: $(GRAMMAR_DIR)/c_sharp/libtree-sitter-c_sharp.$(EXT)
|
|
100
|
+
$(GRAMMAR_DIR)/c_sharp/libtree-sitter-c_sharp.$(EXT):
|
|
101
|
+
@mkdir -p $(GRAMMAR_DIR)
|
|
102
|
+
@if [ ! -d "$(GRAMMAR_DIR)/c_sharp" ]; then \
|
|
103
|
+
echo "Cloning tree-sitter-c-sharp..."; \
|
|
104
|
+
git clone --depth 1 https://github.com/tree-sitter/tree-sitter-c-sharp.git $(GRAMMAR_DIR)/c_sharp; \
|
|
105
|
+
fi
|
|
106
|
+
@echo "Building tree-sitter-c-sharp..."
|
|
107
|
+
@cd $(GRAMMAR_DIR)/c_sharp && $(CC) $(CC_FLAGS) -I src src/parser.c src/scanner.c -o libtree-sitter-c_sharp.$(EXT)
|
|
108
|
+
|
|
109
|
+
clean:
|
|
110
|
+
rm -rf $(GRAMMAR_DIR)
|
|
111
|
+
|
|
112
|
+
# Show which extension will be used
|
|
113
|
+
info:
|
|
114
|
+
@echo "Platform: $(UNAME_S)"
|
|
115
|
+
@echo "Extension: $(EXT)"
|
|
116
|
+
@echo "Grammar directory: $(GRAMMAR_DIR)"
|
data/README.md
ADDED
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
# TreeSitter
|
|
2
|
+
|
|
3
|
+
Ruby bindings for [tree-sitter](https://tree-sitter.github.io/) with code transformation and refactoring capabilities. Parse source code using tree-sitter with a Ruby-friendly API, supporting multiple languages via dynamic grammar loading. Written in Rust, wrapped in Ruby.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Multi-language support** - Load any tree-sitter grammar dynamically
|
|
8
|
+
- **Full AST navigation** - Traverse nodes, access children, siblings, parents
|
|
9
|
+
- **Query support** - Pattern-based node searching using S-expressions
|
|
10
|
+
- **Rewriter API** - Programmatic code transformations (replace, remove, insert, wrap)
|
|
11
|
+
- **Query-based editing** - Bulk edits using tree-sitter queries
|
|
12
|
+
- **Transforms** - Move, copy, swap, and reorder nodes
|
|
13
|
+
- **Insertions** - Syntax-aware insertions with automatic indentation
|
|
14
|
+
|
|
15
|
+
The main difference with this gem and others like it is that this focuses on parsing once, transforming, and outputting. It doesn't really work as well for live syntax highlighting or fitting into tools that need sub-millisecond updates (as a user is typing, for instance).
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
Add to your Gemfile:
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
gem "tree_sitter"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Then run:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
bundle install
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Note:** You need tree-sitter grammar shared libraries (`.so` or `.dylib` files) for the languages you want to parse. See [Grammar Setup](#grammar-setup) below.
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
### Basic Parsing
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
require "tree_sitter"
|
|
39
|
+
|
|
40
|
+
# Register a language from a shared library
|
|
41
|
+
TreeSitter.register_language("ruby", "path/to/libtree-sitter-ruby.{so,dylib}")
|
|
42
|
+
|
|
43
|
+
# Create a parser and set the language
|
|
44
|
+
parser = TreeSitter::Parser.new
|
|
45
|
+
parser.language = "rust"
|
|
46
|
+
|
|
47
|
+
# Parse source code
|
|
48
|
+
source = "fn add(a: i32, b: i32) -> i32 { a + b }"
|
|
49
|
+
tree = parser.parse(source)
|
|
50
|
+
|
|
51
|
+
# Navigate the AST
|
|
52
|
+
root = tree.root_node
|
|
53
|
+
puts root.kind # => "source_file"
|
|
54
|
+
puts root.child_count # => 1
|
|
55
|
+
|
|
56
|
+
fn_item = root.child(0)
|
|
57
|
+
puts fn_item.kind # => "function_item"
|
|
58
|
+
|
|
59
|
+
fn_name = fn_item.child_by_field_name("name")
|
|
60
|
+
puts fn_name.text # => "add"
|
|
61
|
+
|
|
62
|
+
# Access the original source and language from the tree
|
|
63
|
+
puts tree.source # => "fn add(a: i32, b: i32) -> i32 { a + b }"
|
|
64
|
+
puts tree.language.name # => "rust"
|
|
65
|
+
|
|
66
|
+
# Set a parse timeout (in microseconds) to prevent hanging on large inputs
|
|
67
|
+
parser.timeout_micros = 1_000_000 # 1 second
|
|
68
|
+
tree = parser.parse(very_large_source) # Returns nil if timeout exceeded
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Multi-Language Support
|
|
72
|
+
|
|
73
|
+
```ruby
|
|
74
|
+
# Register multiple languages
|
|
75
|
+
TreeSitter.register_language("ruby", ENV["TREE_SITTER_RUBY_PATH"])
|
|
76
|
+
TreeSitter.register_language("python", ENV["TREE_SITTER_PYTHON_PATH"])
|
|
77
|
+
|
|
78
|
+
# Parse Ruby code
|
|
79
|
+
ruby_parser = TreeSitter::Parser.new
|
|
80
|
+
ruby_parser.language = "ruby"
|
|
81
|
+
ruby_tree = ruby_parser.parse("def hello; puts 'hi'; end")
|
|
82
|
+
|
|
83
|
+
# Parse Python code
|
|
84
|
+
python_parser = TreeSitter::Parser.new
|
|
85
|
+
python_parser.language = "python"
|
|
86
|
+
python_tree = python_parser.parse("def hello():\n print('hi')")
|
|
87
|
+
|
|
88
|
+
# List registered languages
|
|
89
|
+
TreeSitter.languages # => ["ruby", "python"]
|
|
90
|
+
|
|
91
|
+
# Language metadata
|
|
92
|
+
lang = TreeSitter.language("ruby")
|
|
93
|
+
lang.name # => "ruby"
|
|
94
|
+
lang.version # => 15 (ABI version)
|
|
95
|
+
lang.node_kind_count # => 200 (number of node types in the grammar)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Node Operations
|
|
99
|
+
|
|
100
|
+
Once you have a node from the AST, you can navigate, inspect, and extract information:
|
|
101
|
+
|
|
102
|
+
```ruby
|
|
103
|
+
# Get nodes to work with
|
|
104
|
+
root = tree.root_node
|
|
105
|
+
fn_item = root.child(0)
|
|
106
|
+
fn_name = fn_item.child_by_field_name("name")
|
|
107
|
+
|
|
108
|
+
# === Navigation ===
|
|
109
|
+
fn_item.parent # => #<TreeSitter::Node kind="source_file" ...>
|
|
110
|
+
fn_item.child(0) # => #<TreeSitter::Node kind="fn" ...>
|
|
111
|
+
fn_item.child_count # => 6
|
|
112
|
+
fn_item.children # => [#<Node>, #<Node>, ...] (array of all children)
|
|
113
|
+
fn_item.named_child(0) # => #<TreeSitter::Node kind="identifier" ...>
|
|
114
|
+
fn_item.named_child_count # => 4
|
|
115
|
+
fn_item.named_children # => [#<Node>, ...] (array of named children only)
|
|
116
|
+
fn_item.child_by_field_name("name") # => #<TreeSitter::Node kind="identifier" ...>
|
|
117
|
+
|
|
118
|
+
# Sibling navigation (using parameters as example)
|
|
119
|
+
params = fn_item.child_by_field_name("parameters")
|
|
120
|
+
first_param = params.named_child(0)
|
|
121
|
+
first_param.next_sibling # => #<TreeSitter::Node kind="," ...>
|
|
122
|
+
first_param.next_named_sibling # => #<TreeSitter::Node kind="parameter" ...>
|
|
123
|
+
|
|
124
|
+
# === Properties ===
|
|
125
|
+
fn_item.kind # => "function_item"
|
|
126
|
+
fn_item.type # => "function_item" (alias for kind)
|
|
127
|
+
fn_item.kind_id # => 188
|
|
128
|
+
fn_item.named? # => true (not anonymous like "{" or ")")
|
|
129
|
+
fn_item.missing? # => false (not inserted by parser for error recovery)
|
|
130
|
+
fn_item.extra? # => false (not extra like comments)
|
|
131
|
+
fn_item.error? # => false (not an ERROR node)
|
|
132
|
+
fn_item.has_error? # => false (no errors in subtree)
|
|
133
|
+
fn_item.has_changes? # => false (not changed in incremental parse)
|
|
134
|
+
|
|
135
|
+
# === Position ===
|
|
136
|
+
fn_name.start_byte # => 3
|
|
137
|
+
fn_name.end_byte # => 6
|
|
138
|
+
fn_name.start_point # => #<TreeSitter::Point row=0 column=3>
|
|
139
|
+
fn_name.end_point # => #<TreeSitter::Point row=0 column=6>
|
|
140
|
+
fn_name.range # => #<TreeSitter::Range start_byte=3 end_byte=6 size=3>
|
|
141
|
+
|
|
142
|
+
# === Text & Display ===
|
|
143
|
+
fn_name.text # => "add"
|
|
144
|
+
fn_name.to_sexp # => "(identifier)"
|
|
145
|
+
fn_name.to_s # => "(identifier)" (alias for to_sexp)
|
|
146
|
+
fn_name.inspect # => "#<TreeSitter::Node kind=\"identifier\" start_byte=3 end_byte=6>"
|
|
147
|
+
|
|
148
|
+
# === Comparison ===
|
|
149
|
+
root == tree.root_node # => true
|
|
150
|
+
root.eql?(tree.root_node) # => true
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Point and Range
|
|
154
|
+
|
|
155
|
+
Nodes provide position information via `Point` and `Range` objects:
|
|
156
|
+
|
|
157
|
+
```ruby
|
|
158
|
+
# Point represents a position (row/column)
|
|
159
|
+
point = fn_name.start_point
|
|
160
|
+
point.row # => 0
|
|
161
|
+
point.column # => 3
|
|
162
|
+
point.to_a # => [0, 3]
|
|
163
|
+
point.inspect # => "#<TreeSitter::Point row=0 column=3>"
|
|
164
|
+
|
|
165
|
+
# Create points directly
|
|
166
|
+
point = TreeSitter::Point.new(0, 3)
|
|
167
|
+
point == fn_name.start_point # => true
|
|
168
|
+
|
|
169
|
+
# Range represents a span with byte offsets and points
|
|
170
|
+
range = fn_name.range
|
|
171
|
+
range.start_byte # => 3
|
|
172
|
+
range.end_byte # => 6
|
|
173
|
+
range.size # => 3
|
|
174
|
+
range.start_point # => #<TreeSitter::Point row=0 column=3>
|
|
175
|
+
range.end_point # => #<TreeSitter::Point row=0 column=6>
|
|
176
|
+
range.inspect # => "#<TreeSitter::Range start_byte=3 end_byte=6 size=3>"
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Query-Based Node Finding
|
|
180
|
+
|
|
181
|
+
Use tree-sitter queries to find nodes matching patterns:
|
|
182
|
+
|
|
183
|
+
```ruby
|
|
184
|
+
# Create a query with capture names (@fn_name, @fn)
|
|
185
|
+
lang = TreeSitter.language("rust")
|
|
186
|
+
query = TreeSitter::Query.new(lang, "(function_item name: (identifier) @fn_name) @fn")
|
|
187
|
+
|
|
188
|
+
# Query properties
|
|
189
|
+
query.pattern_count # => 1
|
|
190
|
+
query.capture_names # => ["fn_name", "fn"]
|
|
191
|
+
|
|
192
|
+
# Execute query with a cursor
|
|
193
|
+
cursor = TreeSitter::QueryCursor.new
|
|
194
|
+
|
|
195
|
+
# Get all matches (each match contains all captures for one pattern match)
|
|
196
|
+
matches = cursor.matches(query, tree.root_node, source)
|
|
197
|
+
matches.length # => 4 (one per function in file)
|
|
198
|
+
|
|
199
|
+
match = matches.first
|
|
200
|
+
match.pattern_index # => 0
|
|
201
|
+
match.captures # => [#<QueryCapture>, #<QueryCapture>]
|
|
202
|
+
match.captures.length # => 2
|
|
203
|
+
|
|
204
|
+
# Get all captures directly (flattened list)
|
|
205
|
+
cursor = TreeSitter::QueryCursor.new # create new cursor
|
|
206
|
+
captures = cursor.captures(query, tree.root_node, source)
|
|
207
|
+
captures.length # => 8 (2 captures x 4 functions)
|
|
208
|
+
|
|
209
|
+
capture = captures.first
|
|
210
|
+
capture.name # => "fn"
|
|
211
|
+
capture.node # => #<TreeSitter::Node kind="function_item" ...>
|
|
212
|
+
capture.node.text # => "fn add(a: i32, b: i32) -> i32 {\n a + b\n}"
|
|
213
|
+
|
|
214
|
+
# Iterate over captures
|
|
215
|
+
captures.each do |cap|
|
|
216
|
+
puts "#{cap.name}: #{cap.node.text[0..20]}..." if cap.name == "fn_name"
|
|
217
|
+
end
|
|
218
|
+
# => fn_name: add...
|
|
219
|
+
# => fn_name: new...
|
|
220
|
+
# => fn_name: distance...
|
|
221
|
+
# => fn_name: main...
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Code Rewriting
|
|
225
|
+
|
|
226
|
+
```ruby
|
|
227
|
+
source = <<~RUST
|
|
228
|
+
fn add(a: i32, b: i32) -> i32 {
|
|
229
|
+
a + b
|
|
230
|
+
}
|
|
231
|
+
RUST
|
|
232
|
+
|
|
233
|
+
tree = parser.parse(source)
|
|
234
|
+
fn_item = tree.root_node.child(0)
|
|
235
|
+
fn_name = fn_item.child_by_field_name("name")
|
|
236
|
+
|
|
237
|
+
# Create a rewriter and apply edits
|
|
238
|
+
new_source = TreeSitter::Rewriter.new(source, tree)
|
|
239
|
+
.replace(fn_name, "sum")
|
|
240
|
+
.insert_before(fn_item, "#[inline]\n")
|
|
241
|
+
.rewrite
|
|
242
|
+
|
|
243
|
+
puts new_source
|
|
244
|
+
# => #[inline]
|
|
245
|
+
# => fn sum(a: i32, b: i32) -> i32 {
|
|
246
|
+
# => a + b
|
|
247
|
+
# => }
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
#### Rewriter Operations
|
|
251
|
+
|
|
252
|
+
```ruby
|
|
253
|
+
rewriter = TreeSitter::Rewriter.new(source, tree)
|
|
254
|
+
|
|
255
|
+
# Replace a node's text
|
|
256
|
+
rewriter.replace(node, "new_text")
|
|
257
|
+
|
|
258
|
+
# Remove a node
|
|
259
|
+
rewriter.remove(node)
|
|
260
|
+
|
|
261
|
+
# Insert before/after a node
|
|
262
|
+
rewriter.insert_before(node, "prefix ")
|
|
263
|
+
rewriter.insert_after(node, " suffix")
|
|
264
|
+
|
|
265
|
+
# Wrap a node
|
|
266
|
+
rewriter.wrap(node, "/* ", " */")
|
|
267
|
+
|
|
268
|
+
# All methods return self for chaining
|
|
269
|
+
rewriter
|
|
270
|
+
.replace(name_node, "new_name")
|
|
271
|
+
.insert_before(fn_node, "// Comment\n")
|
|
272
|
+
.rewrite
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Query-Based Editing
|
|
276
|
+
|
|
277
|
+
Use `QueryRewriter` to find and transform multiple nodes at once:
|
|
278
|
+
|
|
279
|
+
```ruby
|
|
280
|
+
source = <<~RUST
|
|
281
|
+
fn main() {
|
|
282
|
+
old_func();
|
|
283
|
+
old_func();
|
|
284
|
+
other_func();
|
|
285
|
+
}
|
|
286
|
+
RUST
|
|
287
|
+
|
|
288
|
+
tree = parser.parse(source)
|
|
289
|
+
lang = TreeSitter.language("rust")
|
|
290
|
+
|
|
291
|
+
# Rename all calls to old_func
|
|
292
|
+
new_source = TreeSitter::QueryRewriter.new(source, tree, lang)
|
|
293
|
+
.query('(call_expression function: (identifier) @fn)')
|
|
294
|
+
.where { |m| m.captures.any? { |c| c.node.text == "old_func" } }
|
|
295
|
+
.replace("@fn") { "new_func" }
|
|
296
|
+
.rewrite
|
|
297
|
+
|
|
298
|
+
# Add #[derive(Debug)] to all structs
|
|
299
|
+
new_source = TreeSitter::QueryRewriter.new(source, tree, lang)
|
|
300
|
+
.query('(struct_item) @struct')
|
|
301
|
+
.insert_before("@struct") { "#[derive(Debug)]\n" }
|
|
302
|
+
.rewrite
|
|
303
|
+
|
|
304
|
+
# Remove all comments
|
|
305
|
+
new_source = TreeSitter::QueryRewriter.new(source, tree, lang)
|
|
306
|
+
.query('(line_comment) @comment')
|
|
307
|
+
.remove("@comment")
|
|
308
|
+
.rewrite
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Transforms
|
|
312
|
+
|
|
313
|
+
Use `Transformer` to move, copy, swap, or reorder nodes:
|
|
314
|
+
|
|
315
|
+
```ruby
|
|
316
|
+
# Swap two parameters
|
|
317
|
+
transformer = TreeSitter::Transformer.new(source, tree)
|
|
318
|
+
.swap(param_a, param_b)
|
|
319
|
+
.rewrite
|
|
320
|
+
|
|
321
|
+
# Move a function after another
|
|
322
|
+
transformer = TreeSitter::Transformer.new(source, tree)
|
|
323
|
+
.move(first_fn, after: third_fn)
|
|
324
|
+
.rewrite
|
|
325
|
+
|
|
326
|
+
# Copy a node
|
|
327
|
+
transformer = TreeSitter::Transformer.new(source, tree)
|
|
328
|
+
.copy(struct_node, after: fn_node)
|
|
329
|
+
.rewrite
|
|
330
|
+
|
|
331
|
+
# Duplicate with transformation
|
|
332
|
+
transformer = TreeSitter::Transformer.new(source, tree)
|
|
333
|
+
.duplicate(fn_node) { |text| text.gsub("original", "copy") }
|
|
334
|
+
.rewrite
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Insertions
|
|
338
|
+
|
|
339
|
+
Use `Inserter` for syntax-aware insertions that respect indentation:
|
|
340
|
+
|
|
341
|
+
```ruby
|
|
342
|
+
# Insert at end of a block with proper indentation
|
|
343
|
+
new_source = TreeSitter::Inserter.new(source, tree)
|
|
344
|
+
.at_end_of(fn_body)
|
|
345
|
+
.insert_statement("println!(\"done\");")
|
|
346
|
+
.rewrite
|
|
347
|
+
|
|
348
|
+
# Insert a sibling function
|
|
349
|
+
new_source = TreeSitter::Inserter.new(source, tree)
|
|
350
|
+
.after(existing_fn)
|
|
351
|
+
.insert_sibling("fn new_func() {\n // body\n}")
|
|
352
|
+
.rewrite
|
|
353
|
+
|
|
354
|
+
# Insert at start of block
|
|
355
|
+
new_source = TreeSitter::Inserter.new(source, tree)
|
|
356
|
+
.at_start_of(fn_body)
|
|
357
|
+
.insert_statement("let start = Instant::now();")
|
|
358
|
+
.rewrite
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### Refactor
|
|
362
|
+
|
|
363
|
+
Use the `Refactor` module for common refactoring operations:
|
|
364
|
+
|
|
365
|
+
```ruby
|
|
366
|
+
# Rename a symbol (function, variable, type)
|
|
367
|
+
new_source = TreeSitter::Refactor.rename_symbol(
|
|
368
|
+
source, tree, lang,
|
|
369
|
+
from: "old_name",
|
|
370
|
+
to: "new_name",
|
|
371
|
+
kind: :function # or :variable, :type, :identifier
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
# Rename a struct field
|
|
375
|
+
new_source = TreeSitter::Refactor.rename_field(
|
|
376
|
+
source, tree, lang,
|
|
377
|
+
from: "old_field",
|
|
378
|
+
to: "new_field"
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
# Add attributes to matching items
|
|
382
|
+
new_source = TreeSitter::Refactor.add_attribute(
|
|
383
|
+
source, tree, lang,
|
|
384
|
+
query_pattern: "(struct_item) @item",
|
|
385
|
+
attribute: "#[derive(Debug)]"
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
# Remove items matching a pattern
|
|
389
|
+
new_source = TreeSitter::Refactor.remove_matching(
|
|
390
|
+
source, tree, lang,
|
|
391
|
+
query_pattern: "(line_comment) @item"
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
# For regexp matching, use QueryRewriter directly
|
|
395
|
+
new_source = TreeSitter::QueryRewriter.new(source, tree, lang)
|
|
396
|
+
.query('(function_item name: (identifier) @name)')
|
|
397
|
+
.where { |m| m.captures.any? { |c| c.node.text =~ /^test_/ } }
|
|
398
|
+
.replace("@name") { |node| node.text.sub(/^test_/, "spec_") }
|
|
399
|
+
.rewrite
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
## Grammar Setup
|
|
403
|
+
|
|
404
|
+
TreeSitter requires grammar shared libraries for each language you want to parse.
|
|
405
|
+
|
|
406
|
+
### Using a Makefile
|
|
407
|
+
|
|
408
|
+
A sample Makefile is included in the gem which builds all supported grammars locally:
|
|
409
|
+
|
|
410
|
+
```bash
|
|
411
|
+
make grammars
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
This clones and compiles grammars into `.tree-sitter-grammars/` with the correct extension for your platform (`.dylib` on macOS, `.so` on Linux, `.dll` on Windows).
|
|
415
|
+
|
|
416
|
+
Build individual grammars:
|
|
417
|
+
|
|
418
|
+
```bash
|
|
419
|
+
make rust # Just Rust
|
|
420
|
+
make ruby python # Ruby and Python
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
**You can use this Makefile as a reference for your own project! The gem does NOT ship with any grammars!**
|
|
424
|
+
|
|
425
|
+
### Custom Grammar Paths
|
|
426
|
+
|
|
427
|
+
You can override grammar locations with environment variables:
|
|
428
|
+
|
|
429
|
+
```bash
|
|
430
|
+
export TREE_SITTER_RUST_PATH="/path/to/libtree-sitter-rust.dylib"
|
|
431
|
+
export TREE_SITTER_RUBY_PATH="/path/to/libtree-sitter-ruby.so"
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
Environment variables take precedence over auto-discovered grammars.
|
|
435
|
+
|
|
436
|
+
## Supported Languages
|
|
437
|
+
|
|
438
|
+
This gem supports any language with a tree-sitter grammar. The test suite validates:
|
|
439
|
+
|
|
440
|
+
- Rust
|
|
441
|
+
- Ruby
|
|
442
|
+
- Python
|
|
443
|
+
- JavaScript
|
|
444
|
+
- Go
|
|
445
|
+
- PHP
|
|
446
|
+
- Java
|
|
447
|
+
- C#
|
|
448
|
+
|
|
449
|
+
## Development
|
|
450
|
+
|
|
451
|
+
After checking out the repo:
|
|
452
|
+
|
|
453
|
+
```bash
|
|
454
|
+
bin/setup # Install dependencies
|
|
455
|
+
make grammars # Compile the test grammars
|
|
456
|
+
rake compile # Compile the Rust extension
|
|
457
|
+
rake test # Run tests (requires grammar libraries)
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
## Contributing
|
|
461
|
+
|
|
462
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/gjtorikian/tree_sitter.
|
|
463
|
+
|
|
464
|
+
## License
|
|
465
|
+
|
|
466
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|