tree_haver 4.0.5 → 5.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.
@@ -45,8 +45,10 @@ module TreeHaver
45
45
  @loaded = true # rubocop:disable ThreadSafety/ClassInstanceVariable
46
46
  rescue LoadError
47
47
  @loaded = false # rubocop:disable ThreadSafety/ClassInstanceVariable
48
+ # :nocov: defensive code - StandardError during require is extremely rare
48
49
  rescue StandardError
49
50
  @loaded = false # rubocop:disable ThreadSafety/ClassInstanceVariable
51
+ # :nocov:
50
52
  end
51
53
  @loaded # rubocop:disable ThreadSafety/ClassInstanceVariable
52
54
  end
@@ -108,6 +110,22 @@ module TreeHaver
108
110
  @backend = :citrus
109
111
  end
110
112
 
113
+ # Get the language name
114
+ #
115
+ # Derives a name from the grammar module name.
116
+ #
117
+ # @return [Symbol] language name
118
+ def language_name
119
+ # Derive name from grammar module (e.g., TomlRB::Document -> :toml)
120
+ return :unknown unless @grammar_module.respond_to?(:name) && @grammar_module.name
121
+
122
+ name = @grammar_module.name.to_s.split("::").first.downcase
123
+ name.sub(/rb$/, "").to_sym
124
+ end
125
+
126
+ # Alias for language_name (API compatibility)
127
+ alias_method :name, :language_name
128
+
111
129
  # Compare languages for equality
112
130
  #
113
131
  # Citrus languages are equal if they have the same backend and grammar_module.
@@ -192,23 +210,31 @@ module TreeHaver
192
210
 
193
211
  # Set the grammar for this parser
194
212
  #
195
- # Note: TreeHaver::Parser unwraps language objects before calling this method.
196
- # This backend receives the raw Citrus grammar module (unwrapped), not the Language wrapper.
213
+ # Accepts either a Citrus::Language wrapper or a raw Citrus grammar module.
214
+ # When passed a Language wrapper, extracts the grammar_module from it.
215
+ # When passed a raw grammar module, uses it directly.
197
216
  #
198
- # @param grammar [Module] Citrus grammar module with a parse method
217
+ # This flexibility allows both patterns:
218
+ # parser.language = TreeHaver::Backends::Citrus::Language.new(TomlRB::Document)
219
+ # parser.language = TomlRB::Document # Also works
220
+ #
221
+ # @param grammar [Language, Module] Citrus Language wrapper or grammar module
199
222
  # @return [void]
200
- # @example
201
- # require "toml-rb"
202
- # # TreeHaver::Parser unwraps Language.new(TomlRB::Document) to just TomlRB::Document
203
- # parser.language = TomlRB::Document # Backend receives unwrapped module
204
223
  def language=(grammar)
205
- # grammar is already unwrapped by TreeHaver::Parser
206
- unless grammar.respond_to?(:parse)
224
+ # Accept Language wrapper or raw grammar module
225
+ actual_grammar = case grammar
226
+ when Language
227
+ grammar.grammar_module
228
+ else
229
+ grammar
230
+ end
231
+
232
+ unless actual_grammar.respond_to?(:parse)
207
233
  raise ArgumentError,
208
- "Expected Citrus grammar module with parse method, " \
234
+ "Expected Citrus grammar module with parse method or Language wrapper, " \
209
235
  "got #{grammar.class}"
210
236
  end
211
- @grammar = grammar
237
+ @grammar = actual_grammar
212
238
  end
213
239
 
214
240
  # Parse source code
@@ -247,13 +273,18 @@ module TreeHaver
247
273
  # Wraps a Citrus::Match (which represents the parse tree) to provide
248
274
  # tree-sitter-compatible API.
249
275
  #
276
+ # Inherits from Base::Tree to get shared methods like #errors, #warnings,
277
+ # #comments, #has_error?, and #inspect.
278
+ #
250
279
  # @api private
