Skip to main content

svelte_syntax/parse/component/
mod.rs

1use 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}