use std::collections::HashSet;
use std::fmt;
use std::sync::Arc;
use camino::Utf8PathBuf;
use html_escape::decode_html_entities as decode_html_entities_cow;
use serde::{Deserialize, Serialize};
use tree_sitter::{Node, Point};
use crate::ast::Document;
use crate::{CompileError, SourceId, LineColumn, SourceText};
mod elements;
mod legacy;
pub(crate) mod modern;
pub use elements::{
AttributeKind, ElementKind, SvelteElementKind, classify_attribute_name, classify_element_name,
is_component_name, is_custom_element_name, is_valid_component_name, is_valid_element_name,
is_void_element_name,
};
pub use legacy::parse_root as parse_legacy_root_from_cst;
pub use legacy::legacy_root_from_modern;
pub(crate) use legacy::{
find_first_named_child, parse_identifier_name, parse_modern_attributes,
line_column_from_point, text_for_node,
};
pub(crate) use modern::parse_root as parse_root_from_cst;
pub(crate) use modern::parse_root_incremental as parse_root_incremental_from_cst;
pub(crate) use modern::{
attach_leading_comments_to_expression, attach_trailing_comments_to_expression,
find_matching_brace_close, line_column_at_offset, modern_empty_identifier_expression,
named_children_vec, parse_modern_expression_from_text, parse_modern_expression_tag,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "kebab-case")]
pub enum ParseMode {
#[default]
Legacy,
Modern,
}
impl ParseMode {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Legacy => "legacy",
Self::Modern => "modern",
}
}
}
impl fmt::Display for ParseMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl std::str::FromStr for ParseMode {
type Err = ();
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value {
"legacy" => Ok(Self::Legacy),
"modern" => Ok(Self::Modern),
_ => Err(()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(default)]
pub struct ParseOptions {
pub filename: Option<Utf8PathBuf>,
pub root_dir: Option<Utf8PathBuf>,
pub modern: Option<bool>,
pub mode: ParseMode,
pub loose: bool,
}
impl ParseOptions {
#[must_use]
pub fn effective_mode(&self) -> ParseMode {
match self.modern {
Some(true) => ParseMode::Modern,
Some(false) => ParseMode::Legacy,
None => self.mode,
}
}
}
struct SvelteParserCore<'src> {
source: &'src str,
source_filename: Option<Utf8PathBuf>,
options: ParseOptions,
}
impl<'src> SvelteParserCore<'src> {
fn new(source: &'src str, options: ParseOptions) -> Self {
Self {
source,
source_filename: options.filename.clone(),
options,
}
}
fn parse_root(&self, root: Node<'_>) -> crate::ast::Root {
match self.options.effective_mode() {
ParseMode::Legacy => crate::ast::Root::Legacy(parse_legacy_root_from_cst(
self.source,
root,
self.options.loose,
)),
ParseMode::Modern => {
crate::ast::Root::Modern(parse_root_from_cst(self.source, root, self.options.loose))
}
}
}
fn parse(self) -> Result<Document, CompileError> {
let source_text = SourceText::new(
SourceId::new(0),
self.source,
self.source_filename.as_deref(),
);
let cst = crate::cst::parse_svelte(source_text)?;
Ok(Document {
root: self.parse_root(cst.root_node()),
source: Arc::from(self.source),
})
}
}
pub fn parse(source: &str, options: ParseOptions) -> Result<Document, CompileError> {
SvelteParserCore::new(source, options).parse()
}
pub fn parse_modern_root(source: &str) -> Result<crate::ast::modern::Root, CompileError> {
let source_text = SourceText::new(SourceId::new(0), source, None);
let cst = crate::cst::parse_svelte(source_text)?;
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
parse_root_from_cst(source_text.text, cst.root_node(), false)
}))
.map_err(|_| CompileError::internal("failed to parse component root from cst"))
}
#[derive(Debug, Clone)]
pub struct ParseTimings {
pub tree_sitter_parse_us: u64,
pub cst_to_ast_us: u64,
pub enrich_expressions_us: u64,
pub total_us: u64,
}
pub fn parse_modern_root_timed(source: &str) -> Result<(crate::ast::modern::Root, ParseTimings), CompileError> {
use std::time::Instant;
let t0 = Instant::now();
let source_text = SourceText::new(SourceId::new(0), source, None);
let cst = crate::cst::parse_svelte(source_text)?;
let tree_sitter_us = t0.elapsed().as_micros() as u64;
let t1 = Instant::now();
let root = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
parse_root_from_cst(source_text.text, cst.root_node(), false)
}))
.map_err(|_| CompileError::internal("failed to parse component root from cst"))?;
let cst_to_ast_us = t1.elapsed().as_micros() as u64;
let total_us = t0.elapsed().as_micros() as u64;
Ok((root, ParseTimings {
tree_sitter_parse_us: tree_sitter_us,
cst_to_ast_us,
enrich_expressions_us: 0,
total_us,
}))
}
pub fn parse_modern_root_incremental(
source: &str,
old_source: &str,
old_root: &crate::ast::modern::Root,
old_cst: &crate::cst::Document<'_>,
edit: crate::cst::CstEdit,
) -> Result<crate::ast::modern::Root, CompileError> {
use crate::cst;
let source_text = SourceText::new(SourceId::new(0), source, None);
let mut edited_old_cst = old_cst.clone_for_incremental();
edited_old_cst.apply_edit(edit);
let new_cst = cst::parse_svelte_with_old_tree(source_text, &edited_old_cst)?;
let changed_ranges = new_cst.changed_ranges(&edited_old_cst);
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
parse_root_incremental_from_cst(
source_text.text,
new_cst.root_node(),
false,
old_root,
old_source,
&changed_ranges,
)
}))
.map_err(|_| CompileError::internal("failed to incrementally parse component root from cst"))
}