const std = @import("std"); const logger = @import("root").bun.logger; const js_lexer = bun.js_lexer; const importRecord = @import("import_record.zig"); const js_ast = bun.JSAst; const options = @import("options.zig"); const rename = @import("renamer.zig"); const runtime = @import("runtime.zig"); const Lock = @import("./lock.zig").Lock; const Api = @import("./api/schema.zig").Api; const fs = @import("fs.zig"); const bun = @import("root").bun; const string = bun.string; const Output = bun.Output; const Global = bun.Global; const Environment = bun.Environment; const strings = bun.strings; const MutableString = bun.MutableString; const stringZ = bun.stringZ; const default_allocator = bun.default_allocator; const C = bun.C; const Ref = @import("ast/base.zig").Ref; const StoredFileDescriptorType = bun.StoredFileDescriptorType; const FeatureFlags = bun.FeatureFlags; const FileDescriptorType = bun.FileDescriptor; const expect = std.testing.expect; const ImportKind = importRecord.ImportKind; const BindingNodeIndex = js_ast.BindingNodeIndex; const LocRef = js_ast.LocRef; const S = js_ast.S; const B = js_ast.B; const G = js_ast.G; const T = js_lexer.T; const E = js_ast.E; const Stmt = js_ast.Stmt; const Expr = js_ast.Expr; const Binding = js_ast.Binding; const Symbol = js_ast.Symbol; const Level = js_ast.Op.Level; const Op = js_ast.Op; const Scope = js_ast.Scope; const locModuleScope = logger.Loc.Empty; const Ast = js_ast.Ast; const hex_chars = "0123456789ABCDEF"; const first_ascii = 0x20; const last_ascii = 0x7E; const first_high_surrogate = 0xD800; const last_high_surrogate = 0xDBFF; const first_low_surrogate = 0xDC00; const last_low_surrogate = 0xDFFF; const CodepointIterator = @import("./string_immutable.zig").UnsignedCodepointIterator; const assert = std.debug.assert; threadlocal var imported_module_ids_list: std.ArrayList(u32) = undefined; threadlocal var imported_module_ids_list_unset: bool = true; const ImportRecord = bun.ImportRecord; const SourceMap = @import("./sourcemap/sourcemap.zig"); /// For support JavaScriptCore const ascii_only_always_on_unless_minifying = true; fn formatUnsignedIntegerBetween(comptime len: u16, buf: *[len]u8, val: u64) void { comptime var i: u16 = len; var remainder = val; // Write out the number from the end to the front inline while (i > 0) { comptime i -= 1; buf[comptime i] = @as(u8, @intCast((remainder % 10))) + '0'; remainder /= 10; } } pub fn writeModuleId(comptime Writer: type, writer: Writer, module_id: u32) void { std.debug.assert(module_id != 0); // either module_id is forgotten or it should be disabled _ = writer.writeAll("$") catch unreachable; std.fmt.formatInt(module_id, 16, .lower, .{}, writer) catch unreachable; } pub fn canPrintWithoutEscape(comptime CodePointType: type, c: CodePointType, comptime ascii_only: bool) bool { if (c <= last_ascii) { return c >= first_ascii and c != '\\' and c != '"'; } else { return !ascii_only and c != 0xFEFF and (c < first_high_surrogate or c > last_low_surrogate); } } const indentation_buf = [_]u8{' '} ** 128; pub fn bestQuoteCharForString(comptime Type: type, str: []const Type, allow_backtick: bool) u8 { var single_cost: usize = 0; var double_cost: usize = 0; var backtick_cost: usize = 0; var char: u8 = 0; var i: usize = 0; while (i < str.len) { switch (str[i]) { '\'' => { single_cost += 1; }, '"' => { double_cost += 1; }, '`' => { backtick_cost += 1; }, '\r', '\n' => { if (allow_backtick) { return '`'; } }, '\\' => { i += 1; }, '$' => { if (i + 1 < str.len and str[i + 1] == '{') { backtick_cost += 1; } }, else => {}, } i += 1; } char = '"'; if (double_cost > single_cost) { char = '\''; if (single_cost > backtick_cost and allow_backtick) { char = '`'; } } else if (double_cost > backtick_cost and allow_backtick) { char = '`'; } return char; } const Whitespacer = struct { normal: []const u8, minify: []const u8, pub fn append(this: Whitespacer, comptime str: []const u8) Whitespacer { return .{ .normal = this.normal ++ str, .minify = this.minify ++ str }; } }; fn ws(comptime str: []const u8) Whitespacer { const Static = struct { pub const with = str; pub const without = brk: { var buf = [_]u8{0} ** str.len; var i: usize = 0; var buf_i: usize = 0; while (i < str.len) : (i += 1) { if (str[i] != ' ') { buf[buf_i] = str[i]; buf_i += 1; } } break :brk buf[0..buf_i]; }; }; return .{ .normal = Static.with, .minify = Static.without }; } pub fn estimateLengthForJSON(input: []const u8, comptime ascii_only: bool) usize { var remaining = input; var len: u32 = 2; // for quotes while (strings.indexOfNeedsEscape(remaining)) |i| { len += i; remaining = remaining[i..]; const char_len = strings.wtf8ByteSequenceLengthWithInvalid(remaining[0]); const c = strings.decodeWTF8RuneT(remaining.ptr[0..4], char_len, i32, 0); if (canPrintWithoutEscape(i32, c, ascii_only)) { len += @as(u32, char_len); } else if (c <= 0xFFFF) { len += 6; } else { len += 12; } remaining = remaining[char_len..]; } else { return @as(u32, @truncate(remaining.len)) + 2; } return len; } pub fn quoteForJSON(text: []const u8, output_: MutableString, comptime ascii_only: bool) !MutableString { var bytes = output_; try bytes.growIfNeeded(estimateLengthForJSON(text, ascii_only)); try bytes.appendChar('"'); var i: usize = 0; var n: usize = text.len; while (i < n) { const width = strings.wtf8ByteSequenceLengthWithInvalid(text[i]); const c = strings.decodeWTF8RuneT(text.ptr[i .. i + 4][0..4], width, i32, 0); if (canPrintWithoutEscape(i32, c, ascii_only)) { const remain = text[i + @as(usize, width) ..]; if (strings.indexOfNeedsEscape(remain)) |j| { try bytes.appendSlice(text[i .. i + j + @as(usize, width)]); i += j + @as(usize, width); continue; } else { try bytes.appendSlice(text[i..]); i = n; break; } } switch (c) { 0x07 => { try bytes.appendSlice("\\x07"); i += 1; }, 0x08 => { try bytes.appendSlice("\\b"); i += 1; }, 0x0C => { try bytes.appendSlice("\\f"); i += 1; }, '\n' => { try bytes.appendSlice("\\n"); i += 1; }, std.ascii.control_code.cr => { try bytes.appendSlice("\\r"); i += 1; }, // \v std.ascii.control_code.vt => { try bytes.appendSlice("\\v"); i += 1; }, // "\\" '\\' => { try bytes.appendSlice("\\\\"); i += 1; }, '"' => { try bytes.appendSlice("\\\""); i += 1; }, '\t' => { try bytes.appendSlice("\\t"); i += 1; }, else => { i += @as(usize, width); if (c < 0xFFFF) { const k = @as(usize, @intCast(c)); bytes.ensureUnusedCapacity(6) catch unreachable; const old = bytes.list.items.len; bytes.list.items.len += 6; bytes.list.items[old .. old + 6].ptr[0..6].* = [_]u8{ '\\', 'u', hex_chars[(k >> 12) & 0xF], hex_chars[(k >> 8) & 0xF], hex_chars[(k >> 4) & 0xF], hex_chars[k & 0xF], }; } else { bytes.ensureUnusedCapacity(12) catch unreachable; const old = bytes.list.items.len; bytes.list.items.len += 12; const k = c - 0x10000; const lo = @as(usize, @intCast(first_high_surrogate + ((k >> 10) & 0x3FF))); const hi = @as(usize, @intCast(first_low_surrogate + (k & 0x3FF))); bytes.list.items[old .. old + 12][0..12].* = [_]u8{ '\\', 'u', hex_chars[lo >> 12], hex_chars[(lo >> 8) & 15], hex_chars[(lo >> 4) & 15], hex_chars[lo & 15], '\\', 'u', hex_chars[hi >> 12], hex_chars[(hi >> 8) & 15], hex_chars[(hi >> 4) & 15], hex_chars[hi & 15], }; } }, } } bytes.appendChar('"') catch unreachable; return bytes; } const JSONFormatter = struct { input: []const u8, pub fn format(self: JSONFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { try writeJSONString(self.input, @TypeOf(writer), writer, .latin1); } }; /// Expects latin1 pub fn formatJSONString(text: []const u8) JSONFormatter { return JSONFormatter{ .input = text }; } pub fn writeJSONString(input: []const u8, comptime Writer: type, writer: Writer, comptime encoding: strings.Encoding) !void { try writer.writeAll("\""); var text = input; const end = text.ptr + text.len; if (comptime encoding == .utf16) { @compileError("not implemented yet"); } while (text.ptr != end) { const width = if (comptime encoding == .latin1 or encoding == .ascii) 1 else strings.wtf8ByteSequenceLengthWithInvalid(text[0]); const c: i32 = if (comptime encoding == .utf8) strings.decodeWTF8RuneT(text.ptr[0..4], width, i32, 0) else brk: { const char = text[0]; if (char <= 0x7F) { break :brk char; } else break :brk strings.latin1ToCodepointAssumeNotASCII(char, i32); }; if (canPrintWithoutEscape(i32, c, false)) { const remain = text[@as(usize, width)..]; if (encoding != .utf8 and width > 0) { var codepoint_bytes: [4]u8 = undefined; std.mem.writeIntNative(i32, &codepoint_bytes, c); try writer.writeAll( codepoint_bytes[0..strings.encodeWTF8Rune(codepoint_bytes[0..4], c)], ); } if (strings.indexOfNeedsEscape(remain)) |j| { try writer.writeAll(remain[0..j]); text = remain[j..]; continue; } else { try writer.writeAll(remain); break; } } switch (c) { // Special-case the bell character since it may cause dumping this file to // the terminal to make a sound, which is undesirable. Note that we can't // use an octal literal to print this shorter since octal literals are not // allowed in strict mode (or in template strings). 0x07 => { try writer.writeAll("\\x07"); text = text[1..]; }, 0x08 => { try writer.writeAll("\\b"); text = text[1..]; }, 0x0C => { try writer.writeAll("\\f"); text = text[1..]; }, '\n' => { try writer.writeAll("\\n"); text = text[1..]; }, std.ascii.control_code.cr => { try writer.writeAll("\\r"); text = text[1..]; }, // \v std.ascii.control_code.vt => { try writer.writeAll("\\v"); text = text[1..]; }, // "\\" '\\' => { try writer.writeAll("\\\\"); text = text[1..]; }, '"' => { try writer.writeAll("\\\""); text = text[1..]; }, '\t' => { try writer.writeAll("\\t"); text = text[1..]; }, else => { text = text[@as(usize, width)..]; if (c < 0xFFFF) { const k = @as(usize, @intCast(c)); try writer.writeAll(&[_]u8{ '\\', 'u', hex_chars[(k >> 12) & 0xF], hex_chars[(k >> 8) & 0xF], hex_chars[(k >> 4) & 0xF], hex_chars[k & 0xF], }); } else { const k = c - 0x10000; const lo = @as(usize, @intCast(first_high_surrogate + ((k >> 10) & 0x3FF))); const hi = @as(usize, @intCast(first_low_surrogate + (k & 0x3FF))); try writer.writeAll(&[_]u8{ '\\', 'u', hex_chars[lo >> 12], hex_chars[(lo >> 8) & 15], hex_chars[(lo >> 4) & 15], hex_chars[lo & 15], '\\', 'u', hex_chars[hi >> 12], hex_chars[(hi >> 8) & 15], hex_chars[(hi >> 4) & 15], hex_chars[hi & 15], }); } }, } } try writer.writeAll("\""); } test "quoteForJSON" { var allocator = default_allocator; try std.testing.expectEqualStrings( "\"I don't need any quotes.\"", (try quoteForJSON("I don't need any quotes.", MutableString.init(allocator, 0) catch unreachable, false)).list.items, ); try std.testing.expectEqualStrings( "\"I need a quote for \\\"this\\\".\"", (try quoteForJSON("I need a quote for \"this\".", MutableString.init(allocator, 0) catch unreachable, false)).list.items, ); } pub const SourceMapHandler = struct { ctx: *anyopaque, callback: Callback, const Callback = *const fn (*anyopaque, chunk: SourceMap.Chunk, source: logger.Source) anyerror!void; pub fn onSourceMapChunk(self: *const @This(), chunk: SourceMap.Chunk, source: logger.Source) anyerror!void { try self.callback(self.ctx, chunk, source); } pub fn For(comptime Type: type, comptime handler: (fn (t: *Type, chunk: SourceMap.Chunk, source: logger.Source) anyerror!void)) type { return struct { pub fn onChunk(self: *anyopaque, chunk: SourceMap.Chunk, source: logger.Source) anyerror!void { try handler(@as(*Type, @ptrCast(@alignCast(self))), chunk, source); } pub fn init(self: *Type) SourceMapHandler { return SourceMapHandler{ .ctx = self, .callback = onChunk }; } }; } }; pub const Options = struct { transform_imports: bool = true, to_commonjs_ref: Ref = Ref.None, to_esm_ref: Ref = Ref.None, require_ref: ?Ref = null, indent: usize = 0, externals: []u32 = &[_]u32{}, runtime_imports: runtime.Runtime.Imports = runtime.Runtime.Imports{}, module_hash: u32 = 0, source_path: ?fs.Path = null, rewrite_require_resolve: bool = true, allocator: std.mem.Allocator = default_allocator, source_map_handler: ?SourceMapHandler = null, source_map_builder: ?*bun.sourcemap.Chunk.Builder = null, css_import_behavior: Api.CssInJsBehavior = Api.CssInJsBehavior.facade, commonjs_named_exports: js_ast.Ast.CommonJSNamedExports = .{}, commonjs_named_exports_deoptimized: bool = false, commonjs_named_exports_ref: Ref = Ref.None, minify_whitespace: bool = false, minify_identifiers: bool = false, minify_syntax: bool = false, transform_only: bool = false, inline_require_and_import_errors: bool = true, require_or_import_meta_for_source_callback: RequireOrImportMeta.Callback = .{}, module_type: options.OutputFormat = .preserve, /// Used for cross-module inlining of import items when bundling const_values: std.HashMapUnmanaged(Ref, Expr, Ref.HashCtx, 80) = .{}, // TODO: remove this // The reason for this is: // 1. You're bundling a React component // 2. jsx auto imports are prepended to the list of parts // 3. The AST modification for bundling only applies to the final part // 4. This means that it will try to add a toplevel part which is not wrapped in the arrow function, which is an error // TypeError: $30851277 is not a function. (In '$30851277()', '$30851277' is undefined) // at (anonymous) (0/node_modules.server.e1b5ffcd183e9551.jsb:1463:21) // at #init_react/jsx-dev-runtime.js (0/node_modules.server.e1b5ffcd183e9551.jsb:1309:8) // at (esm) (0/node_modules.server.e1b5ffcd183e9551.jsb:1480:30) // The temporary fix here is to tag a stmts ptr as the one we want to prepend to // Then, when we're JUST about to print it, we print the body of prepend_part_value first prepend_part_key: ?*anyopaque = null, prepend_part_value: ?*js_ast.Part = null, // 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: ?SourceMap.LineOffsetTable.List = null, pub inline fn unindent(self: *Options) void { self.indent -|= 1; } pub fn requireOrImportMetaForSource( self: *const Options, id: u32, was_unwrapped_require: bool, ) RequireOrImportMeta { if (self.require_or_import_meta_for_source_callback.ctx == null) return .{}; return self.require_or_import_meta_for_source_callback.call(id, was_unwrapped_require); } }; 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, was_unwrapped_require: bool = false, pub const Callback = struct { const Fn = fn (*anyopaque, u32, bool) RequireOrImportMeta; ctx: ?*anyopaque = null, callback: *const Fn = undefined, pub fn call(self: Callback, id: u32, was_unwrapped_require: bool) RequireOrImportMeta { return self.callback(self.ctx.?, id, was_unwrapped_require); } pub fn init( comptime Type: type, comptime callback: (fn (t: *Type, id: u32, was_unwrapped_require: bool) RequireOrImportMeta), ctx: *Type, ) Callback { return Callback{ .ctx = bun.cast(*anyopaque, ctx), .callback = @as(*const Fn, @ptrCast(&callback)), }; } }; }; pub const PrintResult = union(enum) { result: struct { code: []u8, source_map: ?SourceMap.Chunk = null, }, err: anyerror, pub fn clone( this: PrintResult, allocator: std.mem.Allocator, ) !PrintResult { return switch (this) { .result => PrintResult{ .result = .{ .code = try allocator.dupe(u8, this.result.code), .source_map = this.result.source_map, }, }, .err => PrintResult{ .err = this.err, }, }; } }; // do not make this a packed struct // stage1 compiler bug: // > /optional-chain-with-function.js: Evaluation failed: TypeError: (intermediate value) is not a function // this test failure was caused by the packed structi mplementation const ExprFlag = enum { forbid_call, forbid_in, has_non_optional_chain_parent, expr_result_is_unused, pub const Set = std.enums.EnumSet(ExprFlag); pub fn None() ExprFlag.Set { return Set{}; } pub fn ForbidCall() ExprFlag.Set { return Set.init(.{ .forbid_call = true }); } pub fn ForbidAnd() ExprFlag.Set { return Set.init(.{ .forbid_and = true }); } pub fn HasNonOptionalChainParent() ExprFlag.Set { return Set.init(.{ .has_non_optional_chain_parent = true }); } pub fn ExprResultIsUnused() ExprFlag.Set { return Set.init(.{ .expr_result_is_unused = true }); } }; const ImportVariant = enum { path_only, import_star, import_default, import_star_and_import_default, import_items, import_items_and_default, import_items_and_star, import_items_and_default_and_star, pub inline fn hasItems(import_variant: @This()) @This() { return switch (import_variant) { .import_default => .import_items_and_default, .import_star => .import_items_and_star, .import_star_and_import_default => .import_items_and_default_and_star, else => .import_items, }; } // We always check star first so don't need to be exhaustive here pub inline fn hasStar(import_variant: @This()) @This() { return switch (import_variant) { .path_only => .import_star, else => import_variant, }; } // We check default after star pub inline fn hasDefault(import_variant: @This()) @This() { return switch (import_variant) { .path_only => .import_default, .import_star => .import_star_and_import_default, else => import_variant, }; } pub fn determine(record: *const ImportRecord, s_import: *const S.Import) ImportVariant { var variant = ImportVariant.path_only; if (record.contains_import_star) { variant = variant.hasStar(); } if (!record.was_originally_bare_import) { if (!record.contains_default_alias) { if (s_import.default_name) |default_name| { if (default_name.ref != null) { variant = variant.hasDefault(); } } } else { variant = variant.hasDefault(); } } if (s_import.items.len > 0) { variant = variant.hasItems(); } return variant; } }; fn NewPrinter( comptime ascii_only: bool, comptime Writer: type, comptime rewrite_esm_to_cjs: bool, comptime is_bun_platform: bool, comptime is_json: bool, comptime generate_source_map: bool, ) type { return struct { import_records: []const ImportRecord, needs_semicolon: bool = false, stmt_start: i32 = -1, options: Options, export_default_start: i32 = -1, arrow_expr_start: i32 = -1, for_of_init_start: i32 = -1, prev_op: Op.Code = Op.Code.bin_add, prev_op_end: i32 = -1, prev_num_end: i32 = -1, prev_reg_exp_end: i32 = -1, call_target: ?Expr.Data = null, writer: Writer, has_printed_bundled_import_statement: bool = false, imported_module_ids: std.ArrayList(u32), renamer: rename.Renamer, prev_stmt_tag: Stmt.Tag = .s_empty, source_map_builder: SourceMap.Chunk.Builder = undefined, symbol_counter: u32 = 0, temporary_bindings: std.ArrayListUnmanaged(B.Property) = .{}, const Printer = @This(); pub fn writeAll(p: *Printer, bytes: anytype) anyerror!void { p.print(bytes); return; } pub fn writeByteNTimes(self: *Printer, byte: u8, n: usize) !void { var bytes: [256]u8 = undefined; @memset(bytes[0..], byte); var remaining: usize = n; while (remaining > 0) { const to_write = @min(remaining, bytes.len); try self.writeAll(bytes[0..to_write]); remaining -= to_write; } } fn fmt(p: *Printer, comptime str: string, args: anytype) !void { const len = @call( .{ .modifier = .always_inline, }, std.fmt.count, .{ str, args }, ); var ptr = try p.writer.reserveNext( len, ); const written = @call( .{ .modifier = .always_inline, }, std.fmt.bufPrint, .{ ptr[0..len], str, args }, ) catch unreachable; p.writer.advance(written.len); } pub fn printBuffer(p: *Printer, str: []const u8) void { p.writer.print([]const u8, str); } pub fn print(p: *Printer, str: anytype) void { const StringType = @TypeOf(str); switch (comptime StringType) { comptime_int, u16, u8 => { p.writer.print(StringType, str); }, [6]u8 => { const span = str[0..6]; p.writer.print(@TypeOf(span), span); }, else => { p.writer.print(StringType, str); }, } } pub inline fn unsafePrint(p: *Printer, str: string) void { p.print(str); } pub fn printIndent(p: *Printer) void { if (p.options.indent == 0 or p.options.minify_whitespace) { return; } var i: usize = p.options.indent * 2; while (i > 0) { const amt = @min(i, indentation_buf.len); p.print(indentation_buf[0..amt]); i -= amt; } } pub inline fn printSpace(p: *Printer) void { if (!p.options.minify_whitespace) p.print(" "); } pub inline fn printNewline(p: *Printer) void { if (!p.options.minify_whitespace) p.print("\n"); } pub inline fn printSemicolonAfterStatement(p: *Printer) void { if (!p.options.minify_whitespace) { p.print(";\n"); } else { p.needs_semicolon = true; } } pub fn printSemicolonIfNeeded(p: *Printer) void { if (p.needs_semicolon) { p.print(";"); p.needs_semicolon = false; } } fn @"print = "(p: *Printer) void { if (p.options.minify_whitespace) { p.print("="); } else { p.print(" = "); } } fn printBunJestImportStatement(p: *Printer, import: S.Import) void { if (comptime !is_bun_platform) unreachable; switch (p.options.module_type) { .cjs => { printInternalBunImport(p, import, @TypeOf("globalThis.Bun.jest(__filename)"), "globalThis.Bun.jest(__filename)"); }, else => { printInternalBunImport(p, import, @TypeOf("globalThis.Bun.jest(import.meta.path)"), "globalThis.Bun.jest(import.meta.path)"); }, } } fn printGlobalBunImportStatement(p: *Printer, import: S.Import) void { if (comptime !is_bun_platform) unreachable; printInternalBunImport(p, import, @TypeOf("globalThis.Bun"), "globalThis.Bun"); } fn printHardcodedImportStatement(p: *Printer, import: S.Import) void { if (comptime !is_bun_platform) unreachable; printInternalBunImport(p, import, void, {}); } fn printInternalBunImport(p: *Printer, import: S.Import, comptime Statement: type, statement: Statement) void { if (comptime !is_bun_platform) unreachable; if (import.star_name_loc != null) { p.print("var "); p.printSymbol(import.namespace_ref); p.printSpace(); p.print("="); p.printSpaceBeforeIdentifier(); if (comptime Statement == void) { p.printRequireOrImportExpr(import.import_record_index, false, &.{}, Level.lowest, ExprFlag.None()); } else { p.print(statement); } p.printSemicolonAfterStatement(); p.printIndent(); } if (import.default_name) |default| { p.print("var "); p.printSymbol(default.ref.?); if (comptime Statement == void) { p.@"print = "(); p.printRequireOrImportExpr(import.import_record_index, false, &.{}, Level.lowest, ExprFlag.None()); } else { p.@"print = "(); p.print(statement); } p.printSemicolonAfterStatement(); } if (import.items.len > 0) { p.printWhitespacer(ws("var {")); if (!import.is_single_line) { p.printNewline(); p.options.indent += 1; p.printIndent(); } for (import.items, 0..) |item, i| { if (i > 0) { p.print(","); p.printSpace(); if (!import.is_single_line) { p.printNewline(); p.printIndent(); } } p.printClauseItemAs(item, .@"var"); } if (!import.is_single_line) { p.printNewline(); p.options.unindent(); } else { p.printSpace(); } p.printWhitespacer(ws("} = ")); if (import.star_name_loc == null and import.default_name == null) { if (comptime Statement == void) { p.printRequireOrImportExpr(import.import_record_index, false, &.{}, Level.lowest, ExprFlag.None()); } else { p.print(statement); } } else if (import.default_name) |name| { p.printSymbol(name.ref.?); } else { p.printSymbol(import.namespace_ref); } p.printSemicolonAfterStatement(); } } pub inline fn printSpaceBeforeIdentifier( p: *Printer, ) void { if (p.writer.written > 0 and (js_lexer.isIdentifierContinue(@as(i32, p.writer.prevChar())) or p.writer.written == p.prev_reg_exp_end)) { p.print(" "); } } pub inline fn maybePrintSpace( p: *Printer, ) void { switch (p.writer.prevChar()) { 0, ' ', '\n' => {}, else => { p.print(" "); }, } } pub fn printDotThenPrefix(p: *Printer) Level { p.print(".then(() => "); return .comma; } pub inline fn printUndefined(p: *Printer, loc: logger.Loc, level: Level) void { if (p.options.minify_syntax) { if (level.gte(Level.prefix)) { p.addSourceMapping(loc); p.print("(void 0)"); } else { p.printSpaceBeforeIdentifier(); p.addSourceMapping(loc); p.print("void 0"); } } else { p.addSourceMapping(loc); p.print("undefined"); } } pub fn printBody(p: *Printer, stmt: Stmt) void { switch (stmt.data) { .s_block => |block| { p.printSpace(); p.printBlock(stmt.loc, block.stmts, block.close_brace_loc); p.printNewline(); }, else => { p.printNewline(); p.options.indent += 1; p.printStmt(stmt) catch unreachable; p.options.unindent(); }, } } pub fn printBlockBody(p: *Printer, stmts: []const Stmt) void { for (stmts) |stmt| { p.printSemicolonIfNeeded(); p.printStmt(stmt) catch unreachable; } } pub fn printBlock(p: *Printer, loc: logger.Loc, stmts: []const Stmt, close_brace_loc: ?logger.Loc) void { p.addSourceMapping(loc); p.print("{"); p.printNewline(); p.options.indent += 1; p.printBlockBody(stmts); p.options.unindent(); p.needs_semicolon = false; p.printIndent(); if (close_brace_loc != null and close_brace_loc.?.start > loc.start) { p.addSourceMapping(close_brace_loc.?); } p.print("}"); } pub fn printTwoBlocksInOne(p: *Printer, loc: logger.Loc, stmts: []const Stmt, prepend: []const Stmt) void { p.addSourceMapping(loc); p.print("{"); p.printNewline(); p.options.indent += 1; p.printBlockBody(prepend); p.printBlockBody(stmts); p.options.unindent(); p.needs_semicolon = false; p.printIndent(); p.print("}"); } pub fn printDecls(p: *Printer, comptime keyword: string, decls_: []G.Decl, flags: ExprFlag.Set) void { p.print(keyword); p.printSpace(); var decls = decls_; if (decls.len == 0) { // "var ;" is invalid syntax // assert we never reach it unreachable; } if (comptime FeatureFlags.same_target_becomes_destructuring) { // Minify // // var a = obj.foo, b = obj.bar, c = obj.baz; // // to // // var {a, b, c} = obj; // // Caveats: // - Same consecutive target // - No optional chaining // - No computed property access // - Identifier bindings only if (decls.len > 1) brk: { const first_decl = &decls[0]; const second_decl = &decls[1]; if (first_decl.binding.data != .b_identifier) break :brk; if (second_decl.value == null or second_decl.value.?.data != .e_dot or second_decl.binding.data != .b_identifier) { break :brk; } const target_value = first_decl.value orelse break :brk; const target_e_dot: *E.Dot = if (target_value.data == .e_dot) target_value.data.e_dot else break :brk; const target_ref = if (target_e_dot.target.data == .e_identifier and target_e_dot.optional_chain == null) target_e_dot.target.data.e_identifier.ref else break :brk; const second_e_dot = second_decl.value.?.data.e_dot; if (second_e_dot.target.data != .e_identifier or second_e_dot.optional_chain != null) { break :brk; } const second_ref = second_e_dot.target.data.e_identifier.ref; if (!second_ref.eql(target_ref)) { break :brk; } { // Reset the temporary bindings array early on var temp_bindings = p.temporary_bindings; p.temporary_bindings = .{}; defer { if (p.temporary_bindings.capacity > 0) { temp_bindings.deinit(bun.default_allocator); } else { temp_bindings.clearRetainingCapacity(); p.temporary_bindings = temp_bindings; } } temp_bindings.ensureUnusedCapacity(bun.default_allocator, 2) catch unreachable; temp_bindings.appendAssumeCapacity(.{ .key = Expr.init(E.String, E.String.init(target_e_dot.name), target_e_dot.name_loc), .value = decls[0].binding, }); temp_bindings.appendAssumeCapacity(.{ .key = Expr.init(E.String, E.String.init(second_e_dot.name), second_e_dot.name_loc), .value = decls[1].binding, }); decls = decls[2..]; while (decls.len > 0) { const decl = &decls[0]; if (decl.value == null or decl.value.?.data != .e_dot or decl.binding.data != .b_identifier) { break; } const e_dot = decl.value.?.data.e_dot; if (e_dot.target.data != .e_identifier or e_dot.optional_chain != null) { break; } const ref = e_dot.target.data.e_identifier.ref; if (!ref.eql(target_ref)) { break; } temp_bindings.append(bun.default_allocator, .{ .key = Expr.init(E.String, E.String.init(e_dot.name), e_dot.name_loc), .value = decl.binding, }) catch unreachable; decls = decls[1..]; } var b_object = B.Object{ .properties = temp_bindings.items, .is_single_line = true, }; const binding = Binding.init(&b_object, target_e_dot.target.loc); p.printBinding(binding); } p.printWhitespacer(ws(" = ")); p.printExpr(second_e_dot.target, .comma, flags); if (decls.len == 0) { return; } p.print(","); p.printSpace(); } } { p.printBinding(decls[0].binding); if (decls[0].value) |value| { p.printWhitespacer(ws(" = ")); p.printExpr(value, .comma, flags); } } for (decls[1..]) |*decl| { p.print(","); p.printSpace(); p.printBinding(decl.binding); if (decl.value) |value| { p.printWhitespacer(ws(" = ")); p.printExpr(value, .comma, flags); } } } pub inline fn addSourceMapping(printer: *Printer, location: logger.Loc) void { if (comptime !generate_source_map) { return; } printer.source_map_builder.addSourceMapping(location, printer.writer.slice()); } // pub inline fn addSourceMappingForName(printer: *Printer, location: logger.Loc, name: string, ref: Ref) void { // _ = location; // if (comptime !generate_source_map) { // return; // } // if (printer.symbols().get(printer.symbols().follow(ref))) |symbol| { // if (!strings.eqlLong(symbol.original_name, name)) { // printer.source_map_builder.addSourceMapping() // } // } // } pub fn printSymbol(p: *Printer, ref: Ref) void { std.debug.assert(!ref.isNull()); const name = p.renamer.nameForSymbol(ref); p.printIdentifier(name); } pub fn printClauseAlias(p: *Printer, alias: string) void { std.debug.assert(alias.len > 0); if (!strings.containsNonBmpCodePoint(alias)) { p.printSpaceBeforeIdentifier(); p.printIdentifier(alias); } else { p.printQuotedUTF8(alias, false); } } pub fn printFnArgs( p: *Printer, open_paren_loc: ?logger.Loc, args: []G.Arg, has_rest_arg: bool, // is_arrow can be used for minifying later _: bool, ) void { const wrap = true; if (wrap) { if (open_paren_loc) |loc| { p.addSourceMapping(loc); } p.print("("); } for (args, 0..) |arg, i| { if (i != 0) { p.print(","); p.printSpace(); } if (has_rest_arg and i + 1 == args.len) { p.print("..."); } p.printBinding(arg.binding); if (arg.default) |default| { p.printWhitespacer(ws(" = ")); p.printExpr(default, .comma, ExprFlag.None()); } } if (wrap) { p.print(")"); } } pub fn printFunc(p: *Printer, func: G.Fn) void { p.printFnArgs(func.open_parens_loc, func.args, func.flags.contains(.has_rest_arg), false); p.printSpace(); p.printBlock(func.body.loc, func.body.stmts, null); } pub fn printClass(p: *Printer, class: G.Class) void { if (class.extends) |extends| { p.print(" extends"); p.printSpace(); p.printExpr(extends, Level.new.sub(1), ExprFlag.None()); } p.printSpace(); p.addSourceMapping(class.body_loc); p.print("{"); p.printNewline(); p.options.indent += 1; for (class.properties) |item| { p.printSemicolonIfNeeded(); p.printIndent(); if (item.kind == .class_static_block) { p.print("static"); p.printSpace(); p.printBlock(item.class_static_block.?.loc, item.class_static_block.?.stmts.slice(), null); p.printNewline(); continue; } p.printProperty(item); if (item.value == null) { p.printSemicolonAfterStatement(); } else { p.printNewline(); } } p.needs_semicolon = false; p.options.unindent(); p.printIndent(); if (class.close_brace_loc.start > class.body_loc.start) p.addSourceMapping(class.close_brace_loc); p.print("}"); } pub fn bestQuoteCharForEString(str: *const E.String, allow_backtick: bool) u8 { if (comptime is_json) return '"'; if (str.isUTF8()) { return bestQuoteCharForString(u8, str.data, allow_backtick); } else { return bestQuoteCharForString(u16, str.slice16(), allow_backtick); } } pub fn printWhitespacer(this: *Printer, spacer: Whitespacer) void { if (this.options.minify_whitespace) { this.print(spacer.minify); } else { this.print(spacer.normal); } } pub fn printNonNegativeFloat(p: *Printer, float: f64) void { // Is this actually an integer? @setRuntimeSafety(false); const floored: f64 = @floor(float); const remainder: f64 = (float - floored); const is_integer = remainder == 0; if (float < std.math.maxInt(u52) and is_integer) { @setFloatMode(.Optimized); // In JavaScript, numbers are represented as 64 bit floats // However, they could also be signed or unsigned int 32 (when doing bit shifts) // In this case, it's always going to unsigned since that conversion has already happened. const val = @as(u64, @intFromFloat(float)); switch (val) { 0 => { p.print("0"); }, 1...9 => { var bytes = [1]u8{'0' + @as(u8, @intCast(val))}; p.print(&bytes); }, 10 => { p.print("10"); }, 11...99 => { var buf: *[2]u8 = (p.writer.reserve(2) catch unreachable)[0..2]; formatUnsignedIntegerBetween(2, buf, val); p.writer.advance(2); }, 100 => { p.print("100"); }, 101...999 => { var buf: *[3]u8 = (p.writer.reserve(3) catch unreachable)[0..3]; formatUnsignedIntegerBetween(3, buf, val); p.writer.advance(3); }, 1000 => { p.print("1000"); }, 1001...9999 => { var buf: *[4]u8 = (p.writer.reserve(4) catch unreachable)[0..4]; formatUnsignedIntegerBetween(4, buf, val); p.writer.advance(4); }, 10000 => { p.print("1e4"); }, 100000 => { p.print("1e5"); }, 1000000 => { p.print("1e6"); }, 10000000 => { p.print("1e7"); }, 100000000 => { p.print("1e8"); }, 1000000000 => { p.print("1e9"); }, 10001...99999 => { var buf: *[5]u8 = (p.writer.reserve(5) catch unreachable)[0..5]; formatUnsignedIntegerBetween(5, buf, val); p.writer.advance(5); }, 100001...999999 => { var buf: *[6]u8 = (p.writer.reserve(6) catch unreachable)[0..6]; formatUnsignedIntegerBetween(6, buf, val); p.writer.advance(6); }, 1_000_001...9_999_999 => { var buf: *[7]u8 = (p.writer.reserve(7) catch unreachable)[0..7]; formatUnsignedIntegerBetween(7, buf, val); p.writer.advance(7); }, 10_000_001...99_999_999 => { var buf: *[8]u8 = (p.writer.reserve(8) catch unreachable)[0..8]; formatUnsignedIntegerBetween(8, buf, val); p.writer.advance(8); }, 100_000_001...999_999_999 => { var buf: *[9]u8 = (p.writer.reserve(9) catch unreachable)[0..9]; formatUnsignedIntegerBetween(9, buf, val); p.writer.advance(9); }, 1_000_000_001...9_999_999_999 => { var buf: *[10]u8 = (p.writer.reserve(10) catch unreachable)[0..10]; formatUnsignedIntegerBetween(10, buf, val); p.writer.advance(10); }, else => std.fmt.formatInt(val, 10, .lower, .{}, p) catch unreachable, } return; } std.fmt.formatFloatDecimal( float, .{}, p, ) catch unreachable; } pub fn printQuotedUTF16(e: *Printer, text: []const u16, quote: u8) void { var i: usize = 0; const n: usize = text.len; // e(text.len) catch unreachable; outer: while (i < n) { const CodeUnitType = u32; const c: CodeUnitType = text[i]; i += 1; // TODO: here switch (c) { // Special-case the null character since it may mess with code written in C // that treats null characters as the end of the string. 0x00 => { // We don't want "\x001" to be written as "\01" if (i < n and text[i] >= '0' and text[i] <= '9') { e.print("\\x00"); } else { e.print("\\0"); } }, // Special-case the bell character since it may cause dumping this file to // the terminal to make a sound, which is undesirable. Note that we can't // use an octal literal to print this shorter since octal literals are not // allowed in strict mode (or in template strings). 0x07 => { e.print("\\x07"); }, 0x08 => { if (quote == '`') e.print(0x08) else e.print("\\b"); }, 0x0C => { if (quote == '`') e.print(0x000C) else e.print("\\f"); }, '\t' => { if (quote == '`') e.print("\t") else e.print("\\t"); }, '\n' => { if (quote == '`') { e.print('\n'); } else { e.print("\\n"); } }, // we never print \r un-escaped std.ascii.control_code.cr => { e.print("\\r"); }, // \v std.ascii.control_code.vt => { if (quote == '`') { e.print(std.ascii.control_code.vt); } else { e.print("\\v"); } }, // "\\" '\\' => { e.print("\\\\"); }, '\'' => { if (quote == '\'') { e.print('\\'); } e.print("'"); }, '"' => { if (quote == '"') { e.print('\\'); } e.print("\""); }, '`' => { if (quote == '`') { e.print('\\'); } e.print("`"); }, '$' => { if (quote == '`' and i < n and text[i] == '{') { e.print('\\'); } e.print('$'); }, 0x2028 => { e.print("\\u2028"); }, 0x2029 => { e.print("\\u2029"); }, 0xFEFF => { e.print("\\uFEFF"); }, else => { switch (c) { first_ascii...last_ascii => { e.print(@as(u8, @intCast(c))); // Fast path for printing long UTF-16 template literals // this only applies to template literal strings // but we print a template literal if there is a \n or a \r // which is often if the string is long and UTF-16 if (quote == '`') { const remain = text[i..]; if (remain.len > 1 and remain[0] < last_ascii and remain[0] > first_ascii and remain[0] != '$' and remain[0] != '\\' and remain[0] != '`') { if (strings.@"nextUTF16NonASCIIOr$`\\"([]const u16, remain)) |count_| { if (count_ == 0) unreachable; // conditional above checks this const len = count_ - 1; i += len; var ptr = e.writer.reserve(len) catch unreachable; var to_copy = ptr[0..len]; strings.copyU16IntoU8(to_copy, []const u16, remain[0..len]); e.writer.advance(len); continue :outer; } else { const count = @as(u32, @truncate(remain.len)); var ptr = e.writer.reserve(count) catch unreachable; var to_copy = ptr[0..count]; strings.copyU16IntoU8(to_copy, []const u16, remain); e.writer.advance(count); i += count; } } } }, first_high_surrogate...last_high_surrogate => { // Is there a next character? if (i < n) { const c2: CodeUnitType = text[i]; if (c2 >= first_low_surrogate and c2 <= last_low_surrogate) { i += 1; // Escape this character if UTF-8 isn't allowed if (ascii_only_always_on_unless_minifying) { var ptr = e.writer.reserve(12) catch unreachable; ptr[0..12].* = [_]u8{ '\\', 'u', hex_chars[c >> 12], hex_chars[(c >> 8) & 15], hex_chars[(c >> 4) & 15], hex_chars[c & 15], '\\', 'u', hex_chars[c2 >> 12], hex_chars[(c2 >> 8) & 15], hex_chars[(c2 >> 4) & 15], hex_chars[c2 & 15], }; e.writer.advance(12); continue; // Otherwise, encode to UTF-8 } const r: CodeUnitType = 0x10000 + (((c & 0x03ff) << 10) | (c2 & 0x03ff)); var ptr = e.writer.reserve(4) catch unreachable; e.writer.advance(strings.encodeWTF8RuneT(ptr[0..4], CodeUnitType, r)); continue; } } // Write an unpaired high surrogate var ptr = e.writer.reserve(6) catch unreachable; ptr[0..6].* = [_]u8{ '\\', 'u', hex_chars[c >> 12], hex_chars[(c >> 8) & 15], hex_chars[(c >> 4) & 15], hex_chars[c & 15] }; e.writer.advance(6); }, // Is this an unpaired low surrogate or four-digit hex escape? first_low_surrogate...last_low_surrogate => { // Write an unpaired high surrogate var ptr = e.writer.reserve(6) catch unreachable; ptr[0..6].* = [_]u8{ '\\', 'u', hex_chars[c >> 12], hex_chars[(c >> 8) & 15], hex_chars[(c >> 4) & 15], hex_chars[c & 15] }; e.writer.advance(6); }, else => { if (ascii_only_always_on_unless_minifying) { if (c > 0xFF) { var ptr = e.writer.reserve(6) catch unreachable; // Write an unpaired high surrogate ptr[0..6].* = [_]u8{ '\\', 'u', hex_chars[c >> 12], hex_chars[(c >> 8) & 15], hex_chars[(c >> 4) & 15], hex_chars[c & 15] }; e.writer.advance(6); } else { // Can this be a two-digit hex escape? var ptr = e.writer.reserve(4) catch unreachable; ptr[0..4].* = [_]u8{ '\\', 'x', hex_chars[c >> 4], hex_chars[c & 15] }; e.writer.advance(4); } } else { // chars < 255 as two digit hex escape if (c <= 0xFF) { var ptr = e.writer.reserve(4) catch unreachable; ptr[0..4].* = [_]u8{ '\\', 'x', hex_chars[c >> 4], hex_chars[c & 15] }; e.writer.advance(4); continue; } var ptr = e.writer.reserve(4) catch return; e.writer.advance(strings.encodeWTF8RuneT(ptr[0..4], CodeUnitType, c)); } }, } }, } } } pub fn isUnboundEvalIdentifier(p: *Printer, value: Expr) bool { switch (value.data) { .e_identifier => |ident| { if (ident.ref.isSourceContentsSlice()) return false; const symbol = p.symbols().get(p.symbols().follow(ident.ref)) orelse return false; return symbol.kind == .unbound and strings.eqlComptime(symbol.original_name, "eval"); }, else => { return false; }, } } inline fn symbols(p: *Printer) js_ast.Symbol.Map { return p.renamer.symbols(); } pub fn printRequireError(p: *Printer, text: string) void { p.print("(()=>{ throw new Error(`Cannot require module "); p.printQuotedUTF8(text, false); p.print("`);})()"); } pub inline fn importRecord( p: *const Printer, import_record_index: usize, ) *const ImportRecord { return &p.import_records[import_record_index]; } pub fn printRequireOrImportExpr( p: *Printer, import_record_index: u32, was_unwrapped_require: bool, leading_interior_comments: []G.Comment, level_: Level, flags: ExprFlag.Set, ) void { var level = level_; const wrap = level.gte(.new) or flags.contains(.forbid_call); if (wrap) p.print("("); defer if (wrap) p.print(")"); assert(p.import_records.len > import_record_index); const record = p.importRecord(import_record_index); if (comptime is_bun_platform) { // "bun" is not a real module. It's just globalThis.Bun. // // transform from: // const foo = await import("bun") // const bar = require("bun") // // transform to: // const foo = await Promise.resolve(globalThis.Bun) // const bar = globalThis.Bun // if (record.tag == .bun) { if (record.kind == .dynamic) { p.print("Promise.resolve(globalThis.Bun)"); } else if (record.kind == .require) { p.print("globalThis.Bun"); } return; } } if (record.source_index.isValid()) { var meta = p.options.requireOrImportMetaForSource(record.source_index.get(), was_unwrapped_require); // Don't need the namespace object if the result is unused anyway if (flags.contains(.expr_result_is_unused)) { meta.exports_ref = Ref.None; } // Internal "import()" of async ESM if (record.kind == .dynamic and meta.is_wrapper_async) { p.printSpaceBeforeIdentifier(); p.printSymbol(meta.wrapper_ref); p.print("()"); if (meta.exports_ref.isValid()) { _ = p.printDotThenPrefix(); p.printSpaceBeforeIdentifier(); 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 if (record.kind == .dynamic) p.printDotThenSuffix(); // Make sure the comma operator is propertly wrapped if (meta.exports_ref.isValid() and level.gte(.comma)) { p.print("("); } defer if (meta.exports_ref.isValid() and level.gte(.comma)) p.print(")"); // Wrap this with a call to "__toESM()" if this is a CommonJS file const wrap_with_to_esm = record.wrap_with_to_esm; if (wrap_with_to_esm) { p.printSpaceBeforeIdentifier(); p.printSymbol(p.options.to_esm_ref); p.print("("); } if (!meta.was_unwrapped_require) { // Call the wrapper p.printSpaceBeforeIdentifier(); p.printSymbol(meta.wrapper_ref); p.print("()"); // Return the namespace object if this is an ESM file if (meta.exports_ref.isValid()) { p.print(","); p.printSpace(); // Wrap this with a call to "__toCommonJS()" if this is an ESM file const wrap_with_to_cjs = record.wrap_with_to_commonjs; if (wrap_with_to_cjs) { p.printSymbol(p.options.to_commonjs_ref); p.print("("); } p.printSymbol(meta.exports_ref); if (wrap_with_to_cjs) { p.print(")"); } } } else { if (!meta.exports_ref.isNull()) p.printSymbol(meta.exports_ref); } if (wrap_with_to_esm) { if (p.options.module_type.isESM()) { p.print(","); p.printSpace(); p.print("1"); } p.print(")"); } return; } const is_external = std.mem.indexOfScalar( u32, p.options.externals, import_record_index, ) != null; // External "require()" if (record.kind != .dynamic) { p.printSpaceBeforeIdentifier(); if (p.options.inline_require_and_import_errors) { if (record.path.is_disabled and record.handles_import_errors and !is_external) { p.printRequireError(record.path.text); return; } if (record.path.is_disabled) { p.printDisabledImport(); return; } } if (comptime is_bun_platform) { if (p.options.module_type == .esm) { p.print("import.meta.require"); } else { p.print("require"); } } else { p.printSymbol(p.options.require_ref.?); } p.print("("); p.printImportRecordPath(record); p.print(")"); 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.printSpaceBeforeIdentifier(); // Allow it to fail at runtime, if it should p.print("import("); p.printImportRecordPath(record); p.print(")"); if (leading_interior_comments.len > 0) { p.printNewline(); p.options.unindent(); p.printIndent(); } return; } // noop for now pub inline fn printPure(_: *Printer) void {} pub fn printQuotedUTF8(p: *Printer, str: string, allow_backtick: bool) void { const quote = if (comptime !is_json) bestQuoteCharForString(u8, str, allow_backtick) else '"'; p.print(quote); p.print(str); p.print(quote); } fn printClauseItem(p: *Printer, item: js_ast.ClauseItem) void { return printClauseItemAs(p, item, .import); } fn printExportClauseItem(p: *Printer, item: js_ast.ClauseItem) void { return printClauseItemAs(p, item, .@"export"); } fn printClauseItemAs(p: *Printer, item: js_ast.ClauseItem, comptime as: @Type(.EnumLiteral)) void { const name = p.renamer.nameForSymbol(item.name.ref.?); if (comptime as == .import) { p.printClauseAlias(item.alias); if (!strings.eql(name, item.alias)) { p.print(" as "); p.addSourceMapping(item.alias_loc); p.printIdentifier(name); } } else if (comptime as == .@"var") { p.printClauseAlias(item.alias); if (!strings.eql(name, item.alias)) { p.print(":"); p.printSpace(); p.printIdentifier(name); } } else if (comptime as == .@"export") { p.printIdentifier(name); if (!strings.eql(name, item.alias)) { p.print(" as "); p.addSourceMapping(item.alias_loc); p.printClauseAlias(item.alias); } } else { @compileError("Unknown as"); } } pub inline fn canPrintIdentifier(_: *Printer, name: string) bool { if (comptime is_json) return false; if (comptime ascii_only or ascii_only_always_on_unless_minifying) { return js_lexer.isLatin1Identifier(string, name); } else { return js_lexer.isIdentifier(name); } } pub inline fn canPrintIdentifierUTF16(_: *Printer, name: []const u16) bool { if (comptime ascii_only or ascii_only_always_on_unless_minifying) { return js_lexer.isLatin1Identifier([]const u16, name); } else { return js_lexer.isIdentifierUTF16(name); } } fn printRawTemplateLiteral(p: *Printer, bytes: []const u8) void { if (comptime is_json or !ascii_only) { p.print(bytes); return; } // Translate any non-ASCII to unicode escape sequences // Note that this does not correctly handle malformed template literal strings // template literal strings can contain invalid unicode code points // and pretty much anything else // // we use WTF-8 here, but that's still not good enough. // var ascii_start: usize = 0; var is_ascii = false; var iter = CodepointIterator.init(bytes); var cursor = CodepointIterator.Cursor{}; while (iter.next(&cursor)) { switch (cursor.c) { // unlike other versions, we only want to mutate > 0x7F 0...last_ascii => { if (!is_ascii) { ascii_start = cursor.i; is_ascii = true; } }, else => { if (is_ascii) { p.print(bytes[ascii_start..cursor.i]); is_ascii = false; } switch (cursor.c) { 0...0xFFFF => { p.print([_]u8{ '\\', 'u', hex_chars[cursor.c >> 12], hex_chars[(cursor.c >> 8) & 15], hex_chars[(cursor.c >> 4) & 15], hex_chars[cursor.c & 15], }); }, else => { p.print("\\u{"); std.fmt.formatInt(cursor.c, 16, .lower, .{}, p) catch unreachable; p.print("}"); }, } }, } } if (is_ascii) { p.print(bytes[ascii_start..]); } } pub fn printExpr(p: *Printer, expr: Expr, level: Level, _flags: ExprFlag.Set) void { var flags = _flags; switch (expr.data) { .e_missing => {}, .e_undefined => { p.printUndefined(expr.loc, level); }, .e_super => { p.printSpaceBeforeIdentifier(); p.addSourceMapping(expr.loc); p.print("super"); }, .e_null => { p.printSpaceBeforeIdentifier(); p.addSourceMapping(expr.loc); p.print("null"); }, .e_this => { p.printSpaceBeforeIdentifier(); p.addSourceMapping(expr.loc); p.print("this"); }, .e_spread => |e| { p.addSourceMapping(expr.loc); p.print("..."); p.printExpr(e.value, .comma, ExprFlag.None()); }, .e_new_target => { p.printSpaceBeforeIdentifier(); p.addSourceMapping(expr.loc); p.print("new.target"); }, .e_import_meta => { p.printSpaceBeforeIdentifier(); p.addSourceMapping(expr.loc); p.print("import.meta"); }, .e_commonjs_export_identifier => |id| { p.printSpaceBeforeIdentifier(); p.addSourceMapping(expr.loc); for (p.options.commonjs_named_exports.keys(), p.options.commonjs_named_exports.values()) |key, value| { if (value.loc_ref.ref.?.eql(id.ref)) { if (p.options.commonjs_named_exports_deoptimized or value.needs_decl) { p.printSymbol(p.options.commonjs_named_exports_ref); if (p.canPrintIdentifier(key)) { p.print("."); p.print(key); } else { p.print("["); p.printPossiblyEscapedIdentifierString(key, true); p.print("]"); } } else { p.printSymbol(value.loc_ref.ref.?); } break; } } }, .e_new => |e| { 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.printPure(); } p.printSpaceBeforeIdentifier(); p.addSourceMapping(expr.loc); p.print("new"); p.printSpace(); p.printExpr(e.target, .new, ExprFlag.ForbidCall()); const args = e.args.slice(); if (args.len > 0 or level.gte(.postfix)) { p.print("("); if (args.len > 0) { p.printExpr(args[0], .comma, ExprFlag.None()); for (args[1..]) |arg| { p.print(","); p.printSpace(); p.printExpr(arg, .comma, ExprFlag.None()); } } if (e.close_parens_loc.start > expr.loc.start) { p.addSourceMapping(e.close_parens_loc); } p.print(")"); } if (wrap) { p.print(")"); } }, .e_call => |e| { var wrap = level.gte(.new) or flags.contains(.forbid_call); var target_flags = ExprFlag.None(); if (e.optional_chain == null) { target_flags = ExprFlag.HasNonOptionalChainParent(); } else if (flags.contains(.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; } const is_unbound_eval = !e.is_direct_eval and p.isUnboundEvalIdentifier(e.target); if (wrap) { p.print("("); } if (has_pure_comment) { const was_stmt_start = p.stmt_start == p.writer.written; p.printPure(); if (was_stmt_start) { p.stmt_start = p.writer.written; } } // We only want to generate an unbound eval() in CommonJS p.call_target = e.target.data; if (is_unbound_eval and p.options.module_type != .cjs) { 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("("); const args = e.args.slice(); if (args.len > 0) { p.printExpr(args[0], .comma, ExprFlag.None()); for (args[1..]) |arg| { p.print(","); p.printSpace(); p.printExpr(arg, .comma, ExprFlag.None()); } } if (e.close_paren_loc.start > expr.loc.start) { p.addSourceMapping(e.close_paren_loc); } p.print(")"); if (wrap) { p.print(")"); } }, .e_require_call_target => { p.addSourceMapping(expr.loc); if (p.options.module_type == .cjs or !is_bun_platform) { p.print("require"); } else { p.print("import.meta.require"); } }, .e_require_resolve_call_target => { p.addSourceMapping(expr.loc); if (p.options.module_type == .cjs or !is_bun_platform) { p.print("require.resolve"); } else { p.print("import.meta.resolveSync"); } }, .e_require_string => |e| { if (!rewrite_esm_to_cjs) { p.printRequireOrImportExpr(e.import_record_index, e.unwrapped_id != std.math.maxInt(u32), &([_]G.Comment{}), level, flags); } }, .e_require_resolve_string => |e| { if (p.options.rewrite_require_resolve) { // require.resolve("../src.js") => "../src.js" p.printSpaceBeforeIdentifier(); p.printQuotedUTF8(p.importRecord(e.import_record_index).path.text, true); } else { const wrap = level.gte(.new) or flags.contains(.forbid_call); if (wrap) { p.print("("); } p.printSpaceBeforeIdentifier(); p.print("require.resolve("); p.printQuotedUTF8(p.importRecord(e.import_record_index).path.text, true); p.print(")"); if (wrap) { p.print(")"); } } }, .e_import => |e| { // Handle non-string expressions if (e.isImportRecordNull()) { const wrap = level.gte(.new) or flags.contains(.forbid_call); if (wrap) { p.print("("); } p.printSpaceBeforeIdentifier(); p.addSourceMapping(expr.loc); 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.unindent(); p.printIndent(); } p.print(")"); if (wrap) { p.print(")"); } } else { p.printRequireOrImportExpr(e.import_record_index, false, e.leading_interior_comments, level, flags); } }, .e_dot => |e| { // Ironic 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; var wrap = false; if (e.optional_chain == null) { flags.insert(.has_non_optional_chain_parent); } else { if (flags.contains(.has_non_optional_chain_parent) and e.optional_chain.? == .ccontinue) { wrap = true; p.print("("); } flags.remove(.has_non_optional_chain_parent); } flags.setIntersection(ExprFlag.Set.init(.{ .has_non_optional_chain_parent = true, .forbid_call = true })); p.printExpr( e.target, .postfix, flags, ); if (p.canPrintIdentifier(e.name)) { if (isOptionalChain) { p.print("?."); } else { if (p.prev_num_end == p.writer.written) { // "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 { if (isOptionalChain) { p.print("?.["); } else { p.print("["); } p.printPossiblyEscapedIdentifierString( e.name, true, ); p.print("]"); } if (wrap) { p.print(")"); } }, .e_index => |e| { var wrap = false; if (e.optional_chain == null) { flags.insert(.has_non_optional_chain_parent); } else { if (flags.contains(.has_non_optional_chain_parent)) { wrap = true; p.print("("); } flags.remove(.has_non_optional_chain_parent); } 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 => { const priv = e.index.data.e_private_identifier; if (!is_optional_chain_start) { p.print("."); } p.addSourceMapping(e.index.loc); p.printSymbol(priv.ref); }, else => { p.print("["); p.addSourceMapping(e.index.loc); p.printExpr(e.index, .lowest, ExprFlag.None()); p.print("]"); }, } if (wrap) { p.print(")"); } }, .e_if => |e| { const wrap = level.gte(.conditional); if (wrap) { p.print("("); flags.remove(.forbid_in); } p.printExpr(e.test_, .conditional, flags); p.printSpace(); p.print("?"); p.printSpace(); p.printExpr(e.yes, .yield, ExprFlag.None()); p.printSpace(); p.print(":"); p.printSpace(); flags.insert(.forbid_in); p.printExpr(e.no, .yield, flags); if (wrap) { p.print(")"); } }, .e_arrow => |e| { const wrap = level.gte(.assign); if (wrap) { p.print("("); } if (e.is_async) { p.addSourceMapping(expr.loc); p.printSpaceBeforeIdentifier(); p.print("async"); p.printSpace(); } p.printFnArgs(if (e.is_async) null else expr.loc, e.args, e.has_rest_arg, true); p.printWhitespacer(ws(" => ")); var wasPrinted = false; // This is more efficient than creating a new Part just for the JSX auto imports when bundling if (comptime rewrite_esm_to_cjs) { if (@intFromPtr(p.options.prepend_part_key) > 0 and @intFromPtr(e.body.stmts.ptr) == @intFromPtr(p.options.prepend_part_key)) { p.printTwoBlocksInOne(e.body.loc, e.body.stmts, p.options.prepend_part_value.?.stmts); wasPrinted = true; } } if (e.body.stmts.len == 1 and e.prefer_expr) { switch (e.body.stmts[0].data) { .s_return => { if (e.body.stmts[0].data.s_return.value) |val| { p.arrow_expr_start = p.writer.written; p.printExpr(val, .comma, ExprFlag.Set.init(.{ .forbid_in = true })); wasPrinted = true; } }, else => {}, } } if (!wasPrinted) { p.printBlock(e.body.loc, e.body.stmts, null); } if (wrap) { p.print(")"); } }, .e_function => |e| { const n = p.writer.written; var wrap = p.stmt_start == n or p.export_default_start == n; if (wrap) { p.print("("); } p.printSpaceBeforeIdentifier(); if (e.func.flags.contains(.is_async)) { p.addSourceMapping(expr.loc); p.print("async "); } p.print("function"); if (e.func.flags.contains(.is_generator)) { p.print("*"); p.printSpace(); } if (e.func.name) |sym| { p.printSpaceBeforeIdentifier(); p.addSourceMapping(sym.loc); p.printSymbol(sym.ref orelse Global.panic("internal error: expected E.Function's name symbol to have a ref\n{any}", .{e.func})); } p.printFunc(e.func); if (wrap) { p.print(")"); } }, .e_class => |e| { const n = p.writer.written; var wrap = p.stmt_start == n or p.export_default_start == n; if (wrap) { p.print("("); } p.printSpaceBeforeIdentifier(); p.addSourceMapping(expr.loc); p.print("class"); if (e.class_name) |name| { p.print(" "); p.addSourceMapping(name.loc); p.printSymbol(name.ref orelse Global.panic("internal error: expected E.Class's name symbol to have a ref\n{any}", .{e})); } p.printClass(e.*); if (wrap) { p.print(")"); } }, .e_array => |e| { p.addSourceMapping(expr.loc); p.print("["); const items = e.items.slice(); if (items.len > 0) { if (!e.is_single_line) { p.options.indent += 1; } for (items, 0..) |item, i| { if (i != 0) { p.print(","); if (e.is_single_line) { p.printSpace(); } } if (!e.is_single_line) { p.printNewline(); p.printIndent(); } p.printExpr(item, .comma, ExprFlag.None()); if (i == items.len - 1 and item.data == .e_missing) { // Make sure there's a comma after trailing missing items p.print(","); } } if (!e.is_single_line) { p.options.unindent(); p.printNewline(); p.printIndent(); } } if (e.close_bracket_loc.start > expr.loc.start) { p.addSourceMapping(e.close_bracket_loc); } p.print("]"); }, .e_object => |e| { const n = p.writer.written; const wrap = if (comptime is_json) false else p.stmt_start == n or p.arrow_expr_start == n; if (wrap) { p.print("("); } p.addSourceMapping(expr.loc); p.print("{"); const props = expr.data.e_object.properties.slice(); if (props.len > 0) { p.options.indent += @as(usize, @intFromBool(!e.is_single_line)); if (e.is_single_line) { p.printSpace(); } else { p.printNewline(); p.printIndent(); } p.printProperty(props[0]); if (props.len > 1) { for (props[1..]) |property| { p.print(","); if (e.is_single_line) { p.printSpace(); } else { p.printNewline(); p.printIndent(); } p.printProperty(property); } } if (!e.is_single_line) { p.options.unindent(); p.printNewline(); p.printIndent(); } else { p.printSpace(); } } if (e.close_brace_loc.start > expr.loc.start) { p.addSourceMapping(e.close_brace_loc); } p.print("}"); if (wrap) { p.print(")"); } }, .e_boolean => |e| { p.addSourceMapping(expr.loc); if (p.options.minify_syntax) { if (level.gte(Level.prefix)) { p.print(if (e.value) "(!0)" else "(!1)"); } else { p.print(if (e.value) "!0" else "!1"); } } else { p.printSpaceBeforeIdentifier(); p.print(if (e.value) "true" else "false"); } }, .e_string => |e| { e.resolveRopeIfNeeded(p.options.allocator); p.addSourceMapping(expr.loc); // If this was originally a template literal, print it as one as long as we're not minifying if (e.prefer_template and !p.options.minify_syntax) { p.print("`"); p.printStringContent(e, '`'); p.print("`"); return; } const c = bestQuoteCharForEString(e, true); p.print(c); p.printStringContent(e, c); p.print(c); }, .e_template => |e| { if (e.tag) |tag| { p.addSourceMapping(expr.loc); // 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()); } } else { p.addSourceMapping(expr.loc); } p.print("`"); switch (e.head) { .raw => |raw| p.printRawTemplateLiteral(raw), .cooked => |*cooked| { if (cooked.isPresent()) { cooked.resolveRopeIfNeeded(p.options.allocator); p.printStringContent(cooked, '`'); } }, } for (e.parts) |*part| { p.print("${"); p.printExpr(part.value, .lowest, ExprFlag.None()); p.print("}"); switch (part.tail) { .raw => |raw| p.printRawTemplateLiteral(raw), .cooked => |*cooked| { if (cooked.isPresent()) { cooked.resolveRopeIfNeeded(p.options.allocator); p.printStringContent(cooked, '`'); } }, } } p.print("`"); }, .e_reg_exp => |e| { p.addSourceMapping(expr.loc); p.printRegExpLiteral(e); }, .e_big_int => |e| { p.printSpaceBeforeIdentifier(); p.addSourceMapping(expr.loc); p.print(e.value); p.print('n'); }, .e_number => |e| { const value = e.value; const absValue = @fabs(value); if (std.math.isNan(value)) { p.printSpaceBeforeIdentifier(); p.addSourceMapping(expr.loc); p.print("NaN"); } else if (std.math.isPositiveInf(value)) { p.printSpaceBeforeIdentifier(); p.addSourceMapping(expr.loc); p.print("Infinity"); } else if (std.math.isNegativeInf(value)) { if (level.gte(.prefix)) { p.addSourceMapping(expr.loc); p.print("(-Infinity)"); } else { p.printSpaceBeforeIdentifier(); p.addSourceMapping(expr.loc); p.print("(-Infinity)"); } } else if (!std.math.signbit(value)) { p.printSpaceBeforeIdentifier(); p.addSourceMapping(expr.loc); p.printNonNegativeFloat(absValue); // Remember the end of the latest number p.prev_num_end = p.writer.written; } else if (level.gte(.prefix)) { // Expressions such as "(-1).toString" need to wrap negative numbers. // Instead of testing for "value < 0" we test for "signbit(value)" and // "!isNaN(value)" because we need this to be true for "-0" and "-0 < 0" // is false. p.print("(-"); p.addSourceMapping(expr.loc); p.printNonNegativeFloat(absValue); p.print(")"); } else { p.printSpaceBeforeOperator(Op.Code.un_neg); p.print("-"); p.addSourceMapping(expr.loc); p.printNonNegativeFloat(absValue); // Remember the end of the latest number p.prev_num_end = p.writer.written; } }, .e_identifier => |e| { const name = p.renamer.nameForSymbol(e.ref); const wrap = p.writer.written == p.for_of_init_start and strings.eqlComptime(name, "let"); if (wrap) { p.print("("); } p.printSpaceBeforeIdentifier(); p.addSourceMapping(expr.loc); p.printIdentifier(name); if (wrap) { p.print(")"); } }, .e_import_identifier => |e| { // Potentially use a property access instead of an identifier var didPrint = false; const ref = p.symbols().follow(e.ref); const symbol = p.symbols().get(ref).?; if (symbol.import_item_status == .missing) { p.printUndefined(expr.loc, level); didPrint = true; } else if (symbol.namespace_alias) |namespace| { if (namespace.import_record_index < p.import_records.len) { const import_record = p.importRecord(namespace.import_record_index); if (namespace.was_originally_property_access) { var wrap = false; didPrint = true; if (p.call_target) |target| { wrap = e.was_originally_identifier and (target == .e_identifier and target.e_identifier.ref.eql(expr.data.e_import_identifier.ref)); } if (wrap) { p.printWhitespacer(ws("(0, ")); } p.printSpaceBeforeIdentifier(); p.addSourceMapping(expr.loc); p.printNamespaceAlias(import_record.*, namespace); if (wrap) { p.print(")"); } } else if (import_record.was_originally_require and import_record.path.is_disabled) { p.addSourceMapping(expr.loc); if (import_record.handles_import_errors) { p.printRequireError(import_record.path.text); } else { p.printDisabledImport(); } didPrint = true; } } if (!didPrint) { didPrint = true; const wrap = if (p.call_target) |target| e.was_originally_identifier and (target == .e_identifier and target.e_identifier.ref.eql(expr.data.e_import_identifier.ref)) else false; if (wrap) { p.printWhitespacer(ws("(0, ")); } p.printSpaceBeforeIdentifier(); p.addSourceMapping(expr.loc); p.printSymbol(namespace.namespace_ref); const alias = namespace.alias; if (p.canPrintIdentifier(alias)) { p.print("."); // TODO: addSourceMappingForName p.printIdentifier(alias); } else { p.print("["); // TODO: addSourceMappingForName // p.addSourceMappingForName(alias); p.printPossiblyEscapedIdentifierString(alias, true); p.print("]"); } if (wrap) { p.print(")"); } } } else if (p.options.const_values.count() > 0) { if (p.options.const_values.get(ref)) |const_value| { p.printSpaceBeforeIdentifier(); // TODO: addSourceMappingForName // p.addSourceMappingForName(renamer.nameForSymbol(e.ref)); p.addSourceMapping(expr.loc); p.printExpr(const_value, level, flags); didPrint = true; } } if (!didPrint) { p.printSpaceBeforeIdentifier(); p.addSourceMapping(expr.loc); p.printSymbol(e.ref); } }, .e_await => |e| { const wrap = level.gte(.prefix); if (wrap) { p.print("("); } p.printSpaceBeforeIdentifier(); p.addSourceMapping(expr.loc); p.print("await"); p.printSpace(); p.printExpr(e.value, Level.sub(.prefix, 1), ExprFlag.None()); if (wrap) { p.print(")"); } }, .e_yield => |e| { const wrap = level.gte(.assign); if (wrap) { p.print("("); } p.printSpaceBeforeIdentifier(); p.addSourceMapping(expr.loc); 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| { // 4.00 ms eums.EnumIndexer(src.js_ast.Op.Code).indexOf const entry: *const Op = Op.Table.getPtrConst(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.writer.written; } if (e.op.isPrefix()) { p.printExpr(e.value, Op.Level.sub(.prefix, 1), ExprFlag.None()); } if (wrap) { p.print(")"); } }, .e_binary => |e| { // 4.00 ms enums.EnumIndexer(src.js_ast.Op.Code).indexOf const entry: *const Op = Op.Table.getPtrConst(e.op); const e_level = entry.level; var wrap = level.gte(e_level) or (e.op == Op.Code.bin_in and flags.contains(.forbid_in)); // Destructuring assignments must be parenthesized const n = p.writer.written; if (n == p.stmt_start or n == p.arrow_expr_start) { switch (e.left.data) { .e_object => { wrap = true; }, else => {}, } } if (wrap) { p.print("("); flags.insert(.forbid_in); } var left_level = e_level.sub(1); var right_level = e_level.sub(1); if (e.op.isRightAssociative()) { left_level = e_level; } if (e.op.isLeftAssociative()) { right_level = e_level; } switch (e.op) { // "??" can't directly contain "||" or "&&" without being wrapped in parentheses .bin_nullish_coalescing => { switch (e.left.data) { .e_binary => { const left = e.left.data.e_binary; switch (left.op) { .bin_logical_and, .bin_logical_or => { left_level = .prefix; }, else => {}, } }, else => {}, } switch (e.right.data) { .e_binary => { const right = e.right.data.e_binary; 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 => { const left = e.left.data.e_unary; 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.insert(.forbid_in); 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.printSpaceBeforeOperator(e.op); p.print(entry.text); p.prev_op = e.op; p.prev_op_end = p.writer.written; } p.printSpace(); flags.insert(.forbid_in); // this feels like a hack? I think something is wrong here. if (e.op == .bin_assign) { flags.remove(.expr_result_is_unused); } p.printExpr(e.right, right_level, flags); if (wrap) { p.print(")"); } }, else => { // Global.panic("Unexpected expression of type {any}", .{std.meta.activeTag(expr.data}); }, } } pub fn printSpaceBeforeOperator(p: *Printer, next: Op.Code) void { if (p.prev_op_end == p.writer.written) { const prev = p.prev_op; // "+ + y" => "+ +y" // "+ ++ y" => "+ ++y" // "x + + y" => "x+ +y" // "x ++ + y" => "x+++y" // "x + ++ y" => "x+ ++y" // "-- >" => "-- >" // "< ! --" => " 1 and p.writer.prevPrevChar() == '<')) { p.print(" "); } } } pub inline fn printDotThenSuffix( p: *Printer, ) void { p.print(")"); } // This assumes the string has already been quoted. pub fn printStringContent(p: *Printer, str: *const E.String, c: u8) void { if (!str.isUTF8()) { // its already quoted for us! p.printQuotedUTF16(str.slice16(), c); } else { p.printUTF8StringEscapedQuotes(str.data, c); } } // Add one outer branch so the inner loop does fewer branches pub fn printUTF8StringEscapedQuotes(p: *Printer, str: string, c: u8) void { switch (c) { '`' => _printUTF8StringEscapedQuotes(p, str, '`'), '"' => _printUTF8StringEscapedQuotes(p, str, '"'), '\'' => _printUTF8StringEscapedQuotes(p, str, '\''), else => unreachable, } } pub fn _printUTF8StringEscapedQuotes(p: *Printer, str: string, comptime c: u8) void { var utf8 = str; var i: usize = 0; // Walk the string searching for quote characters // Escape any we find // Skip over already-escaped strings var len = utf8.len; while (i < len) { switch (utf8[i]) { '\\' => i += 2, '$' => { if (comptime c == '`') { p.print(utf8[0..i]); p.print("\\$"); utf8 = utf8[i + 1 ..]; len = utf8.len; i = 0; } else { i += 1; } }, c => { p.print(utf8[0..i]); p.print("\\" ++ &[_]u8{c}); utf8 = utf8[i + 1 ..]; len = utf8.len; i = 0; }, else => i += 1, } } if (utf8.len > 0) { p.print(utf8); } } fn printBindingIdentifierName(p: *Printer, name: string, name_loc: logger.Loc) void { p.addSourceMapping(name_loc); if (comptime !is_json and ascii_only) { const quote = bestQuoteCharForString(u8, name, false); p.print(quote); p.printQuotedIdentifier(name); p.print(quote); } else { p.printQuotedUTF8(name, false); } } fn printPossiblyEscapedIdentifierString(p: *Printer, name: string, allow_backtick: bool) void { if (comptime !ascii_only or is_json) { p.printQuotedUTF8(name, allow_backtick); } else { const quote = if (comptime !is_json) bestQuoteCharForString(u8, name, allow_backtick) else '"'; p.print(quote); p.printQuotedIdentifier(name); p.print(quote); } } pub fn printNamespaceAlias(p: *Printer, import_record: ImportRecord, namespace: G.NamespaceAlias) void { if (import_record.module_id > 0 and !import_record.contains_import_star) { p.print("$"); p.printModuleId(import_record.module_id); } else { p.printSymbol(namespace.namespace_ref); } // In the case of code like this: // module.exports = require("foo") // if "foo" is bundled // then we access it as the namespace symbol itself // that means the namespace alias is empty if (namespace.alias.len == 0) return; if (p.canPrintIdentifier(namespace.alias)) { p.print("."); p.printIdentifier(namespace.alias); } else { p.print("["); p.printPossiblyEscapedIdentifierString(namespace.alias, true); p.print("]"); } } pub fn printRegExpLiteral(p: *Printer, e: *const E.RegExp) void { const n = p.writer.written; // Avoid forming a single-line comment if (n > 0 and p.writer.prevChar() == '/') { p.print(" "); } if (comptime is_bun_platform) { // Translate any non-ASCII to unicode escape sequences var ascii_start: usize = 0; var is_ascii = false; var iter = CodepointIterator.init(e.value); var cursor = CodepointIterator.Cursor{}; while (iter.next(&cursor)) { switch (cursor.c) { first_ascii...last_ascii => { if (!is_ascii) { ascii_start = cursor.i; is_ascii = true; } }, else => { if (is_ascii) { p.print(e.value[ascii_start..cursor.i]); is_ascii = false; } switch (cursor.c) { 0...0xFFFF => { p.print([_]u8{ '\\', 'u', hex_chars[cursor.c >> 12], hex_chars[(cursor.c >> 8) & 15], hex_chars[(cursor.c >> 4) & 15], hex_chars[cursor.c & 15], }); }, else => { p.print("\\u{"); std.fmt.formatInt(cursor.c, 16, .lower, .{}, p) catch unreachable; p.print("}"); }, } }, } } if (is_ascii) { p.print(e.value[ascii_start..]); } } else { // UTF8 sequence is fine p.print(e.value); } // Need a space before the next identifier to avoid it turning into flags p.prev_reg_exp_end = p.writer.written; } pub fn printProperty(p: *Printer, item: G.Property) void { if (comptime !is_json) { if (item.kind == .spread) { if (comptime is_json and Environment.allow_assert) unreachable; p.print("..."); p.printExpr(item.value.?, .comma, ExprFlag.None()); return; } if (item.flags.contains(.is_static)) { if (comptime is_json and Environment.allow_assert) unreachable; p.print("static"); p.printSpace(); } switch (item.kind) { .get => { if (comptime is_json and Environment.allow_assert) unreachable; p.printSpaceBeforeIdentifier(); p.print("get"); p.printSpace(); }, .set => { if (comptime is_json and Environment.allow_assert) unreachable; p.printSpaceBeforeIdentifier(); p.print("set"); p.printSpace(); }, else => {}, } if (item.value) |val| { switch (val.data) { .e_function => |func| { if (item.flags.contains(.is_method)) { if (func.func.flags.contains(.is_async)) { p.printSpaceBeforeIdentifier(); p.print("async"); } if (func.func.flags.contains(.is_generator)) { p.print("*"); } if (func.func.flags.contains(.is_generator) and func.func.flags.contains(.is_async)) { p.printSpace(); } } }, else => {}, } // If var is declared in a parent scope and var is then written via destructuring pattern, key is null // example: // var foo = 1; // if (true) { // var { foo } = { foo: 2 }; // } if (item.key == null) { p.printExpr(val, .comma, ExprFlag.None()); return; } } } const _key = item.key.?; if (item.flags.contains(.is_computed)) { if (comptime is_json) { unreachable; } p.print("["); p.printExpr(_key, .comma, ExprFlag.None()); p.print("]"); if (item.value) |val| { switch (val.data) { .e_function => |func| { if (item.flags.contains(.is_method)) { p.printFunc(func.func); return; } }, else => {}, } p.print(":"); p.printSpace(); p.printExpr(val, .comma, ExprFlag.None()); } if (item.initializer) |initial| { p.printInitializer(initial); } return; } switch (_key.data) { .e_private_identifier => |priv| { if (comptime is_json) { unreachable; } p.addSourceMapping(_key.loc); p.printSymbol(priv.ref); }, .e_string => |key| { p.addSourceMapping(_key.loc); if (key.isUTF8()) { key.resolveRopeIfNeeded(p.options.allocator); p.printSpaceBeforeIdentifier(); var allow_shorthand: bool = true; // In react/cjs/react.development.js, there's part of a function like this: // var escaperLookup = { // "=": "=0", // ":": "=2" // }; // While each of those property keys are ASCII, a subset of ASCII is valid as the start of an identifier // "=" and ":" are not valid // So we need to check if (p.canPrintIdentifier(key.data)) { p.print(key.data); } else { allow_shorthand = false; p.printBindingIdentifierName(key.data, logger.Loc.Empty); } // Use a shorthand property if the names are the same if (item.value) |val| { switch (val.data) { .e_identifier => |e| { if (key.eql(string, p.renamer.nameForSymbol(e.ref))) { if (item.initializer) |initial| { p.printInitializer(initial); } if (allow_shorthand) { return; } } }, .e_import_identifier => |e| inner: { const ref = p.symbols().follow(e.ref); if (p.options.const_values.count() > 0 and p.options.const_values.contains(ref)) break :inner; if (p.symbols().get(ref)) |symbol| { if (symbol.namespace_alias == null and strings.eql(key.data, p.renamer.nameForSymbol(e.ref))) { if (item.initializer) |initial| { p.printInitializer(initial); } if (allow_shorthand) { return; } } } }, else => {}, } } } else if (p.canPrintIdentifierUTF16(key.slice16())) { p.printSpaceBeforeIdentifier(); p.printIdentifierUTF16(key.slice16()) catch unreachable; // Use a shorthand property if the names are the same if (item.value) |val| { switch (val.data) { .e_identifier => |e| { // TODO: is needing to check item.flags.contains(.was_shorthand) a bug? // esbuild doesn't have to do that... // maybe it's a symptom of some other underlying issue // or maybe, it's because i'm not lowering the same way that esbuild does. if (item.flags.contains(.was_shorthand) or strings.utf16EqlString(key.slice16(), p.renamer.nameForSymbol(e.ref))) { if (item.initializer) |initial| { p.printInitializer(initial); } return; } // if (strings) {} }, .e_import_identifier => |e| inner: { const ref = p.symbols().follow(e.ref); if (p.options.const_values.count() > 0 and p.options.const_values.contains(ref)) break :inner; if (p.symbols().get(ref)) |symbol| { if (symbol.namespace_alias == null and strings.utf16EqlString(key.slice16(), p.renamer.nameForSymbol(e.ref))) { if (item.initializer) |initial| { p.printInitializer(initial); } return; } } }, else => {}, } } } else { const c = bestQuoteCharForString(u16, key.slice16(), false); p.print(c); p.printQuotedUTF16(key.slice16(), c); p.print(c); } }, else => { if (comptime is_json) { unreachable; } p.printExpr(_key, .lowest, ExprFlag.Set{}); }, } if (item.kind != .normal) { if (comptime is_json) { bun.unreachablePanic("item.kind must be normal in json, received: {any}", .{item.kind}); } switch (item.value.?.data) { .e_function => |func| { p.printFunc(func.func); return; }, else => {}, } } if (item.value) |val| { switch (val.data) { .e_function => |f| { if (item.flags.contains(.is_method)) { p.printFunc(f.func); return; } }, else => {}, } p.print(":"); p.printSpace(); p.printExpr(val, .comma, ExprFlag.Set{}); } if (comptime is_json) { std.debug.assert(item.initializer == null); } if (item.initializer) |initial| { p.printInitializer(initial); } } pub fn printInitializer(p: *Printer, initial: Expr) void { p.printSpace(); p.print("="); p.printSpace(); p.printExpr(initial, .comma, ExprFlag.None()); } pub fn printBinding(p: *Printer, binding: Binding) void { switch (binding.data) { .b_missing => {}, .b_identifier => |b| { p.printSpaceBeforeIdentifier(); p.addSourceMapping(binding.loc); p.printSymbol(b.ref); }, .b_array => |b| { p.print("["); if (b.items.len > 0) { p.options.indent += @as(usize, @intFromBool(!b.is_single_line)); for (b.items, 0..) |*item, i| { if (i != 0) { p.print(","); if (b.is_single_line) { p.printSpace(); } } if (!b.is_single_line) { p.printNewline(); p.printIndent(); } const is_last = i + 1 == b.items.len; if (b.has_spread and is_last) { p.print("..."); } p.printBinding(item.binding); p.maybePrintDefaultBindingValue(item); // Make sure there's a comma after trailing missing items if (is_last and item.binding.data == .b_missing) { p.print(","); } } if (!b.is_single_line) { p.options.unindent(); p.printNewline(); p.printIndent(); } } p.print("]"); }, .b_object => |b| { p.print("{"); if (b.properties.len > 0) { p.options.indent += @as(usize, @intFromBool(!b.is_single_line)); for (b.properties, 0..) |*property, i| { if (i != 0) { p.print(","); } if (b.is_single_line) { p.printSpace(); } else { p.printNewline(); p.printIndent(); } if (property.flags.contains(.is_spread)) { p.print("..."); } else { if (property.flags.contains(.is_computed)) { p.print("["); p.printExpr(property.key, .comma, ExprFlag.None()); p.print("]:"); p.printSpace(); p.printBinding(property.value); p.maybePrintDefaultBindingValue(property); continue; } switch (property.key.data) { .e_string => |str| { str.resolveRopeIfNeeded(p.options.allocator); p.addSourceMapping(property.key.loc); if (str.isUTF8()) { p.printSpaceBeforeIdentifier(); // Example case: // const Menu = React.memo(function Menu({ // aria-label: ariaLabel, // ^ // That needs to be: // "aria-label": ariaLabel, if (p.canPrintIdentifier(str.data)) { p.printIdentifier(str.data); // Use a shorthand property if the names are the same switch (property.value.data) { .b_identifier => |id| { if (str.eql(string, p.renamer.nameForSymbol(id.ref))) { p.maybePrintDefaultBindingValue(property); continue; } }, else => {}, } } else { p.printPossiblyEscapedIdentifierString(str.data, false); } } else if (p.canPrintIdentifierUTF16(str.slice16())) { p.printSpaceBeforeIdentifier(); p.printIdentifierUTF16(str.slice16()) catch unreachable; // Use a shorthand property if the names are the same switch (property.value.data) { .b_identifier => |id| { if (strings.utf16EqlString(str.slice16(), p.renamer.nameForSymbol(id.ref))) { p.maybePrintDefaultBindingValue(property); continue; } }, else => {}, } } else { p.printExpr(property.key, .lowest, ExprFlag.None()); } }, else => { p.printExpr(property.key, .lowest, ExprFlag.None()); }, } p.print(":"); p.printSpace(); } p.printBinding(property.value); p.maybePrintDefaultBindingValue(property); } if (!b.is_single_line) { p.options.unindent(); p.printNewline(); p.printIndent(); } else { p.printSpace(); } } p.print("}"); }, else => { Global.panic("Unexpected binding of type {any}", .{binding}); }, } } pub fn maybePrintDefaultBindingValue(p: *Printer, property: anytype) void { if (property.default_value) |default| { p.printSpace(); p.print("="); p.printSpace(); p.printExpr(default, .comma, ExprFlag.None()); } } pub fn printStmt(p: *Printer, stmt: Stmt) !void { const prev_stmt_tag = p.prev_stmt_tag; defer { p.prev_stmt_tag = std.meta.activeTag(stmt.data); } p.addSourceMapping(stmt.loc); switch (stmt.data) { .s_comment => |s| { p.printIndentedComment(s.text); }, .s_function => |s| { p.printIndent(); p.printSpaceBeforeIdentifier(); const name = s.func.name orelse Global.panic("Internal error: expected func to have a name ref\n{any}", .{s}); const nameRef = name.ref orelse Global.panic("Internal error: expected func to have a name\n{any}", .{s}); if (s.func.flags.contains(.is_export)) { if (!rewrite_esm_to_cjs) { p.print("export "); } } if (s.func.flags.contains(.is_async)) { p.print("async "); } p.print("function"); if (s.func.flags.contains(.is_generator)) { p.print("*"); p.printSpace(); } p.printSpaceBeforeIdentifier(); p.addSourceMapping(name.loc); p.printSymbol(nameRef); p.printFunc(s.func); // if (rewrite_esm_to_cjs and s.func.flags.contains(.is_export)) { // p.printSemicolonAfterStatement(); // p.print("var "); // p.printSymbol(nameRef); // p.@"print = "(); // p.printSymbol(nameRef); // p.printSemicolonAfterStatement(); // } else { p.printNewline(); // } if (rewrite_esm_to_cjs and s.func.flags.contains(.is_export)) { p.printIndent(); p.printBundledExport(p.renamer.nameForSymbol(nameRef), p.renamer.nameForSymbol(nameRef)); p.printSemicolonAfterStatement(); } }, .s_class => |s| { // Give an extra newline for readaiblity if (prev_stmt_tag != .s_empty) { p.printNewline(); } p.printIndent(); p.printSpaceBeforeIdentifier(); const nameRef = s.class.class_name.?.ref.?; if (s.is_export) { if (!rewrite_esm_to_cjs) { p.print("export "); } } p.print("class "); p.addSourceMapping(s.class.class_name.?.loc); p.printSymbol(nameRef); p.printClass(s.class); if (rewrite_esm_to_cjs and s.is_export) { p.printSemicolonAfterStatement(); } else { p.printNewline(); } if (rewrite_esm_to_cjs) { if (s.is_export) { p.printIndent(); p.printBundledExport(p.renamer.nameForSymbol(nameRef), p.renamer.nameForSymbol(nameRef)); p.printSemicolonAfterStatement(); } } }, .s_empty => { if (p.prev_stmt_tag == .s_empty and p.options.indent == 0) return; p.printIndent(); p.print(";"); p.printNewline(); }, .s_export_default => |s| { p.printIndent(); p.printSpaceBeforeIdentifier(); p.print("export default "); switch (s.value) { .expr => |expr| { // Functions and classes must be wrapped to avoid confusion with their statement forms p.export_default_start = p.writer.written; p.printExpr(expr, .comma, ExprFlag.None()); p.printSemicolonAfterStatement(); return; }, .stmt => |s2| { switch (s2.data) { .s_function => |func| { p.printSpaceBeforeIdentifier(); if (func.func.flags.contains(.is_async)) { p.print("async "); } p.print("function"); if (func.func.flags.contains(.is_generator)) { p.print("*"); p.printSpace(); } else { p.maybePrintSpace(); } if (func.func.name) |name| { p.printSymbol(name.ref.?); } p.printFunc(func.func); p.printNewline(); }, .s_class => |class| { p.printSpaceBeforeIdentifier(); if (class.class.class_name) |name| { p.print("class "); p.printSymbol(name.ref orelse Global.panic("Internal error: Expected class to have a name ref\n{any}", .{class})); } else { p.print("class"); } p.printClass(class.class); p.printNewline(); }, else => { Global.panic("Internal error: unexpected export default stmt data {any}", .{s}); }, } }, } }, .s_export_star => |s| { // Give an extra newline for readaiblity if (!prev_stmt_tag.isExportLike()) { p.printNewline(); } p.printIndent(); p.printSpaceBeforeIdentifier(); if (s.alias != null) p.printWhitespacer(comptime ws("export *").append(" as ")) else p.printWhitespacer(comptime ws("export * from ")); if (s.alias) |alias| { p.printClauseAlias(alias.original_name); p.print(" "); p.printWhitespacer(ws("from ")); } p.printImportRecordPath(p.importRecord(s.import_record_index)); p.printSemicolonAfterStatement(); }, .s_export_clause => |s| { if (rewrite_esm_to_cjs) { p.printIndent(); p.printSpaceBeforeIdentifier(); switch (s.items.len) { 0 => {}, // It unfortunately cannot be so simple as exports.foo = foo; // If we have a lazy re-export and it's read-only... // we have to overwrite it via Object.defineProperty // Object.assign(__export, {prop1, prop2, prop3}); else => { p.print("Object.assign"); p.print("("); p.printModuleExportSymbol(); p.print(","); p.printSpace(); p.print("{"); p.printSpace(); const last = s.items.len - 1; for (s.items, 0..) |item, i| { const symbol = p.symbols().getWithLink(item.name.ref.?).?; const name = symbol.original_name; var did_print = false; if (symbol.namespace_alias) |namespace| { const import_record = p.importRecord(namespace.import_record_index); if (namespace.was_originally_property_access) { p.printIdentifier(name); p.print(": () => "); p.printNamespaceAlias(import_record.*, namespace); did_print = true; } } if (!did_print) { p.printClauseAlias(item.alias); if (!strings.eql(name, item.alias)) { p.print(":"); p.printSpaceBeforeIdentifier(); p.printIdentifier(name); } } if (i < last) { p.print(","); } } p.print("})"); p.printSemicolonAfterStatement(); }, } return; } // Give an extra newline for export default for readability if (!prev_stmt_tag.isExportLike()) { p.printNewline(); } p.printIndent(); p.printSpaceBeforeIdentifier(); p.print("export"); p.printSpace(); if (s.items.len == 0) { p.print("{}"); p.printSemicolonAfterStatement(); return; } // This transforms code like this: // import {Foo, Bar} from 'bundled-module'; // export {Foo, Bar}; // into // export var Foo = $$bundledModule.Foo; (where $$bundledModule is created at import time) // This is necessary unfortunately because export statements do not allow dot expressions // The correct approach here is to invert the logic // instead, make the entire module behave like a CommonJS module // and export that one instead // This particular code segment does the transform inline by adding an extra pass over export clauses // and then swapRemove'ing them as we go var array = std.ArrayListUnmanaged(js_ast.ClauseItem){ .items = s.items, .capacity = s.items.len }; { var i: usize = 0; while (i < array.items.len) { const item: js_ast.ClauseItem = array.items[i]; if (item.original_name.len > 0) { if (p.symbols().get(item.name.ref.?)) |symbol| { if (symbol.namespace_alias) |namespace| { const import_record = p.importRecord(namespace.import_record_index); if (namespace.was_originally_property_access) { p.print("var "); p.printSymbol(item.name.ref.?); p.@"print = "(); p.printNamespaceAlias(import_record.*, namespace); p.printSemicolonAfterStatement(); _ = array.swapRemove(i); if (i < array.items.len) { p.printIndent(); p.printSpaceBeforeIdentifier(); p.print("export"); p.printSpace(); } continue; } } } } i += 1; } if (array.items.len == 0) { return; } s.items = array.items; } p.print("{"); if (!s.is_single_line) { p.options.indent += 1; } else { p.printSpace(); } for (s.items, 0..) |item, i| { if (i != 0) { p.print(","); if (s.is_single_line) { p.printSpace(); } } if (!s.is_single_line) { p.printNewline(); p.printIndent(); } p.printExportClauseItem(item); } if (!s.is_single_line) { p.options.unindent(); p.printNewline(); p.printIndent(); } else { p.printSpace(); } p.print("}"); p.printSemicolonAfterStatement(); }, .s_export_from => |s| { p.printIndent(); p.printSpaceBeforeIdentifier(); const import_record = p.importRecord(s.import_record_index); if (comptime is_bun_platform) { if (import_record.do_commonjs_transform_in_printer) { if (s.items.len == 0) return; p.print("var {"); var symbol_counter: u32 = p.symbol_counter; for (s.items, 0..) |item, i| { if (i > 0) { p.print(","); } p.print(item.original_name); assert(item.original_name.len > 0); p.print(":"); // this is unsound // this is technical debt // we need to handle symbol collisions for this p.print("$eXp0rT_"); var buf: [16]u8 = undefined; p.print(std.fmt.bufPrint(&buf, "{}", .{bun.fmt.hexIntLower(symbol_counter)}) catch unreachable); symbol_counter +|= 1; } p.print("}=import.meta.require("); p.printImportRecordPath(import_record); p.print(")"); p.printSemicolonAfterStatement(); p.printWhitespacer(ws("export {")); // reset symbol counter back symbol_counter = p.symbol_counter; for (s.items, 0..) |item, i| { if (i > 0) { p.print(","); } // this is unsound // this is technical debt // we need to handle symbol collisions for this p.print("$eXp0rT_"); var buf: [16]u8 = undefined; p.print(std.fmt.bufPrint(&buf, "{}", .{bun.fmt.hexIntLower(symbol_counter)}) catch unreachable); symbol_counter +|= 1; p.printWhitespacer(ws(" as ")); p.print(item.alias); } p.print("}"); p.printSemicolonAfterStatement(); p.symbol_counter = symbol_counter; return; } } p.printWhitespacer(ws("export {")); if (!s.is_single_line) { p.options.indent += 1; } else { p.printSpace(); } for (s.items, 0..) |item, i| { if (i != 0) { p.print(","); if (s.is_single_line) { p.printSpace(); } } if (!s.is_single_line) { p.printNewline(); p.printIndent(); } p.printExportClauseItem(item); } if (!s.is_single_line) { p.options.unindent(); p.printNewline(); p.printIndent(); } else { p.printSpace(); } p.printWhitespacer(ws("} from ")); p.printImportRecordPath(import_record); p.printSemicolonAfterStatement(); }, .s_local => |s| { switch (s.kind) { .k_const => { p.printDeclStmt(s.is_export, "const", s.decls.slice()); }, .k_let => { p.printDeclStmt(s.is_export, "let", s.decls.slice()); }, .k_var => { p.printDeclStmt(s.is_export, "var", s.decls.slice()); }, } }, .s_if => |s| { p.printIndent(); p.printIf(s); }, .s_do_while => |s| { p.printIndent(); p.printSpaceBeforeIdentifier(); p.print("do"); switch (s.body.data) { .s_block => { p.printSpace(); p.printBlock(s.body.loc, s.body.data.s_block.stmts, s.body.data.s_block.close_brace_loc); p.printSpace(); }, else => { p.printNewline(); p.options.indent += 1; p.printStmt(s.body) catch unreachable; p.printSemicolonIfNeeded(); p.options.unindent(); p.printIndent(); }, } p.print("while"); p.printSpace(); p.print("("); p.printExpr(s.test_, .lowest, ExprFlag.None()); p.print(")"); p.printSemicolonAfterStatement(); }, .s_for_in => |s| { p.printIndent(); p.printSpaceBeforeIdentifier(); p.print("for"); p.printSpace(); p.print("("); p.printForLoopInit(s.init); p.printSpace(); p.printSpaceBeforeIdentifier(); p.print("in"); p.printSpace(); p.printExpr(s.value, .lowest, ExprFlag.None()); p.print(")"); p.printBody(s.body); }, .s_for_of => |s| { p.printIndent(); p.printSpaceBeforeIdentifier(); p.print("for"); if (s.is_await) { p.print(" await"); } p.printSpace(); p.print("("); p.for_of_init_start = p.writer.written; p.printForLoopInit(s.init); p.printSpace(); p.printSpaceBeforeIdentifier(); p.print("of"); p.printSpace(); p.printExpr(s.value, .comma, ExprFlag.None()); p.print(")"); p.printBody(s.body); }, .s_while => |s| { p.printIndent(); p.printSpaceBeforeIdentifier(); p.print("while"); p.printSpace(); p.print("("); p.printExpr(s.test_, .lowest, ExprFlag.None()); p.print(")"); p.printBody(s.body); }, .s_with => |s| { p.printIndent(); p.printSpaceBeforeIdentifier(); p.print("with"); p.printSpace(); p.print("("); p.printExpr(s.value, .lowest, ExprFlag.None()); p.print(")"); p.printBody(s.body); }, .s_label => |s| { if (!p.options.minify_whitespace and p.options.indent > 0) { p.addSourceMapping(stmt.loc); p.printIndent(); } p.printSpaceBeforeIdentifier(); p.printSymbol(s.name.ref orelse Global.panic("Internal error: expected label to have a name {any}", .{s})); p.print(":"); p.printBody(s.stmt); }, .s_try => |s| { p.printIndent(); p.printSpaceBeforeIdentifier(); p.print("try"); p.printSpace(); p.printBlock(s.body_loc, s.body, null); if (s.catch_) |catch_| { p.printSpace(); p.print("catch"); if (catch_.binding) |binding| { p.printSpace(); p.print("("); p.printBinding(binding); p.print(")"); } p.printSpace(); p.printBlock(catch_.loc, catch_.body, null); } if (s.finally) |finally| { p.printSpace(); p.print("finally"); p.printSpace(); p.printBlock(finally.loc, finally.stmts, null); } p.printNewline(); }, .s_for => |s| { p.printIndent(); p.printSpaceBeforeIdentifier(); p.print("for"); p.printSpace(); p.print("("); if (s.init) |init_| { p.printForLoopInit(init_); } p.print(";"); if (s.test_) |test_| { p.printExpr(test_, .lowest, ExprFlag.None()); } p.print(";"); p.printSpace(); if (s.update) |update| { p.printExpr(update, .lowest, ExprFlag.None()); } p.print(")"); p.printBody(s.body); }, .s_switch => |s| { p.printIndent(); p.printSpaceBeforeIdentifier(); p.print("switch"); p.printSpace(); p.print("("); p.printExpr(s.test_, .lowest, ExprFlag.None()); p.print(")"); p.printSpace(); p.print("{"); p.printNewline(); p.options.indent += 1; for (s.cases) |c| { p.printSemicolonIfNeeded(); p.printIndent(); if (c.value) |val| { p.print("case"); p.printSpace(); p.printExpr(val, .logical_and, ExprFlag.None()); } else { p.print("default"); } p.print(":"); if (c.body.len == 1) { switch (c.body[0].data) { .s_block => { p.printSpace(); p.printBlock(c.body[0].loc, c.body[0].data.s_block.stmts, c.body[0].data.s_block.close_brace_loc); p.printNewline(); continue; }, else => {}, } } p.printNewline(); p.options.indent += 1; for (c.body) |st| { p.printSemicolonIfNeeded(); p.printStmt(st) catch unreachable; } p.options.unindent(); } p.options.unindent(); p.printIndent(); p.print("}"); p.printNewline(); p.needs_semicolon = false; }, .s_import => |s| { std.debug.assert(s.import_record_index < p.import_records.len); const record: *const ImportRecord = p.importRecord(s.import_record_index); switch (record.print_mode) { .css => { switch (p.options.css_import_behavior) { .facade => { // This comment exists to let tooling authors know which files CSS originated from // To parse this, you just look for a line that starts with //@import url(" p.print("//@import url(\""); // We do not URL escape here. p.print(record.path.text); // If they actually use the code, then we emit a facade that just echos whatever they write if (s.default_name) |name| { p.print("\"); css-module-facade\nvar "); p.printSymbol(name.ref.?); p.print(" = new Proxy({}, {get(_,className,__){return className;}});\n"); } else { p.print("\"); css-import-facade\n"); } return; }, .auto_onimportcss, .facade_onimportcss => { p.print("globalThis.document?.dispatchEvent(new CustomEvent(\"onimportcss\", {detail: \""); p.print(record.path.text); p.print("\"}));\n"); // If they actually use the code, then we emit a facade that just echos whatever they write if (s.default_name) |name| { p.print("var "); p.printSymbol(name.ref.?); p.print(" = new Proxy({}, {get(_,className,__){return className;}});\n"); } }, else => {}, } return; }, .import_path => { if (s.default_name) |name| { p.print("var "); p.printSymbol(name.ref.?); p.@"print = "(); p.printImportRecordPath(record); p.printSemicolonAfterStatement(); } else if (record.contains_import_star) { // this case is particularly important for running files without an extension in bun's runtime p.print("var "); p.printSymbol(s.namespace_ref); p.@"print = "(); p.print("{default:"); p.printImportRecordPath(record); p.print("}"); p.printSemicolonAfterStatement(); } return; }, .napi_module => { if (comptime is_bun_platform) { p.printIndent(); p.print("var "); p.printSymbol(s.namespace_ref); p.@"print = "(); p.print("import.meta.require("); p.printImportRecordPath(record); p.print(")"); p.printSemicolonAfterStatement(); } return; }, else => {}, } var item_count: usize = 0; p.printIndent(); p.printSpaceBeforeIdentifier(); if (comptime is_bun_platform) { switch (record.tag) { .bun_test => { p.printBunJestImportStatement(s.*); return; }, .bun => { p.printGlobalBunImportStatement(s.*); return; }, // .hardcoded => { // p.printHardcodedImportStatement(s.*); // return; // }, else => {}, } } if (record.do_commonjs_transform_in_printer or record.path.is_disabled) { const require_ref = p.options.require_ref; const module_id = record.module_id; if (!record.path.is_disabled and std.mem.indexOfScalar(u32, p.imported_module_ids.items, module_id) == null) { p.printWhitespacer(ws("import * as")); p.print(" "); p.printModuleId(module_id); p.print(" "); p.printWhitespacer(ws("from ")); p.print("\""); p.print(record.path.text); p.print("\""); p.printSemicolonAfterStatement(); try p.imported_module_ids.append(module_id); } if (record.contains_import_star) { p.print("var "); p.printSymbol(s.namespace_ref); p.@"print = "(); if (!record.path.is_disabled) { p.printSymbol(require_ref.?); p.print("("); p.printModuleId(module_id); p.print(");"); p.printNewline(); } else { p.printDisabledImport(); p.printSemicolonAfterStatement(); } } if (s.items.len > 0 or s.default_name != null) { p.printIndent(); p.printSpaceBeforeIdentifier(); p.printWhitespacer(ws("var {")); if (s.default_name) |default_name| { p.printSpace(); p.print("default:"); p.printSpace(); p.printSymbol(default_name.ref.?); if (s.items.len > 0) { p.printSpace(); p.print(","); p.printSpace(); for (s.items, 0..) |item, i| { p.printClauseItemAs(item, .@"var"); if (i < s.items.len - 1) { p.print(","); p.printSpace(); } } } } else { for (s.items, 0..) |item, i| { p.printClauseItemAs(item, .@"var"); if (i < s.items.len - 1) { p.print(","); p.printSpace(); } } } p.print("}"); p.@"print = "(); if (record.contains_import_star) { p.printSymbol(s.namespace_ref); p.printSemicolonAfterStatement(); } else if (!record.path.is_disabled) { p.printSymbol(require_ref.?); p.print("("); p.printModuleId(module_id); p.print(")"); p.printSemicolonAfterStatement(); } else { p.printDisabledImport(); p.printSemicolonAfterStatement(); } } return; } if (record.handles_import_errors and record.path.is_disabled and record.kind.isCommonJS()) { return; } p.print("import"); if (s.default_name) |name| { p.print(" "); p.printSymbol(name.ref.?); item_count += 1; } if (s.items.len > 0) { if (item_count > 0) { p.print(","); } p.printSpace(); p.print("{"); if (!s.is_single_line) { p.options.unindent(); } for (s.items, 0..) |item, i| { if (i != 0) { p.print(","); if (s.is_single_line) { p.printSpace(); } } if (!s.is_single_line) { p.printNewline(); p.printIndent(); } p.printClauseItem(item); } if (!s.is_single_line) { p.options.unindent(); p.printNewline(); p.printIndent(); } p.print("}"); item_count += 1; } if (record.contains_import_star) { if (item_count > 0) { p.print(","); } p.printSpace(); p.printWhitespacer(ws("* as")); p.print(" "); p.printSymbol(s.namespace_ref); item_count += 1; } if (item_count > 0) { p.print(" "); p.printWhitespacer(ws("from ")); } p.printImportRecordPath(record); p.printSemicolonAfterStatement(); }, .s_block => |s| { p.printIndent(); p.printBlock(stmt.loc, s.stmts, s.close_brace_loc); p.printNewline(); }, .s_debugger => { p.printIndent(); p.printSpaceBeforeIdentifier(); p.print("debugger"); p.printSemicolonAfterStatement(); }, .s_directive => |s| { if (comptime is_json) unreachable; const c = bestQuoteCharForString(u16, s.value, false); p.printIndent(); p.printSpaceBeforeIdentifier(); p.print(c); p.printQuotedUTF16(s.value, c); p.print(c); p.printSemicolonAfterStatement(); }, .s_break => |s| { p.printIndent(); p.printSpaceBeforeIdentifier(); p.print("break"); if (s.label) |label| { p.print(" "); p.printSymbol(label.ref.?); } p.printSemicolonAfterStatement(); }, .s_continue => |s| { p.printIndent(); p.printSpaceBeforeIdentifier(); p.print("continue"); if (s.label) |label| { p.print(" "); p.printSymbol(label.ref.?); } p.printSemicolonAfterStatement(); }, .s_return => |s| { p.printIndent(); p.printSpaceBeforeIdentifier(); p.print("return"); if (s.value) |value| { p.printSpace(); p.printExpr(value, .lowest, ExprFlag.None()); } p.printSemicolonAfterStatement(); }, .s_throw => |s| { p.printIndent(); p.printSpaceBeforeIdentifier(); p.print("throw"); p.printSpace(); p.printExpr(s.value, .lowest, ExprFlag.None()); p.printSemicolonAfterStatement(); }, .s_expr => |s| { if (!p.options.minify_whitespace and p.options.indent > 0) { p.addSourceMapping(stmt.loc); p.printIndent(); } p.stmt_start = p.writer.written; p.printExpr(s.value, .lowest, ExprFlag.ExprResultIsUnused()); p.printSemicolonAfterStatement(); }, else => { var slice = p.writer.slice(); const to_print: []const u8 = if (slice.len > 1024) slice[slice.len - 1024 ..] else slice; if (to_print.len > 0) { Global.panic("\nvoluntary crash while printing:\n{s}\n---This is a bug. Not your fault.\n", .{to_print}); } else { Global.panic("\nvoluntary crash while printing. This is a bug. Not your fault.\n", .{}); } }, } } pub inline fn printModuleExportSymbol(p: *Printer) void { p.print("module.exports"); } pub fn printImportRecordPath(p: *Printer, import_record: *const ImportRecord) void { if (comptime is_json) unreachable; const quote = bestQuoteCharForString(u8, import_record.path.text, false); if (import_record.print_namespace_in_path and import_record.module_id != 0) { p.print(quote); p.print(import_record.path.namespace); p.print(":"); p.printModuleIdAssumeEnabled(import_record.module_id); p.print(quote); } else if (import_record.print_namespace_in_path and !import_record.path.isFile()) { p.print(quote); p.print(import_record.path.namespace); p.print(":"); p.printIdentifier(import_record.path.text); p.print(quote); } else { p.print(quote); p.printIdentifier(import_record.path.text); p.print(quote); } } pub fn printBundledImport(p: *Printer, record: ImportRecord, s: *S.Import) void { if (record.is_internal) { return; } const import_record = p.importRecord(s.import_record_index); const is_disabled = import_record.path.is_disabled; const module_id = import_record.module_id; // If the bundled import was disabled and only imported for side effects // we can skip it if (record.path.is_disabled) { if (p.symbols().get(s.namespace_ref) == null) return; } switch (ImportVariant.determine(&record, s)) { .path_only => { if (!is_disabled) { p.printCallModuleID(module_id); p.printSemicolonAfterStatement(); } }, .import_items_and_default, .import_default => { if (!is_disabled) { p.print("var $"); p.printModuleId(module_id); p.@"print = "(); p.printLoadFromBundle(s.import_record_index); if (s.default_name) |default_name| { p.print(", "); p.printSymbol(default_name.ref.?); p.print(" = (($"); p.printModuleId(module_id); p.print(" && \"default\" in $"); p.printModuleId(module_id); p.print(") ? $"); p.printModuleId(module_id); p.print(".default : $"); p.printModuleId(module_id); p.print(")"); } } else { if (s.default_name) |default_name| { p.print("var "); p.printSymbol(default_name.ref.?); p.@"print = "(); p.printDisabledImport(); } } p.printSemicolonAfterStatement(); }, .import_star_and_import_default => { p.print("var "); p.printSymbol(s.namespace_ref); p.@"print = "(); p.printLoadFromBundle(s.import_record_index); if (s.default_name) |default_name| { p.print(","); p.printSpace(); p.printSymbol(default_name.ref.?); p.@"print = "(); if (!is_bun_platform) { p.print("("); p.printSymbol(s.namespace_ref); p.printWhitespacer(ws(" && \"default\" in ")); p.printSymbol(s.namespace_ref); p.printWhitespacer(ws(" ? ")); p.printSymbol(s.namespace_ref); p.printWhitespacer(ws(".default : ")); p.printSymbol(s.namespace_ref); p.print(")"); } else { p.printSymbol(s.namespace_ref); } } p.printSemicolonAfterStatement(); }, .import_star => { p.print("var "); p.printSymbol(s.namespace_ref); p.@"print = "(); p.printLoadFromBundle(s.import_record_index); p.printSemicolonAfterStatement(); }, else => { p.print("var $"); p.printModuleIdAssumeEnabled(module_id); p.@"print = "(); p.printLoadFromBundle(s.import_record_index); p.printSemicolonAfterStatement(); }, } } pub fn printLoadFromBundle(p: *Printer, import_record_index: u32) void { p.printLoadFromBundleWithoutCall(import_record_index); p.print("()"); } inline fn printDisabledImport(p: *Printer) void { p.print("(()=>({}))"); } pub fn printLoadFromBundleWithoutCall(p: *Printer, import_record_index: u32) void { const record = p.importRecord(import_record_index); if (record.path.is_disabled) { p.printDisabledImport(); return; } @call(.always_inline, printModuleId, .{ p, p.importRecord(import_record_index).module_id }); } pub fn printCallModuleID(p: *Printer, module_id: u32) void { printModuleId(p, module_id); p.print("()"); } inline fn printModuleId(p: *Printer, module_id: u32) void { std.debug.assert(module_id != 0); // either module_id is forgotten or it should be disabled p.printModuleIdAssumeEnabled(module_id); } inline fn printModuleIdAssumeEnabled(p: *Printer, module_id: u32) void { p.print("$"); std.fmt.formatInt(module_id, 16, .lower, .{}, p) catch unreachable; } pub fn printBundledRequire(p: *Printer, require: E.RequireString) void { if (p.importRecord(require.import_record_index).is_internal) { return; } p.printSymbol(p.options.runtime_imports.__require.?.ref); // d is for default p.print(".d("); p.printLoadFromBundle(require.import_record_index); p.print(")"); } pub fn printBundledRexport(p: *Printer, name: string, import_record_index: u32) void { p.print("Object.defineProperty("); p.printModuleExportSymbol(); p.print(","); p.printQuotedUTF8(name, true); p.printWhitespacer(ws(",{get: () => (")); p.printLoadFromBundle(import_record_index); p.printWhitespacer(ws("), enumerable: true, configurable: true})")); } // We must use Object.defineProperty() to handle re-exports from ESM -> CJS // Here is an example where a runtime error occurs when assigning directly to module.exports // > 24077 | module.exports.init = init; // > ^ // > TypeError: Attempted to assign to readonly property. pub fn printBundledExport(p: *Printer, name: string, identifier: string) void { // In the event that p.print("Object.defineProperty("); p.printModuleExportSymbol(); p.print(","); p.printQuotedUTF8(name, true); p.print(",{get: () => "); p.printIdentifier(identifier); p.print(", enumerable: true, configurable: true})"); } pub fn printForLoopInit(p: *Printer, initSt: Stmt) void { switch (initSt.data) { .s_expr => |s| { p.printExpr( s.value, .lowest, ExprFlag.Set.init(.{ .forbid_in = true, .expr_result_is_unused = true }), ); }, .s_local => |s| { switch (s.kind) { .k_var => { p.printDecls("var", s.decls.slice(), ExprFlag.Set.init(.{ .forbid_in = true })); }, .k_let => { p.printDecls("let", s.decls.slice(), ExprFlag.Set.init(.{ .forbid_in = true })); }, .k_const => { p.printDecls("const", s.decls.slice(), ExprFlag.Set.init(.{ .forbid_in = true })); }, } }, // for(;) .s_empty => {}, else => { Global.panic("Internal error: Unexpected stmt in for loop {any}", .{initSt}); }, } } pub fn printIf(p: *Printer, s: *const S.If) void { p.printSpaceBeforeIdentifier(); p.print("if"); p.printSpace(); p.print("("); p.printExpr(s.test_, .lowest, ExprFlag.None()); p.print(")"); switch (s.yes.data) { .s_block => |block| { p.printSpace(); p.printBlock(s.yes.loc, block.stmts, block.close_brace_loc); if (s.no != null) { p.printSpace(); } else { p.printNewline(); } }, else => { if (wrapToAvoidAmbiguousElse(&s.yes.data)) { p.printSpace(); p.print("{"); p.printNewline(); p.options.indent += 1; p.printStmt(s.yes) catch unreachable; p.options.unindent(); p.needs_semicolon = false; p.printIndent(); p.print("}"); if (s.no != null) { p.printSpace(); } else { p.printNewline(); } } else { p.printNewline(); p.options.indent += 1; p.printStmt(s.yes) catch unreachable; p.options.unindent(); if (s.no != null) { p.printIndent(); } } }, } if (s.no) |no_block| { p.printSemicolonIfNeeded(); p.printSpaceBeforeIdentifier(); p.print("else"); switch (no_block.data) { .s_block => { p.printSpace(); p.printBlock(no_block.loc, no_block.data.s_block.stmts, null); p.printNewline(); }, .s_if => { p.printIf(no_block.data.s_if); }, else => { p.printNewline(); p.options.indent += 1; p.printStmt(no_block) catch unreachable; p.options.unindent(); }, } } } pub fn wrapToAvoidAmbiguousElse(s_: *const Stmt.Data) bool { var s = s_; while (true) { switch (s.*) { .s_if => |index| { if (index.no) |*no| { s = &no.data; } else { return true; } }, .s_for => |current| { s = ¤t.body.data; }, .s_for_in => |current| { s = ¤t.body.data; }, .s_for_of => |current| { s = ¤t.body.data; }, .s_while => |current| { s = ¤t.body.data; }, .s_with => |current| { s = ¤t.body.data; }, else => { return false; }, } } } pub fn printDeclStmt(p: *Printer, is_export: bool, comptime keyword: string, decls: []G.Decl) void { p.printIndent(); p.printSpaceBeforeIdentifier(); if (!rewrite_esm_to_cjs and is_export) { p.print("export "); } p.printDecls(keyword, decls, ExprFlag.None()); p.printSemicolonAfterStatement(); if (rewrite_esm_to_cjs and is_export and decls.len > 0) { for (decls) |decl| { p.printIndent(); p.printSymbol(p.options.runtime_imports.__export.?.ref); p.print("("); p.printSpaceBeforeIdentifier(); p.printModuleExportSymbol(); p.print(","); p.printSpace(); switch (decl.binding.data) { .b_identifier => |ident| { p.print("{"); p.printSpace(); p.printSymbol(ident.ref); if (p.options.minify_whitespace) p.print(":()=>(") else p.print(": () => ("); p.printSymbol(ident.ref); p.print(") }"); }, .b_object => |obj| { p.print("{"); p.printSpace(); for (obj.properties) |prop| { switch (prop.value.data) { .b_identifier => |ident| { p.printSymbol(ident.ref); if (p.options.minify_whitespace) p.print(":()=>(") else p.print(": () => ("); p.printSymbol(ident.ref); p.print("),"); p.printNewline(); }, else => {}, } } p.print("}"); }, else => { p.printBinding(decl.binding); }, } p.print(")"); p.printSemicolonAfterStatement(); } } } pub fn printIdentifier(p: *Printer, identifier: string) void { if (comptime ascii_only) { p.printQuotedIdentifier(identifier); } else { p.print(identifier); } } fn printQuotedIdentifier(p: *Printer, identifier: string) void { var ascii_start: usize = 0; var is_ascii = false; var iter = CodepointIterator.init(identifier); var cursor = CodepointIterator.Cursor{}; while (iter.next(&cursor)) { switch (cursor.c) { first_ascii...last_ascii => { if (!is_ascii) { ascii_start = cursor.i; is_ascii = true; } }, else => { if (is_ascii) { p.print(identifier[ascii_start..cursor.i]); is_ascii = false; } p.print("\\u{"); std.fmt.formatInt(cursor.c, 16, .lower, .{}, p) catch unreachable; p.print("}"); }, } } if (is_ascii) { p.print(identifier[ascii_start..]); } } pub fn printIdentifierUTF16(p: *Printer, name: []const u16) !void { const n = name.len; var i: usize = 0; const CodeUnitType = u32; while (i < n) { var c: CodeUnitType = name[i]; i += 1; if (c & ~@as(CodeUnitType, 0x03ff) == 0xd800 and i < n) { c = 0x10000 + (((c & 0x03ff) << 10) | (name[i] & 0x03ff)); i += 1; } if ((comptime ascii_only) and c > last_ascii) { switch (c) { 0...0xFFFF => { p.print( [_]u8{ '\\', 'u', hex_chars[c >> 12], hex_chars[(c >> 8) & 15], hex_chars[(c >> 4) & 15], hex_chars[c & 15], }, ); }, else => { p.print("\\u"); var buf_ptr = p.writer.reserve(4) catch unreachable; p.writer.advance(strings.encodeWTF8RuneT(buf_ptr[0..4], CodeUnitType, c)); }, } continue; } { var buf_ptr = p.writer.reserve(4) catch unreachable; p.writer.advance(strings.encodeWTF8RuneT(buf_ptr[0..4], CodeUnitType, c)); } } } pub fn printIndentedComment(p: *Printer, _text: string) void { var text = _text; if (strings.startsWith(text, "/*")) { // Re-indent multi-line comments while (strings.indexOfChar(text, '\n')) |newline_index| { p.printIndent(); p.print(text[0 .. newline_index + 1]); text = text[newline_index + 1 ..]; } p.printIndent(); p.print(text); p.printNewline(); } else { // Print a mandatory newline after single-line comments p.printIndent(); p.print(text); p.print("\n"); } } pub fn init( writer: Writer, import_records: []const ImportRecord, opts: Options, renamer: bun.renamer.Renamer, source_map_builder: SourceMap.Chunk.Builder, ) Printer { if (imported_module_ids_list_unset) { imported_module_ids_list = std.ArrayList(u32).init(default_allocator); imported_module_ids_list_unset = false; } imported_module_ids_list.clearRetainingCapacity(); var printer = Printer{ .import_records = import_records, .options = opts, .writer = writer, .imported_module_ids = imported_module_ids_list, .renamer = renamer, .source_map_builder = source_map_builder, }; if (comptime generate_source_map) { // This seems silly to cache but the .items() function apparently costs 1ms according to Instruments. printer.source_map_builder.line_offset_table_byte_offset_list = printer .source_map_builder .line_offset_tables .items(.byte_offset_to_start_of_line); } return printer; } }; } pub const WriteResult = struct { off: u32, len: usize, end_off: u32, }; pub fn NewWriter( comptime ContextType: type, comptime writeByte: fn (ctx: *ContextType, char: u8) anyerror!usize, comptime writeAllFn: fn (ctx: *ContextType, buf: anytype) anyerror!usize, comptime getLastByte: fn (ctx: *const ContextType) u8, comptime getLastLastByte: fn (ctx: *const ContextType) u8, comptime reserveNext: fn (ctx: *ContextType, count: u32) anyerror![*]u8, comptime advanceBy: fn (ctx: *ContextType, count: u32) void, ) type { return struct { const Self = @This(); ctx: ContextType, written: i32 = -1, // Used by the printer prev_char: u8 = 0, prev_prev_char: u8 = 0, err: ?anyerror = null, orig_err: ?anyerror = null, pub fn init(ctx: ContextType) Self { return .{ .ctx = ctx, }; } pub fn isCopyFileRangeSupported() bool { return comptime std.meta.trait.hasFn("copyFileRange")(ContextType); } pub fn copyFileRange(ctx: ContextType, in_file: StoredFileDescriptorType, start: usize, end: usize) !void { ctx.sendfile( in_file, start, end, ); } pub fn slice(this: *Self) string { return this.ctx.slice(); } pub fn getError(writer: *const Self) anyerror!void { if (writer.orig_err) |orig_err| { return orig_err; } if (writer.err) |err| { return err; } } pub inline fn prevChar(writer: *const Self) u8 { return @call(.always_inline, getLastByte, .{&writer.ctx}); } pub inline fn prevPrevChar(writer: *const Self) u8 { return @call(.always_inline, getLastLastByte, .{&writer.ctx}); } pub fn reserve(writer: *Self, count: u32) anyerror![*]u8 { return try reserveNext(&writer.ctx, count); } pub fn advance(writer: *Self, count: u32) void { advanceBy(&writer.ctx, count); writer.written += @as(i32, @intCast(count)); } pub const Error = error{FormatError}; pub fn writeAll(writer: *Self, bytes: anytype) Error!usize { const written = @max(writer.written, 0); writer.print(@TypeOf(bytes), bytes); return @as(usize, @intCast(writer.written)) - @as(usize, @intCast(written)); } pub inline fn print(writer: *Self, comptime ValueType: type, str: ValueType) void { if (FeatureFlags.disable_printing_null) { if (str == 0) { Global.panic("Attempted to print null char", .{}); } } switch (ValueType) { comptime_int, u16, u8 => { const written = writeByte(&writer.ctx, @as(u8, @intCast(str))) catch |err| brk: { writer.orig_err = err; break :brk 0; }; writer.written += @as(i32, @intCast(written)); writer.err = if (written == 0) error.WriteFailed else writer.err; }, else => { const written = writeAllFn(&writer.ctx, str) catch |err| brk: { writer.orig_err = err; break :brk 0; }; writer.written += @as(i32, @intCast(written)); if (written < str.len) { writer.err = if (written == 0) error.WriteFailed else error.PartialWrite; } }, } } const hasFlush = std.meta.trait.hasFn("flush"); pub fn flush(writer: *Self) !void { if (hasFlush(ContextType)) { try writer.ctx.flush(); } } const hasDone = std.meta.trait.hasFn("done"); pub fn done(writer: *Self) !void { if (hasDone(ContextType)) { try writer.ctx.done(); } } }; } pub const DirectWriter = struct { handle: FileDescriptorType, pub fn write(writer: *DirectWriter, buf: []const u8) !usize { return try std.os.write(writer.handle, buf); } pub fn writeAll(writer: *DirectWriter, buf: []const u8) !void { _ = try std.os.write(writer.handle, buf); } pub const Error = std.os.WriteError; }; // Unbuffered 653ms // Buffered 65k 47ms // Buffered 16k 43ms // Buffered 4k 55ms const FileWriterInternal = struct { file: std.fs.File, last_bytes: [2]u8 = [_]u8{ 0, 0 }, threadlocal var buffer: MutableString = undefined; threadlocal var has_loaded_buffer: bool = false; pub fn getBuffer() *MutableString { buffer.reset(); return &buffer; } pub fn init(file: std.fs.File) FileWriterInternal { if (!has_loaded_buffer) { buffer = MutableString.init(default_allocator, 0) catch unreachable; has_loaded_buffer = true; } buffer.reset(); return FileWriterInternal{ .file = file, }; } pub fn writeByte(this: *FileWriterInternal, byte: u8) anyerror!usize { try buffer.appendChar(byte); this.last_bytes = .{ this.last_bytes[1], byte }; return 1; } pub fn writeAll(this: *FileWriterInternal, bytes: anytype) anyerror!usize { try buffer.append(bytes); if (bytes.len >= 2) { this.last_bytes = bytes[bytes.len - 2 ..][0..2].*; } else if (bytes.len >= 1) { this.last_bytes = .{ this.last_bytes[1], bytes[bytes.len - 1] }; } return bytes.len; } pub fn slice(_: *@This()) string { return buffer.list.items; } pub fn getLastByte(this: *const FileWriterInternal) u8 { return this.last_bytes[1]; } pub fn getLastLastByte(this: *const FileWriterInternal) u8 { return this.last_bytes[0]; } pub fn reserveNext(_: *FileWriterInternal, count: u32) anyerror![*]u8 { try buffer.growIfNeeded(count); return @as([*]u8, @ptrCast(&buffer.list.items.ptr[buffer.list.items.len])); } pub fn advanceBy(this: *FileWriterInternal, count: u32) void { if (comptime Environment.isDebug) std.debug.assert(buffer.list.items.len + count <= buffer.list.capacity); buffer.list.items = buffer.list.items.ptr[0 .. buffer.list.items.len + count]; if (count >= 2) { this.last_bytes = buffer.list.items[buffer.list.items.len - 2 ..][0..2].*; } else if (count >= 1) { this.last_bytes = .{ this.last_bytes[1], buffer.list.items[buffer.list.items.len - 1] }; } } pub fn done( ctx: *FileWriterInternal, ) anyerror!void { defer buffer.reset(); var result_ = buffer.toOwnedSliceLeaky(); var result = result_; while (result.len > 0) { switch (result.len) { 0...4096 => { const written = try ctx.file.write(result); if (written == 0 or result.len - written == 0) return; result = result[written..]; }, else => { const first = result.ptr[0 .. result.len / 3]; const second = result[first.len..][0..first.len]; const remain = first.len + second.len; const third: []const u8 = result[remain..]; var vecs = [_]std.os.iovec_const{ .{ .iov_base = first.ptr, .iov_len = first.len, }, .{ .iov_base = second.ptr, .iov_len = second.len, }, .{ .iov_base = third.ptr, .iov_len = third.len, }, }; const written = try std.os.writev(ctx.file.handle, vecs[0..@as(usize, if (third.len > 0) 3 else 2)]); if (written == 0 or result.len - written == 0) return; result = result[written..]; }, } } } pub fn flush( _: *FileWriterInternal, ) anyerror!void {} }; pub const BufferWriter = struct { buffer: MutableString = undefined, written: []u8 = &[_]u8{}, sentinel: [:0]const u8 = "", append_null_byte: bool = false, append_newline: bool = false, approximate_newline_count: usize = 0, last_bytes: [2]u8 = [_]u8{ 0, 0 }, pub fn getWritten(this: *BufferWriter) []u8 { return this.buffer.list.items; } pub fn init(allocator: std.mem.Allocator) !BufferWriter { return BufferWriter{ .buffer = MutableString.init( allocator, 0, ) catch unreachable, }; } pub fn writeByte(ctx: *BufferWriter, byte: u8) anyerror!usize { try ctx.buffer.appendChar(byte); ctx.approximate_newline_count += @intFromBool(byte == '\n'); ctx.last_bytes = .{ ctx.last_bytes[1], byte }; return 1; } pub fn writeAll(ctx: *BufferWriter, bytes: anytype) anyerror!usize { try ctx.buffer.append(bytes); ctx.approximate_newline_count += @intFromBool(bytes.len > 0 and bytes[bytes.len - 1] == '\n'); if (bytes.len >= 2) { ctx.last_bytes = bytes[bytes.len - 2 ..][0..2].*; } else if (bytes.len >= 1) { ctx.last_bytes = .{ ctx.last_bytes[1], bytes[bytes.len - 1] }; } return bytes.len; } pub fn slice(self: *@This()) string { return self.buffer.list.items; } pub fn getLastByte(ctx: *const BufferWriter) u8 { return ctx.last_bytes[1]; } pub fn getLastLastByte(ctx: *const BufferWriter) u8 { return ctx.last_bytes[0]; } pub fn reserveNext(ctx: *BufferWriter, count: u32) anyerror![*]u8 { try ctx.buffer.growIfNeeded(count); return @as([*]u8, @ptrCast(&ctx.buffer.list.items.ptr[ctx.buffer.list.items.len])); } pub fn advanceBy(ctx: *BufferWriter, count: u32) void { if (comptime Environment.isDebug) std.debug.assert(ctx.buffer.list.items.len + count <= ctx.buffer.list.capacity); ctx.buffer.list.items = ctx.buffer.list.items.ptr[0 .. ctx.buffer.list.items.len + count]; if (count >= 2) { ctx.last_bytes = ctx.buffer.list.items[ctx.buffer.list.items.len - 2 ..][0..2].*; } else if (count >= 1) { ctx.last_bytes = .{ ctx.last_bytes[1], ctx.buffer.list.items[ctx.buffer.list.items.len - 1] }; } } pub fn reset(ctx: *BufferWriter) void { ctx.buffer.reset(); ctx.approximate_newline_count = 0; } pub fn writtenWithoutTrailingZero(ctx: *const BufferWriter) []u8 { var written = ctx.written; while (written.len > 0 and written[written.len - 1] == 0) { written = written[0 .. written.len - 1]; } return written; } pub fn done( ctx: *BufferWriter, ) anyerror!void { if (ctx.append_newline) { ctx.append_newline = false; try ctx.buffer.appendChar('\n'); } if (ctx.append_null_byte) { ctx.sentinel = ctx.buffer.toOwnedSentinelLeaky(); ctx.written = ctx.buffer.toOwnedSliceLeaky(); } else { ctx.written = ctx.buffer.toOwnedSliceLeaky(); } } pub fn flush( _: *BufferWriter, ) anyerror!void {} }; pub const BufferPrinter = NewWriter( BufferWriter, BufferWriter.writeByte, BufferWriter.writeAll, BufferWriter.getLastByte, BufferWriter.getLastLastByte, BufferWriter.reserveNext, BufferWriter.advanceBy, ); pub const FileWriter = NewWriter( FileWriterInternal, FileWriterInternal.writeByte, FileWriterInternal.writeAll, FileWriterInternal.getLastByte, FileWriterInternal.getLastLastByte, FileWriterInternal.reserveNext, FileWriterInternal.advanceBy, ); pub fn NewFileWriter(file: std.fs.File) FileWriter { var internal = FileWriterInternal.init(file); return FileWriter.init(internal); } pub const Format = enum { esm, cjs, // bun.js must escape non-latin1 identifiers in the output This is because // we load JavaScript as a UTF-8 buffer instead of a UTF-16 buffer // JavaScriptCore does not support UTF-8 identifiers when the source code // string is loaded as const char* We don't want to double the size of code // in memory... esm_ascii, cjs_ascii, }; const GenerateSourceMap = enum { disable, lazy, eager, }; pub fn getSourceMapBuilder( comptime generate_source_map: GenerateSourceMap, comptime is_bun_platform: bool, opts: Options, source: *const logger.Source, tree: *const Ast, ) SourceMap.Chunk.Builder { if (comptime generate_source_map == .disable) return undefined; return .{ .source_map = SourceMap.Chunk.Builder.SourceMapper.init( opts.allocator, is_bun_platform, ), .cover_lines_without_mappings = true, .approximate_input_line_count = tree.approximate_newline_count, .prepend_count = is_bun_platform and generate_source_map == .lazy, .line_offset_tables = opts.line_offset_tables orelse brk: { if (generate_source_map == .lazy) break :brk SourceMap.LineOffsetTable.generate( opts.allocator, source.contents, @as( i32, @intCast(tree.approximate_newline_count), ), ); break :brk SourceMap.LineOffsetTable.List{}; }, }; } pub fn printAst( comptime Writer: type, _writer: Writer, tree: Ast, symbols: js_ast.Symbol.Map, source: *const logger.Source, comptime ascii_only: bool, opts: Options, comptime generate_source_map: bool, ) !usize { var renamer: rename.Renamer = undefined; var no_op_renamer: rename.NoOpRenamer = undefined; var module_scope = tree.module_scope; if (opts.minify_identifiers) { const allocator = opts.allocator; var reserved_names = try rename.computeInitialReservedNames(allocator); for (module_scope.children.slice()) |child| { child.parent = &module_scope; } rename.computeReservedNamesForScope(&module_scope, &symbols, &reserved_names, allocator); var minify_renamer = try rename.MinifyRenamer.init(allocator, symbols, tree.nested_scope_slot_counts, reserved_names); var top_level_symbols = rename.StableSymbolCount.Array.init(allocator); defer top_level_symbols.deinit(); const uses_exports_ref = tree.uses_exports_ref; const uses_module_ref = tree.uses_module_ref; const exports_ref = tree.exports_ref; const module_ref = tree.module_ref; const parts = tree.parts; const dont_break_the_code = .{ tree.module_ref, tree.exports_ref, tree.require_ref, }; inline for (dont_break_the_code) |ref| { if (symbols.get(ref)) |symbol| { symbol.must_not_be_renamed = true; } } for (tree.named_exports.values()) |named_export| { if (symbols.get(named_export.ref)) |symbol| { symbol.must_not_be_renamed = true; } } if (uses_exports_ref) { try minify_renamer.accumulateSymbolUseCount(&top_level_symbols, exports_ref, 1, &.{source.index.value}); } if (uses_module_ref) { try minify_renamer.accumulateSymbolUseCount(&top_level_symbols, module_ref, 1, &.{source.index.value}); } for (parts.slice()) |part| { try minify_renamer.accumulateSymbolUseCounts(&top_level_symbols, part.symbol_uses, &.{source.index.value}); for (part.declared_symbols.refs()) |declared_ref| { try minify_renamer.accumulateSymbolUseCount(&top_level_symbols, declared_ref, 1, &.{source.index.value}); } } std.sort.block(rename.StableSymbolCount, top_level_symbols.items, {}, rename.StableSymbolCount.lessThan); try minify_renamer.allocateTopLevelSymbolSlots(top_level_symbols); var minifier = tree.char_freq.?.compile(allocator); try minify_renamer.assignNamesByFrequency(&minifier); renamer = minify_renamer.toRenamer(); } else { no_op_renamer = rename.NoOpRenamer.init(symbols, source); renamer = no_op_renamer.toRenamer(); } defer { if (opts.minify_identifiers) { renamer.deinit(opts.allocator); } } const PrinterType = NewPrinter( ascii_only, Writer, false, // if it's ascii_only, it is also bun ascii_only, false, generate_source_map, ); var writer = _writer; var printer = PrinterType.init( writer, tree.import_records.slice(), opts, renamer, getSourceMapBuilder(if (generate_source_map) .lazy else .disable, ascii_only, opts, source, &tree), ); defer { imported_module_ids_list = printer.imported_module_ids; } if (tree.prepend_part) |part| { for (part.stmts) |stmt| { try printer.printStmt(stmt); if (printer.writer.getError()) {} else |err| { return err; } printer.printSemicolonIfNeeded(); } } for (tree.parts.slice()) |part| { for (part.stmts) |stmt| { try printer.printStmt(stmt); if (printer.writer.getError()) {} else |err| { return err; } printer.printSemicolonIfNeeded(); } } if (comptime generate_source_map) { if (opts.source_map_handler) |handler| { try handler.onSourceMapChunk(printer.source_map_builder.generateChunk(printer.writer.ctx.getWritten()), source.*); } } try printer.writer.done(); return @as(usize, @intCast(@max(printer.writer.written, 0))); } pub fn printJSON( comptime Writer: type, _writer: Writer, expr: Expr, source: *const logger.Source, ) !usize { const PrinterType = NewPrinter(false, Writer, false, false, true, false); var writer = _writer; var s_expr = S.SExpr{ .value = expr }; var stmt = Stmt{ .loc = logger.Loc.Empty, .data = .{ .s_expr = &s_expr, } }; var stmts = [_]js_ast.Stmt{stmt}; var parts = [_]js_ast.Part{.{ .stmts = &stmts }}; const ast = Ast.initTest(&parts); var list = js_ast.Symbol.List.init(ast.symbols.slice()); var nested_list = js_ast.Symbol.NestedList.init(&[_]js_ast.Symbol.List{list}); var renamer = rename.NoOpRenamer.init(js_ast.Symbol.Map.initList(nested_list), source); var printer = PrinterType.init( writer, ast.import_records.slice(), .{}, renamer.toRenamer(), undefined, ); printer.printExpr(expr, Level.lowest, ExprFlag.Set{}); if (printer.writer.getError()) {} else |err| { return err; } try printer.writer.done(); return @as(usize, @intCast(@max(printer.writer.written, 0))); } pub fn print( allocator: std.mem.Allocator, target: options.Target, ast: Ast, source: *const logger.Source, opts: Options, import_records: []const ImportRecord, parts: []const js_ast.Part, renamer: bun.renamer.Renamer, comptime generate_source_maps: bool, ) PrintResult { const trace = bun.tracy.traceNamed(@src(), "JSPrinter.print"); defer trace.end(); var buffer_writer = BufferWriter.init(allocator) catch |err| return .{ .err = err }; var buffer_printer = BufferPrinter.init(buffer_writer); return printWithWriter( *BufferPrinter, &buffer_printer, target, ast, source, opts, import_records, parts, renamer, comptime generate_source_maps, ); } pub fn printWithWriter( comptime Writer: type, _writer: Writer, target: options.Target, ast: Ast, source: *const logger.Source, opts: Options, import_records: []const ImportRecord, parts: []const js_ast.Part, renamer: bun.renamer.Renamer, comptime generate_source_maps: bool, ) PrintResult { return switch (target.isBun()) { inline else => |is_bun| printWithWriterAndPlatform( Writer, _writer, is_bun, ast, source, opts, import_records, parts, renamer, generate_source_maps, ), }; } /// The real one pub fn printWithWriterAndPlatform( comptime Writer: type, _writer: Writer, comptime is_bun_platform: bool, ast: Ast, source: *const logger.Source, opts: Options, import_records: []const ImportRecord, parts: []const js_ast.Part, renamer: bun.renamer.Renamer, comptime generate_source_maps: bool, ) PrintResult { const PrinterType = NewPrinter( false, Writer, false, is_bun_platform, false, generate_source_maps, ); var writer = _writer; var printer = PrinterType.init( writer, import_records, opts, renamer, getSourceMapBuilder(if (generate_source_maps) .eager else .disable, is_bun_platform, opts, source, &ast), ); defer printer.temporary_bindings.deinit(bun.default_allocator); defer _writer.* = printer.writer.*; defer { imported_module_ids_list = printer.imported_module_ids; } for (parts) |part| { for (part.stmts) |stmt| { printer.printStmt(stmt) catch |err| { return .{ .err = err }; }; if (printer.writer.getError()) {} else |err| { return .{ .err = err }; } printer.printSemicolonIfNeeded(); } } printer.writer.done() catch |err| return .{ .err = err }; const written = printer.writer.ctx.getWritten(); const source_map: ?SourceMap.Chunk = if (generate_source_maps and written.len > 0) brk: { const chunk = printer.source_map_builder.generateChunk(written); if (chunk.should_ignore) break :brk null; break :brk chunk; } else null; return .{ .result = .{ .code = written, .source_map = source_map, }, }; } pub fn printCommonJS( comptime Writer: type, _writer: Writer, tree: Ast, symbols: js_ast.Symbol.Map, source: *const logger.Source, comptime ascii_only: bool, opts: Options, comptime generate_source_map: bool, ) !usize { const PrinterType = NewPrinter(ascii_only, Writer, true, false, false, generate_source_map); var writer = _writer; var renamer = rename.NoOpRenamer.init(symbols, source); var printer = PrinterType.init( writer, tree.import_records.slice(), opts, renamer.toRenamer(), getSourceMapBuilder(if (generate_source_map) .lazy else .disable, false, opts, source, &tree), ); defer { imported_module_ids_list = printer.imported_module_ids; } if (tree.prepend_part) |part| { for (part.stmts) |stmt| { try printer.printStmt(stmt); if (printer.writer.getError()) {} else |err| { return err; } printer.printSemicolonIfNeeded(); } } for (tree.parts.slice()) |part| { for (part.stmts) |stmt| { try printer.printStmt(stmt); if (printer.writer.getError()) {} else |err| { return err; } printer.printSemicolonIfNeeded(); } } // Add a couple extra newlines at the end printer.writer.print(@TypeOf("\n\n"), "\n\n"); if (comptime generate_source_map) { if (opts.source_map_handler) |handler| { try handler.onSourceMapChunk(printer.source_map_builder.generateChunk(printer.writer.ctx.getWritten()), source.*); } } try printer.writer.done(); return @as(usize, @intCast(@max(printer.writer.written, 0))); }