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