svelte_syntax/parse/component/
mod.rs1use std::collections::HashSet;
2use std::fmt;
3use std::sync::Arc;
4
5use camino::Utf8PathBuf;
6use html_escape::decode_html_entities as decode_html_entities_cow;
7use serde::{Deserialize, Serialize};
8use tree_sitter::{Node, Point};
9
10use crate::ast::Document;
11use crate::{CompileError, SourceId, LineColumn, SourceText};
12
13mod elements;
14mod legacy;
15pub(crate) mod modern;
16
17pub use elements::{
18 AttributeKind, ElementKind, SvelteElementKind, classify_attribute_name, classify_element_name,
19 is_component_name, is_custom_element_name, is_valid_component_name, is_valid_element_name,
20 is_void_element_name,
21};
22pub use legacy::parse_root as parse_legacy_root_from_cst;
23pub use legacy::legacy_root_from_modern;
24pub(crate) use legacy::{
25 find_first_named_child, parse_identifier_name, parse_modern_attributes,
26 line_column_from_point, text_for_node,
27};
28pub(crate) use modern::parse_root as parse_root_from_cst;
29pub(crate) use modern::parse_root_incremental as parse_root_incremental_from_cst;
30pub(crate) use modern::{
31 attach_leading_comments_to_expression, attach_trailing_comments_to_expression,
32 find_matching_brace_close, line_column_at_offset, modern_empty_identifier_expression,
33 named_children_vec, parse_modern_expression_from_text, parse_modern_expression_tag,
34};
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
37#[serde(rename_all = "kebab-case")]
38pub enum ParseMode {
40 #[default]
41 Legacy,
42 Modern,
43}
44
45impl ParseMode {
46 #[must_use]
47 pub const fn as_str(self) -> &'static str {
48 match self {
49 Self::Legacy => "legacy",
50 Self::Modern => "modern",
51 }
52 }
53}
54
55impl fmt::Display for ParseMode {
56 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57 f.write_str(self.as_str())
58 }
59}
60
61impl std::str::FromStr for ParseMode {
62 type Err = ();
63
64 fn from_str(value: &str) -> Result<Self, Self::Err> {
65 match value {
66 "legacy" => Ok(Self::Legacy),
67 "modern" => Ok(Self::Modern),
68 _ => Err(()),
69 }
70 }
71}
72
73#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
74#[serde(default)]
75pub struct ParseOptions {
77 pub filename: Option<Utf8PathBuf>,
79 pub root_dir: Option<Utf8PathBuf>,
81 pub modern: Option<bool>,
83 pub mode: ParseMode,
85 pub loose: bool,
87}
88
89impl ParseOptions {
90 #[must_use]
91 pub fn effective_mode(&self) -> ParseMode {
92 match self.modern {
93 Some(true) => ParseMode::Modern,
94 Some(false) => ParseMode::Legacy,
95 None => self.mode,
96 }
97 }
98}
99
100struct SvelteParserCore<'src> {
101 source: &'src str,
102 source_filename: Option<Utf8PathBuf>,
103 options: ParseOptions,
104}
105
106impl<'src> SvelteParserCore<'src> {
107 fn new(source: &'src str, options: ParseOptions) -> Self {
108 Self {
109 source,
110 source_filename: options.filename.clone(),
111 options,
112 }
113 }
114
115 fn parse_root(&self, root: Node<'_>) -> crate::ast::Root {
116 match self.options.effective_mode() {
117 ParseMode::Legacy => crate::ast::Root::Legacy(parse_legacy_root_from_cst(
118 self.source,
119 root,
120 self.options.loose,
121 )),
122 ParseMode::Modern => {
123 crate::ast::Root::Modern(parse_root_from_cst(self.source, root, self.options.loose))
124 }
125 }
126 }
127
128 fn parse(self) -> Result<Document, CompileError> {
129 let source_text = SourceText::new(
130 SourceId::new(0),
131 self.source,
132 self.source_filename.as_deref(),
133 );
134 let cst = crate::cst::parse_svelte(source_text)?;
135 Ok(Document {
136 root: self.parse_root(cst.root_node()),
137 source: Arc::from(self.source),
138 })
139 }
140}
141
142pub fn parse(source: &str, options: ParseOptions) -> Result<Document, CompileError> {
147 SvelteParserCore::new(source, options).parse()
148}
149
150pub fn parse_modern_root(source: &str) -> Result<crate::ast::modern::Root, CompileError> {
152 let source_text = SourceText::new(SourceId::new(0), source, None);
153 let cst = crate::cst::parse_svelte(source_text)?;
154 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
155 parse_root_from_cst(source_text.text, cst.root_node(), false)
156 }))
157 .map_err(|_| CompileError::internal("failed to parse component root from cst"))
158}
159
160#[derive(Debug, Clone)]
162pub struct ParseTimings {
163 pub tree_sitter_parse_us: u64,
165 pub cst_to_ast_us: u64,
167 pub enrich_expressions_us: u64,
169 pub total_us: u64,
171}
172
173pub fn parse_modern_root_timed(source: &str) -> Result<(crate::ast::modern::Root, ParseTimings), CompileError> {
177 use std::time::Instant;
178
179 let t0 = Instant::now();
180 let source_text = SourceText::new(SourceId::new(0), source, None);
181 let cst = crate::cst::parse_svelte(source_text)?;
182 let tree_sitter_us = t0.elapsed().as_micros() as u64;
183
184 let t1 = Instant::now();
185 let root = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
186 parse_root_from_cst(source_text.text, cst.root_node(), false)
187 }))
188 .map_err(|_| CompileError::internal("failed to parse component root from cst"))?;
189 let cst_to_ast_us = t1.elapsed().as_micros() as u64;
190
191 let total_us = t0.elapsed().as_micros() as u64;
192
193 Ok((root, ParseTimings {
194 tree_sitter_parse_us: tree_sitter_us,
195 cst_to_ast_us,
196 enrich_expressions_us: 0,
197 total_us,
198 }))
199}
200
201pub fn parse_modern_root_incremental(
207 source: &str,
208 old_source: &str,
209 old_root: &crate::ast::modern::Root,
210 old_cst: &crate::cst::Document<'_>,
211 edit: crate::cst::CstEdit,
212) -> Result<crate::ast::modern::Root, CompileError> {
213 use crate::cst;
214
215 let source_text = SourceText::new(SourceId::new(0), source, None);
216
217 let mut edited_old_cst = old_cst.clone_for_incremental();
221 edited_old_cst.apply_edit(edit);
222
223 let new_cst = cst::parse_svelte_with_old_tree(source_text, &edited_old_cst)?;
224 let changed_ranges = new_cst.changed_ranges(&edited_old_cst);
225
226 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
227 parse_root_incremental_from_cst(
228 source_text.text,
229 new_cst.root_node(),
230 false,
231 old_root,
232 old_source,
233 &changed_ranges,
234 )
235 }))
236 .map_err(|_| CompileError::internal("failed to incrementally parse component root from cst"))
237}