tree_sitter 0.0.1

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,214 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "query_rewriter"
4
+ require_relative "transformer"
5
+ require_relative "inserter"
6
+
7
+ module TreeSitter
8
+ # High-level refactoring operations built on QueryRewriter and Transformer.
9
+ # These methods provide common code transformation patterns with a simple API.
10
+ #
11
+ # @example Rename a function throughout the code
12
+ # TreeSitter::Refactor.rename_symbol(source, tree, lang,
13
+ # from: "old_name", to: "new_name")
14
+ #
15
+ module Refactor
16
+ class << self
17
+ # Rename a symbol (function, variable, type) throughout the code
18
+ #
19
+ # @param source [String] Source code
20
+ # @param tree [Tree] Parsed syntax tree
21
+ # @param language [Language] Language for queries
22
+ # @param from [String] Original name
23
+ # @param to [String] New name
24
+ # @param kind [Symbol] Type of symbol (:identifier, :function, :type, :variable)
25
+ # @return [String] Modified source code
26
+ def rename_symbol(source, tree, language, from:, to:, kind: :identifier)
27
+ query_pattern = build_rename_query(kind)
28
+
29
+ QueryRewriter.new(source, tree, language)
30
+ .query(query_pattern)
31
+ .where { |m| match_has_text?(m, from) }
32
+ .replace("@name") { to }
33
+ .rewrite
34
+ end
35
+
36
+ # Rename a struct/class field and its usages
37
+ #
38
+ # @param source [String] Source code
39
+ # @param tree [Tree] Parsed syntax tree
40
+ # @param language [Language] Language for queries
41
+ # @param struct_name [String, nil] Name of struct/class (nil for all)
42
+ # @param from [String] Old field name
43
+ # @param to [String] New field name
44
+ # @return [String] Modified source code
45
+ def rename_field(source, tree, language, struct_name: nil, from:, to:)
46
+ # Query for field declarations and field accesses
47
+ query_pattern = <<~QUERY
48
+ [
49
+ (field_declaration name: (field_identifier) @name)
50
+ (field_expression field: (field_identifier) @name)
51
+ (field_identifier) @name
52
+ ]
53
+ QUERY
54
+
55
+ QueryRewriter.new(source, tree, language)
56
+ .query(query_pattern)
57
+ .where { |m| match_has_text?(m, from) }
58
+ .replace("@name") { to }
59
+ .rewrite
60
+ end
61
+
62
+ # Extract code into a new function
63
+ #
64
+ # @param source [String] Source code
65
+ # @param tree [Tree] Parsed syntax tree
66
+ # @param language [Language] Language for queries
67
+ # @param node [Node] Node to extract
68
+ # @param name [String] Name for extracted function
69
+ # @param parameters [Array<String>] Parameter names
70
+ # @param insert_after [Node, nil] Where to insert the new function
71
+ # @return [String] Modified source code
72
+ def extract_function(source, tree, language, node:, name:, parameters: [], insert_after: nil)
73
+ # Build function call
74
+ param_list = parameters.join(", ")
75
+ call_reference = parameters.empty? ? "#{name}()" : "#{name}(#{param_list})"
76
+
77
+ # Build function definition
78
+ node_text = source[node.start_byte...node.end_byte]
79
+ param_decl = parameters.map { |p| "#{p}: _" }.join(", ")
80
+ fn_def = "fn #{name}(#{param_decl}) {\n #{node_text}\n}"
81
+
82
+ # Determine where to insert
83
+ insert_target = insert_after || find_enclosing_function(node) || tree.root_node
84
+
85
+ Transformer.new(source, tree)
86
+ .extract(node, to: insert_target, reference: call_reference) { fn_def }
87
+ .rewrite
88
+ end
89
+
90
+ # Inline a variable (replace usages with its value)
91
+ #
92
+ # @param source [String] Source code
93
+ # @param tree [Tree] Parsed syntax tree
94
+ # @param language [Language] Language for queries
95
+ # @param name [String] Variable name to inline
96
+ # @param scope [Node, nil] Scope to limit inlining (nil for entire tree)
97
+ # @return [String] Modified source code
98
+ def inline_variable(source, tree, language, name:, scope: nil)
99
+ # Find the variable declaration and its value
100
+ decl_query = "(let_declaration pattern: (identifier) @var_name value: (_) @value)"
101
+
102
+ query = TreeSitter::Query.new(language, decl_query)
103
+ cursor = TreeSitter::QueryCursor.new
104
+ root = scope || tree.root_node
105
+ matches = cursor.matches(query, root, source)
106
+
107
+ # Find the declaration for our variable
108
+ decl_match = matches.find do |m|
109
+ m.captures.any? { |c| c.name == "var_name" && c.node.text == name }
110
+ end
111
+
112
+ return source unless decl_match
113
+
114
+ # Get the value to inline
115
+ value_capture = decl_match.captures.find { |c| c.name == "value" }
116
+ return source unless value_capture
117
+
118
+ value_text = value_capture.node.text
119
+
120
+ # Find all usages and replace
121
+ usage_query = "(identifier) @usage"
122
+
123
+ QueryRewriter.new(source, tree, language)
124
+ .query(usage_query)
125
+ .where { |m| match_has_text?(m, name) && !declaration?(m, source) }
126
+ .replace("@usage") { value_text }
127
+ .rewrite
128
+ end
129
+
130
+ # Add an attribute/annotation to items matching a query
131
+ #
132
+ # @param source [String] Source code
133
+ # @param tree [Tree] Parsed syntax tree
134
+ # @param language [Language] Language for queries
135
+ # @param query_pattern [String] Query to match items
136
+ # @param attribute [String] Attribute to add (e.g., "#[derive(Debug)]")
137
+ # @return [String] Modified source code
138
+ def add_attribute(source, tree, language, query_pattern:, attribute:)
139
+ QueryRewriter.new(source, tree, language)
140
+ .query(query_pattern)
141
+ .insert_before("@item") { "#{attribute}\n" }
142
+ .rewrite
143
+ end
144
+
145
+ # Remove items matching a query
146
+ #
147
+ # @param source [String] Source code
148
+ # @param tree [Tree] Parsed syntax tree
149
+ # @param language [Language] Language for queries
150
+ # @param query_pattern [String] Query to match items to remove
151
+ # @param capture_name [String] Name of capture to remove
152
+ # @return [String] Modified source code
153
+ def remove_matching(source, tree, language, query_pattern:, capture_name: "@item")
154
+ QueryRewriter.new(source, tree, language)
155
+ .query(query_pattern)
156
+ .remove(capture_name)
157
+ .rewrite
158
+ end
159
+
160
+ private
161
+
162
+ def build_rename_query(kind)
163
+ case kind
164
+ when :function
165
+ <<~QUERY
166
+ [
167
+ (function_item name: (identifier) @name)
168
+ (call_expression function: (identifier) @name)
169
+ ]
170
+ QUERY
171
+ when :type
172
+ <<~QUERY
173
+ [
174
+ (struct_item name: (type_identifier) @name)
175
+ (enum_item name: (type_identifier) @name)
176
+ (type_identifier) @name
177
+ ]
178
+ QUERY
179
+ when :variable
180
+ "(identifier) @name"
181
+ else
182
+ "(identifier) @name"
183
+ end
184
+ end
185
+
186
+ def match_has_text?(match, text)
187
+ match.captures.any? { |c| c.node.text == text }
188
+ end
189
+
190
+ DECLARATION_TYPES = ["let_declaration", "parameter", "function_item"].freeze
191
+
192
+ def declaration?(match, _source)
193
+ # Check if this identifier is part of a declaration pattern
194
+ match.captures.any? do |c|
195
+ parent = c.node.parent
196
+ next false unless parent
197
+
198
+ # Common declaration patterns
199
+ DECLARATION_TYPES.include?(parent.type)
200
+ end
201
+ end
202
+
203
+ def find_enclosing_function(node)
204
+ current = node.parent
205
+ while current
206
+ return current if current.type == "function_item"
207
+
208
+ current = current.parent
209
+ end
210
+ nil
211
+ end
212
+ end
213
+ end
214
+ end
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TreeSitter
4
+ # Rewriter class for applying edits to parsed source code.
5
+ # Inspired by Parser::TreeRewriter from the parser gem.
6
+ #
7
+ # @example Basic usage
8
+ # tree = TreeSitter::Parser.new.tap { |p| p.language = "rust" }.parse(source)
9
+ # fn_name = tree.root_node.child(0).child_by_field_name("name")
10
+ #
11
+ # new_source = TreeSitter::Rewriter.new(source, tree)
12
+ # .replace(fn_name, "new_name")
13
+ # .rewrite
14
+ #
15
+ class Rewriter
16
+ # Represents a single edit operation
17
+ Edit = Struct.new(:start_byte, :end_byte, :replacement, keyword_init: true)
18
+
19
+ attr_reader :source, :tree, :edits
20
+
21
+ # Initialize a new Rewriter
22
+ #
23
+ # @param source [String] The source code to rewrite
24
+ # @param tree [TreeSitter::Tree, nil] Optional parsed tree (will parse if not provided)
25
+ # @param parser [TreeSitter::Parser, nil] Optional parser for re-parsing (needed if tree not provided)
26
+ def initialize(source, tree = nil, parser: nil)
27
+ @source = source.dup.freeze
28
+ @tree = tree
29
+ @parser = parser
30
+ @edits = []
31
+ end
32
+
33
+ # Remove the text at the given node or range
34
+ #
35
+ # @param node_or_range [TreeSitter::Node, TreeSitter::Range] The node or range to remove
36
+ # @return [self] Returns self for method chaining
37
+ def remove(node_or_range)
38
+ replace(node_or_range, "")
39
+ end
40
+
41
+ # Replace the text at the given node or range with new content
42
+ #
43
+ # @param node_or_range [TreeSitter::Node, TreeSitter::Range] The node or range to replace
44
+ # @param content [String] The replacement content
45
+ # @return [self] Returns self for method chaining
46
+ def replace(node_or_range, content)
47
+ range = normalize_range(node_or_range)
48
+ @edits << Edit.new(
49
+ start_byte: range.start_byte,
50
+ end_byte: range.end_byte,
51
+ replacement: content.to_s,
52
+ )
53
+ self
54
+ end
55
+
56
+ # Insert text before the given node or range
57
+ #
58
+ # @param node_or_range [TreeSitter::Node, TreeSitter::Range] The node or range
59
+ # @param content [String] The content to insert
60
+ # @return [self] Returns self for method chaining
61
+ def insert_before(node_or_range, content)
62
+ range = normalize_range(node_or_range)
63
+ @edits << Edit.new(
64
+ start_byte: range.start_byte,
65
+ end_byte: range.start_byte,
66
+ replacement: content.to_s,
67
+ )
68
+ self
69
+ end
70
+
71
+ # Insert text after the given node or range
72
+ #
73
+ # @param node_or_range [TreeSitter::Node, TreeSitter::Range] The node or range
74
+ # @param content [String] The content to insert
75
+ # @return [self] Returns self for method chaining
76
+ def insert_after(node_or_range, content)
77
+ range = normalize_range(node_or_range)
78
+ @edits << Edit.new(
79
+ start_byte: range.end_byte,
80
+ end_byte: range.end_byte,
81
+ replacement: content.to_s,
82
+ )
83
+ self
84
+ end
85
+
86
+ # Wrap the node or range with before and after text
87
+ #
88
+ # @param node_or_range [TreeSitter::Node, TreeSitter::Range] The node or range to wrap
89
+ # @param before_text [String] Text to insert before
90
+ # @param after_text [String] Text to insert after
91
+ # @return [self] Returns self for method chaining
92
+ def wrap(node_or_range, before_text, after_text)
93
+ insert_before(node_or_range, before_text)
94
+ insert_after(node_or_range, after_text)
95
+ self
96
+ end
97
+
98
+ # Apply all accumulated edits and return the new source code
99
+ #
100
+ # Edits are applied in reverse order (from end to start) to preserve
101
+ # byte positions of earlier edits.
102
+ #
103
+ # @return [String] The rewritten source code
104
+ def rewrite
105
+ # Sort edits by position descending to apply from end to start
106
+ # This prevents earlier edits from invalidating later positions
107
+ sorted = @edits.sort_by { |e| [-e.start_byte, -e.end_byte] }
108
+
109
+ result = @source.dup
110
+ sorted.each do |edit|
111
+ result[edit.start_byte...edit.end_byte] = edit.replacement
112
+ end
113
+ result
114
+ end
115
+
116
+ # Apply edits and return both the new source and a new parse tree
117
+ #
118
+ # @return [Array<String, TreeSitter::Tree>] The new source and tree
119
+ # @raise [RuntimeError] If no parser is available for re-parsing
120
+ def rewrite_with_tree
121
+ new_source = rewrite
122
+
123
+ parser = @parser || create_parser_from_tree
124
+ raise "No parser available for re-parsing" unless parser
125
+
126
+ new_tree = parser.parse(new_source)
127
+ [new_source, new_tree]
128
+ end
129
+
130
+ private
131
+
132
+ def normalize_range(node_or_range)
133
+ case node_or_range
134
+ when TreeSitter::Node
135
+ node_or_range.range
136
+ when TreeSitter::Range
137
+ node_or_range
138
+ else
139
+ raise ArgumentError,
140
+ "Expected TreeSitter::Node or TreeSitter::Range, got #{node_or_range.class}"
141
+ end
142
+ end
143
+
144
+ def create_parser_from_tree
145
+ return unless @tree
146
+
147
+ parser = TreeSitter::Parser.new
148
+ lang = @tree.language
149
+ parser.language = lang.name if lang
150
+ parser
151
+ rescue StandardError
152
+ nil
153
+ end
154
+ end
155
+ end
@@ -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.0.1"
5
+ end