use crate::error::{StatorError, StatorResult};
use crate::parser::ast::{
ArrayExpr, ArrayPat, ArrowBody, ArrowExpr, AssignExpr, AssignOp, AssignPat, AssignPatProp,
AssignTarget, AwaitExpr, BigIntLit, BinaryExpr, BinaryOp, BlockStmt, BoolLit, BreakStmt,
ClassBody, ClassDecl, ClassExpr, ClassMember, ContinueStmt, DebuggerStmt, DoWhileStmt,
EmptyStmt, ExportAllDecl, ExportDefaultDecl, ExportDefaultExpr, ExportNamedDecl,
ExportSpecifier, Expr, ExprStmt, FnDecl, FnExpr, ForInOfLeft, ForInStmt, ForInit, ForOfStmt,
ForStmt, Ident, IfStmt, ImportDecl, ImportDefaultSpecifier, ImportExpr, ImportNamedSpecifier,
ImportNamespaceSpecifier, ImportSpecifier, KeyValuePatProp, LabeledStmt, LogicalExpr,
LogicalOp, MemberProp, MetaPropExpr, MethodDef, MethodKind, ModuleDecl, ModuleExportName,
NewExpr, NullLit, NumLit, ObjectExpr, ObjectPat, ObjectPatProp, ObjectProp, Param, Pat,
PrivateIdent, Program, ProgramItem, Prop, PropKey, PropValue, PropertyDef, RegExpLit,
RestElement, ReturnStmt, SequenceExpr, SourceLocation, SourceType, SpreadElement, StaticBlock,
Stmt, StringLit, SwitchCase, SwitchStmt, TemplateElement, TemplateLit, ThrowStmt, TryStmt,
UnaryExpr, UnaryOp, UpdateExpr, UpdateOp, VarDecl, VarDeclarator, VarKind, WhileStmt, WithStmt,
YieldExpr,
};
use crate::parser::scanner::{Scanner, Span, Token, TokenKind, TokenValue, cook_template_raw};
const MAX_RECURSION_DEPTH: usize = 256;
pub struct Parser<'src> {
scanner: Scanner<'src>,
current: Token,
no_in: bool,
depth: usize,
class_depth: usize,
strict_mode: bool,
function_depth: usize,
iteration_depth: usize,
breakable_depth: usize,
in_nullish_coalesce: bool,
labels: Vec<(String, bool)>,
is_module: bool,
async_function_depth: usize,
}
impl<'src> Parser<'src> {
fn new(source: &'src str) -> StatorResult<Self> {
let mut scanner = Scanner::new(source);
let current = Self::next_significant(&mut scanner)?;
Ok(Self {
scanner,
current,
no_in: false,
depth: 0,
class_depth: 0,
strict_mode: false,
function_depth: 0,
iteration_depth: 0,
breakable_depth: 0,
in_nullish_coalesce: false,
labels: Vec::new(),
is_module: false,
async_function_depth: 0,
})
}
fn next_significant(scanner: &mut Scanner<'_>) -> StatorResult<Token> {
loop {
let tok = scanner.next_token()?;
match tok.kind {
TokenKind::SingleLineComment
| TokenKind::MultiLineComment
| TokenKind::HashbangComment => continue,
_ => return Ok(tok),
}
}
}
fn peek_kind(&self) -> TokenKind {
self.current.kind
}
fn is_await_using_lookahead(&self) -> bool {
if self.current.kind != TokenKind::Await {
return false;
}
let mut scanner_clone = self.scanner.clone();
matches!(
Self::next_significant(&mut scanner_clone),
Ok(tok) if tok.kind == TokenKind::Using
)
}
fn current_span(&self) -> Span {
self.current.span
}
fn labeled_body_is_iteration(&self) -> bool {
let mut scanner = self.scanner.clone();
let mut tok = self.current.clone();
loop {
match tok.kind {
TokenKind::For | TokenKind::While | TokenKind::Do => return true,
TokenKind::Identifier => {
let Ok(colon) = Self::next_significant(&mut scanner) else {
return false;
};
if colon.kind != TokenKind::Colon {
return false;
}
let Ok(next_tok) = Self::next_significant(&mut scanner) else {
return false;
};
tok = next_tok;
}
_ => return false,
}
}
}
fn bump(&mut self) -> StatorResult<Token> {
let next = Self::next_significant(&mut self.scanner)?;
Ok(std::mem::replace(&mut self.current, next))
}
fn eat(&mut self, kind: TokenKind) -> StatorResult<bool> {
if self.current.kind == kind {
self.bump()?;
Ok(true)
} else {
Ok(false)
}
}
fn expect(&mut self, kind: TokenKind) -> StatorResult<Token> {
if self.current.kind != kind {
let span = self.current.span;
return Err(Self::make_error(
span,
&format!("expected {kind:?}, got {:?}", self.current.kind),
));
}
self.bump()
}
fn error(&self, msg: &str) -> StatorError {
Self::make_error(self.current.span, msg)
}
fn error_at(span: Span, msg: &str) -> StatorError {
Self::make_error(span, msg)
}
fn make_error(span: Span, msg: &str) -> StatorError {
StatorError::SyntaxError(format!(
"at {}:{} \u{2014} {}",
span.start.line, span.start.column, msg
))
}
#[inline]
fn enter(&mut self) -> StatorResult<()> {
self.depth += 1;
if self.depth > MAX_RECURSION_DEPTH {
Err(self.error("maximum nesting depth exceeded"))
} else {
Ok(())
}
}
#[inline]
fn leave(&mut self) {
self.depth -= 1;
}
fn consume_semicolon(&mut self) -> StatorResult<()> {
match self.peek_kind() {
TokenKind::Semicolon => {
self.bump()?;
}
TokenKind::RightBrace | TokenKind::Eof => {}
_ if self.current.had_line_terminator_before => {}
_ => {
return Err(self.error("expected ';' after statement"));
}
}
Ok(())
}
fn directive_prologue_value(stmt: &Stmt) -> Option<String> {
let Stmt::Expr(expr_stmt) = stmt else {
return None;
};
let Expr::Str(str_lit) = expr_stmt.expr.as_ref() else {
return None;
};
let raw = &str_lit.value;
let bytes = raw.as_bytes();
if bytes.len() >= 2
&& (bytes[0] == b'\'' || bytes[0] == b'"')
&& bytes[bytes.len() - 1] == bytes[0]
{
Some(raw[1..raw.len() - 1].to_owned())
} else {
Some(raw.clone())
}
}
fn parse_program(&mut self) -> StatorResult<Program> {
let start = self.current_span();
let mut body = Vec::new();
let mut is_module = self.is_module;
let mut is_strict = self.is_module;
let mut in_directive_prologue = true;
if self.is_module {
self.strict_mode = true;
}
while self.peek_kind() != TokenKind::Eof {
match self.peek_kind() {
TokenKind::Import => {
let saved_scanner = self.scanner.clone();
let saved_current = self.current.clone();
self.bump()?; let next = self.peek_kind();
self.scanner = saved_scanner;
self.current = saved_current;
if next == TokenKind::LeftParen || next == TokenKind::Dot {
let stmt = self.parse_stmt()?;
if in_directive_prologue {
if let Some(value) = Self::directive_prologue_value(&stmt) {
if value == "use strict" {
self.strict_mode = true;
is_strict = true;
}
} else {
in_directive_prologue = false;
}
}
body.push(ProgramItem::Stmt(stmt));
} else {
in_directive_prologue = false;
if !is_module {
is_module = true;
self.is_module = true;
self.strict_mode = true;
is_strict = true;
}
let decl = self.parse_import_decl()?;
body.push(ProgramItem::ModuleDecl(ModuleDecl::Import(decl)));
}
}
TokenKind::Export => {
in_directive_prologue = false;
if !is_module {
is_module = true;
self.is_module = true;
self.strict_mode = true;
is_strict = true;
}
let decl = self.parse_export_decl()?;
body.push(ProgramItem::ModuleDecl(decl));
}
_ => {
let stmt = self.parse_stmt()?;
if in_directive_prologue {
if let Some(value) = Self::directive_prologue_value(&stmt) {
if value == "use strict" {
self.strict_mode = true;
is_strict = true;
}
} else {
in_directive_prologue = false;
}
}
body.push(ProgramItem::Stmt(stmt));
}
}
}
let end = self.current_span();
let is_strict = is_strict || is_module;
Ok(Program {
loc: Self::merge_spans(start, end),
source_type: if is_module {
SourceType::Module
} else {
SourceType::Script
},
body,
is_strict,
})
}
fn parse_stmt(&mut self) -> StatorResult<Stmt> {
stacker::maybe_grow(32 * 1024, 512 * 1024, || {
self.enter()?;
let result = self.parse_stmt_inner();
self.leave();
result
})
}
fn parse_stmt_inner(&mut self) -> StatorResult<Stmt> {
match self.peek_kind() {
TokenKind::Semicolon => {
let span = self.current_span();
self.bump()?;
Ok(Stmt::Empty(EmptyStmt { loc: span }))
}
TokenKind::LeftBrace => self.parse_block().map(Stmt::Block),
TokenKind::Var => {
let tok = self.bump()?;
self.parse_var_decl(VarKind::Var, tok.span)
}
TokenKind::Let => {
let tok = self.bump()?;
self.parse_var_decl(VarKind::Let, tok.span)
}
TokenKind::Const => {
let tok = self.bump()?;
self.parse_var_decl(VarKind::Const, tok.span)
}
TokenKind::Using => {
let tok = self.bump()?;
self.parse_var_decl(VarKind::Using, tok.span)
}
TokenKind::If => self.parse_if(),
TokenKind::While => self.parse_while(),
TokenKind::Do => self.parse_do_while(),
TokenKind::For => self.parse_for(),
TokenKind::Return => self.parse_return(),
TokenKind::Break => self.parse_break(),
TokenKind::Continue => self.parse_continue(),
TokenKind::Throw => self.parse_throw(),
TokenKind::Try => self.parse_try(),
TokenKind::Switch => self.parse_switch(),
TokenKind::With => self.parse_with(),
TokenKind::Debugger => {
let span = self.current_span();
self.bump()?;
self.consume_semicolon()?;
Ok(Stmt::Debugger(DebuggerStmt { loc: span }))
}
TokenKind::Function => {
let tok = self.bump()?;
self.parse_fn_decl(tok.span, false)
}
TokenKind::Async => {
let saved_scanner = self.scanner.clone();
let saved_current = self.current.clone();
let async_tok = self.bump()?; if self.peek_kind() == TokenKind::Function
&& !self.current.had_line_terminator_before
{
let fn_tok = self.bump()?; return self
.parse_fn_decl(Self::merge_spans(async_tok.span, fn_tok.span), true);
}
self.scanner = saved_scanner;
self.current = saved_current;
self.parse_expr_stmt()
}
TokenKind::Class => self.parse_class_decl(),
TokenKind::Await if self.is_await_using_lookahead() => {
let start = self.current_span();
self.bump()?; let tok = self.bump()?; self.parse_var_decl(VarKind::AwaitUsing, Self::merge_spans(start, tok.span))
}
TokenKind::Identifier => {
let saved_scanner = self.scanner.clone();
let saved_current = self.current.clone();
let id_tok = self.bump()?; if self.peek_kind() == TokenKind::Colon {
self.bump()?; let label = self.ident_from_token(&id_tok)?;
let is_iteration = self.labeled_body_is_iteration();
self.labels.push((label.name.clone(), is_iteration));
let body = self.parse_stmt();
self.labels.pop();
let body = body?;
if self.strict_mode && matches!(body, Stmt::FnDecl(_)) {
return Err(Self::error_at(
label.loc,
"labelled function declarations are not allowed in strict mode",
));
}
let end = body.loc();
return Ok(Stmt::Labeled(LabeledStmt {
loc: Self::merge_spans(id_tok.span, end),
label,
body: Box::new(body),
}));
}
self.scanner = saved_scanner;
self.current = saved_current;
self.parse_expr_stmt()
}
_ => self.parse_expr_stmt(),
}
}
fn parse_block(&mut self) -> StatorResult<BlockStmt> {
let start = self.current_span();
self.expect(TokenKind::LeftBrace)?;
let mut body = Vec::new();
while self.peek_kind() != TokenKind::RightBrace {
if self.peek_kind() == TokenKind::Eof {
return Err(self.error("unexpected end of input inside block"));
}
body.push(self.parse_stmt()?);
}
let end = self.current_span();
self.bump()?; Ok(BlockStmt {
loc: Self::merge_spans(start, end),
body,
})
}
fn parse_var_decl(&mut self, kind: VarKind, kw_span: Span) -> StatorResult<Stmt> {
let mut declarators = Vec::new();
loop {
declarators.push(self.parse_var_declarator()?);
if !self.eat(TokenKind::Comma)? {
break;
}
}
if kind == VarKind::Const {
for d in &declarators {
if d.init.is_none() {
return Err(Self::error_at(
d.loc,
"const declarations must have an initializer",
));
}
}
}
self.consume_semicolon()?;
let end = declarators.last().map(|d| d.id.loc()).unwrap_or(kw_span);
Ok(Stmt::VarDecl(VarDecl {
loc: Self::merge_spans(kw_span, end),
kind,
declarators,
}))
}
fn parse_var_declarator(&mut self) -> StatorResult<VarDeclarator> {
let start = self.current_span();
let id = self.parse_binding_pat()?;
let id_end = id.loc();
let init = if self.eat(TokenKind::Equal)? {
Some(Box::new(self.parse_assignment_expr()?))
} else {
None
};
let end = init.as_ref().map(|e| e.loc()).unwrap_or(id_end);
Ok(VarDeclarator {
loc: Self::merge_spans(start, end),
id,
init,
})
}
fn parse_expr_stmt(&mut self) -> StatorResult<Stmt> {
let start = self.current_span();
let expr = self.parse_expr()?;
let end = expr.loc();
self.consume_semicolon()?;
Ok(Stmt::Expr(ExprStmt {
loc: Self::merge_spans(start, end),
expr: Box::new(expr),
}))
}
fn check_forbidden_statement_position(&self, annex_b_function: bool) -> StatorResult<()> {
let span = self.current_span();
match self.peek_kind() {
TokenKind::Async => {
let mut scanner_clone = self.scanner.clone();
if let Ok(next) = Self::next_significant(&mut scanner_clone)
&& next.kind == TokenKind::Function
&& !next.had_line_terminator_before
{
return Err(Self::error_at(
span,
"async function declaration is not allowed in statement position",
));
}
Ok(())
}
TokenKind::Class => Err(Self::error_at(
span,
"class declaration is not allowed in statement position",
)),
TokenKind::Let | TokenKind::Const => Err(Self::error_at(
span,
"lexical declaration is not allowed in statement position",
)),
TokenKind::Function => {
if self.strict_mode {
return Err(Self::error_at(
span,
"function declaration is not allowed in statement position in strict mode",
));
}
let mut scanner_clone = self.scanner.clone();
if let Ok(next) = Self::next_significant(&mut scanner_clone)
&& next.kind == TokenKind::Star
{
return Err(Self::error_at(
span,
"generator function declaration is not allowed in statement position",
));
}
if !annex_b_function {
return Err(Self::error_at(
span,
"function declaration is not allowed in statement position",
));
}
Ok(())
}
_ => Ok(()),
}
}
fn parse_if(&mut self) -> StatorResult<Stmt> {
let start = self.current_span();
self.bump()?; self.expect(TokenKind::LeftParen)?;
let test = self.parse_expr()?;
self.expect(TokenKind::RightParen)?;
self.check_forbidden_statement_position(true)?;
let consequent = self.parse_stmt()?;
let alternate = if self.eat(TokenKind::Else)? {
self.check_forbidden_statement_position(true)?;
Some(Box::new(self.parse_stmt()?))
} else {
None
};
let end = alternate
.as_ref()
.map(|a| a.loc())
.unwrap_or_else(|| consequent.loc());
Ok(Stmt::If(IfStmt {
loc: Self::merge_spans(start, end),
test: Box::new(test),
consequent: Box::new(consequent),
alternate,
}))
}
fn parse_while(&mut self) -> StatorResult<Stmt> {
let start = self.current_span();
self.bump()?; self.expect(TokenKind::LeftParen)?;
let test = self.parse_expr()?;
self.expect(TokenKind::RightParen)?;
self.check_forbidden_statement_position(false)?;
self.iteration_depth += 1;
self.breakable_depth += 1;
let body = self.parse_stmt();
self.breakable_depth -= 1;
self.iteration_depth -= 1;
let body = body?;
let end = body.loc();
Ok(Stmt::While(WhileStmt {
loc: Self::merge_spans(start, end),
test: Box::new(test),
body: Box::new(body),
}))
}
fn parse_do_while(&mut self) -> StatorResult<Stmt> {
let start = self.current_span();
self.bump()?; self.check_forbidden_statement_position(false)?;
self.iteration_depth += 1;
self.breakable_depth += 1;
let body = self.parse_stmt();
self.breakable_depth -= 1;
self.iteration_depth -= 1;
let body = body?;
self.expect(TokenKind::While)?;
self.expect(TokenKind::LeftParen)?;
let test = self.parse_expr()?;
let end = self.current_span();
self.expect(TokenKind::RightParen)?;
self.consume_semicolon()?;
Ok(Stmt::DoWhile(DoWhileStmt {
loc: Self::merge_spans(start, end),
body: Box::new(body),
test: Box::new(test),
}))
}
fn parse_for(&mut self) -> StatorResult<Stmt> {
let start = self.current_span();
self.bump()?;
let is_await = self.eat(TokenKind::Await)?;
self.expect(TokenKind::LeftParen)?;
if matches!(
self.peek_kind(),
TokenKind::Var | TokenKind::Let | TokenKind::Const
) {
let kind = match self.peek_kind() {
TokenKind::Var => VarKind::Var,
TokenKind::Let => VarKind::Let,
TokenKind::Const => VarKind::Const,
_ => unreachable!(),
};
let kw_span = self.current_span();
self.bump()?; let declarator = self.parse_var_declarator()?;
if self.peek_kind() == TokenKind::In || self.peek_kind() == TokenKind::Of {
let is_of = self.peek_kind() == TokenKind::Of;
self.bump()?; let right = self.parse_assignment_expr()?;
self.expect(TokenKind::RightParen)?;
self.check_forbidden_statement_position(false)?;
self.iteration_depth += 1;
self.breakable_depth += 1;
let body = self.parse_stmt();
self.breakable_depth -= 1;
self.iteration_depth -= 1;
let body = body?;
let end = body.loc();
let var_decl = VarDecl {
loc: Self::merge_spans(kw_span, declarator.loc),
kind,
declarators: vec![declarator],
};
let left = ForInOfLeft::VarDecl(var_decl);
return if is_of {
Ok(Stmt::ForOf(ForOfStmt {
loc: Self::merge_spans(start, end),
is_await,
left,
right: Box::new(right),
body: Box::new(body),
}))
} else {
Ok(Stmt::ForIn(ForInStmt {
loc: Self::merge_spans(start, end),
left,
right: Box::new(right),
body: Box::new(body),
}))
};
}
let mut declarators = vec![declarator];
while self.eat(TokenKind::Comma)? {
declarators.push(self.parse_var_declarator()?);
}
let decl_end = declarators.last().map(|d| d.id.loc()).unwrap_or(kw_span);
let init = Some(ForInit::VarDecl(VarDecl {
loc: Self::merge_spans(kw_span, decl_end),
kind,
declarators,
}));
return self.parse_c_style_for_rest(start, init);
}
if self.peek_kind() == TokenKind::Semicolon {
return self.parse_c_style_for_rest(start, None);
}
let saved_no_in = self.no_in;
self.no_in = true;
let init_expr = self.parse_expr()?;
self.no_in = saved_no_in;
if self.peek_kind() == TokenKind::In || self.peek_kind() == TokenKind::Of {
let is_of = self.peek_kind() == TokenKind::Of;
self.bump()?; let right = self.parse_assignment_expr()?;
self.expect(TokenKind::RightParen)?;
self.check_forbidden_statement_position(false)?;
self.iteration_depth += 1;
self.breakable_depth += 1;
let body = self.parse_stmt();
self.breakable_depth -= 1;
self.iteration_depth -= 1;
let body = body?;
let end = body.loc();
let left = self.expr_to_for_lhs(init_expr)?;
return if is_of {
Ok(Stmt::ForOf(ForOfStmt {
loc: Self::merge_spans(start, end),
is_await,
left,
right: Box::new(right),
body: Box::new(body),
}))
} else {
Ok(Stmt::ForIn(ForInStmt {
loc: Self::merge_spans(start, end),
left,
right: Box::new(right),
body: Box::new(body),
}))
};
}
let init = Some(ForInit::Expr(Box::new(init_expr)));
self.parse_c_style_for_rest(start, init)
}
fn parse_c_style_for_rest(&mut self, start: Span, init: Option<ForInit>) -> StatorResult<Stmt> {
self.expect(TokenKind::Semicolon)?;
let test = if self.peek_kind() == TokenKind::Semicolon {
None
} else {
Some(Box::new(self.parse_expr()?))
};
self.expect(TokenKind::Semicolon)?;
let update = if self.peek_kind() == TokenKind::RightParen {
None
} else {
Some(Box::new(self.parse_expr()?))
};
self.expect(TokenKind::RightParen)?;
self.check_forbidden_statement_position(false)?;
self.iteration_depth += 1;
self.breakable_depth += 1;
let body = self.parse_stmt();
self.breakable_depth -= 1;
self.iteration_depth -= 1;
let body = body?;
let end = body.loc();
Ok(Stmt::For(ForStmt {
loc: Self::merge_spans(start, end),
init,
test,
update,
body: Box::new(body),
}))
}
fn expr_to_for_lhs(&self, expr: Expr) -> StatorResult<ForInOfLeft> {
match expr {
Expr::Ident(id) => Ok(ForInOfLeft::Pat(Pat::Ident(id))),
Expr::Array(_) | Expr::Object(_) => Ok(ForInOfLeft::Pat(self.expr_to_pat(expr)?)),
Expr::Member(_) => {
Ok(ForInOfLeft::Expr(Box::new(expr)))
}
other => {
let loc = other.loc();
Err(Self::error_at(
loc,
"invalid left-hand side in for-in/of loop",
))
}
}
}
fn parse_return(&mut self) -> StatorResult<Stmt> {
let start = self.current_span();
self.bump()?; let argument = if !self.current.had_line_terminator_before
&& self.peek_kind() != TokenKind::Semicolon
&& self.peek_kind() != TokenKind::RightBrace
&& self.peek_kind() != TokenKind::Eof
{
Some(Box::new(self.parse_expr()?))
} else {
None
};
let end = argument.as_ref().map(|a| a.loc()).unwrap_or(start);
self.consume_semicolon()?;
Ok(Stmt::Return(ReturnStmt {
loc: Self::merge_spans(start, end),
argument,
}))
}
fn parse_break(&mut self) -> StatorResult<Stmt> {
let start = self.current_span();
self.bump()?; let label = if !self.current.had_line_terminator_before
&& self.peek_kind() == TokenKind::Identifier
{
let tok = self.bump()?;
Some(self.ident_from_token(&tok)?)
} else {
None
};
if label.is_none() && self.breakable_depth == 0 {
return Err(Self::error_at(
start,
"break statement not inside a loop or switch",
));
}
if let Some(ref lbl) = label
&& !self.labels.iter().any(|(n, _)| n == &lbl.name)
{
return Err(Self::error_at(lbl.loc, "undefined label"));
}
let end = label.as_ref().map(|l| l.loc).unwrap_or(start);
self.consume_semicolon()?;
Ok(Stmt::Break(BreakStmt {
loc: Self::merge_spans(start, end),
label,
}))
}
fn parse_continue(&mut self) -> StatorResult<Stmt> {
let start = self.current_span();
self.bump()?; let label = if !self.current.had_line_terminator_before
&& self.peek_kind() == TokenKind::Identifier
{
let tok = self.bump()?;
Some(self.ident_from_token(&tok)?)
} else {
None
};
if self.iteration_depth == 0 {
return Err(Self::error_at(
start,
"continue statement not inside a loop",
));
}
if let Some(ref lbl) = label {
match self.labels.iter().find(|(n, _)| n == &lbl.name) {
None => return Err(Self::error_at(lbl.loc, "undefined label")),
Some((_, false)) => {
return Err(Self::error_at(
lbl.loc,
"continue label is not on an iteration statement",
));
}
Some((_, true)) => {}
}
}
let end = label.as_ref().map(|l| l.loc).unwrap_or(start);
self.consume_semicolon()?;
Ok(Stmt::Continue(ContinueStmt {
loc: Self::merge_spans(start, end),
label,
}))
}
fn parse_throw(&mut self) -> StatorResult<Stmt> {
let start = self.current_span();
self.bump()?; if self.current.had_line_terminator_before {
return Err(self.error("illegal newline after throw"));
}
let argument = self.parse_expr()?;
let end = argument.loc();
self.consume_semicolon()?;
Ok(Stmt::Throw(ThrowStmt {
loc: Self::merge_spans(start, end),
argument: Box::new(argument),
}))
}
fn parse_try(&mut self) -> StatorResult<Stmt> {
let start = self.current_span();
self.bump()?; let block = self.parse_block()?;
let handler = if self.peek_kind() == TokenKind::Catch {
let catch_span = self.current_span();
self.bump()?;
let param = if self.eat(TokenKind::LeftParen)? {
let pat = self.parse_binding_pat()?;
self.expect(TokenKind::RightParen)?;
Some(pat)
} else {
None
};
let body = self.parse_block()?;
Some(crate::parser::ast::CatchClause {
loc: Self::merge_spans(catch_span, body.loc),
param,
body,
})
} else {
None
};
let finalizer = if self.eat(TokenKind::Finally)? {
Some(self.parse_block()?)
} else {
None
};
if handler.is_none() && finalizer.is_none() {
return Err(self.error("try statement must have a catch or finally clause"));
}
let end = finalizer
.as_ref()
.map(|f| f.loc)
.or_else(|| handler.as_ref().map(|h| h.loc))
.unwrap_or(block.loc);
Ok(Stmt::Try(TryStmt {
loc: Self::merge_spans(start, end),
block,
handler,
finalizer,
}))
}
fn parse_with(&mut self) -> StatorResult<Stmt> {
if self.strict_mode {
return Err(self.error("'with' statements are not allowed in strict mode"));
}
let start = self.current_span();
self.bump()?; self.expect(TokenKind::LeftParen)?;
let object = self.parse_expr()?;
self.expect(TokenKind::RightParen)?;
self.check_forbidden_statement_position(false)?;
let body = self.parse_stmt()?;
Ok(Stmt::With(WithStmt {
loc: Self::merge_spans(start, self.current_span()),
object: Box::new(object),
body: Box::new(body),
}))
}
fn parse_switch(&mut self) -> StatorResult<Stmt> {
let start = self.current_span();
self.bump()?; self.expect(TokenKind::LeftParen)?;
let discriminant = self.parse_expr()?;
self.expect(TokenKind::RightParen)?;
self.expect(TokenKind::LeftBrace)?;
self.breakable_depth += 1;
let mut cases = Vec::new();
let mut seen_default = false;
while self.peek_kind() != TokenKind::RightBrace {
if self.peek_kind() == TokenKind::Eof {
return Err(self.error("unexpected end of input in switch statement"));
}
let case_start = self.current_span();
let test = if self.eat(TokenKind::Case)? {
Some(self.parse_expr()?)
} else if self.eat(TokenKind::Default)? {
if seen_default {
return Err(self.error("switch statement cannot have multiple default clauses"));
}
seen_default = true;
None
} else {
return Err(self.error("expected 'case' or 'default'"));
};
self.expect(TokenKind::Colon)?;
let mut consequent = Vec::new();
while self.peek_kind() != TokenKind::Case
&& self.peek_kind() != TokenKind::Default
&& self.peek_kind() != TokenKind::RightBrace
{
if self.peek_kind() == TokenKind::Eof {
return Err(self.error("unexpected end of input in switch clause"));
}
consequent.push(self.parse_stmt()?);
}
let case_end = if let Some(last) = consequent.last() {
last.loc()
} else {
case_start
};
cases.push(SwitchCase {
loc: Self::merge_spans(case_start, case_end),
test,
consequent,
});
}
let end = self.current_span();
self.bump()?; self.breakable_depth -= 1;
Ok(Stmt::Switch(SwitchStmt {
loc: Self::merge_spans(start, end),
discriminant: Box::new(discriminant),
cases,
}))
}
fn parse_fn_decl(&mut self, fn_span: Span, is_async: bool) -> StatorResult<Stmt> {
let is_generator = self.eat(TokenKind::Star)?;
let id = if self.peek_kind() == TokenKind::Identifier {
let tok = self.bump()?;
let ident = self.ident_from_token(&tok)?;
self.check_strict_binding_ident(&ident.name, ident.loc)?;
Some(ident)
} else {
None
};
self.expect(TokenKind::LeftParen)?;
let params = self.parse_formal_params()?;
if is_generator {
self.check_generator_params_for_yield(¶ms, fn_span)?;
}
let outer_strict = self.strict_mode;
let outer_async_depth = self.async_function_depth;
if is_async {
self.async_function_depth = 1;
} else {
self.async_function_depth = 0;
}
let (body, fn_strict, has_use_strict) = self.parse_function_body()?;
self.strict_mode = outer_strict;
self.async_function_depth = outer_async_depth;
if has_use_strict && Self::has_non_simple_params(¶ms) {
return Err(Self::error_at(
fn_span,
"illegal 'use strict' directive in function with non-simple parameter list",
));
}
if fn_strict {
if let Some(ref ident) = id {
self.check_strict_binding_ident(&ident.name, ident.loc)?;
}
self.check_strict_duplicate_params(¶ms)?;
}
let end = body.loc;
Ok(Stmt::FnDecl(Box::new(FnDecl {
loc: Self::merge_spans(fn_span, end),
id,
is_async,
is_generator,
params,
body,
is_strict: fn_strict,
})))
}
fn parse_function_body(&mut self) -> StatorResult<(BlockStmt, bool, bool)> {
let start = self.current_span();
self.expect(TokenKind::LeftBrace)?;
let outer_function_depth = self.function_depth;
let outer_iteration_depth = self.iteration_depth;
let outer_breakable_depth = self.breakable_depth;
let outer_labels = std::mem::take(&mut self.labels);
self.function_depth = 1;
self.iteration_depth = 0;
self.breakable_depth = 0;
let result = self.parse_function_body_inner(start);
self.function_depth = outer_function_depth;
self.iteration_depth = outer_iteration_depth;
self.breakable_depth = outer_breakable_depth;
self.labels = outer_labels;
let (body, end, has_use_strict) = result?;
Ok((
BlockStmt {
loc: Self::merge_spans(start, end),
body,
},
self.strict_mode,
has_use_strict,
))
}
fn parse_function_body_inner(&mut self, _start: Span) -> StatorResult<(Vec<Stmt>, Span, bool)> {
let mut body = Vec::new();
let mut in_directive_prologue = true;
let mut has_use_strict = false;
while self.peek_kind() != TokenKind::RightBrace {
if self.peek_kind() == TokenKind::Eof {
return Err(self.error("unexpected end of input inside block"));
}
let stmt = self.parse_stmt()?;
if in_directive_prologue {
if let Some(value) = Self::directive_prologue_value(&stmt) {
if value == "use strict" {
has_use_strict = true;
self.strict_mode = true;
}
} else {
in_directive_prologue = false;
}
}
body.push(stmt);
}
let end = self.current_span();
self.bump()?; Ok((body, end, has_use_strict))
}
fn parse_class_decl(&mut self) -> StatorResult<Stmt> {
let start = self.current_span();
self.bump()?;
let id = if self.peek_kind() == TokenKind::Identifier {
let tok = self.bump()?;
let ident = self.ident_from_token(&tok)?;
self.check_strict_binding_ident(&ident.name, ident.loc)?;
Some(ident)
} else {
None
};
let super_class = if self.eat(TokenKind::Extends)? {
Some(Box::new(self.parse_assignment_expr()?))
} else {
None
};
let body = self.parse_class_body()?;
let end = body.loc;
Ok(Stmt::ClassDecl(Box::new(ClassDecl {
loc: Self::merge_spans(start, end),
id,
super_class,
body,
})))
}
fn parse_class_expr(&mut self) -> StatorResult<Expr> {
let start = self.current_span();
self.bump()?;
let id = if self.peek_kind() == TokenKind::Identifier {
let tok = self.bump()?;
let ident = self.ident_from_token(&tok)?;
self.check_strict_binding_ident(&ident.name, ident.loc)?;
Some(ident)
} else {
None
};
let super_class = if self.eat(TokenKind::Extends)? {
Some(Box::new(self.parse_assignment_expr()?))
} else {
None
};
let body = self.parse_class_body()?;
let end = body.loc;
Ok(Expr::Class(Box::new(ClassExpr {
loc: Self::merge_spans(start, end),
id,
super_class,
body,
})))
}
fn parse_class_body(&mut self) -> StatorResult<ClassBody> {
let start = self.current_span();
self.expect(TokenKind::LeftBrace)?;
self.class_depth += 1;
let outer_strict = self.strict_mode;
self.strict_mode = true;
let mut members = Vec::new();
while self.peek_kind() != TokenKind::RightBrace {
if self.peek_kind() == TokenKind::Eof {
return Err(self.error("unexpected end of input inside class body"));
}
if self.peek_kind() == TokenKind::Semicolon {
self.bump()?;
continue;
}
let member_start = self.current_span();
let is_static = self.eat(TokenKind::Static)?;
if is_static && self.peek_kind() == TokenKind::LeftBrace {
let block = self.parse_block()?;
let end = block.loc;
members.push(ClassMember::StaticBlock(StaticBlock {
loc: Self::merge_spans(member_start, end),
body: block.body,
}));
continue;
}
let is_async = if self.peek_kind() == TokenKind::Async {
let saved_scanner = self.scanner.clone();
let saved_current = self.current.clone();
self.bump()?; if self.peek_kind() == TokenKind::LeftParen
|| self.peek_kind() == TokenKind::Semicolon
|| self.peek_kind() == TokenKind::Equal
|| self.peek_kind() == TokenKind::RightBrace
|| self.current.had_line_terminator_before
{
self.scanner = saved_scanner;
self.current = saved_current;
false
} else {
true
}
} else {
false
};
let is_generator = self.eat(TokenKind::Star)?;
let (kind, key, is_computed) = if !is_generator
&& (self.peek_kind() == TokenKind::Get || self.peek_kind() == TokenKind::Set)
{
let accessor_kind = if self.peek_kind() == TokenKind::Get {
MethodKind::Get
} else {
MethodKind::Set
};
let accessor_tok = self.bump()?;
if self.peek_kind() == TokenKind::LeftParen
|| self.peek_kind() == TokenKind::Equal
|| self.peek_kind() == TokenKind::Semicolon
|| self.peek_kind() == TokenKind::RightBrace
{
let name = format!("{:?}", accessor_tok.kind).to_lowercase();
(
MethodKind::Method,
PropKey::Ident(Ident {
loc: accessor_tok.span,
name,
}),
false,
)
} else {
let (key, is_computed) = self.parse_class_element_name()?;
(accessor_kind, key, is_computed)
}
} else {
let (key, is_computed) = self.parse_class_element_name()?;
let kind = if !is_static && !is_computed && !is_generator {
if let PropKey::Ident(ref id) = key {
if id.name == "constructor" {
MethodKind::Constructor
} else {
MethodKind::Method
}
} else {
MethodKind::Method
}
} else {
MethodKind::Method
};
(kind, key, is_computed)
};
if self.peek_kind() == TokenKind::LeftParen {
let fn_start = self.current_span();
self.expect(TokenKind::LeftParen)?;
let params = self.parse_formal_params()?;
if is_generator {
self.check_generator_params_for_yield(¶ms, member_start)?;
}
self.check_strict_duplicate_params(¶ms)?;
let outer_fn = self.function_depth;
let outer_it = self.iteration_depth;
let outer_br = self.breakable_depth;
let outer_labels = std::mem::take(&mut self.labels);
let outer_async_depth = self.async_function_depth;
self.function_depth = 1;
self.iteration_depth = 0;
self.breakable_depth = 0;
self.async_function_depth = if is_async { 1 } else { 0 };
let body = self.parse_block();
self.function_depth = outer_fn;
self.iteration_depth = outer_it;
self.breakable_depth = outer_br;
self.labels = outer_labels;
self.async_function_depth = outer_async_depth;
let body = body?;
let fn_end = body.loc;
let value = FnExpr {
loc: Self::merge_spans(fn_start, fn_end),
id: None,
is_async,
is_generator,
params,
body,
is_strict: true,
};
members.push(ClassMember::Method(MethodDef {
loc: Self::merge_spans(member_start, fn_end),
is_static,
kind,
key,
is_computed,
value,
}));
} else {
let value = if self.eat(TokenKind::Equal)? {
Some(Box::new(self.parse_assignment_expr()?))
} else {
None
};
let end = value.as_ref().map(|v| v.loc()).unwrap_or(match &key {
PropKey::Ident(id) => id.loc,
PropKey::Private(id) => id.loc,
PropKey::Str(s) => s.loc,
PropKey::Num(n) => n.loc,
PropKey::Computed(e) => e.loc(),
});
members.push(ClassMember::Property(PropertyDef {
loc: Self::merge_spans(member_start, end),
is_static,
key,
is_computed,
value,
}));
self.eat(TokenKind::Semicolon)?;
}
}
self.class_depth -= 1;
self.strict_mode = outer_strict;
let end = self.current_span();
self.bump()?; Ok(ClassBody {
loc: Self::merge_spans(start, end),
body: members,
})
}
fn parse_class_element_name(&mut self) -> StatorResult<(PropKey, bool)> {
match self.peek_kind() {
TokenKind::PrivateIdentifier => {
let tok = self.bump()?;
let name = match tok.value {
TokenValue::Str(s) => s,
_ => return Err(Self::error_at(tok.span, "invalid private name token")),
};
Ok((
PropKey::Private(PrivateIdent {
loc: tok.span,
name,
}),
false,
))
}
TokenKind::LeftBracket => {
self.bump()?; let key_expr = self.parse_assignment_expr()?;
self.expect(TokenKind::RightBracket)?;
Ok((PropKey::Computed(Box::new(key_expr)), true))
}
TokenKind::StringLiteral => {
let tok = self.bump()?;
let value = match tok.value {
TokenValue::Str(s) => s,
_ => return Err(Self::error_at(tok.span, "invalid string token")),
};
Ok((
PropKey::Str(StringLit {
loc: tok.span,
value,
}),
false,
))
}
TokenKind::NumericLiteral => {
let tok = self.bump()?;
let value = match tok.value {
TokenValue::Number(n) => n,
_ => return Err(Self::error_at(tok.span, "invalid numeric token")),
};
Ok((
PropKey::Num(NumLit {
loc: tok.span,
value,
raw: String::new(),
}),
false,
))
}
_ => {
let tok = self.bump()?;
let name = match &tok.value {
TokenValue::Str(s) => s.clone(),
TokenValue::None => format!("{:?}", tok.kind).to_lowercase(),
_ => return Err(Self::error_at(tok.span, "expected method name")),
};
Ok((
PropKey::Ident(Ident {
loc: tok.span,
name,
}),
false,
))
}
}
}
fn parse_import_decl(&mut self) -> StatorResult<ImportDecl> {
let start = self.current_span();
self.bump()?;
if self.peek_kind() == TokenKind::StringLiteral {
let source = self.parse_string_lit_token()?;
let end = source.loc;
self.consume_semicolon()?;
return Ok(ImportDecl {
loc: Self::merge_spans(start, end),
specifiers: vec![],
source,
attributes: vec![],
});
}
let mut specifiers = Vec::new();
if self.peek_kind() == TokenKind::Star {
self.bump()?; self.expect(TokenKind::As)?;
let local_tok = self.expect(TokenKind::Identifier)?;
let local = self.ident_from_token(&local_tok)?;
specifiers.push(ImportSpecifier::Namespace(ImportNamespaceSpecifier {
loc: Self::merge_spans(start, local.loc),
local,
}));
} else if self.peek_kind() == TokenKind::LeftBrace {
self.parse_import_named_specifiers(&mut specifiers)?;
} else if self.peek_kind() == TokenKind::Identifier {
let local_tok = self.bump()?;
let local = self.ident_from_token(&local_tok)?;
specifiers.push(ImportSpecifier::Default(ImportDefaultSpecifier {
loc: local.loc,
local,
}));
if self.eat(TokenKind::Comma)? {
if self.peek_kind() == TokenKind::Star {
self.bump()?; self.expect(TokenKind::As)?;
let ns_tok = self.expect(TokenKind::Identifier)?;
let ns_local = self.ident_from_token(&ns_tok)?;
specifiers.push(ImportSpecifier::Namespace(ImportNamespaceSpecifier {
loc: Self::merge_spans(start, ns_local.loc),
local: ns_local,
}));
} else if self.peek_kind() == TokenKind::LeftBrace {
self.parse_import_named_specifiers(&mut specifiers)?;
} else {
return Err(self.error("expected '{' or '*' after ',' in import"));
}
}
} else {
return Err(self.error("unexpected token after 'import'"));
}
self.expect(TokenKind::From)?;
let source = self.parse_string_lit_token()?;
let end = source.loc;
self.consume_semicolon()?;
Ok(ImportDecl {
loc: Self::merge_spans(start, end),
specifiers,
source,
attributes: vec![],
})
}
fn parse_import_named_specifiers(
&mut self,
specifiers: &mut Vec<ImportSpecifier>,
) -> StatorResult<()> {
self.expect(TokenKind::LeftBrace)?;
while self.peek_kind() != TokenKind::RightBrace {
let imported_tok = self.bump()?;
let imported = self.module_export_name_from_token(&imported_tok)?;
let local = if self.eat(TokenKind::As)? {
let local_tok = self.expect(TokenKind::Identifier)?;
self.ident_from_token(&local_tok)?
} else {
match &imported {
ModuleExportName::Ident(id) => id.clone(),
ModuleExportName::Str(s) => {
return Err(Self::error_at(
s.loc,
"string import name requires 'as' alias",
));
}
}
};
specifiers.push(ImportSpecifier::Named(ImportNamedSpecifier {
loc: Self::merge_spans(imported_tok.span, local.loc),
imported,
local,
}));
if !self.eat(TokenKind::Comma)? {
break;
}
}
self.expect(TokenKind::RightBrace)?;
Ok(())
}
fn parse_export_decl(&mut self) -> StatorResult<ModuleDecl> {
let start = self.current_span();
self.bump()?;
match self.peek_kind() {
TokenKind::Default => {
self.bump()?; match self.peek_kind() {
TokenKind::Async if !self.current.had_line_terminator_before => {
let saved_scanner = self.scanner.clone();
let saved_current = self.current.clone();
let async_tok = self.bump()?;
if self.peek_kind() == TokenKind::Function
&& !self.current.had_line_terminator_before
{
let fn_tok = self.bump()?;
let fn_stmt = self.parse_fn_decl(
Self::merge_spans(async_tok.span, fn_tok.span),
true,
)?;
let end = fn_stmt.loc();
if let Stmt::FnDecl(fd) = fn_stmt {
return Ok(ModuleDecl::ExportDefault(ExportDefaultDecl {
loc: Self::merge_spans(start, end),
declaration: ExportDefaultExpr::Fn(fd),
}));
}
unreachable!()
}
self.scanner = saved_scanner;
self.current = saved_current;
let expr = self.parse_assignment_expr()?;
let end = expr.loc();
self.consume_semicolon()?;
Ok(ModuleDecl::ExportDefault(ExportDefaultDecl {
loc: Self::merge_spans(start, end),
declaration: ExportDefaultExpr::Expr(Box::new(expr)),
}))
}
TokenKind::Function => {
let fn_tok = self.bump()?;
let fn_stmt = self.parse_fn_decl(fn_tok.span, false)?;
let end = fn_stmt.loc();
if let Stmt::FnDecl(fd) = fn_stmt {
Ok(ModuleDecl::ExportDefault(ExportDefaultDecl {
loc: Self::merge_spans(start, end),
declaration: ExportDefaultExpr::Fn(fd),
}))
} else {
unreachable!()
}
}
TokenKind::Class => {
let class_stmt = self.parse_class_decl()?;
let end = class_stmt.loc();
if let Stmt::ClassDecl(cd) = class_stmt {
Ok(ModuleDecl::ExportDefault(ExportDefaultDecl {
loc: Self::merge_spans(start, end),
declaration: ExportDefaultExpr::Class(cd),
}))
} else {
unreachable!()
}
}
_ => {
let expr = self.parse_assignment_expr()?;
let end = expr.loc();
self.consume_semicolon()?;
Ok(ModuleDecl::ExportDefault(ExportDefaultDecl {
loc: Self::merge_spans(start, end),
declaration: ExportDefaultExpr::Expr(Box::new(expr)),
}))
}
}
}
TokenKind::Async => {
let async_tok = self.bump()?; if self.peek_kind() == TokenKind::Function
&& !self.current.had_line_terminator_before
{
let fn_tok = self.bump()?;
let decl =
self.parse_fn_decl(Self::merge_spans(async_tok.span, fn_tok.span), true)?;
let end = decl.loc();
Ok(ModuleDecl::ExportNamed(ExportNamedDecl {
loc: Self::merge_spans(start, end),
specifiers: vec![],
source: None,
declaration: Some(Box::new(decl)),
attributes: vec![],
}))
} else {
Err(self.error("expected 'function' after 'async' in export"))
}
}
TokenKind::Function => {
let fn_tok = self.bump()?;
let decl = self.parse_fn_decl(fn_tok.span, false)?;
let end = decl.loc();
Ok(ModuleDecl::ExportNamed(ExportNamedDecl {
loc: Self::merge_spans(start, end),
specifiers: vec![],
source: None,
declaration: Some(Box::new(decl)),
attributes: vec![],
}))
}
TokenKind::Class => {
let decl = self.parse_class_decl()?;
let end = decl.loc();
Ok(ModuleDecl::ExportNamed(ExportNamedDecl {
loc: Self::merge_spans(start, end),
specifiers: vec![],
source: None,
declaration: Some(Box::new(decl)),
attributes: vec![],
}))
}
TokenKind::Var | TokenKind::Let | TokenKind::Const => {
let tok = self.bump()?;
let kind = match tok.kind {
TokenKind::Var => VarKind::Var,
TokenKind::Let => VarKind::Let,
TokenKind::Const => VarKind::Const,
_ => unreachable!(),
};
let decl = self.parse_var_decl(kind, tok.span)?;
let end = decl.loc();
Ok(ModuleDecl::ExportNamed(ExportNamedDecl {
loc: Self::merge_spans(start, end),
specifiers: vec![],
source: None,
declaration: Some(Box::new(decl)),
attributes: vec![],
}))
}
TokenKind::LeftBrace => {
self.bump()?; let mut specifiers = Vec::new();
while self.peek_kind() != TokenKind::RightBrace {
let spec_start = self.current_span();
let local_tok = self.bump()?;
let local = self.module_export_name_from_token(&local_tok)?;
let (exported, spec_end) = if self.eat(TokenKind::As)? {
let exp_tok = self.bump()?;
let exp = self.module_export_name_from_token(&exp_tok)?;
(exp, exp_tok.span)
} else {
(local.clone(), local_tok.span)
};
specifiers.push(ExportSpecifier {
loc: Self::merge_spans(spec_start, spec_end),
local,
exported,
});
if !self.eat(TokenKind::Comma)? {
break;
}
}
let rbrace = self.expect(TokenKind::RightBrace)?;
let source = if self.peek_kind() == TokenKind::From {
self.bump()?;
Some(self.parse_string_lit_token()?)
} else {
None
};
let end = source.as_ref().map(|s| s.loc).unwrap_or(rbrace.span);
self.consume_semicolon()?;
Ok(ModuleDecl::ExportNamed(ExportNamedDecl {
loc: Self::merge_spans(start, end),
specifiers,
source,
declaration: None,
attributes: vec![],
}))
}
TokenKind::Star => {
self.bump()?; let exported = if self.eat(TokenKind::As)? {
let tok = self.bump()?;
Some(self.module_export_name_from_token(&tok)?)
} else {
None
};
self.expect(TokenKind::From)?;
let source = self.parse_string_lit_token()?;
let end = source.loc;
self.consume_semicolon()?;
Ok(ModuleDecl::ExportAll(ExportAllDecl {
loc: Self::merge_spans(start, end),
exported,
source,
attributes: vec![],
}))
}
_ => Err(self.error("unexpected token after 'export'")),
}
}
fn parse_string_lit_token(&mut self) -> StatorResult<StringLit> {
let tok = self.expect(TokenKind::StringLiteral)?;
let value = match &tok.value {
TokenValue::Str(s) => s.clone(),
_ => return Err(Self::error_at(tok.span, "expected string literal")),
};
Ok(StringLit {
loc: tok.span,
value,
})
}
fn module_export_name_from_token(&self, tok: &Token) -> StatorResult<ModuleExportName> {
match tok.kind {
TokenKind::Identifier => {
let ident = self.ident_from_token(tok)?;
Ok(ModuleExportName::Ident(ident))
}
TokenKind::StringLiteral => {
let value = match &tok.value {
TokenValue::Str(s) => s.clone(),
_ => return Err(Self::error_at(tok.span, "expected string literal")),
};
Ok(ModuleExportName::Str(StringLit {
loc: tok.span,
value,
}))
}
_ => {
let name = self.name_from_token(tok)?;
Ok(ModuleExportName::Ident(Ident {
loc: tok.span,
name,
}))
}
}
}
fn parse_formal_params(&mut self) -> StatorResult<Vec<Param>> {
let mut params = Vec::new();
while self.peek_kind() != TokenKind::RightParen {
if self.peek_kind() == TokenKind::Eof {
return Err(self.error("unexpected end of input in parameter list"));
}
let start = self.current_span();
if self.peek_kind() == TokenKind::DotDotDot {
let rest_start = self.current_span();
self.bump()?; let arg = self.parse_binding_pat()?;
let rest_end = arg.loc();
let rest_pat = Pat::Rest(Box::new(RestElement {
loc: Self::merge_spans(rest_start, rest_end),
argument: Box::new(arg),
}));
params.push(Param {
loc: Self::merge_spans(start, rest_end),
pat: rest_pat,
default: None,
});
if self.peek_kind() == TokenKind::Comma {
return Err(Self::error_at(
self.current_span(),
"rest parameter may not have a trailing comma",
));
}
break;
}
let pat = self.parse_binding_pat()?;
let pat_end = pat.loc();
let default = if self.eat(TokenKind::Equal)? {
Some(self.parse_assignment_expr()?)
} else {
None
};
let end = default.as_ref().map(|e| e.loc()).unwrap_or(pat_end);
params.push(Param {
loc: Self::merge_spans(start, end),
pat,
default,
});
if !self.eat(TokenKind::Comma)? {
break;
}
}
self.expect(TokenKind::RightParen)?;
Ok(params)
}
fn check_generator_params_for_yield(
&self,
params: &[Param],
error_span: Span,
) -> StatorResult<()> {
if Self::params_contain_yield(params) {
return Err(Self::error_at(
error_span,
"yield expression not allowed in generator parameter list",
));
}
Ok(())
}
fn params_contain_yield(params: &[Param]) -> bool {
params.iter().any(Self::param_contains_yield)
}
fn param_contains_yield(param: &Param) -> bool {
Self::pat_contains_yield(¶m.pat)
|| param
.default
.as_ref()
.is_some_and(Self::expr_contains_yield)
}
fn pat_contains_yield(pat: &Pat) -> bool {
match pat {
Pat::Ident(_) => false,
Pat::Array(array) => array
.elements
.iter()
.flatten()
.any(Self::pat_contains_yield),
Pat::Object(object) => object.properties.iter().any(|prop| match prop {
ObjectPatProp::KeyValue(prop) => {
Self::prop_key_contains_yield(&prop.key)
|| Self::pat_contains_yield(&prop.value)
}
ObjectPatProp::Assign(prop) => {
prop.value.as_deref().is_some_and(Self::expr_contains_yield)
}
ObjectPatProp::Rest(rest) => Self::pat_contains_yield(&rest.argument),
}),
Pat::Rest(rest) => Self::pat_contains_yield(&rest.argument),
Pat::Assign(assign) => {
Self::pat_contains_yield(&assign.left) || Self::expr_contains_yield(&assign.right)
}
Pat::Expr(expr) => Self::expr_contains_yield(expr),
}
}
fn assign_target_contains_yield(target: &AssignTarget) -> bool {
match target {
AssignTarget::Expr(expr) => Self::expr_contains_yield(expr),
AssignTarget::Pat(pat) => Self::pat_contains_yield(pat),
}
}
fn prop_key_contains_yield(key: &PropKey) -> bool {
match key {
PropKey::Computed(expr) => Self::expr_contains_yield(expr),
PropKey::Ident(_) | PropKey::Private(_) | PropKey::Str(_) | PropKey::Num(_) => false,
}
}
fn member_prop_contains_yield(prop: &MemberProp) -> bool {
match prop {
MemberProp::Computed(expr) => Self::expr_contains_yield(expr),
MemberProp::Ident(_) | MemberProp::Private(_) => false,
}
}
fn expr_contains_yield(expr: &Expr) -> bool {
match expr {
Expr::Yield(_) => true,
Expr::Array(array) => array
.elements
.iter()
.flatten()
.any(Self::expr_contains_yield),
Expr::Object(object) => object.properties.iter().any(|prop| match prop {
ObjectProp::Prop(prop) => {
Self::prop_key_contains_yield(&prop.key)
|| match &prop.value {
PropValue::Value(value) => Self::expr_contains_yield(value),
PropValue::Shorthand => false,
PropValue::Get(_) | PropValue::Set(_) | PropValue::Method(_) => false,
}
}
ObjectProp::Spread(spread) => Self::expr_contains_yield(&spread.argument),
}),
Expr::Unary(unary) => Self::expr_contains_yield(&unary.argument),
Expr::Update(update) => Self::expr_contains_yield(&update.argument),
Expr::Binary(binary) => {
Self::expr_contains_yield(&binary.left) || Self::expr_contains_yield(&binary.right)
}
Expr::Logical(logical) => {
Self::expr_contains_yield(&logical.left)
|| Self::expr_contains_yield(&logical.right)
}
Expr::Conditional(conditional) => {
Self::expr_contains_yield(&conditional.test)
|| Self::expr_contains_yield(&conditional.consequent)
|| Self::expr_contains_yield(&conditional.alternate)
}
Expr::Assign(assign) => {
Self::assign_target_contains_yield(&assign.left)
|| Self::expr_contains_yield(&assign.right)
}
Expr::Sequence(sequence) => sequence.expressions.iter().any(Self::expr_contains_yield),
Expr::Member(member) => {
Self::expr_contains_yield(&member.object)
|| Self::member_prop_contains_yield(&member.property)
}
Expr::OptionalMember(member) => {
Self::expr_contains_yield(&member.object)
|| Self::member_prop_contains_yield(&member.property)
}
Expr::Call(call) => {
Self::expr_contains_yield(&call.callee)
|| call.arguments.iter().any(Self::expr_contains_yield)
}
Expr::OptionalCall(call) => {
Self::expr_contains_yield(&call.callee)
|| call.arguments.iter().any(Self::expr_contains_yield)
}
Expr::OptionalChain(inner) => Self::expr_contains_yield(inner),
Expr::New(new_expr) => {
Self::expr_contains_yield(&new_expr.callee)
|| new_expr.arguments.iter().any(Self::expr_contains_yield)
}
Expr::TaggedTemplate(tagged) => {
Self::expr_contains_yield(&tagged.tag)
|| tagged
.quasi
.expressions
.iter()
.any(Self::expr_contains_yield)
}
Expr::Spread(spread) => Self::expr_contains_yield(&spread.argument),
Expr::Await(await_expr) => Self::expr_contains_yield(&await_expr.argument),
Expr::Import(import_expr) => Self::expr_contains_yield(&import_expr.source),
Expr::Template(template) => template.expressions.iter().any(Self::expr_contains_yield),
Expr::Null(_)
| Expr::Bool(_)
| Expr::Num(_)
| Expr::Str(_)
| Expr::BigInt(_)
| Expr::Regexp(_)
| Expr::Ident(_)
| Expr::This(_)
| Expr::Fn(_)
| Expr::Arrow(_)
| Expr::Class(_)
| Expr::MetaProp(_)
| Expr::PrivateName(_) => false,
}
}
fn check_strict_duplicate_params(&self, params: &[Param]) -> StatorResult<()> {
let mut seen = std::collections::HashSet::new();
for param in params {
if let Pat::Ident(id) = ¶m.pat
&& !seen.insert(&id.name)
{
return Err(self.error(&format!(
"duplicate parameter name '{}' not allowed in strict mode",
id.name
)));
}
}
Ok(())
}
fn check_unique_params(&self, params: &[Param]) -> StatorResult<()> {
let mut seen = std::collections::HashSet::new();
for param in params {
if let Pat::Ident(id) = ¶m.pat
&& !seen.insert(&id.name)
{
return Err(self.error(&format!(
"duplicate parameter name '{}' not allowed",
id.name
)));
}
}
Ok(())
}
fn has_non_simple_params(params: &[Param]) -> bool {
for param in params {
if param.default.is_some() {
return true;
}
match ¶m.pat {
Pat::Ident(_) => {}
_ => return true,
}
}
false
}
fn check_octal_escape(raw: &str, span: Span) -> StatorResult<()> {
let bytes = raw.as_bytes();
let len = bytes.len();
let mut i = 0;
while i < len {
if bytes[i] == b'\\' {
i += 1;
if i >= len {
break;
}
let ch = bytes[i];
if (b'1'..=b'9').contains(&ch) {
return Err(Self::error_at(
span,
"octal escape sequences are not allowed in strict mode",
));
}
if ch == b'0' && i + 1 < len && bytes[i + 1].is_ascii_digit() {
return Err(Self::error_at(
span,
"octal escape sequences are not allowed in strict mode",
));
}
if ch == b'u' {
i += 1;
if i < len && bytes[i] == b'{' {
while i < len && bytes[i] != b'}' {
i += 1;
}
} else {
i += 4;
}
} else if ch == b'x' {
i += 2;
}
}
i += 1;
}
Ok(())
}
fn parse_binding_pat(&mut self) -> StatorResult<Pat> {
match self.peek_kind() {
TokenKind::Identifier => {
let tok = self.bump()?;
let ident = self.ident_from_token(&tok)?;
self.check_strict_binding_ident(&ident.name, ident.loc)?;
Ok(Pat::Ident(ident))
}
TokenKind::LeftBracket => self.parse_array_pat(),
TokenKind::LeftBrace => self.parse_object_pat(),
kind if self.is_contextual_keyword_identifier(kind) => {
let tok = self.bump()?;
let name = self.name_from_token(&tok)?;
if self.strict_mode && (name == "let" || name == "static" || name == "yield") {
return Err(self.error(&format!(
"'{name}' cannot be used as a binding name in strict mode",
)));
}
Ok(Pat::Ident(Ident {
loc: tok.span,
name,
}))
}
_ => Err(self.error("expected binding pattern")),
}
}
fn parse_array_pat(&mut self) -> StatorResult<Pat> {
let start = self.current_span();
self.bump()?; let mut elements: Vec<Option<Pat>> = Vec::new();
while self.peek_kind() != TokenKind::RightBracket {
if self.peek_kind() == TokenKind::Eof {
return Err(self.error("unexpected end of input in array pattern"));
}
if self.peek_kind() == TokenKind::Comma {
elements.push(None);
self.bump()?; continue;
}
if self.peek_kind() == TokenKind::DotDotDot {
let rest_start = self.current_span();
self.bump()?; let arg = self.parse_binding_pat()?;
let rest_end = arg.loc();
elements.push(Some(Pat::Rest(Box::new(RestElement {
loc: Self::merge_spans(rest_start, rest_end),
argument: Box::new(arg),
}))));
let _ = self.eat(TokenKind::Comma)?;
break;
}
let elem = self.parse_binding_pat()?;
let elem = if self.peek_kind() == TokenKind::Equal {
let eq_tok = self.bump()?; let _ = eq_tok;
let right = self.parse_assignment_expr()?;
let loc = Self::merge_spans(elem.loc(), right.loc());
Pat::Assign(Box::new(AssignPat {
loc,
left: Box::new(elem),
right: Box::new(right),
}))
} else {
elem
};
elements.push(Some(elem));
if !self.eat(TokenKind::Comma)? {
break;
}
}
let end = self.expect(TokenKind::RightBracket)?;
Ok(Pat::Array(Box::new(ArrayPat {
loc: Self::merge_spans(start, end.span),
elements,
})))
}
fn parse_object_pat(&mut self) -> StatorResult<Pat> {
let start = self.current_span();
self.bump()?; let mut properties: Vec<ObjectPatProp> = Vec::new();
while self.peek_kind() != TokenKind::RightBrace {
if self.peek_kind() == TokenKind::Eof {
return Err(self.error("unexpected end of input in object pattern"));
}
if self.peek_kind() == TokenKind::DotDotDot {
let rest_start = self.current_span();
self.bump()?; let arg = self.parse_binding_pat()?;
let rest_end = arg.loc();
properties.push(ObjectPatProp::Rest(RestElement {
loc: Self::merge_spans(rest_start, rest_end),
argument: Box::new(arg),
}));
let _ = self.eat(TokenKind::Comma)?;
break;
}
let prop_start = self.current_span();
let (key, is_computed) = self.parse_pat_property_key()?;
if self.eat(TokenKind::Colon)? {
let value = self.parse_binding_pat()?;
let value = if self.peek_kind() == TokenKind::Equal {
self.bump()?; let right = self.parse_assignment_expr()?;
let loc = Self::merge_spans(value.loc(), right.loc());
Pat::Assign(Box::new(AssignPat {
loc,
left: Box::new(value),
right: Box::new(right),
}))
} else {
value
};
let prop_end = value.loc();
properties.push(ObjectPatProp::KeyValue(KeyValuePatProp {
loc: Self::merge_spans(prop_start, prop_end),
key,
is_computed,
value,
}));
} else {
let ident = match key {
PropKey::Ident(id) => id,
_ => {
return Err(Self::error_at(
prop_start,
"expected ':' after property name in destructuring pattern",
));
}
};
let default = if self.eat(TokenKind::Equal)? {
Some(Box::new(self.parse_assignment_expr()?))
} else {
None
};
let prop_end = default.as_ref().map(|e| e.loc()).unwrap_or(ident.loc);
properties.push(ObjectPatProp::Assign(AssignPatProp {
loc: Self::merge_spans(prop_start, prop_end),
key: ident,
value: default,
}));
}
if !self.eat(TokenKind::Comma)? {
break;
}
}
let end = self.expect(TokenKind::RightBrace)?;
Ok(Pat::Object(Box::new(ObjectPat {
loc: Self::merge_spans(start, end.span),
properties,
})))
}
fn parse_pat_property_key(&mut self) -> StatorResult<(PropKey, bool)> {
match self.peek_kind() {
TokenKind::LeftBracket => {
self.bump()?; let key_expr = self.parse_assignment_expr()?;
self.expect(TokenKind::RightBracket)?;
Ok((PropKey::Computed(Box::new(key_expr)), true))
}
TokenKind::StringLiteral => {
let tok = self.bump()?;
let value = match tok.value {
TokenValue::Str(s) => s,
_ => return Err(Self::error_at(tok.span, "invalid string token")),
};
Ok((
PropKey::Str(StringLit {
loc: tok.span,
value,
}),
false,
))
}
TokenKind::NumericLiteral => {
let tok = self.bump()?;
let value = match tok.value {
TokenValue::Number(n) => n,
_ => return Err(Self::error_at(tok.span, "invalid numeric token")),
};
Ok((
PropKey::Num(NumLit {
loc: tok.span,
value,
raw: String::new(),
}),
false,
))
}
_ => {
let tok = self.bump()?;
let name = match &tok.value {
TokenValue::Str(s) => s.clone(),
TokenValue::None => format!("{:?}", tok.kind).to_lowercase(),
_ => return Err(Self::error_at(tok.span, "expected property name")),
};
Ok((
PropKey::Ident(Ident {
loc: tok.span,
name,
}),
false,
))
}
}
}
fn parse_expr(&mut self) -> StatorResult<Expr> {
let start = self.current_span();
let first = self.parse_assignment_expr()?;
if self.peek_kind() == TokenKind::Comma {
let mut exprs = vec![first];
while self.eat(TokenKind::Comma)? {
exprs.push(self.parse_assignment_expr()?);
}
let end = exprs.last().map(|e| e.loc()).unwrap_or(start);
return Ok(Expr::Sequence(Box::new(SequenceExpr {
loc: Self::merge_spans(start, end),
expressions: exprs,
})));
}
Ok(first)
}
fn template_element_from_raw(
&self,
loc: Span,
raw: String,
tail: bool,
allow_invalid_escapes: bool,
) -> StatorResult<TemplateElement> {
let cooked = cook_template_raw(&raw);
if !allow_invalid_escapes && cooked.is_none() {
return Err(Self::error_at(
loc,
"invalid escape sequence in untagged template literal",
));
}
Ok(TemplateElement {
loc,
cooked,
raw,
tail,
})
}
fn parse_template_literal(&mut self, allow_invalid_escapes: bool) -> StatorResult<Expr> {
match self.peek_kind() {
TokenKind::NoSubstitutionTemplate => {
let tok = self.bump()?;
let raw = match tok.value {
TokenValue::Str(s) => s,
_ => String::new(),
};
Ok(Expr::Template(Box::new(TemplateLit {
loc: tok.span,
quasis: vec![self.template_element_from_raw(
tok.span,
raw,
true,
allow_invalid_escapes,
)?],
expressions: vec![],
})))
}
TokenKind::TemplateHead => {
let start = self.current_span();
let head_tok = self.bump()?;
let head_raw = match head_tok.value {
TokenValue::Str(s) => s,
_ => String::new(),
};
let mut quasis = vec![self.template_element_from_raw(
head_tok.span,
head_raw,
false,
allow_invalid_escapes,
)?];
let mut expressions = Vec::new();
loop {
expressions.push(self.parse_expr()?);
match self.peek_kind() {
TokenKind::TemplateMiddle => {
let mid_tok = self.bump()?;
let mid_raw = match mid_tok.value {
TokenValue::Str(s) => s,
_ => String::new(),
};
quasis.push(self.template_element_from_raw(
mid_tok.span,
mid_raw,
false,
allow_invalid_escapes,
)?);
}
TokenKind::TemplateTail => {
let tail_tok = self.bump()?;
let tail_raw = match tail_tok.value {
TokenValue::Str(s) => s,
_ => String::new(),
};
quasis.push(self.template_element_from_raw(
tail_tok.span,
tail_raw,
true,
allow_invalid_escapes,
)?);
break;
}
_ => {
return Err(self.error("expected template continuation"));
}
}
}
let end = quasis.last().map(|q| q.loc).unwrap_or(start);
Ok(Expr::Template(Box::new(TemplateLit {
loc: Self::merge_spans(start, end),
quasis,
expressions,
})))
}
_ => Err(self.error("expected template literal")),
}
}
fn parse_assignment_expr(&mut self) -> StatorResult<Expr> {
stacker::maybe_grow(32 * 1024, 512 * 1024, || {
self.enter()?;
let result = self.parse_assignment_expr_inner();
self.leave();
result
})
}
fn parse_assignment_expr_inner(&mut self) -> StatorResult<Expr> {
let start = self.current_span();
if self.peek_kind() == TokenKind::Async {
let saved_scanner = self.scanner.clone();
let saved_current = self.current.clone();
let _async_tok = self.bump()?;
if !self.current.had_line_terminator_before {
if self.peek_kind() == TokenKind::LeftParen {
let paren_saved_scanner = self.scanner.clone();
let paren_saved_current = self.current.clone();
self.bump()?; if self.peek_kind() == TokenKind::RightParen {
self.bump()?; if self.peek_kind() == TokenKind::Arrow {
self.bump()?; let body = self.parse_arrow_body(true)?;
let end = self.arrow_body_loc(&body);
return Ok(Expr::Arrow(Box::new(ArrowExpr {
loc: Self::merge_spans(start, end),
is_async: true,
params: vec![],
body,
is_strict: self.strict_mode,
})));
}
self.scanner = paren_saved_scanner;
self.current = paren_saved_current;
} else {
self.scanner = paren_saved_scanner;
self.current = paren_saved_current;
}
}
if self.peek_kind() == TokenKind::Identifier {
let id_saved_scanner = self.scanner.clone();
let id_saved_current = self.current.clone();
let id_tok = self.bump()?; if self.peek_kind() == TokenKind::Arrow {
self.bump()?; let ident = self.ident_from_token(&id_tok)?;
let params = vec![Param {
loc: ident.loc,
pat: Pat::Ident(ident),
default: None,
}];
let body = self.parse_arrow_body(true)?;
let end = self.arrow_body_loc(&body);
return Ok(Expr::Arrow(Box::new(ArrowExpr {
loc: Self::merge_spans(start, end),
is_async: true,
params,
body,
is_strict: self.strict_mode,
})));
}
self.scanner = id_saved_scanner;
self.current = id_saved_current;
}
}
self.scanner = saved_scanner;
self.current = saved_current;
}
if self.peek_kind() == TokenKind::Yield {
let yield_tok = self.bump()?; let delegate = self.eat(TokenKind::Star)?;
let argument = if !delegate
&& (self.peek_kind() == TokenKind::Semicolon
|| self.peek_kind() == TokenKind::RightBrace
|| self.peek_kind() == TokenKind::RightParen
|| self.peek_kind() == TokenKind::RightBracket
|| self.peek_kind() == TokenKind::Eof
|| self.current.had_line_terminator_before)
{
None
} else {
Some(Box::new(self.parse_assignment_expr()?))
};
let end = argument.as_ref().map(|a| a.loc()).unwrap_or(yield_tok.span);
return Ok(Expr::Yield(Box::new(YieldExpr {
loc: Self::merge_spans(start, end),
delegate,
argument,
})));
}
let saved_scanner = self.scanner.clone();
let saved_current = self.current.clone();
if self.peek_kind() == TokenKind::LeftParen {
self.bump()?; if self.peek_kind() == TokenKind::RightParen {
self.bump()?; if self.peek_kind() == TokenKind::Arrow {
self.bump()?; let body = self.parse_arrow_body(false)?;
let end = self.arrow_body_loc(&body);
return Ok(Expr::Arrow(Box::new(ArrowExpr {
loc: Self::merge_spans(start, end),
is_async: false,
params: vec![],
body,
is_strict: self.strict_mode,
})));
}
self.scanner = saved_scanner.clone();
self.current = saved_current.clone();
} else {
self.scanner = saved_scanner.clone();
self.current = saved_current.clone();
}
}
let lhs = self.parse_conditional_expr()?;
if self.peek_kind() == TokenKind::Arrow {
self.bump()?;
if let Expr::Call(ref call) = lhs
&& let Expr::Ident(ref id) = *call.callee
&& id.name == "async"
{
let params: Vec<Param> = call
.arguments
.clone()
.into_iter()
.map(|e| self.expr_to_single_param(e))
.collect::<StatorResult<Vec<_>>>()?;
self.check_unique_params(¶ms)?;
let body = self.parse_arrow_body(true)?;
let end = self.arrow_body_loc(&body);
return Ok(Expr::Arrow(Box::new(ArrowExpr {
loc: Self::merge_spans(start, end),
is_async: true,
params,
body,
is_strict: self.strict_mode,
})));
}
let params = self.expr_to_arrow_params(lhs)?;
self.check_unique_params(¶ms)?;
let body = self.parse_arrow_body(false)?;
let end = self.arrow_body_loc(&body);
return Ok(Expr::Arrow(Box::new(ArrowExpr {
loc: Self::merge_spans(start, end),
is_async: false,
params,
body,
is_strict: self.strict_mode,
})));
}
let assign_op = match self.peek_kind() {
TokenKind::Equal => Some(AssignOp::Assign),
TokenKind::PlusEqual => Some(AssignOp::AddAssign),
TokenKind::MinusEqual => Some(AssignOp::SubAssign),
TokenKind::StarEqual => Some(AssignOp::MulAssign),
TokenKind::StarStarEqual => Some(AssignOp::ExpAssign),
TokenKind::SlashEqual => Some(AssignOp::DivAssign),
TokenKind::PercentEqual => Some(AssignOp::RemAssign),
TokenKind::LessLessEqual => Some(AssignOp::ShlAssign),
TokenKind::GreaterGreaterEqual => Some(AssignOp::ShrAssign),
TokenKind::GreaterGreaterGreaterEqual => Some(AssignOp::UShrAssign),
TokenKind::AmpersandEqual => Some(AssignOp::BitAndAssign),
TokenKind::PipeEqual => Some(AssignOp::BitOrAssign),
TokenKind::CaretEqual => Some(AssignOp::BitXorAssign),
TokenKind::AmpersandAmpersandEqual => Some(AssignOp::LogicalAndAssign),
TokenKind::PipePipeEqual => Some(AssignOp::LogicalOrAssign),
TokenKind::QuestionQuestionEqual => Some(AssignOp::NullishAssign),
_ => None,
};
if let Some(op) = assign_op {
let left = self.expr_to_assign_target(lhs, op)?;
self.bump()?;
let rhs = self.parse_assignment_expr()?;
let end = rhs.loc();
return Ok(Expr::Assign(Box::new(AssignExpr {
loc: Self::merge_spans(start, end),
op,
left,
right: Box::new(rhs),
})));
}
Ok(lhs)
}
fn parse_conditional_expr(&mut self) -> StatorResult<Expr> {
let start = self.current_span();
let test = self.parse_nullish_coalesce()?;
if self.eat(TokenKind::Question)? {
let consequent = self.parse_assignment_expr()?;
self.expect(TokenKind::Colon)?;
let alternate = self.parse_assignment_expr()?;
let end = alternate.loc();
return Ok(Expr::Conditional(Box::new(
crate::parser::ast::ConditionalExpr {
loc: Self::merge_spans(start, end),
test: Box::new(test),
consequent: Box::new(consequent),
alternate: Box::new(alternate),
},
)));
}
Ok(test)
}
fn parse_nullish_coalesce(&mut self) -> StatorResult<Expr> {
let start = self.current_span();
let mut left = self.parse_logical_or()?;
while self.peek_kind() == TokenKind::QuestionQuestion {
self.bump()?;
let saved = self.in_nullish_coalesce;
self.in_nullish_coalesce = true;
let right = self.parse_logical_or()?;
self.in_nullish_coalesce = saved;
let end = right.loc();
left = Expr::Logical(Box::new(LogicalExpr {
loc: Self::merge_spans(start, end),
op: LogicalOp::NullishCoalesce,
left: Box::new(left),
right: Box::new(right),
}));
}
Ok(left)
}
fn parse_logical_or(&mut self) -> StatorResult<Expr> {
let start = self.current_span();
let mut left = self.parse_logical_and()?;
let mut had_or = false;
while self.peek_kind() == TokenKind::PipePipe {
if self.in_nullish_coalesce {
return Err(self.error("cannot use `||` inside `??` without parentheses"));
}
had_or = true;
self.bump()?;
let right = self.parse_logical_and()?;
let end = right.loc();
left = Expr::Logical(Box::new(LogicalExpr {
loc: Self::merge_spans(start, end),
op: LogicalOp::Or,
left: Box::new(left),
right: Box::new(right),
}));
}
if had_or && self.peek_kind() == TokenKind::QuestionQuestion {
return Err(self.error("cannot use `??` after `||` without parentheses"));
}
Ok(left)
}
fn parse_logical_and(&mut self) -> StatorResult<Expr> {
let start = self.current_span();
let mut left = self.parse_bitwise_or()?;
let mut had_and = false;
while self.peek_kind() == TokenKind::AmpersandAmpersand {
if self.in_nullish_coalesce {
return Err(self.error("cannot use `&&` inside `??` without parentheses"));
}
had_and = true;
self.bump()?;
let right = self.parse_bitwise_or()?;
let end = right.loc();
left = Expr::Logical(Box::new(LogicalExpr {
loc: Self::merge_spans(start, end),
op: LogicalOp::And,
left: Box::new(left),
right: Box::new(right),
}));
}
if had_and && self.peek_kind() == TokenKind::QuestionQuestion {
return Err(self.error("cannot use `??` after `&&` without parentheses"));
}
Ok(left)
}
fn parse_bitwise_or(&mut self) -> StatorResult<Expr> {
self.parse_binary_left_assoc(
Self::parse_bitwise_xor,
&[(TokenKind::Pipe, BinaryOp::BitOr)],
)
}
fn parse_bitwise_xor(&mut self) -> StatorResult<Expr> {
self.parse_binary_left_assoc(
Self::parse_bitwise_and,
&[(TokenKind::Caret, BinaryOp::BitXor)],
)
}
fn parse_bitwise_and(&mut self) -> StatorResult<Expr> {
self.parse_binary_left_assoc(
Self::parse_equality,
&[(TokenKind::Ampersand, BinaryOp::BitAnd)],
)
}
fn parse_equality(&mut self) -> StatorResult<Expr> {
self.parse_binary_left_assoc(
Self::parse_relational,
&[
(TokenKind::EqualEqual, BinaryOp::Eq),
(TokenKind::BangEqual, BinaryOp::NotEq),
(TokenKind::EqualEqualEqual, BinaryOp::StrictEq),
(TokenKind::BangEqualEqual, BinaryOp::StrictNotEq),
],
)
}
fn parse_relational(&mut self) -> StatorResult<Expr> {
if self.peek_kind() == TokenKind::PrivateIdentifier && !self.no_in {
let tok = self.bump()?;
let name = match tok.value {
TokenValue::Str(s) => s,
_ => return Err(Self::error_at(tok.span, "invalid private name token")),
};
if self.peek_kind() != TokenKind::In {
return Err(
self.error("private name #... can only appear on the left-hand side of 'in'")
);
}
self.bump()?; let right = self.parse_shift()?;
let end = right.loc();
return Ok(Expr::Binary(Box::new(BinaryExpr {
loc: Self::merge_spans(tok.span, end),
left: Box::new(Expr::PrivateName(PrivateIdent {
loc: tok.span,
name,
})),
op: BinaryOp::In,
right: Box::new(right),
})));
}
if self.no_in {
self.parse_binary_left_assoc(
Self::parse_shift,
&[
(TokenKind::Less, BinaryOp::Lt),
(TokenKind::LessEqual, BinaryOp::LtEq),
(TokenKind::Greater, BinaryOp::Gt),
(TokenKind::GreaterEqual, BinaryOp::GtEq),
(TokenKind::Instanceof, BinaryOp::Instanceof),
],
)
} else {
self.parse_binary_left_assoc(
Self::parse_shift,
&[
(TokenKind::Less, BinaryOp::Lt),
(TokenKind::LessEqual, BinaryOp::LtEq),
(TokenKind::Greater, BinaryOp::Gt),
(TokenKind::GreaterEqual, BinaryOp::GtEq),
(TokenKind::Instanceof, BinaryOp::Instanceof),
(TokenKind::In, BinaryOp::In),
],
)
}
}
fn parse_shift(&mut self) -> StatorResult<Expr> {
self.parse_binary_left_assoc(
Self::parse_additive,
&[
(TokenKind::LessLess, BinaryOp::Shl),
(TokenKind::GreaterGreater, BinaryOp::Shr),
(TokenKind::GreaterGreaterGreater, BinaryOp::UShr),
],
)
}
fn parse_additive(&mut self) -> StatorResult<Expr> {
self.parse_binary_left_assoc(
Self::parse_multiplicative,
&[
(TokenKind::Plus, BinaryOp::Add),
(TokenKind::Minus, BinaryOp::Sub),
],
)
}
fn parse_multiplicative(&mut self) -> StatorResult<Expr> {
self.parse_binary_left_assoc(
Self::parse_exponentiation,
&[
(TokenKind::Star, BinaryOp::Mul),
(TokenKind::Slash, BinaryOp::Div),
(TokenKind::Percent, BinaryOp::Rem),
],
)
}
fn parse_exponentiation(&mut self) -> StatorResult<Expr> {
let start = self.current_span();
let base = self.parse_unary()?;
if self.peek_kind() == TokenKind::StarStar {
self.bump()?;
let exp = self.parse_exponentiation()?;
let end = exp.loc();
return Ok(Expr::Binary(Box::new(BinaryExpr {
loc: Self::merge_spans(start, end),
op: BinaryOp::Exp,
left: Box::new(base),
right: Box::new(exp),
})));
}
Ok(base)
}
fn parse_unary(&mut self) -> StatorResult<Expr> {
let start = self.current_span();
let op = match self.peek_kind() {
TokenKind::Bang => Some(UnaryOp::Not),
TokenKind::Tilde => Some(UnaryOp::BitNot),
TokenKind::Minus => Some(UnaryOp::Minus),
TokenKind::Plus => Some(UnaryOp::Plus),
TokenKind::Typeof => Some(UnaryOp::Typeof),
TokenKind::Void => Some(UnaryOp::Void),
TokenKind::Delete => Some(UnaryOp::Delete),
TokenKind::Await => {
if self.async_function_depth == 0 && !self.is_module {
return Err(Self::error_at(
start,
"'await' is only valid in async functions and module top-level code",
));
}
self.bump()?;
let argument = self.parse_unary()?;
let end = argument.loc();
let expr = Expr::Await(Box::new(AwaitExpr {
loc: Self::merge_spans(start, end),
argument: Box::new(argument),
}));
if self.peek_kind() == TokenKind::StarStar {
return Err(Self::error_at(
Self::merge_spans(start, end),
"unary operator used immediately before `**`; \
use parentheses to disambiguate",
));
}
return Ok(expr);
}
TokenKind::PlusPlus => {
self.bump()?;
let arg = self.parse_unary()?;
let end = arg.loc();
self.validate_update_target(&arg)?;
if self.peek_kind() == TokenKind::StarStar {
return Err(Self::error_at(
Self::merge_spans(start, end),
"unary operator used immediately before `**`; \
use parentheses to disambiguate",
));
}
return Ok(Expr::Update(Box::new(UpdateExpr {
loc: Self::merge_spans(start, end),
op: UpdateOp::Increment,
prefix: true,
argument: Box::new(arg),
})));
}
TokenKind::MinusMinus => {
self.bump()?;
let arg = self.parse_unary()?;
let end = arg.loc();
self.validate_update_target(&arg)?;
if self.peek_kind() == TokenKind::StarStar {
return Err(Self::error_at(
Self::merge_spans(start, end),
"unary operator used immediately before `**`; \
use parentheses to disambiguate",
));
}
return Ok(Expr::Update(Box::new(UpdateExpr {
loc: Self::merge_spans(start, end),
op: UpdateOp::Decrement,
prefix: true,
argument: Box::new(arg),
})));
}
_ => None,
};
if let Some(op) = op {
self.bump()?;
let arg = self.parse_unary()?;
let end = arg.loc();
if op == UnaryOp::Delete && Self::contains_private_member_delete(&arg) {
return Err(Self::error_at(
Self::merge_spans(start, end),
"private fields can not be deleted",
));
}
if op == UnaryOp::Delete && self.strict_mode && matches!(arg, Expr::Ident(_)) {
return Err(Self::error_at(
Self::merge_spans(start, end),
"deleting an unqualified identifier in strict mode is not allowed",
));
}
if self.peek_kind() == TokenKind::StarStar {
return Err(Self::error_at(
Self::merge_spans(start, end),
"unary operator used immediately before `**`; \
use parentheses to disambiguate",
));
}
return Ok(Expr::Unary(Box::new(UnaryExpr {
loc: Self::merge_spans(start, end),
op,
argument: Box::new(arg),
})));
}
self.parse_postfix()
}
fn parse_postfix(&mut self) -> StatorResult<Expr> {
let start = self.current_span();
let expr = self.parse_call_member()?;
if !self.current.had_line_terminator_before {
let op = match self.peek_kind() {
TokenKind::PlusPlus => Some(UpdateOp::Increment),
TokenKind::MinusMinus => Some(UpdateOp::Decrement),
_ => None,
};
if let Some(op) = op {
self.validate_update_target(&expr)?;
let end = self.current_span();
self.bump()?;
return Ok(Expr::Update(Box::new(UpdateExpr {
loc: Self::merge_spans(start, end),
op,
prefix: false,
argument: Box::new(expr),
})));
}
}
Ok(expr)
}
fn parse_call_member(&mut self) -> StatorResult<Expr> {
let start = self.current_span();
let mut expr = self.parse_primary()?;
let mut after_optional = false;
loop {
match self.peek_kind() {
TokenKind::Dot => {
self.bump()?;
if self.peek_kind() == TokenKind::PrivateIdentifier {
let prop_tok = self.bump()?;
let name = match prop_tok.value {
TokenValue::Str(s) => s,
_ => {
return Err(Self::error_at(
prop_tok.span,
"invalid private name token",
));
}
};
let end = prop_tok.span;
expr = Expr::Member(Box::new(crate::parser::ast::MemberExpr {
loc: Self::merge_spans(start, end),
object: Box::new(expr),
property: crate::parser::ast::MemberProp::Private(PrivateIdent {
loc: prop_tok.span,
name,
}),
is_computed: false,
}));
} else {
let prop_tok = self.bump()?;
let name = self.name_from_token(&prop_tok)?;
let prop_ident = Ident {
loc: prop_tok.span,
name,
};
let end = prop_tok.span;
expr = Expr::Member(Box::new(crate::parser::ast::MemberExpr {
loc: Self::merge_spans(start, end),
object: Box::new(expr),
property: crate::parser::ast::MemberProp::Ident(prop_ident),
is_computed: false,
}));
}
}
TokenKind::LeftBracket => {
self.bump()?;
let prop = self.parse_expr()?;
let end = self.current_span();
self.expect(TokenKind::RightBracket)?;
expr = Expr::Member(Box::new(crate::parser::ast::MemberExpr {
loc: Self::merge_spans(start, end),
object: Box::new(expr),
property: crate::parser::ast::MemberProp::Computed(Box::new(prop)),
is_computed: true,
}));
}
TokenKind::LeftParen => {
self.bump()?;
let args = self.parse_call_args()?;
let end = self.current_span();
expr = Expr::Call(Box::new(crate::parser::ast::CallExpr {
loc: Self::merge_spans(start, end),
callee: Box::new(expr),
arguments: args,
}));
}
TokenKind::QuestionDot => {
after_optional = true;
self.bump()?;
match self.peek_kind() {
TokenKind::LeftParen => {
self.bump()?;
let args = self.parse_call_args()?;
let end = self.current_span();
expr = Expr::OptionalCall(Box::new(
crate::parser::ast::OptionalCallExpr {
loc: Self::merge_spans(start, end),
callee: Box::new(expr),
arguments: args,
},
));
}
TokenKind::LeftBracket => {
self.bump()?;
let prop = self.parse_expr()?;
let end = self.current_span();
self.expect(TokenKind::RightBracket)?;
expr = Expr::OptionalMember(Box::new(
crate::parser::ast::OptionalMemberExpr {
loc: Self::merge_spans(start, end),
object: Box::new(expr),
property: crate::parser::ast::MemberProp::Computed(Box::new(
prop,
)),
is_computed: true,
},
));
}
TokenKind::PrivateIdentifier => {
let prop_tok = self.bump()?;
let name = match prop_tok.value {
TokenValue::Str(s) => s,
_ => {
return Err(Self::error_at(
prop_tok.span,
"invalid private name token",
));
}
};
let end = prop_tok.span;
expr = Expr::OptionalMember(Box::new(
crate::parser::ast::OptionalMemberExpr {
loc: Self::merge_spans(start, end),
object: Box::new(expr),
property: crate::parser::ast::MemberProp::Private(
PrivateIdent {
loc: prop_tok.span,
name,
},
),
is_computed: false,
},
));
}
_ => {
let prop_tok = self.bump()?;
let name = self.name_from_token(&prop_tok)?;
let prop_ident = Ident {
loc: prop_tok.span,
name,
};
let end = prop_tok.span;
expr = Expr::OptionalMember(Box::new(
crate::parser::ast::OptionalMemberExpr {
loc: Self::merge_spans(start, end),
object: Box::new(expr),
property: crate::parser::ast::MemberProp::Ident(prop_ident),
is_computed: false,
},
));
}
}
}
TokenKind::NoSubstitutionTemplate | TokenKind::TemplateHead => {
if after_optional {
return Err(
self.error("tagged template cannot be used in an optional chain")
);
}
let tpl_expr = self.parse_template_literal(true)?;
let quasi = match tpl_expr {
Expr::Template(t) => *t,
_ => unreachable!("template token must produce Expr::Template"),
};
let end = quasi.loc;
expr = Expr::TaggedTemplate(Box::new(crate::parser::ast::TaggedTemplateExpr {
loc: Self::merge_spans(start, end),
tag: Box::new(expr),
quasi,
}));
}
_ => break,
}
}
if after_optional {
expr = Expr::OptionalChain(Box::new(expr));
}
Ok(expr)
}
fn parse_call_args(&mut self) -> StatorResult<Vec<Expr>> {
let mut args = Vec::new();
while self.peek_kind() != TokenKind::RightParen {
if self.peek_kind() == TokenKind::Eof {
return Err(self.error("unexpected end of input in argument list"));
}
if self.peek_kind() == TokenKind::DotDotDot {
let spread_start = self.current_span();
self.bump()?; let argument = self.parse_assignment_expr()?;
let arg_end = argument.loc().end;
args.push(Expr::Spread(Box::new(SpreadElement {
loc: SourceLocation {
start: spread_start.start,
end: arg_end,
},
argument: Box::new(argument),
})));
} else {
args.push(self.parse_assignment_expr()?);
}
if !self.eat(TokenKind::Comma)? {
break;
}
}
self.expect(TokenKind::RightParen)?;
Ok(args)
}
fn parse_primary(&mut self) -> StatorResult<Expr> {
self.enter()?;
let result = self.parse_primary_inner();
self.leave();
result
}
fn parse_primary_inner(&mut self) -> StatorResult<Expr> {
let span = self.current_span();
match self.peek_kind() {
TokenKind::NumericLiteral => {
let tok = self.bump()?;
if self.strict_mode {
let raw = &self.scanner.source()[tok.span.start.offset..tok.span.end.offset];
if raw.len() >= 2 && raw.starts_with('0') && raw.as_bytes()[1].is_ascii_digit()
{
return Err(Self::error_at(
tok.span,
"octal literals are not allowed in strict mode",
));
}
}
match tok.value {
TokenValue::Number(n) => Ok(Expr::Num(NumLit {
loc: tok.span,
value: n,
raw: String::new(),
})),
TokenValue::BigInt(s) => Ok(Expr::BigInt(BigIntLit {
loc: tok.span,
value: s,
})),
_ => Err(Self::error_at(tok.span, "invalid numeric token")),
}
}
TokenKind::StringLiteral => {
let tok = self.bump()?;
let value = match tok.value {
TokenValue::Str(s) => s,
_ => return Err(Self::error_at(tok.span, "invalid string token")),
};
if self.strict_mode {
Self::check_octal_escape(&value, tok.span)?;
}
Ok(Expr::Str(StringLit {
loc: tok.span,
value,
}))
}
TokenKind::RegExpLiteral => {
let tok = self.bump()?;
let raw = match tok.value {
TokenValue::Str(s) => s,
_ => return Err(Self::error_at(tok.span, "invalid regexp token")),
};
let body = &raw[1..];
let closing = body
.rfind('/')
.ok_or_else(|| Self::error_at(tok.span, "malformed regexp literal"))?;
let pattern = body[..closing].to_string();
let flags = body[closing + 1..].to_string();
Ok(Expr::Regexp(RegExpLit {
loc: tok.span,
pattern,
flags,
}))
}
TokenKind::True => {
self.bump()?;
Ok(Expr::Bool(BoolLit {
loc: span,
value: true,
}))
}
TokenKind::False => {
self.bump()?;
Ok(Expr::Bool(BoolLit {
loc: span,
value: false,
}))
}
TokenKind::Null => {
self.bump()?;
Ok(Expr::Null(NullLit { loc: span }))
}
TokenKind::This => {
self.bump()?;
Ok(Expr::This(crate::parser::ast::ThisExpr { loc: span }))
}
TokenKind::Super => {
self.bump()?;
Ok(Expr::Ident(Ident {
loc: span,
name: "super".to_owned(),
}))
}
TokenKind::Identifier => {
let tok = self.bump()?;
let name = match tok.value {
TokenValue::Str(s) => s,
_ => return Err(Self::error_at(tok.span, "invalid identifier token")),
};
if self.strict_mode && Self::is_strict_reserved_word(&name) {
return Err(Self::error_at(
tok.span,
&format!(
"'{name}' is a reserved word and cannot be used as an identifier in strict mode"
),
));
}
Ok(Expr::Ident(Ident {
loc: tok.span,
name,
}))
}
TokenKind::LeftParen => {
self.bump()?;
let saved_nc = self.in_nullish_coalesce;
self.in_nullish_coalesce = false;
let expr = self.parse_expr()?;
self.in_nullish_coalesce = saved_nc;
self.expect(TokenKind::RightParen)?;
Ok(expr)
}
TokenKind::LeftBracket => {
let start = self.current_span();
self.bump()?; let mut elements: Vec<Option<Expr>> = Vec::new();
while self.peek_kind() != TokenKind::RightBracket {
if self.peek_kind() == TokenKind::Eof {
return Err(self.error("unexpected end of input in array literal"));
}
if self.peek_kind() == TokenKind::Comma {
elements.push(None);
self.bump()?;
continue;
}
if self.peek_kind() == TokenKind::DotDotDot {
let spread_start = self.current_span();
self.bump()?; let argument = self.parse_assignment_expr()?;
let arg_end = argument.loc().end;
elements.push(Some(Expr::Spread(Box::new(SpreadElement {
loc: SourceLocation {
start: spread_start.start,
end: arg_end,
},
argument: Box::new(argument),
}))));
} else {
elements.push(Some(self.parse_assignment_expr()?));
}
if !self.eat(TokenKind::Comma)? {
break;
}
}
let end = self.expect(TokenKind::RightBracket)?;
Ok(Expr::Array(Box::new(ArrayExpr {
loc: SourceLocation {
start: start.start,
end: end.span.end,
},
elements,
})))
}
TokenKind::LeftBrace => {
let start = self.current_span();
self.bump()?; let mut properties: Vec<ObjectProp> = Vec::new();
let mut proto_count: u32 = 0;
while self.peek_kind() != TokenKind::RightBrace {
if self.peek_kind() == TokenKind::Eof {
return Err(self.error("unexpected end of input in object literal"));
}
if self.peek_kind() == TokenKind::DotDotDot {
let spread_start = self.current_span();
self.bump()?; let argument = self.parse_assignment_expr()?;
let arg_end = argument.loc().end;
properties.push(ObjectProp::Spread(SpreadElement {
loc: SourceLocation {
start: spread_start.start,
end: arg_end,
},
argument: Box::new(argument),
}));
} else {
let prop_start = self.current_span();
let is_async_method = if self.peek_kind() == TokenKind::Async {
let saved_scanner = self.scanner.clone();
let saved_current = self.current.clone();
self.bump()?; let is_method = !self.current.had_line_terminator_before
&& self.peek_kind() != TokenKind::Colon
&& self.peek_kind() != TokenKind::Comma
&& self.peek_kind() != TokenKind::RightBrace
&& self.peek_kind() != TokenKind::LeftParen;
if is_method {
true
} else {
self.scanner = saved_scanner;
self.current = saved_current;
false
}
} else {
false
};
let getter_setter_kind = if !is_async_method
&& (self.peek_kind() == TokenKind::Get
|| self.peek_kind() == TokenKind::Set)
{
let saved_scanner = self.scanner.clone();
let saved_current = self.current.clone();
let accessor_kind = if self.peek_kind() == TokenKind::Get {
MethodKind::Get
} else {
MethodKind::Set
};
self.bump()?; if self.peek_kind() == TokenKind::Colon
|| self.peek_kind() == TokenKind::Comma
|| self.peek_kind() == TokenKind::RightBrace
|| self.peek_kind() == TokenKind::LeftParen
{
self.scanner = saved_scanner;
self.current = saved_current;
None
} else {
Some(accessor_kind)
}
} else {
None
};
let is_generator_method = if getter_setter_kind.is_none() {
self.eat(TokenKind::Star)?
} else {
false
};
let (key, is_computed) = match self.peek_kind() {
TokenKind::LeftBracket => {
self.bump()?; let key_expr = self.parse_assignment_expr()?;
self.expect(TokenKind::RightBracket)?;
(PropKey::Computed(Box::new(key_expr)), true)
}
TokenKind::StringLiteral => {
let tok = self.bump()?;
let value = match tok.value {
TokenValue::Str(s) => s,
_ => {
return Err(Self::error_at(
tok.span,
"invalid string token",
));
}
};
(
PropKey::Str(StringLit {
loc: tok.span,
value,
}),
false,
)
}
TokenKind::NumericLiteral => {
let tok = self.bump()?;
let value = match tok.value {
TokenValue::Number(n) => n,
_ => {
return Err(Self::error_at(
tok.span,
"invalid numeric token",
));
}
};
(
PropKey::Num(NumLit {
loc: tok.span,
value,
raw: String::new(),
}),
false,
)
}
_ => {
let tok = self.bump()?;
let name = match &tok.value {
TokenValue::Str(s) => s.clone(),
TokenValue::None => {
format!("{:?}", tok.kind).to_lowercase()
}
_ => {
return Err(Self::error_at(
tok.span,
"expected property name",
));
}
};
(
PropKey::Ident(Ident {
loc: tok.span,
name,
}),
false,
)
}
};
let mut is_proto_value_prop = false;
let value = if let Some(accessor_kind) = getter_setter_kind {
let fn_start = self.current_span();
self.expect(TokenKind::LeftParen)?;
let params = self.parse_formal_params()?;
self.check_unique_params(¶ms)?;
let outer_fn = self.function_depth;
let outer_it = self.iteration_depth;
let outer_br = self.breakable_depth;
let outer_labels = std::mem::take(&mut self.labels);
let outer_async_depth = self.async_function_depth;
self.function_depth = 1;
self.iteration_depth = 0;
self.breakable_depth = 0;
self.async_function_depth = 0;
let body = self.parse_block();
self.function_depth = outer_fn;
self.iteration_depth = outer_it;
self.breakable_depth = outer_br;
self.labels = outer_labels;
self.async_function_depth = outer_async_depth;
let body = body?;
let fn_end = body.loc;
let fn_expr = FnExpr {
loc: Self::merge_spans(fn_start, fn_end),
id: None,
is_async: false,
is_generator: false,
params,
body,
is_strict: false,
};
match accessor_kind {
MethodKind::Get => PropValue::Get(fn_expr),
MethodKind::Set => PropValue::Set(fn_expr),
_ => unreachable!(),
}
} else if is_async_method
|| is_generator_method
|| self.peek_kind() == TokenKind::LeftParen
{
let fn_start = self.current_span();
self.expect(TokenKind::LeftParen)?;
let params = self.parse_formal_params()?;
if is_generator_method {
self.check_generator_params_for_yield(¶ms, fn_start)?;
}
self.check_unique_params(¶ms)?;
let outer_fn = self.function_depth;
let outer_it = self.iteration_depth;
let outer_br = self.breakable_depth;
let outer_labels = std::mem::take(&mut self.labels);
let outer_async_depth = self.async_function_depth;
self.function_depth = 1;
self.iteration_depth = 0;
self.breakable_depth = 0;
self.async_function_depth = if is_async_method { 1 } else { 0 };
let body = self.parse_block();
self.function_depth = outer_fn;
self.iteration_depth = outer_it;
self.breakable_depth = outer_br;
self.labels = outer_labels;
self.async_function_depth = outer_async_depth;
let body = body?;
let fn_end = body.loc;
PropValue::Method(FnExpr {
loc: Self::merge_spans(fn_start, fn_end),
id: None,
is_async: is_async_method,
is_generator: is_generator_method,
params,
body,
is_strict: false,
})
} else if self.eat(TokenKind::Colon)? {
is_proto_value_prop = true;
PropValue::Value(Box::new(self.parse_assignment_expr()?))
} else {
match &key {
PropKey::Ident(id) => {
if self.peek_kind() == TokenKind::Equal {
self.bump()?; let rhs = self.parse_assignment_expr()?;
let ident = id.clone();
let assign_loc = Self::merge_spans(ident.loc, rhs.loc());
PropValue::Value(Box::new(Expr::Assign(Box::new(
AssignExpr {
loc: assign_loc,
op: AssignOp::Assign,
left: AssignTarget::Expr(Box::new(Expr::Ident(
ident,
))),
right: Box::new(rhs),
},
))))
} else {
PropValue::Shorthand
}
}
_ => {
return Err(Self::error_at(
prop_start,
"expected ':' after property name",
));
}
}
};
let prop_end = match &value {
PropValue::Value(expr) => expr.loc().end,
PropValue::Shorthand => prop_start.end,
PropValue::Get(f) | PropValue::Set(f) | PropValue::Method(f) => {
f.loc.end
}
};
properties.push(ObjectProp::Prop(Box::new(Prop {
loc: SourceLocation {
start: prop_start.start,
end: prop_end,
},
key: key.clone(),
is_computed,
value,
})));
if !is_computed && is_proto_value_prop {
let is_proto = match &key {
PropKey::Ident(id) => id.name == "__proto__",
PropKey::Str(s) => {
s.value == "\"__proto__\""
|| s.value == "'__proto__'"
|| s.value == "__proto__"
}
_ => false,
};
if is_proto {
proto_count += 1;
if proto_count > 1 {
return Err(Self::error_at(
prop_start,
"duplicate __proto__ property in object literal",
));
}
}
}
}
if !self.eat(TokenKind::Comma)? {
break;
}
}
let end = self.expect(TokenKind::RightBrace)?;
Ok(Expr::Object(Box::new(ObjectExpr {
loc: SourceLocation {
start: start.start,
end: end.span.end,
},
properties,
})))
}
TokenKind::New => {
let new_start = self.current_span();
self.bump()?; if self.peek_kind() == TokenKind::Dot {
self.bump()?; let prop_tok = self.bump()?;
let name = self.name_from_token(&prop_tok)?;
if name == "target" {
if self.function_depth == 0 {
return Err(Self::error_at(
Self::merge_spans(new_start, prop_tok.span),
"new.target is only valid inside functions",
));
}
let end = prop_tok.span;
return Ok(Expr::MetaProp(MetaPropExpr {
loc: Self::merge_spans(new_start, end),
meta: Ident {
loc: new_start,
name: "new".into(),
},
property: Ident {
loc: prop_tok.span,
name: "target".into(),
},
}));
}
return Err(Self::error_at(
prop_tok.span,
&format!("expected 'target' after 'new.', got '{name}'"),
));
}
if self.peek_kind() == TokenKind::Import {
return Err(self.error("cannot use 'new' with 'import'"));
}
let mut callee = self.parse_primary()?;
loop {
match self.peek_kind() {
TokenKind::Dot => {
self.bump()?;
if self.peek_kind() == TokenKind::PrivateIdentifier {
let prop_tok = self.bump()?;
let name = match prop_tok.value {
TokenValue::Str(s) => s,
_ => {
return Err(Self::error_at(
prop_tok.span,
"invalid private name token",
));
}
};
let end = prop_tok.span;
callee = Expr::Member(Box::new(crate::parser::ast::MemberExpr {
loc: Self::merge_spans(new_start, end),
object: Box::new(callee),
property: crate::parser::ast::MemberProp::Private(
PrivateIdent {
loc: prop_tok.span,
name,
},
),
is_computed: false,
}));
} else {
let prop_tok = self.bump()?;
let name = self.name_from_token(&prop_tok)?;
let prop_ident = Ident {
loc: prop_tok.span,
name,
};
let end = prop_tok.span;
callee = Expr::Member(Box::new(crate::parser::ast::MemberExpr {
loc: Self::merge_spans(new_start, end),
object: Box::new(callee),
property: crate::parser::ast::MemberProp::Ident(prop_ident),
is_computed: false,
}));
}
}
TokenKind::LeftBracket => {
self.bump()?;
let prop = self.parse_expr()?;
let end = self.current_span();
self.expect(TokenKind::RightBracket)?;
callee = Expr::Member(Box::new(crate::parser::ast::MemberExpr {
loc: Self::merge_spans(new_start, end),
object: Box::new(callee),
property: crate::parser::ast::MemberProp::Computed(Box::new(prop)),
is_computed: true,
}));
}
_ => break,
}
}
let arguments = if self.peek_kind() == TokenKind::LeftParen {
self.bump()?;
self.parse_call_args()?
} else {
vec![]
};
let end = self.current_span();
Ok(Expr::New(Box::new(NewExpr {
loc: Self::merge_spans(new_start, end),
callee: Box::new(callee),
arguments,
})))
}
TokenKind::Function => {
let fn_span = self.current_span();
self.bump()?;
self.parse_fn_expr(fn_span, false)
}
TokenKind::Async => {
let saved_scanner = self.scanner.clone();
let saved_current = self.current.clone();
let async_tok = self.bump()?; if self.peek_kind() == TokenKind::Function
&& !self.current.had_line_terminator_before
{
let fn_tok = self.bump()?; self.parse_fn_expr(Self::merge_spans(async_tok.span, fn_tok.span), true)
} else {
self.scanner = saved_scanner;
self.current = saved_current;
let tok = self.bump()?;
Ok(Expr::Ident(Ident {
loc: tok.span,
name: "async".into(),
}))
}
}
TokenKind::Import => {
let import_tok = self.bump()?; if self.peek_kind() == TokenKind::Dot {
self.bump()?; let prop_tok = self.bump()?;
let prop_name = self.name_from_token(&prop_tok)?;
if prop_name != "meta" {
return Err(Self::error_at(
prop_tok.span,
&format!(
"the only valid meta property for import is 'import.meta', got 'import.{prop_name}'"
),
));
}
if !self.is_module {
return Err(Self::error_at(
import_tok.span,
"import.meta is only valid in module code",
));
}
let end = prop_tok.span;
Ok(Expr::MetaProp(MetaPropExpr {
loc: Self::merge_spans(import_tok.span, end),
meta: Ident {
loc: import_tok.span,
name: "import".into(),
},
property: Ident {
loc: prop_tok.span,
name: "meta".into(),
},
}))
} else if self.peek_kind() == TokenKind::LeftParen {
self.bump()?; if self.peek_kind() == TokenKind::RightParen {
return Err(Self::error_at(
import_tok.span,
"import() requires a specifier argument",
));
}
let source = self.parse_assignment_expr()?;
if self.eat(TokenKind::Comma)? {
return Err(Self::error_at(
self.current_span(),
"import() requires exactly one argument",
));
}
let end = self.expect(TokenKind::RightParen)?;
Ok(Expr::Import(Box::new(ImportExpr {
loc: Self::merge_spans(import_tok.span, end.span),
source: Box::new(source),
})))
} else {
Err(Self::error_at(
import_tok.span,
"expected '(' or '.' after 'import' in expression context",
))
}
}
TokenKind::Class => self.parse_class_expr(),
TokenKind::NoSubstitutionTemplate | TokenKind::TemplateHead => {
self.parse_template_literal(false)
}
kind if self.is_contextual_keyword_identifier(kind) => {
let tok = self.bump()?;
let name = self.name_from_token(&tok)?;
Ok(Expr::Ident(Ident {
loc: tok.span,
name,
}))
}
other => Err(self.error(&format!("unexpected token {:?}", other))),
}
}
fn parse_fn_expr(&mut self, fn_span: Span, is_async: bool) -> StatorResult<Expr> {
let is_generator = self.eat(TokenKind::Star)?;
let id = if self.peek_kind() == TokenKind::Identifier {
let tok = self.bump()?;
let ident = self.ident_from_token(&tok)?;
self.check_strict_binding_ident(&ident.name, ident.loc)?;
Some(ident)
} else {
None
};
self.expect(TokenKind::LeftParen)?;
let params = self.parse_formal_params()?;
if is_generator {
self.check_generator_params_for_yield(¶ms, fn_span)?;
}
let outer_strict = self.strict_mode;
let outer_async_depth = self.async_function_depth;
if is_async {
self.async_function_depth = 1;
} else {
self.async_function_depth = 0;
}
let (body, fn_strict, has_use_strict) = self.parse_function_body()?;
self.strict_mode = outer_strict;
self.async_function_depth = outer_async_depth;
if has_use_strict && Self::has_non_simple_params(¶ms) {
return Err(Self::error_at(
fn_span,
"illegal 'use strict' directive in function with non-simple parameter list",
));
}
if fn_strict {
if let Some(ref ident) = id {
self.check_strict_binding_ident(&ident.name, ident.loc)?;
}
self.check_strict_duplicate_params(¶ms)?;
}
let end = body.loc;
Ok(Expr::Fn(Box::new(crate::parser::ast::FnExpr {
loc: Self::merge_spans(fn_span, end),
id,
is_async,
is_generator,
params,
body,
is_strict: fn_strict,
})))
}
fn parse_binary_left_assoc(
&mut self,
next: fn(&mut Self) -> StatorResult<Expr>,
ops: &[(TokenKind, BinaryOp)],
) -> StatorResult<Expr> {
let start = self.current_span();
let mut left = next(self)?;
loop {
let maybe_op = ops
.iter()
.find(|(k, _)| *k == self.peek_kind())
.map(|(_, op)| *op);
if let Some(op) = maybe_op {
self.bump()?;
let right = next(self)?;
let end = right.loc();
left = Expr::Binary(Box::new(BinaryExpr {
loc: Self::merge_spans(start, end),
op,
left: Box::new(left),
right: Box::new(right),
}));
} else {
break;
}
}
Ok(left)
}
fn ident_from_token(&self, tok: &Token) -> StatorResult<Ident> {
let name = match &tok.value {
TokenValue::Str(s) => s.clone(),
_ => return Err(Self::error_at(tok.span, "expected identifier")),
};
Ok(Ident {
loc: tok.span,
name,
})
}
fn is_strict_reserved_word(name: &str) -> bool {
matches!(
name,
"implements"
| "interface"
| "let"
| "package"
| "private"
| "protected"
| "public"
| "static"
| "yield"
)
}
fn check_strict_binding_ident(&self, name: &str, span: Span) -> StatorResult<()> {
if self.strict_mode {
if name == "eval" || name == "arguments" {
return Err(Self::error_at(
span,
&format!("'{name}' cannot be used as a binding name in strict mode"),
));
}
if Self::is_strict_reserved_word(name) {
return Err(Self::error_at(
span,
&format!(
"'{name}' is a reserved word and cannot be used as a binding name in strict mode"
),
));
}
}
Ok(())
}
fn is_contextual_keyword_identifier(&self, kind: TokenKind) -> bool {
matches!(
kind,
TokenKind::Let
| TokenKind::Of
| TokenKind::Async
| TokenKind::From
| TokenKind::As
| TokenKind::Get
| TokenKind::Set
| TokenKind::Target
| TokenKind::Meta
| TokenKind::Static
| TokenKind::Using
| TokenKind::Yield
)
}
fn name_from_token(&self, tok: &Token) -> StatorResult<String> {
match &tok.value {
TokenValue::Str(s) => Ok(s.clone()),
TokenValue::None => {
Ok(format!("{:?}", tok.kind).to_lowercase())
}
_ => Err(Self::error_at(tok.span, "expected property name")),
}
}
fn parse_arrow_body(&mut self, is_async: bool) -> StatorResult<ArrowBody> {
if self.peek_kind() == TokenKind::LeftBrace {
let outer_function_depth = self.function_depth;
let outer_iteration_depth = self.iteration_depth;
let outer_breakable_depth = self.breakable_depth;
let outer_labels = std::mem::take(&mut self.labels);
let outer_async_depth = self.async_function_depth;
self.function_depth = 1;
self.iteration_depth = 0;
self.breakable_depth = 0;
self.async_function_depth = if is_async { 1 } else { 0 };
let block = self.parse_block();
self.function_depth = outer_function_depth;
self.iteration_depth = outer_iteration_depth;
self.breakable_depth = outer_breakable_depth;
self.labels = outer_labels;
self.async_function_depth = outer_async_depth;
Ok(ArrowBody::Block(block?))
} else {
let outer_async_depth = self.async_function_depth;
if is_async {
self.async_function_depth = 1;
}
let result = self.parse_assignment_expr();
self.async_function_depth = outer_async_depth;
Ok(ArrowBody::Expr(Box::new(result?)))
}
}
fn arrow_body_loc(&self, body: &ArrowBody) -> Span {
match body {
ArrowBody::Block(b) => Span {
start: b.loc.start,
end: b.loc.end,
},
ArrowBody::Expr(e) => {
let loc = e.loc();
Span {
start: loc.start,
end: loc.end,
}
}
}
}
fn expr_to_arrow_params(&self, expr: Expr) -> StatorResult<Vec<Param>> {
match expr {
Expr::Ident(id) => {
self.check_strict_binding_ident(&id.name, id.loc)?;
Ok(vec![Param {
loc: id.loc,
pat: Pat::Ident(id),
default: None,
}])
}
Expr::Sequence(seq) => seq
.expressions
.into_iter()
.map(|e| self.expr_to_single_param(e))
.collect(),
Expr::Assign(assign) if assign.op == AssignOp::Assign => {
Ok(vec![self.assign_expr_to_param(*assign)?])
}
Expr::Array(_) | Expr::Object(_) => {
let loc = expr.loc();
let pat = self.expr_to_pat(expr)?;
Ok(vec![Param {
loc: Span {
start: loc.start,
end: loc.end,
},
pat,
default: None,
}])
}
Expr::Spread(spread) => {
let loc = spread.loc;
let arg = self.expr_to_pat(*spread.argument)?;
Ok(vec![Param {
loc,
pat: Pat::Rest(Box::new(RestElement {
loc,
argument: Box::new(arg),
})),
default: None,
}])
}
other => {
let loc = other.loc();
Err(Self::error_at(
Span {
start: loc.start,
end: loc.end,
},
"invalid arrow-function parameter",
))
}
}
}
fn expr_to_single_param(&self, expr: Expr) -> StatorResult<Param> {
match expr {
Expr::Ident(id) => {
self.check_strict_binding_ident(&id.name, id.loc)?;
Ok(Param {
loc: id.loc,
pat: Pat::Ident(id),
default: None,
})
}
Expr::Assign(assign) if assign.op == AssignOp::Assign => {
self.assign_expr_to_param(*assign)
}
Expr::Array(_) | Expr::Object(_) => {
let loc = expr.loc();
let pat = self.expr_to_pat(expr)?;
Ok(Param {
loc: Span {
start: loc.start,
end: loc.end,
},
pat,
default: None,
})
}
Expr::Spread(spread) => {
let loc = spread.loc;
let arg = self.expr_to_pat(*spread.argument)?;
Ok(Param {
loc,
pat: Pat::Rest(Box::new(RestElement {
loc,
argument: Box::new(arg),
})),
default: None,
})
}
other => {
let loc = other.loc();
Err(Self::error_at(
Span {
start: loc.start,
end: loc.end,
},
"invalid arrow-function parameter",
))
}
}
}
fn assign_expr_to_param(&self, assign: AssignExpr) -> StatorResult<Param> {
match assign.left {
AssignTarget::Expr(lhs) => {
let pat = self.expr_to_pat(*lhs)?;
Ok(Param {
loc: assign.loc,
pat,
default: Some(*assign.right),
})
}
AssignTarget::Pat(pat) => Ok(Param {
loc: assign.loc,
pat,
default: Some(*assign.right),
}),
}
}
fn expr_to_assign_target(&self, expr: Expr, op: AssignOp) -> StatorResult<AssignTarget> {
match expr {
Expr::Ident(ref id) => {
if self.strict_mode && (id.name == "eval" || id.name == "arguments") {
return Err(Self::error_at(
id.loc,
&format!("cannot assign to '{}' in strict mode", id.name),
));
}
Ok(AssignTarget::Expr(Box::new(expr)))
}
Expr::Member(_) => Ok(AssignTarget::Expr(Box::new(expr))),
Expr::Array(_) if op == AssignOp::Assign => {
let pat = self.expr_to_pat(expr)?;
Ok(AssignTarget::Pat(pat))
}
Expr::Object(_) if op == AssignOp::Assign => {
let pat = self.expr_to_pat(expr)?;
Ok(AssignTarget::Pat(pat))
}
other => {
let loc = other.loc();
Err(Self::error_at(loc, "invalid left-hand side in assignment"))
}
}
}
fn expr_to_pat(&self, expr: Expr) -> StatorResult<Pat> {
match expr {
Expr::Ident(id) => Ok(Pat::Ident(id)),
Expr::Array(arr) => {
let mut elements = Vec::with_capacity(arr.elements.len());
for elem in arr.elements {
match elem {
None => elements.push(None),
Some(e) => elements.push(Some(self.expr_to_pat(e)?)),
}
}
Ok(Pat::Array(Box::new(ArrayPat {
loc: arr.loc,
elements,
})))
}
Expr::Object(obj) => {
let mut properties = Vec::with_capacity(obj.properties.len());
for prop in obj.properties {
properties.push(self.obj_prop_to_pat_prop(prop)?);
}
Ok(Pat::Object(Box::new(ObjectPat {
loc: obj.loc,
properties,
})))
}
Expr::Assign(assign) if assign.op == AssignOp::Assign => {
let left = match assign.left {
AssignTarget::Expr(e) => self.expr_to_pat(*e)?,
AssignTarget::Pat(p) => p,
};
Ok(Pat::Assign(Box::new(AssignPat {
loc: assign.loc,
left: Box::new(left),
right: assign.right,
})))
}
Expr::Spread(spread) => {
let arg = self.expr_to_pat(*spread.argument)?;
Ok(Pat::Rest(Box::new(RestElement {
loc: spread.loc,
argument: Box::new(arg),
})))
}
expr @ Expr::Member(_) => Ok(Pat::Expr(Box::new(expr))),
other => {
let loc = other.loc();
Err(Self::error_at(loc, "invalid destructuring target"))
}
}
}
fn obj_prop_to_pat_prop(&self, prop: ObjectProp) -> StatorResult<ObjectPatProp> {
match prop {
ObjectProp::Spread(spread) => {
let arg = self.expr_to_pat(*spread.argument)?;
Ok(ObjectPatProp::Rest(RestElement {
loc: spread.loc,
argument: Box::new(arg),
}))
}
ObjectProp::Prop(p) => match p.value {
PropValue::Shorthand => {
if let PropKey::Ident(id) = p.key {
Ok(ObjectPatProp::Assign(AssignPatProp {
loc: p.loc,
key: id,
value: None,
}))
} else {
Err(Self::error_at(
p.loc,
"invalid shorthand in destructuring pattern",
))
}
}
PropValue::Value(expr) => {
if let Expr::Assign(ref assign) = *expr
&& let PropKey::Ident(ref key_id) = p.key
&& let AssignTarget::Expr(ref lhs) = assign.left
&& let Expr::Ident(ref lhs_id) = **lhs
&& key_id.name == lhs_id.name
&& assign.op == AssignOp::Assign
{
return Ok(ObjectPatProp::Assign(AssignPatProp {
loc: p.loc,
key: key_id.clone(),
value: Some(assign.right.clone()),
}));
}
let pat = self.expr_to_pat(*expr)?;
Ok(ObjectPatProp::KeyValue(KeyValuePatProp {
loc: p.loc,
key: p.key,
is_computed: p.is_computed,
value: pat,
}))
}
_ => {
Err(Self::error_at(
p.loc,
"invalid property in destructuring pattern",
))
}
},
}
}
fn contains_private_member_delete(expr: &Expr) -> bool {
match expr {
Expr::Member(m) => matches!(m.property, MemberProp::Private(_)),
Expr::Sequence(seq) => seq
.expressions
.last()
.is_some_and(Self::contains_private_member_delete),
Expr::Conditional(c) => {
Self::contains_private_member_delete(&c.consequent)
|| Self::contains_private_member_delete(&c.alternate)
}
Expr::OptionalMember(m) => matches!(m.property, MemberProp::Private(_)),
Expr::OptionalChain(inner) => Self::contains_private_member_delete(inner),
_ => false,
}
}
fn validate_update_target(&self, expr: &Expr) -> StatorResult<()> {
match expr {
Expr::Ident(id) => {
if self.strict_mode && (id.name == "eval" || id.name == "arguments") {
return Err(Self::error_at(
id.loc,
&format!("cannot update '{}' in strict mode", id.name),
));
}
Ok(())
}
Expr::Member(_) => Ok(()),
other => {
let loc = other.loc();
Err(Self::error_at(
loc,
"invalid left-hand side in update expression",
))
}
}
}
fn merge_spans(a: Span, b: Span) -> SourceLocation {
let start = if a.start.offset <= b.start.offset {
a.start
} else {
b.start
};
let end = if a.end.offset >= b.end.offset {
a.end
} else {
b.end
};
let start = if a.start.offset == 0 && b.start.offset != 0 {
b.start
} else {
start
};
Span {
start,
end: if end.offset == 0 { start } else { end },
}
}
}
pub fn parse(source: &str) -> StatorResult<Program> {
stacker::maybe_grow(32 * 1024, 512 * 1024, || {
let mut parser = Parser::new(source)?;
let program = parser.parse_program()?;
Ok(program)
})
}
pub fn parse_module(source: &str) -> StatorResult<Program> {
stacker::maybe_grow(32 * 1024, 512 * 1024, || {
let mut parser = Parser::new(source)?;
parser.is_module = true;
let program = parser.parse_program()?;
Ok(program)
})
}
pub fn parse_script(source: &str) -> StatorResult<Program> {
stacker::maybe_grow(32 * 1024, 512 * 1024, || {
let mut parser = Parser::new(source)?;
let program = parser.parse_program()?;
Ok(program)
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser::ast::{ObjectPatProp, Pat, ProgramItem, PropKey, Stmt, VarKind};
#[test]
fn test_parse_empty_program() {
let prog = parse("").unwrap();
assert!(prog.body.is_empty());
}
#[test]
fn test_parse_var_decl_with_number() {
let prog = parse("var x = 42;").unwrap();
assert_eq!(prog.body.len(), 1);
assert!(matches!(prog.body[0], ProgramItem::Stmt(Stmt::VarDecl(_))));
}
#[test]
fn test_parse_var_binary_expr() {
let prog = parse("var x = 1 + 2;").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
assert_eq!(vd.declarators.len(), 1);
assert!(vd.declarators[0].init.is_some());
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_parse_syntax_error_var_eq() {
let err = parse("var = ;").unwrap_err();
let msg = err.to_string();
assert!(
msg.contains("SyntaxError"),
"expected SyntaxError in {msg:?}"
);
assert!(msg.contains("1:5"), "expected position 1:5 in {msg:?}");
assert!(
msg.contains("expected binding pattern"),
"expected 'expected binding pattern' in {msg:?}"
);
}
#[test]
fn test_parse_expression_stmt() {
let prog = parse("1 + 2;").unwrap();
assert_eq!(prog.body.len(), 1);
assert!(matches!(prog.body[0], ProgramItem::Stmt(Stmt::Expr(_))));
}
#[test]
fn test_parse_if_stmt() {
let prog = parse("if (x) { y; }").unwrap();
assert_eq!(prog.body.len(), 1);
assert!(matches!(prog.body[0], ProgramItem::Stmt(Stmt::If(_))));
}
#[test]
fn test_parse_while_stmt() {
let prog = parse("while (i < 10) { i = i + 1; }").unwrap();
assert_eq!(prog.body.len(), 1);
assert!(matches!(prog.body[0], ProgramItem::Stmt(Stmt::While(_))));
}
#[test]
fn test_parse_function_decl() {
let prog = parse("function add(a, b) { return a + b; }").unwrap();
assert_eq!(prog.body.len(), 1);
assert!(matches!(prog.body[0], ProgramItem::Stmt(Stmt::FnDecl(_))));
}
#[test]
fn test_parse_return_stmt() {
let prog = parse("function f() { return 1; }").unwrap();
assert_eq!(prog.body.len(), 1);
}
#[test]
fn test_parse_multiple_stmts() {
let prog = parse("var a = 1; var b = 2; var c = a + b;").unwrap();
assert_eq!(prog.body.len(), 3);
}
#[test]
fn test_parse_array_literal_simple() {
let prog = parse("var a = [1, 2, 3];").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
if let Some(init) = &vd.declarators[0].init {
if let Expr::Array(arr) = init.as_ref() {
assert_eq!(arr.elements.len(), 3);
assert!(arr.elements.iter().all(|e| e.is_some()));
} else {
panic!("expected Array init");
}
} else {
panic!("expected init");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_parse_array_literal_empty() {
let prog = parse("var a = [];").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
if let Some(init) = &vd.declarators[0].init {
if let Expr::Array(arr) = init.as_ref() {
assert!(arr.elements.is_empty());
} else {
panic!("expected Array init");
}
} else {
panic!("expected init");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_parse_array_literal_trailing_comma() {
let prog = parse("var a = [1, 2,];").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
if let Some(init) = &vd.declarators[0].init {
if let Expr::Array(arr) = init.as_ref() {
assert_eq!(arr.elements.len(), 2);
assert!(arr.elements.iter().all(|e| e.is_some()));
} else {
panic!("expected Array init");
}
} else {
panic!("expected init");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_parse_array_literal_elision() {
let prog = parse("var a = [1,,3];").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
if let Some(init) = &vd.declarators[0].init {
if let Expr::Array(arr) = init.as_ref() {
assert_eq!(arr.elements.len(), 3);
assert!(arr.elements[0].is_some());
assert!(arr.elements[1].is_none());
assert!(arr.elements[2].is_some());
} else {
panic!("expected Array init");
}
} else {
panic!("expected init");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_parse_array_literal_nested() {
let prog = parse("var a = [[1], [2]];").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
if let Some(init) = &vd.declarators[0].init {
if let Expr::Array(arr) = init.as_ref() {
assert_eq!(arr.elements.len(), 2);
for elem in &arr.elements {
assert!(matches!(elem, Some(Expr::Array(_))));
}
} else {
panic!("expected Array init");
}
} else {
panic!("expected init");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_parse_array_literal_spread() {
let prog = parse("var a = [...b];").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
if let Some(init) = &vd.declarators[0].init {
if let Expr::Array(arr) = init.as_ref() {
assert_eq!(arr.elements.len(), 1);
assert!(matches!(arr.elements[0], Some(Expr::Spread(_))));
} else {
panic!("expected Array init");
}
} else {
panic!("expected init");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_parse_object_literal_empty() {
let prog = parse("var o = {};").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
if let Some(init) = &vd.declarators[0].init {
if let Expr::Object(obj) = init.as_ref() {
assert!(obj.properties.is_empty());
} else {
panic!("expected Object init");
}
} else {
panic!("expected init");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_parse_object_literal_simple() {
let prog = parse("var o = {x: 1, y: 2};").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
if let Some(init) = &vd.declarators[0].init {
if let Expr::Object(obj) = init.as_ref() {
assert_eq!(obj.properties.len(), 2);
for prop in &obj.properties {
assert!(matches!(prop, ObjectProp::Prop(_)));
}
} else {
panic!("expected Object init");
}
} else {
panic!("expected init");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_parse_object_literal_shorthand() {
let prog = parse("var o = {x, y};").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
if let Some(init) = &vd.declarators[0].init {
if let Expr::Object(obj) = init.as_ref() {
assert_eq!(obj.properties.len(), 2);
for prop in &obj.properties {
if let ObjectProp::Prop(p) = prop {
assert!(matches!(p.value, PropValue::Shorthand));
} else {
panic!("expected Prop");
}
}
} else {
panic!("expected Object init");
}
} else {
panic!("expected init");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_parse_object_literal_string_key() {
let prog = parse(r#"var o = {"name": 42};"#).unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
if let Some(init) = &vd.declarators[0].init {
if let Expr::Object(obj) = init.as_ref() {
assert_eq!(obj.properties.len(), 1);
if let ObjectProp::Prop(p) = &obj.properties[0] {
assert!(matches!(&p.key, PropKey::Str(_)));
} else {
panic!("expected Prop");
}
} else {
panic!("expected Object init");
}
} else {
panic!("expected init");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_parse_object_literal_numeric_key() {
let prog = parse("var o = {42: true};").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
if let Some(init) = &vd.declarators[0].init {
if let Expr::Object(obj) = init.as_ref() {
assert_eq!(obj.properties.len(), 1);
if let ObjectProp::Prop(p) = &obj.properties[0] {
assert!(matches!(&p.key, PropKey::Num(_)));
} else {
panic!("expected Prop");
}
} else {
panic!("expected Object init");
}
} else {
panic!("expected init");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_parse_object_literal_computed_key() {
let prog = parse("var o = {[x]: 1};").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
if let Some(init) = &vd.declarators[0].init {
if let Expr::Object(obj) = init.as_ref() {
assert_eq!(obj.properties.len(), 1);
if let ObjectProp::Prop(p) = &obj.properties[0] {
assert!(matches!(&p.key, PropKey::Computed(_)));
assert!(p.is_computed);
} else {
panic!("expected Prop");
}
} else {
panic!("expected Object init");
}
} else {
panic!("expected init");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_parse_object_literal_spread() {
let prog = parse("var o = {...a};").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
if let Some(init) = &vd.declarators[0].init {
if let Expr::Object(obj) = init.as_ref() {
assert_eq!(obj.properties.len(), 1);
assert!(matches!(&obj.properties[0], ObjectProp::Spread(_)));
} else {
panic!("expected Object init");
}
} else {
panic!("expected init");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_parse_object_literal_trailing_comma() {
let prog = parse("var o = {a: 1, b: 2,};").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
if let Some(init) = &vd.declarators[0].init {
if let Expr::Object(obj) = init.as_ref() {
assert_eq!(obj.properties.len(), 2);
} else {
panic!("expected Object init");
}
} else {
panic!("expected init");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_parse_object_literal_mixed() {
let prog = parse("var o = {a: 1, b, [c]: 3, ...d};").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
if let Some(init) = &vd.declarators[0].init {
if let Expr::Object(obj) = init.as_ref() {
assert_eq!(obj.properties.len(), 4);
assert!(matches!(&obj.properties[0], ObjectProp::Prop(_)));
if let ObjectProp::Prop(p) = &obj.properties[1] {
assert!(matches!(p.value, PropValue::Shorthand));
}
if let ObjectProp::Prop(p) = &obj.properties[2] {
assert!(p.is_computed);
}
assert!(matches!(&obj.properties[3], ObjectProp::Spread(_)));
} else {
panic!("expected Object init");
}
} else {
panic!("expected init");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_parse_new_with_args() {
let prog = parse("new Foo();").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::Expr(es)) = &prog.body[0] {
if let Expr::New(n) = es.expr.as_ref() {
assert!(matches!(n.callee.as_ref(), Expr::Ident(_)));
assert_eq!(n.arguments.len(), 0);
} else {
panic!("expected New expr, got {:?}", es.expr);
}
} else {
panic!("expected ExprStmt");
}
}
#[test]
fn test_parse_new_without_args() {
let prog = parse("new Foo;").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::Expr(es)) = &prog.body[0] {
if let Expr::New(n) = es.expr.as_ref() {
assert!(matches!(n.callee.as_ref(), Expr::Ident(_)));
assert_eq!(n.arguments.len(), 0);
} else {
panic!("expected New expr, got {:?}", es.expr);
}
} else {
panic!("expected ExprStmt");
}
}
#[test]
fn test_parse_new_with_multiple_args() {
let prog = parse("new Foo(a, b);").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::Expr(es)) = &prog.body[0] {
if let Expr::New(n) = es.expr.as_ref() {
assert!(matches!(n.callee.as_ref(), Expr::Ident(_)));
assert_eq!(n.arguments.len(), 2);
} else {
panic!("expected New expr, got {:?}", es.expr);
}
} else {
panic!("expected ExprStmt");
}
}
#[test]
fn test_parse_new_member_access_after() {
let prog = parse("new Foo().bar;").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::Expr(es)) = &prog.body[0] {
if let Expr::Member(m) = es.expr.as_ref() {
assert!(matches!(m.object.as_ref(), Expr::New(_)));
} else {
panic!("expected Member expr, got {:?}", es.expr);
}
} else {
panic!("expected ExprStmt");
}
}
#[test]
fn test_parse_new_member_callee() {
let prog = parse("new Foo.Bar();").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::Expr(es)) = &prog.body[0] {
if let Expr::New(n) = es.expr.as_ref() {
assert!(matches!(n.callee.as_ref(), Expr::Member(_)));
assert_eq!(n.arguments.len(), 0);
} else {
panic!("expected New expr, got {:?}", es.expr);
}
} else {
panic!("expected ExprStmt");
}
}
#[test]
fn test_parse_new_nested() {
let prog = parse("new new Foo();").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::Expr(es)) = &prog.body[0] {
if let Expr::New(outer) = es.expr.as_ref() {
assert!(matches!(outer.callee.as_ref(), Expr::New(_)));
} else {
panic!("expected New expr, got {:?}", es.expr);
}
} else {
panic!("expected ExprStmt");
}
}
#[test]
fn test_parse_new_error() {
let prog = parse("var e = new Error(\"fail\");").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
if let Some(init) = &vd.declarators[0].init {
if let Expr::New(n) = init.as_ref() {
assert_eq!(n.arguments.len(), 1);
} else {
panic!("expected New expr");
}
} else {
panic!("expected init");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_parse_switch_basic() {
let prog = parse("switch (x) { case 1: break; case 2: break; default: break; }").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::Switch(sw)) = &prog.body[0] {
assert_eq!(sw.cases.len(), 3);
assert!(sw.cases[0].test.is_some());
assert!(sw.cases[1].test.is_some());
assert!(sw.cases[2].test.is_none());
assert_eq!(sw.cases[0].consequent.len(), 1);
assert_eq!(sw.cases[1].consequent.len(), 1);
assert_eq!(sw.cases[2].consequent.len(), 1);
} else {
panic!("expected Switch statement");
}
}
#[test]
fn test_parse_switch_empty_cases() {
let prog = parse("switch (x) { case 1: case 2: break; }").unwrap();
if let ProgramItem::Stmt(Stmt::Switch(sw)) = &prog.body[0] {
assert_eq!(sw.cases.len(), 2);
assert!(sw.cases[0].consequent.is_empty());
assert_eq!(sw.cases[1].consequent.len(), 1);
} else {
panic!("expected Switch statement");
}
}
#[test]
fn test_parse_switch_default_only() {
let prog = parse("switch (val) { default: x = 1; }").unwrap();
if let ProgramItem::Stmt(Stmt::Switch(sw)) = &prog.body[0] {
assert_eq!(sw.cases.len(), 1);
assert!(sw.cases[0].test.is_none());
assert_eq!(sw.cases[0].consequent.len(), 1);
} else {
panic!("expected Switch statement");
}
}
#[test]
fn test_parse_switch_no_cases() {
let prog = parse("switch (x) {}").unwrap();
if let ProgramItem::Stmt(Stmt::Switch(sw)) = &prog.body[0] {
assert!(sw.cases.is_empty());
} else {
panic!("expected Switch statement");
}
}
use crate::parser::ast::ArrowBody;
fn parse_expr_stmt(src: &str) -> Expr {
let prog = parse(src).unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::Expr(es)) = prog.body.into_iter().next().unwrap() {
*es.expr
} else {
panic!("expected expression statement");
}
}
#[test]
fn test_arrow_no_params() {
let expr = parse_expr_stmt("() => 42;");
if let Expr::Arrow(arrow) = &expr {
assert!(!arrow.is_async);
assert!(arrow.params.is_empty());
assert!(matches!(&arrow.body, ArrowBody::Expr(_)));
} else {
panic!("expected Arrow, got {:?}", expr);
}
}
#[test]
fn test_arrow_single_param_no_parens() {
let expr = parse_expr_stmt("x => x + 1;");
if let Expr::Arrow(arrow) = &expr {
assert_eq!(arrow.params.len(), 1);
if let Pat::Ident(id) = &arrow.params[0].pat {
assert_eq!(id.name, "x");
} else {
panic!("expected Ident param");
}
assert!(matches!(&arrow.body, ArrowBody::Expr(_)));
} else {
panic!("expected Arrow, got {:?}", expr);
}
}
#[test]
fn test_arrow_single_param_with_parens() {
let expr = parse_expr_stmt("(x) => x * 2;");
if let Expr::Arrow(arrow) = &expr {
assert_eq!(arrow.params.len(), 1);
if let Pat::Ident(id) = &arrow.params[0].pat {
assert_eq!(id.name, "x");
} else {
panic!("expected Ident param");
}
assert!(matches!(&arrow.body, ArrowBody::Expr(_)));
} else {
panic!("expected Arrow, got {:?}", expr);
}
}
#[test]
fn test_arrow_multiple_params() {
let expr = parse_expr_stmt("(a, b) => a + b;");
if let Expr::Arrow(arrow) = &expr {
assert_eq!(arrow.params.len(), 2);
if let Pat::Ident(id) = &arrow.params[0].pat {
assert_eq!(id.name, "a");
} else {
panic!("expected Ident param 0");
}
if let Pat::Ident(id) = &arrow.params[1].pat {
assert_eq!(id.name, "b");
} else {
panic!("expected Ident param 1");
}
assert!(matches!(&arrow.body, ArrowBody::Expr(_)));
} else {
panic!("expected Arrow, got {:?}", expr);
}
}
#[test]
fn test_arrow_block_body() {
let expr = parse_expr_stmt("(x) => { return x; };");
if let Expr::Arrow(arrow) = &expr {
assert_eq!(arrow.params.len(), 1);
if let ArrowBody::Block(block) = &arrow.body {
assert_eq!(block.body.len(), 1);
assert!(matches!(&block.body[0], Stmt::Return(_)));
} else {
panic!("expected Block body");
}
} else {
panic!("expected Arrow, got {:?}", expr);
}
}
#[test]
fn test_arrow_default_param() {
let expr = parse_expr_stmt("(x = 10) => x;");
if let Expr::Arrow(arrow) = &expr {
assert_eq!(arrow.params.len(), 1);
assert!(arrow.params[0].default.is_some());
if let Pat::Ident(id) = &arrow.params[0].pat {
assert_eq!(id.name, "x");
} else {
panic!("expected Ident param");
}
} else {
panic!("expected Arrow, got {:?}", expr);
}
}
#[test]
fn test_arrow_in_var_decl() {
let prog = parse("var add = (a, b) => a + b;").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
if let Some(init) = &vd.declarators[0].init {
assert!(matches!(init.as_ref(), Expr::Arrow(_)));
} else {
panic!("expected init");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_arrow_no_params_block_body() {
let expr = parse_expr_stmt("() => { return 1; };");
if let Expr::Arrow(arrow) = &expr {
assert!(arrow.params.is_empty());
assert!(matches!(&arrow.body, ArrowBody::Block(_)));
} else {
panic!("expected Arrow, got {:?}", expr);
}
}
#[test]
fn test_parenthesised_expr_not_arrow() {
let expr = parse_expr_stmt("(1 + 2);");
assert!(matches!(&expr, Expr::Binary(_)));
}
#[test]
fn test_for_in_var() {
let prog = parse("for (var x in obj) {}").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::ForIn(fi)) = &prog.body[0] {
assert!(matches!(
&fi.left,
crate::parser::ast::ForInOfLeft::VarDecl(_)
));
} else {
panic!("expected ForIn, got {:?}", prog.body[0]);
}
}
#[test]
fn test_for_of_var() {
let prog = parse("for (var x of arr) {}").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::ForOf(fo)) = &prog.body[0] {
assert!(!fo.is_await);
assert!(matches!(
&fo.left,
crate::parser::ast::ForInOfLeft::VarDecl(_)
));
} else {
panic!("expected ForOf, got {:?}", prog.body[0]);
}
}
#[test]
fn test_for_of_let() {
let prog = parse("for (let x of arr) {}").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::ForOf(fo)) = &prog.body[0] {
if let crate::parser::ast::ForInOfLeft::VarDecl(vd) = &fo.left {
assert_eq!(vd.kind, crate::parser::ast::VarKind::Let);
} else {
panic!("expected VarDecl left");
}
} else {
panic!("expected ForOf, got {:?}", prog.body[0]);
}
}
#[test]
fn test_for_in_const() {
let prog = parse("for (const k in obj) {}").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::ForIn(fi)) = &prog.body[0] {
if let crate::parser::ast::ForInOfLeft::VarDecl(vd) = &fi.left {
assert_eq!(vd.kind, crate::parser::ast::VarKind::Const);
} else {
panic!("expected VarDecl left");
}
} else {
panic!("expected ForIn, got {:?}", prog.body[0]);
}
}
#[test]
fn test_for_in_existing_variable() {
let prog = parse("for (x in obj) {}").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::ForIn(fi)) = &prog.body[0] {
assert!(matches!(&fi.left, crate::parser::ast::ForInOfLeft::Pat(_)));
} else {
panic!("expected ForIn, got {:?}", prog.body[0]);
}
}
#[test]
fn test_for_of_existing_variable() {
let prog = parse("for (x of arr) {}").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::ForOf(fo)) = &prog.body[0] {
assert!(matches!(&fo.left, crate::parser::ast::ForInOfLeft::Pat(_)));
} else {
panic!("expected ForOf, got {:?}", prog.body[0]);
}
}
#[test]
fn test_c_style_for_still_works() {
let prog = parse("for (var i = 0; i < 10; i = i + 1) {}").unwrap();
assert_eq!(prog.body.len(), 1);
assert!(matches!(prog.body[0], ProgramItem::Stmt(Stmt::For(_))));
}
#[test]
fn test_c_style_for_empty_init() {
let prog = parse("for (;;) {}").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::For(f)) = &prog.body[0] {
assert!(f.init.is_none());
assert!(f.test.is_none());
assert!(f.update.is_none());
} else {
panic!("expected For, got {:?}", prog.body[0]);
}
}
#[test]
fn test_c_style_for_expr_init() {
let prog = parse("for (i = 0; i < 10; i = i + 1) {}").unwrap();
assert_eq!(prog.body.len(), 1);
assert!(matches!(prog.body[0], ProgramItem::Stmt(Stmt::For(_))));
}
#[test]
fn test_for_in_with_body() {
let prog = parse("for (var key in obj) { x = key; }").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::ForIn(fi)) = &prog.body[0] {
assert!(matches!(fi.body.as_ref(), Stmt::Block(_)));
} else {
panic!("expected ForIn");
}
}
#[test]
fn test_for_of_with_body() {
let prog = parse("for (let item of items) { process(item); }").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::ForOf(fo)) = &prog.body[0] {
assert!(matches!(fo.body.as_ref(), Stmt::Block(_)));
} else {
panic!("expected ForOf");
}
}
#[test]
fn test_class_decl_empty() {
let prog = parse("class Foo {}").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::ClassDecl(c)) = &prog.body[0] {
assert_eq!(c.id.as_ref().unwrap().name, "Foo");
assert!(c.super_class.is_none());
assert!(c.body.body.is_empty());
} else {
panic!("expected ClassDecl, got {:?}", prog.body[0]);
}
}
#[test]
fn test_class_decl_with_constructor() {
let prog = parse("class Foo { constructor(x) { this.x = x; } }").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::ClassDecl(c)) = &prog.body[0] {
assert_eq!(c.body.body.len(), 1);
if let crate::parser::ast::ClassMember::Method(m) = &c.body.body[0] {
assert!(matches!(
m.kind,
crate::parser::ast::MethodKind::Constructor
));
assert!(!m.is_static);
} else {
panic!("expected Method");
}
} else {
panic!("expected ClassDecl");
}
}
#[test]
fn test_class_decl_with_method() {
let prog = parse("class Foo { greet(name) { return name; } }").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::ClassDecl(c)) = &prog.body[0] {
assert_eq!(c.body.body.len(), 1);
if let crate::parser::ast::ClassMember::Method(m) = &c.body.body[0] {
assert!(matches!(m.kind, crate::parser::ast::MethodKind::Method));
assert!(!m.is_static);
if let crate::parser::ast::PropKey::Ident(ref id) = m.key {
assert_eq!(id.name, "greet");
} else {
panic!("expected Ident key");
}
} else {
panic!("expected Method");
}
} else {
panic!("expected ClassDecl");
}
}
#[test]
fn test_class_decl_static_method() {
let prog = parse("class Foo { static create() { return 1; } }").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::ClassDecl(c)) = &prog.body[0] {
assert_eq!(c.body.body.len(), 1);
if let crate::parser::ast::ClassMember::Method(m) = &c.body.body[0] {
assert!(m.is_static);
assert!(matches!(m.kind, crate::parser::ast::MethodKind::Method));
} else {
panic!("expected Method");
}
} else {
panic!("expected ClassDecl");
}
}
#[test]
fn test_class_decl_extends() {
let prog = parse("class Bar extends Foo { constructor() {} }").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::ClassDecl(c)) = &prog.body[0] {
assert_eq!(c.id.as_ref().unwrap().name, "Bar");
assert!(c.super_class.is_some());
assert_eq!(c.body.body.len(), 1);
} else {
panic!("expected ClassDecl");
}
}
#[test]
fn test_class_decl_getter_setter() {
let prog =
parse("class Foo { get value() { return 1; } set value(v) { this.v = v; } }").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::ClassDecl(c)) = &prog.body[0] {
assert_eq!(c.body.body.len(), 2);
if let crate::parser::ast::ClassMember::Method(m) = &c.body.body[0] {
assert!(matches!(m.kind, crate::parser::ast::MethodKind::Get));
} else {
panic!("expected getter");
}
if let crate::parser::ast::ClassMember::Method(m) = &c.body.body[1] {
assert!(matches!(m.kind, crate::parser::ast::MethodKind::Set));
} else {
panic!("expected setter");
}
} else {
panic!("expected ClassDecl");
}
}
#[test]
fn test_class_expr_anonymous() {
let prog = parse("var x = class {};").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
let init = vd.declarators[0].init.as_ref().unwrap();
assert!(matches!(init.as_ref(), Expr::Class(_)));
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_class_expr_named() {
let prog = parse("var x = class MyClass { foo() {} };").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
let init = vd.declarators[0].init.as_ref().unwrap();
if let Expr::Class(c) = init.as_ref() {
assert_eq!(c.id.as_ref().unwrap().name, "MyClass");
assert_eq!(c.body.body.len(), 1);
} else {
panic!("expected Class expr");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_class_multiple_members() {
let prog = parse(
"class Animal {
constructor(name) { this.name = name; }
speak() { return this.name; }
static create(name) { return 1; }
}",
)
.unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::ClassDecl(c)) = &prog.body[0] {
assert_eq!(c.body.body.len(), 3);
} else {
panic!("expected ClassDecl");
}
}
#[test]
fn test_class_semicolons_in_body() {
let prog = parse("class Foo { ; foo() {} ; bar() {} ; }").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::ClassDecl(c)) = &prog.body[0] {
assert_eq!(c.body.body.len(), 2);
} else {
panic!("expected ClassDecl");
}
}
#[test]
fn test_class_method_named_get() {
let prog = parse("class Foo { get() { return 1; } }").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::ClassDecl(c)) = &prog.body[0] {
assert_eq!(c.body.body.len(), 1);
if let crate::parser::ast::ClassMember::Method(m) = &c.body.body[0] {
assert!(matches!(m.kind, crate::parser::ast::MethodKind::Method));
if let crate::parser::ast::PropKey::Ident(ref id) = m.key {
assert_eq!(id.name, "get");
} else {
panic!("expected Ident key");
}
} else {
panic!("expected Method");
}
} else {
panic!("expected ClassDecl");
}
}
#[test]
#[ignore] fn test_class_computed_method() {
let prog = parse("class Foo { [Symbol.iterator]() {} }").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::ClassDecl(c)) = &prog.body[0] {
assert_eq!(c.body.body.len(), 1);
if let crate::parser::ast::ClassMember::Method(m) = &c.body.body[0] {
assert!(m.is_computed);
} else {
panic!("expected Method");
}
} else {
panic!("expected ClassDecl");
}
}
#[test]
fn test_template_no_substitution() {
let expr = parse_expr_stmt("`hello world`");
if let Expr::Template(tpl) = &expr {
assert_eq!(tpl.quasis.len(), 1);
assert!(tpl.expressions.is_empty());
assert_eq!(tpl.quasis[0].raw, "hello world");
assert_eq!(tpl.quasis[0].cooked.as_deref(), Some("hello world"));
assert!(tpl.quasis[0].tail);
} else {
panic!("expected Template, got {expr:?}");
}
}
#[test]
fn test_template_empty() {
let expr = parse_expr_stmt("``");
if let Expr::Template(tpl) = &expr {
assert_eq!(tpl.quasis.len(), 1);
assert!(tpl.expressions.is_empty());
assert_eq!(tpl.quasis[0].raw, "");
assert!(tpl.quasis[0].tail);
} else {
panic!("expected Template, got {expr:?}");
}
}
#[test]
fn test_template_single_substitution() {
let expr = parse_expr_stmt("`hello ${name}!`");
if let Expr::Template(tpl) = &expr {
assert_eq!(tpl.quasis.len(), 2);
assert_eq!(tpl.expressions.len(), 1);
assert_eq!(tpl.quasis[0].raw, "hello ");
assert!(!tpl.quasis[0].tail);
assert_eq!(tpl.quasis[1].raw, "!");
assert!(tpl.quasis[1].tail);
if let Expr::Ident(id) = &tpl.expressions[0] {
assert_eq!(id.name, "name");
} else {
panic!("expected Ident expression");
}
} else {
panic!("expected Template, got {expr:?}");
}
}
#[test]
fn test_template_multiple_substitutions() {
let expr = parse_expr_stmt("`${a} + ${b} = ${c}`");
if let Expr::Template(tpl) = &expr {
assert_eq!(tpl.quasis.len(), 4);
assert_eq!(tpl.expressions.len(), 3);
assert_eq!(tpl.quasis[0].raw, "");
assert_eq!(tpl.quasis[1].raw, " + ");
assert_eq!(tpl.quasis[2].raw, " = ");
assert_eq!(tpl.quasis[3].raw, "");
assert!(!tpl.quasis[0].tail);
assert!(!tpl.quasis[1].tail);
assert!(!tpl.quasis[2].tail);
assert!(tpl.quasis[3].tail);
} else {
panic!("expected Template, got {expr:?}");
}
}
#[test]
fn test_template_expression_with_binary() {
let expr = parse_expr_stmt("`result: ${1 + 2}`");
if let Expr::Template(tpl) = &expr {
assert_eq!(tpl.quasis.len(), 2);
assert_eq!(tpl.expressions.len(), 1);
assert_eq!(tpl.quasis[0].raw, "result: ");
assert_eq!(tpl.quasis[1].raw, "");
assert!(matches!(&tpl.expressions[0], Expr::Binary(_)));
} else {
panic!("expected Template, got {expr:?}");
}
}
#[test]
fn test_template_nested() {
let expr = parse_expr_stmt("`outer ${`inner ${x}`} end`");
if let Expr::Template(tpl) = &expr {
assert_eq!(tpl.quasis.len(), 2);
assert_eq!(tpl.expressions.len(), 1);
assert_eq!(tpl.quasis[0].raw, "outer ");
assert_eq!(tpl.quasis[1].raw, " end");
if let Expr::Template(inner) = &tpl.expressions[0] {
assert_eq!(inner.quasis.len(), 2);
assert_eq!(inner.expressions.len(), 1);
assert_eq!(inner.quasis[0].raw, "inner ");
assert_eq!(inner.quasis[1].raw, "");
} else {
panic!("expected nested Template");
}
} else {
panic!("expected Template, got {expr:?}");
}
}
#[test]
fn test_template_in_var_decl() {
let prog = parse("var x = `value: ${42}`;").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
let init = vd.declarators[0].init.as_ref().unwrap();
assert!(matches!(init.as_ref(), Expr::Template(_)));
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_regexp_simple() {
let expr = parse_expr_stmt("/abc/;");
if let Expr::Regexp(re) = &expr {
assert_eq!(re.pattern, "abc");
assert_eq!(re.flags, "");
} else {
panic!("expected Regexp, got {expr:?}");
}
}
#[test]
fn test_regexp_with_flags() {
let expr = parse_expr_stmt("/abc/gi;");
if let Expr::Regexp(re) = &expr {
assert_eq!(re.pattern, "abc");
assert_eq!(re.flags, "gi");
} else {
panic!("expected Regexp, got {expr:?}");
}
}
#[test]
fn test_regexp_in_var_decl() {
let prog = parse("var re = /test/;").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
let init = vd.declarators[0].init.as_ref().unwrap();
if let Expr::Regexp(re) = init.as_ref() {
assert_eq!(re.pattern, "test");
assert_eq!(re.flags, "");
} else {
panic!("expected Regexp, got {init:?}");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_regexp_with_char_class() {
let expr = parse_expr_stmt("/[a-z]+/g;");
if let Expr::Regexp(re) = &expr {
assert_eq!(re.pattern, "[a-z]+");
assert_eq!(re.flags, "g");
} else {
panic!("expected Regexp, got {expr:?}");
}
}
#[test]
fn test_regexp_with_escape() {
let expr = parse_expr_stmt(r"/foo\/bar/;");
if let Expr::Regexp(re) = &expr {
assert_eq!(re.pattern, r"foo\/bar");
assert_eq!(re.flags, "");
} else {
panic!("expected Regexp, got {expr:?}");
}
}
#[test]
fn test_regexp_slash_in_char_class() {
let expr = parse_expr_stmt("/[/]/;");
if let Expr::Regexp(re) = &expr {
assert_eq!(re.pattern, "[/]");
assert_eq!(re.flags, "");
} else {
panic!("expected Regexp, got {expr:?}");
}
}
#[test]
fn test_regexp_after_assignment() {
let prog = parse("var x = /pattern/i;").unwrap();
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
let init = vd.declarators[0].init.as_ref().unwrap();
assert!(matches!(init.as_ref(), Expr::Regexp(_)));
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_array_destructuring_simple() {
let prog = parse("let [a, b] = arr;").unwrap();
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
assert_eq!(vd.declarators.len(), 1);
let d = &vd.declarators[0];
if let Pat::Array(ap) = &d.id {
assert_eq!(ap.elements.len(), 2);
assert!(matches!(ap.elements[0], Some(Pat::Ident(_))));
assert!(matches!(ap.elements[1], Some(Pat::Ident(_))));
} else {
panic!("expected ArrayPat, got {:?}", d.id);
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_array_destructuring_elision() {
let prog = parse("let [a, , b] = arr;").unwrap();
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
let d = &vd.declarators[0];
if let Pat::Array(ap) = &d.id {
assert_eq!(ap.elements.len(), 3);
assert!(matches!(ap.elements[0], Some(Pat::Ident(_))));
assert!(ap.elements[1].is_none(), "expected elision (None)");
assert!(matches!(ap.elements[2], Some(Pat::Ident(_))));
} else {
panic!("expected ArrayPat, got {:?}", d.id);
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_array_destructuring_rest() {
let prog = parse("let [a, ...rest] = arr;").unwrap();
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
let d = &vd.declarators[0];
if let Pat::Array(ap) = &d.id {
assert_eq!(ap.elements.len(), 2);
assert!(matches!(ap.elements[0], Some(Pat::Ident(_))));
assert!(matches!(ap.elements[1], Some(Pat::Rest(_))));
} else {
panic!("expected ArrayPat, got {:?}", d.id);
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_array_destructuring_default() {
let prog = parse("let [a = 1] = arr;").unwrap();
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
let d = &vd.declarators[0];
if let Pat::Array(ap) = &d.id {
assert_eq!(ap.elements.len(), 1);
if let Some(Pat::Assign(assign)) = &ap.elements[0] {
assert!(matches!(assign.left.as_ref(), Pat::Ident(_)));
} else {
panic!("expected AssignPat element");
}
} else {
panic!("expected ArrayPat, got {:?}", d.id);
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_array_destructuring_nested() {
let prog = parse("let [a, [b, c]] = arr;").unwrap();
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
let d = &vd.declarators[0];
if let Pat::Array(ap) = &d.id {
assert_eq!(ap.elements.len(), 2);
assert!(matches!(ap.elements[0], Some(Pat::Ident(_))));
assert!(matches!(ap.elements[1], Some(Pat::Array(_))));
} else {
panic!("expected ArrayPat");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_array_destructuring_empty() {
let prog = parse("let [] = arr;").unwrap();
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
let d = &vd.declarators[0];
if let Pat::Array(ap) = &d.id {
assert!(ap.elements.is_empty());
} else {
panic!("expected ArrayPat");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_object_destructuring_simple() {
let prog = parse("let {a, b} = obj;").unwrap();
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
let d = &vd.declarators[0];
if let Pat::Object(op) = &d.id {
assert_eq!(op.properties.len(), 2);
assert!(matches!(op.properties[0], ObjectPatProp::Assign(_)));
assert!(matches!(op.properties[1], ObjectPatProp::Assign(_)));
} else {
panic!("expected ObjectPat, got {:?}", d.id);
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_object_destructuring_rename() {
let prog = parse("let {a: x} = obj;").unwrap();
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
let d = &vd.declarators[0];
if let Pat::Object(op) = &d.id {
assert_eq!(op.properties.len(), 1);
if let ObjectPatProp::KeyValue(kv) = &op.properties[0] {
if let PropKey::Ident(key) = &kv.key {
assert_eq!(key.name, "a");
} else {
panic!("expected ident key");
}
assert!(matches!(&kv.value, Pat::Ident(id) if id.name == "x"));
} else {
panic!("expected KeyValue");
}
} else {
panic!("expected ObjectPat");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_object_destructuring_default() {
let prog = parse("let {a = 1} = obj;").unwrap();
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
let d = &vd.declarators[0];
if let Pat::Object(op) = &d.id {
assert_eq!(op.properties.len(), 1);
if let ObjectPatProp::Assign(ap) = &op.properties[0] {
assert_eq!(ap.key.name, "a");
assert!(ap.value.is_some());
} else {
panic!("expected Assign prop");
}
} else {
panic!("expected ObjectPat");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_object_destructuring_rest() {
let prog = parse("let {...rest} = obj;").unwrap();
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
let d = &vd.declarators[0];
if let Pat::Object(op) = &d.id {
assert_eq!(op.properties.len(), 1);
assert!(matches!(op.properties[0], ObjectPatProp::Rest(_)));
} else {
panic!("expected ObjectPat");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_object_destructuring_nested() {
let prog = parse("let {a: {b}} = obj;").unwrap();
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
let d = &vd.declarators[0];
if let Pat::Object(op) = &d.id {
assert_eq!(op.properties.len(), 1);
if let ObjectPatProp::KeyValue(kv) = &op.properties[0] {
assert!(matches!(&kv.value, Pat::Object(_)));
} else {
panic!("expected KeyValue");
}
} else {
panic!("expected ObjectPat");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_object_destructuring_empty() {
let prog = parse("let {} = obj;").unwrap();
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
let d = &vd.declarators[0];
if let Pat::Object(op) = &d.id {
assert!(op.properties.is_empty());
} else {
panic!("expected ObjectPat");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_object_destructuring_rename_with_default() {
let prog = parse("let {a: x = 5} = obj;").unwrap();
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
let d = &vd.declarators[0];
if let Pat::Object(op) = &d.id {
assert_eq!(op.properties.len(), 1);
if let ObjectPatProp::KeyValue(kv) = &op.properties[0] {
assert!(matches!(&kv.value, Pat::Assign(_)));
} else {
panic!("expected KeyValue");
}
} else {
panic!("expected ObjectPat");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_function_param_array_destructuring() {
let prog = parse("function f([a, b]) {}").unwrap();
if let ProgramItem::Stmt(Stmt::FnDecl(fd)) = &prog.body[0] {
assert_eq!(fd.params.len(), 1);
assert!(matches!(&fd.params[0].pat, Pat::Array(_)));
} else {
panic!("expected FnDecl");
}
}
#[test]
fn test_function_param_object_destructuring() {
let prog = parse("function f({a, b}) {}").unwrap();
if let ProgramItem::Stmt(Stmt::FnDecl(fd)) = &prog.body[0] {
assert_eq!(fd.params.len(), 1);
assert!(matches!(&fd.params[0].pat, Pat::Object(_)));
} else {
panic!("expected FnDecl");
}
}
#[test]
fn test_function_param_default_value() {
let prog = parse("function f(a = 1) {}").unwrap();
if let ProgramItem::Stmt(Stmt::FnDecl(fd)) = &prog.body[0] {
assert_eq!(fd.params.len(), 1);
assert!(matches!(&fd.params[0].pat, Pat::Ident(_)));
assert!(fd.params[0].default.is_some());
} else {
panic!("expected FnDecl");
}
}
#[test]
fn test_function_param_rest() {
let prog = parse("function f(a, ...rest) {}").unwrap();
if let ProgramItem::Stmt(Stmt::FnDecl(fd)) = &prog.body[0] {
assert_eq!(fd.params.len(), 2);
assert!(matches!(&fd.params[0].pat, Pat::Ident(_)));
assert!(matches!(&fd.params[1].pat, Pat::Rest(_)));
} else {
panic!("expected FnDecl");
}
}
#[test]
fn test_function_param_destructuring_with_default() {
let prog = parse("function f({a = 1, b}) {}").unwrap();
if let ProgramItem::Stmt(Stmt::FnDecl(fd)) = &prog.body[0] {
assert_eq!(fd.params.len(), 1);
if let Pat::Object(op) = &fd.params[0].pat {
assert_eq!(op.properties.len(), 2);
} else {
panic!("expected ObjectPat param");
}
} else {
panic!("expected FnDecl");
}
}
#[test]
fn test_const_array_destructuring() {
let prog = parse("const [x, y, z] = [1, 2, 3];").unwrap();
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
assert_eq!(vd.kind, VarKind::Const);
let d = &vd.declarators[0];
if let Pat::Array(ap) = &d.id {
assert_eq!(ap.elements.len(), 3);
} else {
panic!("expected ArrayPat");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_var_object_destructuring() {
let prog = parse("var {x, y} = obj;").unwrap();
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
assert_eq!(vd.kind, VarKind::Var);
let d = &vd.declarators[0];
assert!(matches!(&d.id, Pat::Object(_)));
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_nested_mixed_destructuring() {
let prog = parse("let {a: [b, c]} = obj;").unwrap();
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
let d = &vd.declarators[0];
if let Pat::Object(op) = &d.id {
if let ObjectPatProp::KeyValue(kv) = &op.properties[0] {
assert!(matches!(&kv.value, Pat::Array(_)));
} else {
panic!("expected KeyValue");
}
} else {
panic!("expected ObjectPat");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_array_with_nested_object() {
let prog = parse("let [{a}, {b}] = arr;").unwrap();
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
let d = &vd.declarators[0];
if let Pat::Array(ap) = &d.id {
assert_eq!(ap.elements.len(), 2);
assert!(matches!(ap.elements[0], Some(Pat::Object(_))));
assert!(matches!(ap.elements[1], Some(Pat::Object(_))));
} else {
panic!("expected ArrayPat");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_complex_destructuring() {
let prog = parse("let {a, b: [c, ...d], ...e} = obj;").unwrap();
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
let d = &vd.declarators[0];
if let Pat::Object(op) = &d.id {
assert_eq!(op.properties.len(), 3);
assert!(matches!(&op.properties[0], ObjectPatProp::Assign(_)));
if let ObjectPatProp::KeyValue(kv) = &op.properties[1] {
assert!(matches!(&kv.value, Pat::Array(_)));
} else {
panic!("expected KeyValue for b");
}
assert!(matches!(&op.properties[2], ObjectPatProp::Rest(_)));
} else {
panic!("expected ObjectPat");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_for_of_array_destructuring() {
let prog = parse("for (let [a, b] of arr) {}").unwrap();
assert!(matches!(&prog.body[0], ProgramItem::Stmt(Stmt::ForOf(_))));
}
#[test]
fn test_for_in_object_destructuring() {
let prog = parse("for (let {a, b} in obj) {}").unwrap();
assert!(matches!(&prog.body[0], ProgramItem::Stmt(Stmt::ForIn(_))));
}
#[test]
fn test_catch_array_destructuring() {
let prog = parse("try {} catch ([a, b]) {}").unwrap();
if let ProgramItem::Stmt(Stmt::Try(ts)) = &prog.body[0] {
let handler = ts.handler.as_ref().unwrap();
assert!(matches!(&handler.param, Some(Pat::Array(_))));
} else {
panic!("expected TryStmt");
}
}
#[test]
fn test_catch_object_destructuring() {
let prog = parse("try {} catch ({message}) {}").unwrap();
if let ProgramItem::Stmt(Stmt::Try(ts)) = &prog.body[0] {
let handler = ts.handler.as_ref().unwrap();
assert!(matches!(&handler.param, Some(Pat::Object(_))));
} else {
panic!("expected TryStmt");
}
}
#[test]
fn test_spread_in_call_single() {
let prog = parse("f(...args);").unwrap();
if let ProgramItem::Stmt(Stmt::Expr(es)) = &prog.body[0] {
if let Expr::Call(c) = es.expr.as_ref() {
assert_eq!(c.arguments.len(), 1);
assert!(matches!(&c.arguments[0], Expr::Spread(_)));
} else {
panic!("expected Call expr, got {:?}", es.expr);
}
} else {
panic!("expected ExprStmt");
}
}
#[test]
fn test_spread_in_call_with_leading_arg() {
let prog = parse("f(a, ...args);").unwrap();
if let ProgramItem::Stmt(Stmt::Expr(es)) = &prog.body[0] {
if let Expr::Call(c) = es.expr.as_ref() {
assert_eq!(c.arguments.len(), 2);
assert!(matches!(&c.arguments[0], Expr::Ident(_)));
assert!(matches!(&c.arguments[1], Expr::Spread(_)));
} else {
panic!("expected Call expr, got {:?}", es.expr);
}
} else {
panic!("expected ExprStmt");
}
}
#[test]
fn test_spread_in_new_call() {
let prog = parse("new Foo(...args);").unwrap();
if let ProgramItem::Stmt(Stmt::Expr(es)) = &prog.body[0] {
if let Expr::New(n) = es.expr.as_ref() {
assert_eq!(n.arguments.len(), 1);
assert!(matches!(&n.arguments[0], Expr::Spread(_)));
} else {
panic!("expected New expr, got {:?}", es.expr);
}
} else {
panic!("expected ExprStmt");
}
}
#[test]
fn test_spread_in_object_literal() {
let prog = parse("var o = {...other};").unwrap();
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
if let Some(init) = &vd.declarators[0].init {
if let Expr::Object(obj) = init.as_ref() {
assert_eq!(obj.properties.len(), 1);
assert!(matches!(&obj.properties[0], ObjectProp::Spread(_)));
} else {
panic!("expected Object expr");
}
} else {
panic!("expected init");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_spread_in_object_literal_with_leading_prop() {
let prog = parse("var o = {a: 1, ...other};").unwrap();
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
if let Some(init) = &vd.declarators[0].init {
if let Expr::Object(obj) = init.as_ref() {
assert_eq!(obj.properties.len(), 2);
assert!(matches!(&obj.properties[0], ObjectProp::Prop(_)));
assert!(matches!(&obj.properties[1], ObjectProp::Spread(_)));
} else {
panic!("expected Object expr");
}
} else {
panic!("expected init");
}
} else {
panic!("expected VarDecl");
}
}
use crate::parser::ast::{
ExportDefaultExpr, ImportSpecifier, ModuleDecl, ModuleExportName, SourceType,
};
#[test]
fn test_import_default() {
let prog = parse("import x from \"module\";").unwrap();
assert_eq!(prog.source_type, SourceType::Module);
assert_eq!(prog.body.len(), 1);
if let ProgramItem::ModuleDecl(ModuleDecl::Import(decl)) = &prog.body[0] {
assert_eq!(decl.specifiers.len(), 1);
assert!(matches!(&decl.specifiers[0], ImportSpecifier::Default(_)));
if let ImportSpecifier::Default(d) = &decl.specifiers[0] {
assert_eq!(d.local.name, "x");
}
assert_eq!(decl.source.value, "\"module\"");
} else {
panic!("expected ImportDecl, got {:?}", prog.body[0]);
}
}
#[test]
fn test_import_named_single() {
let prog = parse("import { a } from \"mod\";").unwrap();
assert_eq!(prog.source_type, SourceType::Module);
if let ProgramItem::ModuleDecl(ModuleDecl::Import(decl)) = &prog.body[0] {
assert_eq!(decl.specifiers.len(), 1);
if let ImportSpecifier::Named(n) = &decl.specifiers[0] {
assert_eq!(n.local.name, "a");
if let ModuleExportName::Ident(id) = &n.imported {
assert_eq!(id.name, "a");
} else {
panic!("expected Ident imported name");
}
} else {
panic!("expected Named specifier");
}
assert_eq!(decl.source.value, "\"mod\"");
} else {
panic!("expected ImportDecl");
}
}
#[test]
fn test_import_named_multiple() {
let prog = parse("import { a, b } from \"mod\";").unwrap();
if let ProgramItem::ModuleDecl(ModuleDecl::Import(decl)) = &prog.body[0] {
assert_eq!(decl.specifiers.len(), 2);
assert!(matches!(&decl.specifiers[0], ImportSpecifier::Named(_)));
assert!(matches!(&decl.specifiers[1], ImportSpecifier::Named(_)));
} else {
panic!("expected ImportDecl");
}
}
#[test]
fn test_import_named_aliased() {
let prog = parse("import { a as alias } from \"mod\";").unwrap();
if let ProgramItem::ModuleDecl(ModuleDecl::Import(decl)) = &prog.body[0] {
assert_eq!(decl.specifiers.len(), 1);
if let ImportSpecifier::Named(n) = &decl.specifiers[0] {
if let ModuleExportName::Ident(id) = &n.imported {
assert_eq!(id.name, "a");
} else {
panic!("expected Ident imported");
}
assert_eq!(n.local.name, "alias");
} else {
panic!("expected Named specifier");
}
} else {
panic!("expected ImportDecl");
}
}
#[test]
fn test_import_namespace() {
let prog = parse("import * as mod_ns from \"mod\";").unwrap();
if let ProgramItem::ModuleDecl(ModuleDecl::Import(decl)) = &prog.body[0] {
assert_eq!(decl.specifiers.len(), 1);
if let ImportSpecifier::Namespace(ns) = &decl.specifiers[0] {
assert_eq!(ns.local.name, "mod_ns");
} else {
panic!("expected Namespace specifier");
}
assert_eq!(decl.source.value, "\"mod\"");
} else {
panic!("expected ImportDecl");
}
}
#[test]
fn test_import_side_effect() {
let prog = parse("import \"side-effect\";").unwrap();
assert_eq!(prog.source_type, SourceType::Module);
if let ProgramItem::ModuleDecl(ModuleDecl::Import(decl)) = &prog.body[0] {
assert!(decl.specifiers.is_empty());
assert_eq!(decl.source.value, "\"side-effect\"");
} else {
panic!("expected ImportDecl");
}
}
#[test]
fn test_import_default_and_named() {
let prog = parse("import x, { a, b } from \"mod\";").unwrap();
if let ProgramItem::ModuleDecl(ModuleDecl::Import(decl)) = &prog.body[0] {
assert_eq!(decl.specifiers.len(), 3);
assert!(matches!(&decl.specifiers[0], ImportSpecifier::Default(_)));
assert!(matches!(&decl.specifiers[1], ImportSpecifier::Named(_)));
assert!(matches!(&decl.specifiers[2], ImportSpecifier::Named(_)));
} else {
panic!("expected ImportDecl");
}
}
#[test]
fn test_import_default_and_namespace() {
let prog = parse("import x, * as ns from \"mod\";").unwrap();
if let ProgramItem::ModuleDecl(ModuleDecl::Import(decl)) = &prog.body[0] {
assert_eq!(decl.specifiers.len(), 2);
assert!(matches!(&decl.specifiers[0], ImportSpecifier::Default(_)));
assert!(matches!(&decl.specifiers[1], ImportSpecifier::Namespace(_)));
} else {
panic!("expected ImportDecl");
}
}
#[test]
fn test_import_named_trailing_comma() {
let prog = parse("import { a, b, } from \"mod\";").unwrap();
if let ProgramItem::ModuleDecl(ModuleDecl::Import(decl)) = &prog.body[0] {
assert_eq!(decl.specifiers.len(), 2);
} else {
panic!("expected ImportDecl");
}
}
#[test]
fn test_export_default_expr() {
let prog = parse("export default 42;").unwrap();
assert_eq!(prog.source_type, SourceType::Module);
if let ProgramItem::ModuleDecl(ModuleDecl::ExportDefault(decl)) = &prog.body[0] {
assert!(matches!(&decl.declaration, ExportDefaultExpr::Expr(_)));
} else {
panic!("expected ExportDefault, got {:?}", prog.body[0]);
}
}
#[test]
fn test_export_default_function() {
let prog = parse("export default function foo() {}").unwrap();
if let ProgramItem::ModuleDecl(ModuleDecl::ExportDefault(decl)) = &prog.body[0] {
if let ExportDefaultExpr::Fn(fd) = &decl.declaration {
assert_eq!(fd.id.as_ref().unwrap().name, "foo");
} else {
panic!("expected Fn default export");
}
} else {
panic!("expected ExportDefault");
}
}
#[test]
fn test_export_default_anonymous_function() {
let prog = parse("export default function() {}").unwrap();
if let ProgramItem::ModuleDecl(ModuleDecl::ExportDefault(decl)) = &prog.body[0] {
if let ExportDefaultExpr::Fn(fd) = &decl.declaration {
assert!(fd.id.is_none());
} else {
panic!("expected Fn default export");
}
} else {
panic!("expected ExportDefault");
}
}
#[test]
fn test_export_default_class() {
let prog = parse("export default class Foo {}").unwrap();
if let ProgramItem::ModuleDecl(ModuleDecl::ExportDefault(decl)) = &prog.body[0] {
if let ExportDefaultExpr::Class(cd) = &decl.declaration {
assert_eq!(cd.id.as_ref().unwrap().name, "Foo");
} else {
panic!("expected Class default export");
}
} else {
panic!("expected ExportDefault");
}
}
#[test]
fn test_export_function_decl() {
let prog = parse("export function add(a, b) { return a + b; }").unwrap();
if let ProgramItem::ModuleDecl(ModuleDecl::ExportNamed(decl)) = &prog.body[0] {
assert!(decl.specifiers.is_empty());
assert!(decl.source.is_none());
if let Some(stmt) = &decl.declaration {
assert!(matches!(stmt.as_ref(), Stmt::FnDecl(_)));
} else {
panic!("expected declaration");
}
} else {
panic!("expected ExportNamed, got {:?}", prog.body[0]);
}
}
#[test]
fn test_export_class_decl() {
let prog = parse("export class Foo {}").unwrap();
if let ProgramItem::ModuleDecl(ModuleDecl::ExportNamed(decl)) = &prog.body[0] {
assert!(decl.declaration.is_some());
if let Some(stmt) = &decl.declaration {
assert!(matches!(stmt.as_ref(), Stmt::ClassDecl(_)));
}
} else {
panic!("expected ExportNamed");
}
}
#[test]
fn test_export_const() {
let prog = parse("export const x = 1;").unwrap();
if let ProgramItem::ModuleDecl(ModuleDecl::ExportNamed(decl)) = &prog.body[0] {
if let Some(stmt) = &decl.declaration {
if let Stmt::VarDecl(vd) = stmt.as_ref() {
assert_eq!(vd.kind, VarKind::Const);
} else {
panic!("expected VarDecl");
}
} else {
panic!("expected declaration");
}
} else {
panic!("expected ExportNamed");
}
}
#[test]
fn test_export_let() {
let prog = parse("export let y = 2;").unwrap();
if let ProgramItem::ModuleDecl(ModuleDecl::ExportNamed(decl)) = &prog.body[0] {
if let Some(stmt) = &decl.declaration {
if let Stmt::VarDecl(vd) = stmt.as_ref() {
assert_eq!(vd.kind, VarKind::Let);
} else {
panic!("expected VarDecl");
}
} else {
panic!("expected declaration");
}
} else {
panic!("expected ExportNamed");
}
}
#[test]
fn test_export_var() {
let prog = parse("export var z = 3;").unwrap();
if let ProgramItem::ModuleDecl(ModuleDecl::ExportNamed(decl)) = &prog.body[0] {
if let Some(stmt) = &decl.declaration {
if let Stmt::VarDecl(vd) = stmt.as_ref() {
assert_eq!(vd.kind, VarKind::Var);
} else {
panic!("expected VarDecl");
}
}
} else {
panic!("expected ExportNamed");
}
}
#[test]
fn test_export_named_specifiers() {
let prog = parse("export { name1, name2 };").unwrap();
if let ProgramItem::ModuleDecl(ModuleDecl::ExportNamed(decl)) = &prog.body[0] {
assert_eq!(decl.specifiers.len(), 2);
assert!(decl.source.is_none());
assert!(decl.declaration.is_none());
if let ModuleExportName::Ident(id) = &decl.specifiers[0].local {
assert_eq!(id.name, "name1");
} else {
panic!("expected Ident");
}
} else {
panic!("expected ExportNamed");
}
}
#[test]
fn test_export_named_aliased() {
let prog = parse("export { name as alias };").unwrap();
if let ProgramItem::ModuleDecl(ModuleDecl::ExportNamed(decl)) = &prog.body[0] {
assert_eq!(decl.specifiers.len(), 1);
if let ModuleExportName::Ident(local) = &decl.specifiers[0].local {
assert_eq!(local.name, "name");
}
if let ModuleExportName::Ident(exp) = &decl.specifiers[0].exported {
assert_eq!(exp.name, "alias");
}
} else {
panic!("expected ExportNamed");
}
}
#[test]
fn test_export_named_as_default() {
let prog = parse("export { foo as default };").unwrap();
if let ProgramItem::ModuleDecl(ModuleDecl::ExportNamed(decl)) = &prog.body[0] {
assert_eq!(decl.specifiers.len(), 1);
if let ModuleExportName::Ident(exp) = &decl.specifiers[0].exported {
assert_eq!(exp.name, "default");
}
} else {
panic!("expected ExportNamed");
}
}
#[test]
fn test_export_named_re_export() {
let prog = parse("export { a, b } from \"other\";").unwrap();
if let ProgramItem::ModuleDecl(ModuleDecl::ExportNamed(decl)) = &prog.body[0] {
assert_eq!(decl.specifiers.len(), 2);
assert_eq!(decl.source.as_ref().unwrap().value, "\"other\"");
} else {
panic!("expected ExportNamed");
}
}
#[test]
fn test_export_all() {
let prog = parse("export * from \"other\";").unwrap();
if let ProgramItem::ModuleDecl(ModuleDecl::ExportAll(decl)) = &prog.body[0] {
assert!(decl.exported.is_none());
assert_eq!(decl.source.value, "\"other\"");
} else {
panic!("expected ExportAll, got {:?}", prog.body[0]);
}
}
#[test]
fn test_export_all_as_namespace() {
let prog = parse("export * as ns from \"other\";").unwrap();
if let ProgramItem::ModuleDecl(ModuleDecl::ExportAll(decl)) = &prog.body[0] {
if let Some(ModuleExportName::Ident(id)) = &decl.exported {
assert_eq!(id.name, "ns");
} else {
panic!("expected namespace alias");
}
assert_eq!(decl.source.value, "\"other\"");
} else {
panic!("expected ExportAll");
}
}
#[test]
fn test_export_named_trailing_comma() {
let prog = parse("export { a, b, };").unwrap();
if let ProgramItem::ModuleDecl(ModuleDecl::ExportNamed(decl)) = &prog.body[0] {
assert_eq!(decl.specifiers.len(), 2);
} else {
panic!("expected ExportNamed");
}
}
#[test]
fn test_mixed_imports_and_statements() {
let prog = parse("import x from \"mod\";\r\nvar a = 1;\r\nexport const b = 2;").unwrap();
assert_eq!(prog.source_type, SourceType::Module);
assert_eq!(prog.body.len(), 3);
assert!(matches!(
&prog.body[0],
ProgramItem::ModuleDecl(ModuleDecl::Import(_))
));
assert!(matches!(&prog.body[1], ProgramItem::Stmt(Stmt::VarDecl(_))));
assert!(matches!(
&prog.body[2],
ProgramItem::ModuleDecl(ModuleDecl::ExportNamed(_))
));
}
#[test]
fn test_script_has_no_module_type() {
let prog = parse("var x = 1;").unwrap();
assert_eq!(prog.source_type, SourceType::Script);
}
#[test]
fn test_async_function_declaration() {
let prog = parse("async function fetchData() { return 1; }").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::FnDecl(fd)) = &prog.body[0] {
assert!(fd.is_async);
assert!(!fd.is_generator);
assert_eq!(fd.id.as_ref().unwrap().name, "fetchData");
} else {
panic!("expected async FnDecl, got {:?}", prog.body[0]);
}
}
#[test]
fn test_async_function_expression() {
let prog = parse("var f = async function() { return 1; };").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
if let Some(init) = &vd.declarators[0].init {
if let Expr::Fn(fe) = init.as_ref() {
assert!(fe.is_async);
assert!(fe.id.is_none());
} else {
panic!("expected Fn expr, got {:?}", init);
}
} else {
panic!("expected init");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_async_named_function_expression() {
let prog = parse("var f = async function myFn() { };").unwrap();
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
if let Some(Expr::Fn(fe)) = vd.declarators[0].init.as_deref() {
assert!(fe.is_async);
assert_eq!(fe.id.as_ref().unwrap().name, "myFn");
} else {
panic!("expected async function expression");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_async_arrow_no_params() {
let prog = parse("var f = async () => 42;").unwrap();
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
if let Some(Expr::Arrow(arrow)) = vd.declarators[0].init.as_deref() {
assert!(arrow.is_async);
assert!(arrow.params.is_empty());
} else {
panic!("expected async arrow expression");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_async_arrow_single_param() {
let prog = parse("var f = async x => x + 1;").unwrap();
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
if let Some(Expr::Arrow(arrow)) = vd.declarators[0].init.as_deref() {
assert!(arrow.is_async);
assert_eq!(arrow.params.len(), 1);
if let Pat::Ident(ref id) = arrow.params[0].pat {
assert_eq!(id.name, "x");
} else {
panic!("expected ident param");
}
} else {
panic!("expected async arrow expression");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_async_arrow_multi_params() {
let prog = parse("var f = async (a, b) => a + b;").unwrap();
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
if let Some(Expr::Arrow(arrow)) = vd.declarators[0].init.as_deref() {
assert!(arrow.is_async);
assert_eq!(arrow.params.len(), 2);
} else {
panic!("expected async arrow expression");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_async_arrow_block_body() {
let prog = parse("var f = async () => { return 1; };").unwrap();
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
if let Some(Expr::Arrow(arrow)) = vd.declarators[0].init.as_deref() {
assert!(arrow.is_async);
assert!(matches!(arrow.body, ArrowBody::Block(_)));
} else {
panic!("expected async arrow expression");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_await_expression() {
let prog = parse("async function f() { var x = await promise; }").unwrap();
if let ProgramItem::Stmt(Stmt::FnDecl(fd)) = &prog.body[0] {
assert!(fd.is_async);
if let Stmt::VarDecl(vd) = &fd.body.body[0] {
if let Some(Expr::Await(await_expr)) = vd.declarators[0].init.as_deref() {
if let Expr::Ident(ref id) = *await_expr.argument {
assert_eq!(id.name, "promise");
} else {
panic!("expected ident in await argument");
}
} else {
panic!("expected Await expr in var init");
}
} else {
panic!("expected VarDecl in function body");
}
} else {
panic!("expected async FnDecl");
}
}
#[test]
fn test_await_call_expression() {
let prog = parse("async function f() { await fetch(); }").unwrap();
if let ProgramItem::Stmt(Stmt::FnDecl(fd)) = &prog.body[0] {
assert!(fd.is_async);
if let Stmt::Expr(es) = &fd.body.body[0] {
assert!(matches!(*es.expr, Expr::Await(_)));
} else {
panic!("expected ExprStmt");
}
} else {
panic!("expected async FnDecl");
}
}
#[test]
fn test_async_class_method() {
let prog = parse("class Foo { async fetchData() { return 1; } }").unwrap();
if let ProgramItem::Stmt(Stmt::ClassDecl(cd)) = &prog.body[0] {
if let ClassMember::Method(md) = &cd.body.body[0] {
assert!(md.value.is_async);
assert_eq!(md.kind, MethodKind::Method);
if let PropKey::Ident(ref id) = md.key {
assert_eq!(id.name, "fetchData");
} else {
panic!("expected ident key");
}
} else {
panic!("expected Method");
}
} else {
panic!("expected ClassDecl");
}
}
#[test]
fn test_async_static_class_method() {
let prog = parse("class Foo { static async bar() { } }").unwrap();
if let ProgramItem::Stmt(Stmt::ClassDecl(cd)) = &prog.body[0] {
if let ClassMember::Method(md) = &cd.body.body[0] {
assert!(md.is_static);
assert!(md.value.is_async);
} else {
panic!("expected Method");
}
} else {
panic!("expected ClassDecl");
}
}
#[test]
fn test_async_object_method() {
let prog = parse("var o = { async fetch() { return 1; } };").unwrap();
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
if let Some(Expr::Object(obj)) = vd.declarators[0].init.as_deref() {
if let ObjectProp::Prop(p) = &obj.properties[0] {
if let PropValue::Method(ref fe) = p.value {
assert!(fe.is_async);
} else {
panic!("expected Method PropValue, got {:?}", p.value);
}
if let PropKey::Ident(ref id) = p.key {
assert_eq!(id.name, "fetch");
}
} else {
panic!("expected Prop");
}
} else {
panic!("expected Object expr");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_object_method_shorthand() {
let prog = parse("var o = { greet() { return 1; } };").unwrap();
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
if let Some(Expr::Object(obj)) = vd.declarators[0].init.as_deref() {
if let ObjectProp::Prop(p) = &obj.properties[0] {
assert!(matches!(p.value, PropValue::Method(_)));
if let PropKey::Ident(ref id) = p.key {
assert_eq!(id.name, "greet");
}
} else {
panic!("expected Prop");
}
} else {
panic!("expected Object expr");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_export_async_function() {
let prog = parse("export async function fetchData() { }").unwrap();
if let ProgramItem::ModuleDecl(ModuleDecl::ExportNamed(decl)) = &prog.body[0] {
if let Some(decl_stmt) = &decl.declaration {
if let Stmt::FnDecl(fd) = decl_stmt.as_ref() {
assert!(fd.is_async);
assert_eq!(fd.id.as_ref().unwrap().name, "fetchData");
} else {
panic!("expected FnDecl");
}
} else {
panic!("expected declaration");
}
} else {
panic!("expected ExportNamed");
}
}
#[test]
fn test_export_default_async_function() {
let prog = parse("export default async function fetchData() { }").unwrap();
if let ProgramItem::ModuleDecl(ModuleDecl::ExportDefault(decl)) = &prog.body[0] {
if let ExportDefaultExpr::Fn(fd) = &decl.declaration {
assert!(fd.is_async);
} else {
panic!("expected Fn in export default");
}
} else {
panic!("expected ExportDefault");
}
}
#[test]
fn test_non_async_function_still_works() {
let prog = parse("function f() { return 1; }").unwrap();
if let ProgramItem::Stmt(Stmt::FnDecl(fd)) = &prog.body[0] {
assert!(!fd.is_async);
} else {
panic!("expected FnDecl");
}
}
#[test]
fn test_non_async_arrow_still_works() {
let prog = parse("var f = () => 42;").unwrap();
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
if let Some(Expr::Arrow(arrow)) = vd.declarators[0].init.as_deref() {
assert!(!arrow.is_async);
} else {
panic!("expected arrow expression");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_object_literal_getter() {
let prog = parse("var o = { get x() { return 1; } };").unwrap();
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
if let Some(Expr::Object(obj)) = vd.declarators[0].init.as_deref() {
if let ObjectProp::Prop(p) = &obj.properties[0] {
assert!(matches!(p.value, PropValue::Get(_)));
if let PropKey::Ident(ref id) = p.key {
assert_eq!(id.name, "x");
} else {
panic!("expected Ident key");
}
} else {
panic!("expected Prop");
}
} else {
panic!("expected Object expr");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_object_literal_setter() {
let prog = parse("var o = { set x(v) { } };").unwrap();
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
if let Some(Expr::Object(obj)) = vd.declarators[0].init.as_deref() {
if let ObjectProp::Prop(p) = &obj.properties[0] {
assert!(matches!(p.value, PropValue::Set(_)));
if let PropKey::Ident(ref id) = p.key {
assert_eq!(id.name, "x");
} else {
panic!("expected Ident key");
}
} else {
panic!("expected Prop");
}
} else {
panic!("expected Object expr");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_object_literal_getter_setter_pair() {
let prog = parse("var o = { get x() { return 1; }, set x(v) { } };").unwrap();
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
if let Some(Expr::Object(obj)) = vd.declarators[0].init.as_deref() {
assert_eq!(obj.properties.len(), 2);
assert!(matches!(
&obj.properties[0],
ObjectProp::Prop(p) if matches!(p.value, PropValue::Get(_))
));
assert!(matches!(
&obj.properties[1],
ObjectProp::Prop(p) if matches!(p.value, PropValue::Set(_))
));
} else {
panic!("expected Object expr");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_object_literal_computed_getter() {
let prog = parse("var o = { get [\"x\"]() { return 1; } };").unwrap();
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
if let Some(Expr::Object(obj)) = vd.declarators[0].init.as_deref() {
if let ObjectProp::Prop(p) = &obj.properties[0] {
assert!(matches!(p.value, PropValue::Get(_)));
assert!(p.is_computed);
assert!(matches!(p.key, PropKey::Computed(_)));
} else {
panic!("expected Prop");
}
} else {
panic!("expected Object expr");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_object_literal_computed_setter() {
let prog = parse("var o = { set [\"x\"](v) { } };").unwrap();
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
if let Some(Expr::Object(obj)) = vd.declarators[0].init.as_deref() {
if let ObjectProp::Prop(p) = &obj.properties[0] {
assert!(matches!(p.value, PropValue::Set(_)));
assert!(p.is_computed);
assert!(matches!(p.key, PropKey::Computed(_)));
} else {
panic!("expected Prop");
}
} else {
panic!("expected Object expr");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_get_as_property_name_not_accessor() {
let prog = parse("var o = { get: 1 };").unwrap();
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
if let Some(Expr::Object(obj)) = vd.declarators[0].init.as_deref() {
if let ObjectProp::Prop(p) = &obj.properties[0] {
assert!(matches!(p.value, PropValue::Value(_)));
if let PropKey::Ident(ref id) = p.key {
assert_eq!(id.name, "get");
}
} else {
panic!("expected Prop");
}
} else {
panic!("expected Object expr");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_class_computed_getter_setter() {
let prog = parse("class C { get [\"x\"]() { return 1; } set [\"x\"](v) { } }").unwrap();
if let ProgramItem::Stmt(Stmt::ClassDecl(c)) = &prog.body[0] {
assert_eq!(c.body.body.len(), 2);
if let crate::parser::ast::ClassMember::Method(m) = &c.body.body[0] {
assert!(matches!(m.kind, crate::parser::ast::MethodKind::Get));
assert!(m.is_computed);
} else {
panic!("expected getter method");
}
if let crate::parser::ast::ClassMember::Method(m) = &c.body.body[1] {
assert!(matches!(m.kind, crate::parser::ast::MethodKind::Set));
assert!(m.is_computed);
} else {
panic!("expected setter method");
}
} else {
panic!("expected ClassDecl");
}
}
#[test]
fn test_class_private_field() {
let prog = parse("class C { #x; }").unwrap();
if let ProgramItem::Stmt(Stmt::ClassDecl(c)) = &prog.body[0] {
assert_eq!(c.body.body.len(), 1);
if let ClassMember::Property(p) = &c.body.body[0] {
assert!(!p.is_static);
assert!(p.value.is_none());
if let PropKey::Private(ref id) = p.key {
assert_eq!(id.name, "x");
} else {
panic!("expected Private key");
}
} else {
panic!("expected Property");
}
} else {
panic!("expected ClassDecl");
}
}
#[test]
fn test_class_private_field_with_initializer() {
let prog = parse("class C { #x = 42; }").unwrap();
if let ProgramItem::Stmt(Stmt::ClassDecl(c)) = &prog.body[0] {
assert_eq!(c.body.body.len(), 1);
if let ClassMember::Property(p) = &c.body.body[0] {
assert!(p.value.is_some());
if let PropKey::Private(ref id) = p.key {
assert_eq!(id.name, "x");
} else {
panic!("expected Private key");
}
} else {
panic!("expected Property");
}
} else {
panic!("expected ClassDecl");
}
}
#[test]
fn test_class_static_private_field() {
let prog = parse("class C { static #count = 0; }").unwrap();
if let ProgramItem::Stmt(Stmt::ClassDecl(c)) = &prog.body[0] {
assert_eq!(c.body.body.len(), 1);
if let ClassMember::Property(p) = &c.body.body[0] {
assert!(p.is_static);
if let PropKey::Private(ref id) = p.key {
assert_eq!(id.name, "count");
} else {
panic!("expected Private key");
}
} else {
panic!("expected Property");
}
} else {
panic!("expected ClassDecl");
}
}
#[test]
fn test_class_private_method() {
let prog = parse("class C { #foo() { return 1; } }").unwrap();
if let ProgramItem::Stmt(Stmt::ClassDecl(c)) = &prog.body[0] {
assert_eq!(c.body.body.len(), 1);
if let ClassMember::Method(m) = &c.body.body[0] {
assert!(matches!(m.kind, MethodKind::Method));
if let PropKey::Private(ref id) = m.key {
assert_eq!(id.name, "foo");
} else {
panic!("expected Private key");
}
} else {
panic!("expected Method");
}
} else {
panic!("expected ClassDecl");
}
}
#[test]
fn test_class_private_getter_setter() {
let prog = parse("class C { get #x() { return 1; } set #x(v) { this.v = v; } }").unwrap();
if let ProgramItem::Stmt(Stmt::ClassDecl(c)) = &prog.body[0] {
assert_eq!(c.body.body.len(), 2);
if let ClassMember::Method(m) = &c.body.body[0] {
assert!(matches!(m.kind, MethodKind::Get));
if let PropKey::Private(ref id) = m.key {
assert_eq!(id.name, "x");
} else {
panic!("expected Private key");
}
} else {
panic!("expected getter");
}
if let ClassMember::Method(m) = &c.body.body[1] {
assert!(matches!(m.kind, MethodKind::Set));
if let PropKey::Private(ref id) = m.key {
assert_eq!(id.name, "x");
} else {
panic!("expected Private key");
}
} else {
panic!("expected setter");
}
} else {
panic!("expected ClassDecl");
}
}
#[test]
fn test_class_static_block() {
let prog = parse("class C { static { let x = 1; } }").unwrap();
if let ProgramItem::Stmt(Stmt::ClassDecl(c)) = &prog.body[0] {
assert_eq!(c.body.body.len(), 1);
if let ClassMember::StaticBlock(sb) = &c.body.body[0] {
assert_eq!(sb.body.len(), 1);
} else {
panic!("expected StaticBlock");
}
} else {
panic!("expected ClassDecl");
}
}
#[test]
fn test_class_static_block_empty() {
let prog = parse("class C { static { } }").unwrap();
if let ProgramItem::Stmt(Stmt::ClassDecl(c)) = &prog.body[0] {
assert_eq!(c.body.body.len(), 1);
if let ClassMember::StaticBlock(sb) = &c.body.body[0] {
assert!(sb.body.is_empty());
} else {
panic!("expected StaticBlock");
}
} else {
panic!("expected ClassDecl");
}
}
#[test]
fn test_class_public_field() {
let prog = parse("class C { x = 1; y; }").unwrap();
if let ProgramItem::Stmt(Stmt::ClassDecl(c)) = &prog.body[0] {
assert_eq!(c.body.body.len(), 2);
if let ClassMember::Property(p) = &c.body.body[0] {
assert!(p.value.is_some());
if let PropKey::Ident(ref id) = p.key {
assert_eq!(id.name, "x");
} else {
panic!("expected Ident key");
}
} else {
panic!("expected Property");
}
if let ClassMember::Property(p) = &c.body.body[1] {
assert!(p.value.is_none());
if let PropKey::Ident(ref id) = p.key {
assert_eq!(id.name, "y");
} else {
panic!("expected Ident key");
}
} else {
panic!("expected Property");
}
} else {
panic!("expected ClassDecl");
}
}
#[test]
fn test_class_private_member_access() {
let prog = parse("class C { #x; m() { return this.#x; } }").unwrap();
if let ProgramItem::Stmt(Stmt::ClassDecl(c)) = &prog.body[0] {
assert_eq!(c.body.body.len(), 2);
assert!(matches!(&c.body.body[0], ClassMember::Property(_)));
assert!(matches!(&c.body.body[1], ClassMember::Method(_)));
} else {
panic!("expected ClassDecl");
}
}
#[test]
fn test_class_private_brand_check() {
let prog = parse("class C { #x; check(o) { return #x in o; } }").unwrap();
if let ProgramItem::Stmt(Stmt::ClassDecl(c)) = &prog.body[0] {
assert_eq!(c.body.body.len(), 2);
} else {
panic!("expected ClassDecl");
}
}
#[test]
fn test_class_private_optional_chain() {
let prog = parse("class C { #x; m(o) { return o?.#x; } }").unwrap();
if let ProgramItem::Stmt(Stmt::ClassDecl(c)) = &prog.body[0] {
assert_eq!(c.body.body.len(), 2);
if let ClassMember::Method(m) = &c.body.body[1] {
if let Stmt::Return(ret) = &m.value.body.body[0] {
if let Some(arg) = &ret.argument {
if let Expr::OptionalChain(inner) = arg.as_ref() {
if let Expr::OptionalMember(om) = inner.as_ref() {
assert!(
matches!(
&om.property,
crate::parser::ast::MemberProp::Private(p) if p.name == "x"
),
"expected MemberProp::Private(\"x\")"
);
} else {
panic!("expected OptionalMember expression");
}
} else {
panic!("expected OptionalChain wrapping OptionalMember");
}
} else {
panic!("expected return argument");
}
} else {
panic!("expected return statement");
}
} else {
panic!("expected Method");
}
} else {
panic!("expected ClassDecl");
}
}
#[test]
fn test_class_generator_method() {
let prog = parse("class C { *gen() { yield 1; } }").unwrap();
if let ProgramItem::Stmt(Stmt::ClassDecl(c)) = &prog.body[0] {
assert_eq!(c.body.body.len(), 1);
if let ClassMember::Method(m) = &c.body.body[0] {
assert!(m.value.is_generator);
} else {
panic!("expected Method");
}
} else {
panic!("expected ClassDecl");
}
}
#[test]
fn test_class_mixed_members() {
let prog = parse(
"class C { #x = 1; y = 2; static #z = 3; static { } constructor() {} #method() {} get #a() { return 1; } }",
)
.unwrap();
if let ProgramItem::Stmt(Stmt::ClassDecl(c)) = &prog.body[0] {
assert_eq!(c.body.body.len(), 7);
assert!(matches!(&c.body.body[0], ClassMember::Property(_)));
assert!(matches!(&c.body.body[1], ClassMember::Property(_)));
assert!(matches!(&c.body.body[2], ClassMember::Property(_)));
assert!(matches!(&c.body.body[3], ClassMember::StaticBlock(_)));
assert!(matches!(&c.body.body[4], ClassMember::Method(_)));
assert!(matches!(&c.body.body[5], ClassMember::Method(_)));
assert!(matches!(&c.body.body[6], ClassMember::Method(_)));
} else {
panic!("expected ClassDecl");
}
}
#[test]
fn test_with_basic() {
let prog = parse("with (obj) x;").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::With(w)) = &prog.body[0] {
assert!(matches!(*w.object, Expr::Ident(_)));
assert!(matches!(*w.body, Stmt::Expr(_)));
} else {
panic!("expected WithStmt, got {:?}", prog.body[0]);
}
}
#[test]
fn test_with_block_body() {
let prog = parse("with (obj) { var x = 1; }").unwrap();
if let ProgramItem::Stmt(Stmt::With(w)) = &prog.body[0] {
assert!(matches!(*w.body, Stmt::Block(_)));
} else {
panic!("expected WithStmt");
}
}
#[test]
fn test_with_nested() {
let prog = parse("with (a) with (b) x;").unwrap();
if let ProgramItem::Stmt(Stmt::With(outer)) = &prog.body[0] {
assert!(matches!(*outer.body, Stmt::With(_)));
} else {
panic!("expected nested with");
}
}
#[test]
fn test_parse_async_generator_declaration() {
let prog = parse("async function* gen() { yield 1; }").unwrap();
if let ProgramItem::Stmt(Stmt::FnDecl(f)) = &prog.body[0] {
assert!(f.is_async, "should be async");
assert!(f.is_generator, "should be generator");
assert_eq!(f.id.as_ref().unwrap().name, "gen");
} else {
panic!("expected async generator FnDecl");
}
}
#[test]
fn test_parse_async_generator_expression() {
let prog = parse("var f = async function*() { yield 1; };").unwrap();
if let ProgramItem::Stmt(Stmt::VarDecl(v)) = &prog.body[0] {
if let Some(init) = &v.declarators[0].init {
if let Expr::Fn(f) = init.as_ref() {
assert!(f.is_async, "should be async");
assert!(f.is_generator, "should be generator");
} else {
panic!("expected FnExpr");
}
} else {
panic!("expected init");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_object_generator_method() {
let prog = parse("var o = { *gen() { yield 1; } };").unwrap();
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
if let Some(Expr::Object(obj)) = vd.declarators[0].init.as_deref() {
if let ObjectProp::Prop(p) = &obj.properties[0] {
if let PropValue::Method(ref fe) = p.value {
assert!(fe.is_generator, "should be generator");
assert!(!fe.is_async, "should not be async");
} else {
panic!("expected Method PropValue");
}
if let PropKey::Ident(ref id) = p.key {
assert_eq!(id.name, "gen");
} else {
panic!("expected Ident key");
}
} else {
panic!("expected Prop");
}
} else {
panic!("expected Object");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_object_async_generator_method() {
let prog = parse("var o = { async *gen() { yield 1; } };").unwrap();
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
if let Some(Expr::Object(obj)) = vd.declarators[0].init.as_deref() {
if let ObjectProp::Prop(p) = &obj.properties[0] {
if let PropValue::Method(ref fe) = p.value {
assert!(fe.is_generator, "should be async generator");
assert!(fe.is_async, "should be async");
} else {
panic!("expected Method PropValue");
}
} else {
panic!("expected Prop");
}
} else {
panic!("expected Object");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_object_computed_generator_method() {
let prog = parse("var o = { *[Symbol.iterator]() { yield 1; } };").unwrap();
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
if let Some(Expr::Object(obj)) = vd.declarators[0].init.as_deref() {
if let ObjectProp::Prop(p) = &obj.properties[0] {
assert!(p.is_computed, "key should be computed");
if let PropValue::Method(ref fe) = p.value {
assert!(fe.is_generator, "should be generator");
} else {
panic!("expected Method PropValue");
}
} else {
panic!("expected Prop");
}
} else {
panic!("expected Object");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_yield_star_delegation() {
let prog = parse("function* g() { yield* other(); }").unwrap();
if let ProgramItem::Stmt(Stmt::FnDecl(f)) = &prog.body[0] {
assert!(f.is_generator);
if let Stmt::Expr(es) = &f.body.body[0] {
if let Expr::Yield(y) = es.expr.as_ref() {
assert!(y.delegate, "should be yield* delegation");
assert!(y.argument.is_some());
} else {
panic!("expected YieldExpr");
}
} else {
panic!("expected ExprStmt");
}
} else {
panic!("expected FnDecl");
}
}
#[test]
fn test_generator_param_default_yield_is_syntax_error() {
let result = parse("function* g(a = yield 1) {}");
assert!(matches!(result, Err(StatorError::SyntaxError(_))));
}
#[test]
fn test_generator_method_param_default_yield_is_syntax_error() {
let result = parse("({ *g(a = yield 1) {} })");
assert!(matches!(result, Err(StatorError::SyntaxError(_))));
}
#[test]
fn test_parse_for_await_of() {
let prog = parse("async function* f() { for await (const x of arr) { } }").unwrap();
if let ProgramItem::Stmt(Stmt::FnDecl(f)) = &prog.body[0] {
assert!(f.is_async);
assert!(f.is_generator);
if let Stmt::ForOf(for_of) = &f.body.body[0] {
assert!(for_of.is_await, "for-of should have is_await = true");
} else {
panic!("expected ForOf statement, got {:?}", f.body.body[0]);
}
} else {
panic!("expected FnDecl");
}
}
#[test]
fn test_parse_for_of_without_await() {
let prog = parse("for (const x of arr) { }").unwrap();
if let ProgramItem::Stmt(Stmt::ForOf(for_of)) = &prog.body[0] {
assert!(
!for_of.is_await,
"regular for-of should have is_await = false"
);
} else {
panic!("expected ForOf statement");
}
}
#[test]
fn test_tagged_template_no_substitution() {
let prog = parse("tag`hello`").unwrap();
if let ProgramItem::Stmt(Stmt::Expr(es)) = &prog.body[0] {
assert!(
matches!(es.expr.as_ref(), Expr::TaggedTemplate(_)),
"expected TaggedTemplate, got {:?}",
es.expr
);
if let Expr::TaggedTemplate(t) = es.expr.as_ref() {
assert!(matches!(t.tag.as_ref(), Expr::Ident(_)));
assert_eq!(t.quasi.quasis.len(), 1);
assert_eq!(t.quasi.expressions.len(), 0);
}
} else {
panic!("expected expression statement");
}
}
#[test]
fn test_tagged_template_with_interpolation() {
let prog = parse("tag`a${x}b`").unwrap();
if let ProgramItem::Stmt(Stmt::Expr(es)) = &prog.body[0] {
if let Expr::TaggedTemplate(t) = es.expr.as_ref() {
assert_eq!(t.quasi.quasis.len(), 2);
assert_eq!(t.quasi.expressions.len(), 1);
} else {
panic!("expected TaggedTemplate");
}
} else {
panic!("expected expression statement");
}
}
#[test]
fn test_tagged_template_member_tag() {
let prog = parse("foo.bar`hello`").unwrap();
if let ProgramItem::Stmt(Stmt::Expr(es)) = &prog.body[0] {
if let Expr::TaggedTemplate(t) = es.expr.as_ref() {
assert!(matches!(t.tag.as_ref(), Expr::Member(_)));
assert_eq!(t.quasi.quasis.len(), 1);
} else {
panic!("expected TaggedTemplate");
}
} else {
panic!("expected expression statement");
}
}
#[test]
fn test_tagged_template_chained() {
let prog = parse("tag`a``b`").unwrap();
if let ProgramItem::Stmt(Stmt::Expr(es)) = &prog.body[0] {
if let Expr::TaggedTemplate(outer) = es.expr.as_ref() {
assert!(matches!(outer.tag.as_ref(), Expr::TaggedTemplate(_)));
} else {
panic!("expected nested TaggedTemplate");
}
} else {
panic!("expected expression statement");
}
}
#[test]
fn test_tagged_template_invalid_escape_keeps_raw_and_none_cooked() {
let prog = parse(r#"tag`\unicode${x}`"#).unwrap();
if let ProgramItem::Stmt(Stmt::Expr(es)) = &prog.body[0] {
if let Expr::TaggedTemplate(t) = es.expr.as_ref() {
assert_eq!(t.quasi.quasis.len(), 2);
assert_eq!(t.quasi.quasis[0].raw, r"\unicode");
assert_eq!(t.quasi.quasis[0].cooked, None);
} else {
panic!("expected TaggedTemplate");
}
} else {
panic!("expected expression statement");
}
}
#[test]
fn test_untagged_template_invalid_escape_is_syntax_error() {
let result = parse(r#"`\unicode`"#);
assert!(matches!(result, Err(StatorError::SyntaxError(_))));
}
#[test]
fn test_parse_optional_catch_binding() {
let prog = parse("try {} catch {}").unwrap();
if let ProgramItem::Stmt(Stmt::Try(t)) = &prog.body[0] {
let handler = t.handler.as_ref().unwrap();
assert!(handler.param.is_none(), "catch param should be None");
} else {
panic!("expected try statement");
}
}
#[test]
fn test_parse_catch_with_binding() {
let prog = parse("try {} catch (e) {}").unwrap();
if let ProgramItem::Stmt(Stmt::Try(t)) = &prog.body[0] {
let handler = t.handler.as_ref().unwrap();
assert!(handler.param.is_some(), "catch param should be Some");
} else {
panic!("expected try statement");
}
}
#[test]
fn test_parse_nullish_coalesce() {
use crate::parser::ast::{Expr, LogicalOp};
let prog = parse("a ?? b").unwrap();
if let ProgramItem::Stmt(Stmt::Expr(ExprStmt { expr, .. })) = &prog.body[0] {
if let Expr::Logical(l) = expr.as_ref() {
assert_eq!(l.op, LogicalOp::NullishCoalesce);
} else {
panic!("expected logical expression");
}
} else {
panic!("expected expression statement");
}
}
#[test]
fn test_parse_nullish_coalesce_rejects_mixing_with_or() {
let result = parse("a || b ?? c");
assert!(
result.is_err(),
"mixing || and ?? without parentheses should be rejected"
);
}
#[test]
fn test_parse_logical_and_assign() {
use crate::parser::ast::{AssignOp, Expr};
let prog = parse("a &&= b").unwrap();
if let ProgramItem::Stmt(Stmt::Expr(ExprStmt { expr, .. })) = &prog.body[0] {
if let Expr::Assign(a) = expr.as_ref() {
assert_eq!(a.op, AssignOp::LogicalAndAssign);
} else {
panic!("expected assignment");
}
} else {
panic!("expected expression statement");
}
}
#[test]
fn test_parse_logical_or_assign() {
use crate::parser::ast::{AssignOp, Expr};
let prog = parse("a ||= b").unwrap();
if let ProgramItem::Stmt(Stmt::Expr(ExprStmt { expr, .. })) = &prog.body[0] {
if let Expr::Assign(a) = expr.as_ref() {
assert_eq!(a.op, AssignOp::LogicalOrAssign);
} else {
panic!("expected assignment");
}
} else {
panic!("expected expression statement");
}
}
#[test]
fn test_parse_nullish_assign() {
use crate::parser::ast::{AssignOp, Expr};
let prog = parse("a ??= b").unwrap();
if let ProgramItem::Stmt(Stmt::Expr(ExprStmt { expr, .. })) = &prog.body[0] {
if let Expr::Assign(a) = expr.as_ref() {
assert_eq!(a.op, AssignOp::NullishAssign);
} else {
panic!("expected assignment");
}
} else {
panic!("expected expression statement");
}
}
#[test]
fn test_parse_dynamic_import() {
let prog = parse("import('./module.js')").unwrap();
assert_eq!(prog.body.len(), 1);
if let ProgramItem::Stmt(Stmt::Expr(ExprStmt { expr, .. })) = &prog.body[0] {
assert!(matches!(expr.as_ref(), Expr::Import(_)));
if let Expr::Import(imp) = expr.as_ref() {
assert!(matches!(imp.source.as_ref(), Expr::Str(_)));
}
} else {
panic!("expected expression statement with import()");
}
}
#[test]
fn test_parse_dynamic_import_template_literal_specifier() {
let prog = parse("import(`./module.js`)").unwrap();
if let ProgramItem::Stmt(Stmt::Expr(ExprStmt { expr, .. })) = &prog.body[0] {
if let Expr::Import(imp) = expr.as_ref() {
assert!(matches!(imp.source.as_ref(), Expr::Template(_)));
} else {
panic!("expected import expression");
}
} else {
panic!("expected expression statement");
}
}
#[test]
fn test_parse_dynamic_import_requires_argument() {
let err = parse("import()").unwrap_err();
let msg = format!("{err}");
assert!(
msg.contains("import() requires a specifier argument"),
"unexpected error: {msg}"
);
}
#[test]
fn test_parse_dynamic_import_rejects_second_argument() {
let err = parse("import('./mod.json', { with: { type: 'json' } })").unwrap_err();
let msg = format!("{err}");
assert!(
msg.contains("import() requires exactly one argument"),
"unexpected error: {msg}"
);
}
#[test]
fn test_parse_dynamic_import_rejects_trailing_comma() {
let err = parse("import('./mod.js',)").unwrap_err();
let msg = format!("{err}");
assert!(
msg.contains("import() requires exactly one argument"),
"unexpected error: {msg}"
);
}
#[test]
fn test_parse_dynamic_import_variable_specifier() {
let prog = parse("import(url)").unwrap();
if let ProgramItem::Stmt(Stmt::Expr(ExprStmt { expr, .. })) = &prog.body[0] {
if let Expr::Import(imp) = expr.as_ref() {
assert!(matches!(imp.source.as_ref(), Expr::Ident(_)));
} else {
panic!("expected import expression");
}
} else {
panic!("expected expression statement");
}
}
#[test]
fn test_parse_dynamic_import_not_declaration() {
let prog = parse("import('./foo.js')").unwrap();
assert!(matches!(prog.body[0], ProgramItem::Stmt(_)));
assert_eq!(prog.source_type, SourceType::Script);
}
#[test]
fn test_parse_import_meta() {
let prog = parse_module("import.meta").unwrap();
if let ProgramItem::Stmt(Stmt::Expr(ExprStmt { expr, .. })) = &prog.body[0] {
assert!(
matches!(expr.as_ref(), Expr::MetaProp(_)),
"expected MetaProp, got {expr:?}"
);
} else {
panic!("expected expression statement");
}
}
#[test]
#[ignore] fn test_parse_import_dot_non_meta_is_error() {
let err = parse_module("import.foo").unwrap_err();
let msg = format!("{err}");
assert!(
msg.contains("expected 'meta' after 'import.'"),
"unexpected error: {msg}"
);
}
#[test]
fn test_parse_import_meta_not_module() {
let err = parse("import.meta").unwrap_err();
let msg = format!("{err}");
assert!(
msg.contains("import.meta is only valid in module code"),
"unexpected error: {msg}"
);
}
#[test]
fn test_parse_import_decl_still_works() {
let prog = parse("import foo from 'bar'").unwrap();
assert!(matches!(
prog.body[0],
ProgramItem::ModuleDecl(ModuleDecl::Import(_))
));
assert_eq!(prog.source_type, SourceType::Module);
}
#[test]
fn test_parse_import_decl_is_module_strict() {
let prog = parse("import x from 'y'").unwrap();
assert_eq!(prog.source_type, SourceType::Module);
assert!(prog.is_strict);
}
#[test]
fn test_use_strict_directive_sets_flag() {
let prog = parse("\"use strict\"; var x = 1;").unwrap();
assert!(prog.is_strict);
}
#[test]
fn test_no_use_strict_directive_is_sloppy() {
let prog = parse("var x = 1;").unwrap();
assert!(!prog.is_strict);
}
#[test]
fn test_module_is_always_strict() {
let prog = parse("import x from 'y';").unwrap();
assert!(prog.is_strict);
}
#[test]
fn test_strict_mode_function() {
let prog = parse("function f() { \"use strict\"; return 1; }").unwrap();
if let ProgramItem::Stmt(Stmt::FnDecl(decl)) = &prog.body[0] {
assert!(decl.is_strict);
} else {
panic!("expected FnDecl");
}
}
#[test]
fn test_strict_mode_with_statement_error() {
let result = parse("\"use strict\"; with (obj) { x; }");
assert!(result.is_err());
}
#[test]
fn test_strict_mode_eval_binding_error() {
let result = parse("\"use strict\"; var eval = 1;");
assert!(result.is_err());
}
#[test]
fn test_strict_mode_arguments_binding_error() {
let result = parse("\"use strict\"; var arguments = 1;");
assert!(result.is_err());
}
#[test]
fn test_strict_inherited_in_nested_function() {
let prog = parse("\"use strict\"; function f() { function g() { return 1; } }").unwrap();
assert!(prog.is_strict);
if let ProgramItem::Stmt(Stmt::FnDecl(decl)) = &prog.body[1] {
assert!(decl.is_strict);
} else {
panic!("expected FnDecl");
}
}
#[test]
fn test_strict_duplicate_params_error() {
let result = parse("'use strict'; function f(a, a) {}");
assert!(result.is_err());
}
#[test]
fn test_strict_duplicate_params_fn_body_directive() {
let result = parse("function f(a, a) { 'use strict'; }");
assert!(result.is_err());
}
#[test]
fn test_sloppy_duplicate_params_ok() {
let result = parse("function f(a, a) {}");
assert!(result.is_ok());
}
#[test]
fn test_strict_duplicate_params_fn_expr() {
let result = parse("'use strict'; var f = function(a, a) {};");
assert!(result.is_err());
}
#[test]
fn test_arrow_duplicate_params_error() {
let result = parse("(a, a) => {}");
assert!(result.is_err());
}
#[test]
fn test_arrow_unique_params_ok() {
let result = parse("(a, b) => {}");
assert!(result.is_ok());
}
#[test]
fn test_class_method_duplicate_params_error() {
let result = parse("class C { m(a, a) {} }");
assert!(result.is_err());
}
#[test]
fn test_object_method_duplicate_params_error() {
let result = parse("({ m(a, a) {} })");
assert!(result.is_err());
}
#[test]
fn test_strict_assign_eval_error() {
let result = parse("'use strict'; eval = 1;");
assert!(result.is_err());
}
#[test]
fn test_strict_assign_arguments_error() {
let result = parse("'use strict'; arguments = 1;");
assert!(result.is_err());
}
#[test]
fn test_sloppy_assign_eval_ok() {
let result = parse("eval = 1;");
assert!(result.is_ok());
}
#[test]
fn test_sloppy_assign_arguments_ok() {
let result = parse("arguments = 1;");
assert!(result.is_ok());
}
#[test]
fn test_strict_prefix_increment_eval_error() {
let result = parse("'use strict'; ++eval;");
assert!(result.is_err());
}
#[test]
fn test_strict_octal_literal_error() {
let result = parse("'use strict'; var x = 010;");
assert!(result.is_err());
}
#[test]
fn test_sloppy_octal_literal_ok() {
let result = parse("var x = 010;");
assert!(result.is_ok());
}
#[test]
fn test_strict_octal_escape_in_string_error() {
let result = parse("'use strict'; var x = '\\012';");
assert!(result.is_err());
}
#[test]
fn test_strict_delete_identifier_error() {
let result = parse("'use strict'; delete x;");
assert!(result.is_err());
}
#[test]
fn test_sloppy_delete_identifier_ok() {
let result = parse("delete x;");
assert!(result.is_ok());
}
#[test]
fn test_strict_delete_member_ok() {
let result = parse("'use strict'; delete obj.x;");
assert!(result.is_ok());
}
#[test]
fn test_sloppy_with_ok() {
let result = parse("with (obj) { x; }");
assert!(result.is_ok());
}
#[test]
fn test_duplicate_proto_error() {
let result = parse("({__proto__: null, __proto__: null})");
assert!(result.is_err());
}
#[test]
fn test_single_proto_ok() {
let result = parse("({__proto__: null})");
assert!(result.is_ok());
}
#[test]
fn test_const_no_init_error() {
let result = parse("const x;");
assert!(result.is_err());
}
#[test]
fn test_const_with_init_ok() {
let result = parse("const x = 1;");
assert!(result.is_ok());
}
#[test]
fn test_let_no_init_ok() {
let result = parse("let x;");
assert!(result.is_ok());
}
#[test]
fn test_class_body_rejects_octal() {
let result = parse("class C { m() { var x = 010; } }");
assert!(result.is_err());
}
#[test]
fn test_class_body_rejects_with() {
let result = parse("class C { m() { with (obj) {} } }");
assert!(result.is_err());
}
#[test]
fn test_class_body_rejects_delete_ident() {
let result = parse("class C { m() { delete x; } }");
assert!(result.is_err());
}
#[test]
fn test_strict_eval_binding_error() {
let result = parse("'use strict'; let eval = 1;");
assert!(result.is_err());
}
#[test]
fn test_strict_arguments_binding_error() {
let result = parse("'use strict'; let arguments = 1;");
assert!(result.is_err());
}
#[test]
fn test_strict_const_eval_binding_error() {
let result = parse("'use strict'; const eval = 1;");
assert!(result.is_err());
}
#[test]
fn test_strict_const_arguments_binding_error() {
let result = parse("'use strict'; const arguments = 1;");
assert!(result.is_err());
}
#[test]
fn test_strict_fn_param_eval_error() {
let result = parse("'use strict'; function f(eval) {}");
assert!(result.is_err());
}
#[test]
fn test_strict_fn_param_arguments_error() {
let result = parse("'use strict'; function f(arguments) {}");
assert!(result.is_err());
}
#[test]
fn test_strict_fn_name_eval_error() {
let result = parse("'use strict'; function eval() {}");
assert!(result.is_err());
}
#[test]
fn test_strict_fn_name_arguments_error() {
let result = parse("'use strict'; function arguments() {}");
assert!(result.is_err());
}
#[test]
fn test_strict_arrow_param_eval_error() {
let result = parse("'use strict'; (eval) => {};");
assert!(result.is_err());
}
#[test]
fn test_strict_arrow_param_arguments_error() {
let result = parse("'use strict'; (arguments) => {};");
assert!(result.is_err());
}
#[test]
fn test_strict_arrow_bare_eval_error() {
let result = parse("'use strict'; eval => {};");
assert!(result.is_err());
}
#[test]
fn test_strict_arrow_bare_arguments_error() {
let result = parse("'use strict'; arguments => {};");
assert!(result.is_err());
}
#[test]
fn test_strict_with_statement_error() {
let result = parse("'use strict'; with(obj) {}");
assert!(result.is_err());
}
#[test]
fn test_strict_delete_identifier_with_decl_error() {
let result = parse("'use strict'; var x = 1; delete x;");
assert!(result.is_err());
}
#[test]
fn test_parse_optional_member() {
let prog = parse("obj?.prop").unwrap();
if let ProgramItem::Stmt(Stmt::Expr(es)) = &prog.body[0] {
if let Expr::OptionalChain(inner) = es.expr.as_ref() {
assert!(matches!(inner.as_ref(), Expr::OptionalMember(_)));
} else {
panic!("expected OptionalChain wrapping OptionalMember");
}
} else {
panic!("expected OptionalMember expression");
}
}
#[test]
fn test_parse_optional_member_computed() {
let prog = parse("obj?.[0]").unwrap();
if let ProgramItem::Stmt(Stmt::Expr(es)) = &prog.body[0] {
if let Expr::OptionalChain(inner) = es.expr.as_ref() {
if let Expr::OptionalMember(m) = inner.as_ref() {
assert!(m.is_computed);
} else {
panic!("expected OptionalMember");
}
} else {
panic!("expected OptionalChain");
}
} else {
panic!("expected expression statement");
}
}
#[test]
fn test_parse_optional_call() {
let prog = parse("fn?.()").unwrap();
if let ProgramItem::Stmt(Stmt::Expr(es)) = &prog.body[0] {
if let Expr::OptionalChain(inner) = es.expr.as_ref() {
assert!(matches!(inner.as_ref(), Expr::OptionalCall(_)));
} else {
panic!("expected OptionalChain wrapping OptionalCall");
}
} else {
panic!("expected OptionalCall expression");
}
}
#[test]
fn test_parse_optional_chain_chained() {
let prog = parse("a?.b?.c").unwrap();
if let ProgramItem::Stmt(Stmt::Expr(es)) = &prog.body[0] {
if let Expr::OptionalChain(inner) = es.expr.as_ref() {
if let Expr::OptionalMember(outer) = inner.as_ref() {
assert!(matches!(outer.object.as_ref(), Expr::OptionalMember(_)));
} else {
panic!("expected nested OptionalMember");
}
} else {
panic!("expected OptionalChain");
}
} else {
panic!("expected expression statement");
}
}
#[test]
fn test_parse_optional_member_then_call() {
let prog = parse("obj?.method()").unwrap();
if let ProgramItem::Stmt(Stmt::Expr(es)) = &prog.body[0] {
if let Expr::OptionalChain(inner) = es.expr.as_ref() {
if let Expr::Call(call) = inner.as_ref() {
assert!(
matches!(call.callee.as_ref(), Expr::OptionalMember(_)),
"callee should be OptionalMember"
);
assert!(call.arguments.is_empty());
} else {
panic!("expected Call expression, got {:?}", inner);
}
} else {
panic!("expected OptionalChain");
}
} else {
panic!("expected expression statement");
}
}
#[test]
fn test_parse_optional_member_then_call_with_args() {
let prog = parse("obj?.method(1, 2)").unwrap();
if let ProgramItem::Stmt(Stmt::Expr(es)) = &prog.body[0] {
if let Expr::OptionalChain(inner) = es.expr.as_ref() {
if let Expr::Call(call) = inner.as_ref() {
if let Expr::OptionalMember(m) = call.callee.as_ref() {
assert!(!m.is_computed);
if let crate::parser::ast::MemberProp::Ident(id) = &m.property {
assert_eq!(id.name, "method");
} else {
panic!("expected Ident property");
}
} else {
panic!("expected OptionalMember callee");
}
assert_eq!(call.arguments.len(), 2);
} else {
panic!("expected Call expression");
}
} else {
panic!("expected OptionalChain");
}
} else {
panic!("expected expression statement");
}
}
#[test]
fn test_parse_optional_call_with_args() {
let prog = parse("fn?.(1, 2)").unwrap();
if let ProgramItem::Stmt(Stmt::Expr(es)) = &prog.body[0] {
if let Expr::OptionalChain(inner) = es.expr.as_ref() {
if let Expr::OptionalCall(oc) = inner.as_ref() {
assert_eq!(oc.arguments.len(), 2);
} else {
panic!("expected OptionalCall expression");
}
} else {
panic!("expected OptionalChain");
}
} else {
panic!("expected expression statement");
}
}
#[test]
fn test_parse_optional_computed_member_string_key() {
let prog = parse(r#"obj?.["key"]"#).unwrap();
if let ProgramItem::Stmt(Stmt::Expr(es)) = &prog.body[0] {
if let Expr::OptionalChain(inner) = es.expr.as_ref() {
if let Expr::OptionalMember(m) = inner.as_ref() {
assert!(m.is_computed);
} else {
panic!("expected OptionalMember");
}
} else {
panic!("expected OptionalChain");
}
} else {
panic!("expected expression statement");
}
}
#[test]
fn test_for_of_object_destructuring() {
let prog = parse("for (let {x, y} of arr) {}").unwrap();
if let ProgramItem::Stmt(Stmt::ForOf(fo)) = &prog.body[0] {
if let crate::parser::ast::ForInOfLeft::VarDecl(vd) = &fo.left {
assert_eq!(vd.kind, VarKind::Let);
assert!(matches!(&vd.declarators[0].id, Pat::Object(_)));
} else {
panic!("expected VarDecl left");
}
} else {
panic!("expected ForOf, got {:?}", prog.body[0]);
}
}
#[test]
fn test_for_of_const_item() {
let prog = parse("for (const item of arr) { item; }").unwrap();
if let ProgramItem::Stmt(Stmt::ForOf(fo)) = &prog.body[0] {
assert!(!fo.is_await);
if let crate::parser::ast::ForInOfLeft::VarDecl(vd) = &fo.left {
assert_eq!(vd.kind, VarKind::Const);
if let Pat::Ident(id) = &vd.declarators[0].id {
assert_eq!(id.name, "item");
} else {
panic!("expected Ident pattern");
}
} else {
panic!("expected VarDecl left");
}
} else {
panic!("expected ForOf");
}
}
#[test]
fn test_for_of_array_destructuring_detailed() {
let prog = parse("for (let [a, b] of arr) {}").unwrap();
if let ProgramItem::Stmt(Stmt::ForOf(fo)) = &prog.body[0] {
if let crate::parser::ast::ForInOfLeft::VarDecl(vd) = &fo.left {
assert_eq!(vd.kind, VarKind::Let);
if let Pat::Array(ap) = &vd.declarators[0].id {
assert_eq!(ap.elements.len(), 2);
} else {
panic!("expected Array pattern");
}
} else {
panic!("expected VarDecl left");
}
} else {
panic!("expected ForOf");
}
}
#[test]
fn test_class_extends_with_super_call() {
let prog = parse("class Foo extends Bar { constructor() { super(); } }").unwrap();
if let ProgramItem::Stmt(Stmt::ClassDecl(c)) = &prog.body[0] {
assert_eq!(c.id.as_ref().unwrap().name, "Foo");
assert!(c.super_class.is_some());
if let Expr::Ident(sc) = c.super_class.as_deref().unwrap() {
assert_eq!(sc.name, "Bar");
} else {
panic!("expected Ident super class");
}
assert_eq!(c.body.body.len(), 1);
if let ClassMember::Method(m) = &c.body.body[0] {
assert_eq!(m.kind, MethodKind::Constructor);
assert!(!m.value.body.body.is_empty());
if let Stmt::Expr(es) = &m.value.body.body[0] {
assert!(
matches!(es.expr.as_ref(), Expr::Call(_)),
"expected super() call"
);
} else {
panic!("expected ExprStmt in constructor body");
}
} else {
panic!("expected Method (constructor)");
}
} else {
panic!("expected ClassDecl");
}
}
#[test]
fn test_class_private_field_with_method_access() {
let prog = parse("class Foo { #private = 0; method() { return this.#private; } }").unwrap();
if let ProgramItem::Stmt(Stmt::ClassDecl(c)) = &prog.body[0] {
assert_eq!(c.body.body.len(), 2);
if let ClassMember::Property(p) = &c.body.body[0] {
assert!(matches!(&p.key, PropKey::Private(_)));
assert!(p.value.is_some());
} else {
panic!("expected Property for #private");
}
if let ClassMember::Method(m) = &c.body.body[1] {
assert_eq!(m.kind, MethodKind::Method);
if let PropKey::Ident(id) = &m.key {
assert_eq!(id.name, "method");
} else {
panic!("expected Ident key for method");
}
} else {
panic!("expected Method");
}
} else {
panic!("expected ClassDecl");
}
}
#[test]
fn test_class_static_method_detailed() {
let prog = parse("class Foo { static method() {} }").unwrap();
if let ProgramItem::Stmt(Stmt::ClassDecl(c)) = &prog.body[0] {
assert_eq!(c.body.body.len(), 1);
if let ClassMember::Method(m) = &c.body.body[0] {
assert!(m.is_static);
assert_eq!(m.kind, MethodKind::Method);
if let PropKey::Ident(id) = &m.key {
assert_eq!(id.name, "method");
} else {
panic!("expected Ident key");
}
} else {
panic!("expected Method");
}
} else {
panic!("expected ClassDecl");
}
}
#[test]
fn test_class_getter_setter_detailed() {
let prog = parse("class Foo { get prop() { return 1; } set prop(v) {} }").unwrap();
if let ProgramItem::Stmt(Stmt::ClassDecl(c)) = &prog.body[0] {
assert_eq!(c.body.body.len(), 2);
if let ClassMember::Method(getter) = &c.body.body[0] {
assert_eq!(getter.kind, MethodKind::Get);
assert!(getter.value.params.is_empty());
} else {
panic!("expected getter Method");
}
if let ClassMember::Method(setter) = &c.body.body[1] {
assert_eq!(setter.kind, MethodKind::Set);
assert_eq!(setter.value.params.len(), 1);
} else {
panic!("expected setter Method");
}
} else {
panic!("expected ClassDecl");
}
}
#[test]
fn test_generator_function_multiple_yields() {
let prog = parse("function* gen() { yield 1; yield 2; }").unwrap();
if let ProgramItem::Stmt(Stmt::FnDecl(f)) = &prog.body[0] {
assert!(f.is_generator, "should be generator");
assert!(!f.is_async, "should not be async");
assert_eq!(f.id.as_ref().unwrap().name, "gen");
assert_eq!(f.body.body.len(), 2);
for stmt in &f.body.body {
if let Stmt::Expr(es) = stmt {
if let Expr::Yield(y) = es.expr.as_ref() {
assert!(!y.delegate, "should not be yield*");
assert!(y.argument.is_some());
} else {
panic!("expected YieldExpr");
}
} else {
panic!("expected ExprStmt");
}
}
} else {
panic!("expected generator FnDecl");
}
}
#[test]
fn test_generator_yield_star_array() {
let prog = parse("function* gen() { yield* [1, 2]; }").unwrap();
if let ProgramItem::Stmt(Stmt::FnDecl(f)) = &prog.body[0] {
assert!(f.is_generator);
if let Stmt::Expr(es) = &f.body.body[0] {
if let Expr::Yield(y) = es.expr.as_ref() {
assert!(y.delegate, "should be yield* delegation");
assert!(
matches!(y.argument.as_deref(), Some(Expr::Array(_))),
"argument should be array literal"
);
} else {
panic!("expected YieldExpr");
}
} else {
panic!("expected ExprStmt");
}
} else {
panic!("expected generator FnDecl");
}
}
#[test]
fn test_generator_expression() {
let prog = parse("var g = function*() { yield 42; };").unwrap();
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
if let Some(Expr::Fn(fe)) = vd.declarators[0].init.as_deref() {
assert!(fe.is_generator, "should be generator");
assert!(!fe.is_async);
} else {
panic!("expected generator function expression");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_async_arrow_with_await() {
let prog = parse("var f = async () => await bar();").unwrap();
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
if let Some(Expr::Arrow(arrow)) = vd.declarators[0].init.as_deref() {
assert!(arrow.is_async, "should be async");
if let ArrowBody::Expr(body_expr) = &arrow.body {
assert!(
matches!(body_expr.as_ref(), Expr::Await(_)),
"body should be Await expression"
);
} else {
panic!("expected expression body");
}
} else {
panic!("expected async arrow expression");
}
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_async_function_with_await_call() {
let prog = parse("async function foo() { await bar(); }").unwrap();
if let ProgramItem::Stmt(Stmt::FnDecl(fd)) = &prog.body[0] {
assert!(fd.is_async);
assert!(!fd.is_generator);
assert_eq!(fd.id.as_ref().unwrap().name, "foo");
if let Stmt::Expr(es) = &fd.body.body[0] {
if let Expr::Await(aw) = es.expr.as_ref() {
assert!(
matches!(aw.argument.as_ref(), Expr::Call(_)),
"await argument should be a call"
);
} else {
panic!("expected Await expression");
}
} else {
panic!("expected ExprStmt");
}
} else {
panic!("expected async FnDecl");
}
}
#[test]
fn test_parse_nullish_coalescing() {
let prog = parse("a ?? b").unwrap();
if let ProgramItem::Stmt(Stmt::Expr(es)) = &prog.body[0] {
if let Expr::Logical(l) = es.expr.as_ref() {
assert!(matches!(
l.op,
crate::parser::ast::LogicalOp::NullishCoalesce
));
} else {
panic!("expected Logical expression");
}
} else {
panic!("expected expression statement");
}
}
#[test]
fn test_strict_with_rejected() {
let src = "'use strict'; with ({}) {}";
let result = parse(src);
assert!(
result.is_err(),
"with statement should be rejected in strict mode"
);
}
#[test]
fn test_strict_delete_ident_rejected() {
let src = "'use strict'; var x = 1; delete x;";
let result = parse(src);
assert!(
result.is_err(),
"delete of unqualified identifier should be rejected in strict mode"
);
}
#[test]
fn test_strict_eval_binding_rejected() {
let src = "'use strict'; var eval = 1;";
let result = parse(src);
assert!(
result.is_err(),
"eval as binding should be rejected in strict mode"
);
}
#[test]
fn test_strict_duplicate_params_rejected() {
let src = "'use strict'; function f(a, a) {}";
let result = parse(src);
assert!(
result.is_err(),
"duplicate params should be rejected in strict mode"
);
}
#[test]
fn test_strict_octal_literal_rejected() {
let src = "'use strict'; var x = 0123;";
let result = parse(src);
assert!(
result.is_err(),
"legacy octal literals should be rejected in strict mode"
);
}
#[test]
fn test_nullish_rejects_or_then_nullish() {
assert!(
parse("a || b ?? c").is_err(),
"|| then ?? should be rejected"
);
}
#[test]
fn test_nullish_rejects_nullish_then_or() {
assert!(
parse("a ?? b || c").is_err(),
"?? then || should be rejected"
);
}
#[test]
fn test_nullish_rejects_and_then_nullish() {
assert!(
parse("a && b ?? c").is_err(),
"&& then ?? should be rejected"
);
}
#[test]
fn test_nullish_rejects_nullish_then_and() {
assert!(
parse("a ?? b && c").is_err(),
"?? then && should be rejected"
);
}
#[test]
fn test_nullish_allows_parenthesised_or() {
assert!(parse("(a || b) ?? c").is_ok(), "(|| ) ?? should be allowed");
}
#[test]
fn test_nullish_allows_parenthesised_nullish_then_or() {
assert!(parse("(a ?? b) || c").is_ok(), "(??) || should be allowed");
}
#[test]
fn test_nullish_allows_nullish_paren_or() {
assert!(parse("a ?? (b || c)").is_ok(), "?? (||) should be allowed");
}
#[test]
fn test_optional_chain_tagged_template_rejected() {
assert!(
parse("obj?.prop`template`").is_err(),
"tagged template in optional chain should be rejected"
);
}
#[test]
fn test_regular_tagged_template_allowed() {
assert!(
parse("obj.prop`template`").is_ok(),
"tagged template without optional chain should be allowed"
);
}
#[test]
fn test_exp_rejects_unary_minus() {
assert!(
parse("-2 ** 2").is_err(),
"-x ** y should be rejected without parentheses"
);
}
#[test]
fn test_exp_rejects_unary_plus() {
assert!(
parse("+2 ** 2").is_err(),
"+x ** y should be rejected without parentheses"
);
}
#[test]
fn test_exp_allows_parenthesised_unary() {
assert!(parse("(-2) ** 2").is_ok(), "(-x) ** y should be allowed");
}
#[test]
fn test_exp_rejects_not() {
assert!(parse("!x ** 2").is_err(), "!x ** y should be rejected");
}
#[test]
fn test_exp_basic() {
use crate::parser::ast::{BinaryOp, Expr};
let prog = parse("2 ** 3").unwrap();
if let ProgramItem::Stmt(Stmt::Expr(ExprStmt { expr, .. })) = &prog.body[0] {
if let Expr::Binary(b) = expr.as_ref() {
assert_eq!(b.op, BinaryOp::Exp);
} else {
panic!("expected binary ** expression");
}
} else {
panic!("expected expression statement");
}
}
#[test]
fn test_exp_right_associative() {
use crate::parser::ast::{BinaryOp, Expr};
let prog = parse("2 ** 3 ** 4").unwrap();
if let ProgramItem::Stmt(Stmt::Expr(ExprStmt { expr, .. })) = &prog.body[0] {
if let Expr::Binary(outer) = expr.as_ref() {
assert_eq!(outer.op, BinaryOp::Exp);
assert!(
matches!(outer.right.as_ref(), Expr::Binary(inner) if inner.op == BinaryOp::Exp),
"right side should be another ** expression"
);
} else {
panic!("expected binary expression");
}
} else {
panic!("expected expression statement");
}
}
#[test]
fn test_exp_rejects_typeof() {
assert!(
parse("typeof x ** 2").is_err(),
"typeof x ** y should be rejected"
);
}
#[test]
fn test_exp_allows_postfix_increment() {
assert!(
parse("x++ ** 2").is_ok(),
"x++ ** y should be allowed (postfix)"
);
}
#[test]
fn test_label_for_break() {
parse("outer: for (;;) { break outer; }").unwrap();
}
#[test]
fn test_label_block_break() {
parse("block: { break block; }").unwrap();
}
#[test]
fn test_label_nested_break_outer() {
parse("outer: for (;;) { for (;;) { break outer; } }").unwrap();
}
#[test]
fn test_label_break_undefined() {
assert!(parse("for (;;) { break nonexistent; }").is_err());
}
#[test]
fn test_label_continue_undefined() {
assert!(parse("for (;;) { continue nonexistent; }").is_err());
}
#[test]
fn test_label_continue_non_iteration() {
assert!(parse("block: { for (;;) { continue block; } }").is_err());
}
#[test]
fn test_label_across_function_boundary() {
assert!(parse("outer: for (;;) { (function() { break outer; })(); }").is_err());
}
#[test]
fn test_label_across_arrow_boundary() {
assert!(parse("outer: for (;;) { (() => { break outer; })(); }").is_err());
}
#[test]
fn test_label_continue_on_iteration_ok() {
parse("loop1: for (;;) { continue loop1; }").unwrap();
}
#[test]
fn test_label_continue_on_while_ok() {
parse("loop1: while (true) { continue loop1; }").unwrap();
}
#[test]
fn test_label_continue_on_do_while_ok() {
parse("loop1: do { continue loop1; } while (true)").unwrap();
}
#[test]
fn test_cover_initialized_name_arrow() {
let program = parse("({a = 1, b = 2}) => a + b").unwrap();
if let ProgramItem::Stmt(Stmt::Expr(ref es)) = program.body[0] {
if let Expr::Arrow(ref arrow) = *es.expr {
if let Pat::Object(ref obj) = arrow.params[0].pat {
assert!(
matches!(obj.properties[0], ObjectPatProp::Assign(_)),
"first prop should be AssignPatProp"
);
assert!(
matches!(obj.properties[1], ObjectPatProp::Assign(_)),
"second prop should be AssignPatProp"
);
return;
}
}
}
panic!("unexpected AST shape");
}
#[test]
fn test_cover_initialized_name_plain_shorthand() {
let program = parse("({a, b}) => a + b").unwrap();
if let ProgramItem::Stmt(Stmt::Expr(ref es)) = program.body[0] {
if let Expr::Arrow(ref arrow) = *es.expr {
if let Pat::Object(ref obj) = arrow.params[0].pat {
assert!(
matches!(obj.properties[0], ObjectPatProp::Assign(_)),
"first prop should be AssignPatProp"
);
return;
}
}
}
panic!("unexpected AST shape");
}
#[test]
fn test_destructuring_defaults_in_params() {
parse("function f({a = 1, b = 2}) {}").unwrap();
}
#[test]
fn test_destructuring_nested_in_params() {
parse("function f({a: {b, c}}) {}").unwrap();
}
#[test]
fn test_destructuring_computed_property() {
parse("let {[key]: value} = obj").unwrap();
}
#[test]
fn test_destructuring_rename_with_default() {
parse("let {a: b = 1} = obj").unwrap();
}
#[test]
fn test_for_of_const() {
parse("for (const x of arr) {}").unwrap();
}
#[test]
fn test_arrow_destructuring_obj_params() {
parse("({x, y}) => x + y").unwrap();
}
#[test]
#[ignore] fn test_arrow_rest_param() {
parse("(...args) => args").unwrap();
}
#[test]
fn test_arrow_async_expression_body() {
parse("async (x) => x").unwrap();
}
#[test]
fn test_duplicate_obj_keys_ok() {
parse("var x = {a: 1, a: 2}").unwrap();
}
#[test]
fn test_for_of_let_simple() {
parse("for (let x of [1, 2]) {}").unwrap();
}
#[test]
fn test_strict_directive_double_quote() {
let prog = parse("\"use strict\"; var x = 1;").unwrap();
assert!(prog.is_strict);
}
#[test]
fn test_strict_directive_single_quote() {
let prog = parse("'use strict'; var x = 1;").unwrap();
assert!(prog.is_strict);
}
#[test]
fn test_strict_directive_after_other_directive() {
let prog = parse("'use nothing'; 'use strict'; var x = 1;").unwrap();
assert!(prog.is_strict);
}
#[test]
fn test_strict_directive_not_after_statement() {
let prog = parse("var x = 1; 'use strict';").unwrap();
assert!(!prog.is_strict);
}
#[test]
fn test_strict_directive_in_function_body() {
let prog = parse("function f() { 'use strict'; return 1; }").unwrap();
if let ProgramItem::Stmt(Stmt::FnDecl(decl)) = &prog.body[0] {
assert!(decl.is_strict);
} else {
panic!("expected FnDecl");
}
}
#[test]
fn test_strict_directive_fn_body_not_after_stmt() {
let prog = parse("function f() { var x; 'use strict'; }").unwrap();
if let ProgramItem::Stmt(Stmt::FnDecl(decl)) = &prog.body[0] {
assert!(!decl.is_strict);
} else {
panic!("expected FnDecl");
}
}
#[test]
fn test_strict_directive_with_non_simple_params_error() {
let result = parse("function f(a = 1) { 'use strict'; }");
assert!(result.is_err());
}
#[test]
fn test_strict_octal_literal_0123_error() {
let result = parse("'use strict'; 0123;");
assert!(result.is_err());
}
#[test]
fn test_strict_octal_literal_00_error() {
let result = parse("'use strict'; 00;");
assert!(result.is_err());
}
#[test]
fn test_sloppy_octal_literal_0123_ok() {
parse("0123;").unwrap();
}
#[test]
fn test_strict_0x_hex_ok() {
parse("'use strict'; 0xFF;").unwrap();
}
#[test]
fn test_strict_0o_octal_ok() {
parse("'use strict'; 0o77;").unwrap();
}
#[test]
fn test_strict_0b_binary_ok() {
parse("'use strict'; 0b101;").unwrap();
}
#[test]
fn test_strict_with_error() {
let result = parse("'use strict'; with (obj) { x; }");
assert!(result.is_err());
}
#[test]
fn test_sloppy_with_ok_e2e() {
parse("with (obj) { x; }").unwrap();
}
#[test]
fn test_strict_fn_with_error() {
let result = parse("function f() { 'use strict'; with (obj) { x; } }");
assert!(result.is_err());
}
#[test]
fn test_strict_delete_plain_name_error() {
let result = parse("'use strict'; var x = 1; delete x;");
assert!(result.is_err());
}
#[test]
fn test_strict_delete_member_ok_e2e() {
parse("'use strict'; delete obj.prop;").unwrap();
}
#[test]
fn test_strict_delete_computed_member_ok() {
parse("'use strict'; delete obj['prop'];").unwrap();
}
#[test]
fn test_sloppy_delete_plain_name_ok() {
parse("var x = 1; delete x;").unwrap();
}
#[test]
fn test_strict_dup_params_program_level_error() {
let result = parse("'use strict'; function f(a, a) {}");
assert!(result.is_err());
}
#[test]
fn test_strict_dup_params_fn_body_error() {
let result = parse("function f(a, a) { 'use strict'; }");
assert!(result.is_err());
}
#[test]
fn test_sloppy_dup_params_ok_e2e() {
parse("function f(a, a) { return a; }").unwrap();
}
#[test]
fn test_strict_dup_params_fn_expr_error() {
let result = parse("'use strict'; var f = function(a, a) {};");
assert!(result.is_err());
}
#[test]
fn test_class_method_dup_params_error_e2e() {
let result = parse("class C { m(a, a) {} }");
assert!(result.is_err());
}
#[test]
fn test_strict_assign_to_eval_error() {
let result = parse("'use strict'; eval = 42;");
assert!(result.is_err());
}
#[test]
fn test_strict_assign_to_arguments_error() {
let result = parse("'use strict'; arguments = 42;");
assert!(result.is_err());
}
#[test]
fn test_strict_prefix_inc_eval_error() {
let result = parse("'use strict'; ++eval;");
assert!(result.is_err());
}
#[test]
fn test_strict_postfix_inc_eval_error() {
let result = parse("'use strict'; eval++;");
assert!(result.is_err());
}
#[test]
fn test_strict_prefix_dec_arguments_error() {
let result = parse("'use strict'; --arguments;");
assert!(result.is_err());
}
#[test]
fn test_strict_postfix_dec_arguments_error() {
let result = parse("'use strict'; arguments--;");
assert!(result.is_err());
}
#[test]
fn test_strict_var_eval_binding_error() {
let result = parse("'use strict'; var eval = 1;");
assert!(result.is_err());
}
#[test]
fn test_strict_let_arguments_binding_error() {
let result = parse("'use strict'; let arguments = 1;");
assert!(result.is_err());
}
#[test]
fn test_strict_fn_name_eval_error_e2e() {
let result = parse("'use strict'; function eval() {}");
assert!(result.is_err());
}
#[test]
fn test_strict_fn_param_eval_error_e2e() {
let result = parse("'use strict'; function f(eval) {}");
assert!(result.is_err());
}
#[test]
fn test_sloppy_assign_eval_ok_e2e() {
parse("eval = 42;").unwrap();
}
#[test]
fn test_sloppy_assign_arguments_ok_e2e() {
parse("arguments = 42;").unwrap();
}
#[test]
fn test_strict_octal_escape_seq_error() {
let result = parse("'use strict'; '\\1';");
assert!(result.is_err());
}
#[test]
fn test_strict_octal_escape_012_error() {
let result = parse("'use strict'; '\\012';");
assert!(result.is_err());
}
#[test]
fn test_strict_nul_escape_ok() {
parse("'use strict'; '\\0';").unwrap();
}
#[test]
fn test_strict_octal_89_error() {
let result = parse("'use strict'; '\\8';");
assert!(result.is_err());
}
#[test]
fn test_strict_reserved_implements_as_binding() {
let result = parse("'use strict'; var implements = 1;");
assert!(result.is_err());
}
#[test]
fn test_strict_reserved_interface_as_binding() {
let result = parse("'use strict'; var interface = 1;");
assert!(result.is_err());
}
#[test]
fn test_strict_reserved_package_as_binding() {
let result = parse("'use strict'; var package = 1;");
assert!(result.is_err());
}
#[test]
fn test_strict_reserved_private_as_binding() {
let result = parse("'use strict'; var private = 1;");
assert!(result.is_err());
}
#[test]
fn test_strict_reserved_protected_as_binding() {
let result = parse("'use strict'; var protected = 1;");
assert!(result.is_err());
}
#[test]
fn test_strict_reserved_public_as_binding() {
let result = parse("'use strict'; var public = 1;");
assert!(result.is_err());
}
#[test]
fn test_strict_reserved_implements_as_identifier() {
let result = parse("'use strict'; implements;");
assert!(result.is_err());
}
#[test]
fn test_strict_reserved_interface_as_identifier() {
let result = parse("'use strict'; interface;");
assert!(result.is_err());
}
#[test]
fn test_strict_reserved_package_as_identifier() {
let result = parse("'use strict'; package;");
assert!(result.is_err());
}
#[test]
fn test_strict_reserved_private_as_identifier() {
let result = parse("'use strict'; private;");
assert!(result.is_err());
}
#[test]
fn test_strict_reserved_protected_as_identifier() {
let result = parse("'use strict'; protected;");
assert!(result.is_err());
}
#[test]
fn test_strict_reserved_public_as_identifier() {
let result = parse("'use strict'; public;");
assert!(result.is_err());
}
#[test]
fn test_sloppy_reserved_implements_ok() {
parse("var implements = 1;").unwrap();
}
#[test]
fn test_sloppy_reserved_interface_ok() {
parse("var interface = 1;").unwrap();
}
#[test]
fn test_sloppy_reserved_private_ok() {
parse("var private = 1;").unwrap();
}
#[test]
fn test_strict_labelled_fn_decl_error() {
let result = parse("'use strict'; L: function f() {}");
assert!(result.is_err());
}
#[test]
fn test_sloppy_labelled_fn_decl_ok() {
parse("L: function f() {}").unwrap();
}
#[test]
fn test_class_body_rejects_octal_e2e() {
let result = parse("class C { m() { 010; } }");
assert!(result.is_err());
}
#[test]
fn test_class_body_rejects_with_e2e() {
let result = parse("class C { m() { with(o){} } }");
assert!(result.is_err());
}
#[test]
fn test_class_body_rejects_delete_ident_e2e() {
let result = parse("class C { m() { var x; delete x; } }");
assert!(result.is_err());
}
#[test]
fn test_module_code_always_strict() {
let prog = parse("export var x = 1;").unwrap();
assert!(prog.is_strict);
}
#[test]
fn test_strict_inherited_by_inner_fn() {
let result = parse("'use strict'; function f() { with (obj) {} }");
assert!(result.is_err());
}
#[test]
fn test_strict_fn_body_inner_fn_inherits() {
let result = parse("function f() { 'use strict'; function g() { with (obj) {} } }");
assert!(result.is_err());
}
#[test]
fn test_hashbang_at_start_of_file() {
let prog = parse("#!/usr/bin/env node\nvar x = 1;").unwrap();
assert_eq!(prog.body.len(), 1);
assert!(matches!(prog.body[0], ProgramItem::Stmt(Stmt::VarDecl(_))));
}
#[test]
fn test_hashbang_with_module_code() {
let prog = parse("#!/usr/bin/env node\nimport x from 'mod';").unwrap();
assert_eq!(prog.source_type, SourceType::Module);
assert!(prog.is_strict);
}
#[test]
fn test_hashbang_only_line() {
let prog = parse("#!/usr/bin/env node").unwrap();
assert!(prog.body.is_empty());
}
#[test]
fn test_hashbang_not_at_start_is_error() {
let result = parse("var x;\n#!foo");
assert!(result.is_err());
}
#[test]
fn test_hashbang_after_blank_line_is_error() {
let result = parse("\n#!/usr/bin/env node");
assert!(result.is_err());
}
#[test]
fn test_parse_module_explicit() {
let prog = parse_module("const x = 1;").unwrap();
assert_eq!(prog.source_type, SourceType::Module);
assert!(prog.is_strict);
}
#[test]
fn test_parse_module_with_import() {
let prog = parse_module("import x from 'y'; const a = x;").unwrap();
assert_eq!(prog.source_type, SourceType::Module);
assert_eq!(prog.body.len(), 2);
}
#[test]
fn test_parse_module_with_export() {
let prog = parse_module("export const x = 42;").unwrap();
assert_eq!(prog.source_type, SourceType::Module);
assert!(matches!(
prog.body[0],
ProgramItem::ModuleDecl(ModuleDecl::ExportNamed(_))
));
}
#[test]
fn test_parse_script_explicit() {
let prog = parse_script("var x = 1;").unwrap();
assert_eq!(prog.source_type, SourceType::Script);
assert!(!prog.is_strict);
}
#[test]
fn test_top_level_await_in_module() {
let prog = parse_module("const x = await fetch('/api');").unwrap();
assert_eq!(prog.source_type, SourceType::Module);
if let ProgramItem::Stmt(Stmt::VarDecl(vd)) = &prog.body[0] {
assert!(matches!(
vd.declarators[0].init.as_deref(),
Some(Expr::Await(_))
));
} else {
panic!("expected VarDecl");
}
}
#[test]
fn test_top_level_await_in_script_is_error() {
let result = parse("const x = await fetch('/api');");
assert!(result.is_err());
let msg = format!("{}", result.unwrap_err());
assert!(
msg.contains("'await' is only valid in async functions"),
"unexpected: {msg}"
);
}
#[test]
fn test_await_inside_async_fn_in_script() {
let prog = parse("async function f() { return await 1; }").unwrap();
assert_eq!(prog.source_type, SourceType::Script);
assert!(matches!(prog.body[0], ProgramItem::Stmt(Stmt::FnDecl(_))));
}
#[test]
fn test_import_meta_in_module_mode() {
let prog = parse_module("const url = import.meta.url;").unwrap();
assert_eq!(prog.source_type, SourceType::Module);
}
#[test]
fn test_import_meta_in_script_is_error() {
let result = parse_script("const url = import.meta.url;");
assert!(result.is_err());
let msg = format!("{}", result.unwrap_err());
assert!(
msg.contains("import.meta is only valid in module code"),
"unexpected: {msg}"
);
}
#[test]
fn test_import_meta_after_import_decl() {
let prog = parse("import x from 'y'; console.log(import.meta.url);").unwrap();
assert_eq!(prog.source_type, SourceType::Module);
}
#[test]
fn test_export_default_expression() {
let prog = parse_module("export default 42;").unwrap();
if let ProgramItem::ModuleDecl(ModuleDecl::ExportDefault(ed)) = &prog.body[0] {
assert!(matches!(ed.declaration, ExportDefaultExpr::Expr(_)));
} else {
panic!("expected ExportDefault");
}
}
#[test]
fn test_export_default_function_v2() {
let prog = parse_module("export default function foo() {}").unwrap();
if let ProgramItem::ModuleDecl(ModuleDecl::ExportDefault(ed)) = &prog.body[0] {
assert!(matches!(ed.declaration, ExportDefaultExpr::Fn(_)));
} else {
panic!("expected ExportDefault function");
}
}
#[test]
fn test_export_default_class_v2() {
let prog = parse_module("export default class C {}").unwrap();
if let ProgramItem::ModuleDecl(ModuleDecl::ExportDefault(ed)) = &prog.body[0] {
assert!(matches!(ed.declaration, ExportDefaultExpr::Class(_)));
} else {
panic!("expected ExportDefault class");
}
}
#[test]
fn test_export_named_with_alias() {
let prog = parse_module("const x = 1; export { x as y };").unwrap();
if let ProgramItem::ModuleDecl(ModuleDecl::ExportNamed(en)) = &prog.body[1] {
assert_eq!(en.specifiers.len(), 1);
if let ModuleExportName::Ident(ref id) = en.specifiers[0].local {
assert_eq!(id.name, "x");
}
if let ModuleExportName::Ident(ref id) = en.specifiers[0].exported {
assert_eq!(id.name, "y");
}
} else {
panic!("expected ExportNamed, got {:?}", prog.body[1]);
}
}
#[test]
fn test_export_named_multiple_aliases() {
let prog = parse_module("const a = 1, b = 2; export { a as x, b as y };").unwrap();
if let ProgramItem::ModuleDecl(ModuleDecl::ExportNamed(en)) = &prog.body[1] {
assert_eq!(en.specifiers.len(), 2);
} else {
panic!("expected ExportNamed");
}
}
#[test]
fn test_re_export_named() {
let prog = parse_module("export { x } from 'module';").unwrap();
if let ProgramItem::ModuleDecl(ModuleDecl::ExportNamed(en)) = &prog.body[0] {
assert_eq!(en.specifiers.len(), 1);
assert!(en.source.is_some());
assert_eq!(en.source.as_ref().unwrap().value, "'module'");
} else {
panic!("expected re-export");
}
}
#[test]
fn test_re_export_named_with_alias() {
let prog = parse_module("export { x as y } from 'module';").unwrap();
if let ProgramItem::ModuleDecl(ModuleDecl::ExportNamed(en)) = &prog.body[0] {
assert_eq!(en.specifiers.len(), 1);
assert!(en.source.is_some());
} else {
panic!("expected re-export with alias");
}
}
#[test]
fn test_re_export_all() {
let prog = parse_module("export * from 'module';").unwrap();
if let ProgramItem::ModuleDecl(ModuleDecl::ExportAll(ea)) = &prog.body[0] {
assert!(ea.exported.is_none());
assert_eq!(ea.source.value, "'module'");
} else {
panic!("expected ExportAll");
}
}
#[test]
fn test_re_export_all_as_namespace() {
let prog = parse_module("export * as ns from 'module';").unwrap();
if let ProgramItem::ModuleDecl(ModuleDecl::ExportAll(ea)) = &prog.body[0] {
assert!(ea.exported.is_some());
if let Some(ModuleExportName::Ident(ref id)) = ea.exported {
assert_eq!(id.name, "ns");
}
} else {
panic!("expected ExportAll with namespace alias");
}
}
#[test]
fn test_module_strict_with_statement_error() {
let result = parse_module("with (obj) {}");
assert!(result.is_err());
}
#[test]
fn test_top_level_await_bare_expression() {
let prog = parse_module("await 42;").unwrap();
if let ProgramItem::Stmt(Stmt::Expr(es)) = &prog.body[0] {
assert!(matches!(*es.expr, Expr::Await(_)));
} else {
panic!("expected expression statement with await");
}
}
#[test]
fn test_dynamic_import_in_script_mode() {
let prog = parse_script("import('./foo.js')").unwrap();
assert_eq!(prog.source_type, SourceType::Script);
if let ProgramItem::Stmt(Stmt::Expr(es)) = &prog.body[0] {
assert!(matches!(*es.expr, Expr::Import(_)));
} else {
panic!("expected import expression");
}
}
#[test]
fn test_export_default_async_function_v2() {
let prog = parse_module("export default async function f() {}").unwrap();
if let ProgramItem::ModuleDecl(ModuleDecl::ExportDefault(ed)) = &prog.body[0] {
if let ExportDefaultExpr::Fn(ref fd) = ed.declaration {
assert!(fd.is_async);
} else {
panic!("expected Fn");
}
} else {
panic!("expected ExportDefault");
}
}
#[test]
fn test_await_inside_async_arrow_in_module() {
let prog = parse_module("const f = async () => await 1;").unwrap();
assert_eq!(prog.source_type, SourceType::Module);
}
#[test]
fn test_await_inside_async_arrow_in_script() {
let prog = parse_script("const f = async () => await 1;").unwrap();
assert_eq!(prog.source_type, SourceType::Script);
}
#[test]
fn test_hashbang_scanner_token_kind() {
use crate::parser::scanner::{Scanner, TokenKind};
let mut scanner = Scanner::new("#!/usr/bin/env node\nfoo");
let tok = scanner.next_token().unwrap();
assert_eq!(tok.kind, TokenKind::HashbangComment);
}
#[test]
fn test_module_auto_detect_from_export() {
let prog = parse("export function f() {}").unwrap();
assert_eq!(prog.source_type, SourceType::Module);
assert!(prog.is_strict);
}
#[test]
fn test_export_var_decl() {
let prog = parse_module("export let x = 1, y = 2;").unwrap();
if let ProgramItem::ModuleDecl(ModuleDecl::ExportNamed(en)) = &prog.body[0] {
assert!(en.declaration.is_some());
} else {
panic!("expected ExportNamed with declaration");
}
}
}