pub mod arena;
pub mod ast;
pub mod compat;
pub mod cst;
mod error;
pub(crate) mod estree;
pub mod js;
mod parse;
mod primitives;
mod source;
pub use cst::{
CstEdit, CstParser, Document, ExpressionCache, Language, ParsedDocument,
parse_svelte, parse_svelte_incremental,
Root, Element, TextNode, CommentNode, IfBlock, EachBlock, AwaitBlock,
KeyBlock, SnippetBlock, ExpressionTag, HtmlTag, ConstTag, DebugTag,
RenderTag, AttachTag, AttributeNode, AttributeValuePart, StartTag,
ElseClause, Alternate, TemplateNode, ChildIter, AttributeIter, classify_node,
};
pub use error::{CompileError, DiagnosticKind, LineColumn, SourcePosition};
pub use js::{JsExpression, JsProgram};
pub use parse::{
AttributeKind, ElementKind, ParseMode, ParseOptions, ParseCounters, ParseTimings,
SvelteElementKind, classify_attribute_name, classify_element_name,
find_matching_brace_close, is_component_name, is_custom_element_name,
is_valid_component_name, is_valid_element_name, is_void_element_name,
legacy_root_from_modern, line_column_at_offset, parse, parse_css,
parse_legacy_root_from_cst, parse_modern_css_nodes, parse_modern_expression_from_text,
parse_modern_expression_tag, parse_modern_root, parse_modern_root_incremental,
parse_modern_root_timed, parse_svelte_ignores,
read_parse_counters, reset_parse_counters,
};
pub use primitives::{BytePos, SourceId, Span};
pub use source::SourceText;
#[cfg(test)]
mod tests {
use std::sync::Arc;
use crate::ast::modern::Node;
use crate::cst::{CstEdit, parse_svelte};
use crate::parse_modern_root;
use crate::primitives::SourceId;
use crate::source::SourceText;
#[test]
fn modern_root_scripts_and_template_expressions_keep_oxc_handles() {
let root = parse_modern_root("<script>let count = 0;</script><button>{count + 1}</button>")
.expect("modern root should parse");
let instance = root.instance.as_ref().expect("instance script");
assert_eq!(instance.oxc_program().body.len(), 1);
let Node::RegularElement(element) = &root.fragment.nodes[0] else {
panic!("expected regular element");
};
let Node::ExpressionTag(tag) = &element.fragment.nodes[0] else {
panic!("expected expression tag");
};
assert!(tag.expression.parsed().is_some());
assert!(tag.expression.oxc_expression().is_some());
}
#[test]
fn incremental_parse_reuses_unchanged_script() {
let before = "<script>let count = 0;</script>\n<div>Hello</div>";
let after = "<script>let count = 0;</script>\n<div>World</div>";
let edit_start = "<script>let count = 0;</script>\n<div>".len();
let edit_old_end = "<script>let count = 0;</script>\n<div>Hello".len();
let old_root = parse_modern_root(before).expect("initial parse");
let before_src = SourceText::new(SourceId::new(1), before, None);
let old_cst = parse_svelte(before_src).expect("initial CST parse");
let edit = CstEdit::replace(before, edit_start, edit_old_end, "World");
let new_root = crate::parse_modern_root_incremental(after, before, &old_root, &old_cst, edit)
.expect("incremental parse");
let old_script = old_root.instance.as_ref().expect("old instance");
let new_script = new_root.instance.as_ref().expect("new instance");
assert!(
Arc::ptr_eq(&old_script.content, &new_script.content),
"unchanged script should be Arc-reused (same pointer)",
);
let el_node = new_root.fragment.nodes.iter().find(|n| matches!(n, Node::RegularElement(_)))
.expect("expected regular element in new root fragment");
let Node::RegularElement(new_el) = el_node else { unreachable!() };
let Node::Text(text) = &new_el.fragment.nodes[0] else {
panic!("expected text node in element fragment");
};
assert_eq!(text.data.as_ref(), "World");
}
#[test]
fn verify_tree_sitter_changed_ranges_are_structural_only() {
let before = "<div>A</div>";
let after = "<div>X</div>";
let before_src = SourceText::new(SourceId::new(50), before, None);
let old_cst = parse_svelte(before_src).expect("cst");
let mut edited = old_cst.clone_for_incremental();
let edit = CstEdit::replace(before, 5, 6, "X");
edited.apply_edit(edit);
let after_src = SourceText::new(SourceId::new(51), after, None);
let new_cst = crate::cst::parse_svelte_with_old_tree(after_src, &edited).expect("cst");
let ranges = new_cst.changed_ranges(&edited);
assert!(
ranges.is_empty(),
"tree-sitter changed_ranges should be empty for same-structure edit, got: {ranges:?}"
);
let before2 = "<div>A</div>";
let after2 = "<div>A</div><span>B</span>";
let before_src2 = SourceText::new(SourceId::new(52), before2, None);
let old_cst2 = parse_svelte(before_src2).expect("cst");
let mut edited2 = old_cst2.clone_for_incremental();
let edit2 = CstEdit::insert(before2, before2.len(), "<span>B</span>");
edited2.apply_edit(edit2);
let after_src2 = SourceText::new(SourceId::new(53), after2, None);
let new_cst2 = crate::cst::parse_svelte_with_old_tree(after_src2, &edited2).expect("cst");
let ranges2 = new_cst2.changed_ranges(&edited2);
assert!(
!ranges2.is_empty(),
"tree-sitter changed_ranges should be non-empty for structural edit"
);
}
#[test]
fn incremental_parse_reuses_unchanged_sibling_element() {
let before = "<div>A</div><span>B</span>";
let after = "<div>X</div><span>B</span>";
let edit_start = "<div>".len();
let edit_old_end = "<div>A".len();
let old_root = parse_modern_root(before).expect("initial parse");
let before_src = SourceText::new(SourceId::new(3), before, None);
let old_cst = parse_svelte(before_src).expect("initial CST parse");
let edit = CstEdit::replace(before, edit_start, edit_old_end, "X");
let new_root = crate::parse_modern_root_incremental(after, before, &old_root, &old_cst, edit)
.expect("incremental parse");
assert_eq!(new_root.fragment.nodes.len(), 2);
let Node::RegularElement(new_span) = &new_root.fragment.nodes[1] else {
panic!("expected span element");
};
assert_eq!(new_span.name.as_ref(), "span");
let Node::RegularElement(new_div) = &new_root.fragment.nodes[0] else {
panic!("expected div element");
};
let Node::Text(text) = &new_div.fragment.nodes[0] else {
panic!("expected text in div");
};
assert_eq!(text.data.as_ref(), "X");
}
}