251
- class Tree
252
- attr_reader :root_match, :source
280
+ class Tree < TreeHaver::Base::Tree
281
+ # The raw Citrus::Match root
282
+ # @return [Citrus::Match] The root match
283
+ attr_reader :root_match
253
284
 
254
285
  def initialize(root_match, source)
255
286
  @root_match = root_match
256
- @source = source
287
+ super(root_match, source: source)
257
288
  end
258
289
 
259
290
  def root_node
@@ -273,22 +304,24 @@ module TreeHaver
273
304
  # - matches: child matches
274
305
  # - captures: named groups
275
306
  #
307
+ # Inherits from Base::Node to get shared methods like #first_child, #last_child,
308
+ # #to_s, #inspect, #==, #<=>, #source_position, #start_line, #end_line, etc.
309
+ #
276
310
  # Language-specific helpers can be mixed in for convenience:
277
311
  # require "tree_haver/backends/citrus/toml_helpers"
278
312
  # TreeHaver::Backends::Citrus::Node.include(TreeHaver::Backends::Citrus::TomlHelpers)
279
313
  #
280
314
  # @api private
281
- class Node
282
- include Comparable
283
- include Enumerable
284
-
285
- attr_reader :match, :source
315
+ class Node < TreeHaver::Base::Node
316
+ attr_reader :match
286
317
 
287
318
  def initialize(match, source)
288
319
  @match = match
289
- @source = source
320
+ super(match, source: source)
290
321
  end
291
322
 
323
+ # -- Required API Methods (from Base::Node) ----------------------------
324
+
292
325
  # Get node type from Citrus rule name
293
326
  #
294
327
  # Uses Citrus grammar introspection to dynamically determine node types.
@@ -308,6 +341,50 @@ module TreeHaver
308
341
  extract_type_from_event(@match.events.first)
309
342
  end
310
343
 
344
+ def start_byte
345
+ @match.offset
346
+ end
347
+
348
+ def end_byte
349
+ @match.offset + @match.length
350
+ end
351
+
352
+ def children
353
+ return [] unless @match.respond_to?(:matches)
354
+ @match.matches.map { |m| Node.new(m, @source) }
355
+ end
356
+
357
+ # -- Overridden Methods ------------------------------------------------
358
+
359
+ # Override start_point to calculate from source
360
+ def start_point
361
+ calculate_point(@match.offset)
362
+ end
363
+
364
+ # Override end_point to calculate from source
365
+ def end_point
366
+ calculate_point(@match.offset + @match.length)
367
+ end
368
+
369
+ # Override text to use Citrus match string
370
+ def text
371
+ @match.string
372
+ end
373
+
374
+ # Override child_count for efficiency (avoid building full children array)
375
+ def child_count
376
+ @match.respond_to?(:matches) ? @match.matches.size : 0
377
+ end
378
+
379
+ # Override child to handle negative indices properly
380
+ def child(index)
381
+ return if index.negative?
382
+ return unless @match.respond_to?(:matches)
383
+ return if index >= @match.matches.size
384
+
385
+ Node.new(@match.matches[index], @source)
386
+ end
387
+
311
388
  # Check if this node represents a structural element vs a terminal/token
312
389
  #
313
390
  # Uses Citrus grammar's terminal? method to determine if this is
@@ -387,99 +464,6 @@ module TreeHaver
387
464
  "unknown"
388
465
  end
389
466
 
