svelte_syntax/parse/component/
mod.rs1use std::collections::{BTreeMap, 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::ast::modern::RootCommentType;
12use crate::{CompileError, SourceId, SourceLocation, SourceText};
13
14mod elements;
15mod legacy;
16pub(crate) mod modern;
17
18pub use elements::{
19 AttributeKind, ElementKind, SvelteElementKind, classify_attribute_name, classify_element_name,
20 is_component_name, is_custom_element_name, is_valid_component_name, is_valid_element_name,
21 is_void_element_name,
22};
23pub(crate) use legacy::parse_root as parse_legacy_root_from_cst;
24pub(crate) use legacy::{
25 find_first_named_child, legacy_expression_from_raw_node, parse_identifier_name,
26 parse_modern_attributes, source_location_from_point, text_for_node,
27};
28pub(crate) use modern::parse_root as parse_root_from_cst;
29pub use modern::{
30 RawField, estree_node_field, estree_node_field_array, estree_node_field_mut,
31 estree_node_field_object, estree_node_field_str, estree_node_has_field, estree_node_type,
32 expression_identifier_name, modern_node_end, modern_node_span, modern_node_start,
33 walk_estree_node, walk_raw_value,
34};
35pub(crate) use modern::{
36 attach_leading_comments_to_expression, attach_trailing_comments_to_expression,
37 estree_value_to_usize, find_matching_brace_close, legacy_expression_from_modern_expression,
38 line_column_at_offset, modern_empty_identifier_expression, named_children_vec,
39 normalize_pattern_template_elements, parse_all_comment_nodes,
40 parse_modern_expression_from_text, parse_modern_expression_tag,
41};
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
44#[serde(rename_all = "kebab-case")]
45pub enum ParseMode {
46 #[default]
47 Legacy,
48 Modern,
49}
50
51impl ParseMode {
52 #[must_use]
53 pub const fn as_str(self) -> &'static str {
54 match self {
55 Self::Legacy => "legacy",
56 Self::Modern => "modern",
57 }
58 }
59}
60
61impl fmt::Display for ParseMode {
62 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63 f.write_str(self.as_str())
64 }
65}
66
67impl std::str::FromStr for ParseMode {
68 type Err = ();
69
70 fn from_str(value: &str) -> Result<Self, Self::Err> {
71 match value {
72 "legacy" => Ok(Self::Legacy),
73 "modern" => Ok(Self::Modern),
74 _ => Err(()),
75 }
76 }
77}
78
79#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
80#[serde(default)]
81pub struct ParseOptions {
82 pub filename: Option<Utf8PathBuf>,
83 pub root_dir: Option<Utf8PathBuf>,
84 pub modern: Option<bool>,
85 pub mode: ParseMode,
86 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> {
143 SvelteParserCore::new(source, options).parse()
144}
145
146pub fn parse_modern_root(source: &str) -> Result<crate::ast::modern::Root, CompileError> {
147 let source_text = SourceText::new(SourceId::new(0), source, None);
148 let cst = crate::cst::parse_svelte(source_text)?;
149 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
150 parse_root_from_cst(source_text.text, cst.root_node(), false)
151 }))
152 .map_err(|_| CompileError::internal("failed to parse component root from cst"))
153}