pub fn ParseStmt( comptime parser_feature__typescript: bool, comptime parser_feature__jsx: JSXTransformType, comptime parser_feature__scan_only: bool, ) type { return struct { const P = js_parser.NewParser_(parser_feature__typescript, parser_feature__jsx, parser_feature__scan_only); const createDefaultName = P.createDefaultName; const extractDeclsForBinding = P.extractDeclsForBinding; const is_typescript_enabled = P.is_typescript_enabled; const track_symbol_usage_during_parse_pass = P.track_symbol_usage_during_parse_pass; pub fn parseStmt(p: *P, opts: *ParseStatementOptions) anyerror!Stmt { if (!p.stack_check.isSafeToRecurse()) { try bun.throwStackOverflow(); } const loc = p.lexer.loc(); switch (p.lexer.token) { .t_semicolon => { try p.lexer.next(); return Stmt.empty(); }, .t_export => { const previous_export_keyword = p.esm_export_keyword; if (opts.is_module_scope) { p.esm_export_keyword = p.lexer.range(); } else if (!opts.is_namespace_scope) { try p.lexer.unexpected(); return error.SyntaxError; } try p.lexer.next(); // TypeScript decorators only work on class declarations // "@decorator export class Foo {}" // "@decorator export abstract class Foo {}" // "@decorator export default class Foo {}" // "@decorator export default abstract class Foo {}" // "@decorator export declare class Foo {}" // "@decorator export declare abstract class Foo {}" if (opts.ts_decorators != null and p.lexer.token != js_lexer.T.t_class and p.lexer.token != js_lexer.T.t_default and !p.lexer.isContextualKeyword("abstract") and !p.lexer.isContextualKeyword("declare")) { try p.lexer.expected(js_lexer.T.t_class); } switch (p.lexer.token) { T.t_class, T.t_const, T.t_function, T.t_var => { opts.is_export = true; return p.parseStmt(opts); }, T.t_import => { // "export import foo = bar" if (is_typescript_enabled and (opts.is_module_scope or opts.is_namespace_scope)) { opts.is_export = true; return p.parseStmt(opts); } try p.lexer.unexpected(); return error.SyntaxError; }, T.t_enum => { if (!is_typescript_enabled) { try p.lexer.unexpected(); return error.SyntaxError; } opts.is_export = true; return p.parseStmt(opts); }, T.t_identifier => { if (p.lexer.isContextualKeyword("let")) { opts.is_export = true; return p.parseStmt(opts); } if (comptime is_typescript_enabled) { if (opts.is_typescript_declare and p.lexer.isContextualKeyword("as")) { // "export as namespace ns;" try p.lexer.next(); try p.lexer.expectContextualKeyword("namespace"); try p.lexer.expect(T.t_identifier); try p.lexer.expectOrInsertSemicolon(); return p.s(S.TypeScript{}, loc); } } if (p.lexer.isContextualKeyword("async")) { const asyncRange = p.lexer.range(); try p.lexer.next(); if (p.lexer.has_newline_before) { try p.log.addRangeError(p.source, asyncRange, "Unexpected newline after \"async\""); } try p.lexer.expect(T.t_function); opts.is_export = true; return try p.parseFnStmt(loc, opts, asyncRange); } if (is_typescript_enabled) { if (TypeScript.Identifier.forStr(p.lexer.identifier)) |ident| { switch (ident) { .s_type => { // "export type foo = ..." const type_range = p.lexer.range(); try p.lexer.next(); if (p.lexer.has_newline_before) { try p.log.addErrorFmt(p.source, type_range.end(), p.allocator, "Unexpected newline after \"type\"", .{}); return error.SyntaxError; } var skipper = ParseStatementOptions{ .is_module_scope = opts.is_module_scope, .is_export = true }; try p.skipTypeScriptTypeStmt(&skipper); return p.s(S.TypeScript{}, loc); }, .s_namespace, .s_abstract, .s_module, .s_interface => { // "export namespace Foo {}" // "export abstract class Foo {}" // "export module Foo {}" // "export interface Foo {}" opts.is_export = true; return try p.parseStmt(opts); }, .s_declare => { // "export declare class Foo {}" opts.is_export = true; opts.lexical_decl = .allow_all; opts.is_typescript_declare = true; return try p.parseStmt(opts); }, } } } try p.lexer.unexpected(); return error.SyntaxError; }, T.t_default => { if (!opts.is_module_scope and (!opts.is_namespace_scope or !opts.is_typescript_declare)) { try p.lexer.unexpected(); return error.SyntaxError; } const defaultLoc = p.lexer.loc(); try p.lexer.next(); // TypeScript decorators only work on class declarations // "@decorator export default class Foo {}" // "@decorator export default abstract class Foo {}" if (opts.ts_decorators != null and p.lexer.token != T.t_class and !p.lexer.isContextualKeyword("abstract")) { try p.lexer.expected(T.t_class); } if (p.lexer.isContextualKeyword("async")) { const async_range = p.lexer.range(); try p.lexer.next(); if (p.lexer.token == T.t_function and !p.lexer.has_newline_before) { try p.lexer.next(); var stmtOpts = ParseStatementOptions{ .is_name_optional = true, .lexical_decl = .allow_all, }; const stmt = try p.parseFnStmt(loc, &stmtOpts, async_range); if (@as(Stmt.Tag, stmt.data) == .s_type_script) { // This was just a type annotation return stmt; } const defaultName = if (stmt.data.s_function.func.name) |name| js_ast.LocRef{ .loc = name.loc, .ref = name.ref } else try p.createDefaultName(defaultLoc); const value = js_ast.StmtOrExpr{ .stmt = stmt }; return p.s(S.ExportDefault{ .default_name = defaultName, .value = value }, loc); } const defaultName = try createDefaultName(p, loc); const prefix_expr = try p.parseAsyncPrefixExpr(async_range, Level.comma); const expr = try p.parseSuffix(prefix_expr, Level.comma, null, Expr.EFlags.none); try p.lexer.expectOrInsertSemicolon(); const value = js_ast.StmtOrExpr{ .expr = expr }; p.has_export_default = true; return p.s(S.ExportDefault{ .default_name = defaultName, .value = value }, loc); } if (p.lexer.token == .t_function or p.lexer.token == .t_class or p.lexer.isContextualKeyword("interface")) { var _opts = ParseStatementOptions{ .ts_decorators = opts.ts_decorators, .is_name_optional = true, .lexical_decl = .allow_all, }; const stmt = try p.parseStmt(&_opts); const default_name: js_ast.LocRef = default_name_getter: { switch (stmt.data) { // This was just a type annotation .s_type_script => { return stmt; }, .s_function => |func_container| { if (func_container.func.name) |name| { break :default_name_getter LocRef{ .loc = name.loc, .ref = name.ref }; } }, .s_class => |class| { if (class.class.class_name) |name| { break :default_name_getter LocRef{ .loc = name.loc, .ref = name.ref }; } }, else => {}, } break :default_name_getter createDefaultName(p, defaultLoc) catch unreachable; }; p.has_export_default = true; p.has_es_module_syntax = true; return p.s( S.ExportDefault{ .default_name = default_name, .value = js_ast.StmtOrExpr{ .stmt = stmt } }, loc, ); } const is_identifier = p.lexer.token == .t_identifier; const name = p.lexer.identifier; const expr = try p.parseExpr(.comma); // Handle the default export of an abstract class in TypeScript if (is_typescript_enabled and is_identifier and (p.lexer.token == .t_class or opts.ts_decorators != null) and strings.eqlComptime(name, "abstract")) { switch (expr.data) { .e_identifier => { var stmtOpts = ParseStatementOptions{ .ts_decorators = opts.ts_decorators, .is_name_optional = true, }; const stmt: Stmt = try p.parseClassStmt(loc, &stmtOpts); // Use the statement name if present, since it's a better name const default_name: js_ast.LocRef = default_name_getter: { switch (stmt.data) { // This was just a type annotation .s_type_script => { return stmt; }, .s_function => |func_container| { if (func_container.func.name) |_name| { break :default_name_getter LocRef{ .loc = defaultLoc, .ref = _name.ref }; } }, .s_class => |class| { if (class.class.class_name) |_name| { break :default_name_getter LocRef{ .loc = defaultLoc, .ref = _name.ref }; } }, else => {}, } break :default_name_getter createDefaultName(p, defaultLoc) catch unreachable; }; p.has_export_default = true; return p.s(S.ExportDefault{ .default_name = default_name, .value = js_ast.StmtOrExpr{ .stmt = stmt } }, loc); }, else => { p.panic("internal error: unexpected", .{}); }, } } try p.lexer.expectOrInsertSemicolon(); // Use the expression name if present, since it's a better name p.has_export_default = true; return p.s( S.ExportDefault{ .default_name = p.defaultNameForExpr(expr, defaultLoc), .value = js_ast.StmtOrExpr{ .expr = expr, }, }, loc, ); }, T.t_asterisk => { if (!opts.is_module_scope and !(opts.is_namespace_scope or !opts.is_typescript_declare)) { try p.lexer.unexpected(); return error.SyntaxError; } try p.lexer.next(); var namespace_ref: Ref = Ref.None; var alias: ?js_ast.G.ExportStarAlias = null; var path: ParsedPath = undefined; if (p.lexer.isContextualKeyword("as")) { // "export * as ns from 'path'" try p.lexer.next(); const name = try p.parseClauseAlias("export"); namespace_ref = try p.storeNameInRef(name); alias = G.ExportStarAlias{ .loc = p.lexer.loc(), .original_name = name }; try p.lexer.next(); try p.lexer.expectContextualKeyword("from"); path = try p.parsePath(); } else { // "export * from 'path'" try p.lexer.expectContextualKeyword("from"); path = try p.parsePath(); const name = try fs.PathName.init(path.text).nonUniqueNameString(p.allocator); namespace_ref = try p.storeNameInRef(name); } const import_record_index = p.addImportRecord( ImportKind.stmt, path.loc, path.text, // TODO: import assertions // path.assertions ); if (path.is_macro) { try p.log.addError(p.source, path.loc, "cannot use macro in export statement"); } else if (path.import_tag != .none) { try p.log.addError(p.source, loc, "cannot use export statement with \"type\" attribute"); } if (comptime track_symbol_usage_during_parse_pass) { // In the scan pass, we need _some_ way of knowing *not* to mark as unused p.import_records.items[import_record_index].calls_runtime_re_export_fn = true; } try p.lexer.expectOrInsertSemicolon(); p.has_es_module_syntax = true; return p.s(S.ExportStar{ .namespace_ref = namespace_ref, .alias = alias, .import_record_index = import_record_index, }, loc); }, T.t_open_brace => { if (!opts.is_module_scope and !(opts.is_namespace_scope or !opts.is_typescript_declare)) { try p.lexer.unexpected(); return error.SyntaxError; } const export_clause = try p.parseExportClause(); if (p.lexer.isContextualKeyword("from")) { try p.lexer.expectContextualKeyword("from"); const parsedPath = try p.parsePath(); try p.lexer.expectOrInsertSemicolon(); if (comptime is_typescript_enabled) { // export {type Foo} from 'bar'; // -> // nothing // https://www.typescriptlang.org/play?useDefineForClassFields=true&esModuleInterop=false&declaration=false&target=99&isolatedModules=false&ts=4.5.4#code/KYDwDg9gTgLgBDAnmYcDeAxCEC+cBmUEAtnAOQBGAhlGQNwBQQA if (export_clause.clauses.len == 0 and export_clause.had_type_only_exports) { return p.s(S.TypeScript{}, loc); } } if (parsedPath.is_macro) { try p.log.addError(p.source, loc, "export from cannot be used with \"type\": \"macro\""); } else if (parsedPath.import_tag != .none) { try p.log.addError(p.source, loc, "export from cannot be used with \"type\" attribute"); } const import_record_index = p.addImportRecord(.stmt, parsedPath.loc, parsedPath.text); const path_name = fs.PathName.init(parsedPath.text); const namespace_ref = p.storeNameInRef( std.fmt.allocPrint( p.allocator, "import_{}", .{ path_name.fmtIdentifier(), }, ) catch bun.outOfMemory(), ) catch bun.outOfMemory(); if (comptime track_symbol_usage_during_parse_pass) { // In the scan pass, we need _some_ way of knowing *not* to mark as unused p.import_records.items[import_record_index].calls_runtime_re_export_fn = true; } p.current_scope.is_after_const_local_prefix = true; p.has_es_module_syntax = true; return p.s( S.ExportFrom{ .items = export_clause.clauses, .is_single_line = export_clause.is_single_line, .namespace_ref = namespace_ref, .import_record_index = import_record_index, }, loc, ); } try p.lexer.expectOrInsertSemicolon(); if (comptime is_typescript_enabled) { // export {type Foo}; // -> // nothing // https://www.typescriptlang.org/play?useDefineForClassFields=true&esModuleInterop=false&declaration=false&target=99&isolatedModules=false&ts=4.5.4#code/KYDwDg9gTgLgBDAnmYcDeAxCEC+cBmUEAtnAOQBGAhlGQNwBQQA if (export_clause.clauses.len == 0 and export_clause.had_type_only_exports) { return p.s(S.TypeScript{}, loc); } } p.has_es_module_syntax = true; return p.s(S.ExportClause{ .items = export_clause.clauses, .is_single_line = export_clause.is_single_line, }, loc); }, T.t_equals => { // "export = value;" p.esm_export_keyword = previous_export_keyword; // This wasn't an ESM export statement after all if (is_typescript_enabled) { try p.lexer.next(); const value = try p.parseExpr(.lowest); try p.lexer.expectOrInsertSemicolon(); return p.s(S.ExportEquals{ .value = value }, loc); } try p.lexer.unexpected(); return error.SyntaxError; }, else => { try p.lexer.unexpected(); return error.SyntaxError; }, } }, .t_function => { try p.lexer.next(); return try p.parseFnStmt(loc, opts, null); }, .t_enum => { if (!is_typescript_enabled) { try p.lexer.unexpected(); return error.SyntaxError; } return p.parseTypescriptEnumStmt(loc, opts); }, .t_at => { // Parse decorators before class statements, which are potentially exported if (is_typescript_enabled) { const scope_index = p.scopes_in_order.items.len; const ts_decorators = try p.parseTypeScriptDecorators(); // If this turns out to be a "declare class" statement, we need to undo the // scopes that were potentially pushed while parsing the decorator arguments. // That can look like any one of the following: // // "@decorator declare class Foo {}" // "@decorator declare abstract class Foo {}" // "@decorator export declare class Foo {}" // "@decorator export declare abstract class Foo {}" // opts.ts_decorators = DeferredTsDecorators{ .values = ts_decorators, .scope_index = scope_index, }; // "@decorator class Foo {}" // "@decorator abstract class Foo {}" // "@decorator declare class Foo {}" // "@decorator declare abstract class Foo {}" // "@decorator export class Foo {}" // "@decorator export abstract class Foo {}" // "@decorator export declare class Foo {}" // "@decorator export declare abstract class Foo {}" // "@decorator export default class Foo {}" // "@decorator export default abstract class Foo {}" if (p.lexer.token != .t_class and p.lexer.token != .t_export and !p.lexer.isContextualKeyword("abstract") and !p.lexer.isContextualKeyword("declare")) { try p.lexer.expected(.t_class); } return p.parseStmt(opts); } // notimpl(); try p.lexer.unexpected(); return error.SyntaxError; }, .t_class => { if (opts.lexical_decl != .allow_all) { try p.forbidLexicalDecl(loc); } return try p.parseClassStmt(loc, opts); }, .t_var => { try p.lexer.next(); const decls = try p.parseAndDeclareDecls(.hoisted, opts); try p.lexer.expectOrInsertSemicolon(); return p.s(S.Local{ .kind = .k_var, .decls = Decl.List.fromList(decls), .is_export = opts.is_export }, loc); }, .t_const => { if (opts.lexical_decl != .allow_all) { try p.forbidLexicalDecl(loc); } // p.markSyntaxFeature(compat.Const, p.lexer.Range()) try p.lexer.next(); if (is_typescript_enabled and p.lexer.token == T.t_enum) { return p.parseTypescriptEnumStmt(loc, opts); } const decls = try p.parseAndDeclareDecls(.constant, opts); try p.lexer.expectOrInsertSemicolon(); if (!opts.is_typescript_declare) { try p.requireInitializers(.k_const, decls.items); } return p.s(S.Local{ .kind = .k_const, .decls = Decl.List.fromList(decls), .is_export = opts.is_export }, loc); }, .t_if => { var current_loc = loc; var root_if: ?Stmt = null; var current_if: ?*S.If = null; while (true) { try p.lexer.next(); try p.lexer.expect(.t_open_paren); const test_ = try p.parseExpr(.lowest); try p.lexer.expect(.t_close_paren); var stmtOpts = ParseStatementOptions{ .lexical_decl = .allow_fn_inside_if, }; const yes = try p.parseStmt(&stmtOpts); // Create the if node const if_stmt = p.s(S.If{ .test_ = test_, .yes = yes, .no = null, }, current_loc); // First if statement becomes root if (root_if == null) { root_if = if_stmt; } // Link to previous if statement's else branch if (current_if) |prev_if| { prev_if.no = if_stmt; } // Set current if for next iteration current_if = if_stmt.data.s_if; if (p.lexer.token != .t_else) { return root_if.?; } try p.lexer.next(); // Handle final else if (p.lexer.token != .t_if) { stmtOpts = ParseStatementOptions{ .lexical_decl = .allow_fn_inside_if, }; current_if.?.no = try p.parseStmt(&stmtOpts); return root_if.?; } // Continue with else if current_loc = p.lexer.loc(); } unreachable; }, .t_do => { try p.lexer.next(); var stmtOpts = ParseStatementOptions{}; const body = try p.parseStmt(&stmtOpts); try p.lexer.expect(.t_while); try p.lexer.expect(.t_open_paren); const test_ = try p.parseExpr(.lowest); try p.lexer.expect(.t_close_paren); // This is a weird corner case where automatic semicolon insertion applies // even without a newline present if (p.lexer.token == .t_semicolon) { try p.lexer.next(); } return p.s(S.DoWhile{ .body = body, .test_ = test_ }, loc); }, .t_while => { try p.lexer.next(); try p.lexer.expect(.t_open_paren); const test_ = try p.parseExpr(.lowest); try p.lexer.expect(.t_close_paren); var stmtOpts = ParseStatementOptions{}; const body = try p.parseStmt(&stmtOpts); return p.s(S.While{ .body = body, .test_ = test_, }, loc); }, .t_with => { try p.lexer.next(); try p.lexer.expect(.t_open_paren); const test_ = try p.parseExpr(.lowest); const body_loc = p.lexer.loc(); try p.lexer.expect(.t_close_paren); // Push a scope so we make sure to prevent any bare identifiers referenced // within the body from being renamed. Renaming them might change the // semantics of the code. _ = try p.pushScopeForParsePass(.with, body_loc); var stmtOpts = ParseStatementOptions{}; const body = try p.parseStmt(&stmtOpts); p.popScope(); return p.s(S.With{ .body = body, .body_loc = body_loc, .value = test_ }, loc); }, .t_switch => { try p.lexer.next(); try p.lexer.expect(.t_open_paren); const test_ = try p.parseExpr(.lowest); try p.lexer.expect(.t_close_paren); const body_loc = p.lexer.loc(); _ = try p.pushScopeForParsePass(.block, body_loc); defer p.popScope(); try p.lexer.expect(.t_open_brace); var cases = ListManaged(js_ast.Case).init(p.allocator); var foundDefault = false; var stmtOpts = ParseStatementOptions{ .lexical_decl = .allow_all }; var value: ?js_ast.Expr = null; while (p.lexer.token != .t_close_brace) { var body = StmtList.init(p.allocator); value = null; if (p.lexer.token == .t_default) { if (foundDefault) { try p.log.addRangeError(p.source, p.lexer.range(), "Multiple default clauses are not allowed"); return error.SyntaxError; } foundDefault = true; try p.lexer.next(); try p.lexer.expect(.t_colon); } else { try p.lexer.expect(.t_case); value = try p.parseExpr(.lowest); try p.lexer.expect(.t_colon); } caseBody: while (true) { switch (p.lexer.token) { .t_close_brace, .t_case, .t_default => { break :caseBody; }, else => { stmtOpts = ParseStatementOptions{ .lexical_decl = .allow_all }; try body.append(try p.parseStmt(&stmtOpts)); }, } } try cases.append(js_ast.Case{ .value = value, .body = body.items, .loc = logger.Loc.Empty }); } try p.lexer.expect(.t_close_brace); return p.s(S.Switch{ .test_ = test_, .body_loc = body_loc, .cases = cases.items }, loc); }, .t_try => { try p.lexer.next(); const body_loc = p.lexer.loc(); try p.lexer.expect(.t_open_brace); _ = try p.pushScopeForParsePass(.block, loc); var stmt_opts = ParseStatementOptions{}; const body = try p.parseStmtsUpTo(.t_close_brace, &stmt_opts); p.popScope(); try p.lexer.next(); var catch_: ?js_ast.Catch = null; var finally: ?js_ast.Finally = null; if (p.lexer.token == .t_catch) { const catch_loc = p.lexer.loc(); _ = try p.pushScopeForParsePass(.catch_binding, catch_loc); try p.lexer.next(); var binding: ?js_ast.Binding = null; // The catch binding is optional, and can be omitted if (p.lexer.token != .t_open_brace) { try p.lexer.expect(.t_open_paren); var value = try p.parseBinding(.{}); // Skip over types if (is_typescript_enabled and p.lexer.token == .t_colon) { try p.lexer.expect(.t_colon); try p.skipTypeScriptType(.lowest); } try p.lexer.expect(.t_close_paren); // Bare identifiers are a special case var kind = Symbol.Kind.other; switch (value.data) { .b_identifier => { kind = .catch_identifier; }, else => {}, } try p.declareBinding(kind, &value, &stmt_opts); binding = value; } const catch_body_loc = p.lexer.loc(); try p.lexer.expect(.t_open_brace); _ = try p.pushScopeForParsePass(.block, catch_body_loc); const stmts = try p.parseStmtsUpTo(.t_close_brace, &stmt_opts); p.popScope(); try p.lexer.next(); catch_ = js_ast.Catch{ .loc = catch_loc, .binding = binding, .body = stmts, .body_loc = catch_body_loc, }; p.popScope(); } if (p.lexer.token == .t_finally or catch_ == null) { const finally_loc = p.lexer.loc(); _ = try p.pushScopeForParsePass(.block, finally_loc); try p.lexer.expect(.t_finally); try p.lexer.expect(.t_open_brace); const stmts = try p.parseStmtsUpTo(.t_close_brace, &stmt_opts); try p.lexer.next(); finally = js_ast.Finally{ .loc = finally_loc, .stmts = stmts }; p.popScope(); } return p.s( S.Try{ .body_loc = body_loc, .body = body, .catch_ = catch_, .finally = finally }, loc, ); }, .t_for => { _ = try p.pushScopeForParsePass(.block, loc); defer p.popScope(); try p.lexer.next(); // "for await (let x of y) {}" var isForAwait = p.lexer.isContextualKeyword("await"); if (isForAwait) { const await_range = p.lexer.range(); if (p.fn_or_arrow_data_parse.allow_await != .allow_expr) { try p.log.addRangeError(p.source, await_range, "Cannot use \"await\" outside an async function"); isForAwait = false; } else { // TODO: improve error handling here // didGenerateError := p.markSyntaxFeature(compat.ForAwait, awaitRange) if (p.fn_or_arrow_data_parse.is_top_level) { p.top_level_await_keyword = await_range; // p.markSyntaxFeature(compat.TopLevelAwait, awaitRange) } } try p.lexer.next(); } try p.lexer.expect(.t_open_paren); var init_: ?Stmt = null; var test_: ?Expr = null; var update: ?Expr = null; // "in" expressions aren't allowed here p.allow_in = false; var bad_let_range: ?logger.Range = null; if (p.lexer.isContextualKeyword("let")) { bad_let_range = p.lexer.range(); } var decls: G.Decl.List = .{}; const init_loc = p.lexer.loc(); var is_var = false; switch (p.lexer.token) { // for (var ) .t_var => { is_var = true; try p.lexer.next(); var stmtOpts = ParseStatementOptions{}; decls.update(try p.parseAndDeclareDecls(.hoisted, &stmtOpts)); init_ = p.s(S.Local{ .kind = .k_var, .decls = Decl.List.fromList(decls) }, init_loc); }, // for (const ) .t_const => { try p.lexer.next(); var stmtOpts = ParseStatementOptions{}; decls.update(try p.parseAndDeclareDecls(.constant, &stmtOpts)); init_ = p.s(S.Local{ .kind = .k_const, .decls = Decl.List.fromList(decls) }, init_loc); }, // for (;) .t_semicolon => {}, else => { var stmtOpts = ParseStatementOptions{ .lexical_decl = .allow_all, .is_for_loop_init = true, }; const res = try p.parseExprOrLetStmt(&stmtOpts); switch (res.stmt_or_expr) { .stmt => |stmt| { bad_let_range = null; init_ = stmt; }, .expr => |expr| { init_ = p.s(S.SExpr{ .value = expr, }, init_loc); }, } }, } // "in" expressions are allowed again p.allow_in = true; // Detect for-of loops if (p.lexer.isContextualKeyword("of") or isForAwait) { if (bad_let_range) |r| { try p.log.addRangeError(p.source, r, "\"let\" must be wrapped in parentheses to be used as an expression here"); return error.SyntaxError; } if (isForAwait and !p.lexer.isContextualKeyword("of")) { if (init_ != null) { try p.lexer.expectedString("\"of\""); } else { try p.lexer.unexpected(); return error.SyntaxError; } } try p.forbidInitializers(decls.slice(), "of", false); try p.lexer.next(); const value = try p.parseExpr(.comma); try p.lexer.expect(.t_close_paren); var stmtOpts = ParseStatementOptions{}; const body = try p.parseStmt(&stmtOpts); return p.s(S.ForOf{ .is_await = isForAwait, .init = init_ orelse unreachable, .value = value, .body = body }, loc); } // Detect for-in loops if (p.lexer.token == .t_in) { try p.forbidInitializers(decls.slice(), "in", is_var); try p.lexer.next(); const value = try p.parseExpr(.lowest); try p.lexer.expect(.t_close_paren); var stmtOpts = ParseStatementOptions{}; const body = try p.parseStmt(&stmtOpts); return p.s(S.ForIn{ .init = init_ orelse unreachable, .value = value, .body = body }, loc); } // Only require "const" statement initializers when we know we're a normal for loop if (init_) |init_stmt| { switch (init_stmt.data) { .s_local => { if (init_stmt.data.s_local.kind == .k_const) { try p.requireInitializers(.k_const, decls.slice()); } }, else => {}, } } try p.lexer.expect(.t_semicolon); if (p.lexer.token != .t_semicolon) { test_ = try p.parseExpr(.lowest); } try p.lexer.expect(.t_semicolon); if (p.lexer.token != .t_close_paren) { update = try p.parseExpr(.lowest); } try p.lexer.expect(.t_close_paren); var stmtOpts = ParseStatementOptions{}; const body = try p.parseStmt(&stmtOpts); return p.s( S.For{ .init = init_, .test_ = test_, .update = update, .body = body }, loc, ); }, .t_import => { const previous_import_keyword = p.esm_import_keyword; p.esm_import_keyword = p.lexer.range(); try p.lexer.next(); var stmt: S.Import = S.Import{ .namespace_ref = Ref.None, .import_record_index = std.math.maxInt(u32), }; var was_originally_bare_import = false; // "export import foo = bar" if ((opts.is_export or (opts.is_namespace_scope and !opts.is_typescript_declare)) and p.lexer.token != .t_identifier) { try p.lexer.expected(.t_identifier); } switch (p.lexer.token) { // "import('path')" // "import.meta" .t_open_paren, .t_dot => { p.esm_import_keyword = previous_import_keyword; // this wasn't an esm import statement after all const expr = try p.parseSuffix(try p.parseImportExpr(loc, .lowest), .lowest, null, Expr.EFlags.none); try p.lexer.expectOrInsertSemicolon(); return p.s(S.SExpr{ .value = expr, }, loc); }, .t_string_literal, .t_no_substitution_template_literal => { // "import 'path'" if (!opts.is_module_scope and (!opts.is_namespace_scope or !opts.is_typescript_declare)) { try p.lexer.unexpected(); return error.SyntaxError; } was_originally_bare_import = true; }, .t_asterisk => { // "import * as ns from 'path'" if (!opts.is_module_scope and (!opts.is_namespace_scope or !opts.is_typescript_declare)) { try p.lexer.unexpected(); return error.SyntaxError; } try p.lexer.next(); try p.lexer.expectContextualKeyword("as"); stmt = S.Import{ .namespace_ref = try p.storeNameInRef(p.lexer.identifier), .star_name_loc = p.lexer.loc(), .import_record_index = std.math.maxInt(u32), }; try p.lexer.expect(.t_identifier); try p.lexer.expectContextualKeyword("from"); }, .t_open_brace => { // "import {item1, item2} from 'path'" if (!opts.is_module_scope and (!opts.is_namespace_scope or !opts.is_typescript_declare)) { try p.lexer.unexpected(); return error.SyntaxError; } const importClause = try p.parseImportClause(); if (comptime is_typescript_enabled) { if (importClause.had_type_only_imports and importClause.items.len == 0) { try p.lexer.expectContextualKeyword("from"); _ = try p.parsePath(); try p.lexer.expectOrInsertSemicolon(); return p.s(S.TypeScript{}, loc); } } stmt = S.Import{ .namespace_ref = Ref.None, .import_record_index = std.math.maxInt(u32), .items = importClause.items, .is_single_line = importClause.is_single_line, }; try p.lexer.expectContextualKeyword("from"); }, .t_identifier => { // "import defaultItem from 'path'" // "import foo = bar" if (!opts.is_module_scope and (!opts.is_namespace_scope)) { try p.lexer.unexpected(); return error.SyntaxError; } var default_name = p.lexer.identifier; stmt = S.Import{ .namespace_ref = Ref.None, .import_record_index = std.math.maxInt(u32), .default_name = LocRef{ .loc = p.lexer.loc(), .ref = try p.storeNameInRef(default_name), } }; try p.lexer.next(); if (comptime is_typescript_enabled) { // Skip over type-only imports if (strings.eqlComptime(default_name, "type")) { switch (p.lexer.token) { .t_identifier => { if (!strings.eqlComptime(p.lexer.identifier, "from")) { default_name = p.lexer.identifier; stmt.default_name.?.loc = p.lexer.loc(); try p.lexer.next(); if (p.lexer.token == .t_equals) { // "import type foo = require('bar');" // "import type foo = bar.baz;" opts.is_typescript_declare = true; return try p.parseTypeScriptImportEqualsStmt(loc, opts, stmt.default_name.?.loc, default_name); } else { // "import type foo from 'bar';" try p.lexer.expectContextualKeyword("from"); _ = try p.parsePath(); try p.lexer.expectOrInsertSemicolon(); return p.s(S.TypeScript{}, loc); } } }, .t_asterisk => { // "import type * as foo from 'bar';" try p.lexer.next(); try p.lexer.expectContextualKeyword("as"); try p.lexer.expect(.t_identifier); try p.lexer.expectContextualKeyword("from"); _ = try p.parsePath(); try p.lexer.expectOrInsertSemicolon(); return p.s(S.TypeScript{}, loc); }, .t_open_brace => { // "import type {foo} from 'bar';" _ = try p.parseImportClause(); try p.lexer.expectContextualKeyword("from"); _ = try p.parsePath(); try p.lexer.expectOrInsertSemicolon(); return p.s(S.TypeScript{}, loc); }, else => {}, } } // Parse TypeScript import assignment statements if (p.lexer.token == .t_equals or opts.is_export or (opts.is_namespace_scope and !opts.is_typescript_declare)) { p.esm_import_keyword = previous_import_keyword; // This wasn't an ESM import statement after all; return p.parseTypeScriptImportEqualsStmt(loc, opts, logger.Loc.Empty, default_name); } } if (p.lexer.token == .t_comma) { try p.lexer.next(); switch (p.lexer.token) { // "import defaultItem, * as ns from 'path'" .t_asterisk => { try p.lexer.next(); try p.lexer.expectContextualKeyword("as"); stmt.namespace_ref = try p.storeNameInRef(p.lexer.identifier); stmt.star_name_loc = p.lexer.loc(); try p.lexer.expect(.t_identifier); }, // "import defaultItem, {item1, item2} from 'path'" .t_open_brace => { const importClause = try p.parseImportClause(); stmt.items = importClause.items; stmt.is_single_line = importClause.is_single_line; }, else => { try p.lexer.unexpected(); return error.SyntaxError; }, } } try p.lexer.expectContextualKeyword("from"); }, else => { try p.lexer.unexpected(); return error.SyntaxError; }, } const path = try p.parsePath(); try p.lexer.expectOrInsertSemicolon(); return try p.processImportStatement(stmt, path, loc, was_originally_bare_import); }, .t_break => { try p.lexer.next(); const name = try p.parseLabelName(); try p.lexer.expectOrInsertSemicolon(); return p.s(S.Break{ .label = name }, loc); }, .t_continue => { try p.lexer.next(); const name = try p.parseLabelName(); try p.lexer.expectOrInsertSemicolon(); return p.s(S.Continue{ .label = name }, loc); }, .t_return => { if (p.fn_or_arrow_data_parse.is_return_disallowed) { try p.log.addRangeError(p.source, p.lexer.range(), "A return statement cannot be used here"); } try p.lexer.next(); var value: ?Expr = null; if ((p.lexer.token != .t_semicolon and !p.lexer.has_newline_before and p.lexer.token != .t_close_brace and p.lexer.token != .t_end_of_file)) { value = try p.parseExpr(.lowest); } p.latest_return_had_semicolon = p.lexer.token == .t_semicolon; try p.lexer.expectOrInsertSemicolon(); return p.s(S.Return{ .value = value }, loc); }, .t_throw => { try p.lexer.next(); if (p.lexer.has_newline_before) { try p.log.addError(p.source, logger.Loc{ .start = loc.start + 5, }, "Unexpected newline after \"throw\""); return error.SyntaxError; } const expr = try p.parseExpr(.lowest); try p.lexer.expectOrInsertSemicolon(); return p.s(S.Throw{ .value = expr }, loc); }, .t_debugger => { try p.lexer.next(); try p.lexer.expectOrInsertSemicolon(); return p.s(S.Debugger{}, loc); }, .t_open_brace => { _ = try p.pushScopeForParsePass(.block, loc); defer p.popScope(); try p.lexer.next(); var stmtOpts = ParseStatementOptions{}; const stmts = try p.parseStmtsUpTo(.t_close_brace, &stmtOpts); const close_brace_loc = p.lexer.loc(); try p.lexer.next(); return p.s(S.Block{ .stmts = stmts, .close_brace_loc = close_brace_loc, }, loc); }, else => { const is_identifier = p.lexer.token == .t_identifier; const name = p.lexer.identifier; // Parse either an async function, an async expression, or a normal expression var expr: Expr = Expr{ .loc = loc, .data = Expr.Data{ .e_missing = .{} } }; if (is_identifier and strings.eqlComptime(p.lexer.raw(), "async")) { const async_range = p.lexer.range(); try p.lexer.next(); if (p.lexer.token == .t_function and !p.lexer.has_newline_before) { try p.lexer.next(); return try p.parseFnStmt(async_range.loc, opts, async_range); } expr = try p.parseSuffix(try p.parseAsyncPrefixExpr(async_range, .lowest), .lowest, null, Expr.EFlags.none); } else { const exprOrLet = try p.parseExprOrLetStmt(opts); switch (exprOrLet.stmt_or_expr) { .stmt => |stmt| { try p.lexer.expectOrInsertSemicolon(); return stmt; }, .expr => |_expr| { expr = _expr; }, } } if (is_identifier) { switch (expr.data) { .e_identifier => |ident| { if (p.lexer.token == .t_colon and !opts.hasDecorators()) { _ = try p.pushScopeForParsePass(.label, loc); defer p.popScope(); // Parse a labeled statement try p.lexer.next(); const _name = LocRef{ .loc = expr.loc, .ref = ident.ref }; var nestedOpts = ParseStatementOptions{}; switch (opts.lexical_decl) { .allow_all, .allow_fn_inside_label => { nestedOpts.lexical_decl = .allow_fn_inside_label; }, else => {}, } const stmt = try p.parseStmt(&nestedOpts); return p.s(S.Label{ .name = _name, .stmt = stmt }, loc); } }, else => {}, } if (is_typescript_enabled) { if (js_lexer.TypescriptStmtKeyword.List.get(name)) |ts_stmt| { switch (ts_stmt) { .ts_stmt_type => { if (p.lexer.token == .t_identifier and !p.lexer.has_newline_before) { // "type Foo = any" var stmtOpts = ParseStatementOptions{ .is_module_scope = opts.is_module_scope }; try p.skipTypeScriptTypeStmt(&stmtOpts); return p.s(S.TypeScript{}, loc); } }, .ts_stmt_namespace, .ts_stmt_module => { // "namespace Foo {}" // "module Foo {}" // "declare module 'fs' {}" // "declare module 'fs';" if (((opts.is_module_scope or opts.is_namespace_scope) and (p.lexer.token == .t_identifier or (p.lexer.token == .t_string_literal and opts.is_typescript_declare)))) { return p.parseTypeScriptNamespaceStmt(loc, opts); } }, .ts_stmt_interface => { // "interface Foo {}" var stmtOpts = ParseStatementOptions{ .is_module_scope = opts.is_module_scope }; try p.skipTypeScriptInterfaceStmt(&stmtOpts); return p.s(S.TypeScript{}, loc); }, .ts_stmt_abstract => { if (p.lexer.token == .t_class or opts.ts_decorators != null) { return try p.parseClassStmt(loc, opts); } }, .ts_stmt_global => { // "declare module 'fs' { global { namespace NodeJS {} } }" if (opts.is_namespace_scope and opts.is_typescript_declare and p.lexer.token == .t_open_brace) { try p.lexer.next(); _ = try p.parseStmtsUpTo(.t_close_brace, opts); try p.lexer.next(); return p.s(S.TypeScript{}, loc); } }, .ts_stmt_declare => { opts.lexical_decl = .allow_all; opts.is_typescript_declare = true; // "@decorator declare class Foo {}" // "@decorator declare abstract class Foo {}" if (opts.ts_decorators != null and p.lexer.token != .t_class and !p.lexer.isContextualKeyword("abstract")) { try p.lexer.expected(.t_class); } // "declare global { ... }" if (p.lexer.isContextualKeyword("global")) { try p.lexer.next(); try p.lexer.expect(.t_open_brace); _ = try p.parseStmtsUpTo(.t_close_brace, opts); try p.lexer.next(); return p.s(S.TypeScript{}, loc); } // "declare const x: any" const stmt = try p.parseStmt(opts); if (opts.ts_decorators) |decs| { p.discardScopesUpTo(decs.scope_index); } // Unlike almost all uses of "declare", statements that use // "export declare" with "var/let/const" inside a namespace affect // code generation. They cause any declared bindings to be // considered exports of the namespace. Identifier references to // those names must be converted into property accesses off the // namespace object: // // namespace ns { // export declare const x // export function y() { return x } // } // // (ns as any).x = 1 // console.log(ns.y()) // // In this example, "return x" must be replaced with "return ns.x". // This is handled by replacing each "export declare" statement // inside a namespace with an "export var" statement containing all // of the declared bindings. That "export var" statement will later // cause identifiers to be transformed into property accesses. if (opts.is_namespace_scope and opts.is_export) { var decls: G.Decl.List = .{}; switch (stmt.data) { .s_local => |local| { var _decls = try ListManaged(G.Decl).initCapacity(p.allocator, local.decls.len); for (local.decls.slice()) |decl| { try extractDeclsForBinding(decl.binding, &_decls); } decls.update(_decls); }, else => {}, } if (decls.len > 0) { return p.s(S.Local{ .kind = .k_var, .is_export = true, .decls = decls, }, loc); } } return p.s(S.TypeScript{}, loc); }, } } } } // Output.print("\n\nmVALUE {s}:{s}\n", .{ expr, name }); try p.lexer.expectOrInsertSemicolon(); return p.s(S.SExpr{ .value = expr }, loc); }, } return js_ast.Stmt.empty(); } }; } const bun = @import("bun"); const Output = bun.Output; const logger = bun.logger; const strings = bun.strings; const js_ast = bun.ast; const Binding = js_ast.Binding; const Expr = js_ast.Expr; const LocRef = js_ast.LocRef; const S = js_ast.S; const Stmt = js_ast.Stmt; const Symbol = js_ast.Symbol; const G = js_ast.G; const Decl = G.Decl; const Op = js_ast.Op; const Level = js_ast.Op.Level; const js_lexer = bun.js_lexer; const T = js_lexer.T; const js_parser = bun.js_parser; const DeferredTsDecorators = js_parser.DeferredTsDecorators; const ImportKind = js_parser.ImportKind; const JSXTransformType = js_parser.JSXTransformType; const ParseStatementOptions = js_parser.ParseStatementOptions; const ParsedPath = js_parser.ParsedPath; const Ref = js_parser.Ref; const StmtList = js_parser.StmtList; const TypeScript = js_parser.TypeScript; const fs = js_parser.fs; const std = @import("std"); const List = std.ArrayListUnmanaged; const ListManaged = std.ArrayList;