aboutsummaryrefslogtreecommitdiff
path: root/src/js_printer.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/js_printer.zig')
-rw-r--r--src/js_printer.zig999
1 files changed, 947 insertions, 52 deletions
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);