390
- public
391
-
392
- def start_byte
393
- @match.offset
394
- end
395
-
396
- def end_byte
397
- @match.offset + @match.length
398
- end
399
-
400
- def start_point
401
- calculate_point(@match.offset)
402
- end
403
-
404
- def end_point
405
- calculate_point(@match.offset + @match.length)
406
- end
407
-
408
- # Get the 1-based line number where this node starts
409
- #
410
- # @return [Integer] 1-based line number
411
- def start_line
412
- start_point[:row] + 1
413
- end
414
-
415
- # Get the 1-based line number where this node ends
416
- #
417
- # @return [Integer] 1-based line number
418
- def end_line
419
- end_point[:row] + 1
420
- end
421
-
422
- # Get position information as a hash
423
- #
424
- # Returns a hash with 1-based line numbers and 0-based columns.
425
- # Compatible with *-merge gems' FileAnalysisBase.
426
- #
427
- # @return [Hash{Symbol => Integer}] Position hash
428
- def source_position
429
- {
430
- start_line: start_line,
431
- end_line: end_line,
432
- start_column: start_point[:column],
433
- end_column: end_point[:column],
434
- }
435
- end
436
-
437
- # Get the first child node
438
- #
439
- # @return [Node, nil] First child or nil
440
- def first_child
441
- child(0)
442
- end
443
-
444
- def text
445
- @match.string
446
- end
447
-
448
- def child_count
449
- @match.respond_to?(:matches) ? @match.matches.size : 0
450
- end
451
-
452
- def child(index)
453
- return unless @match.respond_to?(:matches)
454
- return if index >= @match.matches.size
455
-
456
- Node.new(@match.matches[index], @source)
457
- end
458
-
459
- def children
460
- return [] unless @match.respond_to?(:matches)
461
- @match.matches.map { |m| Node.new(m, @source) }
462
- end
463
-
464
- def each(&block)
465
- return to_enum(__method__) unless block_given?
466
- children.each(&block)
467
- end
468
-
469
- def has_error?
470
- false # Citrus raises on parse error, so successful parse has no errors
471
- end
472
-
473
- def missing?
474
- false # Citrus doesn't have the concept of missing nodes
475
- end
476
-
477
- def named?
478
- true # Citrus matches are typically "named" in tree-sitter terminology
479
- end
480
-
481
- private
482
-
483
467
  def calculate_point(offset)
484
468
  return {row: 0, column: 0} if offset <= 0
485
469
 
@@ -494,7 +478,7 @@ module TreeHaver
494
478
  end
495
479
  end
496
480
 
497
- # Register availability checker for RSpec dependency tags
481
+ # Register the availability checker for RSpec dependency tags
498
482
  TreeHaver::BackendRegistry.register_availability_checker(:citrus) do
499
483
  available?
500
484
  end
@@ -17,18 +17,26 @@ module TreeHaver
17
17
  #
18
18
  # == Tree/Node Architecture
19
19
  #
20
- # This backend (like all tree-sitter backends: MRI, Rust, FFI, Java) does NOT
21
- # define its own Tree or Node classes. Instead:
20
+ # This backend defines raw `FFI::Tree` and `FFI::Node` wrapper classes that
21
+ # provide minimal FFI bindings to the tree-sitter C structs. These are **not**
22
+ # intended for direct use by application code.
22
23
  #
23
- # - Parser#parse returns raw FFI-wrapped tree objects
24
- # - These are wrapped by `TreeHaver::Tree` (inherits from `Base::Tree`)
25
- # - `TreeHaver::Tree#root_node` wraps raw nodes in `TreeHaver::Node`
24
+ # The wrapping hierarchy is:
25
+ # FFI::Tree/Node (raw FFI wrappers) TreeHaver::Tree/Node Base::Tree/Node
26
26
  #
27
- # This differs from pure-Ruby backends (Citrus, Prism, Psych) which define
28
- # their own `Backend::X::Tree` and `Backend::X::Node` classes.
27
+ # When you use `TreeHaver::Parser#parse`:
28
+ # 1. `FFI::Parser#parse` returns an `FFI::Tree` (raw pointer wrapper)
29
+ # 2. `TreeHaver::Parser` wraps it in `TreeHaver::Tree` (adds source storage)
30
+ # 3. `TreeHaver::Tree#root_node` wraps `FFI::Node` in `TreeHaver::Node`
29
31
  #
