1pub mod arena;
77pub mod ast;
78pub mod compat;
79pub mod cst;
80mod error;
81pub(crate) mod estree;
82pub mod js;
83mod parse;
84mod primitives;
85mod source;
86
87pub use cst::{
90 CstEdit, CstParser, Document, ExpressionCache, Language, ParsedDocument,
91 parse_svelte, parse_svelte_incremental,
92 Root, Element, TextNode, CommentNode, IfBlock, EachBlock, AwaitBlock,
94 KeyBlock, SnippetBlock, ExpressionTag, HtmlTag, ConstTag, DebugTag,
95 RenderTag, AttachTag, AttributeNode, AttributeValuePart, StartTag,
96 ElseClause, Alternate, TemplateNode, ChildIter, AttributeIter, classify_node,
97};
98
99pub use error::{CompileError, DiagnosticKind, LineColumn, SourcePosition};
102
103pub use js::{JsExpression, JsProgram};
106
107pub use parse::{
110 AttributeKind, ElementKind, ParseMode, ParseOptions, ParseCounters, ParseTimings,
111 SvelteElementKind, classify_attribute_name, classify_element_name,
112 find_matching_brace_close, is_component_name, is_custom_element_name,
113 is_valid_component_name, is_valid_element_name, is_void_element_name,
114 legacy_root_from_modern, line_column_at_offset, parse, parse_css,
115 parse_legacy_root_from_cst, parse_modern_css_nodes, parse_modern_expression_from_text,
116 parse_modern_expression_tag, parse_modern_root, parse_modern_root_incremental,
117 parse_modern_root_timed, parse_svelte_ignores,
118 read_parse_counters, reset_parse_counters,
119};
120
121pub use primitives::{BytePos, SourceId, Span};
124pub use source::SourceText;
125
126#[cfg(test)]
127mod tests {
128 use std::sync::Arc;
129
130 use crate::ast::modern::Node;
131 use crate::cst::{CstEdit, parse_svelte};
132 use crate::parse_modern_root;
133 use crate::primitives::SourceId;
134 use crate::source::SourceText;
135
136 #[test]
137 fn modern_root_scripts_and_template_expressions_keep_oxc_handles() {
138 let root = parse_modern_root("<script>let count = 0;</script><button>{count + 1}</button>")
139 .expect("modern root should parse");
140
141 let instance = root.instance.as_ref().expect("instance script");
142 assert_eq!(instance.oxc_program().body.len(), 1);
143
144 let Node::RegularElement(element) = &root.fragment.nodes[0] else {
145 panic!("expected regular element");
146 };
147 let Node::ExpressionTag(tag) = &element.fragment.nodes[0] else {
148 panic!("expected expression tag");
149 };
150 assert!(tag.expression.parsed().is_some());
151 assert!(tag.expression.oxc_expression().is_some());
152 }
153
154 #[test]
155 fn incremental_parse_reuses_unchanged_script() {
156 let before = "<script>let count = 0;</script>\n<div>Hello</div>";
157 let after = "<script>let count = 0;</script>\n<div>World</div>";
158 let edit_start = "<script>let count = 0;</script>\n<div>".len();
159 let edit_old_end = "<script>let count = 0;</script>\n<div>Hello".len();
160
161 let old_root = parse_modern_root(before).expect("initial parse");
162
163 let before_src = SourceText::new(SourceId::new(1), before, None);
164 let old_cst = parse_svelte(before_src).expect("initial CST parse");
165
166 let edit = CstEdit::replace(before, edit_start, edit_old_end, "World");
167
168 let new_root = crate::parse_modern_root_incremental(after, before, &old_root, &old_cst, edit)
169 .expect("incremental parse");
170
171 let old_script = old_root.instance.as_ref().expect("old instance");
173 let new_script = new_root.instance.as_ref().expect("new instance");
174 assert!(
175 Arc::ptr_eq(&old_script.content, &new_script.content),
176 "unchanged script should be Arc-reused (same pointer)",
177 );
178
179 let el_node = new_root.fragment.nodes.iter().find(|n| matches!(n, Node::RegularElement(_)))
182 .expect("expected regular element in new root fragment");
183 let Node::RegularElement(new_el) = el_node else { unreachable!() };
184 let Node::Text(text) = &new_el.fragment.nodes[0] else {
185 panic!("expected text node in element fragment");
186 };
187 assert_eq!(text.data.as_ref(), "World");
188 }
189
190 #[test]
193 fn verify_tree_sitter_changed_ranges_are_structural_only() {
194 let before = "<div>A</div>";
196 let after = "<div>X</div>";
197
198 let before_src = SourceText::new(SourceId::new(50), before, None);
199 let old_cst = parse_svelte(before_src).expect("cst");
200 let mut edited = old_cst.clone_for_incremental();
201 let edit = CstEdit::replace(before, 5, 6, "X");
202 edited.apply_edit(edit);
203
204 let after_src = SourceText::new(SourceId::new(51), after, None);
205 let new_cst = crate::cst::parse_svelte_with_old_tree(after_src, &edited).expect("cst");
206 let ranges = new_cst.changed_ranges(&edited);
207
208 assert!(
211 ranges.is_empty(),
212 "tree-sitter changed_ranges should be empty for same-structure edit, got: {ranges:?}"
213 );
214
215 let before2 = "<div>A</div>";
217 let after2 = "<div>A</div><span>B</span>";
218 let before_src2 = SourceText::new(SourceId::new(52), before2, None);
219 let old_cst2 = parse_svelte(before_src2).expect("cst");
220 let mut edited2 = old_cst2.clone_for_incremental();
221 let edit2 = CstEdit::insert(before2, before2.len(), "<span>B</span>");
222 edited2.apply_edit(edit2);
223
224 let after_src2 = SourceText::new(SourceId::new(53), after2, None);
225 let new_cst2 = crate::cst::parse_svelte_with_old_tree(after_src2, &edited2).expect("cst");
226 let ranges2 = new_cst2.changed_ranges(&edited2);
227
228 assert!(
230 !ranges2.is_empty(),
231 "tree-sitter changed_ranges should be non-empty for structural edit"
232 );
233 }
234
235 #[test]
236 fn incremental_parse_reuses_unchanged_sibling_element() {
237 let before = "<div>A</div><span>B</span>";
238 let after = "<div>X</div><span>B</span>";
239 let edit_start = "<div>".len();
240 let edit_old_end = "<div>A".len();
241
242 let old_root = parse_modern_root(before).expect("initial parse");
243
244 let before_src = SourceText::new(SourceId::new(3), before, None);
245 let old_cst = parse_svelte(before_src).expect("initial CST parse");
246
247 let edit = CstEdit::replace(before, edit_start, edit_old_end, "X");
248
249 let new_root = crate::parse_modern_root_incremental(after, before, &old_root, &old_cst, edit)
250 .expect("incremental parse");
251
252 assert_eq!(new_root.fragment.nodes.len(), 2);
254 let Node::RegularElement(new_span) = &new_root.fragment.nodes[1] else {
255 panic!("expected span element");
256 };
257 assert_eq!(new_span.name.as_ref(), "span");
258
259 let Node::RegularElement(new_div) = &new_root.fragment.nodes[0] else {
261 panic!("expected div element");
262 };
263 let Node::Text(text) = &new_div.fragment.nodes[0] else {
264 panic!("expected text in div");
265 };
266 assert_eq!(text.data.as_ref(), "X");
267 }
268}