tree_sitter 0.1.0-aarch64-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
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TreeSitter
|
|
4
|
+
# Structural transformations for syntax tree nodes.
|
|
5
|
+
# Provides operations for moving, copying, swapping, and reordering nodes.
|
|
6
|
+
#
|
|
7
|
+
# @example Swap two function arguments
|
|
8
|
+
# Transformer.new(source, tree)
|
|
9
|
+
# .swap(arg1_node, arg2_node)
|
|
10
|
+
# .rewrite
|
|
11
|
+
#
|
|
12
|
+
# @example Move a function to a different location
|
|
13
|
+
# Transformer.new(source, tree)
|
|
14
|
+
# .move(fn_node, after: other_fn_node)
|
|
15
|
+
# .rewrite
|
|
16
|
+
#
|
|
17
|
+
class Transformer
|
|
18
|
+
# Represents a pending structural operation
|
|
19
|
+
Operation = Struct.new(:type, :params, keyword_init: true)
|
|
20
|
+
|
|
21
|
+
attr_reader :source, :tree, :operations
|
|
22
|
+
|
|
23
|
+
# Initialize a new Transformer
|
|
24
|
+
#
|
|
25
|
+
# @param source [String] The source code to transform
|
|
26
|
+
# @param tree [TreeSitter::Tree] The parsed syntax tree
|
|
27
|
+
# @param parser [TreeSitter::Parser, nil] Optional parser for re-parsing
|
|
28
|
+
def initialize(source, tree, parser: nil)
|
|
29
|
+
@source = source.dup.freeze
|
|
30
|
+
@tree = tree
|
|
31
|
+
@parser = parser
|
|
32
|
+
@operations = []
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Move a node to a new location (removes from original, inserts at target)
|
|
36
|
+
#
|
|
37
|
+
# @param node [TreeSitter::Node] The node to move
|
|
38
|
+
# @param before [TreeSitter::Node, nil] Insert before this node
|
|
39
|
+
# @param after [TreeSitter::Node, nil] Insert after this node
|
|
40
|
+
# @param separator [String] Separator to use (default: newline)
|
|
41
|
+
# @return [self] For method chaining
|
|
42
|
+
def move(node, before: nil, after: nil, separator: "\n")
|
|
43
|
+
raise ArgumentError, "Must specify either before: or after:" if before.nil? && after.nil?
|
|
44
|
+
raise ArgumentError, "Cannot specify both before: and after:" if before && after
|
|
45
|
+
|
|
46
|
+
@operations << Operation.new(
|
|
47
|
+
type: :move,
|
|
48
|
+
params: { node: node, before: before, after: after, separator: separator },
|
|
49
|
+
)
|
|
50
|
+
self
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Copy a node to a new location (original remains)
|
|
54
|
+
#
|
|
55
|
+
# @param node [TreeSitter::Node] The node to copy
|
|
56
|
+
# @param before [TreeSitter::Node, nil] Insert before this node
|
|
57
|
+
# @param after [TreeSitter::Node, nil] Insert after this node
|
|
58
|
+
# @param separator [String] Separator to use (default: newline)
|
|
59
|
+
# @return [self] For method chaining
|
|
60
|
+
def copy(node, before: nil, after: nil, separator: "\n")
|
|
61
|
+
raise ArgumentError, "Must specify either before: or after:" if before.nil? && after.nil?
|
|
62
|
+
raise ArgumentError, "Cannot specify both before: and after:" if before && after
|
|
63
|
+
|
|
64
|
+
@operations << Operation.new(
|
|
65
|
+
type: :copy,
|
|
66
|
+
params: { node: node, before: before, after: after, separator: separator },
|
|
67
|
+
)
|
|
68
|
+
self
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Swap two nodes
|
|
72
|
+
#
|
|
73
|
+
# @param node_a [TreeSitter::Node] First node
|
|
74
|
+
# @param node_b [TreeSitter::Node] Second node
|
|
75
|
+
# @return [self] For method chaining
|
|
76
|
+
def swap(node_a, node_b)
|
|
77
|
+
validate_non_overlapping(node_a, node_b)
|
|
78
|
+
|
|
79
|
+
@operations << Operation.new(
|
|
80
|
+
type: :swap,
|
|
81
|
+
params: { node_a: node_a, node_b: node_b },
|
|
82
|
+
)
|
|
83
|
+
self
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Reorder children of a parent node
|
|
87
|
+
#
|
|
88
|
+
# @param parent [TreeSitter::Node] The parent node
|
|
89
|
+
# @param order [Array<Integer>] New order as array of indices
|
|
90
|
+
# @return [self] For method chaining
|
|
91
|
+
# @example Reverse first three children: reorder_children(parent, [2, 1, 0, 3, 4])
|
|
92
|
+
def reorder_children(parent, order)
|
|
93
|
+
@operations << Operation.new(
|
|
94
|
+
type: :reorder,
|
|
95
|
+
params: { parent: parent, order: order },
|
|
96
|
+
)
|
|
97
|
+
self
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Extract node content to a new location with a reference
|
|
101
|
+
#
|
|
102
|
+
# @param node [TreeSitter::Node] Node to extract
|
|
103
|
+
# @param to [TreeSitter::Node] Where to place extracted content (inserted after)
|
|
104
|
+
# @param reference [String] Reference to leave in place of original
|
|
105
|
+
# @yield [String] Optional block to transform extracted content
|
|
106
|
+
# @return [self] For method chaining
|
|
107
|
+
def extract(node, to:, reference:, &wrapper)
|
|
108
|
+
@operations << Operation.new(
|
|
109
|
+
type: :extract,
|
|
110
|
+
params: { node: node, to: to, reference: reference, wrapper: wrapper },
|
|
111
|
+
)
|
|
112
|
+
self
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Duplicate a node immediately after itself
|
|
116
|
+
#
|
|
117
|
+
# @param node [TreeSitter::Node] Node to duplicate
|
|
118
|
+
# @param separator [String] Separator between original and copy
|
|
119
|
+
# @yield [String] Optional block to transform the copy
|
|
120
|
+
# @return [self] For method chaining
|
|
121
|
+
def duplicate(node, separator: "\n", &transformer)
|
|
122
|
+
@operations << Operation.new(
|
|
123
|
+
type: :duplicate,
|
|
124
|
+
params: { node: node, separator: separator, transformer: transformer },
|
|
125
|
+
)
|
|
126
|
+
self
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Apply all accumulated operations
|
|
130
|
+
#
|
|
131
|
+
# @return [String] The transformed source code
|
|
132
|
+
def rewrite
|
|
133
|
+
edits = build_edits
|
|
134
|
+
apply_edits(edits)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Apply operations and return both source and new tree
|
|
138
|
+
#
|
|
139
|
+
# @return [Array<String, Tree>] The new source and re-parsed tree
|
|
140
|
+
def rewrite_with_tree
|
|
141
|
+
new_source = rewrite
|
|
142
|
+
|
|
143
|
+
parser = @parser || create_parser_from_tree
|
|
144
|
+
raise "No parser available for re-parsing" unless parser
|
|
145
|
+
|
|
146
|
+
new_tree = parser.parse(new_source)
|
|
147
|
+
[new_source, new_tree]
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
private
|
|
151
|
+
|
|
152
|
+
def build_edits
|
|
153
|
+
edits = []
|
|
154
|
+
|
|
155
|
+
@operations.each do |op|
|
|
156
|
+
case op.type
|
|
157
|
+
when :swap
|
|
158
|
+
edits.concat(build_swap_edits(op.params))
|
|
159
|
+
when :move
|
|
160
|
+
edits.concat(build_move_edits(op.params))
|
|
161
|
+
when :copy
|
|
162
|
+
edits.concat(build_copy_edits(op.params))
|
|
163
|
+
when :reorder
|
|
164
|
+
edits.concat(build_reorder_edits(op.params))
|
|
165
|
+
when :extract
|
|
166
|
+
edits.concat(build_extract_edits(op.params))
|
|
167
|
+
when :duplicate
|
|
168
|
+
edits.concat(build_duplicate_edits(op.params))
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
edits
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def build_swap_edits(params)
|
|
176
|
+
node_a = params[:node_a]
|
|
177
|
+
node_b = params[:node_b]
|
|
178
|
+
|
|
179
|
+
text_a = node_text(node_a)
|
|
180
|
+
text_b = node_text(node_b)
|
|
181
|
+
|
|
182
|
+
[
|
|
183
|
+
{ start_byte: node_a.start_byte, end_byte: node_a.end_byte, replacement: text_b },
|
|
184
|
+
{ start_byte: node_b.start_byte, end_byte: node_b.end_byte, replacement: text_a },
|
|
185
|
+
]
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def build_move_edits(params)
|
|
189
|
+
node = params[:node]
|
|
190
|
+
before = params[:before]
|
|
191
|
+
after = params[:after]
|
|
192
|
+
separator = params[:separator]
|
|
193
|
+
|
|
194
|
+
text = node_text(node)
|
|
195
|
+
edits = []
|
|
196
|
+
|
|
197
|
+
# Remove from original location
|
|
198
|
+
edits << { start_byte: node.start_byte, end_byte: node.end_byte, replacement: "" }
|
|
199
|
+
|
|
200
|
+
# Insert at new location
|
|
201
|
+
if before
|
|
202
|
+
edits << { start_byte: before.start_byte, end_byte: before.start_byte, replacement: text + separator }
|
|
203
|
+
elsif after
|
|
204
|
+
edits << { start_byte: after.end_byte, end_byte: after.end_byte, replacement: separator + text }
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
edits
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def build_copy_edits(params)
|
|
211
|
+
node = params[:node]
|
|
212
|
+
before = params[:before]
|
|
213
|
+
after = params[:after]
|
|
214
|
+
separator = params[:separator]
|
|
215
|
+
|
|
216
|
+
text = node_text(node)
|
|
217
|
+
|
|
218
|
+
if before
|
|
219
|
+
[{ start_byte: before.start_byte, end_byte: before.start_byte, replacement: text + separator }]
|
|
220
|
+
elsif after
|
|
221
|
+
[{ start_byte: after.end_byte, end_byte: after.end_byte, replacement: separator + text }]
|
|
222
|
+
else
|
|
223
|
+
[]
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def build_reorder_edits(params)
|
|
228
|
+
parent = params[:parent]
|
|
229
|
+
order = params[:order]
|
|
230
|
+
|
|
231
|
+
# Get all named children
|
|
232
|
+
children = []
|
|
233
|
+
parent.named_children.each { |child| children << child }
|
|
234
|
+
|
|
235
|
+
return [] if children.empty?
|
|
236
|
+
|
|
237
|
+
# Validate order indices
|
|
238
|
+
raise ArgumentError, "Order indices out of range" unless order.all? { |i| i >= 0 && i < children.length }
|
|
239
|
+
|
|
240
|
+
# Build content for each position in new order
|
|
241
|
+
new_contents = order.map { |i| node_text(children[i]) }
|
|
242
|
+
|
|
243
|
+
# Create edits to replace each child with its new content
|
|
244
|
+
edits = []
|
|
245
|
+
children.each_with_index do |child, idx|
|
|
246
|
+
new_text = new_contents[idx] || node_text(child)
|
|
247
|
+
next if new_text == node_text(child)
|
|
248
|
+
|
|
249
|
+
edits << { start_byte: child.start_byte, end_byte: child.end_byte, replacement: new_text }
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
edits
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def build_extract_edits(params)
|
|
256
|
+
node = params[:node]
|
|
257
|
+
to = params[:to]
|
|
258
|
+
reference = params[:reference]
|
|
259
|
+
wrapper = params[:wrapper]
|
|
260
|
+
|
|
261
|
+
text = node_text(node)
|
|
262
|
+
extracted = wrapper ? wrapper.call(text) : text
|
|
263
|
+
|
|
264
|
+
[
|
|
265
|
+
# Replace original with reference
|
|
266
|
+
{ start_byte: node.start_byte, end_byte: node.end_byte, replacement: reference },
|
|
267
|
+
# Insert extracted content at target
|
|
268
|
+
{ start_byte: to.end_byte, end_byte: to.end_byte, replacement: "\n\n" + extracted },
|
|
269
|
+
]
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def build_duplicate_edits(params)
|
|
273
|
+
node = params[:node]
|
|
274
|
+
separator = params[:separator]
|
|
275
|
+
transformer = params[:transformer]
|
|
276
|
+
|
|
277
|
+
text = node_text(node)
|
|
278
|
+
copy_text = transformer ? transformer.call(text) : text
|
|
279
|
+
|
|
280
|
+
[{ start_byte: node.end_byte, end_byte: node.end_byte, replacement: separator + copy_text }]
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def apply_edits(edits)
|
|
284
|
+
# Sort by position descending to apply from end to start
|
|
285
|
+
sorted = edits.sort_by { |e| [-e[:start_byte], -e[:end_byte]] }
|
|
286
|
+
|
|
287
|
+
result = @source.dup
|
|
288
|
+
sorted.each do |edit|
|
|
289
|
+
result[edit[:start_byte]...edit[:end_byte]] = edit[:replacement]
|
|
290
|
+
end
|
|
291
|
+
result
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def node_text(node)
|
|
295
|
+
@source[node.start_byte...node.end_byte]
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def validate_non_overlapping(*nodes)
|
|
299
|
+
ranges = nodes.map { |n| (n.start_byte...n.end_byte) }
|
|
300
|
+
|
|
301
|
+
ranges.combination(2).each do |r1, r2|
|
|
302
|
+
if ranges_overlap?(r1, r2)
|
|
303
|
+
raise ArgumentError, "Nodes must not overlap"
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def ranges_overlap?(r1, r2)
|
|
309
|
+
r1.cover?(r2.begin) || r1.cover?(r2.end - 1) ||
|
|
310
|
+
r2.cover?(r1.begin) || r2.cover?(r1.end - 1)
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
def create_parser_from_tree
|
|
314
|
+
return unless @tree
|
|
315
|
+
|
|
316
|
+
parser = TreeSitter::Parser.new
|
|
317
|
+
lang = @tree.language
|
|
318
|
+
parser.language = lang.name if lang
|
|
319
|
+
parser
|
|
320
|
+
rescue StandardError
|
|
321
|
+
nil
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
end
|
data/lib/tree_sitter.rb
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "tree_sitter/version"
|
|
4
|
+
|
|
5
|
+
# Load the native extension
|
|
6
|
+
begin
|
|
7
|
+
RUBY_VERSION =~ /(\d+\.\d+)/
|
|
8
|
+
require "tree_sitter/#{Regexp.last_match(1)}/tree_sitter"
|
|
9
|
+
rescue LoadError
|
|
10
|
+
require "tree_sitter/tree_sitter"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Load pure Ruby components
|
|
14
|
+
require_relative "tree_sitter/rewriter"
|
|
15
|
+
require_relative "tree_sitter/formatting"
|
|
16
|
+
require_relative "tree_sitter/query_rewriter"
|
|
17
|
+
require_relative "tree_sitter/inserter"
|
|
18
|
+
require_relative "tree_sitter/transformer"
|
|
19
|
+
require_relative "tree_sitter/refactor"
|
|
20
|
+
|
|
21
|
+
module TreeSitter
|
|
22
|
+
class Error < StandardError; end
|
|
23
|
+
class ParseError < Error; end
|
|
24
|
+
class QueryError < Error; end
|
|
25
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: tree_sitter
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: aarch64-linux
|
|
6
|
+
authors:
|
|
7
|
+
- Garen J. Torikian
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-01-13 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: rake
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '13.0'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '13.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rake-compiler
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '1.2'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '1.2'
|
|
41
|
+
description: 'Parse and rewrite source code using tree-sitter with a Ruby-friendly
|
|
42
|
+
API. Supports multiple languages via dynamic grammar loading. '
|
|
43
|
+
email:
|
|
44
|
+
- gjtorikian@users.noreply.github.com
|
|
45
|
+
executables: []
|
|
46
|
+
extensions: []
|
|
47
|
+
extra_rdoc_files: []
|
|
48
|
+
files:
|
|
49
|
+
- CHANGELOG.md
|
|
50
|
+
- LICENSE.txt
|
|
51
|
+
- Makefile
|
|
52
|
+
- README.md
|
|
53
|
+
- lib/tree_sitter.rb
|
|
54
|
+
- lib/tree_sitter/3.2/tree_sitter.so
|
|
55
|
+
- lib/tree_sitter/3.3/tree_sitter.so
|
|
56
|
+
- lib/tree_sitter/3.4/tree_sitter.so
|
|
57
|
+
- lib/tree_sitter/4.0/tree_sitter.so
|
|
58
|
+
- lib/tree_sitter/formatting.rb
|
|
59
|
+
- lib/tree_sitter/inserter.rb
|
|
60
|
+
- lib/tree_sitter/query_rewriter.rb
|
|
61
|
+
- lib/tree_sitter/refactor.rb
|
|
62
|
+
- lib/tree_sitter/rewriter.rb
|
|
63
|
+
- lib/tree_sitter/transformer.rb
|
|
64
|
+
- lib/tree_sitter/version.rb
|
|
65
|
+
homepage: https://github.com/gjtorikian/tree_sitter
|
|
66
|
+
licenses:
|
|
67
|
+
- MIT
|
|
68
|
+
metadata:
|
|
69
|
+
allowed_push_host: https://rubygems.org
|
|
70
|
+
funding_uri: https://github.com/sponsors/gjtorikian/
|
|
71
|
+
source_code_uri: https://github.com/gjtorikian/tree_sitter
|
|
72
|
+
rubygems_mfa_required: 'true'
|
|
73
|
+
post_install_message:
|
|
74
|
+
rdoc_options: []
|
|
75
|
+
require_paths:
|
|
76
|
+
- lib
|
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '3.2'
|
|
82
|
+
- - "<"
|
|
83
|
+
- !ruby/object:Gem::Version
|
|
84
|
+
version: 4.1.dev
|
|
85
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - ">="
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '3.4'
|
|
90
|
+
requirements: []
|
|
91
|
+
rubygems_version: 3.5.23
|
|
92
|
+
signing_key:
|
|
93
|
+
specification_version: 4
|
|
94
|
+
summary: Ruby bindings for tree-sitter with code transformation and refactoring capabilities.
|
|
95
|
+
Written in Rust, wrapped in Ruby.
|
|
96
|
+
test_files: []
|