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.
@@ -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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TreeSitter
4
+ VERSION = "0.1.0"
5
+ end
@@ -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: []