quickjs-oxide 0.4.0

A safe Rust JavaScript engine targeting semantic parity with QuickJS
Documentation
//! Lexical-name validation and source diagnostics, preserving first-error order.

use crate::engine::api::error::Error;
use crate::engine::api::error::ErrorKind;
use crate::engine::api::error::NativeErrorMessage;
use crate::engine::compiler::lexer::Identifier;
use crate::engine::compiler::lexer::Keyword;
use crate::engine::compiler::lexer::LexError;
use crate::engine::compiler::lexer::LexErrorKind;
use crate::engine::compiler::lexer::Span;
use crate::engine::compiler::parser::context::Parser;
use crate::engine::value::JsString;
use crate::engine::value::JsStringError;
use crate::source::SourceOffset;

#[derive(Clone, Copy)]
pub(in crate::engine::compiler) enum IdentifierContext {
    Reference,
    Variable,
    FunctionName,
    Argument,
}

impl<'source> Parser<'source> {
    pub(in crate::engine::compiler) fn validate_identifier(
        &self,
        identifier: &Identifier<'source>,
        span: Span,
        strict: bool,
        context: IdentifierContext,
    ) -> Result<(), Error> {
        self.validate_identifier_reservation(identifier, span, strict, context)?;
        if strict
            && !matches!(context, IdentifierContext::Reference)
            && matches!(
                self.identifier_text(identifier).as_ref(),
                "eval" | "arguments"
            )
        {
            let message = match context {
                IdentifierContext::Variable => "invalid variable name in strict mode",
                IdentifierContext::FunctionName => "invalid function name in strict code",
                IdentifierContext::Argument => "invalid argument name in strict code",
                IdentifierContext::Reference => unreachable!("reference context was excluded"),
            };
            return Err(Error::syntax(message, span));
        }
        Ok(())
    }

    pub(in crate::engine::compiler) fn validate_identifier_reservation(
        &self,
        identifier: &Identifier<'source>,
        span: Span,
        strict: bool,
        context: IdentifierContext,
    ) -> Result<(), Error> {
        if identifier.escaped_reserved_word {
            let text = self.identifier_text(identifier);
            return Err(syntax_atom_error(
                "'",
                &text,
                "' is a reserved identifier",
                span,
            )?);
        }
        if strict
            && identifier
                .keyword_hint
                .is_some_and(strict_reserved_identifier)
        {
            let message = match context {
                IdentifierContext::Reference => {
                    let text = self.identifier_text(identifier);
                    return Err(syntax_atom_error(
                        "'",
                        &text,
                        "' is a reserved identifier",
                        span,
                    )?);
                }
                IdentifierContext::Variable => "invalid variable name in strict mode",
                IdentifierContext::FunctionName => "invalid function name in strict code",
                IdentifierContext::Argument => "invalid argument name in strict code",
            };
            return Err(Error::syntax(message, span));
        }
        Ok(())
    }
}

pub(in crate::engine::compiler) fn syntax_atom_error(
    prefix: &str,
    atom: &str,
    suffix: &str,
    span: Span,
) -> Result<Error, JsStringError> {
    Ok(syntax_atom_error_without_span(prefix, atom, suffix)?.with_span(span))
}

pub(in crate::engine::compiler) fn syntax_atom_error_without_span(
    prefix: &str,
    atom: &str,
    suffix: &str,
) -> Result<Error, JsStringError> {
    let atom = JsString::try_from_utf8(atom)?;
    let mut message = NativeErrorMessage::new();
    message.push_utf8(prefix);
    atom.push_atom_get_str_to(&mut message);
    message.push_utf8(suffix);
    Ok(Error::from_native_message(ErrorKind::Syntax, message))
}

pub(in crate::engine::compiler) const fn strict_reserved_identifier(keyword: Keyword) -> bool {
    matches!(
        keyword,
        Keyword::Implements
            | Keyword::Interface
            | Keyword::Let
            | Keyword::Package
            | Keyword::Private
            | Keyword::Protected
            | Keyword::Public
            | Keyword::Static
            | Keyword::Yield
    )
}

#[cold]
#[inline(never)]
pub(in crate::engine::compiler) fn lex_error(error: LexError) -> Error {
    if error.kind == LexErrorKind::StringTooLong {
        Error::new(ErrorKind::JsInternal, error.message)
    } else {
        Error::syntax(error.message, error.span)
    }
}

pub(in crate::engine::compiler) fn source_offset(span: Span) -> Result<SourceOffset, Error> {
    SourceOffset::try_from_usize(span.start.byte_offset)
        .map_err(|error| Error::internal(error.to_string()))
}