30
- # @see TreeHaver::Tree The wrapper class for tree-sitter Tree objects
31
- # @see TreeHaver::Node The wrapper class for tree-sitter Node objects
32
+ # The `TreeHaver::Tree` and `TreeHaver::Node` wrappers provide the full unified
33
+ # API including `#children`, `#text`, `#source`, `#source_position`, etc.
34
+ #
35
+ # This differs from pure-Ruby backends (Citrus, Parslet, Prism, Psych) which
36
+ # define Tree/Node classes that directly inherit from Base::Tree/Base::Node.
37
+ #
38
+ # @see TreeHaver::Tree The wrapper class users should interact with
39
+ # @see TreeHaver::Node The wrapper class users should interact with
32
40
  # @see TreeHaver::Base::Tree Base class documenting the Tree API contract
33
41
  # @see TreeHaver::Base::Node Base class documenting the Node API contract
34
42
  #
@@ -79,16 +87,20 @@ module TreeHaver
79
87
  @loaded = begin # rubocop:disable ThreadSafety/ClassInstanceVariable
80
88
  # TruffleRuby's FFI doesn't support STRUCT_BY_VALUE return types
81
89
  # which tree-sitter uses extensively (ts_tree_root_node, ts_node_child, etc.)
90
+ # :nocov: TruffleRuby returns false early - subsequent FFI code paths unreachable on TruffleRuby
82
91
  if RUBY_ENGINE == "truffleruby"
83
92
  false
93
+ # :nocov:
84
94
  else
85
95
  require "ffi"
86
96
  true
87
97
  end
88
98
  rescue LoadError
89
99
  false
100
+ # :nocov: defensive code - StandardError during require is extremely rare
90
101
  rescue StandardError
91
102
  false
103
+ # :nocov:
92
104
  end
93
105
  @loaded # rubocop:disable ThreadSafety/ClassInstanceVariable
94
106
  end
@@ -371,6 +383,30 @@ module TreeHaver
371
383
  # Alias eql? to ==
372
384
  alias_method :eql?, :==
373
385
 
386
+ # Get the language name
387
+ #
388
+ # Derives a name from the symbol or path.
389
+ #
390
+ # @return [Symbol] language name
391
+ def language_name
392
+ # Try to derive from symbol (e.g., "tree_sitter_toml" -> :toml)
393
+ if @symbol
394
+ name = @symbol.to_s.sub(/^tree_sitter_/, "")
395
+ return name.to_sym
396
+ end
397
+
398
+ # Try to derive from path (e.g., "/path/to/libtree-sitter-toml.so" -> :toml)
399
+ if @path
400
+ name = LibraryPathUtils.derive_language_name_from_path(@path)
401
+ return name.to_sym if name
402
+ end
403
+
404
+ :unknown
405
+ end
406
+
407
+ # Alias for language_name (API compatibility)
408
+ alias_method :name, :language_name
409
+
374
410
  # Convert to FFI pointer for passing to native functions
375
411
  #
376
412
  # @return [FFI::Pointer]
@@ -642,10 +678,37 @@ module TreeHaver
642
678
  end
643
679
  end
644
680
 
645
- # FFI-based tree-sitter node
681
+ # FFI-based tree-sitter node (raw backend node)
682
+ #
683
+ # This is a **raw backend node** that wraps a TSNode by-value struct from the
684
+ # tree-sitter C API. It provides the minimal interface needed for tree-sitter
685
+ # operations but is NOT intended for direct use by application code.
686
+ #
687
+ # == Architecture Note
688
+ #
689
+ # Unlike pure-Ruby backends (Citrus, Parslet, Prism, Psych) which define Node
690
+ # classes that inherit from `TreeHaver::Base::Node`, tree-sitter backends (MRI,
691
+ # Rust, FFI, Java) define raw wrapper classes that get wrapped by `TreeHaver::Node`.
646
692
  #
