tree_haver 1.0.0 → 2.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.
data/lib/tree_haver.rb CHANGED
@@ -7,9 +7,9 @@ require "version_gem"
7
7
  require_relative "tree_haver/version"
8
8
  require_relative "tree_haver/language_registry"
9
9
 
10
- # TreeHaver is a cross-Ruby adapter for the Tree-sitter parsing library.
10
+ # TreeHaver is a cross-Ruby adapter for the tree-sitter parsing library.
11
11
  #
12
- # It provides a unified API for parsing source code using Tree-sitter grammars,
12
+ # It provides a unified API for parsing source code using tree-sitter grammars,
13
13
  # working seamlessly across MRI Ruby, JRuby, and TruffleRuby.
14
14
  #
15
15
  # @example Basic usage with TOML
@@ -54,7 +54,7 @@ require_relative "tree_haver/language_registry"
54
54
  # TreeHaver.backend = :mri # Force MRI backend
55
55
  # TreeHaver.backend = :auto # Auto-select (default)
56
56
  #
57
- # @see https://tree-sitter.github.io/tree-sitter/ Tree-sitter documentation
57
+ # @see https://tree-sitter.github.io/tree-sitter/ tree-sitter documentation
58
58
  # @see GrammarFinder For automatic grammar library discovery
59
59
  module TreeHaver
60
60
  # Base error class for TreeHaver exceptions
@@ -83,12 +83,14 @@ module TreeHaver
83
83
  # - {Backends::MRI} - Uses ruby_tree_sitter (MRI C extension)
84
84
  # - {Backends::Rust} - Uses tree_stump (Rust extension with precompiled binaries)
85
85
  # - {Backends::FFI} - Uses Ruby FFI to call libtree-sitter directly
86
- # - {Backends::Java} - Uses JRuby's Java integration (planned)
86
+ # - {Backends::Java} - Uses JRuby's Java integration
87
+ # - {Backends::Citrus} - Uses Citrus PEG parser (pure Ruby, portable)
87
88
  module Backends
88
89
  autoload :MRI, File.join(__dir__, "tree_haver", "backends", "mri")
89
90
  autoload :Rust, File.join(__dir__, "tree_haver", "backends", "rust")
90
91
  autoload :FFI, File.join(__dir__, "tree_haver", "backends", "ffi")
91
92
  autoload :Java, File.join(__dir__, "tree_haver", "backends", "java")
93
+ autoload :Citrus, File.join(__dir__, "tree_haver", "backends", "citrus")
92
94
  end
93
95
 
94
96
  # Security utilities for validating paths before loading shared libraries
@@ -119,9 +121,15 @@ module TreeHaver
119
121
  # @see PathValidator
120
122
  autoload :GrammarFinder, File.join(__dir__, "tree_haver", "grammar_finder")
121
123
 
124
+ # Unified Node wrapper providing consistent API across backends
125
+ autoload :Node, File.join(__dir__, "tree_haver", "node")
126
+
127
+ # Unified Tree wrapper providing consistent API across backends
128
+ autoload :Tree, File.join(__dir__, "tree_haver", "tree")
129
+
122
130
  # Get the current backend selection
123
131
  #
124
- # @return [Symbol] one of :auto, :mri, :ffi, or :java
132
+ # @return [Symbol] one of :auto, :mri, :rust, :ffi, :java, or :citrus
125
133
  # @note Can be set via ENV["TREE_HAVER_BACKEND"]
126
134
  class << self
127
135
  # @example
@@ -132,13 +140,14 @@ module TreeHaver
132
140
  when "rust" then :rust
133
141
  when "ffi" then :ffi
134
142
  when "java" then :java
143
+ when "citrus" then :citrus
135
144
  else :auto
136
145
  end
137
146
  end
138
147
 
139
148
  # Set the backend to use
140
149
  #
141
- # @param name [Symbol, String, nil] backend name (:auto, :mri, :rust, :ffi, :java)
150
+ # @param name [Symbol, String, nil] backend name (:auto, :mri, :rust, :ffi, :java, :citrus)
142
151
  # @return [Symbol, nil] the backend that was set
143
152
  # @example Force FFI backend
144
153
  # TreeHaver.backend = :ffi
@@ -165,10 +174,11 @@ module TreeHaver
165
174
  # Determine the concrete backend module to use
166
175
  #
167
176
  # This method performs backend auto-selection when backend is :auto.
