mirror of
https://github.com/oven-sh/bun
synced 2026-02-11 03:18:53 +00:00
1371 lines
71 KiB
Zig
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;
|