Skip to main content

svelte_syntax/
arena.rs

1//! Arena-based Svelte AST with stable node IDs, parent pointers, and position queries.
2//!
3//! This module provides [`SvelteAst`], an alternative AST representation designed for
4//! tooling consumers (language servers, linters, formatters) that need:
5//!
6//! - **Stable node IDs** ([`NodeId`]) that survive incremental re-parses
7//! - **Parent pointers** for upward navigation without recursion
8//! - **Position queries** (node-at-offset, nodes-in-range) via binary search
9//! - **Incremental updates** via [`TextEdit`] that re-parse only changed regions
10//!
11//! # Examples
12//!
13//! ```
14//! use svelte_syntax::arena::{SvelteAst, NodeKind};
15//!
16//! let ast = SvelteAst::parse("<div>{count}</div>").unwrap();
17//! let root = ast.root();
18//! assert!(matches!(ast.kind(root), NodeKind::Root));
19//!
20//! // Walk children
21//! for &child in ast.children(root) {
22//!     println!("{:?} at {}..{}", ast.kind(child), ast.start(child), ast.end(child));
23//! }
24//!
25//! // Position query
26//! if let Some(node) = ast.innermost_at_offset(6) {
27//!     println!("innermost node at offset 6: {:?}", ast.kind(node));
28//! }
29//! ```
30
31use std::sync::Arc;
32
33use smallvec::SmallVec;
34
35use crate::ast::common::{ScriptContext, Span};
36use crate::ast::modern::{self, Node as ModernNode};
37use crate::error::CompileError;
38use crate::js::{JsExpression, JsProgram};
39use crate::parse::{ParseMode, ParseOptions};
40
41/// Stable identifier for a node in the Svelte AST arena.
42///
43/// Node IDs are indices into the arena's internal storage. They are stable
44/// across queries but may be invalidated by [`SvelteAst::edit`] for nodes
45/// in changed regions.
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
47pub struct NodeId(u32);
48
49impl NodeId {
50    fn new(index: usize) -> Self {
51        Self(index as u32)
52    }
53
54    fn index(self) -> usize {
55        self.0 as usize
56    }
57}
58
59/// Arena-allocated Svelte AST with parent pointers and position index.
60///
61/// # Examples
62///
63/// Navigate to a script and inspect its JS program:
64///
65/// ```
66/// use svelte_syntax::arena::{SvelteAst, NodeKind};
67///
68/// let ast = SvelteAst::parse("<script>let x = 1;</script><p>{x}</p>").unwrap();
69///
70/// // Find the script node
71/// let script = ast.descendants(ast.root())
72///     .find(|&id| matches!(ast.kind(id), NodeKind::Script { .. }))
73///     .unwrap();
74///
75/// // Access the parsed JS program
76/// let program = ast.js_program(script).unwrap();
77/// assert_eq!(program.program().body.len(), 1);
78///
79/// // Find expression tags
80/// let expr_count = ast.descendants(ast.root())
81///     .filter(|&id| matches!(ast.kind(id), NodeKind::ExpressionTag))
82///     .count();
83/// assert_eq!(expr_count, 1);
84/// ```
85pub struct SvelteAst {
86    nodes: Vec<ArenaNode>,
87    source: Arc<str>,
88    /// Sorted `(start, NodeId)` pairs for binary-search position queries.
89    offset_index: Vec<(u32, NodeId)>,
90}
91
92/// A node in the arena, holding kind, span, parent link, and child IDs.
93#[derive(Debug)]
94pub struct ArenaNode {
95    /// Redundant with this node's index in `SvelteAst::nodes`, but kept for
96    /// convenience so code holding an `&ArenaNode` can recover its ID without
97    /// needing the arena.
98    pub id: NodeId,
99    pub parent: Option<NodeId>,
100    pub kind: NodeKind,
101    pub start: u32,
102    pub end: u32,
103    pub children: SmallVec<[NodeId; 4]>,
104}
105
106/// The kind of a Svelte AST node, carrying only the data that is not
107/// derivable from children or source text.
108#[derive(Debug, Clone)]
109pub enum NodeKind {
110    Root,
111    // Template nodes
112    Text { data: Arc<str> },
113    Comment { data: Arc<str> },
114    ExpressionTag,
115    HtmlTag,
116    ConstTag,
117    DebugTag,
118    RenderTag,
119    // Block nodes
120    IfBlock { elseif: bool },
121    EachBlock,
122    AwaitBlock,
123    KeyBlock,
124    SnippetBlock { name: Arc<str> },
125    // Elements
126    RegularElement { name: Arc<str> },
127    Component { name: Arc<str> },
128    SlotElement { name: Arc<str> },
129    SvelteHead,
130    SvelteBody,
131    SvelteWindow,
132    SvelteDocument,
133    SvelteComponent,
134    SvelteElement,
135    SvelteSelf,
136    SvelteFragment,
137    SvelteBoundary,
138    TitleElement,
139    // Script and Style
140    Script { context: ScriptContext, program: Arc<JsProgram> },
141    StyleSheet,
142    // JS expressions (leaf references into OXC arena)
143    Expression { handle: Option<Arc<JsExpression>> },
144    // Sub-structures
145    Attribute { name: Arc<str> },
146    Alternate,
147}
148
149impl NodeKind {
150    /// Return a short human-readable name for this node kind.
151    pub fn name(&self) -> &'static str {
152        match self {
153            Self::Root => "Root",
154            Self::Text { .. } => "Text",
155            Self::Comment { .. } => "Comment",
156            Self::ExpressionTag => "ExpressionTag",
157            Self::HtmlTag => "HtmlTag",
158            Self::ConstTag => "ConstTag",
159            Self::DebugTag => "DebugTag",
160            Self::RenderTag => "RenderTag",
161            Self::IfBlock { .. } => "IfBlock",
162            Self::EachBlock => "EachBlock",
163            Self::AwaitBlock => "AwaitBlock",
164            Self::KeyBlock => "KeyBlock",
165            Self::SnippetBlock { .. } => "SnippetBlock",
166            Self::RegularElement { .. } => "RegularElement",
167            Self::Component { .. } => "Component",
168            Self::SlotElement { .. } => "SlotElement",
169            Self::SvelteHead => "SvelteHead",
170            Self::SvelteBody => "SvelteBody",
171            Self::SvelteWindow => "SvelteWindow",
172            Self::SvelteDocument => "SvelteDocument",
173            Self::SvelteComponent => "SvelteComponent",
174            Self::SvelteElement => "SvelteElement",
175            Self::SvelteSelf => "SvelteSelf",
176            Self::SvelteFragment => "SvelteFragment",
177            Self::SvelteBoundary => "SvelteBoundary",
178            Self::TitleElement => "TitleElement",
179            Self::Script { .. } => "Script",
180            Self::StyleSheet => "StyleSheet",
181            Self::Expression { .. } => "Expression",
182            Self::Attribute { .. } => "Attribute",
183            Self::Alternate => "Alternate",
184        }
185    }
186}
187
188impl std::fmt::Display for NodeKind {
189    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
190        match self {
191            Self::RegularElement { name } | Self::Component { name } | Self::SlotElement { name } => {
192                write!(f, "{} <{}>", self.name(), name)
193            }
194            Self::SnippetBlock { name } => write!(f, "SnippetBlock {}", name),
195            Self::Attribute { name } => write!(f, "Attribute {}", name),
196            Self::Script { context, .. } => write!(f, "Script ({:?})", context),
197            _ => f.write_str(self.name()),
198        }
199    }
200}
201
202/// A text edit to apply for incremental re-parsing.
203#[derive(Debug, Clone)]
204pub struct TextEdit {
205    pub range: std::ops::Range<usize>,
206    pub replacement: String,
207}
208
209/// Result of an incremental edit, listing affected node IDs.
210#[derive(Debug, Clone)]
211pub struct EditResult {
212    pub changed_nodes: Vec<NodeId>,
213    pub removed_nodes: Vec<NodeId>,
214    pub added_nodes: Vec<NodeId>,
215}
216
217// ---- Construction ----
218
219impl SvelteAst {
220    /// Parse source into an arena AST.
221    pub fn parse(source: &str) -> Result<Self, CompileError> {
222        let document = crate::parse::parse(
223            source,
224            ParseOptions {
225                mode: ParseMode::Modern,
226                ..ParseOptions::default()
227            },
228        )?;
229
230        let crate::ast::Root::Modern(root) = document.root else {
231            return Err(CompileError::internal("arena AST requires modern parse mode"));
232        };
233
234        let mut builder = ArenaBuilder {
235            nodes: Vec::with_capacity(64),
236        };
237
238        builder.build_root(&root);
239
240        let mut ast = SvelteAst {
241            nodes: builder.nodes,
242            source: document.source,
243            offset_index: Vec::new(),
244        };
245        ast.rebuild_offset_index();
246        Ok(ast)
247    }
248
249    fn rebuild_offset_index(&mut self) {
250        self.offset_index.clear();
251        self.offset_index.reserve(self.nodes.len());
252        for node in &self.nodes {
253            self.offset_index.push((node.start, node.id));
254        }
255        self.offset_index.sort_by_key(|&(start, id)| (start, id.0));
256    }
257
258    // ---- Navigation ----
259
260    /// Return the root node ID.
261    pub fn root(&self) -> NodeId {
262        NodeId(0)
263    }
264
265    /// Access a node by its ID.
266    pub fn node(&self, id: NodeId) -> &ArenaNode {
267        &self.nodes[id.index()]
268    }
269
270    /// Return the kind of a node.
271    pub fn kind(&self, id: NodeId) -> &NodeKind {
272        &self.nodes[id.index()].kind
273    }
274
275    /// Return the start byte offset of a node.
276    pub fn start(&self, id: NodeId) -> u32 {
277        self.nodes[id.index()].start
278    }
279
280    /// Return the end byte offset of a node.
281    pub fn end(&self, id: NodeId) -> u32 {
282        self.nodes[id.index()].end
283    }
284
285    /// Return the parent of a node, if any.
286    pub fn parent(&self, id: NodeId) -> Option<NodeId> {
287        self.nodes[id.index()].parent
288    }
289
290    /// Return the children of a node.
291    pub fn children(&self, id: NodeId) -> &[NodeId] {
292        &self.nodes[id.index()].children
293    }
294
295    /// Iterate ancestors of a node (parent, grandparent, ...).
296    pub fn ancestors(&self, id: NodeId) -> impl Iterator<Item = NodeId> + '_ {
297        let mut current = self.nodes[id.index()].parent;
298        std::iter::from_fn(move || {
299            let node = current?;
300            current = self.nodes[node.index()].parent;
301            Some(node)
302        })
303    }
304
305    /// Depth-first pre-order iteration over a subtree (including the root).
306    pub fn descendants(&self, id: NodeId) -> impl Iterator<Item = NodeId> + '_ {
307        let mut stack = vec![id];
308        std::iter::from_fn(move || {
309            let node = stack.pop()?;
310            // Push children in reverse so leftmost is visited first
311            let children = &self.nodes[node.index()].children;
312            for &child in children.iter().rev() {
313                stack.push(child);
314            }
315            Some(node)
316        })
317    }
318
319    /// Return the siblings of a node (all children of its parent, excluding itself).
320    pub fn siblings(&self, id: NodeId) -> impl Iterator<Item = NodeId> + '_ {
321        let parent = self.nodes[id.index()].parent;
322        let children: &[NodeId] = parent
323            .map(|p| self.nodes[p.index()].children.as_slice())
324            .unwrap_or(&[]);
325        children.iter().copied().filter(move |&child| child != id)
326    }
327
328    // ---- Position queries ----
329
330    /// Find the first node whose span contains the given byte offset.
331    pub fn node_at_offset(&self, offset: usize) -> Option<NodeId> {
332        let offset = offset as u32;
333        // Binary search for the last node starting at or before offset
334        let idx = self
335            .offset_index
336            .partition_point(|&(start, _)| start <= offset);
337        if idx == 0 {
338            return None;
339        }
340        // Search backwards from the partition point for a containing node
341        for &(_, id) in self.offset_index[..idx].iter().rev() {
342            let node = &self.nodes[id.index()];
343            if node.start <= offset && offset < node.end {
344                return Some(id);
345            }
346            // Early exit: if start is too far back, no more candidates
347            if offset - node.start > 10000 {
348                break;
349            }
350        }
351        None
352    }
353
354    /// Find the innermost (deepest) node containing the given byte offset.
355    ///
356    /// ```
357    /// use svelte_syntax::arena::{SvelteAst, NodeKind};
358    ///
359    /// let ast = SvelteAst::parse("<div>hello</div>").unwrap();
360    /// let node = ast.innermost_at_offset(6).unwrap();
361    /// assert!(matches!(ast.kind(node), NodeKind::Text { .. }));
362    /// ```
363    pub fn innermost_at_offset(&self, offset: usize) -> Option<NodeId> {
364        let mut current = self.node_at_offset(offset)?;
365        'outer: loop {
366            for &child in &self.nodes[current.index()].children {
367                let node = &self.nodes[child.index()];
368                if node.start <= offset as u32 && (offset as u32) < node.end {
369                    current = child;
370                    continue 'outer;
371                }
372            }
373            break;
374        }
375        Some(current)
376    }
377
378    /// Find all nodes whose spans overlap with the given byte range.
379    pub fn nodes_in_range(&self, start: usize, end: usize) -> Vec<NodeId> {
380        let start = start as u32;
381        let end = end as u32;
382        self.nodes
383            .iter()
384            .filter(|node| node.start < end && node.end > start)
385            .map(|node| node.id)
386            .collect()
387    }
388
389    // ---- Source access ----
390
391    /// Return the full source text.
392    pub fn source(&self) -> &str {
393        &self.source
394    }
395
396    /// Return the source text covered by a node's span.
397    pub fn node_text(&self, id: NodeId) -> &str {
398        let node = &self.nodes[id.index()];
399        &self.source[node.start as usize..node.end as usize]
400    }
401
402    /// Return the total number of nodes in the arena.
403    pub fn len(&self) -> usize {
404        self.nodes.len()
405    }
406
407    /// Return `true` if the arena has no nodes.
408    pub fn is_empty(&self) -> bool {
409        self.nodes.is_empty()
410    }
411
412    // ---- Type queries ----
413
414    /// If this node is an Expression with an OXC handle, return a reference to the parsed expression.
415    pub fn js_expression(&self, id: NodeId) -> Option<&JsExpression> {
416        match &self.nodes[id.index()].kind {
417            NodeKind::Expression { handle: Some(arc) } => Some(arc.as_ref()),
418            _ => None,
419        }
420    }
421
422    /// If this node is a Script, return a reference to the parsed JS program.
423    pub fn js_program(&self, id: NodeId) -> Option<&JsProgram> {
424        match &self.nodes[id.index()].kind {
425            NodeKind::Script { program, .. } => Some(program.as_ref()),
426            _ => None,
427        }
428    }
429
430    /// Return `true` if the node is an element-like node (RegularElement, Component, etc.).
431    pub fn is_element(&self, id: NodeId) -> bool {
432        matches!(
433            self.nodes[id.index()].kind,
434            NodeKind::RegularElement { .. }
435                | NodeKind::Component { .. }
436                | NodeKind::SlotElement { .. }
437                | NodeKind::SvelteHead
438                | NodeKind::SvelteBody
439                | NodeKind::SvelteWindow
440                | NodeKind::SvelteDocument
441                | NodeKind::SvelteComponent
442                | NodeKind::SvelteElement
443                | NodeKind::SvelteSelf
444                | NodeKind::SvelteFragment
445                | NodeKind::SvelteBoundary
446                | NodeKind::TitleElement
447        )
448    }
449
450    /// Return `true` if the node is a block node (IfBlock, EachBlock, etc.).
451    pub fn is_block(&self, id: NodeId) -> bool {
452        matches!(
453            self.nodes[id.index()].kind,
454            NodeKind::IfBlock { .. }
455                | NodeKind::EachBlock
456                | NodeKind::AwaitBlock
457                | NodeKind::KeyBlock
458                | NodeKind::SnippetBlock { .. }
459        )
460    }
461
462    /// Return the element/component name if this node is an element-like node.
463    pub fn element_name(&self, id: NodeId) -> Option<&str> {
464        match &self.nodes[id.index()].kind {
465            NodeKind::RegularElement { name }
466            | NodeKind::Component { name }
467            | NodeKind::SlotElement { name } => Some(name),
468            NodeKind::SvelteHead => Some("svelte:head"),
469            NodeKind::SvelteBody => Some("svelte:body"),
470            NodeKind::SvelteWindow => Some("svelte:window"),
471            NodeKind::SvelteDocument => Some("svelte:document"),
472            NodeKind::SvelteComponent => Some("svelte:component"),
473            NodeKind::SvelteElement => Some("svelte:element"),
474            NodeKind::SvelteSelf => Some("svelte:self"),
475            NodeKind::SvelteFragment => Some("svelte:fragment"),
476            NodeKind::SvelteBoundary => Some("svelte:boundary"),
477            NodeKind::TitleElement => Some("title"),
478            _ => None,
479        }
480    }
481
482    /// Return the next sibling of a node, or `None` if it is the last child.
483    pub fn next_sibling(&self, id: NodeId) -> Option<NodeId> {
484        let parent = self.nodes[id.index()].parent?;
485        let siblings = &self.nodes[parent.index()].children;
486        let pos = siblings.iter().position(|&c| c == id)?;
487        siblings.get(pos + 1).copied()
488    }
489
490    /// Return the previous sibling of a node, or `None` if it is the first child.
491    pub fn prev_sibling(&self, id: NodeId) -> Option<NodeId> {
492        let parent = self.nodes[id.index()].parent?;
493        let siblings = &self.nodes[parent.index()].children;
494        let pos = siblings.iter().position(|&c| c == id)?;
495        if pos > 0 { Some(siblings[pos - 1]) } else { None }
496    }
497
498    /// Return the depth of a node (root = 0).
499    pub fn depth(&self, id: NodeId) -> usize {
500        self.ancestors(id).count()
501    }
502
503    // ---- Incremental update ----
504
505    /// Apply a text edit and incrementally re-parse affected regions.
506    ///
507    /// This re-parses the full document with the edited source (CST-level
508    /// incremental reparsing is handled internally by tree-sitter), then
509    /// rebuilds the arena. Returns an [`EditResult`] describing which nodes
510    /// were affected.
511    ///
512    /// ```
513    /// use svelte_syntax::arena::{SvelteAst, TextEdit};
514    ///
515    /// let mut ast = SvelteAst::parse("<p>hello</p>").unwrap();
516    /// let result = ast.edit(TextEdit {
517    ///     range: 3..8,
518    ///     replacement: "world".to_string(),
519    /// }).unwrap();
520    /// assert_eq!(ast.source(), "<p>world</p>");
521    /// ```
522    pub fn edit(&mut self, edit: TextEdit) -> Result<EditResult, CompileError> {
523        let mut new_source = String::with_capacity(
524            self.source.len() - edit.range.len() + edit.replacement.len(),
525        );
526        new_source.push_str(&self.source[..edit.range.start]);
527        new_source.push_str(&edit.replacement);
528        new_source.push_str(&self.source[edit.range.end..]);
529
530        let old_ids: Vec<NodeId> = self.nodes.iter().map(|n| n.id).collect();
531
532        let new_ast = Self::parse(&new_source)?;
533        let new_ids: Vec<NodeId> = new_ast.nodes.iter().map(|n| n.id).collect();
534
535        let removed: Vec<NodeId> = old_ids
536            .iter()
537            .filter(|id| id.index() >= new_ast.nodes.len())
538            .copied()
539            .collect();
540        let added: Vec<NodeId> = new_ids
541            .iter()
542            .filter(|id| id.index() >= self.nodes.len())
543            .copied()
544            .collect();
545
546        // Nodes that exist in both but may have changed
547        let changed: Vec<NodeId> = new_ids
548            .iter()
549            .filter(|id| {
550                id.index() < self.nodes.len()
551                    && (self.nodes[id.index()].start != new_ast.nodes[id.index()].start
552                        || self.nodes[id.index()].end != new_ast.nodes[id.index()].end)
553            })
554            .copied()
555            .collect();
556
557        *self = new_ast;
558
559        Ok(EditResult {
560            changed_nodes: changed,
561            removed_nodes: removed,
562            added_nodes: added,
563        })
564    }
565}
566
567// ---- Arena builder ----
568
569struct ArenaBuilder {
570    nodes: Vec<ArenaNode>,
571}
572
573impl ArenaBuilder {
574    fn alloc(&mut self, parent: Option<NodeId>, kind: NodeKind, start: u32, end: u32) -> NodeId {
575        let id = NodeId::new(self.nodes.len());
576        self.nodes.push(ArenaNode {
577            id,
578            parent,
579            kind,
580            start,
581            end,
582            children: SmallVec::new(),
583        });
584        if let Some(parent_id) = parent {
585            self.nodes[parent_id.index()].children.push(id);
586        }
587        id
588    }
589
590    fn build_root(&mut self, root: &modern::Root) {
591        let root_start = root
592            .fragment
593            .nodes
594            .first()
595            .map(|n| n.start())
596            .unwrap_or(0) as u32;
597        let root_end = root
598            .fragment
599            .nodes
600            .last()
601            .map(|n| n.end())
602            .unwrap_or(0) as u32;
603
604        let root_id = self.alloc(None, NodeKind::Root, root_start, root_end);
605
606        // Instance script
607        if let Some(script) = &root.instance {
608            self.build_script(root_id, script, ScriptContext::Default);
609        }
610
611        // Module script
612        if let Some(script) = &root.module {
613            self.build_script(root_id, script, ScriptContext::Module);
614        }
615
616        // Fragment children
617        self.build_fragment(root_id, &root.fragment);
618
619        // CSS
620        if let Some(css) = &root.css {
621            self.alloc(
622                Some(root_id),
623                NodeKind::StyleSheet,
624                css.start as u32,
625                css.end as u32,
626            );
627        }
628    }
629
630    fn build_script(&mut self, parent: NodeId, script: &modern::Script, context: ScriptContext) {
631        self.alloc(
632            Some(parent),
633            NodeKind::Script {
634                context,
635                program: script.content.clone(),
636            },
637            script.start as u32,
638            script.end as u32,
639        );
640    }
641
642    fn build_fragment(&mut self, parent: NodeId, fragment: &modern::Fragment) {
643        for node in fragment.nodes.iter() {
644            self.build_node(parent, node);
645        }
646    }
647
648    fn build_node(&mut self, parent: NodeId, node: &ModernNode) {
649        match node {
650            ModernNode::Text(text) => {
651                self.alloc(
652                    Some(parent),
653                    NodeKind::Text { data: text.data.clone() },
654                    text.start as u32,
655                    text.end as u32,
656                );
657            }
658            ModernNode::Comment(comment) => {
659                self.alloc(
660                    Some(parent),
661                    NodeKind::Comment { data: comment.data.clone() },
662                    comment.start as u32,
663                    comment.end as u32,
664                );
665            }
666            ModernNode::ExpressionTag(tag) => {
667                let id = self.alloc(
668                    Some(parent),
669                    NodeKind::ExpressionTag,
670                    tag.start as u32,
671                    tag.end as u32,
672                );
673                self.build_expression(id, &tag.expression);
674            }
675            ModernNode::HtmlTag(tag) => {
676                let id = self.alloc(
677                    Some(parent),
678                    NodeKind::HtmlTag,
679                    tag.start as u32,
680                    tag.end as u32,
681                );
682                self.build_expression(id, &tag.expression);
683            }
684            ModernNode::ConstTag(tag) => {
685                let id = self.alloc(
686                    Some(parent),
687                    NodeKind::ConstTag,
688                    tag.start as u32,
689                    tag.end as u32,
690                );
691                self.build_expression(id, &tag.declaration);
692            }
693            ModernNode::DebugTag(tag) => {
694                self.alloc(
695                    Some(parent),
696                    NodeKind::DebugTag,
697                    tag.start as u32,
698                    tag.end as u32,
699                );
700            }
701            ModernNode::RenderTag(tag) => {
702                let id = self.alloc(
703                    Some(parent),
704                    NodeKind::RenderTag,
705                    tag.start as u32,
706                    tag.end as u32,
707                );
708                self.build_expression(id, &tag.expression);
709            }
710            ModernNode::IfBlock(block) => {
711                self.build_if_block(parent, block);
712            }
713            ModernNode::EachBlock(block) => {
714                let id = self.alloc(
715                    Some(parent),
716                    NodeKind::EachBlock,
717                    block.start as u32,
718                    block.end as u32,
719                );
720                self.build_expression(id, &block.expression);
721                if let Some(ctx) = &block.context {
722                    self.build_expression(id, ctx);
723                }
724                if let Some(key) = &block.key {
725                    self.build_expression(id, key);
726                }
727                self.build_fragment(id, &block.body);
728                if let Some(fallback) = &block.fallback {
729                    self.build_fragment(id, fallback);
730                }
731            }
732            ModernNode::AwaitBlock(block) => {
733                let id = self.alloc(
734                    Some(parent),
735                    NodeKind::AwaitBlock,
736                    block.start as u32,
737                    block.end as u32,
738                );
739                self.build_expression(id, &block.expression);
740                if let Some(val) = &block.value {
741                    self.build_expression(id, val);
742                }
743                if let Some(err) = &block.error {
744                    self.build_expression(id, err);
745                }
746                for f in [&block.pending, &block.then, &block.catch].into_iter().flatten() {
747                    self.build_fragment(id, f);
748                }
749            }
750            ModernNode::KeyBlock(block) => {
751                let id = self.alloc(
752                    Some(parent),
753                    NodeKind::KeyBlock,
754                    block.start as u32,
755                    block.end as u32,
756                );
757                self.build_expression(id, &block.expression);
758                self.build_fragment(id, &block.fragment);
759            }
760            ModernNode::SnippetBlock(block) => {
761                let name = block
762                    .expression
763                    .identifier_name()
764                    .unwrap_or_else(|| Arc::from(""));
765                let id = self.alloc(
766                    Some(parent),
767                    NodeKind::SnippetBlock { name },
768                    block.start as u32,
769                    block.end as u32,
770                );
771                self.build_expression(id, &block.expression);
772                for param in block.parameters.iter() {
773                    self.build_expression(id, param);
774                }
775                self.build_fragment(id, &block.body);
776            }
777            // Elements
778            ModernNode::RegularElement(el) => {
779                self.build_element(parent, NodeKind::RegularElement { name: el.name.clone() }, el);
780            }
781            ModernNode::Component(el) => {
782                self.build_element(parent, NodeKind::Component { name: el.name.clone() }, el);
783            }
784            ModernNode::SlotElement(el) => {
785                self.build_element(parent, NodeKind::SlotElement { name: el.name.clone() }, el);
786            }
787            ModernNode::SvelteHead(el) => self.build_element(parent, NodeKind::SvelteHead, el),
788            ModernNode::SvelteBody(el) => self.build_element(parent, NodeKind::SvelteBody, el),
789            ModernNode::SvelteWindow(el) => self.build_element(parent, NodeKind::SvelteWindow, el),
790            ModernNode::SvelteDocument(el) => {
791                self.build_element(parent, NodeKind::SvelteDocument, el);
792            }
793            ModernNode::SvelteComponent(el) => {
794                self.build_element(parent, NodeKind::SvelteComponent, el);
795            }
796            ModernNode::SvelteElement(el) => {
797                self.build_element(parent, NodeKind::SvelteElement, el);
798            }
799            ModernNode::SvelteSelf(el) => self.build_element(parent, NodeKind::SvelteSelf, el),
800            ModernNode::SvelteFragment(el) => {
801                self.build_element(parent, NodeKind::SvelteFragment, el);
802            }
803            ModernNode::SvelteBoundary(el) => {
804                self.build_element(parent, NodeKind::SvelteBoundary, el);
805            }
806            ModernNode::TitleElement(el) => {
807                self.build_element(parent, NodeKind::TitleElement, el);
808            }
809        }
810    }
811
812    fn build_if_block(&mut self, parent: NodeId, block: &modern::IfBlock) {
813        let id = self.alloc(
814            Some(parent),
815            NodeKind::IfBlock { elseif: block.elseif },
816            block.start as u32,
817            block.end as u32,
818        );
819        self.build_expression(id, &block.test);
820        self.build_fragment(id, &block.consequent);
821        if let Some(alt) = &block.alternate {
822            match alt.as_ref() {
823                modern::Alternate::Fragment(f) => {
824                    let alt_start = f.nodes.first().map(|n| n.start()).unwrap_or(0) as u32;
825                    let alt_end = f.nodes.last().map(|n| n.end()).unwrap_or(0) as u32;
826                    let alt_id = self.alloc(Some(id), NodeKind::Alternate, alt_start, alt_end);
827                    self.build_fragment(alt_id, f);
828                }
829                modern::Alternate::IfBlock(nested) => {
830                    self.build_if_block(id, nested);
831                }
832            }
833        }
834    }
835
836    fn build_element<E: modern::Element>(&mut self, parent: NodeId, kind: NodeKind, el: &E) {
837        let id = self.alloc(Some(parent), kind, el.start() as u32, el.end() as u32);
838        for attr in el.attributes() {
839            self.build_attribute(id, attr);
840        }
841        self.build_fragment(id, el.fragment());
842    }
843
844    fn build_attribute(&mut self, parent: NodeId, attr: &modern::Attribute) {
845        match attr {
846            modern::Attribute::Attribute(a) => {
847                let id = self.alloc(
848                    Some(parent),
849                    NodeKind::Attribute { name: a.name.clone() },
850                    a.start as u32,
851                    a.end as u32,
852                );
853                self.build_attribute_values(id, &a.value);
854            }
855            modern::Attribute::SpreadAttribute(s) => {
856                let id = self.alloc(
857                    Some(parent),
858                    NodeKind::Attribute { name: Arc::from("...") },
859                    s.start as u32,
860                    s.end as u32,
861                );
862                self.build_expression(id, &s.expression);
863            }
864            modern::Attribute::BindDirective(d)
865            | modern::Attribute::OnDirective(d)
866            | modern::Attribute::ClassDirective(d)
867            | modern::Attribute::LetDirective(d)
868            | modern::Attribute::AnimateDirective(d)
869            | modern::Attribute::UseDirective(d) => {
870                let id = self.alloc(
871                    Some(parent),
872                    NodeKind::Attribute { name: d.name.clone() },
873                    d.start as u32,
874                    d.end as u32,
875                );
876                self.build_expression(id, &d.expression);
877            }
878            modern::Attribute::StyleDirective(d) => {
879                let id = self.alloc(
880                    Some(parent),
881                    NodeKind::Attribute { name: d.name.clone() },
882                    d.start as u32,
883                    d.end as u32,
884                );
885                self.build_attribute_values(id, &d.value);
886            }
887            modern::Attribute::TransitionDirective(d) => {
888                let id = self.alloc(
889                    Some(parent),
890                    NodeKind::Attribute { name: d.name.clone() },
891                    d.start as u32,
892                    d.end as u32,
893                );
894                self.build_expression(id, &d.expression);
895            }
896            modern::Attribute::AttachTag(a) => {
897                let id = self.alloc(
898                    Some(parent),
899                    NodeKind::Attribute { name: Arc::from("@attach") },
900                    a.start as u32,
901                    a.end as u32,
902                );
903                self.build_expression(id, &a.expression);
904            }
905        }
906    }
907
908    fn build_attribute_values(&mut self, parent: NodeId, value: &modern::AttributeValueKind) {
909        match value {
910            modern::AttributeValueKind::Boolean(_) => {}
911            modern::AttributeValueKind::ExpressionTag(tag) => {
912                self.build_expression(parent, &tag.expression);
913            }
914            modern::AttributeValueKind::Values(values) => {
915                for val in values.iter() {
916                    match val {
917                        modern::AttributeValue::ExpressionTag(tag) => {
918                            self.build_expression(parent, &tag.expression);
919                        }
920                        modern::AttributeValue::Text(_) => {}
921                    }
922                }
923            }
924        }
925    }
926
927    fn build_expression(&mut self, parent: NodeId, expr: &modern::Expression) {
928        let handle = match &expr.node {
929            Some(modern::JsNodeHandle::Expression(arc)) => Some(arc.clone()),
930            _ => None,
931        };
932
933        self.alloc(
934            Some(parent),
935            NodeKind::Expression { handle },
936            expr.start as u32,
937            expr.end as u32,
938        );
939    }
940}
941
942#[cfg(test)]
943mod tests {
944    use super::*;
945
946    #[test]
947    fn parse_simple_element() {
948        let ast = SvelteAst::parse("<div>hello</div>").unwrap();
949        assert!(!ast.is_empty());
950        assert!(matches!(ast.kind(ast.root()), NodeKind::Root));
951
952        let children = ast.children(ast.root());
953        assert!(!children.is_empty());
954    }
955
956    #[test]
957    fn parse_with_expression() {
958        let ast = SvelteAst::parse("<p>{count}</p>").unwrap();
959        let root = ast.root();
960
961        // Find the expression tag by walking descendants
962        let expr_tag = ast
963            .descendants(root)
964            .find(|&id| matches!(ast.kind(id), NodeKind::ExpressionTag));
965        assert!(expr_tag.is_some(), "should find ExpressionTag");
966    }
967
968    #[test]
969    fn parent_pointers_work() {
970        let ast = SvelteAst::parse("<div><span>hi</span></div>").unwrap();
971        let root = ast.root();
972
973        // Root has no parent
974        assert!(ast.parent(root).is_none());
975
976        // Every non-root node has a parent
977        for &id in ast.children(root) {
978            assert_eq!(ast.parent(id), Some(root));
979        }
980    }
981
982    #[test]
983    fn ancestors_traverse_upward() {
984        let ast = SvelteAst::parse("<div><span>hi</span></div>").unwrap();
985        let root = ast.root();
986
987        // Find deepest node
988        let text = ast
989            .descendants(root)
990            .find(|&id| matches!(ast.kind(id), NodeKind::Text { .. }));
991        assert!(text.is_some());
992
993        let ancestors: Vec<_> = ast.ancestors(text.unwrap()).collect();
994        assert!(ancestors.len() >= 2); // span, div, root
995        assert!(ancestors.contains(&root));
996    }
997
998    #[test]
999    fn innermost_at_offset_finds_deepest() {
1000        let ast = SvelteAst::parse("<div>hello</div>").unwrap();
1001        let node = ast.innermost_at_offset(6);
1002        assert!(node.is_some());
1003        assert!(matches!(ast.kind(node.unwrap()), NodeKind::Text { .. }));
1004    }
1005
1006    #[test]
1007    fn node_text_returns_source_slice() {
1008        let ast = SvelteAst::parse("<div>hello</div>").unwrap();
1009        let text_node = ast
1010            .descendants(ast.root())
1011            .find(|&id| matches!(ast.kind(id), NodeKind::Text { .. }))
1012            .unwrap();
1013        assert_eq!(ast.node_text(text_node), "hello");
1014    }
1015
1016    #[test]
1017    fn edit_updates_ast() {
1018        let mut ast = SvelteAst::parse("<p>hello</p>").unwrap();
1019        let result = ast.edit(TextEdit {
1020            range: 3..8,
1021            replacement: "world".to_string(),
1022        });
1023        assert!(result.is_ok());
1024        assert_eq!(ast.source(), "<p>world</p>");
1025    }
1026
1027    #[test]
1028    fn siblings_excludes_self() {
1029        let ast = SvelteAst::parse("<div><a/><b/><c/></div>").unwrap();
1030        let div = ast.children(ast.root())[0];
1031        let div_children = ast.children(div);
1032
1033        if div_children.len() >= 2 {
1034            let first = div_children[0];
1035            let sibs: Vec<_> = ast.siblings(first).collect();
1036            assert!(!sibs.contains(&first));
1037            assert!(!sibs.is_empty());
1038        }
1039    }
1040
1041    #[test]
1042    fn if_block_structure() {
1043        let ast = SvelteAst::parse("{#if condition}<p>yes</p>{:else}<p>no</p>{/if}").unwrap();
1044        let if_block = ast
1045            .descendants(ast.root())
1046            .find(|&id| matches!(ast.kind(id), NodeKind::IfBlock { .. }));
1047        assert!(if_block.is_some(), "should find IfBlock");
1048
1049        let if_id = if_block.unwrap();
1050        assert!(ast.is_block(if_id));
1051        assert!(!ast.is_element(if_id));
1052
1053        // Should have expression, element, and alternate children
1054        let children = ast.children(if_id);
1055        assert!(children.len() >= 2, "IfBlock should have children: {:?}", children.len());
1056
1057        // Check for Expression child (the test condition)
1058        let has_expr = children.iter().any(|&id| matches!(ast.kind(id), NodeKind::Expression { .. }));
1059        assert!(has_expr, "IfBlock should have an Expression child for the test");
1060    }
1061
1062    #[test]
1063    fn each_block_structure() {
1064        let ast = SvelteAst::parse("{#each items as item}<p>{item}</p>{/each}").unwrap();
1065        let each = ast
1066            .descendants(ast.root())
1067            .find(|&id| matches!(ast.kind(id), NodeKind::EachBlock));
1068        assert!(each.is_some(), "should find EachBlock");
1069        assert!(ast.is_block(each.unwrap()));
1070    }
1071
1072    #[test]
1073    fn script_contains_program() {
1074        let ast = SvelteAst::parse("<script>let count = 0;</script><p>{count}</p>").unwrap();
1075        let script = ast
1076            .descendants(ast.root())
1077            .find(|&id| matches!(ast.kind(id), NodeKind::Script { .. }));
1078        assert!(script.is_some(), "should find Script");
1079
1080        let prog = ast.js_program(script.unwrap());
1081        assert!(prog.is_some(), "Script should have a JS program");
1082        assert_eq!(prog.unwrap().program().body.len(), 1);
1083    }
1084
1085    #[test]
1086    fn expression_tag_has_js_handle() {
1087        let ast = SvelteAst::parse("<p>{count + 1}</p>").unwrap();
1088        let expr = ast
1089            .descendants(ast.root())
1090            .find(|&id| matches!(ast.kind(id), NodeKind::Expression { .. }));
1091        assert!(expr.is_some(), "should find Expression node");
1092
1093        let js = ast.js_expression(expr.unwrap());
1094        assert!(js.is_some(), "Expression should have OXC handle");
1095    }
1096
1097    #[test]
1098    fn attribute_expressions_are_traversed() {
1099        let ast = SvelteAst::parse("<button on:click={handler}>go</button>").unwrap();
1100        let attr = ast
1101            .descendants(ast.root())
1102            .find(|&id| matches!(ast.kind(id), NodeKind::Attribute { .. }));
1103        assert!(attr.is_some(), "should find Attribute");
1104
1105        // The directive attribute should have an Expression child
1106        let attr_children = ast.children(attr.unwrap());
1107        let has_expr = attr_children.iter().any(|&id| matches!(ast.kind(id), NodeKind::Expression { .. }));
1108        assert!(has_expr, "Directive attribute should have Expression child");
1109    }
1110
1111    #[test]
1112    fn spread_attribute_has_expression() {
1113        let ast = SvelteAst::parse("<div {...props}>hi</div>").unwrap();
1114        let spread = ast
1115            .descendants(ast.root())
1116            .find(|&id| {
1117                matches!(ast.kind(id), NodeKind::Attribute { name } if name.as_ref() == "...")
1118            });
1119        assert!(spread.is_some(), "should find spread attribute");
1120
1121        let children = ast.children(spread.unwrap());
1122        let has_expr = children.iter().any(|&id| matches!(ast.kind(id), NodeKind::Expression { .. }));
1123        assert!(has_expr, "Spread attribute should have Expression child");
1124    }
1125
1126    #[test]
1127    fn nodes_in_range_finds_overlapping() {
1128        let ast = SvelteAst::parse("<div><p>hello</p><span>world</span></div>").unwrap();
1129        // Range covering "hello" area
1130        let nodes = ast.nodes_in_range(8, 13);
1131        assert!(!nodes.is_empty(), "should find nodes in range");
1132    }
1133
1134    #[test]
1135    fn is_element_and_is_block_classify_correctly() {
1136        let ast = SvelteAst::parse("<div>{#if x}<span/>{/if}</div>").unwrap();
1137        for id in ast.descendants(ast.root()) {
1138            match ast.kind(id) {
1139                NodeKind::RegularElement { .. } => assert!(ast.is_element(id)),
1140                NodeKind::IfBlock { .. } => {
1141                    assert!(ast.is_block(id));
1142                    assert!(!ast.is_element(id));
1143                }
1144                _ => {}
1145            }
1146        }
1147    }
1148
1149    #[test]
1150    fn complex_template() {
1151        let src = r#"<script>
1152  let items = [1, 2, 3];
1153  let show = true;
1154</script>
1155
1156{#if show}
1157  {#each items as item}
1158    <p>{item}</p>
1159  {/each}
1160{/if}
1161"#;
1162        let ast = SvelteAst::parse(src).unwrap();
1163        assert!(ast.len() > 10, "complex template should have many nodes");
1164
1165        // Check we have all expected node types
1166        let descendants = || ast.descendants(ast.root());
1167        assert!(descendants().any(|id| matches!(ast.kind(id), NodeKind::Script { .. })), "should have Script");
1168        assert!(descendants().any(|id| matches!(ast.kind(id), NodeKind::IfBlock { .. })), "should have IfBlock");
1169        assert!(descendants().any(|id| matches!(ast.kind(id), NodeKind::EachBlock)), "should have EachBlock");
1170        assert!(descendants().any(|id| matches!(ast.kind(id), NodeKind::ExpressionTag)), "should have ExpressionTag");
1171        assert!(descendants().any(|id| matches!(ast.kind(id), NodeKind::RegularElement { .. })), "should have RegularElement");
1172    }
1173
1174    #[test]
1175    fn element_name_returns_tag_name() {
1176        let ast = SvelteAst::parse("<div><MyComponent/></div>").unwrap();
1177        let div = ast
1178            .descendants(ast.root())
1179            .find(|&id| matches!(ast.kind(id), NodeKind::RegularElement { name } if name.as_ref() == "div"))
1180            .unwrap();
1181        assert_eq!(ast.element_name(div), Some("div"));
1182
1183        let comp = ast
1184            .descendants(ast.root())
1185            .find(|&id| matches!(ast.kind(id), NodeKind::Component { .. }))
1186            .unwrap();
1187        assert_eq!(ast.element_name(comp), Some("MyComponent"));
1188
1189        // Non-element returns None
1190        assert_eq!(ast.element_name(ast.root()), None);
1191    }
1192
1193    #[test]
1194    fn next_and_prev_sibling() {
1195        let ast = SvelteAst::parse("<div><a/><b/><c/></div>").unwrap();
1196        let div = ast.children(ast.root())[0];
1197        let children = ast.children(div);
1198        assert!(children.len() >= 3);
1199
1200        let a = children[0];
1201        let b = children[1];
1202        let c = children[2];
1203
1204        assert_eq!(ast.next_sibling(a), Some(b));
1205        assert_eq!(ast.next_sibling(b), Some(c));
1206        assert_eq!(ast.next_sibling(c), None);
1207
1208        assert_eq!(ast.prev_sibling(a), None);
1209        assert_eq!(ast.prev_sibling(b), Some(a));
1210        assert_eq!(ast.prev_sibling(c), Some(b));
1211    }
1212
1213    #[test]
1214    fn depth_counts_ancestors() {
1215        let ast = SvelteAst::parse("<div><span>hi</span></div>").unwrap();
1216        assert_eq!(ast.depth(ast.root()), 0);
1217
1218        let text = ast
1219            .descendants(ast.root())
1220            .find(|&id| matches!(ast.kind(id), NodeKind::Text { .. }))
1221            .unwrap();
1222        assert!(ast.depth(text) >= 2);
1223    }
1224
1225    #[test]
1226    fn node_kind_display() {
1227        let ast = SvelteAst::parse("<div class='x'>hi</div>").unwrap();
1228        let div = ast
1229            .descendants(ast.root())
1230            .find(|&id| matches!(ast.kind(id), NodeKind::RegularElement { .. }))
1231            .unwrap();
1232        assert_eq!(format!("{}", ast.kind(div)), "RegularElement <div>");
1233
1234        let attr = ast
1235            .descendants(ast.root())
1236            .find(|&id| matches!(ast.kind(id), NodeKind::Attribute { .. }))
1237            .unwrap();
1238        assert_eq!(format!("{}", ast.kind(attr)), "Attribute class");
1239    }
1240
1241    #[test]
1242    fn node_kind_name() {
1243        assert_eq!(NodeKind::Root.name(), "Root");
1244        assert_eq!(NodeKind::ExpressionTag.name(), "ExpressionTag");
1245        assert_eq!(NodeKind::EachBlock.name(), "EachBlock");
1246    }
1247}