168
- # On JRuby, prefers Java backend if available, then FFI.
169
- # On MRI, prefers MRI backend if available, then Rust, then FFI.
177
+ # On JRuby, prefers Java backend if available, then FFI, then Citrus.
178
+ # On MRI, prefers MRI backend if available, then Rust, then FFI, then Citrus.
179
+ # Citrus is the final fallback as it's pure Ruby and works everywhere.
170
180
  #
171
- # @return [Module, nil] the backend module (Backends::MRI, Backends::Rust, Backends::FFI, or Backends::Java), or nil if none available
181
+ # @return [Module, nil] the backend module (Backends::MRI, Backends::Rust, Backends::FFI, Backends::Java, or Backends::Citrus), or nil if none available
172
182
  # @example
173
183
  # mod = TreeHaver.backend_module
174
184
  # if mod
@@ -184,8 +194,10 @@ module TreeHaver
184
194
  Backends::FFI
185
195
  when :java
186
196
  Backends::Java
197
+ when :citrus
198
+ Backends::Citrus
187
199
  else
188
- # auto-select: on JRuby prefer Java backend if available; on MRI prefer MRI, then Rust; otherwise FFI
200
+ # auto-select: prefer native/fast backends, fall back to pure Ruby (Citrus)
189
201
  if defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby" && Backends::Java.available?
190
202
  Backends::Java
191
203
  elsif defined?(RUBY_ENGINE) && RUBY_ENGINE == "ruby" && Backends::MRI.available?
@@ -194,8 +206,10 @@ module TreeHaver
194
206
  Backends::Rust
195
207
  elsif Backends::FFI.available?
196
208
  Backends::FFI
209
+ elsif Backends::Citrus.available?
210
+ Backends::Citrus # Pure Ruby fallback
197
211
  else
198
- # No backend available yet
212
+ # No backend available
199
213
  nil
200
214
  end
201
215
  end
@@ -276,7 +290,7 @@ module TreeHaver
276
290
  end
277
291
  end
278
292
 
279
- # Represents a Tree-sitter language grammar
293
+ # Represents a tree-sitter language grammar
280
294
  #
281
295
  # A Language object is an opaque handle to a TSLanguage* that defines
282
296
  # the grammar rules for parsing a specific programming language.
@@ -400,7 +414,7 @@ module TreeHaver
400
414
  end
401
415
  end
402
416
 
403
- # Represents a Tree-sitter parser instance
417
+ # Represents a tree-sitter parser instance
404
418
  #
405
419
  # A Parser is used to parse source code into a syntax tree. You must
406
420
  # set a language before parsing.
@@ -448,7 +462,7 @@ module TreeHaver
448
462
  #
449
463
  # == Incremental Parsing
450
464
  #
451
- # Tree-sitter supports **incremental parsing** where you can pass a previously
465
+ # tree-sitter supports **incremental parsing** where you can pass a previously
452
466
  # parsed tree along with edit information to efficiently re-parse only the
453
467
  # changed portions of source code. This is a major performance optimization
454
468
  # for editors and IDEs that need to re-parse on every keystroke.
@@ -458,7 +472,7 @@ module TreeHaver
458
472
  # 2. User edits the source (e.g., inserts a character)
459
473
  # 3. Call `tree.edit(...)` to update the tree's position data
460
474
  # 4. Re-parse with the old tree: `new_tree = parser.parse_string(tree, new_source)`
461
- # 5. Tree-sitter reuses unchanged nodes, only re-parsing affected regions
475
+ # 5. tree-sitter reuses unchanged nodes, only re-parsing affected regions
462
476
  #
463
477
  # TreeHaver passes through to the underlying backend if it supports incremental
464
478
  # parsing (MRI and Rust backends do). Check `TreeHaver.capabilities[:incremental]`
@@ -467,7 +481,7 @@ module TreeHaver
467
481
  # @param old_tree [Tree, nil] previously parsed tree for incremental parsing, or nil for fresh parse
468
482
  # @param source [String] the source code to parse (should be UTF-8)
469
483
  # @return [Tree] the parsed syntax tree
470
- # @see https://tree-sitter.github.io/tree-sitter/using-parsers#editing Tree-sitter incremental parsing docs
484
+ # @see https://tree-sitter.github.io/tree-sitter/using-parsers#editing tree-sitter incremental parsing docs
471
485
  # @see Tree#edit For marking edits before incremental re-parsing
472
486
  # @example First parse (no old tree)
473
487
  # tree = parser.parse_string(nil, "x = 1")
