diff options
author | 2021-04-23 18:03:03 -0700 | |
---|---|---|
committer | 2021-04-23 18:03:03 -0700 | |
commit | 06b69619b8ea14436dfc9de6e33be892636cd8e3 (patch) | |
tree | d0f23b39477d1fe6b83223c1a6ad59c65f939ba8 /src | |
parent | 6495a07c3120c0d4fb96e98334eb32deaf00df71 (diff) | |
download | bun-06b69619b8ea14436dfc9de6e33be892636cd8e3.tar.gz bun-06b69619b8ea14436dfc9de6e33be892636cd8e3.tar.zst bun-06b69619b8ea14436dfc9de6e33be892636cd8e3.zip |
I believe those are the Stmt!!!!!!
Diffstat (limited to 'src')
-rw-r--r-- | src/js_lexer.zig | 3 | ||||
-rw-r--r-- | src/js_lexer_tables.zig | 41 | ||||
-rw-r--r-- | src/js_parser.zig | 223 |
3 files changed, 265 insertions, 2 deletions
diff --git a/src/js_lexer.zig b/src/js_lexer.zig index cf8f4e863..a16c72c19 100644 --- a/src/js_lexer.zig +++ b/src/js_lexer.zig @@ -20,6 +20,7 @@ pub const tokenToString = tables.tokenToString; pub const jsxEntity = tables.jsxEntity; pub const StrictModeReservedWords = tables.StrictModeReservedWords; pub const PropertyModifierKeyword = tables.PropertyModifierKeyword; +pub const TypescriptStmtKeyword = tables.TypescriptStmtKeyword; // TODO: JSON const IS_JSON_FILE = false; @@ -265,7 +266,7 @@ pub const Lexer = struct { } } - pub fn expect(self: *Lexer, token: T) void { + pub fn expect(self: *Lexer, comptime token: T) void { if (self.token != token) { self.expected(token); } diff --git a/src/js_lexer_tables.zig b/src/js_lexer_tables.zig index 0ae73eac5..0053b7234 100644 --- a/src/js_lexer_tables.zig +++ b/src/js_lexer_tables.zig @@ -485,6 +485,47 @@ pub const tokenToString = comptime { return tokenEnums; }; +pub const TypescriptStmtKeyword = enum { + ts_stmt_type, + ts_stmt_namespace, + ts_stmt_module, + ts_stmt_interface, + ts_stmt_abstract, + ts_stmt_global, + ts_stmt_declare, + + pub const List = std.ComptimeStringMap(TypescriptStmtKeyword, .{ + .{ + "type", + TypescriptStmtKeyword.ts_stmt_type, + }, + .{ + "namespace", + TypescriptStmtKeyword.ts_stmt_namespace, + }, + .{ + "module", + TypescriptStmtKeyword.ts_stmt_module, + }, + .{ + "interface", + TypescriptStmtKeyword.ts_stmt_interface, + }, + .{ + "abstract", + TypescriptStmtKeyword.ts_stmt_abstract, + }, + .{ + "global", + TypescriptStmtKeyword.ts_stmt_global, + }, + .{ + "declare", + TypescriptStmtKeyword.ts_stmt_declare, + }, + }); +}; + pub const JSXEntityMap = std.StringHashMap(CodePoint); pub var jsxEntity: JSXEntityMap = undefined; diff --git a/src/js_parser.zig b/src/js_parser.zig index 0f0149cea..a9775dcfb 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -2239,13 +2239,215 @@ const P = struct { }, else => { - notimpl(); + const is_identifier = p.lexer.token == .t_identifier; + const name = p.lexer.identifier; + var emiss = E.Missing{}; + // Parse either an async function, an async expression, or a normal expression + var expr: Expr = Expr{ .loc = loc, .data = Expr.Data{ .e_missing = &emiss } }; + if (is_identifier and strings.eql(p.lexer.raw(), "async")) { + var async_range = p.lexer.range(); + p.lexer.next(); + if (p.lexer.token == .t_function and !p.lexer.has_newline_before) { + p.lexer.next(); + return try p.parseFnStmt(async_range.loc, opts, async_range); + } + + expr = 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| { + p.lexer.expectOrInsertSemicolon(); + return stmt; + }, + else => {}, + } + } + + if (is_identifier) { + switch (expr.data) { + .e_identifier => |ident| { + if (p.lexer.token == .t_colon and opts.ts_decorators == null) { + _ = try p.pushScopeForParsePass(.label, loc); + defer p.popScope(); + + // Parse a labeled statement + 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 => {}, + } + var stmt = p.parseStmt(&nestedOpts) catch unreachable; + return p.s(S.Label{ .name = _name, .stmt = stmt }, loc); + } + }, + else => {}, + } + + if (p.options.ts) { + 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 }; + 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.parseTypescriptNamespaceTmt(loc, opts); + } + }, + .ts_stmt_interface => { + // "interface Foo {}" + var stmtOpts = ParseStatementOptions{ .is_module_scope = opts.is_module_scope }; + + p.skipTypeScriptInterfaceStmt(&stmtOpts); + return p.s(S.TypeScript{}, loc); + }, + .ts_stmt_abstract => { + if (p.lexer.token == .t_class or opts.ts_decorators != null) { + return 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) { + p.lexer.next(); + _ = p.parseStmtsUpTo(.t_close_brace, opts) catch unreachable; + 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")) { + p.lexer.expected(.t_class); + } + + // "declare global { ... }" + if (p.lexer.isContextualKeyword("global")) { + p.lexer.next(); + p.lexer.expect(.t_open_brace); + _ = p.parseStmtsUpTo(.t_close_brace, opts) catch unreachable; + p.lexer.next(); + return p.s(S.TypeScript{}, loc); + } + + // "declare const x: any" + const stmt = p.parseStmt(opts) catch unreachable; + 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 = &([_]G.Decl{}); + switch (stmt.data) { + .s_local => |local| { + var _decls = try List(G.Decl).initCapacity(p.allocator, local.decls.len); + for (local.decls) |decl| { + try extractDeclsForBinding(decl.binding, &_decls); + } + decls = _decls.toOwnedSlice(); + }, + 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); + }, + } + } + } + } + + p.lexer.expectOrInsertSemicolon(); + return p.s(S.SExpr{ .value = expr }, loc); }, } return js_ast.Stmt.empty(); } + pub fn discardScopesUpTo(p: *P, scope_index: usize) void { + // Remove any direct children from their parent + var scope = p.current_scope orelse unreachable; + var children = scope.children; + for (p.scopes_in_order.items[scope_index..]) |child| { + if (child.scope.parent == p.current_scope) { + var i: usize = children.items.len - 1; + while (i >= 0) { + if (children.items[i] == child.scope) { + _ = children.orderedRemove(i); + break; + } + i -= 1; + } + } + } + + // Truncate the scope order where we started to pretend we never saw this scope + p.scopes_in_order.shrinkAndFree(scope_index); + } + + pub fn skipTypescriptTypeStmt(p: *P, opts: *ParseStatementOptions) void { + notimpl(); + } + + pub fn parseTypescriptNamespaceTmt(p: *P, loc: logger.Loc, opts: *ParseStatementOptions) Stmt { + notimpl(); + } + + pub fn skipTypeScriptInterfaceStmt(p: *P, opts: *ParseStatementOptions) void { + notimpl(); + } + pub fn parseTypeScriptImportEqualsStmt(p: *P, loc: logger.Loc, opts: *ParseStatementOptions, default_name_loc: logger.Loc, default_name: string) Stmt { notimpl(); } @@ -5342,6 +5544,25 @@ const P = struct { return p._parsePrefix(level, errors orelse &DeferredErrors.None, flags); } + fn extractDeclsForBinding(binding: Binding, decls: *List(G.Decl)) !void { + switch (binding.data) { + .b_property, .b_missing => {}, + .b_identifier => { + try decls.append(G.Decl{ .binding = binding }); + }, + .b_array => |arr| { + for (arr.items) |item| { + extractDeclsForBinding(item.binding, decls) catch unreachable; + } + }, + .b_object => |obj| { + for (obj.properties) |prop| { + extractDeclsForBinding(prop.value, decls) catch unreachable; + } + }, + } + } + // This assumes that the open parenthesis has already been parsed by the caller pub fn parseParenExpr(p: *P, loc: logger.Loc, opts: ParenExprOpts) !Expr { var items_list = try List(Expr).initCapacity(p.allocator, 1); |