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.
@@ -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
- @name = name.to_sym
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
- # Derive language name from symbol if provided
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 @language
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
- @inner_tree = stream
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(@inner_tree, @source, @lines)
171
+ Node.new(inner_tree, source: source, lines: lines)
211
172
  end
212
173
 
213
- # Get parse errors
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=#{@inner_tree.children&.size || 0}>"
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
- @inner_node.class.name.split("::").last.downcase
196
+ inner_node.class.name.split("::").last.downcase
290
197
  end
291
198
 
292
- # Alias for tree-sitter compatibility
293
- alias_method :kind, :type
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 @inner_node
209
+ case inner_node
303
210
  when ::Psych::Nodes::Scalar
304
- @inner_node.value.to_s
211
+ inner_node.value.to_s
305
212
  when ::Psych::Nodes::Alias
306
- "*#{@inner_node.anchor}"
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 @inner_node.respond_to?(:children) && @inner_node.children
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
- # Iterate over child nodes
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 @inner_node.respond_to?(:start_line)
233
+ return 0 unless inner_node.respond_to?(:start_line)
353
234
 
354
- line = @inner_node.start_line || 0
355
- col = @inner_node.start_column || 0
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 @inner_node.respond_to?(:end_line)
244
+ return start_byte + text.bytesize unless inner_node.respond_to?(:end_line)
364
245
 
365
- line = @inner_node.end_line || 0
366
- col = @inner_node.end_column || 0
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 (0-based)
253
+ # @return [TreeHaver::Base::Point] Start position
373
254
  def start_point
374
- row = (@inner_node.respond_to?(:start_line) ? @inner_node.start_line : 0) || 0
375
- col = (@inner_node.respond_to?(:start_column) ? @inner_node.start_column : 0) || 0
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 (0-based)
262
+ # @return [TreeHaver::Base::Point] End position
382
263
  def end_point
383
- row = (@inner_node.respond_to?(:end_line) ? @inner_node.end_line : 0) || 0
384
- col = (@inner_node.respond_to?(:end_column) ? @inner_node.end_column : 0) || 0
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
- @inner_node.anchor if @inner_node.respond_to?(:anchor)
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
- @inner_node.tag if @inner_node.respond_to?(:tag)
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
- @inner_node.value if @inner_node.respond_to?(:value)
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
- @inner_node.is_a?(::Psych::Nodes::Mapping)
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
- @inner_node.is_a?(::Psych::Nodes::Sequence)
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
- @inner_node.is_a?(::Psych::Nodes::Scalar)
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
- @inner_node.is_a?(::Psych::Nodes::Alias)
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 @inner_node.respond_to?(:start_line) && @inner_node.respond_to?(:end_line)
339
+ return "" unless inner_node.respond_to?(:start_line) && inner_node.respond_to?(:end_line)
588
340
 
589
- start_line = @inner_node.start_line || 0
590
- end_line = @inner_node.end_line || start_line
591
- start_col = @inner_node.start_column || 0
592
- end_col = @inner_node.end_column || 0
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 start_line == end_line
595
- line = @lines[start_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
- (start_line..end_line).each do |ln|
600
- line = @lines[ln] || ""
601
- result << if ln == start_line
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 == end_line
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 struct for position information
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
- # @return [Hash]
630
- def to_h
631
- {row: row, column: column}
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
- require "tree_stump"
48
-
49
- @loaded = true # rubocop:disable ThreadSafety/ClassInstanceVariable
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