@@ -478,7 +492,14 @@ module TreeHaver
478
492
  # Pass through to backend if it supports incremental parsing
479
493
  if old_tree && @impl.respond_to?(:parse_string)
480
494
  # Extract the underlying implementation from our Tree wrapper
481
- old_impl = old_tree.is_a?(Tree) ? old_tree.instance_variable_get(:@impl) : old_tree
495
+ old_impl = if old_tree.respond_to?(:inner_tree)
496
+ old_tree.inner_tree
497
+ elsif old_tree.respond_to?(:instance_variable_get)
498
+ # Fallback for compatibility
499
+ old_tree.instance_variable_get(:@inner_tree) || old_tree.instance_variable_get(:@impl) || old_tree
500
+ else
501
+ old_tree
502
+ end
482
503
  tree_impl = @impl.parse_string(old_impl, source)
483
504
  Tree.new(tree_impl)
484
505
  elsif @impl.respond_to?(:parse_string)
@@ -491,219 +512,13 @@ module TreeHaver
491
512
  end
492
513
  end
493
514
 
494
- # Represents a parsed syntax tree
495
- #
496
- # A Tree is the result of parsing source code. It provides access to
497
- # the root node of the AST and supports incremental parsing via the
498
- # {#edit} method.
499
- #
500
- # @example Basic usage
501
- # tree = parser.parse(source)
502
- # root = tree.root_node
503
- #
504
- # @example Incremental parsing
505
- # tree = parser.parse_string(nil, original_source)
506
- # tree.edit(
507
- # start_byte: 10,
508
- # old_end_byte: 15,
509
- # new_end_byte: 20,
510
- # start_point: { row: 0, column: 10 },
511
- # old_end_point: { row: 0, column: 15 },
512
- # new_end_point: { row: 0, column: 20 }
513
- # )
514
- # new_tree = parser.parse_string(tree, edited_source)
515
- class Tree
516
- # @api private
517
- def initialize(impl)
518
- @impl = impl
519
- end
520
-
521
- # Get the root node of the syntax tree
522
- #
523
- # @return [Node] the root node
524
- # @example
525
- # root = tree.root_node
526
- # puts root.type # => "document" or similar
527
- def root_node
528
- Node.new(@impl.root_node)
529
- end
530
-
531
- # Mark the tree as edited for incremental re-parsing
532
- #
533
- # Call this method after the source code has been modified but before
534
- # re-parsing. This tells Tree-sitter which parts of the tree are
535
- # invalidated so it can efficiently re-parse only the affected regions.
536
- #
537
- # @param start_byte [Integer] byte offset where the edit starts
538
- # @param old_end_byte [Integer] byte offset where the old text ended
539
- # @param new_end_byte [Integer] byte offset where the new text ends
540
- # @param start_point [Hash] starting position as `{ row:, column: }`
541
- # @param old_end_point [Hash] old ending position as `{ row:, column: }`
542
- # @param new_end_point [Hash] new ending position as `{ row:, column: }`
543
- # @return [void]
544
- # @raise [NotAvailable] if the backend doesn't support incremental parsing
545
- # @see https://tree-sitter.github.io/tree-sitter/using-parsers#editing
546
- #
547
- # @example
548
- # # Original: "x = 1"
549
- # # Edited: "x = 42" (replaced "1" with "42" at byte 4)
550
- # tree.edit(
551
- # start_byte: 4,
552
- # old_end_byte: 5,
553
- # new_end_byte: 6,
554
- # start_point: { row: 0, column: 4 },
555
- # old_end_point: { row: 0, column: 5 },
556
- # new_end_point: { row: 0, column: 6 }
557
- # )
558
- def edit(start_byte:, old_end_byte:, new_end_byte:, start_point:, old_end_point:, new_end_point:)
559
- unless @impl.respond_to?(:edit)
560
- raise NotAvailable, "Incremental parsing not supported by current backend. " \
561
- "Use MRI (ruby_tree_sitter) or Rust (tree_stump) backend."
562
- end
563
-
564
- @impl.edit(
565
- start_byte: start_byte,
566
- old_end_byte: old_end_byte,
567
- new_end_byte: new_end_byte,
568
- start_point: start_point,
569
- old_end_point: old_end_point,
570
- new_end_point: new_end_point,
571
- )
572
- end
573
-
574
- # Check if the underlying implementation supports incremental parsing
575
- #
576
- # @return [Boolean] true if {#edit} can be called on this tree
577
- def supports_editing?
578
- @impl.respond_to?(:edit)
579
- end
580
- end
581
-
582
- # Represents a node in the syntax tree
515
+ # Tree and Node classes have been moved to separate files:
516
+ # - tree_haver/tree.rb: TreeHaver::Tree - unified wrapper providing consistent API
517
+ # - tree_haver/node.rb: TreeHaver::Node - unified wrapper providing consistent API
583
518
  #
