Files
bun.sh/src/ast/parseStmt.zig
Jarred Sumner f9d3ed55f4 try
2025-08-06 22:24:19 -07:00

1371 lines
71 KiB
Zig

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);
var expr = try p.parseAsyncPrefixExpr(async_range, Level.comma);
try p.parseSuffix(&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
var expr = try p.parseImportExpr(loc, .lowest);
try p.parseSuffix(&expr, .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.parseAsyncPrefixExpr(async_range, .lowest);
try p.parseSuffix(&expr, .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;