aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/ast/base.zig50
-rw-r--r--src/fs.zig72
-rw-r--r--src/import_record.zig4
-rw-r--r--src/js_ast.zig224
-rw-r--r--src/js_lexer.zig49
-rw-r--r--src/js_parser.zig1
-rw-r--r--src/js_printer.zig999
-rw-r--r--src/json_parser.zig4
-rw-r--r--src/linker.zig11
-rw-r--r--src/logger.zig8
-rw-r--r--src/main.zig12
-rw-r--r--src/options.zig5
-rw-r--r--src/renamer.zig2
-rw-r--r--src/string_immutable.zig83
14 files changed, 1338 insertions, 186 deletions
diff --git a/src/ast/base.zig b/src/ast/base.zig
index 744b7e2d9..f941e3745 100644
--- a/src/ast/base.zig
+++ b/src/ast/base.zig
@@ -1,4 +1,5 @@
-const unicode = @import("std").unicode;
+const std = @import("std");
+const unicode = std.unicode;
pub const JavascriptString = []u16;
pub fn newJavascriptString(comptime text: []const u8) JavascriptString {
@@ -7,3 +8,50 @@ pub fn newJavascriptString(comptime text: []const u8) JavascriptString {
pub const NodeIndex = u32;
pub const NodeIndexNone = 4294967293;
+
+// TODO: figure out if we actually need this
+// -- original comment --
+// Files are parsed in parallel for speed. We want to allow each parser to
+// generate symbol IDs that won't conflict with each other. We also want to be
+// able to quickly merge symbol tables from all files into one giant symbol
+// table.
+//
+// We can accomplish both goals by giving each symbol ID two parts: a source
+// index that is unique to the parser goroutine, and an inner index that
+// increments as the parser generates new symbol IDs. Then a symbol map can
+// be an array of arrays indexed first by source index, then by inner index.
+// The maps can be merged quickly by creating a single outer array containing
+// all inner arrays from all parsed files.
+pub const Ref = packed struct {
+ source_index: Int = std.math.maxInt(Ref.Int),
+ inner_index: Int = 0,
+
+ // 2 bits of padding for whatever is the parent
+ pub const Int = u31;
+ pub const None = Ref{ .inner_index = std.math.maxInt(Ref.Int) };
+ pub fn isNull(self: *const Ref) bool {
+ return self.source_index == std.math.maxInt(Ref.Int) and self.inner_index == std.math.maxInt(Ref.Int);
+ }
+
+ pub fn isSourceNull(self: *const Ref) bool {
+ return self.source_index == std.math.maxInt(Ref.Int);
+ }
+
+ pub fn isSourceIndexNull(int: Ref.Int) bool {
+ return int == std.math.maxInt(Ref.Int);
+ }
+
+ pub fn eql(ref: Ref, b: Ref) bool {
+ return ref.inner_index == b.inner_index and ref.source_index == b.source_index;
+ }
+};
+
+// This is kind of the wrong place, but it's shared between files
+pub const RequireOrImportMeta = struct {
+ // CommonJS files will return the "require_*" wrapper function and an invalid
+ // exports object reference. Lazily-initialized ESM files will return the
+ // "init_*" wrapper function and the exports object for that file.
+ wrapper_ref: Ref = Ref.None,
+ exports_ref: Ref = Ref.None,
+ is_wrapper_async: bool = false,
+};
diff --git a/src/fs.zig b/src/fs.zig
index 4f2918412..82302b697 100644
--- a/src/fs.zig
+++ b/src/fs.zig
@@ -7,19 +7,75 @@ const expect = std.testing.expect;
// pub const FilesystemImplementation = @import("fs_impl.zig");
-pub const FileSystem = struct { tree: std.AutoHashMap(FileSystemEntry) };
+//
+pub const Stat = packed struct {
+ // milliseconds
+ mtime: i64 = 0,
+ // last queried timestamp
+ qtime: i64 = 0,
+ kind: FileSystemEntry.Kind,
+};
+
+pub const FileSystem = struct {
+ // This maps paths relative to absolute_working_dir to the structure of arrays of paths
+ stats: std.StringHashMap(Stat) = undefined,
+ entries: std.ArrayList(FileSystemEntry),
+
+ absolute_working_dir = "/",
+ implementation: anytype = undefined,
+
+ // pub fn statBatch(fs: *FileSystemEntry, paths: []string) ![]?Stat {
+
+ // }
+ // pub fn stat(fs: *FileSystemEntry, path: string) !Stat {
+
+ // }
+ // pub fn readFile(fs: *FileSystemEntry, path: string) ?string {
-pub const FileSystemEntry = union(enum) {
+ // }
+ // pub fn readDir(fs: *FileSystemEntry, path: string) ?[]string {
+
+ // }
+
+ pub fn Implementation(comptime Context: type) type {
+ return struct {
+ context: *Context,
+
+ pub fn statBatch(context: *Context, path: string) ![]?Stat {
+ return try context.statBatch(path);
+ }
+
+ pub fn stat(context: *Context, path: string) !?Stat {
+ return try context.stat(path);
+ }
+
+ pub fn readFile(context: *Context, path: string) !?File {
+ return try context.readFile(path);
+ }
+
+ pub fn readDir(context: *Context, path: string) []string {
+ return context.readdir(path);
+ }
+ };
+ }
+};
+
+pub const FileNotFound = struct {};
+
+pub const FileSystemEntry = union(FileSystemEntry.Kind) {
file: File,
directory: Directory,
-};
+ not_found: FileNotFound,
-pub const File = struct {
- path: Path,
- mtime: ?usize,
- contents: ?string,
+ pub const Kind = enum(u8) {
+ file,
+ directory,
+ not_found,
+ };
};
-pub const Directory = struct { path: Path, mtime: ?usize, contents: []FileSystemEntry };
+
+pub const Directory = struct { path: Path, contents: []string };
+pub const File = struct { path: Path, contents: string };
pub const PathName = struct {
base: string,
diff --git a/src/import_record.zig b/src/import_record.zig
index 60621379f..ff1fa2768 100644
--- a/src/import_record.zig
+++ b/src/import_record.zig
@@ -1,5 +1,7 @@
const fs = @import("fs.zig");
const logger = @import("logger.zig");
+const std = @import("std");
+usingnamespace @import("ast/base.zig");
pub const ImportKind = enum(u8) {
@@ -32,6 +34,8 @@ pub const ImportRecord = struct {
range: logger.Range,
path: fs.Path,
+ source_index: Ref.Int = std.math.maxInt(Ref.Int),
+
// 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.
diff --git a/src/js_ast.zig b/src/js_ast.zig
index 5a5e6dec4..0cbdc0bda 100644
--- a/src/js_ast.zig
+++ b/src/js_ast.zig
@@ -41,39 +41,6 @@ pub const ExprNodeList = []Expr;
pub const StmtNodeList = []Stmt;
pub const BindingNodeList = []Binding;
-// TODO: figure out if we actually need this
-// -- original comment --
-// Files are parsed in parallel for speed. We want to allow each parser to
-// generate symbol IDs that won't conflict with each other. We also want to be
-// able to quickly merge symbol tables from all files into one giant symbol
-// table.
-//
-// We can accomplish both goals by giving each symbol ID two parts: a source
-// index that is unique to the parser goroutine, and an inner index that
-// increments as the parser generates new symbol IDs. Then a symbol map can
-// be an array of arrays indexed first by source index, then by inner index.
-// The maps can be merged quickly by creating a single outer array containing
-// all inner arrays from all parsed files.
-pub const Ref = packed struct {
- source_index: Ref.Int = std.math.maxInt(Ref.Int),
- inner_index: Ref.Int,
-
- // 2 bits of padding for whatever is the parent
- pub const Int = u31;
- const None = Ref{ .inner_index = std.math.maxInt(Ref.Int) };
- pub fn isNull(self: *const Ref) bool {
- return self.source_index == std.math.maxInt(Ref.Int) and self.inner_index == std.math.maxInt(Ref.Int);
- }
-
- pub fn isSourceNull(self: *const Ref) bool {
- return self.source_index == std.math.maxInt(Ref.Int);
- }
-
- pub fn eql(ref: *Ref, b: Ref) bool {
- return ref.inner_index == b.inner_index and ref.source_index == b.source_index;
- }
-};
-
pub const ImportItemStatus = packed enum {
none,
@@ -547,7 +514,7 @@ pub const Symbol = struct {
symbols_for_source: [][]Symbol = undefined,
pub fn get(self: *Map, ref: Ref) ?Symbol {
- self.symbols_for_source[ref.source_index][ref.inner_index];
+ return self.symbols_for_source[ref.source_index][ref.inner_index];
}
pub fn init(sourceCount: usize, allocator: *std.mem.Allocator) !Map {
@@ -557,15 +524,12 @@ pub const Symbol = struct {
pub fn follow(symbols: *Map, ref: Ref) Ref {
if (symbols.get(ref)) |*symbol| {
- if (symbol.link) |link| {
- if (!link.eql(ref)) {
- symbol.link = ref;
- }
-
- return link;
- } else {
- return symbol;
+ const link = symbol.link orelse return ref;
+ if (!link.eql(ref)) {
+ symbol.link = ref;
}
+
+ return symbol.link orelse unreachable;
} else {
return ref;
}
@@ -805,7 +769,7 @@ pub const E = struct {
tag: ?ExprNodeIndex = null,
head: JavascriptString,
head_raw: string, // This is only filled out for tagged template literals
- parts: ?[]TemplatePart = null,
+ parts: []TemplatePart = &([_]TemplatePart{}),
legacy_octal_loc: logger.Loc = logger.Loc.Empty,
};
@@ -829,16 +793,16 @@ pub const E = struct {
};
pub const Require = struct {
- import_record_index: u32 = 0,
+ import_record_index: Ref.Int = 0,
};
pub const RequireOrRequireResolve = struct {
- import_record_index: u32 = 0,
+ import_record_index: Ref.Int = 0,
};
pub const Import = struct {
expr: ExprNodeIndex,
- import_record_index: u32,
+ import_record_index: Ref.Int,
// Comments inside "import()" expressions have special meaning for Webpack.
// Preserving comments inside these expressions makes it possible to use
@@ -2109,6 +2073,15 @@ pub const Expr = struct {
);
}
+ pub fn isOptionalChain(self: *const @This()) bool {
+ return switch (self.data) {
+ .e_dot => |dot| dot.optional_chain != null,
+ .e_index => |dot| dot.optional_chain != null,
+ .e_call => |dot| dot.optional_chain != null,
+ else => false,
+ };
+ }
+
pub const Data = union(Tag) {
e_array: *E.Array,
e_unary: *E.Unary,
@@ -2147,15 +2120,6 @@ pub const Expr = struct {
e_require_or_require_resolve: *E.RequireOrRequireResolve,
e_import: *E.Import,
- pub fn isOptionalChain(self: *Expr) bool {
- return switch (self) {
- Expr.e_dot => |dot| dot.optional_chain != null,
- Expr.e_index => |dot| dot.optional_chain != null,
- Expr.e_call => |dot| dot.optional_chain != null,
- else => false,
- };
- }
-
pub fn isBooleanValue(self: *Expr) bool {
// TODO:
return false;
@@ -2371,7 +2335,7 @@ pub const Finally = struct {
pub const Case = struct { loc: logger.Loc, value: ?ExprNodeIndex, body: StmtNodeList };
pub const Op = struct {
- // If you add a new token, remember to add it to "OpTable" too
+ // If you add a new token, remember to add it to "Table" too
pub const Code = enum {
// Prefix
un_pos,
@@ -2437,6 +2401,33 @@ pub const Op = struct {
bin_nullish_coalescing_assign,
bin_logical_or_assign,
bin_logical_and_assign,
+
+ pub fn unaryAssignTarget(code: Op.Code) AssignTarget {
+ if (@enumToInt(code) >= @enumToInt(Op.Code.un_pre_dec) and @enumToInt(code) <= @enumToInt(Op.Code.un_post_inc)) {
+ return AssignTarget.update;
+ } else {
+ return AssignTarget.none;
+ }
+ }
+ pub fn isLeftAssociative(code: Op.Code) bool {
+ return @enumToInt(code) >= @enumToInt(Op.Code.bin_add) and @enumToInt(code) < @enumToInt(Op.Code.bin_comma) and code != .bin_pow;
+ }
+ pub fn isRightAssociative(code: Op.Code) bool {
+ return @enumToInt(code) >= @enumToInt(Op.Code.bin_assign) or code == .bin_pow;
+ }
+ pub fn binaryAssignTarget(code: Op.Code) AssignTarget {
+ if (code == .bin_assign) {
+ return AssignTarget.replace;
+ } else if (@enumToInt(code) > @enumToInt(Op.Code.bin_assign)) {
+ return .update;
+ } else {
+ return .none;
+ }
+ }
+
+ pub fn isPrefix(code: Op.Code) bool {
+ return @enumToInt(code) < @enumToInt(Op.Code.un_post_dec);
+ }
};
pub const Level = packed enum(u6) {
@@ -2492,71 +2483,84 @@ pub const Op = struct {
level: Level,
is_keyword: bool = false,
- const Table = []Op{
+ pub fn init(triple: anytype) Op {
+ return Op{
+ .text = triple.@"0",
+ .level = triple.@"1",
+ .is_keyword = triple.@"2",
+ };
+ }
+
+ pub const TableType: std.EnumArray(Op.Code, Op);
+ pub const Table = comptime {
+ var table = std.EnumArray(Op.Code, Op).initUndefined();
+
// Prefix
- .{ "+", Level.prefix, false },
- .{ "-", Level.prefix, false },
- .{ "~", Level.prefix, false },
- .{ "!", Level.prefix, false },
- .{ "void", Level.prefix, true },
- .{ "typeof", Level.prefix, true },
- .{ "delete", Level.prefix, true },
+ table.set(Op.Code.un_pos, Op.init(.{ "+", Level.prefix, false }));
+ table.set(Op.Code.un_neg, Op.init(.{ "-", Level.prefix, false }));
+ table.set(Op.Code.un_cpl, Op.init(.{ "~", Level.prefix, false }));
+ table.set(Op.Code.un_not, Op.init(.{ "!", Level.prefix, false }));
+ table.set(Op.Code.un_void, Op.init(.{ "void", Level.prefix, true }));
+ table.set(Op.Code.un_typeof, Op.init(.{ "typeof", Level.prefix, true }));
+ table.set(Op.Code.un_delete, Op.init(.{ "delete", Level.prefix, true }));
// Prefix update
- .{ "--", Level.prefix, false },
- .{ "++", Level.prefix, false },
+ table.set(Op.Code.un_pre_dec, Op.init(.{ "--", Level.prefix, false }));
+ table.set(Op.Code.un_pre_inc, Op.init(.{ "++", Level.prefix, false }));
// Postfix update
- .{ "--", Level.postfix, false },
- .{ "++", Level.postfix, false },
+ table.set(Op.Code.un_post_dec, Op.init(.{ "--", Level.postfix, false }));
+ table.set(Op.Code.un_post_inc, Op.init(.{ "++", Level.postfix, false }));
// Left-associative
- .{ "+", Level.add, false },
- .{ "-", Level.add, false },
- .{ "*", Level.multiply, false },
- .{ "/", Level.multiply, false },
- .{ "%", Level.multiply, false },
- .{ "**", Level.exponentiation, false }, // Right-associative
- .{ "<", Level.compare, false },
- .{ "<=", Level.compare, false },
- .{ ">", Level.compare, false },
- .{ ">=", Level.compare, false },
- .{ "in", Level.compare, true },
- .{ "instanceof", Level.compare, true },
- .{ "<<", Level.shift, false },
- .{ ">>", Level.shift, false },
- .{ ">>>", Level.shift, false },
- .{ "==", Level.equals, false },
- .{ "!=", Level.equals, false },
- .{ "===", Level.equals, false },
- .{ "!==", Level.equals, false },
- .{ "??", Level.nullish_coalescing, false },
- .{ "||", Level.logical_or, false },
- .{ "&&", Level.logical_and, false },
- .{ "|", Level.bitwise_or, false },
- .{ "&", Level.bitwise_and, false },
- .{ "^", Level.bitwise_xor, false },
+ table.set(Op.Code.bin_add, Op.init(.{ "+", Level.add, false }));
+ table.set(Op.Code.bin_sub, Op.init(.{ "-", Level.add, false }));
+ table.set(Op.Code.bin_mul, Op.init(.{ "*", Level.multiply, false }));
+ table.set(Op.Code.bin_div, Op.init(.{ "/", Level.multiply, false }));
+ table.set(Op.Code.bin_rem, Op.init(.{ "%", Level.multiply, false }));
+ table.set(Op.Code.bin_pow, Op.init(.{ "**", Level.exponentiation, false }));
+ table.set(Op.Code.bin_lt, Op.init(.{ "<", Level.compare, false }));
+ table.set(Op.Code.bin_le, Op.init(.{ "<=", Level.compare, false }));
+ table.set(Op.Code.bin_gt, Op.init(.{ ">", Level.compare, false }));
+ table.set(Op.Code.bin_ge, Op.init(.{ ">=", Level.compare, false }));
+ table.set(Op.Code.bin_in, Op.init(.{ "in", Level.compare, true }));
+ table.set(Op.Code.bin_instanceof, Op.init(.{ "instanceof", Level.compare, true }));
+ table.set(Op.Code.bin_shl, Op.init(.{ "<<", Level.shift, false }));
+ table.set(Op.Code.bin_shr, Op.init(.{ ">>", Level.shift, false }));
+ table.set(Op.Code.bin_u_shr, Op.init(.{ ">>>", Level.shift, false }));
+ table.set(Op.Code.bin_loose_eq, Op.init(.{ "==", Level.equals, false }));
+ table.set(Op.Code.bin_loose_ne, Op.init(.{ "!=", Level.equals, false }));
+ table.set(Op.Code.bin_strict_eq, Op.init(.{ "===", Level.equals, false }));
+ table.set(Op.Code.bin_strict_ne, Op.init(.{ "!==", Level.equals, false }));
+ table.set(Op.Code.bin_nullish_coalescing, Op.init(.{ "??", Level.nullish_coalescing, false }));
+ table.set(Op.Code.bin_logical_or, Op.init(.{ "||", Level.logical_or, false }));
+ table.set(Op.Code.bin_logical_and, Op.init(.{ "&&", Level.logical_and, false }));
+ table.set(Op.Code.bin_bitwise_or, Op.init(.{ "|", Level.bitwise_or, false }));
+ table.set(Op.Code.bin_bitwise_and, Op.init(.{ "&", Level.bitwise_and, false }));
+ table.set(Op.Code.bin_bitwise_xor, Op.init(.{ "^", Level.bitwise_xor, false }));
// Non-associative
- .{ ",", LComma, false },
+ table.set(Op.Code.bin_comma, Op.init(.{ ",", Level.comma, false }));
// Right-associative
- .{ "=", Level.assign, false },
- .{ "+=", Level.assign, false },
- .{ "-=", Level.assign, false },
- .{ "*=", Level.assign, false },
- .{ "/=", Level.assign, false },
- .{ "%=", Level.assign, false },
- .{ "**=", Level.assign, false },
- .{ "<<=", Level.assign, false },
- .{ ">>=", Level.assign, false },
- .{ ">>>=", Level.assign, false },
- .{ "|=", Level.assign, false },
- .{ "&=", Level.assign, false },
- .{ "^=", Level.assign, false },
- .{ "??=", Level.assign, false },
- .{ "||=", Level.assign, false },
- .{ "&&=", Level.assign, false },
+ table.set(Op.Code.bin_assign, Op.init(.{ "=", Level.assign, false }));
+ table.set(Op.Code.bin_add_assign, Op.init(.{ "+=", Level.assign, false }));
+ table.set(Op.Code.bin_sub_assign, Op.init(.{ "-=", Level.assign, false }));
+ table.set(Op.Code.bin_mul_assign, Op.init(.{ "*=", Level.assign, false }));
+ table.set(Op.Code.bin_div_assign, Op.init(.{ "/=", Level.assign, false }));
+ table.set(Op.Code.bin_rem_assign, Op.init(.{ "%=", Level.assign, false }));
+ table.set(Op.Code.bin_pow_assign, Op.init(.{ "**=", Level.assign, false }));
+ table.set(Op.Code.bin_shl_assign, Op.init(.{ "<<=", Level.assign, false }));
+ table.set(Op.Code.bin_shr_assign, Op.init(.{ ">>=", Level.assign, false }));
+ table.set(Op.Code.bin_u_shr_assign, Op.init(.{ ">>>=", Level.assign, false }));
+ table.set(Op.Code.bin_bitwise_or_assign, Op.init(.{ "|=", Level.assign, false }));
+ table.set(Op.Code.bin_bitwise_and_assign, Op.init(.{ "&=", Level.assign, false }));
+ table.set(Op.Code.bin_bitwise_xor_assign, Op.init(.{ "^=", Level.assign, false }));
+ table.set(Op.Code.bin_nullish_coalescing_assign, Op.init(.{ "??=", Level.assign, false }));
+ table.set(Op.Code.bin_logical_or_assign, Op.init(.{ "||=", Level.assign, false }));
+ table.set(Op.Code.bin_logical_and_assign, Op.init(.{ "&&=", Level.assign, false }));
+
+ return table;
};
};
diff --git a/src/js_lexer.zig b/src/js_lexer.zig
index bef9893c8..409b3a269 100644
--- a/src/js_lexer.zig
+++ b/src/js_lexer.zig
@@ -1503,6 +1503,55 @@ pub fn isWhitespace(codepoint: CodePoint) bool {
}
}
+pub fn isIdentifier(text: string) bool {
+ if (text.len == 0) {
+ return false;
+ }
+
+ var iter = std.unicode.Utf8Iterator{ .bytes = text, .i = 0 };
+ if (!isIdentifierStart(iter.nextCodepoint() orelse unreachable)) {
+ return false;
+ }
+
+ while (iter.nextCodepoint()) |codepoint| {
+ if (!isIdentifierContinue(@intCast(CodePoint, codepoint))) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+pub fn isIdentifierUTF16(text: JavascriptString) bool {
+ const n = text.len;
+ if (n == 0) {
+ return false;
+ }
+
+ var i: usize = 0;
+ while (i < n) : (i += 1) {
+ var r1 = text[i];
+ if (r1 >= 0xD800 and r1 <= 0xDBFF and i + 1 < n) {
+ const r2 = text[i + 1];
+ if (r2 >= 0xDC00 and r2 <= 0xDFFF) {
+ r1 = (r1 << 10) + r2 + (0x10000 - (0xD800 << 10) - 0xDC00);
+ i += 1;
+ }
+ }
+ if (i == 0) {
+ if (!isIdentifierStart(@intCast(u21, r1))) {
+ return false;
+ }
+ } else {
+ if (!isIDentifierContinue(@intCast(u21, r1))) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
// TODO: implement this to actually work right
// this fn is a stub!
pub fn rangeOfIdentifier(source: *Source, loc: logger.Loc) logger.Range {
diff --git a/src/js_parser.zig b/src/js_parser.zig
index 44cb5e036..7fbb83065 100644
--- a/src/js_parser.zig
+++ b/src/js_parser.zig
@@ -21,7 +21,6 @@ const StmtNodeList = js_ast.StmtNodeList;
const BindingNodeList = js_ast.BindingNodeList;
const assert = std.debug.assert;
-const Ref = js_ast.Ref;
const LocRef = js_ast.LocRef;
const S = js_ast.S;
const B = js_ast.B;
diff --git a/src/js_printer.zig b/src/js_printer.zig
index c0f2d3c84..2bd2f5da0 100644
--- a/src/js_printer.zig
+++ b/src/js_printer.zig
@@ -16,7 +16,6 @@ const expect = std.testing.expect;
const ImportKind = importRecord.ImportKind;
const BindingNodeIndex = js_ast.BindingNodeIndex;
-const Ref = js_ast.Ref;
const LocRef = js_ast.LocRef;
const S = js_ast.S;
const B = js_ast.B;
@@ -40,6 +39,8 @@ const first_high_surrogate: u21 = 0xD800;
const last_high_surrogate: u21 = 0xDBFF;
const first_low_surrogate: u21 = 0xDC00;
const last_low_surrogate: u21 = 0xDFFF;
+const assert = std.debug.assert;
+const Linker = @import("linker.zig").Linker;
fn notimpl() void {
std.debug.panic("Not implemented yet!", .{});
@@ -71,6 +72,8 @@ pub const SourceMapChunk = struct {
pub const Options = struct {
to_module_ref: js_ast.Ref,
indent: usize = 0,
+
+ rewrite_require_resolve: bool = true,
// If we're writing out a source map, this table of line start indices lets
// us do binary search on to figure out what line a given AST node came from
// line_offset_tables: []LineOffsetTable
@@ -78,11 +81,33 @@ pub const Options = struct {
pub const PrintResult = struct { js: string, source_map: ?SourceMapChunk = null };
-const ExprFlag = enum {
- forbid_call,
- forbid_in,
- has_non_optional_chain_parent,
- expr_result_is_unused,
+// Zig represents booleans in packed structs as 1 bit, with no padding
+// This is effectively a bit field
+const ExprFlag = packed struct {
+ forbid_call: bool = false,
+ forbid_in: bool = false,
+ has_non_optional_chain_parent: bool = false,
+ expr_result_is_unused: bool = false,
+
+ pub fn None() ExprFlag {
+ return ExprFlag{};
+ }
+
+ pub fn ForbidCall() ExprFlag {
+ return ExprFlag{ .forbid_call = true };
+ }
+
+ pub fn ForbidAnd() ExprFlag {
+ return ExprFlag{ .forbid_and = true };
+ }
+
+ pub fn HasNonOptionalChainParent() ExprFlag {
+ return ExprFlag{ .has_non_optional_chain_parent = true };
+ }
+
+ pub fn ExprResultIsUnused() ExprFlag {
+ return ExprFlag{ .expr_result_is_unused = true };
+ }
};
pub fn NewPrinter(comptime ascii_only: bool) type {
@@ -93,7 +118,7 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
return struct {
symbols: Symbol.Map,
import_records: []importRecord.ImportRecord,
-
+ linker: *Linker,
js: MutableString,
needs_semicolon: bool = false,
@@ -110,6 +135,7 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
int_to_bytes_buffer: [64]u8 = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
writer: MutableString.Writer,
allocator: *std.mem.Allocator,
+ renamer: rename.Renamer,
const Printer = @This();
pub fn comptime_flush(p: *Printer) void {}
@@ -159,6 +185,10 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
// }
// }
+ pub fn printFmt(p: *Printer, fmt: comptime string, args: anytype) void {
+ std.fmt.bufPrint(p.writer, fmt, args);
+ }
+
pub fn print(p: *Printer, str: anytype) void {
switch (@TypeOf(str)) {
comptime_int => {
@@ -204,7 +234,7 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
p.print(" ");
}
pub fn printNewline(p: *Printer) void {
- notimpl();
+ p.print("\n");
}
pub fn printSemicolonAfterStatement(p: *Printer) void {
p.print(";\n");
@@ -221,20 +251,25 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
}
}
pub fn printDotThenPrefix(p: *Printer) Level {
- return .lowest;
+ p.print(".then(() => ");
+ return .comma;
}
- pub fn printUndefined(level: Level) void {
- notimpl();
+ pub fn printUndefined(p: *Printer, level: Level) void {
+ // void 0 is more efficient in output size
+ // however, "void 0" is the same as "undefined" is a point of confusion for many
+ // since we are optimizing for development, undefined is more clear.
+ // an ideal development bundler would output very readable code, even without source maps.
+ p.print("undefined");
}
- pub fn printBody(stmt: Stmt) void {
+ pub fn printBody(p: *Printer, stmt: Stmt) void {
notimpl();
}
- pub fn printBlock(loc: logger.Loc, stmts: []Stmt) void {
+ pub fn printBlock(p: *Printer, loc: logger.Loc, stmts: []Stmt) void {
notimpl();
}
- pub fn printDecls(keyword: string, decls: []G.Decl, flags: ExprFlag) void {
+ pub fn printDecls(p: *Printer, keyword: string, decls: []G.Decl, flags: ExprFlag) void {
notimpl();
}
@@ -254,7 +289,7 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
notimpl();
}
- pub fn bestQuoteCharForString(p: *Printer, str: JavascriptString, allow_backtick: bool) u8 {
+ pub fn bestQuoteCharForString(p: *Printer, str: anytype, allow_backtick: bool) u8 {
var single_cost: usize = 0;
var double_cost: usize = 0;
var backtick_cost: usize = 0;
@@ -473,72 +508,606 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
}
}
- pub fn printExpr(p: *Printer, expr: Expr, level: Level, flags: ExprFlag) void {
+ pub fn isUnboundEvalIdentifier(p: *Printer, value: Expr) bool {
+ switch (value.data) {
+ .e_identifier => |ident| {
+ const symbol = p.symbols.get(p.symbols.follow(ident.ref)) orelse return false;
+ return symbol.kind == .unbound and strings.eql(symbol.original_name, "eval");
+ },
+ else => {
+ return false;
+ },
+ }
+ }
+
+ pub fn printRequireOrImportExpr(p: *Printer, import_record_index: Ref.Int, leading_interior_comments: []G.Comment, _level: Level, flags: ExprFlag) void {
+ var level = _level;
+ assert(p.import_records.len > import_record_index);
+ const record = p.import_records[import_record_index];
+
+ if (level.gte(.new) or flags.forbid_call) {
+ p.print("(");
+ defer p.print(")");
+ level = .lowest;
+ }
+
+ if (Ref.isSourceIndexNull(record.source_index)) {
+ // External "require()"
+ // This case should ideally not happen.
+ // Emitting "require" when targeting a browser is broken code and a sign of something wrong.
+ if (record.kind != .dynamic) {
+
+ // First, we will assert to make detecting this case a little clearer for us in development.
+ if (std.builtin.mode == std.builtin.Mode.Debug) {
+ std.debug.panic("Internal error: {s} is an external require, which should never happen.", .{record});
+ }
+
+ p.printSpaceBeforeIdentifier();
+
+ // Then, we will *dangerously* import it as esm, assuming **top-level await support**.
+ // This is not a transform that will always work, but it most closely mimicks the behavior of require()
+ // For ESM interop, webpack & other bundlers typically make the default export the equivalent of require("foo").default
+ // so that's require("foo").default.bar rather than require("foo").bar
+ // We are assuming that the target import has been converted into something with an "export default".
+ // If it's not esm, the code won't work anyway
+ p.printFmt("/* require(\"{s}\") */(await import(", .{record.path.text});
+ p.addSourceMapping(record.range.loc);
+ p.printQuotedUTF8(record.path.text, true);
+ p.print(").default)");
+ return;
+ }
+
+ // External import()
+ if (leading_interior_comments.len > 0) {
+ p.printNewline();
+ p.options.indent += 1;
+ for (leading_interior_comments) |comment| {
+ p.printIndentedComment(comment.text);
+ }
+ p.printIndent();
+ }
+ p.addSourceMapping(record.range.loc);
+ p.printQuotedUTF8(record.path.text, true);
+ if (leading_interior_comments.len > 0) {
+ p.printNewline();
+ p.options.indent -= 1;
+ p.printIndent();
+ }
+
+ return;
+ }
+
+ var meta = p.linker.requireOrImportMetaForSource(record.source_index);
+
+ // Don't need the namespace object if the result is unused anyway
+ if (flags.expr_result_is_unused) {
+ meta.exports_ref = Ref.None;
+ }
+
+ // Internal "import()" of async ESM
+ if (record.kind == .dynamic and meta.is_wrapper_async) {
+ p.printSymbol(meta.wrapper_ref);
+ p.print("()");
+
+ if (!meta.exports_ref.isNull()) {
+ _ = p.printDotThenPrefix();
+ p.printSymbol(meta.exports_ref);
+ p.printDotThenSuffix();
+ }
+ return;
+ }
+
+ // Internal "require()" or "import()"
+ if (record.kind == .dynamic) {
+ p.printSpaceBeforeIdentifier();
+ p.print("Promise.resolve()");
+ level = p.printDotThenPrefix();
+ defer p.printDotThenSuffix();
+ }
+
+ // Make sure the comma operator is propertly wrapped
+ if (!meta.exports_ref.isNull() and level.gte(.comma)) {
+ p.print("(");
+ defer p.print(")");
+ }
+
+ // Wrap this with a call to "__toModule()" if this is a CommonJS file
+ if (record.wrap_with_to_module) {
+ p.printSymbol(p.options.to_module_ref);
+ p.print("(");
+ defer p.print(")");
+ }
+
+ // Call the wrapper
+ p.printSymbol(meta.wrapper_ref);
+ p.print("()");
+
+ // Return the namespace object if this is an ESM file
+ if (!meta.exports_ref.isNull()) {
+ p.print(",");
+ p.printSpace();
+ p.printSymbol(meta.exports_ref);
+ }
+ }
+
+ pub fn printQuotedUTF8(p: *Printer, str: string, allow_backtick: bool) void {
+ const quote = p.bestQuoteCharForString(str, allow_backtick);
+ p.print(quote);
+ // fast path: small strings get a stack allocation
+ if (str.len < 128) {
+ var buf = [128]u16{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+ const bufEnd = strings.toUTF16Buf(str, &buf);
+ p.printQuotedUTF16(buf[0..bufEnd], quote);
+ } else {
+ // slow path: big strings get a heap allocation
+ p.printQuotedUTF16(strings.toUTF16Alloc(str, p.allocator) catch unreachable, quote);
+ }
+ p.print(quote);
+ }
+
+ pub fn canPrintIdentifier(p: *Printer, name: string) bool {
+ if (ascii_only) {
+ return js_lexer.isIdentifier(name) and !strings.containsNonBmpCodePoint(name);
+ } else {
+ return js_lexer.isIdentifier(name);
+ }
+ }
+
+ pub fn canPrintIdentifierUTF16(p: *Printer, name: JavascriptString) bool {
+ if (ascii_only) {
+ return js_lexer.isIdentifierUTF16(name) and !strings.containsNonBmpCodePointUTF16(name);
+ } else {
+ return js_lexer.isIdentifierUTF16(name);
+ }
+ }
+
+ pub fn printExpr(p: *Printer, expr: Expr, level: Level, _flags: ExprFlag) void {
p.addSourceMapping(expr.loc);
+ var flags = _flags;
switch (expr.data) {
- .e_missing => |e| {
- notimpl();
- },
+ .e_missing => |e| {},
.e_undefined => |e| {
- notimpl();
+ p.printSpaceBeforeIdentifier();
+
+ p.printUndefined(level);
},
.e_super => |e| {
- notimpl();
+ p.printSpaceBeforeIdentifier();
+ p.print("super");
},
.e_null => |e| {
- notimpl();
+ p.printSpaceBeforeIdentifier();
+ p.print("null");
},
.e_this => |e| {
- notimpl();
+ p.printSpaceBeforeIdentifier();
+ p.print("this");
},
.e_spread => |e| {
- notimpl();
+ p.print("...");
+ p.printExpr(e.value, .comma, ExprFlag.None());
},
.e_new_target => |e| {
- notimpl();
+ p.printSpaceBeforeIdentifier();
+ p.print("new.target");
},
.e_import_meta => |e| {
- notimpl();
+ p.printSpaceBeforeIdentifier();
+ p.print("import.meta");
},
.e_new => |e| {
- notimpl();
+ const has_pure_comment = e.can_be_unwrapped_if_unused;
+ const wrap = level.gte(.call) or (has_pure_comment and level.gte(.postfix));
+
+ if (wrap) {
+ p.print("(");
+ }
+
+ if (has_pure_comment) {
+ p.print("/* @__PURE__ */ ");
+ }
+
+ p.printSpaceBeforeIdentifier();
+ p.print("new");
+ p.printSpace();
+ p.printExpr(e.target, .new, ExprFlag.ForbidCall());
+
+ if (e.args.len > 0 or level.gte(.postfix)) {
+ p.print("(");
+
+ if (e.args.len > 0) {
+ var i: usize = 0;
+ p.printExpr(e.args[i], .comma, ExprFlag.None());
+ i = 1;
+
+ while (i < e.args.len) {
+ p.print(",");
+ p.printSpace();
+ p.printExpr(e.args[i], .comma, ExprFlag.None());
+ i += 1;
+ }
+ }
+
+ p.print(")");
+ }
+
+ if (wrap) {
+ p.print(")");
+ }
},
.e_call => |e| {
- notimpl();
+ var wrap = level.gte(.new) or flags.forbid_call;
+ var target_flags = ExprFlag.None();
+ if (e.optional_chain == null) {
+ target_flags = ExprFlag.HasNonOptionalChainParent();
+ } else if (flags.has_non_optional_chain_parent) {
+ wrap = true;
+ }
+
+ const has_pure_comment = e.can_be_unwrapped_if_unused;
+ if (has_pure_comment and level.gte(.postfix)) {
+ wrap = true;
+ }
+
+ if (wrap) {
+ p.print("(");
+ }
+
+ if (has_pure_comment) {
+ const was_stmt_start = p.stmt_start == p.js.len();
+ p.print("/* @__PURE__ */ ");
+ if (was_stmt_start) {
+ p.stmt_start = p.js.lenI();
+ }
+ }
+ // We don't ever want to accidentally generate a direct eval expression here
+ p.call_target = e.target.data;
+ if (!e.is_direct_eval and p.isUnboundEvalIdentifier(e.target)) {
+ p.print("(0, ");
+ p.printExpr(e.target, .postfix, ExprFlag.None());
+ p.print(")");
+ } else {
+ p.printExpr(e.target, .postfix, target_flags);
+ }
+
+ if (e.optional_chain != null and (e.optional_chain orelse unreachable) == .start) {
+ p.print("?.");
+ }
+ p.print("(");
+
+ if (e.args.len > 0) {
+ p.printExpr(e.args[0], .comma, ExprFlag.None());
+ var i: usize = 1;
+ while (i < e.args.len) {
+ p.print(",");
+ p.printSpace();
+ p.printExpr(e.args[i], .comma, ExprFlag.None());
+ i += 1;
+ }
+ }
+
+ p.print(")");
+ if (wrap) {
+ p.print(")");
+ }
},
.e_require => |e| {
- notimpl();
+ p.printRequireOrImportExpr(e.import_record_index, &([_]G.Comment{}), level, flags);
},
.e_require_or_require_resolve => |e| {
- notimpl();
+ const wrap = level.gte(.new) or flags.forbid_call;
+ if (wrap) {
+ p.print("(");
+ }
+
+ if (p.options.rewrite_require_resolve) {
+ // require.resolve("../src.js") => new URL("/src.js", location.origin).href
+ // require.resolve is not available to the browser
+ // if we return the relative filepath, that could be inaccessible if they're viewing the development server
+ // on a different origin than where it's compiling
+ // instead of doing that, we make the following assumption: the assets are same-origin
+ p.printSpaceBeforeIdentifier();
+ p.print("new URL(");
+ p.printQuotedUTF8(p.import_records[e.import_record_index].path.text, true);
+ p.print(", location.origin).href");
+ } else {
+ p.printSpaceBeforeIdentifier();
+ p.printQuotedUTF8(p.import_records[e.import_record_index].path.text, true);
+ }
+
+ if (wrap) {
+ p.print(")");
+ }
},
.e_import => |e| {
- notimpl();
+ // Handle non-string expressions
+ if (Ref.isSourceIndexNull(e.import_record_index)) {
+ const wrap = level.gte(.new) or flags.forbid_call;
+ if (wrap) {
+ p.print("(");
+ }
+ p.printSpaceBeforeIdentifier();
+ p.print("import(");
+ if (e.leading_interior_comments.len > 0) {
+ p.printNewline();
+ p.options.indent += 1;
+ for (e.leading_interior_comments) |comment| {
+ p.printIndentedComment(comment.text);
+ }
+ p.printIndent();
+ }
+ p.printExpr(e.expr, .comma, ExprFlag.None());
+
+ if (e.leading_interior_comments.len > 0) {
+ p.printNewline();
+ p.options.indent -= 1;
+ p.printIndent();
+ }
+ p.print(")");
+ if (wrap) {
+ p.print(")");
+ }
+ } else {
+ p.printRequireOrImportExpr(e.import_record_index, e.leading_interior_comments, level, flags);
+ }
},
.e_dot => |e| {
- notimpl();
+ var wrap = false;
+ if (e.optional_chain == null) {
+ flags.has_non_optional_chain_parent = false;
+ } else {
+ if (flags.has_non_optional_chain_parent) {
+ wrap = true;
+ p.print("(");
+ }
+
+ flags.has_non_optional_chain_parent = true;
+ }
+ p.printExpr(e.target, .postfix, flags);
+ // Zig compiler bug: e.optional_chain == null or e.optional_chain == .start causes broken LLVM IR
+ // https://github.com/ziglang/zig/issues/6059
+ const isOptionalChain = (e.optional_chain orelse js_ast.OptionalChain.ccontinue) == js_ast.OptionalChain.start;
+
+ if (isOptionalChain) {
+ p.print("?");
+ }
+ if (p.canPrintIdentifier(e.name)) {
+ if (isOptionalChain and p.prev_num_end == p.js.len()) {
+ // "1.toString" is a syntax error, so print "1 .toString" instead
+ p.print(" ");
+ }
+ p.print(".");
+ p.addSourceMapping(e.name_loc);
+ p.printIdentifier(e.name);
+ } else {
+ p.print("[");
+ p.addSourceMapping(e.name_loc);
+ p.printQuotedUTF8(e.name, true);
+ p.print("]");
+ }
+
+ if (wrap) {
+ p.print(")");
+ }
},
.e_index => |e| {
- notimpl();
+ var wrap = false;
+ if (e.optional_chain == null) {
+ flags.has_non_optional_chain_parent = false;
+ } else {
+ if (flags.has_non_optional_chain_parent) {
+ wrap = true;
+ p.print("(");
+ }
+ flags.has_non_optional_chain_parent = false;
+ }
+
+ p.printExpr(e.target, .postfix, flags);
+
+ // Zig compiler bug: e.optional_chain == null or e.optional_chain == .start causes broken LLVM IR
+ // https://github.com/ziglang/zig/issues/6059
+ const is_optional_chain_start = (e.optional_chain orelse js_ast.OptionalChain.ccontinue) == js_ast.OptionalChain.start;
+
+ if (is_optional_chain_start) {
+ p.print("?.");
+ }
+
+ switch (e.index.data) {
+ .e_private_identifier => |priv| {
+ if (is_optional_chain_start) {
+ p.print(".");
+ }
+
+ p.printSymbol(priv.ref);
+ },
+ else => {
+ p.print("[");
+ p.printExpr(e.index, .lowest, ExprFlag.None());
+ p.print("]");
+ },
+ }
+
+ if (wrap) {
+ p.print(")");
+ }
},
.e_if => |e| {
- notimpl();
+ const wrap = level.gte(.conditional);
+ if (wrap) {
+ p.print("(");
+ flags.forbid_in = !flags.forbid_in;
+ }
+ flags.forbid_in = true;
+ p.printExpr(e.test_, .conditional, flags);
+ p.printSpace();
+ p.print("?");
+ p.printExpr(e.yes, .yield, ExprFlag.None());
+ p.printSpace();
+ p.print(":");
+ flags.forbid_in = true;
+ p.printExpr(e.no, .yield, flags);
+ if (wrap) {
+ p.print(")");
+ }
},
.e_arrow => |e| {
- notimpl();
+ const wrap = level.gte(.assign);
+
+ if (wrap) {
+ p.print("(");
+ }
+
+ if (e.is_async) {
+ p.printSpaceBeforeIdentifier();
+ p.print("async");
+ p.printSpace();
+ }
+
+ var wasPrinted = false;
+ if (e.body.stmts.len == 1 and e.prefer_expr) {
+ switch (e.body.stmts[0].data) {
+ .s_return => |ret| {
+ if (ret.value) |val| {
+ p.arrow_expr_start = p.js.lenI();
+ p.printExpr(val, .comma, ExprFlag.None());
+ wasPrinted = true;
+ }
+ },
+ else => {},
+ }
+ }
+
+ if (!wasPrinted) {
+ p.printBlock(e.body.loc, e.body.stmts);
+ }
+
+ if (wrap) {
+ p.print(")");
+ }
},
.e_function => |e| {
- notimpl();
+ const n = p.js.lenI();
+ var wrap = p.stmt_start == n or p.export_default_start == n;
+ if (wrap) {
+ p.print("(");
+ }
+
+ p.printSpaceBeforeIdentifier();
+ if (e.func.flags.is_async) {
+ p.print("async ");
+ }
+ p.print("function");
+ if (e.func.flags.is_generator) {
+ p.print("*");
+ p.printSpace();
+ }
+
+ if (e.func.name) |sym| {
+ p.printSymbol(sym.ref orelse std.debug.panic("internal error: expected E.Function's name symbol to have a ref\n{s}", .{e.func}));
+ }
+ p.printFunc(e.func);
+ if (wrap) {
+ p.print(")");
+ }
},
.e_class => |e| {
- notimpl();
+ const n = p.js.lenI();
+ var wrap = p.stmt_start == n or p.export_default_start == n;
+ if (wrap) {
+ p.print("(");
+ }
+
+ p.printSpaceBeforeIdentifier();
+ p.print("class");
+ if (e.class_name) |name| {
+ p.printSymbol(name.ref orelse std.debug.panic("internal error: expected E.Class's name symbol to have a ref\n{s}", .{e}));
+ }
+ p.printClass(e.*);
+ if (wrap) {
+ p.print(")");
+ }
},
.e_array => |e| {
- notimpl();
+ p.print("[");
+ if (e.items.len > 0) {
+ if (!e.is_single_line) {
+ p.options.indent += 1;
+ }
+
+ var i: usize = 0;
+ while (i < e.items.len) : (i += 1) {
+ if (i != 0) {
+ p.print(",");
+ if (e.is_single_line) {
+ p.printSpace();
+ }
+ }
+ if (!e.is_single_line) {
+ p.printNewline();
+ p.printIndent();
+ }
+ p.printExpr(e.items[i], .comma, ExprFlag.None());
+
+ if (i == e.items.len - 1) {
+ // Make sure there's a comma after trailing missing items
+ switch (e.items[i].data) {
+ .e_missing => {
+ p.print(",");
+ },
+ else => {},
+ }
+ }
+ }
+
+ if (!e.is_single_line) {
+ p.options.indent -= 1;
+ p.printNewline();
+ p.printIndent();
+ }
+ }
+
+ p.print("]");
},
.e_object => |e| {
- notimpl();
+ const n = p.js.lenI();
+ const wrap = p.stmt_start == n or p.arrow_expr_start == n;
+
+ if (wrap) {
+ p.print("(");
+ }
+ p.print("{");
+ if (e.properties.len > 0) {
+ if (!e.is_single_line) {
+ p.options.indent += 1;
+ }
+
+ var i: usize = 0;
+ while (i < e.properties.len) : (i += 1) {
+ if (i != 0) {
+ p.print(",");
+ if (e.is_single_line) {
+ p.printSpace();
+ }
+ }
+
+ if (!e.is_single_line) {
+ p.printNewline();
+ p.printIndent();
+ }
+ p.printProperty(e.properties[i]);
+ }
+
+ if (!e.is_single_line) {
+ p.options.indent -= 1;
+ p.printNewline();
+ p.printIndent();
+ }
+ }
+ p.print("}");
+ if (wrap) {
+ p.print(")");
+ }
},
.e_boolean => |e| {
p.printSpaceBeforeIdentifier();
@@ -559,10 +1128,49 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
p.print(c);
},
.e_template => |e| {
- notimpl();
+ if (e.tag) |tag| {
+ // Optional chains are forbidden in template tags
+ if (expr.isOptionalChain()) {
+ p.print("(");
+ p.printExpr(tag, .lowest, ExprFlag.None());
+ p.print(")");
+ } else {
+ p.printExpr(tag, .postfix, ExprFlag.None());
+ }
+ }
+
+ p.print("`");
+ if (e.tag != null) {
+ p.print(e.head_raw);
+ } else {
+ p.printQuotedUTF16(e.head, '`');
+ }
+
+ for (e.parts) |part| {
+ p.print("${");
+ p.printExpr(part.value, .lowest, ExprFlag.None());
+ p.print("}");
+ if (e.tag != null) {
+ p.print(part.tail_raw);
+ } else {
+ p.printQuotedUTF16(part.tail, '`');
+ }
+ }
+ p.print("`");
},
.e_reg_exp => |e| {
- notimpl();
+ const n = p.js.len();
+ const tail = p.js.list.items[n - 1];
+
+ // Avoid forming a single-line comment
+ if (n > 0 and tail == '/') {
+ p.print(" ");
+ }
+
+ p.print(e.value);
+
+ // Need a space before the next identifier to avoid it turning into flags
+ p.prev_reg_exp_end = p.js.lenI();
},
.e_big_int => |e| {
p.printSpaceBeforeIdentifier();
@@ -610,22 +1218,229 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
}
},
.e_identifier => |e| {
- notimpl();
+ const name = p.renamer.nameForSymbol(e.ref);
+ const wrap = p.js.lenI() == p.for_of_init_start and strings.eql(name, "let");
+
+ if (wrap) {
+ p.print("(");
+ }
+
+ p.printSpaceBeforeIdentifier();
+ p.printIdentifier(name);
+
+ if (wrap) {
+ p.print(")");
+ }
},
.e_import_identifier => |e| {
- notimpl();
+ // Potentially use a property access instead of an identifier
+ const ref = p.symbols.follow(e.ref);
+ var didPrint = false;
+ if (p.symbols.get(ref)) |symbol| {
+ if (symbol.import_item_status == .missing) {
+ p.printUndefined(level);
+ didPrint = true;
+ } else if (symbol.namespace_alias) |namespace| {
+ // this feels crashy
+ var wrap = false;
+
+ if (p.call_target) |target| {
+ wrap = e.was_originally_identifier and target.e_import_identifier == e;
+ }
+
+ if (wrap) {
+ p.print("(0, ");
+ }
+
+ p.printSymbol(namespace.namespace_ref);
+ const alias = namespace.alias;
+ if (p.canPrintIdentifier(alias)) {
+ p.print(".");
+ p.printIdentifier(alias);
+ } else {
+ p.print("[");
+ p.printQuotedUTF8(alias, true);
+ p.print("]");
+ }
+ didPrint = true;
+
+ if (wrap) {
+ p.print(")");
+ }
+ }
+ }
+
+ if (!didPrint) {
+ p.printSymbol(e.ref);
+ }
},
.e_await => |e| {
- notimpl();
+ const wrap = level.gte(.prefix);
+
+ if (wrap) {
+ p.print("(");
+ }
+
+ p.printSpaceBeforeIdentifier();
+ p.print("await");
+ p.printSpace();
+ p.printExpr(e.value, Level.sub(.prefix, 1), ExprFlag.None());
+
+ if (wrap) {
+ p.print(")");
+ }
},
.e_yield => |e| {
- notimpl();
+ const wrap = level.gte(.assign);
+ if (wrap) {
+ p.print("(");
+ }
+
+ p.printSpaceBeforeIdentifier();
+ p.print("yield");
+
+ if (e.value) |val| {
+ if (e.is_star) {
+ p.print("*");
+ }
+ p.printSpace();
+ p.printExpr(val, .yield, ExprFlag.None());
+ }
+
+ if (wrap) {
+ p.print(")");
+ }
},
.e_unary => |e| {
- notimpl();
+ const entry: Op = Op.Table.get(e.op);
+ const wrap = level.gte(entry.level);
+
+ if (wrap) {
+ p.print("(");
+ }
+
+ if (!e.op.isPrefix()) {
+ p.printExpr(e.value, Op.Level.sub(.postfix, 1), ExprFlag.None());
+ }
+
+ if (entry.is_keyword) {
+ p.printSpaceBeforeIdentifier();
+ p.print(entry.text);
+ p.printSpace();
+ } else {
+ p.printSpaceBeforeOperator(e.op);
+ p.print(entry.text);
+ p.prev_op = e.op;
+ p.prev_op_end = p.js.lenI();
+ }
+
+ if (e.op.isPrefix()) {
+ p.printExpr(e.value, Op.Level.sub(.prefix, 1), ExprFlag.None());
+ }
+
+ if (wrap) {
+ p.print(")");
+ }
},
.e_binary => |e| {
- notimpl();
+ const entry: Op = Op.Table.get(e.op);
+ var wrap = level.gte(entry.level) or (e.op == Op.Code.bin_in and flags.forbid_in);
+
+ // Destructuring assignments must be parenthesized
+ const n = p.js.lenI();
+ if (n == p.stmt_start or n == p.arrow_expr_start) {
+ switch (e.left.data) {
+ .e_object => {
+ wrap = true;
+ },
+ else => {},
+ }
+ }
+
+ var left_level = entry.level.sub(1);
+ var right_level = left_level;
+
+ if (e.op.isRightAssociative()) {
+ left_level = entry.level;
+ }
+
+ if (e.op.isLeftAssociative()) {
+ right_level = entry.level;
+ }
+
+ switch (e.op) {
+ // "??" can't directly contain "||" or "&&" without being wrapped in parentheses
+ .bin_nullish_coalescing => {
+ switch (e.left.data) {
+ .e_binary => |left| {
+ switch (left.op) {
+ .bin_logical_and, .bin_logical_or => {
+ left_level = .prefix;
+ },
+ else => {},
+ }
+ },
+ else => {},
+ }
+
+ switch (e.right.data) {
+ .e_binary => |right| {
+ switch (right.op) {
+ .bin_logical_and, .bin_logical_or => {
+ right_level = .prefix;
+ },
+ else => {},
+ }
+ },
+ else => {},
+ }
+ },
+ // "**" can't contain certain unary expressions
+ .bin_pow => {
+ switch (e.left.data) {
+ .e_unary => |left| {
+ if (left.op.unaryAssignTarget() == .none) {
+ left_level = .call;
+ }
+ },
+ .e_await, .e_undefined, .e_number => {
+ left_level = .call;
+ },
+ else => {},
+ }
+ },
+ else => {},
+ }
+
+ // Special-case "#foo in bar"
+ if (e.op == .bin_in and @as(Expr.Tag, e.left.data) == .e_private_identifier) {
+ p.printSymbol(e.left.data.e_private_identifier.ref);
+ } else {
+ flags.forbid_in = true;
+ p.printExpr(e.left, left_level, flags);
+ }
+
+ if (e.op != .bin_comma) {
+ p.printSpace();
+ }
+
+ if (entry.is_keyword) {
+ p.printSpaceBeforeIdentifier();
+ p.print(entry.text);
+ } else {
+ p.printSpaceBeforeIdentifier();
+ p.print(entry.text);
+ p.prev_op = e.op;
+ p.prev_op_end = p.js.lenI();
+ }
+
+ p.printSpace();
+ flags.forbid_in = true;
+ p.printExpr(e.right, right_level, flags);
+
+ if (wrap) {
+ p.print(")");
+ }
},
else => {
std.debug.panic("Unexpected expression of type {s}", .{expr.data});
@@ -653,6 +1468,12 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
}
}
+ pub fn printDotThenSuffix(
+ p: *Printer,
+ ) callconv(.Inline) void {
+ p.print(")");
+ }
+
pub fn printProperty(p: *Printer, prop: G.Property) void {
notimpl();
}
@@ -697,7 +1518,7 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
.s_expr => |s| {
p.printIndent();
p.stmt_start = p.js.lenI();
- p.printExpr(s.value, .lowest, .expr_result_is_unused);
+ p.printExpr(s.value, .lowest, ExprFlag.ExprResultIsUnused());
p.printSemicolonAfterStatement();
},
else => {
@@ -706,6 +1527,44 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
}
}
+ pub fn printIdentifier(p: *Printer, identifier: string) void {
+ if (ascii_only) {
+ quoteIdentifier(&p.js, identifier) catch unreachable;
+ } else {
+ p.print(identifier);
+ }
+ }
+
+ pub fn printIdentifierUTF16(p: *Printer, name: JavascriptString) !void {
+ var temp = [_]u8{ 0, 0, 0, 0, 0, 0 };
+ const n = name.len;
+ var i: usize = 0;
+ while (i < n) : (i += 1) {
+ var c: u21 = name[i];
+
+ if (c >= first_high_surrogate and c <= last_high_surrogate and i + 1 < n) {
+ const c2: u21 = name[i + 1];
+ if (c2 >= first_low_surrogate and c2 <= last_low_surrogate) {
+ c = (c << 10) + c2 + (0x10000 - (first_high_surrogate << 10) - first_low_surrogate);
+ i += 1;
+ }
+ }
+
+ if (ascii_only and c > last_ascii) {
+ if (c > last_low_surrogate and c <= 0xFFFF) {
+ temp = [_]u8{ '\\', 'u', hex_chars[c >> 12], hex_chars[(c >> 8) & 15], hex_chars[(c >> 4) & 15], hex_chars[c & 15] };
+ p.print(&temp);
+ } else {
+ std.debug.panic("Not implemented yet: unicode escapes in ascii only", .{});
+ }
+ continue;
+ }
+
+ const width = std.unicode.utf8Encode(c, temp);
+ p.print(temp[0..width]);
+ }
+ }
+
pub fn printIndentedComment(p: *Printer, _text: string) void {
var text = _text;
if (strings.startsWith(text, "/*")) {
@@ -726,7 +1585,7 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
}
}
- pub fn init(allocator: *std.mem.Allocator, tree: Ast, symbols: Symbol.Map, opts: Options) !Printer {
+ pub fn init(allocator: *std.mem.Allocator, tree: Ast, symbols: Symbol.Map, opts: Options, linker: *Linker) !Printer {
var js = try MutableString.init(allocator, 1024);
return Printer{
.allocator = allocator,
@@ -735,17 +1594,47 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
.symbols = symbols,
.js = js,
.writer = js.writer(),
+ .linker = linker,
+ .renamer = rename.Renamer{
+ .symbols = symbols,
+ },
};
}
};
}
+// TODO:
+pub fn quoteIdentifier(js: *MutableString, identifier: string) !void {
+ return try js.append(identifier);
+ // assert(identifier.len > 0);
+ // var utf8iter = std.unicode.Utf8Iterator{ .bytes = identifier, .i = 0 };
+ // try js.growIfNeeded(identifier.len);
+
+ // var init = utf8iter.nextCodepoint() orelse unreachable;
+ // var ascii_start: usize = if (init >= first_ascii and init <= last_ascii) 0 else std.math.maxInt(usize);
+
+ // while (utf8iter.nextCodepoint()) |code_point| {
+ // switch (code_point) {
+ // first_ascii...last_ascii => {},
+ // else => {
+ // ascii_start = utf8iter.i;
+ // },
+ // }
+ // }
+}
+
const UnicodePrinter = NewPrinter(false);
const AsciiPrinter = NewPrinter(true);
-pub fn printAst(allocator: *std.mem.Allocator, tree: Ast, symbols: js_ast.Symbol.Map, ascii_only: bool, opts: Options) !PrintResult {
+pub fn printAst(allocator: *std.mem.Allocator, tree: Ast, symbols: js_ast.Symbol.Map, ascii_only: bool, opts: Options, linker: *Linker) !PrintResult {
if (ascii_only) {
- var printer = try AsciiPrinter.init(allocator, tree, symbols, opts);
+ var printer = try AsciiPrinter.init(
+ allocator,
+ tree,
+ symbols,
+ opts,
+ linker,
+ );
for (tree.parts) |part| {
for (part.stmts) |stmt| {
try printer.printStmt(stmt);
@@ -756,7 +1645,13 @@ pub fn printAst(allocator: *std.mem.Allocator, tree: Ast, symbols: js_ast.Symbol
.js = printer.js.toOwnedSlice(),
};
} else {
- var printer = try UnicodePrinter.init(allocator, tree, symbols, opts);
+ var printer = try UnicodePrinter.init(
+ allocator,
+ tree,
+ symbols,
+ opts,
+ linker,
+ );
for (tree.parts) |part| {
for (part.stmts) |stmt| {
try printer.printStmt(stmt);
diff --git a/src/json_parser.zig b/src/json_parser.zig
index f42fbc89f..a46375929 100644
--- a/src/json_parser.zig
+++ b/src/json_parser.zig
@@ -22,7 +22,6 @@ const StmtNodeList = js_ast.StmtNodeList;
const BindingNodeList = js_ast.BindingNodeList;
const assert = std.debug.assert;
-const Ref = js_ast.Ref;
const LocRef = js_ast.LocRef;
const S = js_ast.S;
const B = js_ast.B;
@@ -239,8 +238,9 @@ fn expectPrintedJSON(_contents: string, expected: string) void {
if (log.msgs.items.len > 0) {
std.debug.panic("--FAIL--\nExpr {s}\nLog: {s}\n--FAIL--", .{ expr, log.msgs.items[0].data.text });
}
+ var linker = @import("linker.zig").Linker{};
- const result = js_printer.printAst(alloc.dynamic, tree, symbol_map, true, js_printer.Options{ .to_module_ref = Ref{ .inner_index = 0 } }) catch unreachable;
+ const result = js_printer.printAst(alloc.dynamic, tree, symbol_map, true, js_printer.Options{ .to_module_ref = Ref{ .inner_index = 0 } }, &linker) catch unreachable;
var js = result.js;
diff --git a/src/linker.zig b/src/linker.zig
new file mode 100644
index 000000000..884dfe6ee
--- /dev/null
+++ b/src/linker.zig
@@ -0,0 +1,11 @@
+const std = @import("std");
+const fs = @import("fs.zig");
+usingnamespace @import("ast/base.zig");
+
+pub const Linker = struct {
+ // fs: fs.FileSystem,
+ // TODO:
+ pub fn requireOrImportMetaForSource(c: Linker, source_index: Ref.Int) RequireOrImportMeta {
+ return RequireOrImportMeta{};
+ }
+};
diff --git a/src/logger.zig b/src/logger.zig
index 0aa37bdc8..a914f4741 100644
--- a/src/logger.zig
+++ b/src/logger.zig
@@ -273,14 +273,10 @@ pub const Source = struct {
pub const ErrorPosition = struct { line_start: usize, line_end: usize, column_count: usize, line_count: usize };
pub fn initFile(file: fs.File, allocator: *std.mem.Allocator) Source {
- std.debug.assert(file.contents != null);
var name = file.path.name;
var identifier_name = name.nonUniqueNameString(allocator) catch unreachable;
- if (file.contents) |contents| {
- return Source{ .path = file.path, .identifier_name = identifier_name, .contents = contents };
- } else {
- std.debug.panic("Expected file.contents to not be null. {s}", .{file});
- }
+
+ return Source{ .path = file.path, .identifier_name = identifier_name, .contents = file.contents };
}
pub fn initPathString(pathString: string, contents: string) Source {
diff --git a/src/main.zig b/src/main.zig
index 5410b0e2a..6144fdf24 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -6,6 +6,7 @@ const options = @import("options.zig");
const js_parser = @import("js_parser.zig");
const js_printer = @import("js_printer.zig");
const js_ast = @import("js_ast.zig");
+const linker = @import("linker.zig");
pub fn main() anyerror!void {
try alloc.setup(std.heap.page_allocator);
@@ -28,8 +29,15 @@ pub fn main() anyerror!void {
var source = logger.Source.initFile(opts.entry_point, alloc.dynamic);
var parser = try js_parser.Parser.init(opts, &log, &source, alloc.dynamic);
var res = try parser.parse();
-
- const printed = try js_printer.printAst(alloc.dynamic, res.ast, js_ast.Symbol.Map{}, false, js_printer.Options{ .to_module_ref = js_ast.Ref{ .inner_index = 0 } });
+ var _linker = linker.Linker{};
+ const printed = try js_printer.printAst(
+ alloc.dynamic,
+ res.ast,
+ js_ast.Symbol.Map{},
+ false,
+ js_printer.Options{ .to_module_ref = js_ast.Ref{ .inner_index = 0 } },
+ &_linker,
+ );
std.debug.print("{s}\n{s}", .{ res, printed });
}
diff --git a/src/options.zig b/src/options.zig
index f58149efb..35ea37e66 100644
--- a/src/options.zig
+++ b/src/options.zig
@@ -56,7 +56,10 @@ pub const TransformOptions = struct {
var filesystemCache = std.StringHashMap(fs.File).init(allocator);
- var entryPoint = fs.File{ .path = fs.Path.init(entryPointName), .contents = code, .mtime = null };
+ var entryPoint = fs.File{
+ .path = fs.Path.init(entryPointName),
+ .contents = code,
+ };
var define = std.StringHashMap(string).init(allocator);
try define.ensureCapacity(1);
diff --git a/src/renamer.zig b/src/renamer.zig
index ed811bd1c..c39a51847 100644
--- a/src/renamer.zig
+++ b/src/renamer.zig
@@ -1,4 +1,6 @@
const js_ast = @import("js_ast.zig");
+usingnamespace @import("strings.zig");
+const std = @import("std");
pub const Renamer = struct {
symbols: js_ast.Symbol.Map,
diff --git a/src/string_immutable.zig b/src/string_immutable.zig
index abfeec072..c1197e6ef 100644
--- a/src/string_immutable.zig
+++ b/src/string_immutable.zig
@@ -52,9 +52,7 @@ pub fn endsWithAny(self: string, str: string) bool {
return false;
}
-pub fn lastNonwhitespace(self: string, str: string) bool {
-
-}
+pub fn lastNonwhitespace(self: string, str: string) bool {}
pub fn endsWithAnyComptime(self: string, comptime str: string) bool {
if (str.len < 10) {
@@ -90,3 +88,82 @@ pub fn index(self: string, str: string) i32 {
pub fn eqlUtf16(comptime self: string, other: JavascriptString) bool {
return std.mem.eql(u16, std.unicode.utf8ToUtf16LeStringLiteral(self), other);
}
+
+pub fn toUTF16Buf(in: string, out: []u16) usize {
+ var utf8Iterator = std.unicode.Utf8Iterator{ .bytes = in, .i = 0 };
+
+ var c: u21 = 0;
+ var i: usize = 0;
+ while (utf8Iterator.nextCodepoint()) |code_point| {
+ switch (code_point) {
+ 0...0xFFFF => {
+ out[i] = @intCast(u16, code_point);
+ i += 1;
+ },
+ else => {
+ c = code_point - 0x10000;
+ out[i] = @intCast(u16, 0xD800 + ((c >> 10) & 0x3FF));
+ i += 1;
+ out[i] = @intCast(u16, 0xDC00 + (c & 0x3FF));
+ i += 1;
+ },
+ }
+ }
+
+ return utf8Iterator.i;
+}
+
+pub fn toUTF16Alloc(in: string, allocator: *std.mem.Allocator) !JavascriptString {
+ var utf8Iterator = std.unicode.Utf8Iterator{ .bytes = in, .i = 0 };
+ var out = try std.ArrayList(u16).initCapacity(allocator, in.len);
+
+ var c: u21 = 0;
+ var i: usize = 0;
+ while (utf8Iterator.nextCodepoint()) |code_point| {
+ switch (code_point) {
+ 0...0xFFFF => {
+ try out.append(@intCast(u16, code_point));
+ },
+ else => {
+ c = code_point - 0x10000;
+ try out.append(@intCast(u16, 0xD800 + ((c >> 10) & 0x3FF)));
+ try out.append(@intCast(u16, 0xDC00 + (c & 0x3FF)));
+ },
+ }
+ }
+
+ return out.toOwnedSlice();
+}
+
+pub fn containsNonBmpCodePoint(text: string) bool {
+ var iter = std.unicode.Utf8Iterator{ .bytes = text, .i = 0 };
+
+ while (iter.nextCodepoint()) |codepoint| {
+ if (codepoint > 0xFFFF) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+pub fn containsNonBmpCodePointUTF16(_text: JavascriptString) bool {
+ const n = _text.len;
+ if (n > 0) {
+ var i: usize = 0;
+ var c: u16 = 0;
+ var c2: u16 = 0;
+ var text = _text[0 .. n - 1];
+ while (i < n - 1) : (i += 1) {
+ c = text[i];
+ if (c >= 0xD800 and c <= 0xDBFF) {
+ c2 = text[i + 1];
+ if (c2 >= 0xDC00 and c2 <= 0xDFFF) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}