tree_haver 3.2.6 → 4.0.0
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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +41 -3
- data/README.md +94 -12
- data/lib/tree_haver/backend_registry.rb +234 -0
- data/lib/tree_haver/backends/citrus.rb +7 -1
- data/lib/tree_haver/backends/ffi.rb +36 -9
- data/lib/tree_haver/backends/java.rb +59 -61
- data/lib/tree_haver/backends/mri.rb +38 -11
- data/lib/tree_haver/backends/prism.rb +67 -234
- data/lib/tree_haver/backends/psych.rb +75 -346
- data/lib/tree_haver/backends/rust.rb +32 -11
- data/lib/tree_haver/base/language.rb +98 -0
- data/lib/tree_haver/base/node.rb +315 -0
- data/lib/tree_haver/base/parser.rb +24 -0
- data/lib/tree_haver/base/point.rb +48 -0
- data/lib/tree_haver/base/tree.rb +128 -0
- data/lib/tree_haver/base.rb +12 -0
- data/lib/tree_haver/node.rb +14 -10
- data/lib/tree_haver/rspec/dependency_tags.rb +9 -5
- data/lib/tree_haver/tree.rb +4 -5
- data/lib/tree_haver/version.rb +2 -2
- data/lib/tree_haver.rb +30 -18
- data.tar.gz.sig +0 -0
- metadata +11 -6
- metadata.gz.sig +0 -0
- data/lib/tree_haver/backends/commonmarker.rb +0 -516
- data/lib/tree_haver/backends/markly.rb +0 -590
|
@@ -28,15 +28,19 @@ module TreeHaver
|
|
|
28
28
|
# @return [Boolean] true if psych is available
|
|
29
29
|
class << self
|
|
30
30
|
def available?
|
|
31
|
-
return @loaded if @load_attempted
|
|
32
|
-
@load_attempted = true
|
|
31
|
+
return @loaded if @load_attempted # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
32
|
+
@load_attempted = true # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
33
33
|
begin
|
|
34
34
|
require "psych"
|
|
35
|
-
@loaded = true
|
|
35
|
+
@loaded = true # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
36
36
|
rescue LoadError
|
|
37
|
-
@loaded = false
|
|
37
|
+
@loaded = false # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
38
|
+
rescue StandardError
|
|
39
|
+
# :nocov: defensive code - StandardError during require is extremely rare
|
|
40
|
+
@loaded = false # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
41
|
+
# :nocov:
|
|
38
42
|
end
|
|
39
|
-
@loaded
|
|
43
|
+
@loaded # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
40
44
|
end
|
|
41
45
|
|
|
42
46
|
# Reset the load state (primarily for testing)
|
|
@@ -44,8 +48,8 @@ module TreeHaver
|
|
|
44
48
|
# @return [void]
|
|
45
49
|
# @api private
|
|
46
50
|
def reset!
|
|
47
|
-
@load_attempted = false
|
|
48
|
-
@loaded = false
|
|
51
|
+
@load_attempted = false # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
52
|
+
@loaded = false # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
49
53
|
end
|
|
50
54
|
|
|
51
55
|
# Get capabilities supported by this backend
|
|
@@ -74,23 +78,12 @@ module TreeHaver
|
|
|
74
78
|
# @example
|
|
75
79
|
# language = TreeHaver::Backends::Psych::Language.yaml
|
|
76
80
|
# parser.language = language
|
|
77
|
-
class Language
|
|
78
|
-
include Comparable
|
|
79
|
-
|
|
80
|
-
# The language name (always :yaml for Psych)
|
|
81
|
-
# @return [Symbol]
|
|
82
|
-
attr_reader :name
|
|
83
|
-
|
|
84
|
-
# The backend this language is for
|
|
85
|
-
# @return [Symbol]
|
|
86
|
-
attr_reader :backend
|
|
87
|
-
|
|
81
|
+
class Language < TreeHaver::Base::Language
|
|
88
82
|
# Create a new Psych language instance
|
|
89
83
|
#
|
|
90
84
|
# @param name [Symbol] Language name (should be :yaml)
|
|
91
85
|
def initialize(name = :yaml)
|
|
92
|
-
|
|
93
|
-
@backend = :psych
|
|
86
|
+
super(name, backend: :psych, options: {})
|
|
94
87
|
end
|
|
95
88
|
|
|
96
89
|
class << self
|
|
@@ -104,17 +97,14 @@ module TreeHaver
|
|
|
104
97
|
# Load language from library path (API compatibility)
|
|
105
98
|
#
|
|
106
99
|
# Psych only supports YAML, so path and symbol parameters are ignored.
|
|
107
|
-
# This method exists for API consistency with tree-sitter backends,
|
|
108
|
-
# allowing `TreeHaver.parser_for(:yaml)` to work regardless of backend.
|
|
109
100
|
#
|
|
110
101
|
# @param _path [String] Ignored - Psych doesn't load external grammars
|
|
111
|
-
# @param symbol [String, nil] Ignored
|
|
102
|
+
# @param symbol [String, nil] Ignored - Psych only supports YAML
|
|
112
103
|
# @param name [String, nil] Language name hint (defaults to :yaml)
|
|
113
104
|
# @return [Language] YAML language
|
|
114
105
|
# @raise [TreeHaver::NotAvailable] if requested language is not YAML
|
|
115
|
-
def from_library(_path = nil, symbol: nil, name: nil)
|
|
116
|
-
|
|
117
|
-
lang_name = name || symbol&.to_s&.sub(/^tree_sitter_/, "")&.to_sym || :yaml
|
|
106
|
+
def from_library(_path = nil, symbol: nil, name: nil) # rubocop:disable Lint/UnusedMethodArgument
|
|
107
|
+
lang_name = name || :yaml
|
|
118
108
|
|
|
119
109
|
unless lang_name == :yaml
|
|
120
110
|
raise TreeHaver::NotAvailable,
|
|
@@ -125,20 +115,6 @@ module TreeHaver
|
|
|
125
115
|
yaml
|
|
126
116
|
end
|
|
127
117
|
end
|
|
128
|
-
|
|
129
|
-
# Comparison for sorting/equality
|
|
130
|
-
#
|
|
131
|
-
# @param other [Language] other language
|
|
132
|
-
# @return [Integer, nil] comparison result
|
|
133
|
-
def <=>(other)
|
|
134
|
-
return unless other.is_a?(Language)
|
|
135
|
-
name <=> other.name
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
# @return [String] human-readable representation
|
|
139
|
-
def inspect
|
|
140
|
-
"#<TreeHaver::Backends::Psych::Language name=#{name}>"
|
|
141
|
-
end
|
|
142
118
|
end
|
|
143
119
|
|
|
144
120
|
# Psych parser wrapper
|
|
@@ -149,22 +125,14 @@ module TreeHaver
|
|
|
149
125
|
# parser = TreeHaver::Backends::Psych::Parser.new
|
|
150
126
|
# parser.language = Language.yaml
|
|
151
127
|
# tree = parser.parse(yaml_source)
|
|
152
|
-
class Parser
|
|
153
|
-
# @return [Language, nil] The language to parse
|
|
154
|
-
attr_accessor :language
|
|
155
|
-
|
|
156
|
-
# Create a new Psych parser
|
|
157
|
-
def initialize
|
|
158
|
-
@language = nil
|
|
159
|
-
end
|
|
160
|
-
|
|
128
|
+
class Parser < TreeHaver::Base::Parser
|
|
161
129
|
# Parse YAML source code
|
|
162
130
|
#
|
|
163
131
|
# @param source [String] YAML source to parse
|
|
164
132
|
# @return [Tree] Parsed tree
|
|
165
133
|
# @raise [::Psych::SyntaxError] on syntax errors
|
|
166
134
|
def parse(source)
|
|
167
|
-
raise "Language not set" unless
|
|
135
|
+
raise "Language not set" unless language
|
|
168
136
|
Psych.available? or raise "Psych not available"
|
|
169
137
|
|
|
170
138
|
ast = ::Psych.parse_stream(source)
|
|
@@ -184,61 +152,28 @@ module TreeHaver
|
|
|
184
152
|
# Psych tree wrapper
|
|
185
153
|
#
|
|
186
154
|
# Wraps a Psych::Nodes::Stream to provide TreeHaver-compatible tree interface.
|
|
187
|
-
class Tree
|
|
155
|
+
class Tree < TreeHaver::Base::Tree
|
|
188
156
|
# @return [::Psych::Nodes::Stream] The underlying Psych stream
|
|
189
157
|
attr_reader :inner_tree
|
|
190
158
|
|
|
191
|
-
# @return [String] The original source
|
|
192
|
-
attr_reader :source
|
|
193
|
-
|
|
194
159
|
# Create a new tree wrapper
|
|
195
160
|
#
|
|
196
161
|
# @param stream [::Psych::Nodes::Stream] Psych stream node
|
|
197
162
|
# @param source [String] Original source
|
|
198
163
|
def initialize(stream, source)
|
|
199
|
-
|
|
200
|
-
@source = source
|
|
201
|
-
@lines = source.lines
|
|
164
|
+
super(stream, source: source)
|
|
202
165
|
end
|
|
203
166
|
|
|
204
167
|
# Get the root node
|
|
205
168
|
#
|
|
206
|
-
# For YAML, the stream is the root. We wrap it as a Node.
|
|
207
|
-
#
|
|
208
169
|
# @return [Node] Root node
|
|
209
170
|
def root_node
|
|
210
|
-
Node.new(
|
|
171
|
+
Node.new(inner_tree, source: source, lines: lines)
|
|
211
172
|
end
|
|
212
173
|
|
|
213
|
-
#
|
|
214
|
-
#
|
|
215
|
-
# Psych raises exceptions on parse errors rather than recording them,
|
|
216
|
-
# so this is always empty if we got a tree.
|
|
217
|
-
#
|
|
218
|
-
# @return [Array] Empty array (no errors if parsing succeeded)
|
|
219
|
-
def errors
|
|
220
|
-
[]
|
|
221
|
-
end
|
|
222
|
-
|
|
223
|
-
# Get parse warnings
|
|
224
|
-
#
|
|
225
|
-
# @return [Array] Empty array (Psych doesn't produce warnings)
|
|
226
|
-
def warnings
|
|
227
|
-
[]
|
|
228
|
-
end
|
|
229
|
-
|
|
230
|
-
# Get comments from the document
|
|
231
|
-
#
|
|
232
|
-
# Psych doesn't preserve comments in the AST by default.
|
|
233
|
-
#
|
|
234
|
-
# @return [Array] Empty array
|
|
235
|
-
def comments
|
|
236
|
-
[]
|
|
237
|
-
end
|
|
238
|
-
|
|
239
|
-
# @return [String] human-readable representation
|
|
174
|
+
# Human-readable representation
|
|
240
175
|
def inspect
|
|
241
|
-
"#<TreeHaver::Backends::Psych::Tree documents=#{
|
|
176
|
+
"#<TreeHaver::Backends::Psych::Tree documents=#{inner_tree.children&.size || 0}>"
|
|
242
177
|
end
|
|
243
178
|
end
|
|
244
179
|
|
|
@@ -252,58 +187,30 @@ module TreeHaver
|
|
|
252
187
|
# - Mapping: Hash/object
|
|
253
188
|
# - Sequence: Array/list
|
|
254
189
|
# - Scalar: Primitive value (string, number, boolean, null)
|
|
255
|
-
# - Alias: YAML anchor reference
|
|
256
|
-
class Node
|
|
257
|
-
include Comparable
|
|
258
|
-
include Enumerable
|
|
259
|
-
|
|
260
|
-
# @return [::Psych::Nodes::Node] The underlying Psych node
|
|
261
|
-
attr_reader :inner_node
|
|
262
|
-
|
|
263
|
-
# @return [String] The original source
|
|
264
|
-
attr_reader :source
|
|
265
|
-
|
|
266
|
-
# Create a new node wrapper
|
|
267
|
-
#
|
|
268
|
-
# @param node [::Psych::Nodes::Node] Psych node
|
|
269
|
-
# @param source [String] Original source
|
|
270
|
-
# @param lines [Array<String>] Source lines for text extraction
|
|
271
|
-
def initialize(node, source, lines = nil)
|
|
272
|
-
@inner_node = node
|
|
273
|
-
@source = source
|
|
274
|
-
@lines = lines || source.lines
|
|
275
|
-
end
|
|
276
|
-
|
|
190
|
+
# - Psych::Nodes::Alias: YAML anchor reference
|
|
191
|
+
class Node < TreeHaver::Base::Node
|
|
277
192
|
# Get the node type as a string
|
|
278
193
|
#
|
|
279
|
-
# Maps Psych class names to lowercase type strings:
|
|
280
|
-
# - Psych::Nodes::Stream → "stream"
|
|
281
|
-
# - Psych::Nodes::Document → "document"
|
|
282
|
-
# - Psych::Nodes::Mapping → "mapping"
|
|
283
|
-
# - Psych::Nodes::Sequence → "sequence"
|
|
284
|
-
# - Psych::Nodes::Scalar → "scalar"
|
|
285
|
-
# - Psych::Nodes::Alias → "alias"
|
|
286
|
-
#
|
|
287
194
|
# @return [String] Node type
|
|
288
195
|
def type
|
|
289
|
-
|
|
196
|
+
inner_node.class.name.split("::").last.downcase
|
|
290
197
|
end
|
|
291
198
|
|
|
292
|
-
# Alias for
|
|
293
|
-
|
|
199
|
+
# Alias for type (API compatibility)
|
|
200
|
+
# @return [String] node type
|
|
201
|
+
def kind
|
|
202
|
+
type
|
|
203
|
+
end
|
|
294
204
|
|
|
295
205
|
# Get the text content of this node
|
|
296
206
|
#
|
|
297
|
-
# For Scalar nodes, returns the value. For containers, returns
|
|
298
|
-
# the source text spanning the node's location.
|
|
299
|
-
#
|
|
300
207
|
# @return [String] Node text
|
|
301
208
|
def text
|
|
302
|
-
case
|
|
209
|
+
case inner_node
|
|
303
210
|
when ::Psych::Nodes::Scalar
|
|
304
|
-
|
|
211
|
+
inner_node.value.to_s
|
|
305
212
|
when ::Psych::Nodes::Alias
|
|
306
|
-
"*#{
|
|
213
|
+
"*#{inner_node.anchor}"
|
|
307
214
|
else
|
|
308
215
|
# For container nodes, extract from source using location
|
|
309
216
|
extract_text_from_location
|
|
@@ -314,45 +221,19 @@ module TreeHaver
|
|
|
314
221
|
#
|
|
315
222
|
# @return [Array<Node>] Child nodes
|
|
316
223
|
def children
|
|
317
|
-
return [] unless
|
|
318
|
-
|
|
319
|
-
@inner_node.children.map { |child| Node.new(child, @source, @lines) }
|
|
320
|
-
end
|
|
224
|
+
return [] unless inner_node.respond_to?(:children) && inner_node.children
|
|
321
225
|
|
|
322
|
-
|
|
323
|
-
#
|
|
324
|
-
# @yield [Node] Each child node
|
|
325
|
-
# @return [Enumerator, nil]
|
|
326
|
-
def each(&block)
|
|
327
|
-
return to_enum(__method__) unless block
|
|
328
|
-
children.each(&block)
|
|
329
|
-
end
|
|
330
|
-
|
|
331
|
-
# Get the number of children
|
|
332
|
-
#
|
|
333
|
-
# @return [Integer] Child count
|
|
334
|
-
def child_count
|
|
335
|
-
children.size
|
|
336
|
-
end
|
|
337
|
-
|
|
338
|
-
# Get child by index
|
|
339
|
-
#
|
|
340
|
-
# @param index [Integer] Child index
|
|
341
|
-
# @return [Node, nil] Child node
|
|
342
|
-
def child(index)
|
|
343
|
-
children[index]
|
|
226
|
+
inner_node.children.map { |child| Node.new(child, source: source, lines: lines) }
|
|
344
227
|
end
|
|
345
228
|
|
|
346
229
|
# Get start byte offset
|
|
347
230
|
#
|
|
348
|
-
# Psych doesn't provide byte offsets directly, so we calculate from line/column.
|
|
349
|
-
#
|
|
350
231
|
# @return [Integer] Start byte offset
|
|
351
232
|
def start_byte
|
|
352
|
-
return 0 unless
|
|
233
|
+
return 0 unless inner_node.respond_to?(:start_line)
|
|
353
234
|
|
|
354
|
-
line =
|
|
355
|
-
col =
|
|
235
|
+
line = inner_node.start_line || 0
|
|
236
|
+
col = inner_node.start_column || 0
|
|
356
237
|
calculate_byte_offset(line, col)
|
|
357
238
|
end
|
|
358
239
|
|
|
@@ -360,188 +241,78 @@ module TreeHaver
|
|
|
360
241
|
#
|
|
361
242
|
# @return [Integer] End byte offset
|
|
362
243
|
def end_byte
|
|
363
|
-
return start_byte + text.bytesize unless
|
|
244
|
+
return start_byte + text.bytesize unless inner_node.respond_to?(:end_line)
|
|
364
245
|
|
|
365
|
-
line =
|
|
366
|
-
col =
|
|
246
|
+
line = inner_node.end_line || 0
|
|
247
|
+
col = inner_node.end_column || 0
|
|
367
248
|
calculate_byte_offset(line, col)
|
|
368
249
|
end
|
|
369
250
|
|
|
370
|
-
# Get start point (row, column)
|
|
251
|
+
# Get start point (row, column) - 0-based
|
|
371
252
|
#
|
|
372
|
-
# @return [Point] Start position
|
|
253
|
+
# @return [TreeHaver::Base::Point] Start position
|
|
373
254
|
def start_point
|
|
374
|
-
row = (
|
|
375
|
-
col = (
|
|
376
|
-
Point.new(row, col)
|
|
255
|
+
row = (inner_node.respond_to?(:start_line) ? inner_node.start_line : 0) || 0
|
|
256
|
+
col = (inner_node.respond_to?(:start_column) ? inner_node.start_column : 0) || 0
|
|
257
|
+
TreeHaver::Base::Point.new(row, col)
|
|
377
258
|
end
|
|
378
259
|
|
|
379
|
-
# Get end point (row, column)
|
|
260
|
+
# Get end point (row, column) - 0-based
|
|
380
261
|
#
|
|
381
|
-
# @return [Point] End position
|
|
262
|
+
# @return [TreeHaver::Base::Point] End position
|
|
382
263
|
def end_point
|
|
383
|
-
row = (
|
|
384
|
-
col = (
|
|
385
|
-
Point.new(row, col)
|
|
386
|
-
end
|
|
387
|
-
|
|
388
|
-
# Get the 1-based line number where this node starts
|
|
389
|
-
#
|
|
390
|
-
# Psych provides 0-based line numbers, so we add 1.
|
|
391
|
-
#
|
|
392
|
-
# @return [Integer] 1-based line number
|
|
393
|
-
def start_line
|
|
394
|
-
row = start_point.row
|
|
395
|
-
row + 1
|
|
396
|
-
end
|
|
397
|
-
|
|
398
|
-
# Get the 1-based line number where this node ends
|
|
399
|
-
#
|
|
400
|
-
# @return [Integer] 1-based line number
|
|
401
|
-
def end_line
|
|
402
|
-
row = end_point.row
|
|
403
|
-
row + 1
|
|
404
|
-
end
|
|
405
|
-
|
|
406
|
-
# Get position information as a hash
|
|
407
|
-
#
|
|
408
|
-
# Returns a hash with 1-based line numbers and 0-based columns.
|
|
409
|
-
# Compatible with *-merge gems' FileAnalysisBase.
|
|
410
|
-
#
|
|
411
|
-
# @return [Hash{Symbol => Integer}] Position hash
|
|
412
|
-
def source_position
|
|
413
|
-
{
|
|
414
|
-
start_line: start_line,
|
|
415
|
-
end_line: end_line,
|
|
416
|
-
start_column: start_point.column,
|
|
417
|
-
end_column: end_point.column,
|
|
418
|
-
}
|
|
419
|
-
end
|
|
420
|
-
|
|
421
|
-
# Get the first child node
|
|
422
|
-
#
|
|
423
|
-
# @return [Node, nil] First child or nil
|
|
424
|
-
def first_child
|
|
425
|
-
children.first
|
|
426
|
-
end
|
|
427
|
-
|
|
428
|
-
# Check if this is a named (structural) node
|
|
429
|
-
#
|
|
430
|
-
# All Psych nodes are structural.
|
|
431
|
-
#
|
|
432
|
-
# @return [Boolean] true
|
|
433
|
-
def named?
|
|
434
|
-
true
|
|
435
|
-
end
|
|
436
|
-
|
|
437
|
-
# Alias for tree-sitter compatibility
|
|
438
|
-
alias_method :structural?, :named?
|
|
439
|
-
|
|
440
|
-
# Check if the node or any descendant has an error
|
|
441
|
-
#
|
|
442
|
-
# Psych raises on errors rather than embedding them.
|
|
443
|
-
#
|
|
444
|
-
# @return [Boolean] false
|
|
445
|
-
def has_error?
|
|
446
|
-
false
|
|
447
|
-
end
|
|
448
|
-
|
|
449
|
-
# Check if this is a missing node
|
|
450
|
-
#
|
|
451
|
-
# Psych doesn't have missing nodes.
|
|
452
|
-
#
|
|
453
|
-
# @return [Boolean] false
|
|
454
|
-
def missing?
|
|
455
|
-
false
|
|
456
|
-
end
|
|
457
|
-
|
|
458
|
-
# Comparison for sorting
|
|
459
|
-
#
|
|
460
|
-
# @param other [Node] other node
|
|
461
|
-
# @return [Integer, nil] comparison result
|
|
462
|
-
def <=>(other)
|
|
463
|
-
return unless other.respond_to?(:start_byte)
|
|
464
|
-
cmp = start_byte <=> other.start_byte
|
|
465
|
-
return cmp unless cmp&.zero?
|
|
466
|
-
end_byte <=> other.end_byte
|
|
467
|
-
end
|
|
468
|
-
|
|
469
|
-
# @return [String] human-readable representation
|
|
470
|
-
def inspect
|
|
471
|
-
"#<TreeHaver::Backends::Psych::Node type=#{type} children=#{child_count}>"
|
|
472
|
-
end
|
|
473
|
-
|
|
474
|
-
# Get the next sibling
|
|
475
|
-
#
|
|
476
|
-
# @raise [NotImplementedError] Psych nodes don't have sibling references
|
|
477
|
-
# @return [void]
|
|
478
|
-
def next_sibling
|
|
479
|
-
raise NotImplementedError, "Psych backend does not support sibling navigation"
|
|
480
|
-
end
|
|
481
|
-
|
|
482
|
-
# Get the previous sibling
|
|
483
|
-
#
|
|
484
|
-
# @raise [NotImplementedError] Psych nodes don't have sibling references
|
|
485
|
-
# @return [void]
|
|
486
|
-
def prev_sibling
|
|
487
|
-
raise NotImplementedError, "Psych backend does not support sibling navigation"
|
|
488
|
-
end
|
|
489
|
-
|
|
490
|
-
# Get the parent node
|
|
491
|
-
#
|
|
492
|
-
# @raise [NotImplementedError] Psych nodes don't have parent references
|
|
493
|
-
# @return [void]
|
|
494
|
-
def parent
|
|
495
|
-
raise NotImplementedError, "Psych backend does not support parent navigation"
|
|
264
|
+
row = (inner_node.respond_to?(:end_line) ? inner_node.end_line : 0) || 0
|
|
265
|
+
col = (inner_node.respond_to?(:end_column) ? inner_node.end_column : 0) || 0
|
|
266
|
+
TreeHaver::Base::Point.new(row, col)
|
|
496
267
|
end
|
|
497
268
|
|
|
498
269
|
# Psych-specific: Get the anchor name for Alias/anchored nodes
|
|
499
270
|
#
|
|
500
271
|
# @return [String, nil] Anchor name
|
|
501
272
|
def anchor
|
|
502
|
-
|
|
273
|
+
inner_node.anchor if inner_node.respond_to?(:anchor)
|
|
503
274
|
end
|
|
504
275
|
|
|
505
276
|
# Psych-specific: Get the tag for tagged nodes
|
|
506
277
|
#
|
|
507
278
|
# @return [String, nil] Tag
|
|
508
279
|
def tag
|
|
509
|
-
|
|
280
|
+
inner_node.tag if inner_node.respond_to?(:tag)
|
|
510
281
|
end
|
|
511
282
|
|
|
512
283
|
# Psych-specific: Get the scalar value
|
|
513
284
|
#
|
|
514
285
|
# @return [String, nil] Value for scalar nodes
|
|
515
286
|
def value
|
|
516
|
-
|
|
287
|
+
inner_node.value if inner_node.respond_to?(:value)
|
|
517
288
|
end
|
|
518
289
|
|
|
519
290
|
# Psych-specific: Check if this is a mapping (hash)
|
|
520
291
|
#
|
|
521
292
|
# @return [Boolean]
|
|
522
293
|
def mapping?
|
|
523
|
-
|
|
294
|
+
inner_node.is_a?(::Psych::Nodes::Mapping)
|
|
524
295
|
end
|
|
525
296
|
|
|
526
297
|
# Psych-specific: Check if this is a sequence (array)
|
|
527
298
|
#
|
|
528
299
|
# @return [Boolean]
|
|
529
300
|
def sequence?
|
|
530
|
-
|
|
301
|
+
inner_node.is_a?(::Psych::Nodes::Sequence)
|
|
531
302
|
end
|
|
532
303
|
|
|
533
304
|
# Psych-specific: Check if this is a scalar (primitive)
|
|
534
305
|
#
|
|
535
306
|
# @return [Boolean]
|
|
536
307
|
def scalar?
|
|
537
|
-
|
|
308
|
+
inner_node.is_a?(::Psych::Nodes::Scalar)
|
|
538
309
|
end
|
|
539
310
|
|
|
540
311
|
# Psych-specific: Check if this is an alias
|
|
541
312
|
#
|
|
542
313
|
# @return [Boolean]
|
|
543
314
|
def alias?
|
|
544
|
-
|
|
315
|
+
inner_node.is_a?(::Psych::Nodes::Alias)
|
|
545
316
|
end
|
|
546
317
|
|
|
547
318
|
# Psych-specific: Get mapping entries as key-value pairs
|
|
@@ -561,46 +332,27 @@ module TreeHaver
|
|
|
561
332
|
|
|
562
333
|
private
|
|
563
334
|
|
|
564
|
-
# Calculate byte offset from line and column
|
|
565
|
-
#
|
|
566
|
-
# @param line [Integer] 0-based line number
|
|
567
|
-
# @param column [Integer] 0-based column
|
|
568
|
-
# @return [Integer] Byte offset
|
|
569
|
-
def calculate_byte_offset(line, column)
|
|
570
|
-
offset = 0
|
|
571
|
-
@lines.each_with_index do |line_content, idx|
|
|
572
|
-
if idx < line
|
|
573
|
-
offset += line_content.bytesize
|
|
574
|
-
offset += 1 unless line_content.end_with?("\n") # Add newline
|
|
575
|
-
else
|
|
576
|
-
offset += [column, line_content.bytesize].min
|
|
577
|
-
break
|
|
578
|
-
end
|
|
579
|
-
end
|
|
580
|
-
offset
|
|
581
|
-
end
|
|
582
|
-
|
|
583
335
|
# Extract text from source using location
|
|
584
336
|
#
|
|
585
337
|
# @return [String] Extracted text
|
|
586
338
|
def extract_text_from_location
|
|
587
|
-
return "" unless
|
|
339
|
+
return "" unless inner_node.respond_to?(:start_line) && inner_node.respond_to?(:end_line)
|
|
588
340
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
start_col =
|
|
592
|
-
end_col =
|
|
341
|
+
start_ln = inner_node.start_line || 0
|
|
342
|
+
end_ln = inner_node.end_line || start_ln
|
|
343
|
+
start_col = inner_node.start_column || 0
|
|
344
|
+
end_col = inner_node.end_column || 0
|
|
593
345
|
|
|
594
|
-
if
|
|
595
|
-
line =
|
|
346
|
+
if start_ln == end_ln
|
|
347
|
+
line = lines[start_ln] || ""
|
|
596
348
|
line[start_col...end_col] || ""
|
|
597
349
|
else
|
|
598
350
|
result = []
|
|
599
|
-
(
|
|
600
|
-
line =
|
|
601
|
-
result << if ln ==
|
|
351
|
+
(start_ln..end_ln).each do |ln|
|
|
352
|
+
line = lines[ln] || ""
|
|
353
|
+
result << if ln == start_ln
|
|
602
354
|
line[start_col..]
|
|
603
|
-
elsif ln ==
|
|
355
|
+
elsif ln == end_ln
|
|
604
356
|
line[0...end_col]
|
|
605
357
|
else
|
|
606
358
|
line
|
|
@@ -611,35 +363,12 @@ module TreeHaver
|
|
|
611
363
|
end
|
|
612
364
|
end
|
|
613
365
|
|
|
614
|
-
# Point
|
|
615
|
-
|
|
616
|
-
# Provides both method and hash-style access for compatibility.
|
|
617
|
-
Point = Struct.new(:row, :column) do
|
|
618
|
-
# Hash-like access
|
|
619
|
-
#
|
|
620
|
-
# @param key [Symbol, String] :row or :column
|
|
621
|
-
# @return [Integer, nil]
|
|
622
|
-
def [](key)
|
|
623
|
-
case key
|
|
624
|
-
when :row, "row" then row
|
|
625
|
-
when :column, "column" then column
|
|
626
|
-
end
|
|
627
|
-
end
|
|
366
|
+
# Alias Point to the base class for compatibility
|
|
367
|
+
Point = TreeHaver::Base::Point
|
|
628
368
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
end
|
|
633
|
-
|
|
634
|
-
# @return [String]
|
|
635
|
-
def to_s
|
|
636
|
-
"(#{row}, #{column})"
|
|
637
|
-
end
|
|
638
|
-
|
|
639
|
-
# @return [String]
|
|
640
|
-
def inspect
|
|
641
|
-
"#<TreeHaver::Backends::Psych::Point row=#{row} column=#{column}>"
|
|
642
|
-
end
|
|
369
|
+
# Register availability checker for RSpec dependency tags
|
|
370
|
+
TreeHaver::BackendRegistry.register_availability_checker(:psych) do
|
|
371
|
+
available?
|
|
643
372
|
end
|
|
644
373
|
end
|
|
645
374
|
end
|
|
@@ -11,6 +11,23 @@ module TreeHaver
|
|
|
11
11
|
# tree_stump supports incremental parsing and the Query API, making it
|
|
12
12
|
# suitable for editor/IDE use cases where performance is critical.
|
|
13
13
|
#
|
|
14
|
+
# == Tree/Node Architecture
|
|
15
|
+
#
|
|
16
|
+
# This backend (like all tree-sitter backends: MRI, Rust, FFI, Java) does NOT
|
|
17
|
+
# define its own Tree or Node classes. Instead:
|
|
18
|
+
#
|
|
19
|
+
# - Parser#parse returns raw `::TreeStump::Tree` objects
|
|
20
|
+
# - These are wrapped by `TreeHaver::Tree` (inherits from `Base::Tree`)
|
|
21
|
+
# - `TreeHaver::Tree#root_node` wraps raw nodes in `TreeHaver::Node`
|
|
22
|
+
#
|
|
23
|
+
# This differs from pure-Ruby backends (Citrus, Prism, Psych) which define
|
|
24
|
+
# their own `Backend::X::Tree` and `Backend::X::Node` classes.
|
|
25
|
+
#
|
|
26
|
+
# @see TreeHaver::Tree The wrapper class for tree-sitter Tree objects
|
|
27
|
+
# @see TreeHaver::Node The wrapper class for tree-sitter Node objects
|
|
28
|
+
# @see TreeHaver::Base::Tree Base class documenting the Tree API contract
|
|
29
|
+
# @see TreeHaver::Base::Node Base class documenting the Node API contract
|
|
30
|
+
#
|
|
14
31
|
# == Platform Compatibility
|
|
15
32
|
#
|
|
16
33
|
# - MRI Ruby: ✓ Full support
|
|
@@ -35,20 +52,19 @@ module TreeHaver
|
|
|
35
52
|
def available?
|
|
36
53
|
return @loaded if @load_attempted # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
37
54
|
@load_attempted = true # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
38
|
-
|
|
39
|
-
# tree_stump uses magnus which requires MRI's C API
|
|
40
|
-
# It doesn't work on JRuby or TruffleRuby
|
|
41
|
-
unless RUBY_ENGINE == "ruby"
|
|
42
|
-
@loaded = false # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
43
|
-
return @loaded
|
|
44
|
-
end
|
|
45
|
-
|
|
46
55
|
begin
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
56
|
+
# tree_stump uses magnus which requires MRI's C API
|
|
57
|
+
# It doesn't work on JRuby or TruffleRuby
|
|
58
|
+
if RUBY_ENGINE == "ruby"
|
|
59
|
+
require "tree_stump"
|
|
60
|
+
@loaded = true # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
61
|
+
else
|
|
62
|
+
@loaded = false # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
63
|
+
end
|
|
50
64
|
rescue LoadError
|
|
51
65
|
@loaded = false # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
66
|
+
rescue StandardError
|
|
67
|
+
@loaded = false # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
52
68
|
end
|
|
53
69
|
@loaded # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
54
70
|
end
|
|
@@ -213,6 +229,11 @@ module TreeHaver
|
|
|
213
229
|
@parser.parse(source)
|
|
214
230
|
end
|
|
215
231
|
end
|
|
232
|
+
|
|
233
|
+
# Register availability checker for RSpec dependency tags
|
|
234
|
+
TreeHaver::BackendRegistry.register_availability_checker(:rust) do
|
|
235
|
+
available?
|
|
236
|
+
end
|
|
216
237
|
end
|
|
217
238
|
end
|
|
218
239
|
end
|