584
- # A Node represents a single element in the parsed AST. Each node has
585
- # a type (like "string", "number", "table", etc.) and may have child nodes.
586
- #
587
- # @example Traversing nodes
588
- # root = tree.root_node
589
- # root.each do |child|
590
- # puts "Child type: #{child.type}"
591
- # child.each { |grandchild| puts " Grandchild: #{grandchild.type}" }
592
- # end
593
- class Node
594
- # @api private
595
- def initialize(impl)
596
- @impl = impl
597
- end
598
-
599
- # Get the type name of this node
600
- #
601
- # The type corresponds to the grammar rule that produced this node
602
- # (e.g., "document", "table", "string_literal", "pair", etc.).
603
- #
604
- # @return [String] the node type
605
- # @example
606
- # node.type # => "table"
607
- def type
608
- @impl.type
609
- end
610
-
611
- # Iterate over child nodes
612
- #
613
- # @yieldparam child [Node] each child node
614
- # @return [Enumerator, nil] an enumerator if no block given, nil otherwise
615
- # @example With a block
616
- # node.each { |child| puts child.type }
617
- #
618
- # @example Without a block
619
- # children = node.each.to_a
620
- def each(&blk)
621
- return enum_for(:each) unless block_given?
622
- @impl.each { |child_impl| blk.call(Node.new(child_impl)) }
623
- end
624
-
625
- # Get the start position of this node in the source
626
- #
627
- # @return [Object] point object with row and column
628
- # @example
629
- # node.start_point.row # => 0
630
- # node.start_point.column # => 4
631
- def start_point
632
- @impl.start_point
633
- end
634
-
635
- # Get the end position of this node in the source
636
- #
637
- # @return [Object] point object with row and column
638
- # @example
639
- # node.end_point.row # => 0
640
- # node.end_point.column # => 10
641
- def end_point
642
- @impl.end_point
643
- end
644
-
645
- # Get the start byte offset of this node in the source
646
- #
647
- # @return [Integer] byte offset from beginning of source
648
- def start_byte
649
- @impl.start_byte
650
- end
651
-
652
- # Get the end byte offset of this node in the source
653
- #
654
- # @return [Integer] byte offset from beginning of source
655
- def end_byte
656
- @impl.end_byte
657
- end
658
-
659
- # Check if this node or any descendant has a parse error
660
- #
661
- # @return [Boolean] true if there is an error in the subtree
662
- def has_error?
663
- @impl.respond_to?(:has_error?) && @impl.has_error?
664
- end
665
-
666
- # Check if this node is a MISSING node (inserted by error recovery)
667
- #
668
- # @return [Boolean] true if this is a missing node
669
- def missing?
670
- @impl.respond_to?(:missing?) && @impl.missing?
671
- end
672
-
673
- # Get string representation of this node
674
- #
675
- # @return [String] string representation
676
- def to_s
677
- @impl.to_s
678
- end
679
-
680
- # Check if node responds to a method (includes delegation to @impl)
681
- #
682
- # @param method_name [Symbol] method to check
683
- # @param include_private [Boolean] include private methods
684
- # @return [Boolean]
685
- def respond_to_missing?(method_name, include_private = false)
686
- @impl.respond_to?(method_name, include_private) || super
687
- end
688
-
689
- # Delegate unknown methods to the underlying implementation
690
- #
691
- # This provides full compatibility with ruby_tree_sitter nodes
692
- # for methods not explicitly wrapped.
693
- #
694
- # @param method_name [Symbol] method to call
695
- # @param args [Array] arguments to pass
696
- # @param block [Proc] block to pass
697
- # @return [Object] result from the underlying implementation
698
- def method_missing(method_name, *args, **kwargs, &block)
699
- if @impl.respond_to?(method_name)
700
- @impl.public_send(method_name, *args, **kwargs, &block)
701
- else
702
- super
703
- end
704
- end
705
- end
706
- end
519
+ # These provide a unified interface across all backends (MRI, Rust, FFI, Java, Citrus).
520
+ # All backends now return properly wrapped TreeHaver::Tree and TreeHaver::Node objects.
521
+ end # end module TreeHaver
707
522
 
