Skip to main content

tree_sitter_perl_rs/
lib.rs

1//! Rust-native Perl parser with tree-sitter-style ergonomics and tree-sitter-compatible
2//! output. This is a facade over the v3 native parser (`perl-parser-core`); it is NOT
3//! bindings to the C tree-sitter grammar. For the conventional tree-sitter binding, see
4//! `tree-sitter-perl-c`.
5//!
6//! # Quick start
7//!
8//! ```rust
9//! use tree_sitter_perl_rs::Parser;
10//!
11//! let mut parser = Parser::new();
12//! if let Some(tree) = parser.parse("my $x = 42;") {
13//!     let root = tree.root_node();
14//!     println!("{}", root.to_sexp());
15//! }
16//! ```
17//!
18//! # Design
19//!
20//! This crate wraps the v3 recursive-descent Perl parser (`perl-parser-core`) with an API
21//! surface that matches the conventions of the `tree-sitter` crate. Users familiar with
22//! tree-sitter can work with Perl ASTs immediately, while the underlying engine is the
23//! full-featured native v3 stack (not the C tree-sitter grammar).
24//!
25//! Key properties:
26//! - `Parser::parse()` returns `Option<Tree>` — `None` only on complete parse failure.
27//!   The v3 parser is highly error-tolerant and almost always produces a partial tree.
28//! - `Node::to_sexp()` delegates to `perl_ast::Node::to_sexp()` for tree-sitter-compatible
29//!   S-expression output.
30//! - `Node::kind()` returns the tree-sitter grammar-canonical kind string.
31//! - `Node::start_byte()` / `Node::end_byte()` expose the `SourceLocation` byte offsets.
32//! - `Node::children()` and `Node::child()` mirror tree-sitter traversal conventions.
33//!
34//! # Relationship to `tree-sitter-perl-c`
35//!
36//! | Crate | Backing engine | Use when |
37//! |-------|---------------|----------|
38//! | `tree-sitter-perl-rs` | v3 native Rust parser (this crate) | You want the full-featured Rust toolchain |
39//! | `tree-sitter-perl-c` | C tree-sitter grammar | You need compatibility with the tree-sitter C ecosystem |
40
41#![deny(unsafe_code)]
42#![deny(unreachable_pub)]
43#![deny(clippy::print_stderr)]
44#![deny(clippy::print_stdout)]
45#![warn(rust_2018_idioms)]
46#![warn(missing_docs)]
47#![allow(
48    clippy::module_name_repetitions,
49    clippy::must_use_candidate,
50    clippy::missing_errors_doc,
51    clippy::missing_panics_doc
52)]
53
54use perl_ast::{Node as AstNode, NodeKind};
55use perl_module::parse_module_import_head;
56use perl_parser_core::Parser as CoreParser;
57use perl_pragma::{PragmaState, PragmaTracker};
58use perl_semantic_analyzer::semantic::SemanticModel;
59
60/// Re-export of Edit type for tree-sitter-compatible incremental parsing.
61///
62/// Mirrors `tree_sitter::InputEdit` field layout for drop-in compatibility.
63pub use perl_parser_core::edit::Edit as InputEdit;
64
65/// A tree-sitter-compatible source position.
66///
67/// `row` and `column` are both zero-based and `column` is measured in bytes.
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69#[non_exhaustive]
70pub struct Point {
71    /// Zero-based row number.
72    pub row: usize,
73    /// Zero-based byte column within `row`.
74    pub column: usize,
75}
76
77/// A Perl parser with tree-sitter-style ergonomics.
78///
79/// Wraps the v3 recursive-descent Perl parser. Create one parser instance and call
80/// [`parse`][Parser::parse] for each source file you need to process.
81///
82/// # Example
83///
84/// ```rust
85/// use tree_sitter_perl_rs::Parser;
86///
87/// let mut parser = Parser::new();
88/// let tree = parser.parse("sub greet { print \"hello\"; }");
89/// assert!(tree.is_some());
90/// ```
91pub struct Parser {
92    // Stateless currently; the v3 CoreParser takes source at construction time.
93    // Stored as a unit struct for forward compatibility (e.g. future options).
94    _priv: (),
95}
96
97impl Parser {
98    /// Create a new parser instance.
99    pub fn new() -> Self {
100        Parser { _priv: () }
101    }
102
103    /// Parse a Perl source string and return a [`Tree`], or `None` on complete failure.
104    ///
105    /// The v3 parser is highly error-tolerant — even malformed input usually produces a
106    /// partial tree. `None` is reserved for extreme edge cases where no AST can be built
107    /// at all.
108    ///
109    /// # Example
110    ///
111    /// ```rust
112    /// use tree_sitter_perl_rs::Parser;
113    ///
114    /// let mut parser = Parser::new();
115    /// let tree = parser.parse("my $x = 42;");
116    /// assert!(tree.is_some());
117    /// ```
118    pub fn parse(&mut self, source: &str) -> Option<Tree> {
119        let mut core = CoreParser::new(source);
120        match core.parse() {
121            Ok(root) => Some(Tree { root, source: source.to_string(), pending_edits: Vec::new() }),
122            Err(_) => None,
123        }
124    }
125
126    /// Parse `source` using `old_tree` as a hint for incremental re-parsing.
127    ///
128    /// In the current implementation this performs a full re-parse (equivalent
129    /// to [`parse`][Parser::parse]). The `old_tree` parameter is accepted for
130    /// API compatibility with `tree_sitter::Parser::parse_with_old_tree`; future
131    /// versions will use it to skip unchanged regions.
132    ///
133    /// Returns `None` on complete parse failure (same semantics as `parse`).
134    pub fn parse_with_old_tree(&mut self, source: &str, old_tree: &Tree) -> Option<Tree> {
135        // Fast path: if source is unchanged and no edits were recorded, reuse the old tree
136        // instead of re-parsing. This mirrors tree-sitter's incremental no-op behavior.
137        if source == old_tree.source() && old_tree.pending_edits.is_empty() {
138            return Some(old_tree.clone());
139        }
140
141        self.parse(source)
142    }
143}
144
145impl Default for Parser {
146    fn default() -> Self {
147        Self::new()
148    }
149}
150
151/// A descriptor for the Perl language as parsed by the native v3 engine.
152///
153/// Provides node kind names and field metadata for Rust-native tooling.
154/// This is NOT a `tree_sitter::Language` — it does not require a C toolchain
155/// and cannot be used with `tree_sitter::Parser::set_language`. For drop-in
156/// tree-sitter compatibility use `tree-sitter-perl-c` instead.
157///
158/// # Example
159///
160/// ```rust
161/// use tree_sitter_perl_rs::language;
162///
163/// let lang = language();
164/// assert!(lang.node_kind_count() > 0);
165/// ```
166#[derive(Debug, Clone, Copy, PartialEq, Eq)]
167pub struct PerlLanguage {
168    kind_names: &'static [&'static str],
169}
170
171impl PerlLanguage {
172    /// Returns the number of distinct node kinds in the grammar.
173    pub fn node_kind_count(&self) -> usize {
174        self.kind_names.len()
175    }
176
177    /// Returns all node kind names, in alphabetical order.
178    pub fn node_kind_names(&self) -> &[&'static str] {
179        self.kind_names
180    }
181
182    /// Returns `true` if the given kind name is a named (non-anonymous) node kind.
183    pub fn node_kind_is_named(&self, kind: &str) -> bool {
184        self.kind_names.contains(&kind)
185    }
186}
187
188impl Default for PerlLanguage {
189    fn default() -> Self {
190        LANGUAGE
191    }
192}
193
194/// Returns the [`PerlLanguage`] descriptor for Rust-native tooling.
195///
196/// Note: This is NOT equivalent to `tree_sitter::Language`. See [`PerlLanguage`].
197pub fn language() -> PerlLanguage {
198    LANGUAGE
199}
200
201/// The [`PerlLanguage`] descriptor as a constant.
202pub static LANGUAGE: PerlLanguage = PerlLanguage { kind_names: perl_ast::NodeKind::ALL_KIND_NAMES };
203
204/// The result of a successful parse: an owned syntax tree and the source text.
205///
206/// Use [`root_node`][Tree::root_node] to begin traversal.
207#[derive(Debug, Clone, PartialEq)]
208pub struct Tree {
209    root: AstNode,
210    source: String,
211    /// Pending edits recorded via [`Tree::edit`].
212    pending_edits: Vec<InputEdit>,
213}
214
215/// Experimental semantic overlay query handle.
216///
217/// This API is intentionally limited while the facade integration is in development.
218/// Query capabilities will expand over time.
219#[derive(Debug, Clone, Copy)]
220#[non_exhaustive]
221pub struct SemanticOverlay<'tree> {
222    tree: &'tree Tree,
223}
224
225/// Symbol definition returned by [`SemanticOverlay`] queries.
226#[derive(Debug, Clone, PartialEq, Eq)]
227#[non_exhaustive]
228pub struct OverlayDefinition {
229    /// Symbol name as written in source.
230    pub name: String,
231    /// Package-qualified symbol name.
232    pub qualified_name: String,
233    /// Symbol kind label (debug string form).
234    pub kind: String,
235    /// Definition span start byte (inclusive).
236    pub start_byte: usize,
237    /// Definition span end byte (exclusive).
238    pub end_byte: usize,
239}
240
241/// Import statement visible at a specific source offset.
242#[derive(Debug, Clone, PartialEq, Eq)]
243#[non_exhaustive]
244pub struct VisibleImport {
245    /// Imported module token (`Foo::Bar`, `strict`, etc.).
246    pub module: String,
247    /// Statement start byte (inclusive).
248    pub statement_start_byte: usize,
249    /// Statement end byte (exclusive).
250    pub statement_end_byte: usize,
251}
252
253impl Tree {
254    /// Returns the root node of the syntax tree.
255    pub fn root_node(&self) -> Node<'_> {
256        Node { inner: &self.root, tree_source: &self.source }
257    }
258
259    /// Returns the source text this tree was built from.
260    pub fn source(&self) -> &str {
261        &self.source
262    }
263
264    /// Records a source edit on this tree, invalidating affected byte ranges.
265    ///
266    /// After calling `edit()`, pass this tree and the new source to
267    /// [`Parser::parse_with_old_tree`] to re-parse efficiently.
268    ///
269    /// In the current implementation this stores the edit for API compatibility;
270    /// true incremental re-parsing (skipping unchanged regions) is a planned
271    /// optimization.
272    pub fn edit(&mut self, edit: &InputEdit) {
273        self.pending_edits.push(edit.clone());
274    }
275
276    /// Returns a cursor positioned at the root node for stateful tree traversal.
277    ///
278    /// This mirrors `tree_sitter::Tree::walk()` and is equivalent to
279    /// `tree.root_node().walk()`.
280    pub fn walk(&self) -> TreeCursor<'_> {
281        self.root_node().walk()
282    }
283
284    /// Returns the experimental semantic overlay query handle for this tree.
285    pub fn semantic_overlay(&self) -> SemanticOverlay<'_> {
286        SemanticOverlay { tree: self }
287    }
288}
289
290impl<'tree> SemanticOverlay<'tree> {
291    /// Resolve a symbol definition at a byte offset in the source.
292    pub fn definition_at_offset(&self, offset: usize) -> Option<OverlayDefinition> {
293        let model = SemanticModel::build(&self.tree.root, self.tree.source());
294        model.definition_at(offset).map(|symbol| OverlayDefinition {
295            name: symbol.name.clone(),
296            qualified_name: symbol.qualified_name.clone(),
297            kind: format!("{:?}", symbol.kind),
298            start_byte: symbol.location.start,
299            end_byte: symbol.location.end,
300        })
301    }
302
303    /// Resolve a symbol definition for the given node span.
304    ///
305    /// Uses the node start byte as the query point.
306    pub fn definition_for_node(&self, node: &Node<'_>) -> Option<OverlayDefinition> {
307        self.definition_at_offset(node.start_byte())
308    }
309
310    /// Returns the list of `use`-import modules visible at `offset`.
311    ///
312    /// Visibility is currently lexical-by-position: this returns `use` statements
313    /// with starts less than or equal to `offset`.
314    pub fn visible_imports_at_offset(&self, offset: usize) -> Vec<VisibleImport> {
315        let mut imports = Vec::new();
316        collect_visible_use_imports(&self.tree.root, self.tree.source(), offset, &mut imports);
317        let mut deduped = Vec::new();
318        for import in imports {
319            if !deduped.iter().any(|existing: &VisibleImport| existing.module == import.module) {
320                deduped.push(import);
321            }
322        }
323        deduped
324    }
325
326    /// Returns the effective pragma state at a byte offset.
327    pub fn pragma_state_at_offset(&self, offset: usize) -> PragmaState {
328        let pragma_map = PragmaTracker::build(&self.tree.root);
329        PragmaTracker::state_for_offset(&pragma_map, offset)
330    }
331}
332
333/// A borrowed reference to a node in the syntax tree.
334///
335/// Mirrors the tree-sitter `Node` API surface. Lifetime `'tree` is tied to the
336/// owning [`Tree`].
337pub struct Node<'tree> {
338    inner: &'tree AstNode,
339    tree_source: &'tree str,
340}
341
342impl<'tree> Node<'tree> {
343    /// Returns the tree-sitter grammar-canonical node kind name.
344    ///
345    /// This matches tree-sitter expectations for `Node::kind()`, for example the
346    /// root node kind is `"source_file"`. Use [`native_kind`][Node::native_kind]
347    /// for the v3 parser's internal PascalCase kind name.
348    pub fn kind(&self) -> String {
349        self.grammar_kind()
350    }
351
352    /// Returns the v3 parser's internal node kind name (e.g. `"Program"`).
353    pub fn native_kind(&self) -> &'static str {
354        self.inner.kind.kind_name()
355    }
356
357    /// Returns the tree-sitter grammar-canonical node kind name.
358    ///
359    /// Alias of [`kind`][Node::kind] kept for compatibility.
360    ///
361    /// This method returns the grammar name
362    /// used in S-expressions (e.g., `"source_file"`, `"sub"`).
363    /// This matches the kind strings returned by `tree-sitter-perl-c` and the
364    /// upstream tree-sitter Perl grammar.
365    /// Error nodes use `"ERROR"` (uppercase), matching tree-sitter convention.
366    ///
367    /// For most nodes the grammar name is a simple lowercase mapping. For some
368    /// nodes (e.g., operator-named `Binary`, dynamic `VariableDeclaration`) the
369    /// name depends on runtime data; this method extracts it from `to_sexp()`.
370    ///
371    /// # Example
372    ///
373    /// ```rust
374    /// use tree_sitter_perl_rs::Parser;
375    ///
376    /// let mut parser = Parser::new();
377    /// let tree = parser.parse("my $x = 42;");
378    /// assert!(tree.is_some());
379    /// assert_eq!(tree.unwrap().root_node().grammar_kind(), "source_file");
380    /// ```
381    pub fn grammar_kind(&self) -> String {
382        // Extract the node type from the leading `(word` in the S-expression.
383        // to_sexp() always starts with `(kind_name` or just `(kind_name)`.
384        //
385        // Edge case: NodeKind::VariableWithAttributes produces a double-paren sexp
386        // of the form `((variable $ foo) (attributes :lvalue))` because it delegates
387        // the outer wrapper to the child variable's to_sexp(). In that case the sexp
388        // does not begin with `(kind_name` -- it begins with `((child_kind`. We detect
389        // this and fall back to the v3 kind_name() converted to snake_case.
390        let sexp = self.to_sexp();
391        if sexp.starts_with("((") {
392            // Double-paren form: no independent grammar kind token in the sexp.
393            // Derive a stable snake_case name from the v3 kind_name() as fallback.
394            return pascal_to_snake(self.inner.kind.kind_name());
395        }
396        let inner = sexp.trim_start_matches('(');
397        // Take up to the first space or closing paren.
398        let end = inner.find([' ', ')']).unwrap_or(inner.len());
399        inner[..end].to_string()
400    }
401
402    /// Returns a tree-sitter-compatible S-expression for this node and its subtree.
403    ///
404    /// Delegates to `perl_ast::Node::to_sexp()`. Example output:
405    /// `(source_file (my_declaration (variable $ x) (number 42)))`.
406    pub fn to_sexp(&self) -> String {
407        self.inner.to_sexp()
408    }
409
410    /// Returns the number of direct children.
411    pub fn child_count(&self) -> usize {
412        ast_child_count(self.inner)
413    }
414
415    /// Returns the `i`-th direct child, or `None` if out of range.
416    pub fn child(&self, i: usize) -> Option<Node<'tree>> {
417        ast_child_at(self.inner, i)
418            .map(|child| Node { inner: child, tree_source: self.tree_source })
419    }
420
421    /// Returns an iterator over direct children.
422    ///
423    /// The iterator yields [`Node`] values sharing the same `'tree` lifetime as `self`.
424    pub fn children(&self) -> impl Iterator<Item = Node<'tree>> + '_ {
425        // Collect into a Vec so we can own the references. The lifetimes are valid
426        // because all child nodes are part of the same owned tree (Tree::root).
427        let kids = ast_children(self.inner);
428        kids.into_iter().map(move |child| Node { inner: child, tree_source: self.tree_source })
429    }
430
431    /// Returns the start byte offset in the source text (inclusive).
432    pub fn start_byte(&self) -> usize {
433        self.inner.location.start
434    }
435
436    /// Returns the end byte offset in the source text (exclusive).
437    pub fn end_byte(&self) -> usize {
438        self.inner.location.end.min(self.tree_source.len())
439    }
440
441    /// Returns the start position as a tree-sitter-compatible [`Point`].
442    ///
443    /// `row`/`column` are zero-based and `column` is measured in bytes.
444    pub fn start_position(&self) -> Point {
445        byte_to_point(self.tree_source, self.start_byte())
446    }
447
448    /// Returns the end position as a tree-sitter-compatible [`Point`].
449    ///
450    /// `row`/`column` are zero-based and `column` is measured in bytes.
451    pub fn end_position(&self) -> Point {
452        byte_to_point(self.tree_source, self.end_byte())
453    }
454
455    /// Extracts the source text slice covered by this node.
456    ///
457    /// Returns `Err` only when the byte range contains invalid UTF-8, which is unlikely
458    /// for content produced from a valid Rust `&str`.
459    ///
460    /// If the node's byte offsets extend beyond `source`, the result is clamped to
461    /// the available range rather than panicking. This can happen when `source` is a
462    /// different buffer than the one used to build the tree.
463    pub fn utf8_text<'a>(&self, source: &'a [u8]) -> Result<&'a str, std::str::Utf8Error> {
464        let start = self.inner.location.start.min(source.len());
465        let end = self.inner.location.end.min(source.len());
466        std::str::from_utf8(&source[start..end])
467    }
468
469    /// Returns `true` if this node has no children (is a leaf node).
470    pub fn is_leaf(&self) -> bool {
471        self.inner.first_child().is_none()
472    }
473
474    /// Returns the source text that was provided when creating the owning [`Tree`].
475    pub fn tree_source(&self) -> &'tree str {
476        self.tree_source
477    }
478
479    /// Returns the inner `perl_ast::Node` for direct access to the v3 AST.
480    ///
481    /// This escape hatch lets callers use capabilities that go beyond the tree-sitter
482    /// surface (e.g., match on [`PerlNodeKind`] variants).
483    pub fn inner(&self) -> &'tree AstNode {
484        self.inner
485    }
486
487    /// Returns a cursor positioned at this node for stateful tree traversal.
488    ///
489    /// Mirrors `tree_sitter::TreeCursor` style navigation with a lightweight,
490    /// allocation-free path stack.
491    pub fn walk(&self) -> TreeCursor<'tree> {
492        TreeCursor { root: self.inner, tree_source: self.tree_source, path: Vec::new() }
493    }
494}
495
496/// Re-export of [`perl_ast::NodeKind`] so callers can pattern-match node variants
497/// without a direct dependency on `perl-ast`.
498pub use perl_ast::NodeKind as PerlNodeKind;
499
500/// Stateful cursor for navigating a subtree.
501///
502/// The cursor is rooted at the [`Node`] that created it via [`Node::walk`].
503/// Calling [`goto_parent`][TreeCursor::goto_parent] at the root returns `false`
504/// and keeps the cursor at the root.
505pub struct TreeCursor<'tree> {
506    root: &'tree AstNode,
507    tree_source: &'tree str,
508    /// Child indices from `root` to the current node.
509    path: Vec<usize>,
510}
511
512impl<'tree> TreeCursor<'tree> {
513    /// Returns the node currently selected by the cursor.
514    pub fn node(&self) -> Node<'tree> {
515        Node { inner: self.current_ast_node(), tree_source: self.tree_source }
516    }
517
518    /// Moves to the first child of the current node.
519    ///
520    /// Returns `true` when movement succeeds, `false` when the node has no children.
521    pub fn goto_first_child(&mut self) -> bool {
522        if self.current_ast_node().first_child().is_none() {
523            return false;
524        }
525        self.path.push(0);
526        true
527    }
528
529    /// Moves to the last child of the current node.
530    ///
531    /// Returns `true` when movement succeeds, `false` when the node has no children.
532    pub fn goto_last_child(&mut self) -> bool {
533        let child_count = self.current_ast_node().children().len();
534        if child_count == 0 {
535            return false;
536        }
537        self.path.push(child_count - 1);
538        true
539    }
540
541    /// Moves to the next sibling of the current node.
542    ///
543    /// Returns `true` on success. Returns `false` if the cursor is at root or if
544    /// there is no next sibling.
545    pub fn goto_next_sibling(&mut self) -> bool {
546        if self.path.is_empty() {
547            return false;
548        }
549
550        let parent = self.current_parent_ast_node();
551        let sibling_count = ast_child_count(parent);
552        let current_index = self.path[self.path.len() - 1];
553        let next = current_index + 1;
554        if next >= sibling_count {
555            return false;
556        }
557
558        let last_pos = self.path.len() - 1;
559        self.path[last_pos] = next;
560        true
561    }
562
563    /// Moves to the previous sibling of the current node.
564    ///
565    /// Returns `true` on success. Returns `false` if the cursor is at root or if
566    /// there is no previous sibling.
567    pub fn goto_previous_sibling(&mut self) -> bool {
568        if self.path.is_empty() {
569            return false;
570        }
571
572        let current_index = self.path[self.path.len() - 1];
573        if current_index == 0 {
574            return false;
575        }
576
577        let last_pos = self.path.len() - 1;
578        self.path[last_pos] = current_index - 1;
579        true
580    }
581
582    /// Moves to the parent node.
583    ///
584    /// Returns `true` when movement succeeds, `false` when already at root.
585    pub fn goto_parent(&mut self) -> bool {
586        self.path.pop().is_some()
587    }
588
589    /// Resets the cursor back to its root node.
590    pub fn reset(&mut self) {
591        self.path.clear();
592    }
593
594    fn current_ast_node(&self) -> &'tree AstNode {
595        resolve_path(self.root, &self.path)
596    }
597
598    fn current_parent_ast_node(&self) -> &'tree AstNode {
599        debug_assert!(!self.path.is_empty(), "current_parent_ast_node requires a non-root cursor");
600        let parent_path_len = self.path.len() - 1;
601        resolve_path(self.root, &self.path[..parent_path_len])
602    }
603}
604
605// ---------------------------------------------------------------------------
606// Private helpers
607// ---------------------------------------------------------------------------
608
609// Collect the direct children of an `AstNode` as a `Vec<&AstNode>`.
610//
611// This thin wrapper exists because the public `Node::children()` method in `perl_ast`
612// has the same name as our facade method and would be ambiguous in `impl` blocks.
613#[inline]
614fn ast_children(node: &AstNode) -> Vec<&AstNode> {
615    node.children()
616}
617
618#[inline]
619fn ast_child_count(node: &AstNode) -> usize {
620    let mut count = 0usize;
621    node.for_each_child(|_| count += 1);
622    count
623}
624
625#[inline]
626fn ast_child_at(node: &AstNode, index: usize) -> Option<&AstNode> {
627    let mut idx = 0usize;
628    let mut found = None;
629    node.for_each_child(|child| {
630        if found.is_none() && idx == index {
631            found = Some(child);
632        }
633        idx += 1;
634    });
635    found
636}
637
638fn collect_visible_use_imports(
639    node: &AstNode,
640    source: &str,
641    offset: usize,
642    out: &mut Vec<VisibleImport>,
643) {
644    // Only attempt import extraction on Use AST nodes. Container nodes (Program,
645    // Block, Subroutine, etc.) span large source ranges that may accidentally start
646    // with a `use` token, producing imports with incorrect statement byte ranges.
647    // Use nodes have no children (for_each_child is a no-op), so they are visited
648    // exactly once per tree traversal — no inner dedup needed.
649    if matches!(node.kind, NodeKind::Use { .. }) && node.location.start <= offset {
650        let start = node.location.start.min(source.len());
651        let end = node.location.end.min(source.len());
652        let statement_text = &source[start..end];
653        if let Some(import_head) = parse_module_import_head(statement_text) {
654            out.push(VisibleImport {
655                module: import_head.token.to_string(),
656                statement_start_byte: start,
657                statement_end_byte: end,
658            });
659        }
660    }
661
662    node.for_each_child(|child| collect_visible_use_imports(child, source, offset, out));
663}
664
665// Invariant: TreeCursor path is constructed by traversal methods in this type.
666// If a stale/invalid path somehow appears, return the last valid node instead
667// of panicking, preserving total API safety guarantees.
668fn resolve_path<'tree>(root: &'tree AstNode, path: &[usize]) -> &'tree AstNode {
669    let mut current = root;
670    for &index in path {
671        match ast_child_at(current, index) {
672            Some(child) => current = child,
673            None => {
674                debug_assert!(false, "TreeCursor path must reference a valid child");
675                break;
676            }
677        }
678    }
679    current
680}
681
682/// Convert a PascalCase kind name (e.g. `"VariableWithAttributes"`) to snake_case
683/// (e.g. `"variable_with_attributes"`). Used as a fallback in [`Node::grammar_kind`]
684/// when the S-expression does not have a simple `(kind_name ...)` prefix.
685fn pascal_to_snake(s: &str) -> String {
686    let mut out = String::with_capacity(s.len() + 4);
687    for (i, c) in s.char_indices() {
688        if c.is_uppercase() && i > 0 {
689            out.push('_');
690        }
691        out.extend(c.to_lowercase());
692    }
693    out
694}
695
696fn byte_to_point(source: &str, byte: usize) -> Point {
697    let clamped = byte.min(source.len());
698    let mut row = 0usize;
699    let mut column = 0usize;
700
701    for b in source.as_bytes().iter().take(clamped) {
702        if *b == b'\n' {
703            row += 1;
704            column = 0;
705        } else {
706            column += 1;
707        }
708    }
709
710    Point { row, column }
711}
712
713// ---------------------------------------------------------------------------
714// Tests
715// ---------------------------------------------------------------------------
716
717#[cfg(test)]
718mod tests {
719    use super::*;
720    use perl_tdd_support::must_some;
721
722    #[test]
723    fn test_parser_creates_tree() {
724        let mut p = Parser::new();
725        let tree = p.parse("my $x = 42;");
726        assert!(tree.is_some());
727    }
728
729    #[test]
730    fn test_root_node_kind() {
731        let mut p = Parser::new();
732        let tree = must_some(p.parse("my $x = 42;"));
733        assert_eq!(tree.root_node().kind(), "source_file");
734        assert_eq!(tree.root_node().native_kind(), "Program");
735    }
736
737    #[test]
738    fn test_to_sexp_starts_with_source_file() {
739        let mut p = Parser::new();
740        let tree = must_some(p.parse("my $x = 42;"));
741        let sexp = tree.root_node().to_sexp();
742        assert!(
743            sexp.starts_with("(source_file"),
744            "sexp should start with (source_file, got: {}",
745            sexp
746        );
747    }
748
749    #[test]
750    fn test_child_count_for_program_with_statements() {
751        let mut p = Parser::new();
752        let tree = must_some(p.parse("my $x = 42;\nmy $y = 99;"));
753        let root = tree.root_node();
754        assert!(root.child_count() >= 1, "root should have children");
755    }
756
757    #[test]
758    fn test_start_and_end_byte() {
759        let source = "my $x = 42;";
760        let mut p = Parser::new();
761        let tree = must_some(p.parse(source));
762        let root = tree.root_node();
763        assert_eq!(root.start_byte(), 0);
764        assert_eq!(root.end_byte(), source.len(), "root end_byte should clamp to source length");
765    }
766
767    #[test]
768    fn test_start_and_end_position_are_tree_sitter_compatible() {
769        let source = "my $x = 1;\nmy $y = 2;";
770        let mut p = Parser::new();
771        let tree = must_some(p.parse(source));
772        let root = tree.root_node();
773
774        assert_eq!(root.start_position(), Point { row: 0, column: 0 });
775        assert_eq!(root.end_position(), Point { row: 1, column: 10 });
776    }
777
778    #[test]
779    fn test_end_position_column_uses_bytes_not_chars() {
780        let source = "my $emoji = \"😀\";";
781        let mut p = Parser::new();
782        let tree = must_some(p.parse(source));
783        let root = tree.root_node();
784
785        assert_eq!(root.end_byte(), source.len());
786        assert_eq!(root.end_position(), Point { row: 0, column: source.len() });
787    }
788
789    /// Verify the end_byte clamp invariant: for every node in the tree,
790    /// `end_byte()` must not exceed `tree.source().len()`.  This exercises the
791    /// `.min(self.tree_source.len())` guard on the full node set, not just the
792    /// root, so that any future parser regression producing an out-of-bounds
793    /// location is caught here.
794    #[test]
795    fn test_end_byte_never_exceeds_source_len_for_all_nodes() {
796        let sources = [
797            "my $x = 42;",
798            "sub foo { return 1; }",
799            "use strict;\nuse warnings;\nmy @arr = (1, 2, 3);",
800            // empty source — edge case for zero-length trees
801            "",
802        ];
803        for source in sources {
804            let mut p = Parser::new();
805            let tree = match p.parse(source) {
806                Some(t) => t,
807                // v3 parser returns None only on extreme failure; skip rather than panic
808                None => continue,
809            };
810            let source_len = tree.source().len();
811            // Walk all direct children of root and check the invariant
812            let root = tree.root_node();
813            assert!(
814                root.end_byte() <= source_len,
815                "root end_byte {} > source_len {} for source {:?}",
816                root.end_byte(),
817                source_len,
818                source
819            );
820            for child in root.children() {
821                assert!(
822                    child.end_byte() <= source_len,
823                    "child end_byte {} > source_len {} for source {:?}",
824                    child.end_byte(),
825                    source_len,
826                    source
827                );
828            }
829        }
830    }
831
832    #[test]
833    fn test_utf8_text_round_trip() {
834        let source = "my $x = 42;";
835        let mut p = Parser::new();
836        let tree = must_some(p.parse(source));
837        let root = tree.root_node();
838        let text = root.utf8_text(source.as_bytes());
839        assert!(text.is_ok(), "utf8_text should succeed");
840        // The root node spans the whole source — verify the actual content, not just Ok.
841        let extracted = must_some(text.ok());
842        assert_eq!(extracted, source, "utf8_text should return the full source for the root node");
843    }
844
845    #[test]
846    fn test_utf8_text_multibyte_unicode() {
847        // 'é' is 2 bytes in UTF-8; the parser must not split a codepoint boundary.
848        let source = "my $x = 'café';";
849        let mut p = Parser::new();
850        let tree = must_some(p.parse(source));
851        let root = tree.root_node();
852        let bytes = source.as_bytes();
853        let text = root.utf8_text(bytes);
854        assert!(text.is_ok(), "utf8_text should handle multi-byte UTF-8");
855    }
856
857    #[test]
858    fn test_utf8_text_mismatched_source_does_not_panic() {
859        // utf8_text takes a caller-supplied byte slice. When the slice is shorter
860        // than the tree's byte offsets, the implementation must clamp rather than panic.
861        let source = "my $x = 42;";
862        let mut p = Parser::new();
863        let tree = must_some(p.parse(source));
864        let root = tree.root_node();
865        // A shorter slice — would panic without the start.min(source.len()) guard.
866        let short = b"my";
867        let result = root.utf8_text(short);
868        assert!(result.is_ok(), "utf8_text should not panic with short source slice");
869    }
870
871    #[test]
872    fn test_invalid_perl_returns_some_tree() {
873        // The v3 parser is error-tolerant — even syntactically invalid Perl should
874        // produce a partial tree (Some), not None. None is only returned on cancellation.
875        let mut p = Parser::new();
876        let tree = p.parse("sub { @@@@invalid{{{{");
877        assert!(tree.is_some(), "invalid Perl should still yield an error-recovery tree");
878    }
879
880    #[test]
881    fn test_children_iterator_matches_child_count() {
882        let mut p = Parser::new();
883        let tree = must_some(p.parse("my $x = 1; my $y = 2;"));
884        let root = tree.root_node();
885        let collected: Vec<_> = root.children().collect();
886        assert_eq!(collected.len(), root.child_count());
887    }
888
889    #[test]
890    fn test_child_by_index() {
891        let mut p = Parser::new();
892        let tree = must_some(p.parse("my $x = 1; my $y = 2;"));
893        let root = tree.root_node();
894        if root.child_count() > 0 {
895            let first = root.child(0);
896            assert!(first.is_some());
897        }
898        assert!(root.child(9999).is_none());
899    }
900
901    #[test]
902    fn test_empty_source_yields_tree() {
903        // The v3 parser is error-tolerant; empty input returns Program { statements: [] }.
904        let mut p = Parser::new();
905        let tree = p.parse("");
906        assert!(tree.is_some(), "empty input should still yield a tree");
907    }
908
909    #[test]
910    fn test_source_accessor() {
911        let source = "sub foo { }";
912        let mut p = Parser::new();
913        let tree = must_some(p.parse(source));
914        assert_eq!(tree.source(), source);
915    }
916
917    #[test]
918    fn test_default_parser() {
919        let mut p = Parser::default();
920        let tree = p.parse("1;");
921        assert!(tree.is_some());
922    }
923
924    #[test]
925    fn test_is_leaf_for_leaf_nodes() {
926        let mut p = Parser::new();
927        let tree = must_some(p.parse("42"));
928        let root = tree.root_node();
929        // The root Program is not a leaf.
930        assert!(!root.is_leaf());
931    }
932
933    // Tests for grammar_kind() method
934
935    #[test]
936    fn test_pascal_to_snake_helper() {
937        assert_eq!(pascal_to_snake("VariableWithAttributes"), "variable_with_attributes");
938        assert_eq!(pascal_to_snake("Program"), "program");
939        assert_eq!(pascal_to_snake("FunctionCall"), "function_call");
940        assert_eq!(pascal_to_snake("If"), "if");
941    }
942
943    #[test]
944    fn test_grammar_kind_returns_source_file_for_root() {
945        let mut p = Parser::new();
946        let tree = must_some(p.parse("my $x = 42;"));
947        assert_eq!(tree.root_node().grammar_kind(), "source_file");
948    }
949
950    #[test]
951    fn test_grammar_kind_returns_variable_with_attributes_for_list_form() {
952        let mut p = Parser::new();
953        // VariableWithAttributes is only produced for per-variable attributes in list form:
954        // `my ($x : lvalue);`. Scalar form `my $x : lvalue;` does not produce this node.
955        let tree = must_some(p.parse("my ($x : lvalue);"));
956        let root = tree.root_node();
957        let mut found_var_with_attrs = false;
958        for child in root.children() {
959            if child.grammar_kind() == "my_declaration" {
960                for sub in child.children() {
961                    if sub.grammar_kind() == "variable_with_attributes" {
962                        found_var_with_attrs = true;
963                    }
964                }
965            }
966        }
967        assert!(found_var_with_attrs, "should find variable_with_attributes");
968    }
969
970    #[test]
971    fn test_grammar_kind_double_paren_edge_case() {
972        // Test that grammar_kind() handles the double-paren sexp form correctly.
973        // VariableWithAttributes produces ((variable $ foo) (attributes :lvalue))
974        // and should fall back to pascal_to_snake() to derive the grammar kind.
975        let mut p = Parser::new();
976        let tree = must_some(p.parse("my $x : lvalue = 42;"));
977        let root = tree.root_node();
978        let sexp = root.to_sexp();
979        // Verify the structure includes a my_declaration.
980        assert!(sexp.contains("my_declaration"), "sexp should include my_declaration");
981    }
982
983    // Tests for PerlLanguage descriptor
984
985    #[test]
986    fn test_language_returns_descriptor_with_nonzero_kind_count() {
987        let lang = language();
988        assert!(lang.node_kind_count() > 0, "language should report at least one node kind");
989    }
990
991    #[test]
992    fn test_language_constant_has_nonzero_kind_count() {
993        assert!(LANGUAGE.node_kind_count() > 0, "LANGUAGE should have at least one node kind");
994    }
995
996    #[test]
997    fn test_language_reports_program_as_named_kind() {
998        let lang = language();
999        assert!(lang.node_kind_is_named("Program"), "'Program' should be a named kind");
1000    }
1001
1002    #[test]
1003    fn test_language_rejects_unknown_kind() {
1004        let lang = language();
1005        assert!(
1006            !lang.node_kind_is_named("__nonexistent_kind__"),
1007            "unknown kind should not be named"
1008        );
1009    }
1010
1011    #[test]
1012    fn test_language_kind_names_contains_program() {
1013        let lang = language();
1014        let names = lang.node_kind_names();
1015        assert!(names.contains(&"Program"), "kind names should include 'Program'");
1016    }
1017
1018    #[test]
1019    fn test_language_default_returns_same_as_language() {
1020        // PartialEq compares the backing slice elements, not just the pointer.
1021        // Both language() and PerlLanguage::default() return LANGUAGE so this
1022        // also verifies the Default impl wires up the correct constant.
1023        assert_eq!(language(), PerlLanguage::default());
1024    }
1025
1026    #[test]
1027    fn test_language_kind_names_are_sorted_alphabetically() {
1028        // node_kind_names() documents "in alphabetical order"; enforce that contract.
1029        let lang = language();
1030        let names = lang.node_kind_names();
1031        let mut sorted = names.to_vec();
1032        sorted.sort_unstable();
1033        assert_eq!(
1034            names,
1035            sorted.as_slice(),
1036            "node_kind_names() must be in alphabetical order; \
1037             re-sort ALL_KIND_NAMES in perl-ast if a new variant was added out of order"
1038        );
1039    }
1040
1041    #[test]
1042    fn test_language_is_named_with_empty_string_returns_false() {
1043        // Empty string is not a valid kind name and must not be found.
1044        assert!(!language().node_kind_is_named(""), "empty kind name must return false");
1045    }
1046
1047    #[test]
1048    fn test_tree_cursor_walks_children_and_siblings() {
1049        let mut parser = Parser::new();
1050        let tree = must_some(parser.parse("my $x = 1; my $y = 2;"));
1051        let root = tree.root_node();
1052        let mut cursor = root.walk();
1053
1054        assert_eq!(cursor.node().grammar_kind(), "source_file");
1055        assert!(cursor.goto_first_child(), "source_file should have at least one child");
1056        assert_eq!(cursor.node().grammar_kind(), "my_declaration");
1057        assert!(cursor.goto_next_sibling(), "first statement should have a sibling");
1058        assert_eq!(cursor.node().grammar_kind(), "my_declaration");
1059        assert!(!cursor.goto_next_sibling(), "second statement should be the last sibling");
1060    }
1061
1062    #[test]
1063    fn test_tree_walk_starts_cursor_at_root() {
1064        let mut parser = Parser::new();
1065        let tree = must_some(parser.parse("my $x = 1;"));
1066        let mut cursor = tree.walk();
1067
1068        assert_eq!(cursor.node().grammar_kind(), "source_file");
1069        assert!(cursor.goto_first_child(), "root should have a child");
1070        assert_eq!(cursor.node().grammar_kind(), "my_declaration");
1071    }
1072
1073    #[test]
1074    fn test_tree_cursor_parent_and_reset_behavior() {
1075        let mut parser = Parser::new();
1076        let tree = must_some(parser.parse("my $x = 1;"));
1077        let root = tree.root_node();
1078        let mut cursor = root.walk();
1079
1080        assert!(!cursor.goto_parent(), "cursor at root must not move to parent");
1081        assert!(cursor.goto_first_child(), "root should have a child");
1082        assert!(cursor.goto_parent(), "child should have root as parent");
1083        assert_eq!(cursor.node().grammar_kind(), "source_file");
1084
1085        assert!(cursor.goto_first_child(), "root should still have a child");
1086        cursor.reset();
1087        assert_eq!(cursor.node().grammar_kind(), "source_file");
1088    }
1089
1090    #[test]
1091    fn test_tree_cursor_last_child_and_previous_sibling_behavior() {
1092        let mut p = Parser::new();
1093        let tree = must_some(p.parse("my $a = 1; my $b = 2;"));
1094        let root = tree.root_node();
1095        let mut cursor = root.walk();
1096
1097        assert!(cursor.goto_last_child(), "root should have a last child");
1098        assert_eq!(cursor.node().grammar_kind(), "my_declaration");
1099        assert!(cursor.goto_previous_sibling(), "last child should have a previous sibling");
1100        assert_eq!(cursor.node().grammar_kind(), "my_declaration");
1101        assert!(!cursor.goto_previous_sibling(), "first sibling should not have previous sibling");
1102    }
1103
1104    #[test]
1105    fn test_tree_cursor_last_child_returns_false_for_leaf() {
1106        let mut p = Parser::new();
1107        let tree = must_some(p.parse("my $x = 42;"));
1108        let root = tree.root_node();
1109        let mut cursor = root.walk();
1110
1111        assert!(cursor.goto_first_child(), "root should have a child");
1112        assert!(cursor.goto_first_child(), "my_declaration should have a child");
1113        let at_leaf = !cursor.goto_last_child();
1114        assert!(at_leaf, "leaf nodes should not have a last child");
1115    }
1116
1117    #[test]
1118    fn test_tree_cursor_goto_first_child_returns_false_for_leaf() {
1119        // A leaf node has no children; goto_first_child must return false and
1120        // leave the cursor positioned at the leaf rather than panicking.
1121        let mut parser = Parser::new();
1122        let tree = must_some(parser.parse("my $x = 1;"));
1123        let root = tree.root_node();
1124        let mut cursor = root.walk();
1125
1126        // Navigate to a leaf: root -> first child (my_declaration) -> first child (leaf token).
1127        assert!(cursor.goto_first_child(), "root should have a child");
1128        assert!(cursor.goto_first_child(), "my_declaration should have a child");
1129        // The leaf must refuse another goto_first_child.
1130        let at_leaf = !cursor.goto_first_child();
1131        assert!(at_leaf, "goto_first_child must return false on a leaf node");
1132    }
1133
1134    #[test]
1135    fn test_tree_cursor_multiple_goto_next_sibling_exhausts() {
1136        // When repeatedly calling goto_next_sibling, the cursor must eventually
1137        // return false and stay positioned at the last sibling.
1138        let mut parser = Parser::new();
1139        let tree = must_some(parser.parse("1; 2; 3;"));
1140        let mut cursor = tree.walk();
1141
1142        // Navigate to first statement
1143        assert!(cursor.goto_first_child());
1144        let mut _count = 1;
1145        // Keep advancing siblings until we can't
1146        while cursor.goto_next_sibling() {
1147            _count += 1;
1148        }
1149        // After last goto_next_sibling returns false, cursor should still be valid
1150        // and still have a node (the last sibling).
1151        let node = cursor.node();
1152        assert!(
1153            !node.kind().is_empty(),
1154            "cursor should remain at valid node after exhausting siblings"
1155        );
1156    }
1157
1158    #[test]
1159    fn test_tree_cursor_goto_parent_at_root_repeatedly() {
1160        // Calling goto_parent at root should return false every time, keeping cursor at root.
1161        let mut parser = Parser::new();
1162        let tree = must_some(parser.parse("my $x = 1;"));
1163        let mut cursor = tree.walk();
1164
1165        // We should always be at root initially
1166        assert_eq!(cursor.node().grammar_kind(), "source_file");
1167
1168        // Try to go up multiple times — must stay at root
1169        for _ in 0..3 {
1170            let result = cursor.goto_parent();
1171            assert!(!result, "goto_parent at root must return false");
1172            assert_eq!(
1173                cursor.node().grammar_kind(),
1174                "source_file",
1175                "cursor must remain at root after failed goto_parent"
1176            );
1177        }
1178    }
1179
1180    #[test]
1181    fn test_tree_cursor_reset_from_deep_nesting() {
1182        // reset() must return cursor to root regardless of depth.
1183        let mut parser = Parser::new();
1184        let tree = must_some(parser.parse("sub foo { my $x = 1; }"));
1185        let mut cursor = tree.walk();
1186
1187        // Navigate deep into the tree
1188        let mut depth = 0;
1189        while cursor.goto_first_child() && depth < 10 {
1190            depth += 1;
1191        }
1192        assert!(depth > 0, "should have navigated at least one level deep");
1193
1194        // reset() should bring us back to root
1195        cursor.reset();
1196        assert_eq!(cursor.node().grammar_kind(), "source_file", "reset must return cursor to root");
1197    }
1198
1199    #[test]
1200    fn test_tree_cursor_empty_source_root_is_valid() {
1201        // Empty source still produces a (minimal) tree; cursor at root should be valid.
1202        let mut parser = Parser::new();
1203        let tree = must_some(parser.parse(""));
1204        let cursor = tree.walk();
1205
1206        let node = cursor.node();
1207        assert_eq!(node.grammar_kind(), "source_file");
1208        assert!(node.child_count() == 0, "empty source tree should have no statements");
1209    }
1210
1211    #[test]
1212    fn test_tree_cursor_empty_source_goto_first_child_returns_false() {
1213        // Empty source root has no children; goto_first_child must return false.
1214        let mut parser = Parser::new();
1215        let tree = must_some(parser.parse(""));
1216        let mut cursor = tree.walk();
1217
1218        let result = cursor.goto_first_child();
1219        assert!(!result, "empty tree root should have no first child");
1220    }
1221
1222    #[test]
1223    fn test_tree_cursor_single_statement_navigation() {
1224        // Single statement: root -> statement -> (children or leaf).
1225        let mut parser = Parser::new();
1226        let tree = must_some(parser.parse("42;"));
1227        let mut cursor = tree.walk();
1228
1229        assert_eq!(cursor.node().grammar_kind(), "source_file");
1230        assert!(cursor.goto_first_child(), "root should have exactly one statement");
1231
1232        // The single statement should have no next sibling
1233        assert!(!cursor.goto_next_sibling(), "single statement should be the only child");
1234
1235        // Going back up should land at root
1236        assert!(cursor.goto_parent(), "should be able to return to root");
1237        assert_eq!(cursor.node().grammar_kind(), "source_file");
1238    }
1239
1240    #[test]
1241    fn test_tree_cursor_sibling_navigation_exact_count() {
1242        // Navigate through all siblings and verify the count matches child_count.
1243        let mut parser = Parser::new();
1244        let tree = must_some(parser.parse("1; 2; 3; 4;"));
1245        let root = tree.root_node();
1246        let child_count = root.child_count();
1247
1248        let mut cursor = tree.walk();
1249        assert!(cursor.goto_first_child());
1250
1251        let mut sibling_count = 1;
1252        while cursor.goto_next_sibling() {
1253            sibling_count += 1;
1254        }
1255
1256        assert_eq!(sibling_count, child_count, "sibling count should match root.child_count()");
1257    }
1258
1259    #[test]
1260    fn test_tree_cursor_alternating_parent_child_navigation() {
1261        // Test mixed navigation: down, up, down again at different indices.
1262        let mut parser = Parser::new();
1263        let tree = must_some(parser.parse("sub a { 1; } sub b { 2; }"));
1264        let mut cursor = tree.walk();
1265
1266        // Down to first sub
1267        assert!(cursor.goto_first_child());
1268        let first_kind = cursor.node().grammar_kind().to_string();
1269
1270        // Back to root
1271        assert!(cursor.goto_parent());
1272        assert_eq!(cursor.node().grammar_kind(), "source_file");
1273
1274        // Down again to first sub (should be the same)
1275        assert!(cursor.goto_first_child());
1276        assert_eq!(
1277            cursor.node().grammar_kind(),
1278            first_kind,
1279            "re-navigating should reach the same node"
1280        );
1281    }
1282
1283    #[test]
1284    fn test_tree_cursor_complex_traversal_sequence() {
1285        // Complex sequence: down, sibling, sibling, up, down, sibling.
1286        let mut parser = Parser::new();
1287        let tree = must_some(parser.parse("my $a = 1; my $b = 2; my $c = 3;"));
1288        let mut cursor = tree.walk();
1289
1290        // Down to first statement
1291        assert!(cursor.goto_first_child(), "down to first stmt");
1292        assert_eq!(cursor.node().grammar_kind(), "my_declaration");
1293
1294        // Move to second statement
1295        assert!(cursor.goto_next_sibling(), "sibling to second stmt");
1296        assert_eq!(cursor.node().grammar_kind(), "my_declaration");
1297
1298        // Move to third statement
1299        assert!(cursor.goto_next_sibling(), "sibling to third stmt");
1300        assert_eq!(cursor.node().grammar_kind(), "my_declaration");
1301
1302        // No fourth statement
1303        assert!(!cursor.goto_next_sibling(), "no fourth statement");
1304
1305        // Back to root
1306        assert!(cursor.goto_parent(), "back to root");
1307        assert_eq!(cursor.node().grammar_kind(), "source_file");
1308
1309        // Down to first again
1310        assert!(cursor.goto_first_child(), "down to first again");
1311        assert_eq!(cursor.node().grammar_kind(), "my_declaration");
1312    }
1313
1314    #[test]
1315    fn test_tree_cursor_node_identity_after_traversal() {
1316        // A node retrieved at the same path should be equal across separate traversals.
1317        let mut parser = Parser::new();
1318        let tree = must_some(parser.parse("my $x = 1;"));
1319        let mut cursor = tree.walk();
1320
1321        // First traversal: get to first child and extract its kind
1322        assert!(cursor.goto_first_child());
1323        let first_kind = cursor.node().grammar_kind().to_string();
1324
1325        // Reset and repeat
1326        cursor.reset();
1327        assert!(cursor.goto_first_child());
1328        let second_kind = cursor.node().grammar_kind().to_string();
1329
1330        assert_eq!(
1331            first_kind, second_kind,
1332            "node at the same path should have the same kind in both traversals"
1333        );
1334    }
1335
1336    #[test]
1337    fn test_tree_cursor_sibling_with_unicode_identifiers() {
1338        // Cursor must correctly navigate siblings even when source contains Unicode.
1339        let mut parser = Parser::new();
1340        let tree = must_some(parser.parse("my $café = 1; my $naïve = 2;"));
1341        let mut cursor = tree.walk();
1342
1343        let root = tree.root_node();
1344        let expected_count = root.child_count();
1345
1346        // Count siblings via cursor
1347        assert!(cursor.goto_first_child());
1348        let mut count = 1;
1349        while cursor.goto_next_sibling() {
1350            count += 1;
1351        }
1352
1353        assert_eq!(
1354            count, expected_count,
1355            "sibling count should match even with Unicode identifiers"
1356        );
1357    }
1358
1359    #[test]
1360    fn test_tree_cursor_deeply_nested_structure() {
1361        // Verify cursor can navigate a deeply nested structure without stack overflow.
1362        let mut parser = Parser::new();
1363        // Create nested blocks: { { { ... } } }
1364        let mut code = String::new();
1365        for i in 0..5 {
1366            code.push_str(&format!("sub level_{} {{ ", i));
1367        }
1368        code.push_str("1;");
1369        for _ in 0..5 {
1370            code.push_str(" }");
1371        }
1372
1373        let tree = must_some(parser.parse(&code));
1374        let mut cursor = tree.walk();
1375
1376        // Navigate down as far as possible
1377        let mut depth = 0;
1378        while cursor.goto_first_child() && depth < 50 {
1379            depth += 1;
1380        }
1381
1382        // Should have navigated several levels
1383        assert!(depth > 2, "should navigate multiple levels in nested structure");
1384
1385        // Navigate back up
1386        while cursor.goto_parent() {
1387            depth -= 1;
1388        }
1389
1390        // Should be back at root
1391        assert_eq!(cursor.node().grammar_kind(), "source_file");
1392        assert_eq!(depth, 0, "should have gone back up to root (depth 0)");
1393    }
1394}