From de976f2ffe387d8214db0d25e375566505fd53ed Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Fri, 23 Apr 2021 12:30:37 -0700 Subject: [PATCH] exports! --- src/import_record.zig | 14 +- src/js_ast.zig | 558 ++++++++++++++++++++------------------- src/js_lexer.zig | 6 +- src/js_parser.zig | 203 +++++++++++++- src/logger.zig | 27 ++ src/string_immutable.zig | 4 + 6 files changed, 528 insertions(+), 284 deletions(-) diff --git a/src/import_record.zig b/src/import_record.zig index 8570506556..60621379f3 100644 --- a/src/import_record.zig +++ b/src/import_record.zig @@ -35,30 +35,30 @@ pub const ImportRecord = struct { // Sometimes the parser creates an import record and decides it isn't needed. // For example, TypeScript code may have import statements that later turn // out to be type-only imports after analyzing the whole file. - is_unused: bool, + is_unused: bool = false, // If this is true, the import contains syntax like "* as ns". This is used // to determine whether modules that have no exports need to be wrapped in a // CommonJS wrapper or not. - contains_import_star: bool, + contains_import_star: bool = false, // If this is true, the import contains an import for the alias "default", // either via the "import x from" or "import {default as x} from" syntax. - contains_default_alias: bool, + contains_default_alias: bool = false, // If true, this "export * from 'path'" statement is evaluated at run-time by // calling the "__reExport()" helper function - calls_run_time_re_export_fn: bool, + calls_run_time_re_export_fn: bool = false, // Tell the printer to wrap this call to "require()" in "__toModule(...)" - wrap_with_to_module: bool, + wrap_with_to_module: bool = false, // True for require calls like this: "try { require() } catch {}". In this // case we shouldn't generate an error if the path could not be resolved. - is_inside_try_body: bool, + is_inside_try_body: bool = false, // If true, this was originally written as a bare "import 'file'" statement - was_originally_bare_import: bool, + was_originally_bare_import: bool = false, kind: ImportKind, }; diff --git a/src/js_ast.zig b/src/js_ast.zig index ee6e31c183..6054c0ae8a 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -237,6 +237,15 @@ pub const G = struct { alias: string, }; + pub const ExportStarAlias = struct { + loc: logger.Loc, + + // Although this alias name starts off as being the same as the statement's + // namespace symbol, it may diverge if the namespace symbol name is minified. + // The original alias name is preserved here to avoid this scenario. + original_name: string, + }; + pub const Class = struct { class_keyword: logger.Range = logger.Range.None, ts_decorators: ExprNodeList = &([_]Expr{}), @@ -822,252 +831,228 @@ pub const Stmt = struct { var None = S.Empty{}; - pub fn init(st: anytype, loc: logger.Loc) Stmt { - if (@typeInfo(@TypeOf(st)) != .Pointer) { + pub fn init(origData: anytype, loc: logger.Loc) Stmt { + if (@typeInfo(@TypeOf(origData)) != .Pointer) { @compileError("Stmt.init needs a pointer."); } - switch (@TypeOf(st.*)) { + switch (@TypeOf(origData.*)) { S.Block => { - return Stmt{ .loc = loc, .data = Data{ .s_block = st } }; - }, - S.SExpr => { - return Stmt{ .loc = loc, .data = Data{ .s_expr = st } }; - }, - S.Comment => { - return Stmt{ .loc = loc, .data = Data{ .s_comment = st } }; - }, - S.Directive => { - return Stmt{ .loc = loc, .data = Data{ .s_directive = st } }; - }, - S.ExportClause => { - return Stmt{ .loc = loc, .data = Data{ .s_export_clause = st } }; - }, - S.Empty => { - return Stmt{ .loc = loc, .data = Data{ .s_empty = st } }; - }, - S.TypeScript => { - return Stmt{ .loc = loc, .data = Data{ .s_type_script = st } }; - }, - S.Debugger => { - return Stmt{ .loc = loc, .data = Data{ .s_debugger = st } }; - }, - S.ExportFrom => { - return Stmt{ .loc = loc, .data = Data{ .s_export_from = st } }; - }, - S.ExportDefault => { - return Stmt{ .loc = loc, .data = Data{ .s_export_default = st } }; - }, - S.Enum => { - return Stmt{ .loc = loc, .data = Data{ .s_enum = st } }; - }, - S.Namespace => { - return Stmt{ .loc = loc, .data = Data{ .s_namespace = st } }; - }, - S.Function => { - return Stmt{ .loc = loc, .data = Data{ .s_function = st } }; - }, - S.Class => { - return Stmt{ .loc = loc, .data = Data{ .s_class = st } }; - }, - S.If => { - return Stmt{ .loc = loc, .data = Data{ .s_if = st } }; - }, - S.For => { - return Stmt{ .loc = loc, .data = Data{ .s_for = st } }; - }, - S.ForIn => { - return Stmt{ .loc = loc, .data = Data{ .s_for_in = st } }; - }, - S.ForOf => { - return Stmt{ .loc = loc, .data = Data{ .s_for_of = st } }; - }, - S.DoWhile => { - return Stmt{ .loc = loc, .data = Data{ .s_do_while = st } }; - }, - S.While => { - return Stmt{ .loc = loc, .data = Data{ .s_while = st } }; - }, - S.With => { - return Stmt{ .loc = loc, .data = Data{ .s_with = st } }; - }, - S.Try => { - return Stmt{ .loc = loc, .data = Data{ .s_try = st } }; - }, - S.Switch => { - return Stmt{ .loc = loc, .data = Data{ .s_switch = st } }; - }, - S.Import => { - return Stmt{ .loc = loc, .data = Data{ .s_import = st } }; - }, - S.Return => { - return Stmt{ .loc = loc, .data = Data{ .s_return = st } }; - }, - S.Throw => { - return Stmt{ .loc = loc, .data = Data{ .s_throw = st } }; - }, - S.Local => { - return Stmt{ .loc = loc, .data = Data{ .s_local = st } }; + return Stmt.comptime_init("s_block", S.Block, origData, loc); }, S.Break => { - return Stmt{ .loc = loc, .data = Data{ .s_break = st } }; + return Stmt.comptime_init("s_break", S.Break, origData, loc); + }, + S.Class => { + return Stmt.comptime_init("s_class", S.Class, origData, loc); + }, + S.Comment => { + return Stmt.comptime_init("s_comment", S.Comment, origData, loc); }, S.Continue => { - return Stmt{ .loc = loc, .data = Data{ .s_continue = st } }; + return Stmt.comptime_init("s_continue", S.Continue, origData, loc); + }, + S.Debugger => { + return Stmt.comptime_init("s_debugger", S.Debugger, origData, loc); + }, + S.Directive => { + return Stmt.comptime_init("s_directive", S.Directive, origData, loc); + }, + S.DoWhile => { + return Stmt.comptime_init("s_do_while", S.DoWhile, origData, loc); + }, + S.Empty => { + return Stmt.comptime_init("s_empty", S.Empty, origData, loc); + }, + S.Enum => { + return Stmt.comptime_init("s_enum", S.Enum, origData, loc); + }, + S.ExportClause => { + return Stmt.comptime_init("s_export_clause", S.ExportClause, origData, loc); + }, + S.ExportDefault => { + return Stmt.comptime_init("s_export_default", S.ExportDefault, origData, loc); + }, + S.ExportEquals => { + return Stmt.comptime_init("s_export_equals", S.ExportEquals, origData, loc); + }, + S.ExportFrom => { + return Stmt.comptime_init("s_export_from", S.ExportFrom, origData, loc); + }, + S.ExportStar => { + return Stmt.comptime_init("s_export_star", S.ExportStar, origData, loc); + }, + S.SExpr => { + return Stmt.comptime_init("s_expr", S.SExpr, origData, loc); + }, + S.ForIn => { + return Stmt.comptime_init("s_for_in", S.ForIn, origData, loc); + }, + S.ForOf => { + return Stmt.comptime_init("s_for_of", S.ForOf, origData, loc); + }, + S.For => { + return Stmt.comptime_init("s_for", S.For, origData, loc); + }, + S.Function => { + return Stmt.comptime_init("s_function", S.Function, origData, loc); + }, + S.If => { + return Stmt.comptime_init("s_if", S.If, origData, loc); + }, + S.Import => { + return Stmt.comptime_init("s_import", S.Import, origData, loc); + }, + S.Label => { + return Stmt.comptime_init("s_label", S.Label, origData, loc); + }, + S.LazyExport => { + return Stmt.comptime_init("s_lazy_export", S.LazyExport, origData, loc); + }, + S.Local => { + return Stmt.comptime_init("s_local", S.Local, origData, loc); + }, + S.Namespace => { + return Stmt.comptime_init("s_namespace", S.Namespace, origData, loc); + }, + S.Return => { + return Stmt.comptime_init("s_return", S.Return, origData, loc); + }, + S.Switch => { + return Stmt.comptime_init("s_switch", S.Switch, origData, loc); + }, + S.Throw => { + return Stmt.comptime_init("s_throw", S.Throw, origData, loc); + }, + S.Try => { + return Stmt.comptime_init("s_try", S.Try, origData, loc); + }, + S.TypeScript => { + return Stmt.comptime_init("s_type_script", S.TypeScript, origData, loc); + }, + S.While => { + return Stmt.comptime_init("s_while", S.While, origData, loc); + }, + S.With => { + return Stmt.comptime_init("s_with", S.With, origData, loc); }, else => { @compileError("Invalid type in Stmt.init"); }, } } + fn comptime_alloc(allocator: *std.mem.Allocator, comptime tag_name: string, comptime typename: type, origData: anytype, loc: logger.Loc) callconv(.Inline) Stmt { + var st = allocator.create(typename) catch unreachable; + st.* = origData; + return Stmt{ .loc = loc, .data = @unionInit(Data, tag_name, st) }; + } + + fn comptime_init(comptime tag_name: string, comptime typename: type, origData: anytype, loc: logger.Loc) callconv(.Inline) Stmt { + return Stmt{ .loc = loc, .data = @unionInit(Data, tag_name, origData) }; + } pub fn alloc(allocator: *std.mem.Allocator, origData: anytype, loc: logger.Loc) Stmt { switch (@TypeOf(origData)) { S.Block => { - var st = allocator.create(S.Block) catch unreachable; - st.* = origData; - return Stmt{ .loc = loc, .data = Data{ .s_block = st } }; - }, - S.SExpr => { - var st = allocator.create(S.SExpr) catch unreachable; - st.* = origData; - return Stmt{ .loc = loc, .data = Data{ .s_expr = st } }; - }, - S.Comment => { - var st = allocator.create(S.Comment) catch unreachable; - st.* = origData; - return Stmt{ .loc = loc, .data = Data{ .s_comment = st } }; - }, - S.Directive => { - var st = allocator.create(S.Directive) catch unreachable; - st.* = origData; - return Stmt{ .loc = loc, .data = Data{ .s_directive = st } }; - }, - S.ExportClause => { - var st = allocator.create(S.ExportClause) catch unreachable; - st.* = origData; - return Stmt{ .loc = loc, .data = Data{ .s_export_clause = st } }; - }, - S.Empty => { - var st = allocator.create(S.Empty) catch unreachable; - st.* = origData; - return Stmt{ .loc = loc, .data = Data{ .s_empty = st } }; - }, - S.TypeScript => { - var st = allocator.create(S.TypeScript) catch unreachable; - st.* = origData; - return Stmt{ .loc = loc, .data = Data{ .s_type_script = st } }; - }, - S.Debugger => { - var st = allocator.create(S.Debugger) catch unreachable; - st.* = origData; - return Stmt{ .loc = loc, .data = Data{ .s_debugger = st } }; - }, - S.ExportFrom => { - var st = allocator.create(S.ExportFrom) catch unreachable; - st.* = origData; - return Stmt{ .loc = loc, .data = Data{ .s_export_from = st } }; - }, - S.ExportDefault => { - var st = allocator.create(S.ExportDefault) catch unreachable; - st.* = origData; - return Stmt{ .loc = loc, .data = Data{ .s_export_default = st } }; - }, - S.Enum => { - var st = allocator.create(S.Enum) catch unreachable; - st.* = origData; - return Stmt{ .loc = loc, .data = Data{ .s_enum = st } }; - }, - S.Namespace => { - var st = allocator.create(S.Namespace) catch unreachable; - st.* = origData; - return Stmt{ .loc = loc, .data = Data{ .s_namespace = st } }; - }, - S.Function => { - var st = allocator.create(S.Function) catch unreachable; - st.* = origData; - return Stmt{ .loc = loc, .data = Data{ .s_function = st } }; - }, - S.Class => { - var st = allocator.create(S.Class) catch unreachable; - st.* = origData; - return Stmt{ .loc = loc, .data = Data{ .s_class = st } }; - }, - S.If => { - var st = allocator.create(S.If) catch unreachable; - st.* = origData; - return Stmt{ .loc = loc, .data = Data{ .s_if = st } }; - }, - S.For => { - var st = allocator.create(S.For) catch unreachable; - st.* = origData; - return Stmt{ .loc = loc, .data = Data{ .s_for = st } }; - }, - S.ForIn => { - var st = allocator.create(S.ForIn) catch unreachable; - st.* = origData; - return Stmt{ .loc = loc, .data = Data{ .s_for_in = st } }; - }, - S.ForOf => { - var st = allocator.create(S.ForOf) catch unreachable; - st.* = origData; - return Stmt{ .loc = loc, .data = Data{ .s_for_of = st } }; - }, - S.DoWhile => { - var st = allocator.create(S.DoWhile) catch unreachable; - st.* = origData; - return Stmt{ .loc = loc, .data = Data{ .s_do_while = st } }; - }, - S.While => { - var st = allocator.create(S.While) catch unreachable; - st.* = origData; - return Stmt{ .loc = loc, .data = Data{ .s_while = st } }; - }, - S.With => { - var st = allocator.create(S.With) catch unreachable; - st.* = origData; - return Stmt{ .loc = loc, .data = Data{ .s_with = st } }; - }, - S.Try => { - var st = allocator.create(S.Try) catch unreachable; - st.* = origData; - return Stmt{ .loc = loc, .data = Data{ .s_try = st } }; - }, - S.Switch => { - var st = allocator.create(S.Switch) catch unreachable; - st.* = origData; - return Stmt{ .loc = loc, .data = Data{ .s_switch = st } }; - }, - S.Import => { - var st = allocator.create(S.Import) catch unreachable; - st.* = origData; - return Stmt{ .loc = loc, .data = Data{ .s_import = st } }; - }, - S.Return => { - var st = allocator.create(S.Return) catch unreachable; - st.* = origData; - return Stmt{ .loc = loc, .data = Data{ .s_return = st } }; - }, - S.Throw => { - var st = allocator.create(S.Throw) catch unreachable; - st.* = origData; - return Stmt{ .loc = loc, .data = Data{ .s_throw = st } }; - }, - S.Local => { - var st = allocator.create(S.Local) catch unreachable; - st.* = origData; - return Stmt{ .loc = loc, .data = Data{ .s_local = st } }; + return Stmt.comptime_alloc(allocator, "s_block", S.Block, origData, loc); }, S.Break => { - var st = allocator.create(S.Break) catch unreachable; - st.* = origData; - return Stmt{ .loc = loc, .data = Data{ .s_break = st } }; + return Stmt.comptime_alloc(allocator, "s_break", S.Break, origData, loc); + }, + S.Class => { + return Stmt.comptime_alloc(allocator, "s_class", S.Class, origData, loc); + }, + S.Comment => { + return Stmt.comptime_alloc(allocator, "s_comment", S.Comment, origData, loc); }, S.Continue => { - var st = allocator.create(S.Continue) catch unreachable; - st.* = origData; - return Stmt{ .loc = loc, .data = Data{ .s_continue = st } }; + return Stmt.comptime_alloc(allocator, "s_continue", S.Continue, origData, loc); }, + S.Debugger => { + return Stmt.comptime_alloc(allocator, "s_debugger", S.Debugger, origData, loc); + }, + S.Directive => { + return Stmt.comptime_alloc(allocator, "s_directive", S.Directive, origData, loc); + }, + S.DoWhile => { + return Stmt.comptime_alloc(allocator, "s_do_while", S.DoWhile, origData, loc); + }, + S.Empty => { + return Stmt.comptime_alloc(allocator, "s_empty", S.Empty, origData, loc); + }, + S.Enum => { + return Stmt.comptime_alloc(allocator, "s_enum", S.Enum, origData, loc); + }, + S.ExportClause => { + return Stmt.comptime_alloc(allocator, "s_export_clause", S.ExportClause, origData, loc); + }, + S.ExportDefault => { + return Stmt.comptime_alloc(allocator, "s_export_default", S.ExportDefault, origData, loc); + }, + S.ExportEquals => { + return Stmt.comptime_alloc(allocator, "s_export_equals", S.ExportEquals, origData, loc); + }, + S.ExportFrom => { + return Stmt.comptime_alloc(allocator, "s_export_from", S.ExportFrom, origData, loc); + }, + S.ExportStar => { + return Stmt.comptime_alloc(allocator, "s_export_star", S.ExportStar, origData, loc); + }, + S.SExpr => { + return Stmt.comptime_alloc(allocator, "s_expr", S.SExpr, origData, loc); + }, + S.ForIn => { + return Stmt.comptime_alloc(allocator, "s_for_in", S.ForIn, origData, loc); + }, + S.ForOf => { + return Stmt.comptime_alloc(allocator, "s_for_of", S.ForOf, origData, loc); + }, + S.For => { + return Stmt.comptime_alloc(allocator, "s_for", S.For, origData, loc); + }, + S.Function => { + return Stmt.comptime_alloc(allocator, "s_function", S.Function, origData, loc); + }, + S.If => { + return Stmt.comptime_alloc(allocator, "s_if", S.If, origData, loc); + }, + S.Import => { + return Stmt.comptime_alloc(allocator, "s_import", S.Import, origData, loc); + }, + S.Label => { + return Stmt.comptime_alloc(allocator, "s_label", S.Label, origData, loc); + }, + S.LazyExport => { + return Stmt.comptime_alloc(allocator, "s_lazy_export", S.LazyExport, origData, loc); + }, + S.Local => { + return Stmt.comptime_alloc(allocator, "s_local", S.Local, origData, loc); + }, + S.Namespace => { + return Stmt.comptime_alloc(allocator, "s_namespace", S.Namespace, origData, loc); + }, + S.Return => { + return Stmt.comptime_alloc(allocator, "s_return", S.Return, origData, loc); + }, + S.Switch => { + return Stmt.comptime_alloc(allocator, "s_switch", S.Switch, origData, loc); + }, + S.Throw => { + return Stmt.comptime_alloc(allocator, "s_throw", S.Throw, origData, loc); + }, + S.Try => { + return Stmt.comptime_alloc(allocator, "s_try", S.Try, origData, loc); + }, + S.TypeScript => { + return Stmt.comptime_alloc(allocator, "s_type_script", S.TypeScript, origData, loc); + }, + S.While => { + return Stmt.comptime_alloc(allocator, "s_while", S.While, origData, loc); + }, + S.With => { + return Stmt.comptime_alloc(allocator, "s_with", S.With, origData, loc); + }, + else => { @compileError("Invalid type in Stmt.init"); }, @@ -1076,66 +1061,74 @@ pub const Stmt = struct { pub const Tag = packed enum { s_block, - s_comment, - s_directive, - s_export_clause, - s_empty, - s_type_script, - s_debugger, - s_export_from, - s_export_default, - s_enum, - s_namespace, - s_function, + s_break, s_class, - s_if, - s_for, + s_comment, + s_continue, + s_debugger, + s_directive, + s_do_while, + s_empty, + s_enum, + s_export_clause, + s_export_default, + s_export_equals, + s_export_from, + s_export_star, + s_expr, s_for_in, s_for_of, - s_do_while, + s_for, + s_function, + s_if, + s_import, + s_label, + s_lazy_export, + s_local, + s_namespace, + s_return, + s_switch, + s_throw, + s_try, + s_type_script, s_while, s_with, - s_try, - s_switch, - s_import, - s_return, - s_throw, - s_local, - s_break, - s_continue, - s_expr, }; pub const Data = union(Tag) { s_block: *S.Block, - s_expr: *S.SExpr, - s_comment: *S.Comment, - s_directive: *S.Directive, - s_export_clause: *S.ExportClause, - s_empty: *S.Empty, - s_type_script: *S.TypeScript, - s_debugger: *S.Debugger, - s_export_from: *S.ExportFrom, - s_export_default: *S.ExportDefault, - s_enum: *S.Enum, - s_namespace: *S.Namespace, - s_function: *S.Function, + s_break: *S.Break, s_class: *S.Class, - s_if: *S.If, - s_for: *S.For, + s_comment: *S.Comment, + s_continue: *S.Continue, + s_debugger: *S.Debugger, + s_directive: *S.Directive, + s_do_while: *S.DoWhile, + s_empty: *S.Empty, + s_enum: *S.Enum, + s_export_clause: *S.ExportClause, + s_export_default: *S.ExportDefault, + s_export_equals: *S.ExportEquals, + s_export_from: *S.ExportFrom, + s_export_star: *S.ExportStar, + s_expr: *S.SExpr, s_for_in: *S.ForIn, s_for_of: *S.ForOf, - s_do_while: *S.DoWhile, + s_for: *S.For, + s_function: *S.Function, + s_if: *S.If, + s_import: *S.Import, + s_label: *S.Label, + s_lazy_export: *S.LazyExport, + s_local: *S.Local, + s_namespace: *S.Namespace, + s_return: *S.Return, + s_switch: *S.Switch, + s_throw: *S.Throw, + s_try: *S.Try, + s_type_script: *S.TypeScript, s_while: *S.While, s_with: *S.With, - s_try: *S.Try, - s_switch: *S.Switch, - s_import: *S.Import, - s_return: *S.Return, - s_throw: *S.Throw, - s_local: *S.Local, - s_break: *S.Break, - s_continue: *S.Continue, }; pub fn caresAboutScope(self: *Stmt) bool { @@ -2141,10 +2134,25 @@ pub const S = struct { pub const Directive = struct { value: JavascriptString, legacy_octal_loc: logger.Loc }; - pub const ExportClause = struct { items: []ClauseItem }; + pub const ExportClause = struct { items: []ClauseItem, is_single_line: bool = false }; pub const Empty = struct {}; + pub const ExportStar = struct { + namespace_ref: Ref, + alias: ?G.ExportStarAlias = null, + import_record_index: u32, + }; + + // This is an "export = value;" statement in TypeScript + pub const ExportEquals = struct { value: ExprNodeIndex }; + + // The decision of whether to export an expression using "module.exports" or + // "export default" is deferred until linking using this statement kind + pub const LazyExport = struct { value: ExprNodeIndex }; + + pub const Label = struct { name: LocRef, stmt: StmtNodeIndex }; + // This is a stand-in for a TypeScript type declaration pub const TypeScript = struct {}; @@ -2194,9 +2202,13 @@ pub const S = struct { // May be a SConst, SLet, SVar, or SExpr init: StmtNodeIndex, value: ExprNodeIndex, body: StmtNodeIndex }; - pub const ForOf = struct { is_await: bool, - // May be a SConst, SLet, SVar, or SExpr - init: StmtNodeIndex, value: ExprNodeIndex, body: StmtNodeIndex }; + pub const ForOf = struct { + is_await: bool = false, + // May be a SConst, SLet, SVar, or SExpr + init: StmtNodeIndex, + value: ExprNodeIndex, + body: StmtNodeIndex, + }; pub const DoWhile = struct { body: StmtNodeIndex, test_: ExprNodeIndex }; diff --git a/src/js_lexer.zig b/src/js_lexer.zig index 7c0ffef896..3a7997924f 100644 --- a/src/js_lexer.zig +++ b/src/js_lexer.zig @@ -303,7 +303,7 @@ pub const Lexer = struct { } } - pub fn expectContextualKeyword(self: *Lexer, keyword: string) void { + pub fn expectContextualKeyword(self: *Lexer, comptime keyword: string) void { if (!self.isContextualKeyword(keyword)) { self.addError(self.start, "\"{s}\"", .{keyword}, true); } @@ -765,7 +765,7 @@ pub const Lexer = struct { return self.source.contents[self.start..self.end]; } - pub fn isContextualKeyword(self: *Lexer, keyword: string) bool { + pub fn isContextualKeyword(self: *Lexer, comptime keyword: string) bool { return self.token == .t_identifier and strings.eql(self.raw(), keyword); } @@ -844,7 +844,7 @@ pub const Lexer = struct { // TODO: use wtf-8 encoding. pub fn utf16ToString(lexer: *Lexer, js: JavascriptString) string { - return std.unicode.utf16leToUtf8Alloc(lexer.alloc, js) catch unreachable; + return std.unicode.utf16leToUtf8Alloc(lexer.allocator, js) catch unreachable; } pub fn nextInsideJSXElement() void { diff --git a/src/js_parser.zig b/src/js_parser.zig index e3c8903045..f435d50fa3 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -5,10 +5,13 @@ const importRecord = @import("import_record.zig"); const js_ast = @import("js_ast.zig"); const options = @import("options.zig"); const alloc = @import("alloc.zig"); + +const fs = @import("fs.zig"); usingnamespace @import("strings.zig"); usingnamespace @import("ast/base.zig"); usingnamespace js_ast.G; +const ImportKind = importRecord.ImportKind; const BindingNodeIndex = js_ast.BindingNodeIndex; const StmtNodeIndex = js_ast.StmtNodeIndex; const ExprNodeIndex = js_ast.ExprNodeIndex; @@ -65,6 +68,8 @@ const ThenCatchChain = struct { has_catch: bool = false, }; +const ParsedPath = struct { loc: logger.Loc, text: string }; + const StrictModeFeature = enum { with_statement, delete_bare_name, @@ -305,6 +310,8 @@ pub const Parser = struct { } }; +const ExportClauseResult = struct { clauses: []js_ast.ClauseItem = &([_]js_ast.ClauseItem{}), is_single_line: bool = false }; + const DeferredTsDecorators = struct { values: []js_ast.Expr, @@ -1461,8 +1468,83 @@ const P = struct { p.lexer.expectOrInsertSemicolon(); return p.s(S.ExportDefault{ .default_name = createDefaultName(p, loc) catch unreachable, .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)) { + p.lexer.unexpected(); + } + + p.lexer.next(); + var namespace_ref: js_ast.Ref = undefined; + var alias: ?js_ast.G.ExportStarAlias = null; + var path_loc: logger.Loc = undefined; + var path_text: string = undefined; + + if (p.lexer.isContextualKeyword("as")) { + // "export * as ns from 'path'" + const name = p.lexer.identifier; + namespace_ref = p.storeNameInRef(name) catch unreachable; + alias = G.ExportStarAlias{ .loc = p.lexer.loc(), .original_name = name }; + if (!p.lexer.isIdentifierOrKeyword()) { + p.lexer.expect(.t_identifier); + } + p.checkForNonBMPCodePoint((alias orelse unreachable).loc, name); + p.lexer.next(); + p.lexer.expectContextualKeyword("from"); + const parsedPath = p.parsePath(); + path_loc = parsedPath.loc; + path_text = parsedPath.text; + } else { + // "export * from 'path'" + p.lexer.expectContextualKeyword("from"); + const parsedPath = p.parsePath(); + path_loc = parsedPath.loc; + path_text = parsedPath.text; + var path_name = fs.PathName.init(strings.append(p.allocator, path_text, "_star") catch unreachable); + namespace_ref = p.storeNameInRef(path_name.nonUniqueNameString(p.allocator) catch unreachable) catch unreachable; + } + + var import_record_index = p.addImportRecord(ImportKind.stmt, path_loc, path_text); + p.lexer.expectOrInsertSemicolon(); + 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)) { + p.lexer.unexpected(); + } + + const export_clause = p.parseExportClause(); + if (p.lexer.isContextualKeyword("from")) { + p.lexer.expectContextualKeyword("from"); + const parsedPath = p.parsePath(); + const import_record_index = p.addImportRecord(.stmt, parsedPath.loc, parsedPath.text); + var path_name = fs.PathName.init(strings.append(p.allocator, "import_", parsedPath.text) catch unreachable); + const namespace_ref = p.storeNameInRef(path_name.nonUniqueNameString(p.allocator) catch unreachable) catch unreachable; + p.lexer.expectOrInsertSemicolon(); + 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); + } + p.lexer.expectOrInsertSemicolon(); + return p.s(S.ExportClause{ .items = export_clause.clauses, .is_single_line = export_clause.is_single_line }, loc); + }, + T.t_equals => { + // "export = value;" + + p.es6_export_keyword = previousExportKeyword; // This wasn't an ESM export statement after all + if (p.options.ts) { + p.lexer.next(); + var value = p.parseExpr(.lowest); + p.lexer.expectOrInsertSemicolon(); + return p.s(S.ExportEquals{ .value = value }, loc); + } + p.lexer.unexpected(); + return Stmt.empty(); + }, else => { - notimpl(); + p.lexer.unexpected(); + return Stmt.empty(); }, } }, @@ -1475,6 +1557,114 @@ const P = struct { return js_ast.Stmt.empty(); } + pub fn parseExportClause(p: *P) ExportClauseResult { + var items = List(js_ast.ClauseItem).initCapacity(p.allocator, 1) catch unreachable; + var first_keyword_item_loc = logger.Loc{}; + p.lexer.expect(.t_open_brace); + var is_single_line = !p.lexer.has_newline_before; + + while (p.lexer.token != .t_close_brace) { + var alias = p.lexer.identifier; + var alias_loc = p.lexer.loc(); + + var name = LocRef{ + .loc = alias_loc, + .ref = p.storeNameInRef(alias) catch unreachable, + }; + var original_name = alias; + + // The name can actually be a keyword if we're really an "export from" + // statement. However, we won't know until later. Allow keywords as + // identifiers for now and throw an error later if there's no "from". + // + // // This is fine + // export { default } from 'path' + // + // // This is a syntax error + // export { default } + // + if (p.lexer.token != .t_identifier) { + if (!p.lexer.isIdentifierOrKeyword()) { + p.lexer.expect(.t_identifier); + } + if (first_keyword_item_loc.start == 0) { + first_keyword_item_loc = p.lexer.loc(); + } + } + + p.checkForNonBMPCodePoint(alias_loc, alias); + p.lexer.next(); + + if (p.lexer.isContextualKeyword("as")) { + p.lexer.next(); + alias = p.lexer.identifier; + alias_loc = p.lexer.loc(); + + // The alias may be a keyword + if (!p.lexer.isIdentifierOrKeyword()) { + p.lexer.expect(.t_identifier); + } + p.checkForNonBMPCodePoint(alias_loc, alias); + p.lexer.next(); + } + + items.append(js_ast.ClauseItem{ + .alias = alias, + .alias_loc = alias_loc, + .name = name, + .original_name = original_name, + }) catch unreachable; + + // we're done if there's no comma + if (p.lexer.token != .t_comma) { + break; + } + + if (p.lexer.has_newline_before) { + is_single_line = false; + } + p.lexer.next(); + if (p.lexer.has_newline_before) { + is_single_line = false; + } + } + + if (p.lexer.has_newline_before) { + is_single_line = false; + } + p.lexer.expect(.t_close_brace); + + // Throw an error here if we found a keyword earlier and this isn't an + // "export from" statement after all + if (first_keyword_item_loc.start != 0 and !p.lexer.isContextualKeyword("from")) { + const r = js_lexer.rangeOfIdentifier(&p.source, first_keyword_item_loc); + p.lexer.addRangeError(r, "Expected identifier but found \"{s}\"", .{p.source.textForRange(r)}, true); + } + + return ExportClauseResult{ + .clauses = items.toOwnedSlice(), + .is_single_line = is_single_line, + }; + } + + pub fn parsePath(p: *P) ParsedPath { + var path = ParsedPath{ + .loc = p.lexer.loc(), + .text = p.lexer.utf16ToString(p.lexer.string_literal), + }; + + if (p.lexer.token == .t_no_substitution_template_literal) { + p.lexer.next(); + } else { + p.lexer.expect(.t_string_literal); + } + + return path; + } + + // TODO: + pub fn checkForNonBMPCodePoint(p: *P, loc: logger.Loc, name: string) void {} + pub fn parseStmtsUpTo(p: *P, eend: js_lexer.T, opts: *ParseStatementOptions) ![]Stmt { var stmts = try StmtList.initCapacity(p.allocator, 1); @@ -1970,6 +2160,17 @@ const P = struct { return p.parseSuffix(expr, level, errors, flags); } + pub fn addImportRecord(p: *P, kind: ImportKind, loc: logger.Loc, name: string) u32 { + var index = p.import_records.items.len; + const record = ImportRecord{ + .kind = kind, + .range = p.source.rangeOfString(loc), + .path = fs.Path.init(name), + }; + p.import_records.append(record) catch unreachable; + return @intCast(u32, index); + } + pub fn popScope(p: *P) void { const current_scope = p.current_scope orelse unreachable; // We cannot rename anything inside a scope containing a direct eval() call diff --git a/src/logger.zig b/src/logger.zig index a7086c44af..ce57c61729 100644 --- a/src/logger.zig +++ b/src/logger.zig @@ -266,6 +266,33 @@ pub const Source = struct { return Range{ .loc = loc }; } + pub fn rangeOfString(self: *Source, loc: Loc) Range { + const text = self.contents[loc.i()..]; + + if (text.len == 0) { + return Range.None; + } + + const quote = text[0]; + + if (quote == '"' or quote == '\'') { + var i: usize = 1; + var c: u8 = undefined; + while (i < text.len) { + c = text[i]; + + if (c == quote) { + return Range{ .loc = loc, .len = @intCast(i32, i + 1) }; + } else if (c == '\\') { + i += 1; + } + i += 1; + } + } + + return Range{ .loc = loc, .len = 0 }; + } + pub fn rangeOfOperatorAfter(self: *Source, loc: Loc, op: string) Range { const text = self.contents[loc.i()..]; const index = strings.index(text, op); diff --git a/src/string_immutable.zig b/src/string_immutable.zig index 3fc6b141e7..f9d485df68 100644 --- a/src/string_immutable.zig +++ b/src/string_immutable.zig @@ -29,6 +29,10 @@ pub fn eql(self: string, other: anytype) bool { return std.mem.eql(u8, self, other); } +pub fn append(allocator: *std.mem.Allocator, self: string, other: string) !string { + return std.fmt.allocPrint(allocator, "{s}{s}", .{ self, other }); +} + pub fn index(self: string, str: string) i32 { if (std.mem.indexOf(u8, self, str)) |i| { return @intCast(i32, i);