2026-02-23 07:50:46 -03:00
|
|
|
/*
|
|
|
|
|
* Copyright (c) 2026-present, the Ladybird developers.
|
|
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
//! Expression parsing: primary, secondary (binary/postfix), unary, and
|
|
|
|
|
//! precedence climbing.
|
|
|
|
|
|
|
|
|
|
use std::rc::Rc;
|
|
|
|
|
|
|
|
|
|
use crate::ast::*;
|
|
|
|
|
use crate::lexer::ch;
|
2026-02-24 06:40:18 -03:00
|
|
|
use crate::parser::{
|
2026-04-18 00:37:40 -03:00
|
|
|
Associativity, ForbiddenTokens, FunctionKind, MethodKind, PRECEDENCE_ASSIGNMENT, PRECEDENCE_COMMA,
|
|
|
|
|
PRECEDENCE_MEMBER, PRECEDENCE_UNARY, ParamInfo, ParsedParameters, Parser, Position, PropertyKey,
|
|
|
|
|
is_strict_reserved_word,
|
2026-02-24 06:40:18 -03:00
|
|
|
};
|
2026-02-23 07:50:46 -03:00
|
|
|
use crate::token::{Token, TokenType};
|
|
|
|
|
|
|
|
|
|
#[derive(PartialEq, Eq)]
|
|
|
|
|
enum EscapeMode {
|
|
|
|
|
/// Tagged template literals: invalid escapes produce `undefined` cooked value.
|
|
|
|
|
TaggedTemplate,
|
|
|
|
|
/// String literals: invalid escapes emit syntax errors, legacy octals are tracked.
|
|
|
|
|
StringLiteral,
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-27 06:40:42 -03:00
|
|
|
impl Parser<'_> {
|
2026-02-23 07:50:46 -03:00
|
|
|
pub(crate) fn match_expression(&mut self) -> bool {
|
|
|
|
|
match self.current_token_type() {
|
|
|
|
|
TokenType::BoolLiteral
|
|
|
|
|
| TokenType::NumericLiteral
|
|
|
|
|
| TokenType::BigIntLiteral
|
|
|
|
|
| TokenType::StringLiteral
|
|
|
|
|
| TokenType::NullLiteral
|
|
|
|
|
| TokenType::RegexLiteral
|
|
|
|
|
| TokenType::TemplateLiteralStart
|
|
|
|
|
| TokenType::This
|
|
|
|
|
| TokenType::Super
|
|
|
|
|
| TokenType::New
|
|
|
|
|
| TokenType::Class
|
|
|
|
|
| TokenType::Function
|
|
|
|
|
| TokenType::ParenOpen
|
|
|
|
|
| TokenType::CurlyOpen
|
|
|
|
|
| TokenType::BracketOpen
|
|
|
|
|
| TokenType::PrivateIdentifier
|
|
|
|
|
| TokenType::Slash
|
|
|
|
|
| TokenType::SlashEquals => true,
|
|
|
|
|
|
|
|
|
|
TokenType::Async => true,
|
|
|
|
|
TokenType::Yield => true,
|
|
|
|
|
TokenType::Await => true,
|
|
|
|
|
|
|
|
|
|
// https://tc39.es/ecma262/#sec-import-calls
|
|
|
|
|
// https://tc39.es/ecma262/#sec-import-meta
|
|
|
|
|
// `import` is only a valid expression start if followed by `(` or `.`.
|
|
|
|
|
TokenType::Import => {
|
|
|
|
|
let next = self.next_token();
|
|
|
|
|
next.token_type == TokenType::ParenOpen || next.token_type == TokenType::Period
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_ => {
|
|
|
|
|
if self.match_identifier() {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
self.match_unary_prefixed_expression()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) fn match_unary_prefixed_expression(&self) -> bool {
|
|
|
|
|
matches!(
|
|
|
|
|
self.current_token_type(),
|
|
|
|
|
TokenType::PlusPlus
|
|
|
|
|
| TokenType::MinusMinus
|
|
|
|
|
| TokenType::ExclamationMark
|
|
|
|
|
| TokenType::Tilde
|
|
|
|
|
| TokenType::Plus
|
|
|
|
|
| TokenType::Minus
|
|
|
|
|
| TokenType::Typeof
|
|
|
|
|
| TokenType::Void
|
|
|
|
|
| TokenType::Delete
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) fn match_secondary_expression(&self, forbidden: &ForbiddenTokens) -> bool {
|
|
|
|
|
let tt = self.current_token_type();
|
|
|
|
|
if !forbidden.allows(tt) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
match tt {
|
2026-04-18 00:37:40 -03:00
|
|
|
TokenType::Period | TokenType::BracketOpen | TokenType::ParenOpen | TokenType::QuestionMarkPeriod => true,
|
|
|
|
|
TokenType::PlusPlus | TokenType::MinusMinus => !self.current_token.trivia_has_line_terminator,
|
2026-02-23 07:50:46 -03:00
|
|
|
TokenType::DoubleAsterisk
|
2026-02-24 06:40:18 -03:00
|
|
|
| TokenType::Asterisk
|
|
|
|
|
| TokenType::Slash
|
|
|
|
|
| TokenType::Percent
|
|
|
|
|
| TokenType::Plus
|
|
|
|
|
| TokenType::Minus
|
|
|
|
|
| TokenType::ShiftLeft
|
|
|
|
|
| TokenType::ShiftRight
|
|
|
|
|
| TokenType::UnsignedShiftRight
|
|
|
|
|
| TokenType::LessThan
|
|
|
|
|
| TokenType::LessThanEquals
|
|
|
|
|
| TokenType::GreaterThan
|
|
|
|
|
| TokenType::GreaterThanEquals
|
|
|
|
|
| TokenType::In
|
|
|
|
|
| TokenType::Instanceof
|
|
|
|
|
| TokenType::EqualsEquals
|
|
|
|
|
| TokenType::ExclamationMarkEquals
|
|
|
|
|
| TokenType::EqualsEqualsEquals
|
|
|
|
|
| TokenType::ExclamationMarkEqualsEquals
|
|
|
|
|
| TokenType::Ampersand
|
|
|
|
|
| TokenType::Caret
|
|
|
|
|
| TokenType::Pipe
|
|
|
|
|
| TokenType::DoubleQuestionMark
|
|
|
|
|
| TokenType::DoubleAmpersand
|
|
|
|
|
| TokenType::DoublePipe => true,
|
2026-02-23 07:50:46 -03:00
|
|
|
TokenType::QuestionMark => true,
|
|
|
|
|
TokenType::Equals
|
2026-02-24 06:40:18 -03:00
|
|
|
| TokenType::PlusEquals
|
|
|
|
|
| TokenType::MinusEquals
|
|
|
|
|
| TokenType::DoubleAsteriskEquals
|
|
|
|
|
| TokenType::AsteriskEquals
|
|
|
|
|
| TokenType::SlashEquals
|
|
|
|
|
| TokenType::PercentEquals
|
|
|
|
|
| TokenType::ShiftLeftEquals
|
|
|
|
|
| TokenType::ShiftRightEquals
|
2026-02-23 07:50:46 -03:00
|
|
|
| TokenType::UnsignedShiftRightEquals
|
2026-02-24 06:40:18 -03:00
|
|
|
| TokenType::AmpersandEquals
|
|
|
|
|
| TokenType::CaretEquals
|
|
|
|
|
| TokenType::PipeEquals
|
|
|
|
|
| TokenType::DoubleAmpersandEquals
|
|
|
|
|
| TokenType::DoublePipeEquals
|
2026-02-23 07:50:46 -03:00
|
|
|
| TokenType::DoubleQuestionMarkEquals => true,
|
|
|
|
|
_ => false,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) fn parse_expression_any(&mut self) -> Expression {
|
2026-04-18 00:37:40 -03:00
|
|
|
self.parse_expression(PRECEDENCE_COMMA, Associativity::Right, ForbiddenTokens::none())
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) fn parse_assignment_expression(&mut self) -> Expression {
|
2026-04-18 00:37:40 -03:00
|
|
|
self.parse_expression(PRECEDENCE_ASSIGNMENT, Associativity::Right, ForbiddenTokens::none())
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
2026-02-24 06:40:18 -03:00
|
|
|
pub(crate) fn parse_expression(
|
|
|
|
|
&mut self,
|
|
|
|
|
min_precedence: i32,
|
|
|
|
|
associativity: Associativity,
|
|
|
|
|
forbidden: ForbiddenTokens,
|
|
|
|
|
) -> Expression {
|
2026-02-23 07:50:46 -03:00
|
|
|
if self.match_unary_prefixed_expression() {
|
|
|
|
|
let start = self.position();
|
|
|
|
|
let expression = self.parse_unary_prefixed_expression();
|
|
|
|
|
|
|
|
|
|
// https://tc39.es/ecma262/#sec-exp-operator
|
|
|
|
|
// ExponentiationExpression :
|
|
|
|
|
// UnaryExpression
|
|
|
|
|
// UpdateExpression `**` ExponentiationExpression
|
|
|
|
|
// NB: UnaryExpression cannot be the base of `**`, only UpdateExpression can.
|
|
|
|
|
// This prevents ambiguity like `-x ** y` (is it `(-x) ** y` or `-(x ** y)`?).
|
|
|
|
|
// ++x ** y and --x ** y are valid (they're UpdateExpressions, not UnaryExpressions).
|
2026-04-18 00:37:40 -03:00
|
|
|
if self.match_token(TokenType::DoubleAsterisk) && !Self::is_update_expression(&expression) {
|
|
|
|
|
self.syntax_error("Unparenthesized unary expression can't appear on the left-hand side of '**'");
|
2026-02-24 06:40:18 -03:00
|
|
|
}
|
|
|
|
|
|
2026-04-18 00:37:40 -03:00
|
|
|
return self.continue_parse_expression(start, expression, min_precedence, associativity, forbidden);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let lhs_start = self.position();
|
2026-03-12 20:29:48 -03:00
|
|
|
self.last_primary_was_parenthesized = false;
|
2026-02-23 07:50:46 -03:00
|
|
|
let (expression, should_continue) = self.parse_primary_expression(min_precedence);
|
|
|
|
|
|
|
|
|
|
// C++ checks for freestanding `arguments` references here (after
|
|
|
|
|
// parse_primary_expression), NOT during consume(). This avoids
|
|
|
|
|
// falsely flagging parameter names like `function f(arguments)`.
|
2026-02-24 08:36:14 -03:00
|
|
|
if let ExpressionKind::Identifier(ref id) = expression.inner
|
|
|
|
|
&& id.name == utf16!("arguments")
|
2026-02-24 08:45:48 -03:00
|
|
|
{
|
2026-03-19 21:57:05 -03:00
|
|
|
// https://tc39.es/ecma262/#sec-class-static-initialization-blocks
|
|
|
|
|
// It is a Syntax Error if ContainsArguments of ClassStaticBlockBody is true.
|
|
|
|
|
if self.flags.in_class_static_init_block {
|
2026-04-18 00:37:40 -03:00
|
|
|
self.syntax_error("'arguments' is not allowed in class static initialization blocks");
|
|
|
|
|
} else if !self.flags.strict_mode && !self.scope_collector.has_declaration_in_current_function(&id.name) {
|
2026-03-19 21:57:05 -03:00
|
|
|
self.scope_collector
|
|
|
|
|
.set_contains_access_to_arguments_object_in_non_strict_mode();
|
|
|
|
|
}
|
2026-02-24 08:45:48 -03:00
|
|
|
}
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
if !should_continue {
|
|
|
|
|
// Yield/Await expressions don't participate in secondary expression
|
|
|
|
|
// parsing (e.g. member access), but they DO participate in comma
|
|
|
|
|
// expressions (e.g. `yield 1, yield 2`). Check for comma here.
|
2026-04-18 00:37:40 -03:00
|
|
|
let expression = self.parse_comma_expression(lhs_start, expression, min_precedence, forbidden);
|
2026-03-17 09:57:24 -03:00
|
|
|
self.report_invalid_private_identifier_usage(&expression);
|
|
|
|
|
return expression;
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let expression = self.parse_tagged_template_literals(lhs_start, expression);
|
|
|
|
|
|
2026-04-18 00:37:40 -03:00
|
|
|
self.continue_parse_expression(lhs_start, expression, min_precedence, associativity, forbidden)
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn continue_parse_expression(
|
|
|
|
|
&mut self,
|
|
|
|
|
lhs_start: Position,
|
|
|
|
|
mut expression: Expression,
|
|
|
|
|
min_precedence: i32,
|
|
|
|
|
associativity: Associativity,
|
|
|
|
|
mut forbidden: ForbiddenTokens,
|
|
|
|
|
) -> Expression {
|
|
|
|
|
let original_forbidden = forbidden;
|
2026-03-12 20:29:48 -03:00
|
|
|
let mut lhs_is_parenthesized = self.last_primary_was_parenthesized;
|
|
|
|
|
self.last_primary_was_parenthesized = false;
|
2026-02-23 07:50:46 -03:00
|
|
|
while self.match_secondary_expression(&forbidden) {
|
|
|
|
|
let new_precedence = Self::operator_precedence(self.current_token_type());
|
|
|
|
|
if new_precedence < min_precedence {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if new_precedence == min_precedence && associativity == Associativity::Left {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-24 06:40:18 -03:00
|
|
|
let result = self.parse_secondary_expression(
|
|
|
|
|
lhs_start,
|
|
|
|
|
expression,
|
|
|
|
|
new_precedence,
|
|
|
|
|
original_forbidden,
|
2026-03-12 20:29:48 -03:00
|
|
|
lhs_is_parenthesized,
|
2026-02-24 06:40:18 -03:00
|
|
|
);
|
2026-03-12 20:29:48 -03:00
|
|
|
lhs_is_parenthesized = false;
|
2026-02-23 07:50:46 -03:00
|
|
|
expression = result.0;
|
|
|
|
|
forbidden = forbidden.merge(result.1);
|
|
|
|
|
|
|
|
|
|
// Tagged template literals bind tighter than any operator, so we
|
|
|
|
|
// consume them eagerly after each secondary expression — but NOT
|
|
|
|
|
// after update expressions (x++`template` is not valid).
|
|
|
|
|
if !Self::is_update_expression(&expression) {
|
|
|
|
|
expression = self.parse_tagged_template_literals(lhs_start, expression);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-18 00:37:40 -03:00
|
|
|
let expression = self.parse_comma_expression(lhs_start, expression, min_precedence, forbidden);
|
2026-03-17 09:57:24 -03:00
|
|
|
self.report_invalid_private_identifier_usage(&expression);
|
|
|
|
|
expression
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn parse_comma_expression(
|
|
|
|
|
&mut self,
|
|
|
|
|
start: Position,
|
|
|
|
|
expression: Expression,
|
|
|
|
|
min_precedence: i32,
|
|
|
|
|
forbidden: ForbiddenTokens,
|
|
|
|
|
) -> Expression {
|
2026-04-18 00:37:40 -03:00
|
|
|
if min_precedence <= 1 && self.match_token(TokenType::Comma) && forbidden.allows(TokenType::Comma) {
|
2026-03-17 09:57:24 -03:00
|
|
|
self.report_invalid_private_identifier_usage(&expression);
|
2026-02-23 07:50:46 -03:00
|
|
|
let mut expressions = vec![expression];
|
|
|
|
|
while self.match_token(TokenType::Comma) {
|
|
|
|
|
self.consume();
|
2026-03-17 09:57:24 -03:00
|
|
|
let expression = self.parse_assignment_expression();
|
|
|
|
|
self.report_invalid_private_identifier_usage(&expression);
|
|
|
|
|
expressions.push(expression);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-03-22 14:47:08 -03:00
|
|
|
return self.expression(start, ExpressionKind::Sequence(Box::new(expressions)));
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
expression
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-17 09:57:24 -03:00
|
|
|
fn report_invalid_private_identifier_usage(&mut self, expression: &Expression) {
|
|
|
|
|
if matches!(expression.inner, ExpressionKind::PrivateIdentifier(_)) {
|
|
|
|
|
self.syntax_error("Private identifier must be followed by 'in'");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-23 07:50:46 -03:00
|
|
|
/// Parse a primary expression (literal, identifier, `this`, etc.).
|
|
|
|
|
/// Returns `(expression, should_continue)` — `false` means the caller
|
|
|
|
|
/// should not attempt to parse a secondary expression (e.g. arrow).
|
|
|
|
|
fn parse_primary_expression(&mut self, min_precedence: i32) -> (Expression, bool) {
|
|
|
|
|
let start = self.position();
|
|
|
|
|
let token = self.current_token().clone();
|
|
|
|
|
|
|
|
|
|
match token.token_type {
|
|
|
|
|
TokenType::ParenOpen => {
|
|
|
|
|
let paren_start = self.position();
|
|
|
|
|
self.consume_token(TokenType::ParenOpen);
|
2026-04-18 00:37:40 -03:00
|
|
|
if let Some(arrow) = self.try_parse_arrow_function_expression(true, false, Some(paren_start)) {
|
2026-02-23 07:50:46 -03:00
|
|
|
return (arrow, false);
|
|
|
|
|
}
|
|
|
|
|
if self.match_token(TokenType::ParenClose) {
|
|
|
|
|
self.syntax_error("Unexpected token )");
|
|
|
|
|
self.consume();
|
|
|
|
|
return (self.expression(start, ExpressionKind::Error), true);
|
|
|
|
|
}
|
2026-03-17 09:57:25 -03:00
|
|
|
let mut expression = self.parse_expression_any();
|
2026-02-23 07:50:46 -03:00
|
|
|
self.consume_token(TokenType::ParenClose);
|
2026-03-17 09:57:25 -03:00
|
|
|
if let ExpressionKind::New(ref mut new_expression) = expression.inner {
|
|
|
|
|
// Mirrors C++ Parser: `(new Foo)` sets "inside grouping parens".
|
|
|
|
|
new_expression.is_inside_parens = true;
|
|
|
|
|
}
|
2026-03-12 20:29:48 -03:00
|
|
|
self.last_primary_was_parenthesized = true;
|
2026-02-23 07:50:46 -03:00
|
|
|
(expression, true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TokenType::This => {
|
|
|
|
|
self.consume();
|
|
|
|
|
self.scope_collector.set_uses_this();
|
|
|
|
|
(self.expression(start, ExpressionKind::This), true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TokenType::Class => {
|
|
|
|
|
let expression = self.parse_class_expression(false);
|
|
|
|
|
(expression, true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// https://tc39.es/ecma262/#sec-super-keyword
|
|
|
|
|
// SuperProperty : `super` `.` IdentifierName
|
|
|
|
|
// | `super` `[` Expression `]`
|
|
|
|
|
// SuperCall : `super` Arguments
|
|
|
|
|
// NB: `super` must be followed by `.`, `[`, or `(`; bare `super` is invalid.
|
|
|
|
|
TokenType::Super => {
|
|
|
|
|
self.consume();
|
|
|
|
|
// C++ creates SuperCall in parse_call_expression which does
|
|
|
|
|
// push_start() after `super` is consumed, so position is at `(`.
|
|
|
|
|
let after_super = self.position();
|
|
|
|
|
if self.scope_collector.has_current_scope() {
|
|
|
|
|
self.scope_collector.set_uses_new_target();
|
|
|
|
|
}
|
|
|
|
|
if self.match_token(TokenType::ParenOpen) {
|
|
|
|
|
if !self.flags.allow_super_constructor_call {
|
|
|
|
|
self.syntax_error("'super' keyword unexpected here");
|
|
|
|
|
}
|
|
|
|
|
let arguments = self.parse_arguments();
|
2026-02-24 06:40:18 -03:00
|
|
|
(
|
|
|
|
|
self.expression(
|
|
|
|
|
after_super,
|
2026-03-22 13:31:57 -03:00
|
|
|
ExpressionKind::SuperCall(Box::new(SuperCallData {
|
2026-02-24 06:40:18 -03:00
|
|
|
arguments,
|
|
|
|
|
is_synthetic: false,
|
2026-03-22 13:31:57 -03:00
|
|
|
})),
|
2026-02-24 06:40:18 -03:00
|
|
|
),
|
|
|
|
|
true,
|
|
|
|
|
)
|
2026-04-18 00:37:40 -03:00
|
|
|
} else if self.match_token(TokenType::Period) || self.match_token(TokenType::BracketOpen) {
|
2026-02-23 07:50:46 -03:00
|
|
|
if !self.flags.allow_super_property_lookup {
|
|
|
|
|
self.syntax_error("'super' keyword unexpected here");
|
|
|
|
|
}
|
|
|
|
|
(self.expression(start, ExpressionKind::Super), true)
|
|
|
|
|
} else {
|
|
|
|
|
self.syntax_error("'super' keyword unexpected here");
|
|
|
|
|
(self.expression(start, ExpressionKind::Super), true)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TokenType::NumericLiteral => {
|
|
|
|
|
let token = self.consume_and_validate_numeric_literal();
|
|
|
|
|
let value_str = self.token_value(&token);
|
|
|
|
|
let value = parse_numeric_value(value_str);
|
2026-04-18 00:37:40 -03:00
|
|
|
(self.expression(start, ExpressionKind::NumericLiteral(value)), true)
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TokenType::BigIntLiteral => {
|
|
|
|
|
let token = self.consume();
|
|
|
|
|
let value = self.token_value(&token);
|
|
|
|
|
// Store the raw value including the 'n' suffix, matching C++.
|
2026-02-24 06:40:18 -03:00
|
|
|
let value_utf8: String = value
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|&c| {
|
2026-04-18 00:37:40 -03:00
|
|
|
assert!(c < 128, "BigIntLiteral should only contain ASCII characters");
|
2026-02-24 06:40:18 -03:00
|
|
|
c as u8 as char
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
(
|
2026-03-22 14:38:20 -03:00
|
|
|
self.expression(start, ExpressionKind::BigIntLiteral(Box::new(value_utf8))),
|
2026-02-24 06:40:18 -03:00
|
|
|
true,
|
|
|
|
|
)
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TokenType::BoolLiteral => {
|
|
|
|
|
let token = self.consume();
|
|
|
|
|
let value = self.token_value(&token);
|
|
|
|
|
let is_true = value == utf16!("true");
|
2026-04-18 00:37:40 -03:00
|
|
|
(self.expression(start, ExpressionKind::BooleanLiteral(is_true)), true)
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TokenType::StringLiteral => {
|
|
|
|
|
let token = self.consume();
|
|
|
|
|
// C++ calls consume() before push_start() for StringLiteral,
|
|
|
|
|
// so its position is the token AFTER the string.
|
|
|
|
|
let after_string = self.position();
|
|
|
|
|
let (value, has_octal) = self.parse_string_value(&token);
|
|
|
|
|
if has_octal {
|
|
|
|
|
if self.flags.strict_mode {
|
2026-04-18 00:37:40 -03:00
|
|
|
self.syntax_error("Octal escape sequence in string literal not allowed in strict mode");
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
|
|
|
|
self.flags.string_legacy_octal_escape_sequence_in_scope = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-24 06:40:18 -03:00
|
|
|
(
|
2026-03-22 14:37:12 -03:00
|
|
|
self.expression(after_string, ExpressionKind::StringLiteral(Box::new(value))),
|
2026-02-24 06:40:18 -03:00
|
|
|
true,
|
|
|
|
|
)
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TokenType::NullLiteral => {
|
|
|
|
|
self.consume();
|
|
|
|
|
(self.expression(start, ExpressionKind::NullLiteral), true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TokenType::CurlyOpen => {
|
|
|
|
|
let expression = self.parse_object_expression();
|
|
|
|
|
(expression, true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TokenType::BracketOpen => {
|
|
|
|
|
let expression = self.parse_array_expression();
|
|
|
|
|
(expression, true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TokenType::Function => {
|
|
|
|
|
let expression = self.parse_function_expression();
|
|
|
|
|
(expression, true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// https://tc39.es/ecma262/#sec-async-function-definitions
|
|
|
|
|
// `async` [no LineTerminator here] `function` starts an async function expression.
|
|
|
|
|
// `async` [no LineTerminator here] ArrowParameters `=>` starts an async arrow.
|
|
|
|
|
// Otherwise, `async` is just an identifier reference.
|
|
|
|
|
TokenType::Async => {
|
|
|
|
|
let next = self.next_token();
|
|
|
|
|
if next.token_type == TokenType::Function && !next.trivia_has_line_terminator {
|
|
|
|
|
let expression = self.parse_function_expression();
|
|
|
|
|
return (expression, true);
|
|
|
|
|
}
|
2026-04-18 00:37:40 -03:00
|
|
|
if let Some(arrow) =
|
|
|
|
|
self.try_parse_arrow_function_expression(next.token_type == TokenType::ParenOpen, true, None)
|
|
|
|
|
{
|
2026-02-23 07:50:46 -03:00
|
|
|
return (arrow, false);
|
|
|
|
|
}
|
2026-04-18 00:37:40 -03:00
|
|
|
self.arrow_function_failed_positions.remove(&(start.offset as usize));
|
2026-03-17 09:57:23 -03:00
|
|
|
// `async => ...` is a regular arrow function with parameter name `async`
|
|
|
|
|
// (not an async arrow function).
|
|
|
|
|
if let Some(arrow) = self.try_parse_arrow_function_expression(false, false, None) {
|
|
|
|
|
return (arrow, false);
|
|
|
|
|
}
|
2026-02-23 07:50:46 -03:00
|
|
|
let token = self.consume_and_check_identifier();
|
2026-04-05 18:52:55 -03:00
|
|
|
let id = self.make_identifier(start, self.token_identifier_name(&token));
|
2026-04-05 19:38:51 -03:00
|
|
|
self.scope_collector.register_identifier(id.clone(), None);
|
2026-02-23 07:50:46 -03:00
|
|
|
(self.expression(start, ExpressionKind::Identifier(id)), true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TokenType::TemplateLiteralStart => {
|
|
|
|
|
let expression = self.parse_template_literal(false);
|
|
|
|
|
(expression, true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TokenType::New => {
|
|
|
|
|
let expression = self.parse_new_expression();
|
|
|
|
|
(expression, true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// https://tc39.es/ecma262/#sec-import-calls
|
|
|
|
|
// ImportCall : `import` `(` AssignmentExpression `,`? `)
|
|
|
|
|
// | `import` `(` AssignmentExpression `,` AssignmentExpression `,`? `)`
|
|
|
|
|
// https://tc39.es/ecma262/#sec-import-meta
|
|
|
|
|
// `import.meta` is only valid in module code.
|
|
|
|
|
TokenType::Import => {
|
|
|
|
|
self.consume();
|
|
|
|
|
if self.match_token(TokenType::Period) {
|
|
|
|
|
self.consume();
|
|
|
|
|
let meta_token = self.current_token.clone();
|
|
|
|
|
self.consume_token(TokenType::Identifier);
|
|
|
|
|
let meta_utf16: [u16; 4] = [ch(b'm'), ch(b'e'), ch(b't'), ch(b'a')];
|
|
|
|
|
if self.token_original_value(&meta_token) != meta_utf16 {
|
|
|
|
|
self.syntax_error("Expected 'meta' after 'import.'");
|
|
|
|
|
}
|
|
|
|
|
if self.program_type != ProgramType::Module {
|
|
|
|
|
self.syntax_error("import.meta is only allowed in modules");
|
|
|
|
|
}
|
2026-02-24 06:40:18 -03:00
|
|
|
(
|
2026-04-18 00:37:40 -03:00
|
|
|
self.expression(start, ExpressionKind::MetaProperty(MetaPropertyType::ImportMeta)),
|
2026-02-24 06:40:18 -03:00
|
|
|
true,
|
|
|
|
|
)
|
2026-02-23 07:50:46 -03:00
|
|
|
} else if self.match_token(TokenType::ParenOpen) {
|
|
|
|
|
self.consume();
|
|
|
|
|
let specifier = self.parse_assignment_expression();
|
|
|
|
|
let options = if self.match_token(TokenType::Comma) {
|
|
|
|
|
self.consume();
|
|
|
|
|
if self.match_token(TokenType::ParenClose) {
|
|
|
|
|
None
|
|
|
|
|
} else {
|
|
|
|
|
let opts = self.parse_assignment_expression();
|
|
|
|
|
if self.match_token(TokenType::Comma) {
|
|
|
|
|
self.consume();
|
|
|
|
|
}
|
|
|
|
|
Some(Box::new(opts))
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
|
|
|
|
self.consume_token(TokenType::ParenClose);
|
2026-02-24 06:40:18 -03:00
|
|
|
(
|
|
|
|
|
self.expression(
|
|
|
|
|
start,
|
2026-03-22 15:26:00 -03:00
|
|
|
ExpressionKind::ImportCall(Box::new(ImportCallData {
|
2026-02-24 06:40:18 -03:00
|
|
|
specifier: Box::new(specifier),
|
|
|
|
|
options,
|
2026-03-22 15:26:00 -03:00
|
|
|
})),
|
2026-02-24 06:40:18 -03:00
|
|
|
),
|
|
|
|
|
true,
|
|
|
|
|
)
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
|
|
|
|
self.expected("'.' or '('");
|
|
|
|
|
(self.expression(start, ExpressionKind::Error), true)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// https://tc39.es/ecma262/#sec-generator-function-definitions
|
|
|
|
|
// YieldExpression : `yield`
|
|
|
|
|
// | `yield` [no LineTerminator here] AssignmentExpression
|
|
|
|
|
// | `yield` [no LineTerminator here] `*` AssignmentExpression
|
|
|
|
|
// YieldExpression is at AssignmentExpression level (precedence 3).
|
|
|
|
|
// When min_precedence is higher (e.g. void/typeof at 17), yield must
|
|
|
|
|
// be treated as an identifier, not a yield expression.
|
|
|
|
|
TokenType::Yield if self.flags.in_generator_function_context && min_precedence <= 3 => {
|
|
|
|
|
let expression = self.parse_yield_expression();
|
|
|
|
|
(expression, false)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// https://tc39.es/ecma262/#sec-async-function-definitions
|
|
|
|
|
// AwaitExpression : `await` UnaryExpression
|
|
|
|
|
// NB: Unlike yield (AssignmentExpression level), await is at
|
|
|
|
|
// UnaryExpression level, so `await 1 + 2` is `(await 1) + 2`.
|
|
|
|
|
// We set should_continue=true to allow binary operators.
|
|
|
|
|
TokenType::Await if self.flags.await_expression_is_valid => {
|
|
|
|
|
let expression = self.parse_await_expression();
|
|
|
|
|
(expression, true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TokenType::PrivateIdentifier => {
|
|
|
|
|
let id = self.parse_private_identifier(start);
|
2026-02-24 06:40:18 -03:00
|
|
|
(
|
2026-03-22 14:45:16 -03:00
|
|
|
self.expression(start, ExpressionKind::PrivateIdentifier(Box::new(id))),
|
2026-02-24 06:40:18 -03:00
|
|
|
true,
|
|
|
|
|
)
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TokenType::RegexLiteral => {
|
|
|
|
|
let token = self.consume();
|
|
|
|
|
(self.parse_regex_literal(start, &token), true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TokenType::Slash | TokenType::SlashEquals => {
|
|
|
|
|
let token = self.lexer.force_slash_as_regex();
|
|
|
|
|
self.current_token = token;
|
|
|
|
|
let token = self.consume();
|
|
|
|
|
(self.parse_regex_literal(start, &token), true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_ => {
|
|
|
|
|
// NB: When Await/Yield guards above don't match, those tokens fall
|
|
|
|
|
// through here. match_identifier() may return false for Await in
|
|
|
|
|
// class static init blocks, but we still need to try arrow function
|
|
|
|
|
// parsing and identifier consumption (with appropriate errors).
|
|
|
|
|
// This matches C++'s "goto read_as_identifier" pattern.
|
2026-04-18 00:37:40 -03:00
|
|
|
if self.match_identifier() || self.match_token(TokenType::Await) || self.match_token(TokenType::Yield) {
|
|
|
|
|
if let Some(arrow) = self.try_parse_arrow_function_expression(false, false, None) {
|
2026-02-23 07:50:46 -03:00
|
|
|
return (arrow, false);
|
|
|
|
|
}
|
|
|
|
|
if self.match_token(TokenType::Await)
|
|
|
|
|
&& (self.program_type == ProgramType::Module
|
|
|
|
|
|| self.flags.await_expression_is_valid
|
|
|
|
|
|| self.flags.in_class_static_init_block)
|
|
|
|
|
{
|
2026-04-18 00:37:40 -03:00
|
|
|
self.syntax_error("'await' is not allowed as an identifier in this context");
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
if self.match_token(TokenType::Yield)
|
|
|
|
|
&& (self.flags.strict_mode || self.flags.in_generator_function_context)
|
|
|
|
|
{
|
2026-04-18 00:37:40 -03:00
|
|
|
self.syntax_error("'yield' is not allowed as an identifier in this context");
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
let token = self.consume_and_check_identifier();
|
2026-04-05 18:52:55 -03:00
|
|
|
let id = self.make_identifier(start, self.token_identifier_name(&token));
|
2026-04-05 19:38:51 -03:00
|
|
|
self.scope_collector.register_identifier(id.clone(), None);
|
2026-02-23 07:50:46 -03:00
|
|
|
(self.expression(start, ExpressionKind::Identifier(id)), true)
|
|
|
|
|
} else if self.match_token(TokenType::EscapedKeyword) {
|
|
|
|
|
self.syntax_error("Keyword must not contain escaped characters");
|
|
|
|
|
let token = self.consume_and_check_identifier();
|
2026-04-05 18:52:55 -03:00
|
|
|
let id = self.make_identifier(start, self.token_identifier_name(&token));
|
2026-04-05 19:38:51 -03:00
|
|
|
self.scope_collector.register_identifier(id.clone(), None);
|
2026-02-23 07:50:46 -03:00
|
|
|
(self.expression(start, ExpressionKind::Identifier(id)), true)
|
|
|
|
|
} else {
|
|
|
|
|
self.expected("primary expression");
|
|
|
|
|
self.consume();
|
|
|
|
|
(self.expression(start, ExpressionKind::Error), true)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn parse_regex_literal(&mut self, start: Position, token: &Token) -> Expression {
|
|
|
|
|
let value = self.token_value(token);
|
|
|
|
|
let pattern = if value.len() >= 2 {
|
|
|
|
|
value[1..value.len() - 1].to_vec()
|
|
|
|
|
} else {
|
|
|
|
|
value.to_vec()
|
|
|
|
|
};
|
|
|
|
|
let flags = if self.match_token(TokenType::RegexFlags) {
|
|
|
|
|
let ftok = self.consume();
|
|
|
|
|
self.token_value(&ftok).to_vec()
|
|
|
|
|
} else {
|
|
|
|
|
Vec::new()
|
|
|
|
|
};
|
|
|
|
|
self.validate_regex_flags(&flags);
|
2026-03-25 09:17:51 -03:00
|
|
|
let compiled_regex = match crate::bytecode::ffi::compile_regex(&pattern, &flags) {
|
|
|
|
|
Ok(handle) => Rc::new(CompiledRegex::new(handle)),
|
|
|
|
|
Err(msg) => {
|
|
|
|
|
self.syntax_error_at_position(&msg, start);
|
|
|
|
|
Rc::new(CompiledRegex::new(std::ptr::null_mut()))
|
|
|
|
|
}
|
|
|
|
|
};
|
2026-02-24 06:40:18 -03:00
|
|
|
self.expression(
|
|
|
|
|
start,
|
2026-03-22 14:22:20 -03:00
|
|
|
ExpressionKind::RegExpLiteral(Box::new(RegExpLiteralData {
|
2026-02-24 06:40:18 -03:00
|
|
|
pattern: pattern.into(),
|
|
|
|
|
flags: flags.into(),
|
2026-02-26 18:39:24 -03:00
|
|
|
compiled_regex,
|
2026-03-22 14:22:20 -03:00
|
|
|
})),
|
2026-02-24 06:40:18 -03:00
|
|
|
)
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
2026-02-24 06:40:18 -03:00
|
|
|
fn parse_secondary_expression(
|
|
|
|
|
&mut self,
|
|
|
|
|
_lhs_start: Position,
|
|
|
|
|
lhs: Expression,
|
|
|
|
|
min_precedence: i32,
|
|
|
|
|
forbidden: ForbiddenTokens,
|
2026-03-12 20:29:48 -03:00
|
|
|
lhs_is_parenthesized: bool,
|
2026-02-24 06:40:18 -03:00
|
|
|
) -> (Expression, ForbiddenTokens) {
|
2026-02-23 07:50:46 -03:00
|
|
|
let start = self.position();
|
|
|
|
|
let tt = self.current_token_type();
|
|
|
|
|
|
2026-03-17 09:57:24 -03:00
|
|
|
if matches!(lhs.inner, ExpressionKind::PrivateIdentifier(_)) && tt != TokenType::In {
|
|
|
|
|
self.syntax_error("Private identifier must be followed by 'in'");
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-23 07:50:46 -03:00
|
|
|
match tt {
|
|
|
|
|
// === Binary operators ===
|
2026-02-24 06:40:18 -03:00
|
|
|
TokenType::Plus
|
|
|
|
|
| TokenType::Minus
|
|
|
|
|
| TokenType::Asterisk
|
|
|
|
|
| TokenType::Slash
|
|
|
|
|
| TokenType::Percent
|
|
|
|
|
| TokenType::DoubleAsterisk
|
|
|
|
|
| TokenType::ShiftLeft
|
|
|
|
|
| TokenType::ShiftRight
|
|
|
|
|
| TokenType::UnsignedShiftRight
|
|
|
|
|
| TokenType::Ampersand
|
|
|
|
|
| TokenType::Caret
|
|
|
|
|
| TokenType::Pipe
|
|
|
|
|
| TokenType::LessThan
|
|
|
|
|
| TokenType::LessThanEquals
|
|
|
|
|
| TokenType::GreaterThan
|
|
|
|
|
| TokenType::GreaterThanEquals
|
|
|
|
|
| TokenType::EqualsEquals
|
|
|
|
|
| TokenType::ExclamationMarkEquals
|
|
|
|
|
| TokenType::EqualsEqualsEquals
|
|
|
|
|
| TokenType::ExclamationMarkEqualsEquals
|
|
|
|
|
| TokenType::Instanceof => {
|
2026-02-23 07:50:46 -03:00
|
|
|
let op = token_to_binary_op(tt);
|
|
|
|
|
self.consume();
|
2026-04-18 00:37:40 -03:00
|
|
|
let rhs = self.parse_expression(min_precedence, Self::operator_associativity(tt), forbidden);
|
2026-02-24 06:40:18 -03:00
|
|
|
(
|
|
|
|
|
self.expression(
|
|
|
|
|
start,
|
2026-03-22 14:54:18 -03:00
|
|
|
ExpressionKind::Binary(Box::new(BinaryExprData {
|
2026-02-24 06:40:18 -03:00
|
|
|
op,
|
|
|
|
|
lhs: Box::new(lhs),
|
|
|
|
|
rhs: Box::new(rhs),
|
2026-03-22 14:54:18 -03:00
|
|
|
})),
|
2026-03-20 06:02:46 -03:00
|
|
|
),
|
|
|
|
|
ForbiddenTokens::none(),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TokenType::In => {
|
|
|
|
|
let is_private_in = matches!(&lhs.inner, ExpressionKind::PrivateIdentifier(_));
|
|
|
|
|
self.consume();
|
2026-04-18 00:37:40 -03:00
|
|
|
let rhs = self.parse_expression(min_precedence, Self::operator_associativity(tt), forbidden);
|
2026-03-20 06:02:46 -03:00
|
|
|
if is_private_in
|
|
|
|
|
&& let ExpressionKind::Function(fn_id) = &rhs.inner
|
|
|
|
|
&& self.function_table.get(*fn_id).is_arrow_function
|
|
|
|
|
{
|
2026-04-18 00:37:40 -03:00
|
|
|
self.syntax_error(
|
|
|
|
|
"Arrow function is not allowed as the right-hand side of a private 'in' expression",
|
|
|
|
|
);
|
2026-03-20 06:02:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
(
|
|
|
|
|
self.expression(
|
|
|
|
|
start,
|
2026-03-22 14:54:18 -03:00
|
|
|
ExpressionKind::Binary(Box::new(BinaryExprData {
|
2026-03-20 06:02:46 -03:00
|
|
|
op: BinaryOp::In,
|
|
|
|
|
lhs: Box::new(lhs),
|
|
|
|
|
rhs: Box::new(rhs),
|
2026-03-22 14:54:18 -03:00
|
|
|
})),
|
2026-02-24 06:40:18 -03:00
|
|
|
),
|
|
|
|
|
ForbiddenTokens::none(),
|
|
|
|
|
)
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// === Logical operators ===
|
|
|
|
|
// https://tc39.es/ecma262/#sec-binary-logical-operators
|
|
|
|
|
// It is a Syntax Error if ShortCircuitExpression includes both
|
|
|
|
|
// LogicalORExpression (||/&&) and CoalesceExpression (??), since
|
|
|
|
|
// their precedence is ambiguous without explicit parentheses.
|
|
|
|
|
TokenType::DoubleAmpersand => {
|
|
|
|
|
self.consume();
|
|
|
|
|
let new_forbidden = forbidden.forbid(&[TokenType::DoubleQuestionMark]);
|
|
|
|
|
let rhs = self.parse_expression(min_precedence, Associativity::Left, new_forbidden);
|
2026-02-24 06:40:18 -03:00
|
|
|
(
|
|
|
|
|
self.expression(
|
|
|
|
|
start,
|
2026-03-22 14:56:01 -03:00
|
|
|
ExpressionKind::Logical(Box::new(LogicalExprData {
|
2026-02-24 06:40:18 -03:00
|
|
|
op: LogicalOp::And,
|
|
|
|
|
lhs: Box::new(lhs),
|
|
|
|
|
rhs: Box::new(rhs),
|
2026-03-22 14:56:01 -03:00
|
|
|
})),
|
2026-02-24 06:40:18 -03:00
|
|
|
),
|
|
|
|
|
new_forbidden,
|
|
|
|
|
)
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
TokenType::DoublePipe => {
|
|
|
|
|
self.consume();
|
|
|
|
|
let new_forbidden = forbidden.forbid(&[TokenType::DoubleQuestionMark]);
|
|
|
|
|
let rhs = self.parse_expression(min_precedence, Associativity::Left, new_forbidden);
|
2026-02-24 06:40:18 -03:00
|
|
|
(
|
|
|
|
|
self.expression(
|
|
|
|
|
start,
|
2026-03-22 14:56:01 -03:00
|
|
|
ExpressionKind::Logical(Box::new(LogicalExprData {
|
2026-02-24 06:40:18 -03:00
|
|
|
op: LogicalOp::Or,
|
|
|
|
|
lhs: Box::new(lhs),
|
|
|
|
|
rhs: Box::new(rhs),
|
2026-03-22 14:56:01 -03:00
|
|
|
})),
|
2026-02-24 06:40:18 -03:00
|
|
|
),
|
|
|
|
|
new_forbidden,
|
|
|
|
|
)
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
TokenType::DoubleQuestionMark => {
|
|
|
|
|
self.consume();
|
2026-04-18 00:37:40 -03:00
|
|
|
let new_forbidden = forbidden.forbid(&[TokenType::DoubleAmpersand, TokenType::DoublePipe]);
|
2026-02-23 07:50:46 -03:00
|
|
|
let rhs = self.parse_expression(min_precedence, Associativity::Left, new_forbidden);
|
2026-02-24 06:40:18 -03:00
|
|
|
(
|
|
|
|
|
self.expression(
|
|
|
|
|
start,
|
2026-03-22 14:56:01 -03:00
|
|
|
ExpressionKind::Logical(Box::new(LogicalExprData {
|
2026-02-24 06:40:18 -03:00
|
|
|
op: LogicalOp::NullishCoalescing,
|
|
|
|
|
lhs: Box::new(lhs),
|
|
|
|
|
rhs: Box::new(rhs),
|
2026-03-22 14:56:01 -03:00
|
|
|
})),
|
2026-02-24 06:40:18 -03:00
|
|
|
),
|
|
|
|
|
new_forbidden,
|
|
|
|
|
)
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// === Assignment ===
|
2026-02-24 06:40:18 -03:00
|
|
|
TokenType::Equals
|
|
|
|
|
| TokenType::PlusEquals
|
|
|
|
|
| TokenType::MinusEquals
|
|
|
|
|
| TokenType::DoubleAsteriskEquals
|
|
|
|
|
| TokenType::AsteriskEquals
|
|
|
|
|
| TokenType::SlashEquals
|
|
|
|
|
| TokenType::PercentEquals
|
|
|
|
|
| TokenType::ShiftLeftEquals
|
|
|
|
|
| TokenType::ShiftRightEquals
|
|
|
|
|
| TokenType::UnsignedShiftRightEquals
|
|
|
|
|
| TokenType::AmpersandEquals
|
|
|
|
|
| TokenType::CaretEquals
|
|
|
|
|
| TokenType::PipeEquals
|
|
|
|
|
| TokenType::DoubleAmpersandEquals
|
|
|
|
|
| TokenType::DoublePipeEquals
|
2026-02-23 07:50:46 -03:00
|
|
|
| TokenType::DoubleQuestionMarkEquals => {
|
|
|
|
|
let op = token_to_assignment_op(tt);
|
2026-02-24 06:40:18 -03:00
|
|
|
if op == AssignmentOp::Assignment
|
2026-03-12 20:29:48 -03:00
|
|
|
&& !lhs_is_parenthesized
|
2026-02-24 06:40:18 -03:00
|
|
|
&& (Self::is_object_expression(&lhs) || Self::is_array_expression(&lhs))
|
|
|
|
|
{
|
2026-02-23 07:50:46 -03:00
|
|
|
// Save pattern_bound_names so that an outer binding
|
|
|
|
|
// pattern parse in progress doesn't lose its entries.
|
|
|
|
|
let saved_bound_names = std::mem::take(&mut self.pattern_bound_names);
|
|
|
|
|
// Use the expression's own range start, not the outer
|
|
|
|
|
// lhs_start. When the expression is parenthesized (e.g.
|
|
|
|
|
// `([a,b]) = ...`), lhs_start points to `(` but we need
|
|
|
|
|
// to re-lex from `[` to correctly synthesize the pattern.
|
2026-02-27 06:18:31 -03:00
|
|
|
|
|
|
|
|
let binding_pattern = self.synthesize_binding_pattern(lhs.range.start);
|
|
|
|
|
|
|
|
|
|
// Register synthesized identifiers with the scope collector so
|
|
|
|
|
// they get resolved as locals during analyze().
|
2026-03-19 22:04:41 -03:00
|
|
|
let bound_names: Vec<_> = self.pattern_bound_names.drain(..).collect();
|
|
|
|
|
for (name, id) in &bound_names {
|
|
|
|
|
self.check_identifier_name_for_assignment_validity(name, false);
|
2026-04-05 19:38:51 -03:00
|
|
|
self.scope_collector.register_identifier(id.clone(), None);
|
2026-02-24 08:45:48 -03:00
|
|
|
}
|
2026-02-27 06:18:31 -03:00
|
|
|
self.pattern_bound_names = saved_bound_names;
|
|
|
|
|
self.consume();
|
2026-04-18 00:37:40 -03:00
|
|
|
let rhs = self.parse_expression(min_precedence, Associativity::Right, forbidden);
|
2026-02-27 06:18:31 -03:00
|
|
|
return (
|
|
|
|
|
self.expression(
|
|
|
|
|
start,
|
2026-03-22 15:02:14 -03:00
|
|
|
ExpressionKind::Assignment(Box::new(AssignmentExprData {
|
2026-02-27 06:18:31 -03:00
|
|
|
op,
|
|
|
|
|
lhs: AssignmentLhs::Pattern(binding_pattern),
|
|
|
|
|
rhs: Box::new(rhs),
|
2026-03-22 15:02:14 -03:00
|
|
|
})),
|
2026-02-27 06:18:31 -03:00
|
|
|
),
|
|
|
|
|
ForbiddenTokens::none(),
|
|
|
|
|
);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-02-24 06:40:18 -03:00
|
|
|
let allow_call = !matches!(
|
|
|
|
|
tt,
|
|
|
|
|
TokenType::DoubleAmpersandEquals
|
|
|
|
|
| TokenType::DoublePipeEquals
|
|
|
|
|
| TokenType::DoubleQuestionMarkEquals
|
|
|
|
|
);
|
2026-03-12 20:29:48 -03:00
|
|
|
if !Self::is_simple_assignment_target(&lhs, allow_call, self.flags.strict_mode) {
|
2026-02-23 07:50:46 -03:00
|
|
|
self.syntax_error("Invalid left-hand side in assignment");
|
|
|
|
|
}
|
|
|
|
|
if let ExpressionKind::Identifier(ref id) = lhs.inner {
|
|
|
|
|
self.check_identifier_name_for_assignment_validity(&id.name, false);
|
|
|
|
|
}
|
|
|
|
|
self.consume();
|
|
|
|
|
let rhs = self.parse_expression(min_precedence, Associativity::Right, forbidden);
|
2026-02-24 06:40:18 -03:00
|
|
|
(
|
|
|
|
|
self.expression(
|
|
|
|
|
start,
|
2026-03-22 15:02:14 -03:00
|
|
|
ExpressionKind::Assignment(Box::new(AssignmentExprData {
|
2026-02-24 06:40:18 -03:00
|
|
|
op,
|
|
|
|
|
lhs: AssignmentLhs::Expression(Box::new(lhs)),
|
|
|
|
|
rhs: Box::new(rhs),
|
2026-03-22 15:02:14 -03:00
|
|
|
})),
|
2026-02-24 06:40:18 -03:00
|
|
|
),
|
|
|
|
|
ForbiddenTokens::none(),
|
|
|
|
|
)
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// === Ternary ===
|
|
|
|
|
TokenType::QuestionMark => {
|
|
|
|
|
self.consume();
|
|
|
|
|
let consequent = self.parse_assignment_expression();
|
|
|
|
|
self.consume_token(TokenType::Colon);
|
2026-04-18 00:37:40 -03:00
|
|
|
let alternate = self.parse_expression(PRECEDENCE_ASSIGNMENT, Associativity::Right, forbidden);
|
2026-02-24 06:40:18 -03:00
|
|
|
(
|
|
|
|
|
self.expression(
|
|
|
|
|
start,
|
2026-03-22 15:04:48 -03:00
|
|
|
ExpressionKind::Conditional(Box::new(ConditionalExprData {
|
2026-02-24 06:40:18 -03:00
|
|
|
test: Box::new(lhs),
|
|
|
|
|
consequent: Box::new(consequent),
|
|
|
|
|
alternate: Box::new(alternate),
|
2026-03-22 15:04:48 -03:00
|
|
|
})),
|
2026-02-24 06:40:18 -03:00
|
|
|
),
|
|
|
|
|
ForbiddenTokens::none(),
|
|
|
|
|
)
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// === Member access ===
|
|
|
|
|
TokenType::Period => {
|
|
|
|
|
self.consume();
|
|
|
|
|
if self.match_token(TokenType::PrivateIdentifier) {
|
2026-03-19 21:57:05 -03:00
|
|
|
// https://tc39.es/ecma262/#sec-static-semantics-early-errors
|
|
|
|
|
// It is a Syntax Error if MemberExpression is SuperProperty
|
|
|
|
|
// and the PrivateIdentifier is present.
|
|
|
|
|
if matches!(lhs.inner, ExpressionKind::Super) {
|
|
|
|
|
self.syntax_error("Cannot access private field or method via 'super'");
|
|
|
|
|
}
|
2026-04-26 15:37:30 -03:00
|
|
|
let property_start = self.position();
|
|
|
|
|
let id = self.parse_private_identifier(property_start);
|
|
|
|
|
let property = self.expression(property_start, ExpressionKind::PrivateIdentifier(Box::new(id)));
|
2026-02-24 06:40:18 -03:00
|
|
|
(
|
|
|
|
|
self.expression(
|
|
|
|
|
start,
|
2026-03-22 15:14:03 -03:00
|
|
|
ExpressionKind::Member(Box::new(MemberExprData {
|
2026-02-24 06:40:18 -03:00
|
|
|
object: Box::new(lhs),
|
|
|
|
|
property: Box::new(property),
|
|
|
|
|
computed: false,
|
2026-03-22 15:14:03 -03:00
|
|
|
})),
|
2026-02-24 06:40:18 -03:00
|
|
|
),
|
|
|
|
|
ForbiddenTokens::none(),
|
|
|
|
|
)
|
2026-02-23 07:50:46 -03:00
|
|
|
} else if self.match_identifier_name() {
|
2026-04-26 15:37:30 -03:00
|
|
|
let property_start = self.position();
|
2026-02-23 07:50:46 -03:00
|
|
|
let token = self.consume();
|
2026-04-26 15:37:30 -03:00
|
|
|
let property_identifier = self.make_identifier(property_start, self.token_identifier_name(&token));
|
|
|
|
|
let property = self.expression(property_start, ExpressionKind::Identifier(property_identifier));
|
2026-02-24 06:40:18 -03:00
|
|
|
(
|
|
|
|
|
self.expression(
|
|
|
|
|
start,
|
2026-03-22 15:14:03 -03:00
|
|
|
ExpressionKind::Member(Box::new(MemberExprData {
|
2026-02-24 06:40:18 -03:00
|
|
|
object: Box::new(lhs),
|
|
|
|
|
property: Box::new(property),
|
|
|
|
|
computed: false,
|
2026-03-22 15:14:03 -03:00
|
|
|
})),
|
2026-02-24 06:40:18 -03:00
|
|
|
),
|
|
|
|
|
ForbiddenTokens::none(),
|
|
|
|
|
)
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
|
|
|
|
self.expected("property name");
|
|
|
|
|
(lhs, ForbiddenTokens::none())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// === Computed member access ===
|
|
|
|
|
TokenType::BracketOpen => {
|
|
|
|
|
self.consume();
|
|
|
|
|
let property = self.parse_expression_any();
|
|
|
|
|
self.consume_token(TokenType::BracketClose);
|
2026-02-24 06:40:18 -03:00
|
|
|
(
|
|
|
|
|
self.expression(
|
|
|
|
|
start,
|
2026-03-22 15:14:03 -03:00
|
|
|
ExpressionKind::Member(Box::new(MemberExprData {
|
2026-02-24 06:40:18 -03:00
|
|
|
object: Box::new(lhs),
|
|
|
|
|
property: Box::new(property),
|
|
|
|
|
computed: true,
|
2026-03-22 15:14:03 -03:00
|
|
|
})),
|
2026-02-24 06:40:18 -03:00
|
|
|
),
|
|
|
|
|
ForbiddenTokens::none(),
|
|
|
|
|
)
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// === Call ===
|
|
|
|
|
TokenType::ParenOpen => {
|
|
|
|
|
let expression = self.parse_call_expression(lhs);
|
|
|
|
|
(expression, ForbiddenTokens::none())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// === Optional chaining ===
|
|
|
|
|
TokenType::QuestionMarkPeriod => {
|
2026-03-17 09:57:24 -03:00
|
|
|
// https://tc39.es/ecma262/#prod-OptionalExpression
|
|
|
|
|
// Optional chaining directly on an unparenthesized `new` expression is invalid:
|
|
|
|
|
// `new Foo?.bar` // SyntaxError
|
2026-03-17 09:57:24 -03:00
|
|
|
// while parenthesized/new-call forms remain valid:
|
2026-03-17 09:57:24 -03:00
|
|
|
// `(new Foo)?.bar`
|
2026-03-17 09:57:24 -03:00
|
|
|
// `new Foo()?.bar`
|
|
|
|
|
if let ExpressionKind::New(ref new_expression) = lhs.inner
|
2026-03-17 09:57:25 -03:00
|
|
|
&& !new_expression.is_parenthesized
|
2026-03-17 09:57:24 -03:00
|
|
|
&& !new_expression.is_inside_parens
|
|
|
|
|
{
|
2026-03-17 09:57:24 -03:00
|
|
|
self.syntax_error("'new' cannot be used with optional chaining");
|
|
|
|
|
self.consume();
|
|
|
|
|
return (lhs, ForbiddenTokens::none());
|
|
|
|
|
}
|
2026-02-23 07:50:46 -03:00
|
|
|
let chain = self.parse_optional_chain(start, lhs);
|
|
|
|
|
(chain, ForbiddenTokens::none())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// === Postfix ===
|
|
|
|
|
// https://tc39.es/ecma262/#sec-update-expressions
|
|
|
|
|
// UpdateExpression : LeftHandSideExpression [no LineTerminator here] `++`
|
|
|
|
|
// | LeftHandSideExpression [no LineTerminator here] `--`
|
|
|
|
|
// NB: The [no LineTerminator here] is enforced by match_secondary_expression
|
|
|
|
|
// which checks trivia_has_line_terminator for PlusPlus/MinusMinus.
|
|
|
|
|
TokenType::PlusPlus => {
|
2026-03-12 20:29:48 -03:00
|
|
|
if !Self::is_simple_assignment_target(&lhs, true, self.flags.strict_mode) {
|
2026-02-23 07:50:46 -03:00
|
|
|
self.syntax_error("Invalid left-hand side in postfix operation");
|
|
|
|
|
}
|
|
|
|
|
if let ExpressionKind::Identifier(ref id) = lhs.inner {
|
|
|
|
|
self.check_identifier_name_for_assignment_validity(&id.name, false);
|
|
|
|
|
}
|
|
|
|
|
self.consume();
|
2026-02-24 06:40:18 -03:00
|
|
|
(
|
|
|
|
|
self.expression(
|
|
|
|
|
start,
|
2026-03-22 15:00:22 -03:00
|
|
|
ExpressionKind::Update(Box::new(UpdateExprData {
|
2026-02-24 06:40:18 -03:00
|
|
|
op: UpdateOp::Increment,
|
|
|
|
|
argument: Box::new(lhs),
|
|
|
|
|
prefixed: false,
|
2026-03-22 15:00:22 -03:00
|
|
|
})),
|
2026-02-24 06:40:18 -03:00
|
|
|
),
|
|
|
|
|
ForbiddenTokens::none(),
|
|
|
|
|
)
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
TokenType::MinusMinus => {
|
2026-03-12 20:29:48 -03:00
|
|
|
if !Self::is_simple_assignment_target(&lhs, true, self.flags.strict_mode) {
|
2026-02-23 07:50:46 -03:00
|
|
|
self.syntax_error("Invalid left-hand side in postfix operation");
|
|
|
|
|
}
|
|
|
|
|
if let ExpressionKind::Identifier(ref id) = lhs.inner {
|
|
|
|
|
self.check_identifier_name_for_assignment_validity(&id.name, false);
|
|
|
|
|
}
|
|
|
|
|
self.consume();
|
2026-02-24 06:40:18 -03:00
|
|
|
(
|
|
|
|
|
self.expression(
|
|
|
|
|
start,
|
2026-03-22 15:00:22 -03:00
|
|
|
ExpressionKind::Update(Box::new(UpdateExprData {
|
2026-02-24 06:40:18 -03:00
|
|
|
op: UpdateOp::Decrement,
|
|
|
|
|
argument: Box::new(lhs),
|
|
|
|
|
prefixed: false,
|
2026-03-22 15:00:22 -03:00
|
|
|
})),
|
2026-02-24 06:40:18 -03:00
|
|
|
),
|
|
|
|
|
ForbiddenTokens::none(),
|
|
|
|
|
)
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_ => {
|
|
|
|
|
self.expected("secondary expression");
|
|
|
|
|
(lhs, ForbiddenTokens::none())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn parse_unary_prefixed_expression(&mut self) -> Expression {
|
|
|
|
|
let start = self.position();
|
|
|
|
|
let tt = self.current_token_type();
|
|
|
|
|
|
|
|
|
|
match tt {
|
|
|
|
|
TokenType::PlusPlus => {
|
|
|
|
|
self.consume();
|
2026-04-18 00:37:40 -03:00
|
|
|
let expression = self.parse_expression(PRECEDENCE_UNARY, Associativity::Right, ForbiddenTokens::none());
|
2026-03-12 20:29:48 -03:00
|
|
|
if !Self::is_simple_assignment_target(&expression, true, self.flags.strict_mode) {
|
2026-02-23 07:50:46 -03:00
|
|
|
self.syntax_error("Invalid left-hand side in prefix operation");
|
|
|
|
|
}
|
|
|
|
|
if let ExpressionKind::Identifier(ref id) = expression.inner {
|
|
|
|
|
self.check_identifier_name_for_assignment_validity(&id.name, false);
|
|
|
|
|
}
|
2026-02-24 06:40:18 -03:00
|
|
|
self.expression(
|
|
|
|
|
start,
|
2026-03-22 15:00:22 -03:00
|
|
|
ExpressionKind::Update(Box::new(UpdateExprData {
|
2026-02-24 06:40:18 -03:00
|
|
|
op: UpdateOp::Increment,
|
|
|
|
|
argument: Box::new(expression),
|
|
|
|
|
prefixed: true,
|
2026-03-22 15:00:22 -03:00
|
|
|
})),
|
2026-02-24 06:40:18 -03:00
|
|
|
)
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
TokenType::MinusMinus => {
|
|
|
|
|
self.consume();
|
2026-04-18 00:37:40 -03:00
|
|
|
let expression = self.parse_expression(PRECEDENCE_UNARY, Associativity::Right, ForbiddenTokens::none());
|
2026-03-12 20:29:48 -03:00
|
|
|
if !Self::is_simple_assignment_target(&expression, true, self.flags.strict_mode) {
|
2026-02-23 07:50:46 -03:00
|
|
|
self.syntax_error("Invalid left-hand side in prefix operation");
|
|
|
|
|
}
|
|
|
|
|
if let ExpressionKind::Identifier(ref id) = expression.inner {
|
|
|
|
|
self.check_identifier_name_for_assignment_validity(&id.name, false);
|
|
|
|
|
}
|
2026-02-24 06:40:18 -03:00
|
|
|
self.expression(
|
|
|
|
|
start,
|
2026-03-22 15:00:22 -03:00
|
|
|
ExpressionKind::Update(Box::new(UpdateExprData {
|
2026-02-24 06:40:18 -03:00
|
|
|
op: UpdateOp::Decrement,
|
|
|
|
|
argument: Box::new(expression),
|
|
|
|
|
prefixed: true,
|
2026-03-22 15:00:22 -03:00
|
|
|
})),
|
2026-02-24 06:40:18 -03:00
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
TokenType::ExclamationMark
|
|
|
|
|
| TokenType::Tilde
|
|
|
|
|
| TokenType::Plus
|
|
|
|
|
| TokenType::Minus
|
|
|
|
|
| TokenType::Typeof
|
|
|
|
|
| TokenType::Void => {
|
2026-02-23 07:50:46 -03:00
|
|
|
let op = match tt {
|
|
|
|
|
TokenType::ExclamationMark => UnaryOp::Not,
|
|
|
|
|
TokenType::Tilde => UnaryOp::BitwiseNot,
|
|
|
|
|
TokenType::Plus => UnaryOp::Plus,
|
|
|
|
|
TokenType::Minus => UnaryOp::Minus,
|
|
|
|
|
TokenType::Typeof => UnaryOp::Typeof,
|
|
|
|
|
_ => UnaryOp::Void,
|
|
|
|
|
};
|
|
|
|
|
self.consume();
|
2026-04-18 00:37:40 -03:00
|
|
|
let expression = self.parse_expression(PRECEDENCE_UNARY, Associativity::Right, ForbiddenTokens::none());
|
2026-03-17 09:57:24 -03:00
|
|
|
self.report_invalid_private_identifier_usage(&expression);
|
2026-02-24 06:40:18 -03:00
|
|
|
self.expression(
|
|
|
|
|
start,
|
|
|
|
|
ExpressionKind::Unary {
|
|
|
|
|
op,
|
|
|
|
|
operand: Box::new(expression),
|
|
|
|
|
},
|
|
|
|
|
)
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
// https://tc39.es/ecma262/#sec-delete-operator-static-semantics-early-errors
|
|
|
|
|
// It is a Syntax Error if the UnaryExpression is an IdentifierReference
|
|
|
|
|
// and the source text matched by the enclosing Script or Module is strict mode code.
|
|
|
|
|
TokenType::Delete => {
|
|
|
|
|
self.consume();
|
|
|
|
|
let rhs_start = self.position();
|
2026-04-18 00:37:40 -03:00
|
|
|
let expression = self.parse_expression(PRECEDENCE_UNARY, Associativity::Right, ForbiddenTokens::none());
|
2026-03-17 09:57:24 -03:00
|
|
|
self.report_invalid_private_identifier_usage(&expression);
|
2026-02-23 07:50:46 -03:00
|
|
|
if self.flags.strict_mode && Self::is_identifier(&expression) {
|
2026-02-24 06:40:18 -03:00
|
|
|
self.syntax_error_at(
|
|
|
|
|
"Delete of an unqualified identifier in strict mode.",
|
|
|
|
|
rhs_start.line,
|
|
|
|
|
rhs_start.column,
|
|
|
|
|
);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-03-22 15:14:03 -03:00
|
|
|
if let ExpressionKind::Member(ref data) = expression.inner
|
|
|
|
|
&& matches!(data.property.inner, ExpressionKind::PrivateIdentifier(_))
|
2026-02-24 08:45:48 -03:00
|
|
|
{
|
|
|
|
|
self.syntax_error("Private fields cannot be deleted");
|
|
|
|
|
}
|
2026-02-24 06:40:18 -03:00
|
|
|
self.expression(
|
|
|
|
|
start,
|
|
|
|
|
ExpressionKind::Unary {
|
|
|
|
|
op: UnaryOp::Delete,
|
|
|
|
|
operand: Box::new(expression),
|
|
|
|
|
},
|
|
|
|
|
)
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
_ => {
|
|
|
|
|
self.expected("unary expression");
|
|
|
|
|
self.consume();
|
|
|
|
|
self.expression(start, ExpressionKind::Error)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Parse a `new` expression, handling `new.target` and nested `new` calls.
|
|
|
|
|
// https://tc39.es/ecma262/#sec-new-operator
|
|
|
|
|
// MemberExpression : `new` MemberExpression Arguments
|
|
|
|
|
// NewExpression : `new` NewExpression
|
|
|
|
|
// https://tc39.es/ecma262/#sec-meta-properties
|
|
|
|
|
// NewTarget : `new` `.` `target`
|
|
|
|
|
fn parse_new_expression(&mut self) -> Expression {
|
|
|
|
|
let start = self.position();
|
|
|
|
|
self.consume_token(TokenType::New);
|
|
|
|
|
|
|
|
|
|
if self.match_token(TokenType::Period) {
|
|
|
|
|
self.consume();
|
|
|
|
|
let target_token = self.current_token.clone();
|
|
|
|
|
self.consume_token(TokenType::Identifier);
|
|
|
|
|
if self.token_original_value(&target_token) != utf16!("target") {
|
|
|
|
|
self.syntax_error("Expected 'target' after 'new.'");
|
|
|
|
|
}
|
|
|
|
|
// https://tc39.es/ecma262/#sec-new.target
|
|
|
|
|
// It is a Syntax Error if NewTarget is not enclosed, directly or indirectly
|
|
|
|
|
// (but not crossing function or class static initialization block boundaries),
|
|
|
|
|
// within a FunctionBody, ConciseBody, ClassStaticBlock, or ClassBody.
|
2026-03-19 22:01:19 -03:00
|
|
|
if !self.flags.new_target_is_valid && !self.in_eval_function_context {
|
2026-02-23 07:50:46 -03:00
|
|
|
self.syntax_error("'new.target' not allowed outside of a function");
|
|
|
|
|
}
|
|
|
|
|
if self.scope_collector.has_current_scope() {
|
|
|
|
|
self.scope_collector.set_uses_new_target();
|
|
|
|
|
}
|
2026-04-18 00:37:40 -03:00
|
|
|
return self.expression(start, ExpressionKind::MetaProperty(MetaPropertyType::NewTarget));
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let callee = if self.match_token(TokenType::New) {
|
|
|
|
|
self.parse_new_expression()
|
|
|
|
|
} else {
|
2026-04-18 00:37:40 -03:00
|
|
|
let forbidden = ForbiddenTokens::none().forbid(&[TokenType::ParenOpen, TokenType::QuestionMarkPeriod]);
|
2026-02-23 07:50:46 -03:00
|
|
|
self.parse_expression(PRECEDENCE_MEMBER, Associativity::Right, forbidden)
|
|
|
|
|
};
|
|
|
|
|
|
2026-03-22 15:26:00 -03:00
|
|
|
if matches!(callee.inner, ExpressionKind::ImportCall(_)) {
|
2026-02-23 07:50:46 -03:00
|
|
|
self.syntax_error("Cannot call new on dynamic import");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if self.match_token(TokenType::ParenOpen) {
|
|
|
|
|
let arguments = self.parse_arguments();
|
2026-02-24 06:40:18 -03:00
|
|
|
self.expression(
|
|
|
|
|
start,
|
2026-03-22 13:31:15 -03:00
|
|
|
ExpressionKind::New(Box::new(CallExpressionData {
|
2026-02-24 06:40:18 -03:00
|
|
|
callee: Box::new(callee),
|
|
|
|
|
arguments,
|
2026-03-17 09:57:25 -03:00
|
|
|
// Mirrors C++ InvocationStyle::Parenthesized for `new Foo(...)`.
|
|
|
|
|
is_parenthesized: true,
|
|
|
|
|
is_inside_parens: false,
|
2026-03-22 13:31:15 -03:00
|
|
|
})),
|
2026-02-24 06:40:18 -03:00
|
|
|
)
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
2026-02-24 06:40:18 -03:00
|
|
|
self.expression(
|
|
|
|
|
start,
|
2026-03-22 13:31:15 -03:00
|
|
|
ExpressionKind::New(Box::new(CallExpressionData {
|
2026-02-24 06:40:18 -03:00
|
|
|
callee: Box::new(callee),
|
|
|
|
|
arguments: Vec::new(),
|
|
|
|
|
is_parenthesized: false,
|
|
|
|
|
is_inside_parens: false,
|
2026-03-22 13:31:15 -03:00
|
|
|
})),
|
2026-02-24 06:40:18 -03:00
|
|
|
)
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Parse a call expression `callee(arguments...)`.
|
|
|
|
|
// https://tc39.es/ecma262/#sec-function-calls
|
|
|
|
|
// https://tc39.es/ecma262/#sec-function-calls-runtime-semantics-evaluation
|
|
|
|
|
// NB: A direct call to `eval` (bare identifier `eval` as callee) uses the
|
|
|
|
|
// running execution context's variable environment, not a fresh one.
|
|
|
|
|
pub(crate) fn parse_call_expression(&mut self, callee: Expression) -> Expression {
|
|
|
|
|
let start = self.position();
|
|
|
|
|
let arguments = self.parse_arguments();
|
|
|
|
|
// Check the actual callee expression kind, matching C++ which does
|
|
|
|
|
// is<Identifier>(callee) && callee.string() == "eval".
|
2026-02-24 08:36:14 -03:00
|
|
|
if let ExpressionKind::Identifier(ref id) = callee.inner
|
2026-02-24 08:45:48 -03:00
|
|
|
&& id.name == utf16!("eval")
|
|
|
|
|
{
|
|
|
|
|
self.scope_collector.set_contains_direct_call_to_eval();
|
|
|
|
|
self.scope_collector.set_uses_this();
|
|
|
|
|
}
|
2026-02-24 06:40:18 -03:00
|
|
|
self.expression(
|
|
|
|
|
start,
|
2026-03-22 13:28:05 -03:00
|
|
|
ExpressionKind::Call(Box::new(CallExpressionData {
|
2026-02-24 06:40:18 -03:00
|
|
|
callee: Box::new(callee),
|
|
|
|
|
arguments,
|
|
|
|
|
is_parenthesized: false,
|
|
|
|
|
is_inside_parens: false,
|
2026-03-22 13:28:05 -03:00
|
|
|
})),
|
2026-02-24 06:40:18 -03:00
|
|
|
)
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) fn parse_arguments(&mut self) -> Vec<CallArgument> {
|
|
|
|
|
self.consume_token(TokenType::ParenOpen);
|
|
|
|
|
let mut arguments = Vec::new();
|
|
|
|
|
|
|
|
|
|
while !self.match_token(TokenType::ParenClose) && !self.done() {
|
|
|
|
|
let is_spread = self.eat(TokenType::TripleDot);
|
|
|
|
|
let value = self.parse_assignment_expression();
|
|
|
|
|
arguments.push(CallArgument { value, is_spread });
|
|
|
|
|
if !self.match_token(TokenType::Comma) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
self.consume();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.consume_token(TokenType::ParenClose);
|
|
|
|
|
arguments
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Parse an optional chaining expression (`a?.b`, `a?.[x]`, `a?.()`).
|
|
|
|
|
// https://tc39.es/ecma262/#sec-optional-chains
|
|
|
|
|
// OptionalExpression : MemberExpression OptionalChain
|
|
|
|
|
// OptionalChain : `?.` Arguments
|
|
|
|
|
// | `?.` `[` Expression `]`
|
|
|
|
|
// | `?.` IdentifierName
|
|
|
|
|
// | `?.` TemplateLiteral -- NOTE: this is a syntax error (see below)
|
|
|
|
|
// | OptionalChain Arguments
|
|
|
|
|
// | OptionalChain `[` Expression `]`
|
|
|
|
|
// | OptionalChain `.` IdentifierName
|
|
|
|
|
// | OptionalChain TemplateLiteral -- also a syntax error
|
|
|
|
|
fn parse_optional_chain(&mut self, start: Position, base: Expression) -> Expression {
|
|
|
|
|
let mut references = Vec::new();
|
|
|
|
|
|
|
|
|
|
loop {
|
|
|
|
|
if self.match_token(TokenType::QuestionMarkPeriod) {
|
|
|
|
|
self.consume();
|
|
|
|
|
match self.current_token_type() {
|
|
|
|
|
TokenType::ParenOpen => {
|
|
|
|
|
let arguments = self.parse_arguments();
|
|
|
|
|
references.push(OptionalChainReference::Call {
|
|
|
|
|
arguments,
|
|
|
|
|
mode: OptionalChainMode::Optional,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
TokenType::BracketOpen => {
|
|
|
|
|
self.consume();
|
|
|
|
|
let expression = self.parse_expression_any();
|
|
|
|
|
self.consume_token(TokenType::BracketClose);
|
|
|
|
|
references.push(OptionalChainReference::ComputedReference {
|
|
|
|
|
expression: Box::new(expression),
|
|
|
|
|
mode: OptionalChainMode::Optional,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
TokenType::PrivateIdentifier => {
|
|
|
|
|
let property_start = self.position();
|
|
|
|
|
let id = self.parse_private_identifier(property_start);
|
|
|
|
|
references.push(OptionalChainReference::PrivateMemberReference {
|
|
|
|
|
private_identifier: id,
|
|
|
|
|
mode: OptionalChainMode::Optional,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
// https://tc39.es/ecma262/#sec-optional-chaining-chain-production
|
|
|
|
|
// It is a Syntax Error if any code matches this production:
|
|
|
|
|
// OptionalChain : `?.` TemplateLiteral
|
|
|
|
|
// Tagged templates cannot be used with optional chaining.
|
|
|
|
|
TokenType::TemplateLiteralStart => {
|
|
|
|
|
self.syntax_error("Invalid tagged template literal after ?.");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
_ => {
|
|
|
|
|
if self.match_identifier_name() {
|
|
|
|
|
let property_start = self.position();
|
|
|
|
|
let token = self.consume();
|
|
|
|
|
references.push(OptionalChainReference::MemberReference {
|
2026-04-18 00:37:40 -03:00
|
|
|
identifier: self.make_identifier(property_start, self.token_identifier_name(&token)),
|
2026-02-23 07:50:46 -03:00
|
|
|
mode: OptionalChainMode::Optional,
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
self.syntax_error("Invalid optional chain reference after ?.");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if self.match_token(TokenType::ParenOpen) {
|
|
|
|
|
let arguments = self.parse_arguments();
|
|
|
|
|
references.push(OptionalChainReference::Call {
|
|
|
|
|
arguments,
|
|
|
|
|
mode: OptionalChainMode::NotOptional,
|
|
|
|
|
});
|
|
|
|
|
} else if self.match_token(TokenType::Period) {
|
|
|
|
|
self.consume();
|
|
|
|
|
if self.match_token(TokenType::PrivateIdentifier) {
|
|
|
|
|
let property_start = self.position();
|
|
|
|
|
let id = self.parse_private_identifier(property_start);
|
|
|
|
|
references.push(OptionalChainReference::PrivateMemberReference {
|
|
|
|
|
private_identifier: id,
|
|
|
|
|
mode: OptionalChainMode::NotOptional,
|
|
|
|
|
});
|
|
|
|
|
} else if self.match_identifier_name() {
|
|
|
|
|
let property_start = self.position();
|
|
|
|
|
let token = self.consume();
|
|
|
|
|
references.push(OptionalChainReference::MemberReference {
|
2026-04-18 00:37:40 -03:00
|
|
|
identifier: self.make_identifier(property_start, self.token_identifier_name(&token)),
|
2026-02-23 07:50:46 -03:00
|
|
|
mode: OptionalChainMode::NotOptional,
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
self.expected("an identifier");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
} else if self.match_token(TokenType::TemplateLiteralStart) {
|
|
|
|
|
self.syntax_error("Invalid tagged template literal after optional chain");
|
|
|
|
|
break;
|
|
|
|
|
} else if self.match_token(TokenType::BracketOpen) {
|
|
|
|
|
self.consume();
|
|
|
|
|
let expression = self.parse_expression_any();
|
|
|
|
|
self.consume_token(TokenType::BracketClose);
|
|
|
|
|
references.push(OptionalChainReference::ComputedReference {
|
|
|
|
|
expression: Box::new(expression),
|
|
|
|
|
mode: OptionalChainMode::NotOptional,
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if self.done() {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-24 06:40:18 -03:00
|
|
|
self.expression(
|
|
|
|
|
start,
|
2026-03-22 15:16:49 -03:00
|
|
|
ExpressionKind::OptionalChain(Box::new(OptionalChainData {
|
2026-02-24 06:40:18 -03:00
|
|
|
base: Box::new(base),
|
|
|
|
|
references,
|
2026-03-22 15:16:49 -03:00
|
|
|
})),
|
2026-02-24 06:40:18 -03:00
|
|
|
)
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Parse a `yield` or `yield*` expression.
|
|
|
|
|
// https://tc39.es/ecma262/#sec-generator-function-definitions
|
|
|
|
|
// YieldExpression : `yield`
|
|
|
|
|
// | `yield` [no LineTerminator here] AssignmentExpression
|
|
|
|
|
// | `yield` [no LineTerminator here] `*` AssignmentExpression
|
|
|
|
|
// https://tc39.es/ecma262/#sec-generator-function-definitions-static-semantics-early-errors
|
|
|
|
|
// It is a Syntax Error if YieldExpression appears within FormalParameters.
|
|
|
|
|
fn parse_yield_expression(&mut self) -> Expression {
|
|
|
|
|
let start = self.position();
|
|
|
|
|
|
|
|
|
|
if self.flags.in_formal_parameter_context {
|
2026-04-18 00:37:40 -03:00
|
|
|
self.syntax_error("'Yield' expression is not allowed in formal parameters of generator function");
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.consume_token(TokenType::Yield);
|
|
|
|
|
|
|
|
|
|
if self.current_token.trivia_has_line_terminator {
|
2026-02-24 06:40:18 -03:00
|
|
|
return self.expression(
|
|
|
|
|
start,
|
2026-03-22 15:27:45 -03:00
|
|
|
ExpressionKind::Yield(Box::new(YieldExprData {
|
2026-02-24 06:40:18 -03:00
|
|
|
argument: None,
|
|
|
|
|
is_yield_from: false,
|
2026-03-22 15:27:45 -03:00
|
|
|
})),
|
2026-02-24 06:40:18 -03:00
|
|
|
);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let is_yield_from = self.match_token(TokenType::Asterisk);
|
|
|
|
|
if is_yield_from {
|
|
|
|
|
self.consume();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if is_yield_from || self.match_expression() || self.match_token(TokenType::Class) {
|
|
|
|
|
let argument = self.parse_assignment_expression();
|
2026-02-24 06:40:18 -03:00
|
|
|
self.expression(
|
|
|
|
|
start,
|
2026-03-22 15:27:45 -03:00
|
|
|
ExpressionKind::Yield(Box::new(YieldExprData {
|
2026-02-24 06:40:18 -03:00
|
|
|
argument: Some(Box::new(argument)),
|
|
|
|
|
is_yield_from,
|
2026-03-22 15:27:45 -03:00
|
|
|
})),
|
2026-02-24 06:40:18 -03:00
|
|
|
)
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
2026-02-24 06:40:18 -03:00
|
|
|
self.expression(
|
|
|
|
|
start,
|
2026-03-22 15:27:45 -03:00
|
|
|
ExpressionKind::Yield(Box::new(YieldExprData {
|
2026-02-24 06:40:18 -03:00
|
|
|
argument: None,
|
|
|
|
|
is_yield_from: false,
|
2026-03-22 15:27:45 -03:00
|
|
|
})),
|
2026-02-24 06:40:18 -03:00
|
|
|
)
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Parse an `await` expression.
|
|
|
|
|
// https://tc39.es/ecma262/#sec-async-function-definitions
|
|
|
|
|
// AwaitExpression : `await` UnaryExpression
|
|
|
|
|
// https://tc39.es/ecma262/#sec-async-function-definitions-static-semantics-early-errors
|
|
|
|
|
// It is a Syntax Error if AwaitExpression appears within FormalParameters.
|
|
|
|
|
fn parse_await_expression(&mut self) -> Expression {
|
|
|
|
|
let start = self.position();
|
|
|
|
|
|
|
|
|
|
if self.flags.in_formal_parameter_context {
|
2026-04-18 00:37:40 -03:00
|
|
|
self.syntax_error("'Await' expression is not allowed in formal parameters of an async function");
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.consume_token(TokenType::Await);
|
2026-04-18 00:37:40 -03:00
|
|
|
let argument = self.parse_expression(PRECEDENCE_UNARY, Associativity::Right, ForbiddenTokens::none());
|
2026-02-23 07:50:46 -03:00
|
|
|
self.scope_collector.set_contains_await_expression();
|
|
|
|
|
self.expression(start, ExpressionKind::Await(Box::new(argument)))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn parse_object_expression(&mut self) -> Expression {
|
|
|
|
|
let start = self.position();
|
|
|
|
|
self.consume_token(TokenType::CurlyOpen);
|
|
|
|
|
|
|
|
|
|
let mut properties = Vec::new();
|
|
|
|
|
let mut has_proto_setter = false;
|
|
|
|
|
while !self.match_token(TokenType::CurlyClose) && !self.done() {
|
|
|
|
|
if self.match_token(TokenType::TripleDot) {
|
2026-04-26 15:37:30 -03:00
|
|
|
let spread_start = self.position();
|
2026-02-23 07:50:46 -03:00
|
|
|
self.consume();
|
|
|
|
|
let expression = self.parse_assignment_expression();
|
|
|
|
|
properties.push(ObjectProperty {
|
2026-04-26 15:37:30 -03:00
|
|
|
range: self.range_from(spread_start),
|
2026-02-23 07:50:46 -03:00
|
|
|
property_type: ObjectPropertyType::Spread,
|
|
|
|
|
key: Box::new(expression),
|
|
|
|
|
is_computed: false,
|
|
|
|
|
value: None,
|
|
|
|
|
is_method: false,
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
let property = self.parse_object_property(start);
|
|
|
|
|
// https://tc39.es/ecma262/#sec-object-initializer-static-semantics-early-errors
|
|
|
|
|
// It is a Syntax Error if PropertyNameList of PropertyDefinitionList
|
|
|
|
|
// contains any duplicate entries for "__proto__" and at least two of
|
|
|
|
|
// those entries were obtained from productions of the form
|
|
|
|
|
// PropertyDefinition : PropertyName `:` AssignmentExpression.
|
|
|
|
|
if property.property_type == ObjectPropertyType::ProtoSetter {
|
|
|
|
|
if has_proto_setter {
|
2026-04-18 00:37:40 -03:00
|
|
|
self.syntax_error("Duplicate __proto__ fields are not allowed in object expressions");
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
has_proto_setter = true;
|
|
|
|
|
}
|
|
|
|
|
properties.push(property);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !self.match_token(TokenType::Comma) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
self.consume();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.consume_token(TokenType::CurlyClose);
|
2026-03-22 14:49:59 -03:00
|
|
|
self.expression(start, ExpressionKind::Object(Box::new(properties)))
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn parse_object_property(&mut self, obj_start: Position) -> ObjectProperty {
|
|
|
|
|
let start = self.position();
|
|
|
|
|
let mut is_getter = false;
|
|
|
|
|
let mut is_setter = false;
|
|
|
|
|
let mut is_async = false;
|
|
|
|
|
let mut is_generator = false;
|
|
|
|
|
|
|
|
|
|
if self.match_identifier_name() {
|
|
|
|
|
let value = self.token_original_value(&self.current_token).to_vec();
|
|
|
|
|
if value == utf16!("get") && self.match_property_key_ahead() {
|
|
|
|
|
is_getter = true;
|
|
|
|
|
self.consume();
|
|
|
|
|
} else if value == utf16!("set") && self.match_property_key_ahead() {
|
|
|
|
|
is_setter = true;
|
|
|
|
|
self.consume();
|
|
|
|
|
} else if value == utf16!("async") {
|
|
|
|
|
let next = self.next_token();
|
|
|
|
|
if !next.trivia_has_line_terminator
|
|
|
|
|
&& next.token_type != TokenType::ParenOpen
|
|
|
|
|
&& next.token_type != TokenType::Colon
|
|
|
|
|
&& next.token_type != TokenType::Comma
|
|
|
|
|
&& next.token_type != TokenType::CurlyClose
|
|
|
|
|
{
|
|
|
|
|
is_async = true;
|
|
|
|
|
self.consume();
|
|
|
|
|
if self.match_token(TokenType::Asterisk) {
|
|
|
|
|
is_generator = true;
|
|
|
|
|
self.consume();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !is_getter && !is_setter && !is_async && self.match_token(TokenType::Asterisk) {
|
|
|
|
|
is_generator = true;
|
|
|
|
|
self.consume();
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-24 06:40:18 -03:00
|
|
|
let PropertyKey {
|
|
|
|
|
expression: key,
|
|
|
|
|
name: key_value,
|
|
|
|
|
is_proto,
|
|
|
|
|
is_computed,
|
|
|
|
|
is_identifier,
|
2026-04-26 15:37:30 -03:00
|
|
|
} = self.parse_property_key();
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// https://tc39.es/ecma262/#sec-object-initializer
|
|
|
|
|
// Private names are not allowed in object literals, even inside class bodies.
|
|
|
|
|
if let ExpressionKind::PrivateIdentifier(_) = key.inner {
|
|
|
|
|
self.syntax_error("Private field or method is not allowed in object literal");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if self.match_token(TokenType::ParenOpen) {
|
2026-02-24 06:40:18 -03:00
|
|
|
let method_kind = if is_getter {
|
|
|
|
|
MethodKind::Getter
|
|
|
|
|
} else if is_setter {
|
|
|
|
|
MethodKind::Setter
|
|
|
|
|
} else {
|
|
|
|
|
MethodKind::Normal
|
|
|
|
|
};
|
2026-02-23 07:50:46 -03:00
|
|
|
let function = self.parse_method_definition(is_async, is_generator, method_kind, start);
|
2026-02-24 06:40:18 -03:00
|
|
|
let property_type = if is_getter {
|
|
|
|
|
ObjectPropertyType::Getter
|
|
|
|
|
} else if is_setter {
|
|
|
|
|
ObjectPropertyType::Setter
|
|
|
|
|
} else {
|
|
|
|
|
ObjectPropertyType::KeyValue
|
|
|
|
|
};
|
2026-02-23 07:50:46 -03:00
|
|
|
return ObjectProperty {
|
|
|
|
|
range: self.range_from(obj_start),
|
|
|
|
|
property_type,
|
|
|
|
|
key: Box::new(key),
|
|
|
|
|
value: Some(Box::new(function)),
|
|
|
|
|
is_method: true,
|
|
|
|
|
is_computed,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// async modifier requires a method (must have parens)
|
|
|
|
|
if is_async {
|
|
|
|
|
self.syntax_error("Expected function after async keyword");
|
|
|
|
|
}
|
2026-03-19 21:57:05 -03:00
|
|
|
// Generator shorthand requires a method body.
|
|
|
|
|
if is_generator {
|
|
|
|
|
self.syntax_error("Expected method after generator star");
|
|
|
|
|
}
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
if is_getter || is_setter {
|
2026-02-24 06:40:18 -03:00
|
|
|
let method_kind = if is_getter {
|
|
|
|
|
MethodKind::Getter
|
|
|
|
|
} else {
|
|
|
|
|
MethodKind::Setter
|
|
|
|
|
};
|
2026-02-23 07:50:46 -03:00
|
|
|
let function = self.parse_method_definition(false, false, method_kind, start);
|
2026-02-24 06:40:18 -03:00
|
|
|
let property_type = if is_getter {
|
|
|
|
|
ObjectPropertyType::Getter
|
|
|
|
|
} else {
|
|
|
|
|
ObjectPropertyType::Setter
|
|
|
|
|
};
|
2026-02-23 07:50:46 -03:00
|
|
|
return ObjectProperty {
|
|
|
|
|
range: self.range_from(obj_start),
|
|
|
|
|
property_type,
|
|
|
|
|
key: Box::new(key),
|
|
|
|
|
value: Some(Box::new(function)),
|
|
|
|
|
is_method: true,
|
|
|
|
|
is_computed,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if self.match_token(TokenType::Colon) {
|
|
|
|
|
self.consume();
|
|
|
|
|
let value = self.parse_assignment_expression();
|
2026-02-24 06:40:18 -03:00
|
|
|
let property_type = if is_proto {
|
|
|
|
|
ObjectPropertyType::ProtoSetter
|
|
|
|
|
} else {
|
|
|
|
|
ObjectPropertyType::KeyValue
|
|
|
|
|
};
|
2026-02-23 07:50:46 -03:00
|
|
|
return ObjectProperty {
|
|
|
|
|
range: self.range_from(obj_start),
|
|
|
|
|
property_type,
|
|
|
|
|
key: Box::new(key),
|
|
|
|
|
value: Some(Box::new(value)),
|
|
|
|
|
is_method: false,
|
|
|
|
|
is_computed,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// https://tc39.es/ecma262/#sec-object-initializer
|
|
|
|
|
// CoverInitializedName : IdentifierReference Initializer
|
|
|
|
|
// https://tc39.es/ecma262/#sec-object-initializer-static-semantics-early-errors
|
|
|
|
|
// It is a Syntax Error if PropertyDefinitionList contains any CoverInitializedName.
|
|
|
|
|
// NB: This is not a valid object literal, but is a valid destructuring assignment
|
|
|
|
|
// target. We parse the initializer to advance the lexer, but roll back scope records
|
|
|
|
|
// since this expression is discarded. synthesize_binding_pattern will
|
|
|
|
|
// re-parse from source and create the real scope records.
|
2026-02-24 08:45:48 -03:00
|
|
|
if self.match_token(TokenType::Equals)
|
|
|
|
|
&& is_identifier
|
|
|
|
|
&& let Some(kv) = &key_value
|
|
|
|
|
{
|
|
|
|
|
let id = self.make_identifier(obj_start, kv.clone());
|
2026-04-05 19:38:51 -03:00
|
|
|
self.scope_collector.register_identifier(id.clone(), None);
|
2026-02-24 08:45:48 -03:00
|
|
|
let value = self.expression(obj_start, ExpressionKind::Identifier(id));
|
|
|
|
|
self.consume(); // consume '='
|
|
|
|
|
// NB: Add a syntax error for CoverInitializedName. This error will
|
|
|
|
|
// be cleared by synthesize_binding_pattern if the containing object
|
|
|
|
|
// is reinterpreted as a destructuring pattern, but will persist if
|
|
|
|
|
// the object is used in expression context (e.g. as a member base).
|
|
|
|
|
self.syntax_error("Invalid property in object literal");
|
|
|
|
|
let saved_scope_state = self.scope_collector.save_state();
|
|
|
|
|
let _initializer = self.parse_assignment_expression();
|
|
|
|
|
self.scope_collector.load_state(saved_scope_state);
|
|
|
|
|
return ObjectProperty {
|
|
|
|
|
range: self.range_from(obj_start),
|
|
|
|
|
property_type: ObjectPropertyType::KeyValue,
|
|
|
|
|
key: Box::new(key),
|
|
|
|
|
value: Some(Box::new(value)),
|
|
|
|
|
is_method: false,
|
|
|
|
|
is_computed: false,
|
|
|
|
|
};
|
|
|
|
|
}
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Shorthand property: { x }
|
|
|
|
|
// Only identifiers can be shorthand properties, not string/numeric literals.
|
|
|
|
|
if let Some(kv) = key_value.filter(|_| is_identifier) {
|
|
|
|
|
// https://tc39.es/ecma262/#sec-object-initializer-static-semantics-early-errors
|
|
|
|
|
// Strict-mode reserved words cannot be used as shorthand properties.
|
|
|
|
|
if self.flags.strict_mode && is_strict_reserved_word(&kv) {
|
|
|
|
|
let name_str = String::from_utf16_lossy(&kv);
|
2026-02-27 05:50:35 -03:00
|
|
|
self.syntax_error(&format!("'{name_str}' is a reserved keyword"));
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
let id = self.make_identifier(obj_start, kv);
|
2026-04-05 19:38:51 -03:00
|
|
|
self.scope_collector.register_identifier(id.clone(), None);
|
2026-02-23 07:50:46 -03:00
|
|
|
let value = self.expression(obj_start, ExpressionKind::Identifier(id));
|
|
|
|
|
return ObjectProperty {
|
|
|
|
|
range: self.range_from(obj_start),
|
|
|
|
|
property_type: ObjectPropertyType::KeyValue,
|
|
|
|
|
key: Box::new(key),
|
|
|
|
|
value: Some(Box::new(value)),
|
|
|
|
|
is_method: false,
|
|
|
|
|
is_computed: false,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.expected("':' or '('");
|
|
|
|
|
ObjectProperty {
|
|
|
|
|
range: self.range_from(obj_start),
|
|
|
|
|
property_type: ObjectPropertyType::KeyValue,
|
|
|
|
|
key: Box::new(key),
|
|
|
|
|
value: None,
|
|
|
|
|
is_method: false,
|
|
|
|
|
is_computed,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) fn match_property_key_ahead(&mut self) -> bool {
|
|
|
|
|
let next = self.next_token();
|
|
|
|
|
matches!(
|
|
|
|
|
next.token_type,
|
|
|
|
|
TokenType::BracketOpen
|
|
|
|
|
| TokenType::StringLiteral
|
|
|
|
|
| TokenType::NumericLiteral
|
|
|
|
|
| TokenType::BigIntLiteral
|
|
|
|
|
| TokenType::PrivateIdentifier
|
|
|
|
|
) || next.token_type.is_identifier_name()
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-26 15:37:30 -03:00
|
|
|
pub(crate) fn parse_property_key(&mut self) -> PropertyKey {
|
2026-02-23 07:50:46 -03:00
|
|
|
let proto_name = utf16!("__proto__");
|
|
|
|
|
let start = self.position();
|
|
|
|
|
match self.current_token_type() {
|
|
|
|
|
TokenType::BracketOpen => {
|
|
|
|
|
self.consume();
|
|
|
|
|
let expression = self.parse_assignment_expression();
|
|
|
|
|
self.consume_token(TokenType::BracketClose);
|
2026-02-24 06:40:18 -03:00
|
|
|
PropertyKey {
|
|
|
|
|
expression,
|
|
|
|
|
name: None,
|
|
|
|
|
is_proto: false,
|
|
|
|
|
is_computed: true,
|
|
|
|
|
is_identifier: false,
|
|
|
|
|
}
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
TokenType::StringLiteral => {
|
2026-04-26 15:30:04 -03:00
|
|
|
let token = self.consume_property_key_token();
|
2026-02-23 07:50:46 -03:00
|
|
|
// C++ calls consume() before push_start() for StringLiteral,
|
|
|
|
|
// so its position is the token AFTER the string.
|
|
|
|
|
let after_string = self.position();
|
|
|
|
|
let (value, has_octal) = self.parse_string_value(&token);
|
|
|
|
|
if has_octal {
|
|
|
|
|
if self.flags.strict_mode {
|
2026-04-18 00:37:40 -03:00
|
|
|
self.syntax_error("Octal escape sequence in string literal not allowed in strict mode");
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
|
|
|
|
self.flags.string_legacy_octal_escape_sequence_in_scope = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let is_proto = value == proto_name;
|
2026-04-18 00:37:40 -03:00
|
|
|
let expression = self.expression(after_string, ExpressionKind::StringLiteral(Box::new(value.clone())));
|
2026-02-24 06:40:18 -03:00
|
|
|
PropertyKey {
|
|
|
|
|
expression,
|
|
|
|
|
name: Some(value),
|
|
|
|
|
is_proto,
|
|
|
|
|
is_computed: false,
|
|
|
|
|
is_identifier: false,
|
|
|
|
|
}
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
TokenType::NumericLiteral => {
|
|
|
|
|
let token = self.consume_and_validate_numeric_literal();
|
|
|
|
|
let value_str = self.token_value(&token);
|
|
|
|
|
let value = parse_numeric_value(value_str);
|
|
|
|
|
let expression = self.expression(start, ExpressionKind::NumericLiteral(value));
|
2026-02-24 06:40:18 -03:00
|
|
|
PropertyKey {
|
|
|
|
|
expression,
|
|
|
|
|
name: None,
|
|
|
|
|
is_proto: false,
|
|
|
|
|
is_computed: false,
|
|
|
|
|
is_identifier: false,
|
|
|
|
|
}
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
TokenType::BigIntLiteral => {
|
|
|
|
|
let token = self.consume();
|
|
|
|
|
let value = self.token_value(&token);
|
|
|
|
|
// Store the raw value including the 'n' suffix, matching C++.
|
2026-02-24 06:40:18 -03:00
|
|
|
let value_utf8: String = value
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|&c| {
|
2026-04-18 00:37:40 -03:00
|
|
|
assert!(c < 128, "BigIntLiteral should only contain ASCII characters");
|
2026-02-24 06:40:18 -03:00
|
|
|
c as u8 as char
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
2026-04-18 00:37:40 -03:00
|
|
|
let expression = self.expression(start, ExpressionKind::BigIntLiteral(Box::new(value_utf8)));
|
2026-02-24 06:40:18 -03:00
|
|
|
PropertyKey {
|
|
|
|
|
expression,
|
|
|
|
|
name: None,
|
|
|
|
|
is_proto: false,
|
|
|
|
|
is_computed: false,
|
|
|
|
|
is_identifier: false,
|
|
|
|
|
}
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
// https://tc39.es/ecma262/#sec-class-definitions-static-semantics-early-errors
|
|
|
|
|
// It is a Syntax Error if the StringValue of PrivateIdentifier is "#constructor".
|
|
|
|
|
TokenType::PrivateIdentifier => {
|
|
|
|
|
let token = self.consume();
|
|
|
|
|
let value = Utf16String::from(self.token_value(&token));
|
|
|
|
|
if value == utf16!("#constructor") {
|
|
|
|
|
self.syntax_error("Private property with name '#constructor' is not allowed");
|
|
|
|
|
}
|
2026-02-24 06:40:18 -03:00
|
|
|
let expression = self.expression(
|
2026-04-26 15:37:30 -03:00
|
|
|
start,
|
2026-03-22 14:45:16 -03:00
|
|
|
ExpressionKind::PrivateIdentifier(Box::new(PrivateIdentifier {
|
2026-04-26 15:37:30 -03:00
|
|
|
range: self.range_from(start),
|
2026-02-24 06:40:18 -03:00
|
|
|
name: value.clone(),
|
2026-03-22 14:45:16 -03:00
|
|
|
})),
|
2026-02-24 06:40:18 -03:00
|
|
|
);
|
|
|
|
|
PropertyKey {
|
|
|
|
|
expression,
|
|
|
|
|
name: Some(value),
|
|
|
|
|
is_proto: false,
|
|
|
|
|
is_computed: false,
|
|
|
|
|
is_identifier: false,
|
|
|
|
|
}
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
_ => {
|
|
|
|
|
if self.match_identifier_name() {
|
|
|
|
|
// is_identifier is true only for valid identifier references (not just
|
|
|
|
|
// identifier names). This matters for shorthand properties: { await }
|
|
|
|
|
// is not valid in class static blocks since await is not an identifier there.
|
|
|
|
|
let is_ident = self.match_identifier();
|
2026-04-26 15:30:04 -03:00
|
|
|
let token = self.consume_property_key_token();
|
2026-02-23 07:50:46 -03:00
|
|
|
let value = Utf16String::from(self.token_value(&token));
|
|
|
|
|
let is_proto = value == proto_name;
|
2026-04-26 15:37:30 -03:00
|
|
|
let expression = self.expression(start, ExpressionKind::StringLiteral(Box::new(value.clone())));
|
2026-02-24 06:40:18 -03:00
|
|
|
PropertyKey {
|
|
|
|
|
expression,
|
|
|
|
|
name: Some(value),
|
|
|
|
|
is_proto,
|
|
|
|
|
is_computed: false,
|
|
|
|
|
is_identifier: is_ident,
|
|
|
|
|
}
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
|
|
|
|
self.expected("property key");
|
2026-04-26 15:30:04 -03:00
|
|
|
self.consume_property_key_token();
|
2026-04-18 00:37:40 -03:00
|
|
|
let expression =
|
|
|
|
|
self.expression(start, ExpressionKind::StringLiteral(Box::new(Utf16String::new())));
|
2026-02-24 06:40:18 -03:00
|
|
|
PropertyKey {
|
|
|
|
|
expression,
|
|
|
|
|
name: None,
|
|
|
|
|
is_proto: false,
|
|
|
|
|
is_computed: false,
|
|
|
|
|
is_identifier: false,
|
|
|
|
|
}
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn parse_array_expression(&mut self) -> Expression {
|
|
|
|
|
let start = self.position();
|
|
|
|
|
self.consume_token(TokenType::BracketOpen);
|
|
|
|
|
|
|
|
|
|
let mut elements: Vec<Option<Expression>> = Vec::new();
|
|
|
|
|
while !self.match_token(TokenType::BracketClose) && !self.done() {
|
|
|
|
|
if self.match_token(TokenType::Comma) {
|
|
|
|
|
elements.push(None);
|
|
|
|
|
self.consume();
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if self.match_token(TokenType::TripleDot) {
|
2026-04-26 15:37:30 -03:00
|
|
|
let spread_start = self.position();
|
2026-02-23 07:50:46 -03:00
|
|
|
self.consume();
|
|
|
|
|
let expression = self.parse_assignment_expression();
|
2026-02-24 06:40:18 -03:00
|
|
|
elements.push(Some(
|
2026-04-26 15:37:30 -03:00
|
|
|
self.expression(spread_start, ExpressionKind::Spread(Box::new(expression))),
|
2026-02-24 06:40:18 -03:00
|
|
|
));
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
|
|
|
|
elements.push(Some(self.parse_assignment_expression()));
|
|
|
|
|
}
|
|
|
|
|
if !self.match_token(TokenType::Comma) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
self.consume();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.consume_token(TokenType::BracketClose);
|
2026-03-22 14:48:27 -03:00
|
|
|
self.expression(start, ExpressionKind::Array(Box::new(elements)))
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Parse a template literal (`` `...${expression}...` ``).
|
|
|
|
|
// https://tc39.es/ecma262/#sec-template-literals
|
|
|
|
|
// TemplateLiteral : NoSubstitutionTemplate
|
|
|
|
|
// | SubstitutionTemplate
|
|
|
|
|
// SubstitutionTemplate : TemplateHead Expression TemplateSpans
|
|
|
|
|
// NB: In tagged templates, invalid escape sequences produce `undefined` for the
|
|
|
|
|
// cooked value instead of a syntax error (sec-template-literals-static-semantics-early-errors).
|
|
|
|
|
/// Consume any tagged template literals following an expression.
|
|
|
|
|
/// Tagged templates bind tighter than any binary operator, so they
|
|
|
|
|
/// are handled outside the normal precedence loop.
|
2026-04-18 00:37:40 -03:00
|
|
|
fn parse_tagged_template_literals(&mut self, tag_start: Position, mut expression: Expression) -> Expression {
|
2026-02-23 07:50:46 -03:00
|
|
|
while self.match_token(TokenType::TemplateLiteralStart) {
|
|
|
|
|
let template = self.parse_template_literal(true);
|
2026-02-24 06:40:18 -03:00
|
|
|
expression = self.expression(
|
|
|
|
|
tag_start,
|
2026-03-22 15:24:36 -03:00
|
|
|
ExpressionKind::TaggedTemplateLiteral(Box::new(TaggedTemplateData {
|
2026-02-24 06:40:18 -03:00
|
|
|
tag: Box::new(expression),
|
|
|
|
|
template_literal: Box::new(template),
|
2026-03-22 15:24:36 -03:00
|
|
|
})),
|
2026-02-24 06:40:18 -03:00
|
|
|
);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
expression
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) fn parse_template_literal(&mut self, is_tagged: bool) -> Expression {
|
|
|
|
|
let start = self.position();
|
|
|
|
|
self.consume_token(TokenType::TemplateLiteralStart);
|
|
|
|
|
|
|
|
|
|
let mut expressions = Vec::new();
|
|
|
|
|
let mut raw_strings = Vec::new();
|
|
|
|
|
|
|
|
|
|
let needs_leading_empty = !self.match_token(TokenType::TemplateLiteralString);
|
|
|
|
|
if needs_leading_empty {
|
|
|
|
|
if is_tagged {
|
|
|
|
|
raw_strings.push(Utf16String::new());
|
|
|
|
|
}
|
2026-04-18 00:37:40 -03:00
|
|
|
expressions.push(self.expression(start, ExpressionKind::StringLiteral(Box::new(Utf16String::new()))));
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// For non-tagged templates, we collect parts as expressions (alternating
|
|
|
|
|
// string parts and interpolation expressions). For tagged templates, we
|
|
|
|
|
// also collect raw strings separately.
|
|
|
|
|
|
|
|
|
|
loop {
|
|
|
|
|
if self.match_token(TokenType::TemplateLiteralEnd) {
|
|
|
|
|
self.consume();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if self.match_token(TokenType::TemplateLiteralString) {
|
|
|
|
|
let token = self.consume();
|
|
|
|
|
// C++ calls parse_string_literal after consume(), so its position
|
|
|
|
|
// is after the template string token. Match that behavior.
|
|
|
|
|
let string_pos = self.position();
|
|
|
|
|
let raw = self.token_value(&token).to_vec();
|
|
|
|
|
if is_tagged {
|
|
|
|
|
let raw_value = raw_template_value(&raw);
|
|
|
|
|
raw_strings.push(raw_value);
|
|
|
|
|
match self.process_template_escape_sequences(&raw) {
|
2026-04-18 00:37:40 -03:00
|
|
|
Some(cooked) => expressions
|
|
|
|
|
.push(self.expression(string_pos, ExpressionKind::StringLiteral(Box::new(cooked)))),
|
2026-02-23 07:50:46 -03:00
|
|
|
// C++ uses rule_start (template literal start) for NullLiteral.
|
2026-02-24 06:40:18 -03:00
|
|
|
None => {
|
2026-02-27 05:54:44 -03:00
|
|
|
expressions.push(self.expression(start, ExpressionKind::NullLiteral));
|
2026-02-24 06:40:18 -03:00
|
|
|
}
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
let (value, has_octal) = self.process_escape_sequences(&raw);
|
|
|
|
|
if has_octal {
|
|
|
|
|
self.syntax_error("Octal escape sequence not allowed in template literal");
|
|
|
|
|
}
|
2026-04-18 00:37:40 -03:00
|
|
|
expressions.push(self.expression(string_pos, ExpressionKind::StringLiteral(Box::new(value))));
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
} else if self.match_token(TokenType::TemplateLiteralExprStart) {
|
|
|
|
|
self.consume();
|
|
|
|
|
let expression = self.parse_expression_any();
|
|
|
|
|
expressions.push(expression);
|
|
|
|
|
self.consume_token(TokenType::TemplateLiteralExprEnd);
|
|
|
|
|
// After an expression, if no template string follows, insert empty.
|
|
|
|
|
if !self.match_token(TokenType::TemplateLiteralString) {
|
2026-04-18 00:37:40 -03:00
|
|
|
expressions
|
|
|
|
|
.push(self.expression(start, ExpressionKind::StringLiteral(Box::new(Utf16String::new()))));
|
2026-02-23 07:50:46 -03:00
|
|
|
if is_tagged {
|
|
|
|
|
raw_strings.push(Utf16String::new());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if self.done() {
|
|
|
|
|
self.expected("template literal end");
|
|
|
|
|
break;
|
|
|
|
|
} else {
|
|
|
|
|
self.consume();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-24 06:40:18 -03:00
|
|
|
self.expression(
|
|
|
|
|
start,
|
2026-03-22 13:40:48 -03:00
|
|
|
ExpressionKind::TemplateLiteral(Box::new(TemplateLiteralData {
|
2026-02-24 06:40:18 -03:00
|
|
|
expressions,
|
|
|
|
|
raw_strings,
|
2026-03-22 13:40:48 -03:00
|
|
|
})),
|
2026-02-24 06:40:18 -03:00
|
|
|
)
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn process_template_escape_sequences(&self, raw: &[u16]) -> Option<Utf16String> {
|
|
|
|
|
let result = process_escape_sequences_impl(raw, EscapeMode::TaggedTemplate);
|
2026-04-18 00:37:40 -03:00
|
|
|
if result.failed { None } else { Some(result.value) }
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Parse a string literal token's value, processing escape sequences.
|
|
|
|
|
/// Returns `(value, has_legacy_octal)`.
|
|
|
|
|
pub(crate) fn parse_string_value(&mut self, token: &Token) -> (Utf16String, bool) {
|
|
|
|
|
let raw = self.token_value(token).to_vec();
|
|
|
|
|
if raw.len() < 2 {
|
|
|
|
|
return (Utf16String::default(), false);
|
|
|
|
|
}
|
|
|
|
|
self.process_escape_sequences(&raw[1..raw.len() - 1])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) fn process_escape_sequences(&mut self, inner: &[u16]) -> (Utf16String, bool) {
|
|
|
|
|
let result = process_escape_sequences_impl(inner, EscapeMode::StringLiteral);
|
|
|
|
|
if result.malformed_hex {
|
|
|
|
|
self.syntax_error("Malformed hexadecimal escape sequence");
|
|
|
|
|
}
|
|
|
|
|
if result.malformed_unicode {
|
|
|
|
|
self.syntax_error("Malformed unicode escape sequence");
|
|
|
|
|
}
|
|
|
|
|
(result.value, result.has_legacy_octal)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct EscapeResult {
|
|
|
|
|
value: Utf16String,
|
|
|
|
|
has_legacy_octal: bool,
|
|
|
|
|
/// Tagged template encountered an invalid escape.
|
|
|
|
|
failed: bool,
|
|
|
|
|
malformed_hex: bool,
|
|
|
|
|
malformed_unicode: bool,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Unified escape sequence processor for both string and template literals.
|
|
|
|
|
///
|
|
|
|
|
/// In `TaggedTemplate` mode, `failed` is true when an invalid escape is
|
|
|
|
|
/// encountered (the cooked value becomes `undefined`).
|
|
|
|
|
fn process_escape_sequences_impl(input: &[u16], mode: EscapeMode) -> EscapeResult {
|
|
|
|
|
let mut result = Utf16String(Vec::with_capacity(input.len()));
|
|
|
|
|
let mut has_legacy_octal = false;
|
|
|
|
|
let mut malformed_hex = false;
|
|
|
|
|
let mut malformed_unicode = false;
|
|
|
|
|
let mut i = 0;
|
|
|
|
|
|
|
|
|
|
const N: u16 = ch(b'n');
|
|
|
|
|
const R: u16 = ch(b'r');
|
|
|
|
|
const T: u16 = ch(b't');
|
|
|
|
|
const B: u16 = ch(b'b');
|
|
|
|
|
const F: u16 = ch(b'f');
|
|
|
|
|
const V: u16 = ch(b'v');
|
|
|
|
|
const ZERO: u16 = ch(b'0');
|
|
|
|
|
const ONE: u16 = ch(b'1');
|
|
|
|
|
const SEVEN: u16 = ch(b'7');
|
|
|
|
|
const EIGHT: u16 = ch(b'8');
|
|
|
|
|
const NINE: u16 = ch(b'9');
|
|
|
|
|
const X: u16 = ch(b'x');
|
|
|
|
|
const U: u16 = ch(b'u');
|
|
|
|
|
const LF: u16 = ch(b'\n');
|
|
|
|
|
const CR: u16 = ch(b'\r');
|
|
|
|
|
const LS: u16 = 0x2028;
|
|
|
|
|
const PS: u16 = 0x2029;
|
|
|
|
|
|
|
|
|
|
while i < input.len() {
|
|
|
|
|
if input[i] == b'\\' as u16 && i + 1 < input.len() {
|
|
|
|
|
i += 1;
|
|
|
|
|
match input[i] {
|
|
|
|
|
N => result.0.push(ch(b'\n')),
|
|
|
|
|
R => result.0.push(ch(b'\r')),
|
|
|
|
|
T => result.0.push(ch(b'\t')),
|
|
|
|
|
B => result.0.push(8),
|
|
|
|
|
F => result.0.push(12),
|
|
|
|
|
V => result.0.push(11),
|
|
|
|
|
ZERO => {
|
|
|
|
|
if mode == EscapeMode::TaggedTemplate {
|
2026-02-24 06:40:18 -03:00
|
|
|
if i + 1 < input.len()
|
2026-04-18 00:37:40 -03:00
|
|
|
&& (is_octal_char(input[i + 1]) || input[i + 1] == EIGHT || input[i + 1] == NINE)
|
2026-02-24 06:40:18 -03:00
|
|
|
{
|
|
|
|
|
return EscapeResult {
|
|
|
|
|
value: result,
|
|
|
|
|
has_legacy_octal: false,
|
|
|
|
|
failed: true,
|
|
|
|
|
malformed_hex: false,
|
|
|
|
|
malformed_unicode: false,
|
|
|
|
|
};
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
result.0.push(0);
|
|
|
|
|
} else if i + 1 < input.len() && is_octal_char(input[i + 1]) {
|
|
|
|
|
has_legacy_octal = true;
|
|
|
|
|
let (val, consumed) = parse_octal_escape(input, i);
|
|
|
|
|
result.0.push(val);
|
|
|
|
|
i += consumed;
|
2026-04-18 00:37:40 -03:00
|
|
|
} else if i + 1 < input.len() && (input[i + 1] == EIGHT || input[i + 1] == NINE) {
|
2026-02-23 07:50:46 -03:00
|
|
|
has_legacy_octal = true;
|
|
|
|
|
result.0.push(0);
|
|
|
|
|
} else {
|
|
|
|
|
result.0.push(0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ONE..=SEVEN => {
|
|
|
|
|
if mode == EscapeMode::TaggedTemplate {
|
2026-02-24 06:40:18 -03:00
|
|
|
return EscapeResult {
|
|
|
|
|
value: result,
|
|
|
|
|
has_legacy_octal: false,
|
|
|
|
|
failed: true,
|
|
|
|
|
malformed_hex: false,
|
|
|
|
|
malformed_unicode: false,
|
|
|
|
|
};
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
has_legacy_octal = true;
|
|
|
|
|
let (val, consumed) = parse_octal_escape(input, i);
|
|
|
|
|
result.0.push(val);
|
|
|
|
|
i += consumed;
|
|
|
|
|
}
|
|
|
|
|
EIGHT | NINE => {
|
|
|
|
|
if mode == EscapeMode::TaggedTemplate {
|
2026-02-24 06:40:18 -03:00
|
|
|
return EscapeResult {
|
|
|
|
|
value: result,
|
|
|
|
|
has_legacy_octal: false,
|
|
|
|
|
failed: true,
|
|
|
|
|
malformed_hex: false,
|
|
|
|
|
malformed_unicode: false,
|
|
|
|
|
};
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
has_legacy_octal = true;
|
|
|
|
|
result.0.push(input[i]);
|
|
|
|
|
}
|
|
|
|
|
X => {
|
|
|
|
|
if let Some((advance, ch)) = parse_hex_escape(input, i) {
|
|
|
|
|
result.0.push(ch);
|
|
|
|
|
i += advance;
|
|
|
|
|
} else if mode == EscapeMode::TaggedTemplate {
|
2026-02-24 06:40:18 -03:00
|
|
|
return EscapeResult {
|
|
|
|
|
value: result,
|
|
|
|
|
has_legacy_octal: false,
|
|
|
|
|
failed: true,
|
|
|
|
|
malformed_hex: false,
|
|
|
|
|
malformed_unicode: false,
|
|
|
|
|
};
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
|
|
|
|
malformed_hex = true;
|
|
|
|
|
result.0.push(input[i]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
U => {
|
|
|
|
|
if let Some((advance, code_point)) = parse_unicode_escape(input, i) {
|
|
|
|
|
push_code_point(&mut result.0, code_point);
|
|
|
|
|
i += advance;
|
|
|
|
|
} else if mode == EscapeMode::TaggedTemplate {
|
2026-02-24 06:40:18 -03:00
|
|
|
return EscapeResult {
|
|
|
|
|
value: result,
|
|
|
|
|
has_legacy_octal: false,
|
|
|
|
|
failed: true,
|
|
|
|
|
malformed_hex: false,
|
|
|
|
|
malformed_unicode: false,
|
|
|
|
|
};
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
|
|
|
|
malformed_unicode = true;
|
|
|
|
|
result.0.push(input[i]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
LF => { /* line continuation */ }
|
|
|
|
|
CR => {
|
|
|
|
|
if i + 1 < input.len() && input[i + 1] == LF {
|
|
|
|
|
i += 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
LS | PS => { /* skip LS/PS */ }
|
|
|
|
|
c => result.0.push(c),
|
|
|
|
|
}
|
|
|
|
|
} else if input[i] == ch(b'\r') {
|
|
|
|
|
// Normalize \r\n and bare \r to \n per spec (12.9.6).
|
|
|
|
|
result.0.push(ch(b'\n'));
|
|
|
|
|
if i + 1 < input.len() && input[i + 1] == ch(b'\n') {
|
|
|
|
|
i += 1;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
result.0.push(input[i]);
|
|
|
|
|
}
|
|
|
|
|
i += 1;
|
|
|
|
|
}
|
2026-02-24 06:40:18 -03:00
|
|
|
EscapeResult {
|
|
|
|
|
value: result,
|
|
|
|
|
has_legacy_octal,
|
|
|
|
|
failed: false,
|
|
|
|
|
malformed_hex,
|
|
|
|
|
malformed_unicode,
|
|
|
|
|
}
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
2026-02-27 06:40:42 -03:00
|
|
|
impl Parser<'_> {
|
2026-02-24 12:14:34 -03:00
|
|
|
/// Try to parse an arrow function expression, with memoization.
|
|
|
|
|
/// If a previous attempt at this position already failed, returns `None`
|
|
|
|
|
/// immediately. Otherwise attempts the parse and caches the failure.
|
|
|
|
|
/// This prevents exponential re-processing of nested expressions like
|
|
|
|
|
/// `(a=(b=(c=0)))` where each outer failed arrow attempt would re-trigger
|
|
|
|
|
/// inner attempts during grouping expression re-parse.
|
2026-02-23 07:50:46 -03:00
|
|
|
// https://tc39.es/ecma262/#sec-arrow-function-definitions
|
|
|
|
|
// ArrowFunction : ArrowParameters [no LineTerminator here] `=>` ConciseBody
|
|
|
|
|
// ConciseBody : [lookahead != `{`] ExpressionBody
|
|
|
|
|
// | `{` FunctionBody `}`
|
2026-02-24 06:40:18 -03:00
|
|
|
pub(crate) fn try_parse_arrow_function_expression(
|
|
|
|
|
&mut self,
|
|
|
|
|
expect_parens: bool,
|
|
|
|
|
is_async: bool,
|
2026-02-24 12:14:34 -03:00
|
|
|
source_start_override: Option<Position>,
|
2026-02-24 06:40:18 -03:00
|
|
|
) -> Option<Expression> {
|
2026-02-24 12:14:34 -03:00
|
|
|
let offset = source_start_override.map_or(self.current_token.offset, |p| p.offset) as usize;
|
|
|
|
|
if self.arrow_function_failed_positions.contains(&offset) {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
2026-04-18 00:37:40 -03:00
|
|
|
let result = self.try_parse_arrow_function_expression_impl(expect_parens, is_async, source_start_override);
|
2026-02-24 12:14:34 -03:00
|
|
|
if result.is_none() {
|
|
|
|
|
self.arrow_function_failed_positions.insert(offset);
|
|
|
|
|
}
|
|
|
|
|
result
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
2026-02-24 12:14:34 -03:00
|
|
|
fn try_parse_arrow_function_expression_impl(
|
2026-02-24 06:40:18 -03:00
|
|
|
&mut self,
|
|
|
|
|
expect_parens: bool,
|
|
|
|
|
is_async: bool,
|
|
|
|
|
source_start_override: Option<Position>,
|
|
|
|
|
) -> Option<Expression> {
|
2026-02-23 07:50:46 -03:00
|
|
|
let start = source_start_override.unwrap_or_else(|| self.position());
|
|
|
|
|
|
|
|
|
|
if !expect_parens && !is_async {
|
2026-04-18 00:37:40 -03:00
|
|
|
if !self.match_identifier() && !self.match_token(TokenType::Await) && !self.match_token(TokenType::Yield) {
|
2026-02-23 07:50:46 -03:00
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
let next = self.next_token();
|
|
|
|
|
if next.token_type != TokenType::Arrow || next.trivia_has_line_terminator {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Save and clear pattern_bound_names so that parameter and body
|
|
|
|
|
// parsing inside this arrow function doesn't steal binding names
|
|
|
|
|
// accumulated by an outer binding pattern context.
|
|
|
|
|
let saved_pattern_bound_names = std::mem::take(&mut self.pattern_bound_names);
|
|
|
|
|
|
|
|
|
|
self.save_state();
|
|
|
|
|
|
|
|
|
|
// Reset in_formal_parameter_context so that yield/await inside
|
|
|
|
|
// arrow function bodies nested in parameter defaults are not
|
|
|
|
|
// rejected. (load_state restores flags on error paths.)
|
|
|
|
|
let saved_formal_parameter_ctx = self.flags.in_formal_parameter_context;
|
|
|
|
|
self.flags.in_formal_parameter_context = false;
|
|
|
|
|
|
|
|
|
|
if is_async {
|
|
|
|
|
self.consume(); // consume 'async'
|
|
|
|
|
if self.current_token.trivia_has_line_terminator {
|
|
|
|
|
self.pattern_bound_names = saved_pattern_bound_names;
|
|
|
|
|
self.load_state();
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
if expect_parens {
|
|
|
|
|
self.consume_token(TokenType::ParenOpen);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Open function scope before parsing parameters so that default
|
|
|
|
|
// value expressions are resolved inside the function scope.
|
|
|
|
|
// save_state() above captured scope collector state, so any
|
|
|
|
|
// load_state() rollback will undo this.
|
|
|
|
|
self.scope_collector.open_function_scope(None);
|
|
|
|
|
self.scope_collector.set_is_arrow_function();
|
2026-04-26 09:19:54 -03:00
|
|
|
self.push_function_context();
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Set await_expression_is_valid during parameter parsing so that
|
|
|
|
|
// 'await' is rejected as an identifier in async arrow parameters.
|
|
|
|
|
let saved_await = self.flags.await_expression_is_valid;
|
|
|
|
|
let saved_static_init = self.flags.in_class_static_init_block;
|
|
|
|
|
if is_async {
|
|
|
|
|
self.flags.await_expression_is_valid = true;
|
|
|
|
|
}
|
|
|
|
|
// NB: Do NOT clear in_class_static_init_block here. Arrow functions don't
|
|
|
|
|
// create a new `await` boundary, so `await` in parameter defaults must still
|
|
|
|
|
// be rejected inside class static initializer blocks. The flag is cleared
|
|
|
|
|
// below, after parameter parsing, for the arrow body only.
|
|
|
|
|
|
|
|
|
|
let parsed;
|
|
|
|
|
|
|
|
|
|
if expect_parens {
|
|
|
|
|
let previous_errors = self.errors.len();
|
|
|
|
|
parsed = self.parse_formal_parameters_impl(true);
|
|
|
|
|
if self.errors.len() > previous_errors {
|
|
|
|
|
self.pattern_bound_names = saved_pattern_bound_names;
|
|
|
|
|
self.load_state();
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
if !self.match_token(TokenType::ParenClose) {
|
|
|
|
|
self.pattern_bound_names = saved_pattern_bound_names;
|
|
|
|
|
self.load_state();
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
self.consume(); // consume ')'
|
|
|
|
|
} else if self.match_identifier() || self.match_token(TokenType::Await) {
|
|
|
|
|
let token = self.consume();
|
|
|
|
|
let value = self.token_value(&token).to_vec();
|
|
|
|
|
if is_async && value == utf16!("await") {
|
|
|
|
|
self.syntax_error("'await' is a reserved identifier in async functions");
|
|
|
|
|
}
|
|
|
|
|
// C++ uses rule_start (arrow function start, which is `async` for async arrows).
|
2026-04-18 00:37:40 -03:00
|
|
|
let binding = Rc::new(Identifier::new(self.range_from(start), value.clone().into()));
|
2026-02-23 07:50:46 -03:00
|
|
|
parsed = ParsedParameters {
|
|
|
|
|
parameters: vec![FunctionParameter {
|
|
|
|
|
binding: FunctionParameterBinding::Identifier(binding.clone()),
|
|
|
|
|
default_value: None,
|
|
|
|
|
is_rest: false,
|
|
|
|
|
}],
|
|
|
|
|
function_length: 1,
|
2026-02-24 06:40:18 -03:00
|
|
|
parameter_info: vec![ParamInfo {
|
|
|
|
|
name: value.into(),
|
|
|
|
|
is_rest: false,
|
|
|
|
|
is_from_pattern: false,
|
|
|
|
|
identifier: Some(binding),
|
|
|
|
|
}],
|
2026-02-23 07:50:46 -03:00
|
|
|
is_simple: true,
|
|
|
|
|
};
|
|
|
|
|
} else {
|
|
|
|
|
self.flags.await_expression_is_valid = saved_await;
|
|
|
|
|
self.pattern_bound_names = saved_pattern_bound_names;
|
|
|
|
|
self.load_state();
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Restore await flag during arrow-check; it will be set
|
|
|
|
|
// again for the body below.
|
|
|
|
|
self.flags.await_expression_is_valid = saved_await;
|
|
|
|
|
|
|
|
|
|
// [no LineTerminator here] before `=>`
|
|
|
|
|
if !self.match_token(TokenType::Arrow) || self.current_token.trivia_has_line_terminator {
|
|
|
|
|
self.pattern_bound_names = saved_pattern_bound_names;
|
|
|
|
|
self.load_state();
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
self.consume(); // consume =>
|
|
|
|
|
|
|
|
|
|
self.discard_saved_state();
|
|
|
|
|
|
2026-02-24 06:40:18 -03:00
|
|
|
let ParsedParameters {
|
|
|
|
|
parameters,
|
|
|
|
|
function_length,
|
|
|
|
|
parameter_info,
|
|
|
|
|
is_simple,
|
|
|
|
|
} = parsed;
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Arrow functions always reject duplicate parameter names.
|
|
|
|
|
self.check_arrow_duplicate_parameters(¶meter_info);
|
|
|
|
|
|
|
|
|
|
self.register_function_parameters_with_scope(¶meters, ¶meter_info);
|
|
|
|
|
|
2026-02-24 06:40:18 -03:00
|
|
|
let fn_kind = if is_async {
|
|
|
|
|
FunctionKind::Async
|
|
|
|
|
} else {
|
|
|
|
|
FunctionKind::Normal
|
|
|
|
|
};
|
2026-02-23 07:50:46 -03:00
|
|
|
let src_start = source_start_override.unwrap_or(start).offset;
|
|
|
|
|
|
|
|
|
|
// Set context flags for the arrow body.
|
|
|
|
|
let saved_await_body = self.flags.await_expression_is_valid;
|
|
|
|
|
self.flags.await_expression_is_valid = is_async;
|
|
|
|
|
self.flags.in_class_static_init_block = false;
|
|
|
|
|
|
|
|
|
|
if self.match_token(TokenType::CurlyOpen) {
|
2026-04-18 00:37:40 -03:00
|
|
|
let (body, has_use_strict, insights) = self.parse_function_body(is_async, false, is_simple);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
self.scope_collector.close_scope();
|
|
|
|
|
self.pattern_bound_names = saved_pattern_bound_names;
|
|
|
|
|
|
2026-03-19 22:04:41 -03:00
|
|
|
if has_use_strict || fn_kind != FunctionKind::Normal || self.flags.strict_mode {
|
2026-04-18 00:37:40 -03:00
|
|
|
self.check_parameters_post_body(¶meter_info, has_use_strict || self.flags.strict_mode, fn_kind);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.flags.await_expression_is_valid = saved_await_body;
|
|
|
|
|
self.flags.in_class_static_init_block = saved_static_init;
|
|
|
|
|
self.flags.in_formal_parameter_context = saved_formal_parameter_ctx;
|
2026-04-26 09:19:54 -03:00
|
|
|
let nested_function_ids = self.pop_function_context();
|
|
|
|
|
let function_id = self.insert_function_data(FunctionData {
|
2026-02-23 07:50:46 -03:00
|
|
|
name: None,
|
|
|
|
|
source_text_start: src_start,
|
|
|
|
|
source_text_end: self.source_text_end_offset(),
|
|
|
|
|
body: Box::new(body),
|
|
|
|
|
parameters,
|
|
|
|
|
function_length,
|
|
|
|
|
kind: fn_kind,
|
|
|
|
|
is_strict_mode: self.flags.strict_mode || has_use_strict,
|
|
|
|
|
is_arrow_function: true,
|
|
|
|
|
parsing_insights: insights,
|
2026-04-26 09:19:54 -03:00
|
|
|
nested_function_ids: Some(nested_function_ids),
|
2026-02-23 07:50:46 -03:00
|
|
|
});
|
|
|
|
|
Some(self.expression(start, ExpressionKind::Function(function_id)))
|
|
|
|
|
} else {
|
|
|
|
|
let expression = self.parse_assignment_expression();
|
|
|
|
|
// C++ uses rule_start (function start) for ReturnStatement and FunctionBody.
|
2026-02-24 06:40:18 -03:00
|
|
|
let return_statement = Statement::new(
|
|
|
|
|
self.range_from(start),
|
|
|
|
|
StatementKind::Return(Some(Box::new(expression))),
|
|
|
|
|
);
|
2026-02-23 07:50:46 -03:00
|
|
|
let scope = ScopeData::shared_with_children(vec![return_statement]);
|
|
|
|
|
self.scope_collector.set_scope_node(scope.clone());
|
2026-02-24 06:40:18 -03:00
|
|
|
let body = Statement::new(
|
|
|
|
|
self.range_from(start),
|
|
|
|
|
StatementKind::FunctionBody {
|
|
|
|
|
scope,
|
|
|
|
|
in_strict_mode: self.flags.strict_mode,
|
|
|
|
|
},
|
|
|
|
|
);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// C++ only sets contains_direct_call_to_eval and uses_this_from_environment
|
|
|
|
|
// for expression-body arrows (not uses_this).
|
|
|
|
|
let insights = FunctionParsingInsights {
|
|
|
|
|
contains_direct_call_to_eval: self.scope_collector.contains_direct_call_to_eval(),
|
|
|
|
|
uses_this_from_environment: self.scope_collector.uses_this_from_environment(),
|
|
|
|
|
..FunctionParsingInsights::default()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
self.scope_collector.close_scope();
|
|
|
|
|
self.pattern_bound_names = saved_pattern_bound_names;
|
|
|
|
|
|
2026-03-19 22:04:41 -03:00
|
|
|
if self.flags.strict_mode || fn_kind != FunctionKind::Normal {
|
|
|
|
|
self.check_parameters_post_body(¶meter_info, self.flags.strict_mode, fn_kind);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-23 07:50:46 -03:00
|
|
|
self.flags.await_expression_is_valid = saved_await_body;
|
|
|
|
|
self.flags.in_class_static_init_block = saved_static_init;
|
|
|
|
|
self.flags.in_formal_parameter_context = saved_formal_parameter_ctx;
|
2026-04-26 09:19:54 -03:00
|
|
|
let nested_function_ids = self.pop_function_context();
|
|
|
|
|
let function_id = self.insert_function_data(FunctionData {
|
2026-02-23 07:50:46 -03:00
|
|
|
name: None,
|
|
|
|
|
source_text_start: src_start,
|
|
|
|
|
source_text_end: self.source_text_end_offset(),
|
|
|
|
|
body: Box::new(body),
|
|
|
|
|
parameters,
|
|
|
|
|
function_length,
|
|
|
|
|
kind: fn_kind,
|
|
|
|
|
is_strict_mode: self.flags.strict_mode,
|
|
|
|
|
is_arrow_function: true,
|
|
|
|
|
parsing_insights: insights,
|
2026-04-26 09:19:54 -03:00
|
|
|
nested_function_ids: Some(nested_function_ids),
|
2026-02-23 07:50:46 -03:00
|
|
|
});
|
|
|
|
|
Some(self.expression(start, ExpressionKind::Function(function_id)))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-24 06:40:18 -03:00
|
|
|
pub(crate) fn parse_method_definition(
|
|
|
|
|
&mut self,
|
|
|
|
|
is_async: bool,
|
|
|
|
|
is_generator: bool,
|
|
|
|
|
method_kind: MethodKind,
|
|
|
|
|
function_start: Position,
|
|
|
|
|
) -> Expression {
|
2026-02-23 07:50:46 -03:00
|
|
|
let start = function_start;
|
|
|
|
|
|
|
|
|
|
let saved_might_need_arguments = self.flags.function_might_need_arguments_object;
|
|
|
|
|
self.flags.function_might_need_arguments_object = false;
|
|
|
|
|
|
|
|
|
|
let fn_kind = FunctionKind::from_async_generator(is_async, is_generator);
|
|
|
|
|
|
|
|
|
|
// Open function scope for method.
|
|
|
|
|
self.scope_collector.open_function_scope(None);
|
|
|
|
|
|
|
|
|
|
let in_generator_before = self.flags.in_generator_function_context;
|
|
|
|
|
let await_before = self.flags.await_expression_is_valid;
|
|
|
|
|
let saved_static_init = self.flags.in_class_static_init_block;
|
|
|
|
|
let saved_field_init = self.flags.in_class_field_initializer;
|
|
|
|
|
let saved_allow_super_call = self.flags.allow_super_constructor_call;
|
|
|
|
|
let saved_allow_super_lookup = self.flags.allow_super_property_lookup;
|
2026-03-19 22:01:19 -03:00
|
|
|
let saved_new_target = self.flags.new_target_is_valid;
|
2026-02-23 07:50:46 -03:00
|
|
|
self.flags.in_generator_function_context = is_generator;
|
|
|
|
|
self.flags.await_expression_is_valid = is_async;
|
|
|
|
|
self.flags.in_class_static_init_block = false;
|
|
|
|
|
self.flags.in_class_field_initializer = false;
|
2026-04-18 00:37:40 -03:00
|
|
|
self.flags.allow_super_constructor_call = method_kind == MethodKind::Constructor && self.class_has_super_class;
|
2026-02-23 07:50:46 -03:00
|
|
|
self.flags.allow_super_property_lookup = true;
|
2026-03-19 22:01:19 -03:00
|
|
|
self.flags.new_target_is_valid = true;
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Save pattern_bound_names so that destructuring patterns in the
|
|
|
|
|
// method body don't steal names from an outer binding context.
|
|
|
|
|
let saved_pattern_bound_names = std::mem::take(&mut self.pattern_bound_names);
|
|
|
|
|
|
2026-04-26 09:19:54 -03:00
|
|
|
self.push_function_context();
|
2026-02-23 07:50:46 -03:00
|
|
|
let parsed = self.parse_formal_parameters();
|
|
|
|
|
|
|
|
|
|
self.register_function_parameters_with_scope(&parsed.parameters, &parsed.parameter_info);
|
|
|
|
|
|
|
|
|
|
if method_kind == MethodKind::Getter && !parsed.parameters.is_empty() {
|
|
|
|
|
self.syntax_error("Getter function must have no arguments");
|
|
|
|
|
}
|
|
|
|
|
if method_kind == MethodKind::Setter
|
2026-04-18 00:37:40 -03:00
|
|
|
&& (parsed.parameters.len() != 1 || parsed.parameters.first().is_some_and(|p| p.is_rest))
|
2026-02-23 07:50:46 -03:00
|
|
|
{
|
|
|
|
|
self.syntax_error("Setter function must have one argument");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.flags.in_generator_function_context = in_generator_before;
|
|
|
|
|
self.flags.await_expression_is_valid = await_before;
|
|
|
|
|
|
2026-04-18 00:37:40 -03:00
|
|
|
let (body, has_use_strict, mut insights) = self.parse_function_body(is_async, is_generator, parsed.is_simple);
|
2026-02-23 07:50:46 -03:00
|
|
|
self.flags.allow_super_constructor_call = saved_allow_super_call;
|
|
|
|
|
self.flags.allow_super_property_lookup = saved_allow_super_lookup;
|
|
|
|
|
|
|
|
|
|
self.scope_collector.close_scope();
|
|
|
|
|
self.pattern_bound_names = saved_pattern_bound_names;
|
|
|
|
|
|
2026-03-19 22:53:18 -03:00
|
|
|
// Check parameters before restoring flags so that the method's
|
|
|
|
|
// context is used (e.g. in_class_static_init_block must remain
|
|
|
|
|
// false to allow `await` as a parameter name in generators).
|
2026-02-23 07:50:46 -03:00
|
|
|
if has_use_strict || fn_kind != FunctionKind::Normal {
|
|
|
|
|
self.check_parameters_post_body(&parsed.parameter_info, has_use_strict, fn_kind);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-19 22:53:18 -03:00
|
|
|
self.flags.in_class_static_init_block = saved_static_init;
|
|
|
|
|
self.flags.in_class_field_initializer = saved_field_init;
|
|
|
|
|
self.flags.new_target_is_valid = saved_new_target;
|
|
|
|
|
|
2026-02-23 07:50:46 -03:00
|
|
|
insights.might_need_arguments_object = self.flags.function_might_need_arguments_object;
|
|
|
|
|
self.flags.function_might_need_arguments_object = saved_might_need_arguments;
|
|
|
|
|
|
|
|
|
|
// Class constructors always need a function environment for `this` binding
|
|
|
|
|
// management (super() binds this in derived constructors, and base constructors
|
|
|
|
|
// need it for OrdinaryCallBindThis).
|
|
|
|
|
if method_kind == MethodKind::Constructor {
|
|
|
|
|
insights.uses_this = true;
|
|
|
|
|
insights.uses_this_from_environment = true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-26 09:19:54 -03:00
|
|
|
let nested_function_ids = self.pop_function_context();
|
|
|
|
|
let function_id = self.insert_function_data(FunctionData {
|
2026-02-23 07:50:46 -03:00
|
|
|
name: None,
|
|
|
|
|
source_text_start: function_start.offset,
|
|
|
|
|
source_text_end: self.source_text_end_offset(),
|
|
|
|
|
body: Box::new(body),
|
|
|
|
|
parameters: parsed.parameters,
|
|
|
|
|
function_length: parsed.function_length,
|
|
|
|
|
kind: fn_kind,
|
|
|
|
|
is_strict_mode: self.flags.strict_mode || has_use_strict,
|
|
|
|
|
is_arrow_function: false,
|
|
|
|
|
parsing_insights: insights,
|
2026-04-26 09:19:54 -03:00
|
|
|
nested_function_ids: Some(nested_function_ids),
|
2026-02-23 07:50:46 -03:00
|
|
|
});
|
|
|
|
|
self.expression(start, ExpressionKind::Function(function_id))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn hex_digit(c: u16) -> Option<u16> {
|
|
|
|
|
match c {
|
|
|
|
|
0x30..=0x39 => Some(c - 0x30),
|
|
|
|
|
0x41..=0x46 => Some(c - 0x41 + 10),
|
|
|
|
|
0x61..=0x66 => Some(c - 0x61 + 10),
|
|
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn is_octal_char(c: u16) -> bool {
|
|
|
|
|
c >= ch(b'0') && c <= ch(b'7')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn parse_octal_escape(inner: &[u16], i: usize) -> (u16, usize) {
|
|
|
|
|
let first = (inner[i] - ch(b'0')) as u32;
|
|
|
|
|
let mut value = first;
|
|
|
|
|
let mut consumed = 0;
|
|
|
|
|
|
|
|
|
|
if i + 1 < inner.len() && is_octal_char(inner[i + 1]) {
|
|
|
|
|
value = value * 8 + (inner[i + 1] - ch(b'0')) as u32;
|
|
|
|
|
consumed = 1;
|
|
|
|
|
|
|
|
|
|
if i + 2 < inner.len() && is_octal_char(inner[i + 2]) && first <= 3 {
|
|
|
|
|
value = value * 8 + (inner[i + 2] - ch(b'0')) as u32;
|
|
|
|
|
consumed = 2;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
(value as u16, consumed)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn parse_hex_escape(raw: &[u16], i: usize) -> Option<(usize, u16)> {
|
|
|
|
|
if i + 2 >= raw.len() {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
let high = hex_digit(raw[i + 1])?;
|
|
|
|
|
let low = hex_digit(raw[i + 2])?;
|
|
|
|
|
Some((2, high * 16 + low))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn parse_unicode_escape(raw: &[u16], i: usize) -> Option<(usize, u32)> {
|
|
|
|
|
if i + 1 >= raw.len() {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
if raw[i + 1] == ch(b'{') {
|
|
|
|
|
let mut j = i + 2;
|
|
|
|
|
let mut value: u32 = 0;
|
|
|
|
|
let mut digits = 0;
|
|
|
|
|
while j < raw.len() && raw[j] != ch(b'}') {
|
|
|
|
|
let d = hex_digit(raw[j])? as u32;
|
|
|
|
|
value = value * 16 + d;
|
|
|
|
|
if value > 0x10FFFF {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
digits += 1;
|
|
|
|
|
j += 1;
|
|
|
|
|
}
|
|
|
|
|
if j >= raw.len() || digits == 0 {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
Some((j - i, value))
|
|
|
|
|
} else {
|
|
|
|
|
if i + 4 >= raw.len() {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
let d0 = hex_digit(raw[i + 1])? as u32;
|
|
|
|
|
let d1 = hex_digit(raw[i + 2])? as u32;
|
|
|
|
|
let d2 = hex_digit(raw[i + 3])? as u32;
|
|
|
|
|
let d3 = hex_digit(raw[i + 4])? as u32;
|
|
|
|
|
Some((4, (d0 << 12) | (d1 << 8) | (d2 << 4) | d3))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn push_code_point(result: &mut Vec<u16>, code_point: u32) {
|
|
|
|
|
if code_point > 0xFFFF {
|
|
|
|
|
let adjusted = code_point - 0x10000;
|
|
|
|
|
result.push((0xD800 | ((adjusted >> 10) & 0x3FF)) as u16);
|
|
|
|
|
result.push((0xDC00 | (adjusted & 0x3FF)) as u16);
|
|
|
|
|
} else {
|
|
|
|
|
result.push(code_point as u16);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn raw_template_value(raw: &[u16]) -> Utf16String {
|
|
|
|
|
let mut result = Utf16String(Vec::with_capacity(raw.len()));
|
|
|
|
|
let mut i = 0;
|
|
|
|
|
while i < raw.len() {
|
|
|
|
|
if raw[i] == ch(b'\r') {
|
|
|
|
|
result.0.push(ch(b'\n'));
|
|
|
|
|
if i + 1 < raw.len() && raw[i + 1] == ch(b'\n') {
|
|
|
|
|
i += 1;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
result.0.push(raw[i]);
|
|
|
|
|
}
|
|
|
|
|
i += 1;
|
|
|
|
|
}
|
|
|
|
|
result
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn token_to_binary_op(tt: TokenType) -> BinaryOp {
|
|
|
|
|
match tt {
|
|
|
|
|
TokenType::Plus => BinaryOp::Addition,
|
|
|
|
|
TokenType::Minus => BinaryOp::Subtraction,
|
|
|
|
|
TokenType::Asterisk => BinaryOp::Multiplication,
|
|
|
|
|
TokenType::Slash => BinaryOp::Division,
|
|
|
|
|
TokenType::Percent => BinaryOp::Modulo,
|
|
|
|
|
TokenType::DoubleAsterisk => BinaryOp::Exponentiation,
|
|
|
|
|
TokenType::EqualsEqualsEquals => BinaryOp::StrictlyEquals,
|
|
|
|
|
TokenType::ExclamationMarkEqualsEquals => BinaryOp::StrictlyInequals,
|
|
|
|
|
TokenType::EqualsEquals => BinaryOp::LooselyEquals,
|
|
|
|
|
TokenType::ExclamationMarkEquals => BinaryOp::LooselyInequals,
|
|
|
|
|
TokenType::GreaterThan => BinaryOp::GreaterThan,
|
|
|
|
|
TokenType::GreaterThanEquals => BinaryOp::GreaterThanEquals,
|
|
|
|
|
TokenType::LessThan => BinaryOp::LessThan,
|
|
|
|
|
TokenType::LessThanEquals => BinaryOp::LessThanEquals,
|
|
|
|
|
TokenType::Ampersand => BinaryOp::BitwiseAnd,
|
|
|
|
|
TokenType::Pipe => BinaryOp::BitwiseOr,
|
|
|
|
|
TokenType::Caret => BinaryOp::BitwiseXor,
|
|
|
|
|
TokenType::ShiftLeft => BinaryOp::LeftShift,
|
|
|
|
|
TokenType::ShiftRight => BinaryOp::RightShift,
|
|
|
|
|
TokenType::UnsignedShiftRight => BinaryOp::UnsignedRightShift,
|
|
|
|
|
TokenType::In => BinaryOp::In,
|
|
|
|
|
TokenType::Instanceof => BinaryOp::InstanceOf,
|
|
|
|
|
_ => unreachable!("unexpected token {:?} in binary expression", tt),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn token_to_assignment_op(tt: TokenType) -> AssignmentOp {
|
|
|
|
|
match tt {
|
|
|
|
|
TokenType::Equals => AssignmentOp::Assignment,
|
|
|
|
|
TokenType::PlusEquals => AssignmentOp::AdditionAssignment,
|
|
|
|
|
TokenType::MinusEquals => AssignmentOp::SubtractionAssignment,
|
|
|
|
|
TokenType::AsteriskEquals => AssignmentOp::MultiplicationAssignment,
|
|
|
|
|
TokenType::SlashEquals => AssignmentOp::DivisionAssignment,
|
|
|
|
|
TokenType::PercentEquals => AssignmentOp::ModuloAssignment,
|
|
|
|
|
TokenType::DoubleAsteriskEquals => AssignmentOp::ExponentiationAssignment,
|
|
|
|
|
TokenType::AmpersandEquals => AssignmentOp::BitwiseAndAssignment,
|
|
|
|
|
TokenType::PipeEquals => AssignmentOp::BitwiseOrAssignment,
|
|
|
|
|
TokenType::CaretEquals => AssignmentOp::BitwiseXorAssignment,
|
|
|
|
|
TokenType::ShiftLeftEquals => AssignmentOp::LeftShiftAssignment,
|
|
|
|
|
TokenType::ShiftRightEquals => AssignmentOp::RightShiftAssignment,
|
|
|
|
|
TokenType::UnsignedShiftRightEquals => AssignmentOp::UnsignedRightShiftAssignment,
|
|
|
|
|
TokenType::DoubleAmpersandEquals => AssignmentOp::AndAssignment,
|
|
|
|
|
TokenType::DoublePipeEquals => AssignmentOp::OrAssignment,
|
|
|
|
|
TokenType::DoubleQuestionMarkEquals => AssignmentOp::NullishAssignment,
|
|
|
|
|
_ => unreachable!("unexpected token {:?} in assignment expression", tt),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) fn parse_numeric_value(value: &[u16]) -> f64 {
|
2026-02-24 06:40:18 -03:00
|
|
|
let s: String = value
|
|
|
|
|
.iter()
|
|
|
|
|
.filter(|&&c| c != '_' as u16)
|
|
|
|
|
.map(|&c| c as u8 as char)
|
|
|
|
|
.collect();
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
if s.starts_with("0x") || s.starts_with("0X") {
|
|
|
|
|
parse_integer_with_radix(&s[2..], 16)
|
|
|
|
|
} else if s.starts_with("0o") || s.starts_with("0O") {
|
|
|
|
|
parse_integer_with_radix(&s[2..], 8)
|
|
|
|
|
} else if s.starts_with("0b") || s.starts_with("0B") {
|
|
|
|
|
parse_integer_with_radix(&s[2..], 2)
|
|
|
|
|
} else if s.starts_with('0') && s.len() > 1 && s.as_bytes()[1].is_ascii_digit() {
|
|
|
|
|
let digits = &s[1..];
|
|
|
|
|
if digits.bytes().all(|b| (b'0'..=b'7').contains(&b)) {
|
|
|
|
|
parse_integer_with_radix(digits, 8)
|
|
|
|
|
} else {
|
|
|
|
|
s.parse::<f64>().unwrap_or(f64::NAN)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
s.parse::<f64>().unwrap_or(f64::NAN)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn parse_integer_with_radix(digits: &str, radix: u32) -> f64 {
|
|
|
|
|
if let Ok(v) = u64::from_str_radix(digits, radix) {
|
|
|
|
|
return v as f64;
|
|
|
|
|
}
|
|
|
|
|
let mut result: f64 = 0.0;
|
|
|
|
|
for ch in digits.chars() {
|
|
|
|
|
let digit = ch.to_digit(radix);
|
|
|
|
|
if let Some(d) = digit {
|
|
|
|
|
result = result * (radix as f64) + (d as f64);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
result
|
|
|
|
|
}
|