647
- # Wraps a TSNode by-value struct. TSNode is passed by value in the
648
- # tree-sitter C API, so we store the struct value directly.
693
+ # The wrapping hierarchy is:
694
+ # FFI::Node (this class) TreeHaver::Node Base::Node
695
+ #
696
+ # When you use `TreeHaver::Parser#parse`, the returned tree's nodes are already
697
+ # wrapped in `TreeHaver::Node`, which provides the full unified API including:
698
+ # - `#children` - Array of child nodes
699
+ # - `#text` - Extract text from source
700
+ # - `#first_child`, `#last_child` - Convenience accessors
701
+ # - `#start_line`, `#end_line` - 1-based line numbers
702
+ # - `#source_position` - Hash with position info
703
+ # - `#each`, `#map`, etc. - Enumerable methods
704
+ # - `#to_s`, `#inspect` - String representations
705
+ #
706
+ # This raw class only implements methods that require direct FFI calls to the
707
+ # tree-sitter C library. The wrapper adds Ruby-level conveniences.
708
+ #
709
+ # @api private
710
+ # @see TreeHaver::Node The wrapper class users should interact with
711
+ # @see TreeHaver::Base::Node The base class documenting the full Node API
649
712
  class Node
650
713
  include Enumerable
651
714
 
@@ -937,7 +1000,7 @@ module TreeHaver
937
1000
  end
938
1001
  end
939
1002
 
940
- # Register availability checker for RSpec dependency tags
1003
+ # Register the availability checker for RSpec dependency tags
941
1004
  TreeHaver::BackendRegistry.register_availability_checker(:ffi) do
942
1005
  available?
943
1006
  end
@@ -17,18 +17,27 @@ module TreeHaver
17
17
  #
18
18
  # == Tree/Node Architecture
19
19
  #
20
- # This backend (like all tree-sitter backends: MRI, Rust, FFI, Java) does NOT
21
- # define its own Tree or Node classes at the Ruby level. Instead:
20
+ # This backend defines Ruby wrapper classes (`Java::Language`, `Java::Parser`,
21
+ # `Java::Tree`, `Java::Node`) that wrap the raw jtreesitter Java objects via
22
+ # JRuby's Java interop. These are **raw backend wrappers** not intended for
23
+ # direct use by application code.
22
24
  #
23
- # - Parser#parse returns raw Java tree objects (via JRuby interop)
24
- # - These are wrapped by `TreeHaver::Tree` (inherits from `Base::Tree`)
25
- # - `TreeHaver::Tree#root_node` wraps raw nodes in `TreeHaver::Node`
25
+ # The wrapping hierarchy is:
26
+ # Java::Tree/Node (this backend) TreeHaver::Tree/Node Base::Tree/Node
26
27
  #
27
- # This differs from pure-Ruby backends (Citrus, Prism, Psych) which define
28
- # their own `Backend::X::Tree` and `Backend::X::Node` classes.
28
+ # When you use `TreeHaver::Parser#parse`:
29
+ # 1. `Java::Parser#parse` returns a `Java::Tree` (wrapper around jtreesitter Tree)
30
+ # 2. `TreeHaver::Parser` wraps it in `TreeHaver::Tree` (adds source storage)
31
+ # 3. `TreeHaver::Tree#root_node` wraps `Java::Node` in `TreeHaver::Node`
29
32
  #
30
- # @see TreeHaver::Tree The wrapper class for tree-sitter Tree objects
31
- # @see TreeHaver::Node The wrapper class for tree-sitter Node objects
33
+ # The `TreeHaver::Tree` and `TreeHaver::Node` wrappers provide the full unified
34
+ # API including `#children`, `#text`, `#source`, `#source_position`, etc.
35
+ #
36
+ # This differs from pure-Ruby backends (Citrus, Parslet, Prism, Psych) which
37
+ # define Tree/Node classes that directly inherit from Base::Tree/Base::Node.
38
+ #
39
+ # @see TreeHaver::Tree The wrapper class users should interact with
40
+ # @see TreeHaver::Node The wrapper class users should interact with
32
41
  # @see TreeHaver::Base::Tree Base class documenting the Tree API contract
33
42
  # @see TreeHaver::Base::Node Base class documenting the Node API contract
