svelte-syntax 0.1.5

Lightweight syntax-layer crate for the Rust Svelte toolchain
Documentation
use std::sync::Arc;

use oxc_allocator::Allocator;
use oxc_parser::Parser as OxcParser;
use oxc_span::SourceType as OxcSourceType;

use crate::ast::modern::Expression;
use crate::js::{JsExpression, JsProgram};
use crate::parse::ParsedProgramContent;

pub(crate) struct OxcProgramOffsets {
    pub global_start: usize,
}

impl OxcProgramOffsets {
    pub(crate) fn for_root_source(_source_len: usize) -> Self {
        Self { global_start: 0 }
    }
}

pub(crate) struct SvelteOxcParser<'src> {
    source: &'src str,
    offsets: OxcProgramOffsets,
}

impl<'src> SvelteOxcParser<'src> {
    pub(crate) fn new(source: &'src str) -> Self {
        Self {
            source,
            offsets: OxcProgramOffsets::for_root_source(source.len()),
        }
    }

    pub(crate) fn with_offsets(mut self, offsets: OxcProgramOffsets) -> Self {
        self.offsets = offsets;
        self
    }

    pub(crate) fn parse_program_for_compile(&self, is_ts: bool) -> Option<ParsedProgramContent> {
        let source_type = if is_ts {
            OxcSourceType::ts().with_module(true)
        } else {
            OxcSourceType::mjs()
        };
        let parsed = Arc::new(JsProgram::parse(self.source, source_type));
        Some(ParsedProgramContent { parsed })
    }

    pub(crate) fn parse_expression_for_template(&self) -> Option<Expression> {
        let source_type = OxcSourceType::ts().with_module(true);
        let parsed = Arc::new(JsExpression::parse(self.source, source_type).ok()?);
        Some(Expression::from_expression(
            parsed,
            self.offsets.global_start,
            self.offsets.global_start + self.source.len(),
        ))
    }

    pub(crate) fn parse_expression_error_for_template(&self) -> Option<Arc<str>> {
        self.parse_expression_error_detail_for_template()
            .map(|(_, message)| message)
    }

    pub(crate) fn parse_expression_error_detail_for_template(&self) -> Option<(usize, Arc<str>)> {
        let allocator = Allocator::default();
        let source_type = OxcSourceType::ts().with_module(true);
        let errors = OxcParser::new(&allocator, self.source, source_type)
            .parse_expression()
            .err()?;
        let error = errors
            .iter()
            .min_by_key(|error| match error.to_string().as_str() {
                message if message.starts_with("Unexpected keyword ") => 0,
                "Unexpected token" => 1,
                _ => 2,
            })?;
        let start = error
            .labels
            .as_ref()
            .and_then(|labels| labels.first())
            .map(|label| self.offsets.global_start + label.inner().offset())
            .unwrap_or(self.offsets.global_start);

        Some((start, normalize_expression_error_message(error.to_string())))
    }
}

fn normalize_expression_error_message(message: String) -> Arc<str> {
    match message.as_str() {
        "Cannot assign to this expression" => Arc::from("Assigning to rvalue"),
        _ => Arc::from(message),
    }
}