From f8089da959472631ddcfd4304048840095a4e68c Mon Sep 17 00:00:00 2001 From: Pierre Le Fevre Date: Sun, 22 Mar 2026 09:42:25 +0100 Subject: [PATCH] Fix export default anonymous function/class parsing export default function() {} and export default class {} are valid JavaScript but the parser incorrectly required a name. Check whether the function/class has a name and route to expression parsing when anonymous. Co-Authored-By: Claude Opus 4.6 (1M context) --- crates/js/src/parser.rs | 63 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 59 insertions(+), 4 deletions(-) diff --git a/crates/js/src/parser.rs b/crates/js/src/parser.rs index 7d950fe..55f8dc3 100644 --- a/crates/js/src/parser.rs +++ b/crates/js/src/parser.rs @@ -1222,16 +1222,35 @@ impl Parser { // `export default ...` if self.eat(&TokenKind::Default) { if matches!(self.peek_kind(), TokenKind::Function) { - let decl = self.parse_function_declaration()?; + // Named function → declaration, anonymous → default expression + let has_name = matches!(self.peek_ahead(1), TokenKind::Identifier(_)) + || (matches!(self.peek_ahead(1), TokenKind::Star) + && matches!(self.peek_ahead(2), TokenKind::Identifier(_))); + if has_name { + let decl = self.parse_function_declaration()?; + return Ok(Stmt { + kind: StmtKind::Export(ExportDecl::Declaration(Box::new(decl))), + span: self.span_from(start), + }); + } + let expr = self.parse_function_expression()?; return Ok(Stmt { - kind: StmtKind::Export(ExportDecl::Declaration(Box::new(decl))), + kind: StmtKind::Export(ExportDecl::Default(expr)), span: self.span_from(start), }); } if matches!(self.peek_kind(), TokenKind::Class) { - let decl = self.parse_class_declaration()?; + // Named class → declaration, anonymous → default expression + if matches!(self.peek_ahead(1), TokenKind::Identifier(_)) { + let decl = self.parse_class_declaration()?; + return Ok(Stmt { + kind: StmtKind::Export(ExportDecl::Declaration(Box::new(decl))), + span: self.span_from(start), + }); + } + let expr = self.parse_class_expression()?; return Ok(Stmt { - kind: StmtKind::Export(ExportDecl::Declaration(Box::new(decl))), + kind: StmtKind::Export(ExportDecl::Default(expr)), span: self.span_from(start), }); } @@ -3465,6 +3484,42 @@ mod tests { } } + #[test] + fn test_export_default_anonymous_function() { + let prog = Parser::parse_module("export default function() {}").unwrap(); + match &prog.body[0].kind { + StmtKind::Export(ExportDecl::Default(expr)) => { + assert!(matches!(expr.kind, ExprKind::Function(ref def) if def.id.is_none())); + } + _ => panic!("expected export default anonymous function"), + } + } + + #[test] + fn test_export_default_anonymous_class() { + let prog = Parser::parse_module("export default class {}").unwrap(); + match &prog.body[0].kind { + StmtKind::Export(ExportDecl::Default(expr)) => { + assert!(matches!(expr.kind, ExprKind::Class(ref def) if def.id.is_none())); + } + _ => panic!("expected export default anonymous class"), + } + } + + #[test] + fn test_export_default_anonymous_generator() { + let prog = Parser::parse_module("export default function*() {}").unwrap(); + match &prog.body[0].kind { + StmtKind::Export(ExportDecl::Default(expr)) => { + assert!(matches!( + expr.kind, + ExprKind::Function(ref def) if def.id.is_none() && def.is_generator + )); + } + _ => panic!("expected export default anonymous generator"), + } + } + // ── ASI (Automatic Semicolon Insertion) ───────────── #[test] -- 2.51.2