34
43
  #
@@ -228,8 +237,17 @@ module TreeHaver
228
237
  # :nocov:
229
238
  end
230
239
 
231
- # Wrapper for java-tree-sitter Language
240
+ # Java backend language wrapper (raw backend language)
241
+ #
242
+ # This is a **raw backend language** that wraps a jtreesitter Language object
243
+ # via JRuby's Java interop. It is used to configure the parser for a specific
244
+ # grammar (e.g., TOML, JSON, etc.).
232
245
  #
246
+ # Unlike `TreeHaver::Language` (which is a module with factory methods), this
247
+ # class holds the actual loaded language data from a grammar shared library.
248
+ #
249
+ # @api private
250
+ # @see TreeHaver::Language The factory module users should interact with
233
251
  # @see https://tree-sitter.github.io/java-tree-sitter/io/github/treesitter/jtreesitter/Language.html
234
252
  #
235
253
  # :nocov:
@@ -456,8 +474,19 @@ module TreeHaver
456
474
  end
457
475
  end
458
476
 
459
- # Wrapper for java-tree-sitter Parser
477
+ # Java backend parser wrapper (raw backend parser)
478
+ #
479
+ # This is a **raw backend parser** that wraps a jtreesitter Parser object via
480
+ # JRuby's Java interop. It is NOT intended for direct use by application code.
460
481
  #
482
+ # Users should use `TreeHaver::Parser` which wraps this class and provides:
483
+ # - Automatic backend selection
484
+ # - Language wrapper unwrapping
485
+ # - Tree wrapping with source storage
486
+ # - Unified API across all backends
487
+ #
488
+ # @api private
489
+ # @see TreeHaver::Parser The wrapper class users should interact with
461
490
  # @see https://tree-sitter.github.io/java-tree-sitter/io/github/treesitter/jtreesitter/Parser.html
462
491
  class Parser
463
492
  # Create a new parser instance
@@ -543,8 +572,35 @@ module TreeHaver
543
572
  end
544
573
  end
545
574
 
546
- # Wrapper for java-tree-sitter Tree
575
+ # Java backend tree wrapper (raw backend tree)
576
+ #
577
+ # This is a **raw backend tree** that wraps a jtreesitter Tree object via
578
+ # JRuby's Java interop. It is NOT intended for direct use by application code.
579
+ #
580
+ # == Architecture Note
581
+ #
582
+ # Unlike pure-Ruby backends (Citrus, Parslet, Prism, Psych) which define Tree
583
+ # classes that inherit from `TreeHaver::Base::Tree`, tree-sitter backends (MRI,
584
+ # Rust, FFI, Java) define raw wrapper classes that get wrapped by `TreeHaver::Tree`.
547
585
  #
586
+ # The wrapping hierarchy is:
587
+ # Java::Tree (this class) → TreeHaver::Tree → Base::Tree
588
+ #
589
+ # When you use `TreeHaver::Parser#parse`, the returned tree is already wrapped
590
+ # in `TreeHaver::Tree`, which provides the full unified API including:
591
+ # - `#source` - The original source text
592
+ # - `#root_node` - Returns a `TreeHaver::Node` (not raw `Java::Node`)
593
+ # - `#errors`, `#warnings`, `#comments` - Parse diagnostics
594
+ # - `#edit` - Mark tree as edited for incremental parsing
595
+ # - `#to_s`, `#inspect` - String representations
596
+ #
597
+ # This raw class only implements methods that require direct calls to jtreesitter.
598
+ # The wrapper adds Ruby-level conveniences and stores the source text needed for
599
+ # `Node#text` extraction.
600
+ #
601
+ # @api private
602
+ # @see TreeHaver::Tree The wrapper class users should interact with
603
+ # @see TreeHaver::Base::Tree The base class documenting the full Tree API
548
604
  # @see https://tree-sitter.github.io/java-tree-sitter/io/github/treesitter/jtreesitter/Tree.html