708
523
  TreeHaver::Version.class_eval do
709
524
  extend VersionGem::Basic
@@ -47,7 +47,7 @@ module TreeHaver
47
47
  # Get list of candidate library names
48
48
  def self.lib_candidates: () -> Array[String]
49
49
 
50
- # Load the Tree-sitter runtime library
50
+ # Load the tree-sitter runtime library
51
51
  def self.try_load!: () -> void
52
52
 
53
53
  # Check if the library is loaded
@@ -281,5 +281,72 @@ module TreeHaver
281
281
  class Node
282
282
  end
283
283
  end
284
+
285
+ # Citrus backend using pure Ruby Citrus parser
286
+ module Citrus
287
+ # Check if the Citrus backend is available
288
+ def self.available?: () -> bool
289
+
290
+ # Reset the load state (for testing)
291
+ def self.reset!: () -> void
292
+
293
+ # Get capabilities supported by this backend
294
+ def self.capabilities: () -> Hash[Symbol, untyped]
295
+
296
+ class Language
297
+ attr_reader grammar_module: untyped
298
+
299
+ # Create a new language from a Citrus grammar module
300
+ def initialize: (untyped grammar_module) -> void
301
+ end
302
+
303
+ class Parser
304
+ # Create a new parser instance
305
+ def initialize: () -> void
306
+
307
+ # Set the grammar for this parser
308
+ def language=: (Language | untyped grammar) -> (Language | untyped)
309
+
310
+ # Parse source code
311
+ def parse: (String source) -> untyped
312
+
313
+ private
314
+
315
+ @grammar: untyped
316
+ end
317
+
318
+ class Tree
319
+ attr_reader root_match: untyped
320
+ attr_reader source: String
321
+
322
+ def initialize: (untyped root_match, String source) -> void
323
+
324
+ def root_node: () -> Node
325
+ end
326
+
327
+ class Node
328
+ attr_reader match: untyped
329
+ attr_reader source: String
330
+
331
+ def initialize: (untyped match, String source) -> void
332
+
333
+ def type: () -> String
334
+
335
+ def start_byte: () -> Integer
336
+
337
+ def end_byte: () -> Integer
338
+
339
+ def text: () -> String
340
+
341
+ def child_count: () -> Integer
342
+
343
+ def child: (Integer index) -> Node?
344
+
345
+ def children: () -> Array[Node]
346
+
347
+ def each: () { (Node) -> void } -> void
348
+ | () -> Enumerator[Node, nil]
349
+ end
350
+ end
284
351
  end
285
352
  end
@@ -26,6 +26,7 @@ module TreeHaver
26
26
  def self.sanitize_language_name: (String | Symbol | nil name) -> Symbol?
27
27
  def self.validation_errors: (String? path) -> Array[String]
28
28
  def self.windows_absolute_path?: (String path) -> bool
29
+ def self.has_valid_extension?: (String path) -> bool
29
30
  end
30
31
  end
31
32
 
data/sig/tree_haver.rbs CHANGED
@@ -1,6 +1,6 @@
1
1
  # Type definitions for TreeHaver
2
2
  #
3
- # TreeHaver is a cross-Ruby adapter for the Tree-sitter parsing library
3
+ # TreeHaver is a cross-Ruby adapter for the tree-sitter parsing library
4
4
 
5
5
  module TreeHaver
6
6
  VERSION: String
@@ -44,7 +44,7 @@ module TreeHaver
44
44
  # Fetch a registered language entry
45
45
  def self.registered_language: (Symbol | String name) -> Hash[Symbol, String?]?
46
46
 
47
- # Represents a Tree-sitter language grammar
47
+ # Represents a tree-sitter language grammar
48
48
  class Language
49
49
  # Load a language grammar from a shared library
50
50
  def self.from_library: (String path, ?symbol: String?, ?name: String?) -> Language
@@ -57,7 +57,7 @@ module TreeHaver
57
57
  def self.respond_to_missing?: (Symbol method_name, ?bool include_private) -> bool
58
58
  end
59
59
 
60
- # Represents a Tree-sitter parser instance
60
+ # Represents a tree-sitter parser instance
61
61
  class Parser
62
62
  # Create a new parser instance
63
63
  def initialize: () -> void
@@ -68,6 +68,9 @@ module TreeHaver
68
68
  # Parse source code into a syntax tree
69
69
  def parse: (String source) -> Tree
70
70
 
