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(crate) use legacy::parse_root as parse_legacy_root_from_cst;
23pub(crate) use legacy::{
24 find_first_named_child, parse_identifier_name, parse_modern_attributes,
25 line_column_from_point, text_for_node,
26};
27pub(crate) use modern::parse_root as parse_root_from_cst;
28pub(crate) use modern::parse_root_incremental as parse_root_incremental_from_cst;
29pub(crate) use modern::{
30 attach_leading_comments_to_expression, attach_trailing_comments_to_expression,
31 find_matching_brace_close, line_column_at_offset, modern_empty_identifier_expression,
32 named_children_vec, parse_modern_expression_from_text, parse_modern_expression_tag,
33};
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
36#[serde(rename_all = "kebab-case")]
37pub enum ParseMode {
39 #[default]
40 Legacy,
41 Modern,
42}
43
44impl ParseMode {
45 #[must_use]
46 pub const fn as_str(self) -> &'static str {
47 match self {
48 Self::Legacy => "legacy",
49 Self::Modern => "modern",
50 }
51 }
52}
53
54impl fmt::Display for ParseMode {
55 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56 f.write_str(self.as_str())
57 }
58}
59
60impl std::str::FromStr for ParseMode {
61 type Err = ();
62
63 fn from_str(value: &str) -> Result<Self, Self::Err> {
64 match value {
65 "legacy" => Ok(Self::Legacy),
66 "modern" => Ok(Self::Modern),
67 _ => Err(()),
68 }
69 }
70}
71
72#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
73#[serde(default)]
74pub struct ParseOptions {
76 pub filename: Option<Utf8PathBuf>,
78 pub root_dir: Option<Utf8PathBuf>,
80 pub modern: Option<bool>,
82 pub mode: ParseMode,
84 pub loose: bool,
86}
87
88impl ParseOptions {
89 #[must_use]
90 pub fn effective_mode(&self) -> ParseMode {
91 match self.modern {
92 Some(true) => ParseMode::Modern,
93 Some(false) => ParseMode::Legacy,
94 None => self.mode,
95 }
96 }
97}
98
99struct SvelteParserCore<'src> {
100 source: &'src str,
101 source_filename: Option<Utf8PathBuf>,
102 options: ParseOptions,
103}
104
105impl<'src> SvelteParserCore<'src> {
106 fn new(source: &'src str, options: ParseOptions) -> Self {
107 Self {
108 source,
109 source_filename: options.filename.clone(),
110 options,
111 }
112 }
113
114 fn parse_root(&self, root: Node<'_>) -> crate::ast::Root {
115 match self.options.effective_mode() {
116 ParseMode::Legacy => crate::ast::Root::Legacy(parse_legacy_root_from_cst(
117 self.source,
118 root,
119 self.options.loose,
120 )),
121 ParseMode::Modern => {
122 crate::ast::Root::Modern(parse_root_from_cst(self.source, root, self.options.loose))
123 }
124 }
125 }
126
127 fn parse(self) -> Result<Document, CompileError> {
128 let source_text = SourceText::new(
129 SourceId::new(0),
130 self.source,
131 self.source_filename.as_deref(),
132 );
133 let cst = crate::cst::parse_svelte(source_text)?;
134 Ok(Document {
135 root: self.parse_root(cst.root_node()),
136 source: Arc::from(self.source),
137 })
138 }
139}
140
141pub fn parse(source: &str, options: ParseOptions) -> Result<Document, CompileError> {
146 SvelteParserCore::new(source, options).parse()
147}
148
149pub fn parse_modern_root(source: &str) -> Result<crate::ast::modern::Root, CompileError> {
151 let source_text = SourceText::new(SourceId::new(0), source, None);
152 let cst = crate::cst::parse_svelte(source_text)?;
153 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
154 parse_root_from_cst(source_text.text, cst.root_node(), false)
155 }))
156 .map_err(|_| CompileError::internal("failed to parse component root from cst"))
157}
158
159pub fn parse_modern_root_incremental(
165 source: &str,
166 old_source: &str,
167 old_root: &crate::ast::modern::Root,
168 old_cst: &crate::cst::Document<'_>,
169 edit: crate::cst::CstEdit,
170) -> Result<crate::ast::modern::Root, CompileError> {
171 use crate::cst;
172
173 let source_text = SourceText::new(SourceId::new(0), source, None);
174
175 let mut edited_old_cst = old_cst.clone_for_incremental();
179 edited_old_cst.apply_edit(edit);
180
181 let new_cst = cst::parse_svelte_with_old_tree(source_text, &edited_old_cst)?;
182 let changed_ranges = new_cst.changed_ranges(&edited_old_cst);
183
184 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
185 parse_root_incremental_from_cst(
186 source_text.text,
187 new_cst.root_node(),
188 false,
189 old_root,
190 old_source,
191 &changed_ranges,
192 )
193 }))
194 .map_err(|_| CompileError::internal("failed to incrementally parse component root from cst"))
195}