549
605
  class Tree
550
606
  attr_reader :impl
@@ -601,8 +657,37 @@ module TreeHaver
601
657
  end
602
658
  end
603
659
 
604
- # Wrapper for java-tree-sitter Node
660
+ # Java backend node wrapper (raw backend node)
661
+ #
662
+ # This is a **raw backend node** that wraps a jtreesitter Node object via
663
+ # JRuby's Java interop. It provides the minimal interface needed for tree-sitter
664
+ # operations but is NOT intended for direct use by application code.
665
+ #
666
+ # == Architecture Note
605
667
  #
668
+ # Unlike pure-Ruby backends (Citrus, Parslet, Prism, Psych) which define Node
669
+ # classes that inherit from `TreeHaver::Base::Node`, tree-sitter backends (MRI,
670
+ # Rust, FFI, Java) define raw wrapper classes that get wrapped by `TreeHaver::Node`.
671
+ #
672
+ # The wrapping hierarchy is:
673
+ # Java::Node (this class) → TreeHaver::Node → Base::Node
674
+ #
675
+ # When you use `TreeHaver::Parser#parse`, the returned tree's nodes are already
676
+ # wrapped in `TreeHaver::Node`, which provides the full unified API including:
677
+ # - `#children` - Array of child nodes
678
+ # - `#text` - Extract text from source
679
+ # - `#first_child`, `#last_child` - Convenience accessors
680
+ # - `#start_line`, `#end_line` - 1-based line numbers
681
+ # - `#source_position` - Hash with position info
682
+ # - `#each`, `#map`, etc. - Enumerable methods
683
+ # - `#to_s`, `#inspect` - String representations
684
+ #
685
+ # This raw class only implements methods that require direct calls to jtreesitter.
686
+ # The wrapper adds Ruby-level conveniences.
687
+ #
688
+ # @api private
689
+ # @see TreeHaver::Node The wrapper class users should interact with
690
+ # @see TreeHaver::Base::Node The base class documenting the full Node API
606
691
  # @see https://tree-sitter.github.io/java-tree-sitter/io/github/treesitter/jtreesitter/Node.html
607
692
  class Node
608
693
  attr_reader :impl
@@ -799,7 +884,7 @@ module TreeHaver
799
884
  end
800
885
  # :nocov:
801
886
 
802
- # Register availability checker for RSpec dependency tags
887
+ # Register the availability checker for RSpec dependency tags
803
888
  TreeHaver::BackendRegistry.register_availability_checker(:java) do
804
889
  available?
805
890
  end
@@ -142,6 +142,30 @@ module TreeHaver
142
142
  @symbol = symbol
143
143
  end
144
144
 
145
+ # Get the language name
146
+ #
147
+ # Derives a name from the symbol or path.
148
+ #
149
+ # @return [Symbol] language name
150
+ def language_name
151
+ # Try to derive from symbol (e.g., "tree_sitter_toml" -> :toml)
152
+ if @symbol
153
+ name = @symbol.to_s.sub(/^tree_sitter_/, "")
154
+ return name.to_sym
155
+ end
156
+
157
+ # Try to derive from path (e.g., "/path/to/libtree-sitter-toml.so" -> :toml)
158
+ if @path
159
+ name = LibraryPathUtils.derive_language_name_from_path(@path)
160
+ return name.to_sym if name
161
+ end
162
+
163
+ :unknown
164
+ end
165
+
166
+ # Alias for language_name (API compatibility)
167
+ alias_method :name, :language_name
168
+
145
169
  # Compare languages for equality
146
170
  #
147
171
  # MRI languages are equal if they have the same backend, path, and symbol.
@@ -329,7 +353,7 @@ module TreeHaver
329
353
  end
330
354
  end
331
355
 
332
- # Register availability checker for RSpec dependency tags
356
+ # Register the availability checker for RSpec dependency tags
333
357
  TreeHaver::BackendRegistry.register_availability_checker(:mri) do
334
358
  available?
335
359
  end