71
+ # Parse with optional incremental parsing support
72
+ def parse_string: (Tree? old_tree, String source) -> Tree
73
+
71
74
  private
72
75
 
73
76
  @impl: untyped
@@ -75,30 +78,113 @@ module TreeHaver
75
78
 
76
79
  # Represents a parsed syntax tree
77
80
  class Tree
81
+ attr_reader inner_tree: untyped
82
+ attr_reader source: String?
83
+
78
84
  # Get the root node of the syntax tree
79
- def root_node: () -> Node
85
+ def root_node: () -> Node?
80
86
 
81
- private
87
+ # Edit the tree for incremental parsing
88
+ def edit: (
89
+ start_byte: Integer,
90
+ old_end_byte: Integer,
91
+ new_end_byte: Integer,
92
+ start_point: Hash[Symbol, Integer],
93
+ old_end_point: Hash[Symbol, Integer],
94
+ new_end_point: Hash[Symbol, Integer]
95
+ ) -> void
82
96
 
83
- def initialize: (untyped impl) -> void
97
+ # Check if the current backend supports incremental parsing
98
+ def supports_editing?: () -> bool
84
99
 
85
- @impl: untyped
100
+ # String representation
101
+ def inspect: () -> String
102
+
103
+ # Check if tree responds to a method (includes delegation to inner_tree)
104
+ def respond_to_missing?: (Symbol method_name, ?bool include_private) -> bool
105
+
106
+ # Delegate unknown methods to the underlying backend-specific tree
107
+ def method_missing: (Symbol method_name, *untyped args, **untyped kwargs) ?{ () -> untyped } -> untyped
108
+
109
+ private
110
+
111
+ def initialize: (untyped inner_tree, ?source: String?) -> void
86
112
  end
87
113
 
88
114
  # Represents a node in the syntax tree
89
115
  class Node
116
+ attr_reader inner_node: untyped
117
+ attr_reader source: String?
118
+
90
119
  # Get the type name of this node
91
120
  def type: () -> String
92
121
 
122
+ # Get the text content of this node
123
+ def text: () -> String?
124
+
125
+ # Get the start byte offset
126
+ def start_byte: () -> Integer
127
+
128
+ # Get the end byte offset
129
+ def end_byte: () -> Integer
130
+
131
+ # Get the start point (row, column)
132
+ def start_point: () -> Point
133
+
134
+ # Get the end point (row, column)
135
+ def end_point: () -> Point
136
+
137
+ # Get the number of child nodes
138
+ def child_count: () -> Integer
139
+
140
+ # Get a child node by index
141
+ def child: (Integer index) -> Node?
142
+
143
+ # Get all children as an array
144
+ def children: () -> Array[Node]
145
+
93
146
  # Iterate over child nodes
94
147
  def each: () { (Node child) -> void } -> nil
95
148
  | () -> Enumerator[Node, nil]
96
149
 
150
+ # Get a named child by field name
151
+ def child_by_field_name: (String | Symbol field_name) -> Node?
152
+
153
+ # Get the parent node
154
+ def parent: () -> Node?
155
+
156
+ # Get the next sibling node
157
+ def next_sibling: () -> Node?
158
+
159
+ # Get the previous sibling node
160
+ def prev_sibling: () -> Node?
161
+
162
+ # Check if node responds to a method (includes delegation to inner_node)
163
+ def respond_to_missing?: (Symbol method_name, ?bool include_private) -> bool
164
+
165
+ # Delegate unknown methods to the underlying backend-specific node
166
+ def method_missing: (Symbol method_name, *untyped args, **untyped kwargs) ?{ () -> untyped } -> untyped
167
+
97
168
  private
98
169
 
99
- def initialize: (untyped impl) -> void
170
+ def initialize: (untyped inner_node, ?source: String?) -> void
171
+ end
172
+
173
+ # Position in source code that works as both object and hash
174
+ class Point
175
+ attr_reader row: Integer
176
+ attr_reader column: Integer
100
177
 
101
- @impl: untyped
178
+ def initialize: (row: Integer, column: Integer) -> void
179
+
180
+ # Hash-style access
181
+ def []: (Symbol key) -> Integer?
182
+
183
+ # Convert to hash
184
+ def to_h: () -> Hash[Symbol, Integer]
185
+
186
+ # String representation
187
+ def inspect: () -> String
102
188
  end
103
189
 
104
190
  # Thread-safe language registrations and cache
data.tar.gz.sig CHANGED
Binary file