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::{ParsedJsExpression, ParsedJsProgram};
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<ParsedJsProgram> },
141    StyleSheet,
142    // JS expressions (leaf references into OXC arena)
143    Expression { handle: Option<Arc<ParsedJsExpression>> },
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<&ParsedJsExpression> {
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<&ParsedJsProgram> {
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 fragment in [&block.pending, &block.then, &block.catch] {
747                    if let Some(f) = fragment {
748                        self.build_fragment(id, f);
749                    }
750                }
751            }
752            ModernNode::KeyBlock(block) => {
753                let id = self.alloc(
754                    Some(parent),
755                    NodeKind::KeyBlock,
756                    block.start as u32,
757                    block.end as u32,
758                );
759                self.build_expression(id, &block.expression);
760                self.build_fragment(id, &block.fragment);
761            }
762            ModernNode::SnippetBlock(block) => {
763                let name = block
764                    .expression
765                    .identifier_name()
766                    .unwrap_or_else(|| Arc::from(""));
767                let id = self.alloc(
768                    Some(parent),
769                    NodeKind::SnippetBlock { name },
770                    block.start as u32,
771                    block.end as u32,
772                );
773                self.build_expression(id, &block.expression);
774                for param in block.parameters.iter() {
775                    self.build_expression(id, param);
776                }
777                self.build_fragment(id, &block.body);
778            }
779            // Elements
780            ModernNode::RegularElement(el) => {
781                self.build_element(parent, NodeKind::RegularElement { name: el.name.clone() }, el);
782            }
783            ModernNode::Component(el) => {
784                self.build_element(parent, NodeKind::Component { name: el.name.clone() }, el);
785            }
786            ModernNode::SlotElement(el) => {
787                self.build_element(parent, NodeKind::SlotElement { name: el.name.clone() }, el);
788            }
789            ModernNode::SvelteHead(el) => self.build_element(parent, NodeKind::SvelteHead, el),
790            ModernNode::SvelteBody(el) => self.build_element(parent, NodeKind::SvelteBody, el),
791            ModernNode::SvelteWindow(el) => self.build_element(parent, NodeKind::SvelteWindow, el),
792            ModernNode::SvelteDocument(el) => {
793                self.build_element(parent, NodeKind::SvelteDocument, el);
794            }
795            ModernNode::SvelteComponent(el) => {
796                self.build_element(parent, NodeKind::SvelteComponent, el);
797            }
798            ModernNode::SvelteElement(el) => {
799                self.build_element(parent, NodeKind::SvelteElement, el);
800            }
801            ModernNode::SvelteSelf(el) => self.build_element(parent, NodeKind::SvelteSelf, el),
802            ModernNode::SvelteFragment(el) => {
803                self.build_element(parent, NodeKind::SvelteFragment, el);
804            }
805            ModernNode::SvelteBoundary(el) => {
806                self.build_element(parent, NodeKind::SvelteBoundary, el);
807            }
808            ModernNode::TitleElement(el) => {
809                self.build_element(parent, NodeKind::TitleElement, el);
810            }
811        }
812    }
813
814    fn build_if_block(&mut self, parent: NodeId, block: &modern::IfBlock) {
815        let id = self.alloc(
816            Some(parent),
817            NodeKind::IfBlock { elseif: block.elseif },
818            block.start as u32,
819            block.end as u32,
820        );
821        self.build_expression(id, &block.test);
822        self.build_fragment(id, &block.consequent);
823        if let Some(alt) = &block.alternate {
824            match alt.as_ref() {
825                modern::Alternate::Fragment(f) => {
826                    let alt_start = f.nodes.first().map(|n| n.start()).unwrap_or(0) as u32;
827                    let alt_end = f.nodes.last().map(|n| n.end()).unwrap_or(0) as u32;
828                    let alt_id = self.alloc(Some(id), NodeKind::Alternate, alt_start, alt_end);
829                    self.build_fragment(alt_id, f);
830                }
831                modern::Alternate::IfBlock(nested) => {
832                    self.build_if_block(id, nested);
833                }
834            }
835        }
836    }
837
838    fn build_element<E: modern::Element>(&mut self, parent: NodeId, kind: NodeKind, el: &E) {
839        let id = self.alloc(Some(parent), kind, el.start() as u32, el.end() as u32);
840        for attr in el.attributes() {
841            self.build_attribute(id, attr);
842        }
843        self.build_fragment(id, el.fragment());
844    }
845
846    fn build_attribute(&mut self, parent: NodeId, attr: &modern::Attribute) {
847        match attr {
848            modern::Attribute::Attribute(a) => {
849                let id = self.alloc(
850                    Some(parent),
851                    NodeKind::Attribute { name: a.name.clone() },
852                    a.start as u32,
853                    a.end as u32,
854                );
855                self.build_attribute_values(id, &a.value);
856            }
857            modern::Attribute::SpreadAttribute(s) => {
858                let id = self.alloc(
859                    Some(parent),
860                    NodeKind::Attribute { name: Arc::from("...") },
861                    s.start as u32,
862                    s.end as u32,
863                );
864                self.build_expression(id, &s.expression);
865            }
866            modern::Attribute::BindDirective(d)
867            | modern::Attribute::OnDirective(d)
868            | modern::Attribute::ClassDirective(d)
869            | modern::Attribute::LetDirective(d)
870            | modern::Attribute::AnimateDirective(d)
871            | modern::Attribute::UseDirective(d) => {
872                let id = self.alloc(
873                    Some(parent),
874                    NodeKind::Attribute { name: d.name.clone() },
875                    d.start as u32,
876                    d.end as u32,
877                );
878                self.build_expression(id, &d.expression);
879            }
880            modern::Attribute::StyleDirective(d) => {
881                let id = self.alloc(
882                    Some(parent),
883                    NodeKind::Attribute { name: d.name.clone() },
884                    d.start as u32,
885                    d.end as u32,
886                );
887                self.build_attribute_values(id, &d.value);
888            }
889            modern::Attribute::TransitionDirective(d) => {
890                let id = self.alloc(
891                    Some(parent),
892                    NodeKind::Attribute { name: d.name.clone() },
893                    d.start as u32,
894                    d.end as u32,
895                );
896                self.build_expression(id, &d.expression);
897            }
898            modern::Attribute::AttachTag(a) => {
899                let id = self.alloc(
900                    Some(parent),
901                    NodeKind::Attribute { name: Arc::from("@attach") },
902                    a.start as u32,
903                    a.end as u32,
904                );
905                self.build_expression(id, &a.expression);
906            }
907        }
908    }
909
910    fn build_attribute_values(&mut self, parent: NodeId, value: &modern::AttributeValueList) {
911        match value {
912            modern::AttributeValueList::Boolean(_) => {}
913            modern::AttributeValueList::ExpressionTag(tag) => {
914                self.build_expression(parent, &tag.expression);
915            }
916            modern::AttributeValueList::Values(values) => {
917                for val in values.iter() {
918                    match val {
919                        modern::AttributeValue::ExpressionTag(tag) => {
920                            self.build_expression(parent, &tag.expression);
921                        }
922                        modern::AttributeValue::Text(_) => {}
923                    }
924                }
925            }
926        }
927    }
928
929    fn build_expression(&mut self, parent: NodeId, expr: &modern::Expression) {
930        let handle = match &expr.node {
931            Some(modern::JsNodeHandle::Expression(arc)) => Some(arc.clone()),
932            _ => None,
933        };
934
935        self.alloc(
936            Some(parent),
937            NodeKind::Expression { handle },
938            expr.start as u32,
939            expr.end as u32,
940        );
941    }
942}
943
944#[cfg(test)]
945mod tests {
946    use super::*;
947
948    #[test]
949    fn parse_simple_element() {
950        let ast = SvelteAst::parse("<div>hello</div>").unwrap();
951        assert!(!ast.is_empty());
952        assert!(matches!(ast.kind(ast.root()), NodeKind::Root));
953
954        let children = ast.children(ast.root());
955        assert!(!children.is_empty());
956    }
957
958    #[test]
959    fn parse_with_expression() {
960        let ast = SvelteAst::parse("<p>{count}</p>").unwrap();
961        let root = ast.root();
962
963        // Find the expression tag by walking descendants
964        let expr_tag = ast
965            .descendants(root)
966            .find(|&id| matches!(ast.kind(id), NodeKind::ExpressionTag));
967        assert!(expr_tag.is_some(), "should find ExpressionTag");
968    }
969
970    #[test]
971    fn parent_pointers_work() {
972        let ast = SvelteAst::parse("<div><span>hi</span></div>").unwrap();
973        let root = ast.root();
974
975        // Root has no parent
976        assert!(ast.parent(root).is_none());
977
978        // Every non-root node has a parent
979        for &id in ast.children(root) {
980            assert_eq!(ast.parent(id), Some(root));
981        }
982    }
983
984    #[test]
985    fn ancestors_traverse_upward() {
986        let ast = SvelteAst::parse("<div><span>hi</span></div>").unwrap();
987        let root = ast.root();
988
989        // Find deepest node
990        let text = ast
991            .descendants(root)
992            .find(|&id| matches!(ast.kind(id), NodeKind::Text { .. }));
993        assert!(text.is_some());
994
995        let ancestors: Vec<_> = ast.ancestors(text.unwrap()).collect();
996        assert!(ancestors.len() >= 2); // span, div, root
997        assert!(ancestors.contains(&root));
998    }
999
1000    #[test]
1001    fn innermost_at_offset_finds_deepest() {
1002        let ast = SvelteAst::parse("<div>hello</div>").unwrap();
1003        let node = ast.innermost_at_offset(6);
1004        assert!(node.is_some());
1005        assert!(matches!(ast.kind(node.unwrap()), NodeKind::Text { .. }));
1006    }
1007
1008    #[test]
1009    fn node_text_returns_source_slice() {
1010        let ast = SvelteAst::parse("<div>hello</div>").unwrap();
1011        let text_node = ast
1012            .descendants(ast.root())
1013            .find(|&id| matches!(ast.kind(id), NodeKind::Text { .. }))
1014            .unwrap();
1015        assert_eq!(ast.node_text(text_node), "hello");
1016    }
1017
1018    #[test]
1019    fn edit_updates_ast() {
1020        let mut ast = SvelteAst::parse("<p>hello</p>").unwrap();
1021        let result = ast.edit(TextEdit {
1022            range: 3..8,
1023            replacement: "world".to_string(),
1024        });
1025        assert!(result.is_ok());
1026        assert_eq!(ast.source(), "<p>world</p>");
1027    }
1028
1029    #[test]
1030    fn siblings_excludes_self() {
1031        let ast = SvelteAst::parse("<div><a/><b/><c/></div>").unwrap();
1032        let div = ast.children(ast.root())[0];
1033        let div_children = ast.children(div);
1034
1035        if div_children.len() >= 2 {
1036            let first = div_children[0];
1037            let sibs: Vec<_> = ast.siblings(first).collect();
1038            assert!(!sibs.contains(&first));
1039            assert!(!sibs.is_empty());
1040        }
1041    }
1042
1043    #[test]
1044    fn if_block_structure() {
1045        let ast = SvelteAst::parse("{#if condition}<p>yes</p>{:else}<p>no</p>{/if}").unwrap();
1046        let if_block = ast
1047            .descendants(ast.root())
1048            .find(|&id| matches!(ast.kind(id), NodeKind::IfBlock { .. }));
1049        assert!(if_block.is_some(), "should find IfBlock");
1050
1051        let if_id = if_block.unwrap();
1052        assert!(ast.is_block(if_id));
1053        assert!(!ast.is_element(if_id));
1054
1055        // Should have expression, element, and alternate children
1056        let children = ast.children(if_id);
1057        assert!(children.len() >= 2, "IfBlock should have children: {:?}", children.len());
1058
1059        // Check for Expression child (the test condition)
1060        let has_expr = children.iter().any(|&id| matches!(ast.kind(id), NodeKind::Expression { .. }));
1061        assert!(has_expr, "IfBlock should have an Expression child for the test");
1062    }
1063
1064    #[test]
1065    fn each_block_structure() {
1066        let ast = SvelteAst::parse("{#each items as item}<p>{item}</p>{/each}").unwrap();
1067        let each = ast
1068            .descendants(ast.root())
1069            .find(|&id| matches!(ast.kind(id), NodeKind::EachBlock));
1070        assert!(each.is_some(), "should find EachBlock");
1071        assert!(ast.is_block(each.unwrap()));
1072    }
1073
1074    #[test]
1075    fn script_contains_program() {
1076        let ast = SvelteAst::parse("<script>let count = 0;</script><p>{count}</p>").unwrap();
1077        let script = ast
1078            .descendants(ast.root())
1079            .find(|&id| matches!(ast.kind(id), NodeKind::Script { .. }));
1080        assert!(script.is_some(), "should find Script");
1081
1082        let prog = ast.js_program(script.unwrap());
1083        assert!(prog.is_some(), "Script should have a JS program");
1084        assert_eq!(prog.unwrap().program().body.len(), 1);
1085    }
1086
1087    #[test]
1088    fn expression_tag_has_js_handle() {
1089        let ast = SvelteAst::parse("<p>{count + 1}</p>").unwrap();
1090        let expr = ast
1091            .descendants(ast.root())
1092            .find(|&id| matches!(ast.kind(id), NodeKind::Expression { .. }));
1093        assert!(expr.is_some(), "should find Expression node");
1094
1095        let js = ast.js_expression(expr.unwrap());
1096        assert!(js.is_some(), "Expression should have OXC handle");
1097    }
1098
1099    #[test]
1100    fn attribute_expressions_are_traversed() {
1101        let ast = SvelteAst::parse("<button on:click={handler}>go</button>").unwrap();
1102        let attr = ast
1103            .descendants(ast.root())
1104            .find(|&id| matches!(ast.kind(id), NodeKind::Attribute { .. }));
1105        assert!(attr.is_some(), "should find Attribute");
1106
1107        // The directive attribute should have an Expression child
1108        let attr_children = ast.children(attr.unwrap());
1109        let has_expr = attr_children.iter().any(|&id| matches!(ast.kind(id), NodeKind::Expression { .. }));
1110        assert!(has_expr, "Directive attribute should have Expression child");
1111    }
1112
1113    #[test]
1114    fn spread_attribute_has_expression() {
1115        let ast = SvelteAst::parse("<div {...props}>hi</div>").unwrap();
1116        let spread = ast
1117            .descendants(ast.root())
1118            .find(|&id| {
1119                matches!(ast.kind(id), NodeKind::Attribute { name } if name.as_ref() == "...")
1120            });
1121        assert!(spread.is_some(), "should find spread attribute");
1122
1123        let children = ast.children(spread.unwrap());
1124        let has_expr = children.iter().any(|&id| matches!(ast.kind(id), NodeKind::Expression { .. }));
1125        assert!(has_expr, "Spread attribute should have Expression child");
1126    }
1127
1128    #[test]
1129    fn nodes_in_range_finds_overlapping() {
1130        let ast = SvelteAst::parse("<div><p>hello</p><span>world</span></div>").unwrap();
1131        // Range covering "hello" area
1132        let nodes = ast.nodes_in_range(8, 13);
1133        assert!(!nodes.is_empty(), "should find nodes in range");
1134    }
1135
1136    #[test]
1137    fn is_element_and_is_block_classify_correctly() {
1138        let ast = SvelteAst::parse("<div>{#if x}<span/>{/if}</div>").unwrap();
1139        for id in ast.descendants(ast.root()) {
1140            match ast.kind(id) {
1141                NodeKind::RegularElement { .. } => assert!(ast.is_element(id)),
1142                NodeKind::IfBlock { .. } => {
1143                    assert!(ast.is_block(id));
1144                    assert!(!ast.is_element(id));
1145                }
1146                _ => {}
1147            }
1148        }
1149    }
1150
1151    #[test]
1152    fn complex_template() {
1153        let src = r#"<script>
1154  let items = [1, 2, 3];
1155  let show = true;
1156</script>
1157
1158{#if show}
1159  {#each items as item}
1160    <p>{item}</p>
1161  {/each}
1162{/if}
1163"#;
1164        let ast = SvelteAst::parse(src).unwrap();
1165        assert!(ast.len() > 10, "complex template should have many nodes");
1166
1167        // Check we have all expected node types
1168        let descendants = || ast.descendants(ast.root());
1169        assert!(descendants().any(|id| matches!(ast.kind(id), NodeKind::Script { .. })), "should have Script");
1170        assert!(descendants().any(|id| matches!(ast.kind(id), NodeKind::IfBlock { .. })), "should have IfBlock");
1171        assert!(descendants().any(|id| matches!(ast.kind(id), NodeKind::EachBlock)), "should have EachBlock");
1172        assert!(descendants().any(|id| matches!(ast.kind(id), NodeKind::ExpressionTag)), "should have ExpressionTag");
1173        assert!(descendants().any(|id| matches!(ast.kind(id), NodeKind::RegularElement { .. })), "should have RegularElement");
1174    }
1175
1176    #[test]
1177    fn element_name_returns_tag_name() {
1178        let ast = SvelteAst::parse("<div><MyComponent/></div>").unwrap();
1179        let div = ast
1180            .descendants(ast.root())
1181            .find(|&id| matches!(ast.kind(id), NodeKind::RegularElement { name } if name.as_ref() == "div"))
1182            .unwrap();
1183        assert_eq!(ast.element_name(div), Some("div"));
1184
1185        let comp = ast
1186            .descendants(ast.root())
1187            .find(|&id| matches!(ast.kind(id), NodeKind::Component { .. }))
1188            .unwrap();
1189        assert_eq!(ast.element_name(comp), Some("MyComponent"));
1190
1191        // Non-element returns None
1192        assert_eq!(ast.element_name(ast.root()), None);
1193    }
1194
1195    #[test]
1196    fn next_and_prev_sibling() {
1197        let ast = SvelteAst::parse("<div><a/><b/><c/></div>").unwrap();
1198        let div = ast.children(ast.root())[0];
1199        let children = ast.children(div);
1200        assert!(children.len() >= 3);
1201
1202        let a = children[0];
1203        let b = children[1];
1204        let c = children[2];
1205
1206        assert_eq!(ast.next_sibling(a), Some(b));
1207        assert_eq!(ast.next_sibling(b), Some(c));
1208        assert_eq!(ast.next_sibling(c), None);
1209
1210        assert_eq!(ast.prev_sibling(a), None);
1211        assert_eq!(ast.prev_sibling(b), Some(a));
1212        assert_eq!(ast.prev_sibling(c), Some(b));
1213    }
1214
1215    #[test]
1216    fn depth_counts_ancestors() {
1217        let ast = SvelteAst::parse("<div><span>hi</span></div>").unwrap();
1218        assert_eq!(ast.depth(ast.root()), 0);
1219
1220        let text = ast
1221            .descendants(ast.root())
1222            .find(|&id| matches!(ast.kind(id), NodeKind::Text { .. }))
1223            .unwrap();
1224        assert!(ast.depth(text) >= 2);
1225    }
1226
1227    #[test]
1228    fn node_kind_display() {
1229        let ast = SvelteAst::parse("<div class='x'>hi</div>").unwrap();
1230        let div = ast
1231            .descendants(ast.root())
1232            .find(|&id| matches!(ast.kind(id), NodeKind::RegularElement { .. }))
1233            .unwrap();
1234        assert_eq!(format!("{}", ast.kind(div)), "RegularElement <div>");
1235
1236        let attr = ast
1237            .descendants(ast.root())
1238            .find(|&id| matches!(ast.kind(id), NodeKind::Attribute { .. }))
1239            .unwrap();
1240        assert_eq!(format!("{}", ast.kind(attr)), "Attribute class");
1241    }
1242
1243    #[test]
1244    fn node_kind_name() {
1245        assert_eq!(NodeKind::Root.name(), "Root");
1246        assert_eq!(NodeKind::ExpressionTag.name(), "ExpressionTag");
1247        assert_eq!(NodeKind::EachBlock.name(), "EachBlock");
1248    }
1249}