mirror of
https://github.com/oven-sh/bun
synced 2026-02-16 22:01:47 +00:00
When --minify-syntax is enabled, transform arrow functions of the form `() => obj.method()` into `obj.method.bind(obj)`. This reduces closure allocation overhead by using the more optimized `.bind()` mechanism. The optimization uses a two-phase approach: 1. During visiting, mark eligible arrows with the receiver's symbol ref 2. During printing, check if the symbol was ever assigned to and only apply the transformation if not This allows the optimization to work with: - const bindings (never reassigned by definition) - let/var bindings that are never reassigned in practice - Function parameters that are never reassigned The optimization is NOT applied when: - The arrow has parameters - The arrow is async - The call has arguments - Optional chaining is used anywhere - The receiver is an unbound global (could be reassigned externally) - The receiver symbol is assigned to anywhere in the code Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
6408 lines
253 KiB
Zig
6408 lines
253 KiB
Zig
const hex_chars = "0123456789ABCDEF";
|
|
const first_ascii = 0x20;
|
|
const last_ascii = 0x7E;
|
|
const first_high_surrogate = 0xD800;
|
|
const first_low_surrogate = 0xDC00;
|
|
const last_low_surrogate = 0xDFFF;
|
|
|
|
/// 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 {
|
|
bun.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 != '"' and c != '\'' and c != '`' and c != '$';
|
|
} else {
|
|
return !ascii_only and c != 0xFEFF and c != 0x2028 and c != 0x2029 and (c < first_high_surrogate or c > last_low_surrogate);
|
|
}
|
|
}
|
|
|
|
const indentation_space_buf = [_]u8{' '} ** 128;
|
|
const indentation_tab_buf = [_]u8{'\t'} ** 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 i: usize = 0;
|
|
while (i < @min(str.len, 1024)) {
|
|
switch (str[i]) {
|
|
'\'' => {
|
|
single_cost += 1;
|
|
},
|
|
'"' => {
|
|
double_cost += 1;
|
|
},
|
|
'`' => {
|
|
backtick_cost += 1;
|
|
},
|
|
'\n' => {
|
|
single_cost += 1;
|
|
double_cost += 1;
|
|
},
|
|
'\\' => {
|
|
i += 1;
|
|
},
|
|
'$' => {
|
|
if (i + 1 < str.len and str[i + 1] == '{') {
|
|
backtick_cost += 1;
|
|
}
|
|
},
|
|
else => {},
|
|
}
|
|
i += 1;
|
|
}
|
|
|
|
if (allow_backtick and backtick_cost < @min(single_cost, double_cost)) {
|
|
return '`';
|
|
}
|
|
if (single_cost < double_cost) {
|
|
return '\'';
|
|
}
|
|
return '"';
|
|
}
|
|
|
|
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 = std.mem.zeroes([str.len]u8);
|
|
var buf_i: usize = 0;
|
|
for (str) |c| {
|
|
if (c != ' ') {
|
|
buf[buf_i] = c;
|
|
buf_i += 1;
|
|
}
|
|
}
|
|
const final = buf[0..buf_i].*;
|
|
break :brk &final;
|
|
};
|
|
};
|
|
|
|
return .{ .normal = Static.with, .minify = Static.without };
|
|
}
|
|
|
|
pub fn estimateLengthForUTF8(input: []const u8, comptime ascii_only: bool, comptime quote_char: u8) usize {
|
|
var remaining = input;
|
|
var len: usize = 2; // for quotes
|
|
|
|
while (strings.indexOfNeedsEscapeForJavaScriptString(remaining, quote_char)) |i| {
|
|
len += i;
|
|
remaining = remaining[i..];
|
|
const char_len = strings.wtf8ByteSequenceLengthWithInvalid(remaining[0]);
|
|
const c = strings.decodeWTF8RuneT(
|
|
&switch (char_len) {
|
|
// 0 is not returned by `wtf8ByteSequenceLengthWithInvalid`
|
|
1 => .{ remaining[0], 0, 0, 0 },
|
|
2 => remaining[0..2].* ++ .{ 0, 0 },
|
|
3 => remaining[0..3].* ++ .{0},
|
|
4 => remaining[0..4].*,
|
|
else => unreachable,
|
|
},
|
|
char_len,
|
|
i32,
|
|
0,
|
|
);
|
|
if (canPrintWithoutEscape(i32, c, ascii_only)) {
|
|
len += @as(usize, char_len);
|
|
} else if (c <= 0xFFFF) {
|
|
len += 6;
|
|
} else {
|
|
len += 12;
|
|
}
|
|
remaining = remaining[char_len..];
|
|
} else {
|
|
return remaining.len + 2;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
pub fn writePreQuotedString(text_in: []const u8, comptime Writer: type, writer: Writer, comptime quote_char: u8, comptime ascii_only: bool, comptime json: bool, comptime encoding: strings.Encoding) !void {
|
|
const text = if (comptime encoding == .utf16) @as([]const u16, @alignCast(std.mem.bytesAsSlice(u16, text_in))) else text_in;
|
|
if (comptime json and quote_char != '"') @compileError("for json, quote_char must be '\"'");
|
|
var i: usize = 0;
|
|
const n: usize = text.len;
|
|
while (i < n) {
|
|
const width = switch (comptime encoding) {
|
|
.latin1, .ascii => 1,
|
|
.utf8 => strings.wtf8ByteSequenceLengthWithInvalid(text[i]),
|
|
.utf16 => 1,
|
|
};
|
|
const clamped_width = @min(@as(usize, width), n -| i);
|
|
const c = switch (encoding) {
|
|
.utf8 => strings.decodeWTF8RuneT(
|
|
&switch (clamped_width) {
|
|
// 0 is not returned by `wtf8ByteSequenceLengthWithInvalid`
|
|
1 => .{ text[i], 0, 0, 0 },
|
|
2 => text[i..][0..2].* ++ .{ 0, 0 },
|
|
3 => text[i..][0..3].* ++ .{0},
|
|
4 => text[i..][0..4].*,
|
|
else => unreachable,
|
|
},
|
|
width,
|
|
i32,
|
|
0,
|
|
),
|
|
.ascii => brk: {
|
|
std.debug.assert(text[i] <= 0x7F);
|
|
break :brk text[i];
|
|
},
|
|
.latin1 => text[i],
|
|
.utf16 => brk: {
|
|
// TODO: if this is a part of a surrogate pair, we could parse the whole codepoint in order
|
|
// to emit it as a single \u{result} rather than two paired \uLOW\uHIGH.
|
|
// eg: "\u{10334}" will convert to "\uD800\uDF34" without this.
|
|
break :brk @as(i32, text[i]);
|
|
},
|
|
};
|
|
if (canPrintWithoutEscape(i32, c, ascii_only)) {
|
|
const remain = text[i + clamped_width ..];
|
|
|
|
switch (encoding) {
|
|
.ascii, .utf8 => {
|
|
if (strings.indexOfNeedsEscapeForJavaScriptString(remain, quote_char)) |j| {
|
|
const text_chunk = text[i .. i + clamped_width];
|
|
try writer.writeAll(text_chunk);
|
|
i += clamped_width;
|
|
try writer.writeAll(remain[0..j]);
|
|
i += j;
|
|
} else {
|
|
try writer.writeAll(text[i..]);
|
|
i = n;
|
|
break;
|
|
}
|
|
},
|
|
.latin1, .utf16 => {
|
|
var codepoint_bytes: [4]u8 = undefined;
|
|
const codepoint_len = strings.encodeWTF8Rune(codepoint_bytes[0..4], c);
|
|
try writer.writeAll(codepoint_bytes[0..codepoint_len]);
|
|
i += clamped_width;
|
|
},
|
|
}
|
|
continue;
|
|
}
|
|
switch (c) {
|
|
0x07 => {
|
|
try writer.writeAll("\\x07");
|
|
i += 1;
|
|
},
|
|
0x08 => {
|
|
try writer.writeAll("\\b");
|
|
i += 1;
|
|
},
|
|
0x0C => {
|
|
try writer.writeAll("\\f");
|
|
i += 1;
|
|
},
|
|
'\n' => {
|
|
if (quote_char == '`') {
|
|
try writer.writeAll("\n");
|
|
} else {
|
|
try writer.writeAll("\\n");
|
|
}
|
|
i += 1;
|
|
},
|
|
std.ascii.control_code.cr => {
|
|
try writer.writeAll("\\r");
|
|
i += 1;
|
|
},
|
|
// \v
|
|
std.ascii.control_code.vt => {
|
|
try writer.writeAll("\\v");
|
|
i += 1;
|
|
},
|
|
// "\\"
|
|
'\\' => {
|
|
try writer.writeAll("\\\\");
|
|
i += 1;
|
|
},
|
|
'"' => {
|
|
if (quote_char == '"') {
|
|
try writer.writeAll("\\\"");
|
|
} else {
|
|
try writer.writeAll("\"");
|
|
}
|
|
i += 1;
|
|
},
|
|
'\'' => {
|
|
if (quote_char == '\'') {
|
|
try writer.writeAll("\\'");
|
|
} else {
|
|
try writer.writeAll("'");
|
|
}
|
|
i += 1;
|
|
},
|
|
'`' => {
|
|
if (quote_char == '`') {
|
|
try writer.writeAll("\\`");
|
|
} else {
|
|
try writer.writeAll("`");
|
|
}
|
|
i += 1;
|
|
},
|
|
'$' => {
|
|
if (quote_char == '`') {
|
|
const remain = text[i + clamped_width ..];
|
|
if (remain.len > 0 and remain[0] == '{') {
|
|
try writer.writeAll("\\$");
|
|
} else {
|
|
try writer.writeAll("$");
|
|
}
|
|
} else {
|
|
try writer.writeAll("$");
|
|
}
|
|
i += 1;
|
|
},
|
|
|
|
'\t' => {
|
|
if (quote_char == '`') {
|
|
try writer.writeAll("\t");
|
|
} else {
|
|
try writer.writeAll("\\t");
|
|
}
|
|
i += 1;
|
|
},
|
|
|
|
else => {
|
|
i += @as(usize, width);
|
|
|
|
if (c <= 0xFF and !json) {
|
|
const k = @as(usize, @intCast(c));
|
|
|
|
try writer.writeAll(&[_]u8{
|
|
'\\',
|
|
'x',
|
|
hex_chars[(k >> 4) & 0xF],
|
|
hex_chars[k & 0xF],
|
|
});
|
|
} else 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],
|
|
});
|
|
}
|
|
},
|
|
}
|
|
}
|
|
}
|
|
pub fn quoteForJSON(text: []const u8, bytes: *MutableString, comptime ascii_only: bool) !void {
|
|
const writer = bytes.writer();
|
|
|
|
try bytes.growIfNeeded(estimateLengthForUTF8(text, ascii_only, '"'));
|
|
try bytes.appendChar('"');
|
|
try writePreQuotedString(text, @TypeOf(writer), writer, '"', ascii_only, true, .utf8);
|
|
bytes.appendChar('"') catch unreachable;
|
|
}
|
|
|
|
pub fn writeJSONString(input: []const u8, comptime Writer: type, writer: Writer, comptime encoding: strings.Encoding) !void {
|
|
try writer.writeAll("\"");
|
|
try writePreQuotedString(input, Writer, writer, '"', false, true, encoding);
|
|
try writer.writeAll("\"");
|
|
}
|
|
|
|
pub const SourceMapHandler = struct {
|
|
ctx: *anyopaque,
|
|
callback: Callback,
|
|
|
|
const Callback = *const fn (*anyopaque, chunk: SourceMap.Chunk, source: *const logger.Source) anyerror!void;
|
|
pub fn onSourceMapChunk(self: *const @This(), chunk: SourceMap.Chunk, source: *const 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: *const logger.Source) anyerror!void)) type {
|
|
return struct {
|
|
pub fn onChunk(self: *anyopaque, chunk: SourceMap.Chunk, source: *const 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 {
|
|
bundling: bool = false,
|
|
transform_imports: bool = true,
|
|
to_commonjs_ref: Ref = Ref.None,
|
|
to_esm_ref: Ref = Ref.None,
|
|
require_ref: ?Ref = null,
|
|
import_meta_ref: Ref = Ref.None,
|
|
hmr_ref: Ref = Ref.None,
|
|
indent: Indentation = .{},
|
|
runtime_imports: runtime.Runtime.Imports = runtime.Runtime.Imports{},
|
|
module_hash: u32 = 0,
|
|
source_path: ?fs.Path = null,
|
|
allocator: std.mem.Allocator = default_allocator,
|
|
source_map_allocator: ?std.mem.Allocator = null,
|
|
source_map_handler: ?SourceMapHandler = null,
|
|
source_map_builder: ?*bun.SourceMap.Chunk.Builder = null,
|
|
css_import_behavior: api.CssInJsBehavior = api.CssInJsBehavior.facade,
|
|
target: options.Target = .browser,
|
|
|
|
runtime_transpiler_cache: ?*bun.jsc.RuntimeTranspilerCache = null,
|
|
module_info: ?*analyze_transpiled_module.ModuleInfo = null,
|
|
input_files_for_dev_server: ?[]logger.Source = null,
|
|
|
|
commonjs_named_exports: js_ast.Ast.CommonJSNamedExports = .{},
|
|
commonjs_named_exports_deoptimized: bool = false,
|
|
commonjs_module_exports_assigned_deoptimized: bool = false,
|
|
commonjs_named_exports_ref: Ref = Ref.None,
|
|
commonjs_module_ref: Ref = Ref.None,
|
|
|
|
minify_whitespace: bool = false,
|
|
minify_identifiers: bool = false,
|
|
minify_syntax: bool = false,
|
|
print_dce_annotations: bool = true,
|
|
|
|
transform_only: bool = false,
|
|
inline_require_and_import_errors: bool = true,
|
|
has_run_symbol_renamer: bool = false,
|
|
|
|
require_or_import_meta_for_source_callback: RequireOrImportMeta.Callback = .{},
|
|
|
|
/// The module type of the importing file (after linking), used to determine interop helper behavior.
|
|
/// Controls whether __toESM uses Node ESM semantics (isNodeMode=1 for .esm) or respects __esModule markers.
|
|
input_module_type: options.ModuleType = .unknown,
|
|
module_type: options.Format = .esm,
|
|
|
|
// /// Used for cross-module inlining of import items when bundling
|
|
// const_values: Ast.ConstValuesMap = .{},
|
|
ts_enums: Ast.TsEnumsMap = .{},
|
|
|
|
// 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,
|
|
|
|
mangled_props: ?*const bun.bundle_v2.MangledProps,
|
|
|
|
// Default indentation is 2 spaces
|
|
pub const Indentation = struct {
|
|
scalar: usize = 2,
|
|
count: usize = 0,
|
|
character: Character = .space,
|
|
|
|
pub const Character = enum { tab, space };
|
|
};
|
|
|
|
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)),
|
|
};
|
|
}
|
|
};
|
|
};
|
|
|
|
fn isIdentifierOrNumericConstantOrPropertyAccess(expr: *const Expr) bool {
|
|
return switch (expr.data) {
|
|
.e_identifier, .e_dot, .e_index => true,
|
|
.e_number => |e| std.math.isInf(e.value) or std.math.isNan(e.value),
|
|
else => false,
|
|
};
|
|
}
|
|
|
|
pub const PrintResult = union(enum) {
|
|
result: Success,
|
|
err: anyerror,
|
|
|
|
pub const Success = struct {
|
|
code: []u8,
|
|
source_map: ?SourceMap.Chunk = null,
|
|
};
|
|
};
|
|
|
|
// 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 struct implementation
|
|
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.flags.contains_import_star) {
|
|
variant = variant.hasStar();
|
|
}
|
|
|
|
if (!record.flags.was_originally_bare_import) {
|
|
if (!record.flags.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,
|
|
|
|
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) = .{},
|
|
|
|
binary_expression_stack: std.array_list.Managed(BinaryExpressionVisitor) = undefined,
|
|
|
|
was_lazy_export: bool = false,
|
|
module_info: if (!may_have_module_info) void else ?*analyze_transpiled_module.ModuleInfo = if (!may_have_module_info) {} else null,
|
|
|
|
const Printer = @This();
|
|
|
|
const may_have_module_info = is_bun_platform and !rewrite_esm_to_cjs;
|
|
const TopLevelAndIsExport = if (!may_have_module_info) struct {} else struct {
|
|
is_export: bool = false,
|
|
is_top_level: ?analyze_transpiled_module.ModuleInfo.VarKind = null,
|
|
};
|
|
const TopLevel = if (!may_have_module_info) struct {
|
|
pub inline fn init(_: IsTopLevel) @This() {
|
|
return .{};
|
|
}
|
|
pub inline fn subVar(_: @This()) @This() {
|
|
return .{};
|
|
}
|
|
pub inline fn isTopLevel(_: @This()) bool {
|
|
return false;
|
|
}
|
|
} else struct {
|
|
is_top_level: IsTopLevel = .no,
|
|
pub inline fn init(is_top_level: IsTopLevel) @This() {
|
|
return .{ .is_top_level = is_top_level };
|
|
}
|
|
pub fn subVar(self: @This()) @This() {
|
|
if (self.is_top_level == .no) return @This().init(.no);
|
|
return @This().init(.var_only);
|
|
}
|
|
pub inline fn isTopLevel(self: @This()) bool {
|
|
return self.is_top_level != .no;
|
|
}
|
|
};
|
|
const IsTopLevel = enum { yes, var_only, no };
|
|
inline fn moduleInfo(self: *const Printer) ?*analyze_transpiled_module.ModuleInfo {
|
|
if (!may_have_module_info) return null;
|
|
return self.module_info;
|
|
}
|
|
|
|
/// When Printer is used as a io.Writer, this represents it's error type, aka nothing.
|
|
pub const Error = error{};
|
|
|
|
/// The handling of binary expressions is convoluted because we're using
|
|
/// iteration on the heap instead of recursion on the call stack to avoid
|
|
/// stack overflow for deeply-nested ASTs. See the comments for the similar
|
|
/// code in the JavaScript parser for details.
|
|
pub const BinaryExpressionVisitor = struct {
|
|
// Inputs
|
|
e: *E.Binary,
|
|
level: Level = .lowest,
|
|
flags: ExprFlag.Set = ExprFlag.None(),
|
|
|
|
// Input for visiting the left child
|
|
left_level: Level = .lowest,
|
|
left_flags: ExprFlag.Set = ExprFlag.None(),
|
|
|
|
// "Local variables" passed from "checkAndPrepare" to "visitRightAndFinish"
|
|
entry: *const Op = undefined,
|
|
wrap: bool = false,
|
|
right_level: Level = .lowest,
|
|
|
|
pub fn checkAndPrepare(v: *BinaryExpressionVisitor, p: *Printer) bool {
|
|
var e = v.e;
|
|
|
|
const entry: *const Op = Op.Table.getPtrConst(e.op);
|
|
const e_level = entry.level;
|
|
v.entry = entry;
|
|
v.wrap = v.level.gte(e_level) or (e.op == Op.Code.bin_in and v.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 => {
|
|
v.wrap = true;
|
|
},
|
|
else => {},
|
|
}
|
|
}
|
|
|
|
if (v.wrap) {
|
|
p.print("(");
|
|
v.flags.insert(.forbid_in);
|
|
}
|
|
|
|
v.left_level = e_level.sub(1);
|
|
v.right_level = e_level.sub(1);
|
|
const left_level = &v.left_level;
|
|
const right_level = &v.right_level;
|
|
|
|
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;
|
|
},
|
|
.e_boolean, .e_branch_boolean => {
|
|
// When minifying, booleans are printed as "!0 and "!1"
|
|
if (p.options.minify_syntax) {
|
|
left_level.* = .call;
|
|
}
|
|
},
|
|
else => {},
|
|
}
|
|
},
|
|
else => {},
|
|
}
|
|
|
|
// Special-case "#foo in bar"
|
|
if (e.left.data == .e_private_identifier and e.op == .bin_in) {
|
|
const private = e.left.data.e_private_identifier;
|
|
const name = p.renamer.nameForSymbol(private.ref);
|
|
p.addSourceMappingForName(e.left.loc, name, private.ref);
|
|
p.printIdentifier(name);
|
|
v.visitRightAndFinish(p);
|
|
return false;
|
|
}
|
|
|
|
v.left_flags = ExprFlag.Set{};
|
|
|
|
if (v.flags.contains(.forbid_in)) {
|
|
v.left_flags.insert(.forbid_in);
|
|
}
|
|
|
|
if (e.op == .bin_comma)
|
|
v.left_flags.insert(.expr_result_is_unused);
|
|
|
|
return true;
|
|
}
|
|
pub fn visitRightAndFinish(v: *BinaryExpressionVisitor, p: *Printer) void {
|
|
const e = v.e;
|
|
const entry = v.entry;
|
|
var flags = ExprFlag.Set{};
|
|
|
|
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();
|
|
|
|
// The result of the right operand of the comma operator is unused if the caller doesn't use it
|
|
if (e.op == .bin_comma and v.flags.contains(.expr_result_is_unused)) {
|
|
flags.insert(.expr_result_is_unused);
|
|
}
|
|
|
|
if (v.flags.contains(.forbid_in)) {
|
|
flags.insert(.forbid_in);
|
|
}
|
|
|
|
p.printExpr(e.right, v.right_level, flags);
|
|
|
|
if (v.wrap) {
|
|
p.print(")");
|
|
}
|
|
}
|
|
};
|
|
|
|
pub fn writeAll(p: *Printer, bytes: anytype) anyerror!void {
|
|
p.print(bytes);
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
pub fn writeBytesNTimes(self: *Printer, bytes: []const u8, n: usize) anyerror!void {
|
|
var i: usize = 0;
|
|
while (i < n) : (i += 1) {
|
|
try self.writeAll(bytes);
|
|
}
|
|
}
|
|
|
|
fn fmt(p: *Printer, comptime str: string, args: anytype) !void {
|
|
const len = @call(
|
|
bun.callmod_inline,
|
|
std.fmt.count,
|
|
.{ str, args },
|
|
);
|
|
var ptr = try p.writer.reserve(len);
|
|
|
|
const written = @call(
|
|
bun.callmod_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 unindent(p: *Printer) void {
|
|
p.options.indent.count -|= 1;
|
|
}
|
|
|
|
pub inline fn indent(p: *Printer) void {
|
|
p.options.indent.count += 1;
|
|
}
|
|
|
|
pub fn printIndent(p: *Printer) void {
|
|
if (p.options.indent.count == 0 or p.options.minify_whitespace) {
|
|
return;
|
|
}
|
|
|
|
const indentation_buf = switch (p.options.indent.character) {
|
|
.space => indentation_space_buf,
|
|
.tab => indentation_tab_buf,
|
|
};
|
|
|
|
var i: usize = p.options.indent.count * p.options.indent.scalar;
|
|
|
|
while (i > 0) {
|
|
const amt = @min(i, indentation_buf.len);
|
|
p.print(indentation_buf[0..amt]);
|
|
i -= amt;
|
|
}
|
|
}
|
|
|
|
pub fn mangledPropName(p: *Printer, _ref: Ref) string {
|
|
const ref = p.symbols().follow(_ref);
|
|
// TODO: we don't support that
|
|
if (p.options.mangled_props != null) {
|
|
if (p.options.mangled_props.?.get(ref)) |name| return name;
|
|
}
|
|
return p.renamer.nameForSymbol(ref);
|
|
}
|
|
|
|
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 printGlobalBunImportStatement(p: *Printer, import: S.Import) void {
|
|
if (comptime !is_bun_platform) unreachable;
|
|
printInternalBunImport(p, import, @TypeOf("globalThis.Bun"), "globalThis.Bun");
|
|
}
|
|
|
|
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,
|
|
&.{},
|
|
Expr.empty,
|
|
Level.lowest,
|
|
ExprFlag.None(),
|
|
);
|
|
} else {
|
|
p.print(statement);
|
|
}
|
|
|
|
p.printSemicolonAfterStatement();
|
|
p.printIndent();
|
|
}
|
|
|
|
if (import.default_name) |default| {
|
|
p.printSemicolonIfNeeded();
|
|
p.print("var ");
|
|
p.printSymbol(default.ref.?);
|
|
if (comptime Statement == void) {
|
|
p.@"print = "();
|
|
p.printRequireOrImportExpr(
|
|
import.import_record_index,
|
|
false,
|
|
&.{},
|
|
Expr.empty,
|
|
Level.lowest,
|
|
ExprFlag.None(),
|
|
);
|
|
} else {
|
|
p.@"print = "();
|
|
p.print(statement);
|
|
}
|
|
p.printSemicolonAfterStatement();
|
|
}
|
|
|
|
if (import.items.len > 0) {
|
|
p.printSemicolonIfNeeded();
|
|
p.printWhitespacer(ws("var {"));
|
|
|
|
if (!import.is_single_line) {
|
|
p.printNewline();
|
|
p.indent();
|
|
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.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, &.{}, Expr.empty, 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();
|
|
}
|
|
|
|
// Record var declarations for module_info. printGlobalBunImportStatement
|
|
// bypasses printDeclStmt/printBinding, so we must record vars explicitly.
|
|
if (may_have_module_info) {
|
|
if (p.moduleInfo()) |mi| {
|
|
if (import.star_name_loc != null) {
|
|
const name = p.renamer.nameForSymbol(import.namespace_ref);
|
|
bun.handleOom(mi.addVar(bun.handleOom(mi.str(name)), .declared));
|
|
}
|
|
if (import.default_name) |default| {
|
|
const name = p.renamer.nameForSymbol(default.ref.?);
|
|
bun.handleOom(mi.addVar(bun.handleOom(mi.str(name)), .declared));
|
|
}
|
|
for (import.items) |item| {
|
|
const name = p.renamer.nameForSymbol(item.name.ref.?);
|
|
bun.handleOom(mi.addVar(bun.handleOom(mi.str(name)), .declared));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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.printSpaceBeforeIdentifier();
|
|
p.addSourceMapping(loc);
|
|
p.print("undefined");
|
|
}
|
|
}
|
|
|
|
pub fn printBody(p: *Printer, stmt: Stmt, tlmtlo: TopLevel) void {
|
|
switch (stmt.data) {
|
|
.s_block => |block| {
|
|
p.printSpace();
|
|
p.printBlock(stmt.loc, block.stmts, block.close_brace_loc, tlmtlo);
|
|
p.printNewline();
|
|
},
|
|
else => {
|
|
p.printNewline();
|
|
p.indent();
|
|
p.printStmt(stmt, tlmtlo) catch unreachable;
|
|
p.unindent();
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn printBlockBody(p: *Printer, stmts: []const Stmt, tlmtlo: TopLevel) void {
|
|
for (stmts) |stmt| {
|
|
p.printSemicolonIfNeeded();
|
|
p.printStmt(stmt, tlmtlo) catch unreachable;
|
|
}
|
|
}
|
|
|
|
pub fn printBlock(p: *Printer, loc: logger.Loc, stmts: []const Stmt, close_brace_loc: ?logger.Loc, tlmtlo: TopLevel) void {
|
|
p.addSourceMapping(loc);
|
|
p.print("{");
|
|
if (stmts.len > 0) {
|
|
@branchHint(.likely);
|
|
p.printNewline();
|
|
|
|
p.indent();
|
|
p.printBlockBody(stmts, tlmtlo);
|
|
p.unindent();
|
|
|
|
p.printIndent();
|
|
}
|
|
if (close_brace_loc != null and close_brace_loc.?.start > loc.start) {
|
|
p.addSourceMapping(close_brace_loc.?);
|
|
}
|
|
p.print("}");
|
|
|
|
p.needs_semicolon = false;
|
|
}
|
|
|
|
pub fn printTwoBlocksInOne(p: *Printer, loc: logger.Loc, stmts: []const Stmt, prepend: []const Stmt) void {
|
|
p.addSourceMapping(loc);
|
|
p.print("{");
|
|
p.printNewline();
|
|
|
|
p.indent();
|
|
p.printBlockBody(prepend, TopLevel.init(.no));
|
|
p.printBlockBody(stmts, TopLevel.init(.no));
|
|
p.unindent();
|
|
p.needs_semicolon = false;
|
|
|
|
p.printIndent();
|
|
p.print("}");
|
|
}
|
|
|
|
pub fn printDecls(p: *Printer, comptime keyword: string, decls_: []G.Decl, flags: ExprFlag.Set, tlm: TopLevelAndIsExport) 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, tlm);
|
|
}
|
|
|
|
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, tlm);
|
|
|
|
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, tlm);
|
|
|
|
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, _: string, _: Ref) void {
|
|
if (comptime !generate_source_map) {
|
|
return;
|
|
}
|
|
// TODO: esbuild does this to make the source map more accurate with E.NameOfSymbol
|
|
// if (printer.symbols().get(printer.symbols().follow(ref))) |original_symbol| {
|
|
// if (!bun.strings.eql( original_symbol.original_name, name)) {
|
|
// printer.source_map_builder.addSourceMapping(location, originalName);
|
|
// return;
|
|
// }
|
|
// }
|
|
printer.addSourceMapping(location);
|
|
}
|
|
|
|
pub fn printSymbol(p: *Printer, ref: Ref) void {
|
|
bun.assert(!ref.isNull()); // Invalid Symbol
|
|
const name = p.renamer.nameForSymbol(ref);
|
|
|
|
p.printIdentifier(name);
|
|
}
|
|
pub fn printClauseAlias(p: *Printer, alias: string) void {
|
|
bun.assert(alias.len > 0);
|
|
|
|
if (!strings.containsNonBmpCodePointOrIsInvalidIdentifier(alias)) {
|
|
p.printSpaceBeforeIdentifier();
|
|
p.printIdentifier(alias);
|
|
} else {
|
|
p.printStringLiteralUTF8(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, TopLevel.init(.no));
|
|
}
|
|
|
|
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.indent();
|
|
|
|
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, TopLevel.init(.no));
|
|
p.printNewline();
|
|
continue;
|
|
}
|
|
|
|
p.printProperty(item);
|
|
|
|
if (item.value == null) {
|
|
p.printSemicolonAfterStatement();
|
|
} else {
|
|
p.printNewline();
|
|
}
|
|
}
|
|
|
|
p.needs_semicolon = false;
|
|
p.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 => {
|
|
const 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 => {
|
|
const 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 => {
|
|
const 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 => {
|
|
const buf: *[5]u8 = (p.writer.reserve(5) catch unreachable)[0..5];
|
|
formatUnsignedIntegerBetween(5, buf, val);
|
|
p.writer.advance(5);
|
|
},
|
|
100001...999999 => {
|
|
const 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 => {
|
|
const 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 => {
|
|
const 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 => {
|
|
const 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 => {
|
|
const buf: *[10]u8 = (p.writer.reserve(10) catch unreachable)[0..10];
|
|
formatUnsignedIntegerBetween(10, buf, val);
|
|
p.writer.advance(10);
|
|
},
|
|
else => p.fmt("{d}", .{val}) catch unreachable,
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
p.fmt("{d}", .{float}) catch {};
|
|
}
|
|
|
|
pub fn printStringCharactersUTF8(e: *Printer, text: []const u8, quote: u8) void {
|
|
const writer = e.writer.stdWriter();
|
|
(switch (quote) {
|
|
'\'' => writePreQuotedString(text, @TypeOf(writer), writer, '\'', ascii_only, false, .utf8),
|
|
'"' => writePreQuotedString(text, @TypeOf(writer), writer, '"', ascii_only, false, .utf8),
|
|
'`' => writePreQuotedString(text, @TypeOf(writer), writer, '`', ascii_only, false, .utf8),
|
|
else => unreachable,
|
|
}) catch |err| switch (err) {};
|
|
}
|
|
pub fn printStringCharactersUTF16(e: *Printer, text: []const u16, quote: u8) void {
|
|
const slice = std.mem.sliceAsBytes(text);
|
|
|
|
const writer = e.writer.stdWriter();
|
|
(switch (quote) {
|
|
'\'' => writePreQuotedString(slice, @TypeOf(writer), writer, '\'', ascii_only, false, .utf16),
|
|
'"' => writePreQuotedString(slice, @TypeOf(writer), writer, '"', ascii_only, false, .utf16),
|
|
'`' => writePreQuotedString(slice, @TypeOf(writer), writer, '`', ascii_only, false, .utf16),
|
|
else => unreachable,
|
|
}) catch |err| switch (err) {};
|
|
}
|
|
|
|
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.printStringLiteralUTF8(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 isUnboundIdentifier(p: *Printer, expr: *const Expr) bool {
|
|
if (expr.data != .e_identifier) return false;
|
|
const ref = expr.data.e_identifier.ref;
|
|
const symbol = p.symbols().get(p.symbols().follow(ref)) orelse return false;
|
|
return symbol.kind == .unbound;
|
|
}
|
|
|
|
pub fn printRequireOrImportExpr(
|
|
p: *Printer,
|
|
import_record_index: u32,
|
|
was_unwrapped_require: bool,
|
|
leading_interior_comments: []G.Comment,
|
|
import_options: Expr,
|
|
level_: Level,
|
|
flags: ExprFlag.Set,
|
|
) void {
|
|
_ = leading_interior_comments; // TODO:
|
|
|
|
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);
|
|
const module_type = p.options.module_type;
|
|
|
|
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
|
|
//
|
|
switch (record.tag) {
|
|
.bun => {
|
|
if (record.kind == .dynamic) {
|
|
p.print("Promise.resolve(globalThis.Bun)");
|
|
return;
|
|
} else if (record.kind == .require or record.kind == .stmt) {
|
|
p.print("globalThis.Bun");
|
|
return;
|
|
}
|
|
},
|
|
else => {},
|
|
}
|
|
}
|
|
|
|
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 properly wrapped
|
|
const wrap_comma_operator = meta.exports_ref.isValid() and
|
|
meta.wrapper_ref.isValid() and
|
|
level.gte(.comma);
|
|
if (wrap_comma_operator) p.print("(");
|
|
defer if (wrap_comma_operator) p.print(")");
|
|
|
|
// Wrap this with a call to "__toESM()" if this is a CommonJS file
|
|
const wrap_with_to_esm = record.flags.wrap_with_to_esm;
|
|
if (wrap_with_to_esm) {
|
|
p.printSpaceBeforeIdentifier();
|
|
p.printSymbol(p.options.to_esm_ref);
|
|
p.print("(");
|
|
}
|
|
|
|
if (p.options.input_files_for_dev_server) |input_files| {
|
|
bun.assert(module_type == .internal_bake_dev);
|
|
p.printSpaceBeforeIdentifier();
|
|
p.printSymbol(p.options.hmr_ref);
|
|
p.print(".require(");
|
|
const path = input_files[record.source_index.get()].path;
|
|
p.printStringLiteralUTF8(path.pretty, false);
|
|
p.print(")");
|
|
} else if (!meta.was_unwrapped_require) {
|
|
// Call the wrapper
|
|
if (meta.wrapper_ref.isValid()) {
|
|
p.printSpaceBeforeIdentifier();
|
|
p.printSymbol(meta.wrapper_ref);
|
|
p.print("()");
|
|
|
|
if (meta.exports_ref.isValid()) {
|
|
p.print(",");
|
|
p.printSpace();
|
|
}
|
|
}
|
|
|
|
// Return the namespace object if this is an ESM file
|
|
if (meta.exports_ref.isValid()) {
|
|
// Wrap this with a call to "__toCommonJS()" if this is an ESM file
|
|
const wrap_with_to_cjs = record.flags.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.input_module_type == .esm) {
|
|
p.print(",");
|
|
p.printSpace();
|
|
p.print("1");
|
|
}
|
|
p.print(")");
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// External "require()"
|
|
if (record.kind != .dynamic) {
|
|
p.printSpaceBeforeIdentifier();
|
|
|
|
if (p.options.inline_require_and_import_errors) {
|
|
if (record.path.is_disabled and record.flags.handles_import_errors) {
|
|
p.printRequireError(record.path.text);
|
|
return;
|
|
}
|
|
|
|
if (record.path.is_disabled) {
|
|
p.printDisabledImport();
|
|
return;
|
|
}
|
|
}
|
|
|
|
const wrap_with_to_esm = record.flags.wrap_with_to_esm;
|
|
|
|
if (module_type == .internal_bake_dev) {
|
|
p.printSpaceBeforeIdentifier();
|
|
p.printSymbol(p.options.hmr_ref);
|
|
if (record.tag == .builtin)
|
|
p.print(".builtin(")
|
|
else
|
|
p.print(".require(");
|
|
const path = record.path;
|
|
p.printStringLiteralUTF8(path.pretty, false);
|
|
p.print(")");
|
|
return;
|
|
} else if (wrap_with_to_esm) {
|
|
p.printSpaceBeforeIdentifier();
|
|
p.printSymbol(p.options.to_esm_ref);
|
|
p.print("(");
|
|
}
|
|
|
|
if (p.options.require_ref) |ref| {
|
|
p.printSymbol(ref);
|
|
} else {
|
|
p.print("require");
|
|
}
|
|
|
|
p.print("(");
|
|
p.printImportRecordPath(record);
|
|
p.print(")");
|
|
|
|
if (wrap_with_to_esm) {
|
|
p.print(")");
|
|
}
|
|
return;
|
|
}
|
|
|
|
// External import()
|
|
// if (leading_interior_comments.len > 0) {
|
|
// p.printNewline();
|
|
// p.indent();
|
|
// for (leading_interior_comments) |comment| {
|
|
// p.printIndentedComment(comment.text);
|
|
// }
|
|
// p.printIndent();
|
|
// }
|
|
p.addSourceMapping(record.range.loc);
|
|
|
|
p.printSpaceBeforeIdentifier();
|
|
|
|
// Wrap with __toESM if importing a CommonJS module
|
|
const wrap_with_to_esm = record.flags.wrap_with_to_esm;
|
|
|
|
// Allow it to fail at runtime, if it should
|
|
if (module_type != .internal_bake_dev) {
|
|
p.print("import(");
|
|
p.printImportRecordPath(record);
|
|
} else {
|
|
p.printSymbol(p.options.hmr_ref);
|
|
p.print(".dynamicImport(");
|
|
const path = record.path;
|
|
p.printStringLiteralUTF8(path.pretty, false);
|
|
}
|
|
|
|
if (!import_options.isMissing()) {
|
|
p.printWhitespacer(ws(", "));
|
|
p.printExpr(import_options, .comma, .{});
|
|
}
|
|
|
|
p.print(")");
|
|
|
|
// For CJS modules, unwrap the default export and convert to ESM
|
|
if (wrap_with_to_esm) {
|
|
p.print(".then((m)=>");
|
|
p.printSymbol(p.options.to_esm_ref);
|
|
p.print("(m.default");
|
|
if (p.options.input_module_type == .esm) {
|
|
p.print(",1");
|
|
}
|
|
p.print("))");
|
|
}
|
|
|
|
// if (leading_interior_comments.len > 0) {
|
|
// p.printNewline();
|
|
// p.unindent();
|
|
// p.printIndent();
|
|
// }
|
|
|
|
return;
|
|
}
|
|
|
|
pub inline fn printPure(p: *Printer) void {
|
|
if (p.options.print_dce_annotations) {
|
|
p.printWhitespacer(ws("/* @__PURE__ */ "));
|
|
}
|
|
}
|
|
|
|
pub fn printStringLiteralEString(p: *Printer, str: *E.String, allow_backtick: bool) void {
|
|
const quote = bestQuoteCharForEString(str, allow_backtick);
|
|
p.print(quote);
|
|
p.printStringCharactersEString(str, quote);
|
|
p.print(quote);
|
|
}
|
|
pub fn printStringLiteralUTF8(p: *Printer, str: string, allow_backtick: bool) void {
|
|
if (Environment.allow_assert) std.debug.assert(std.unicode.wtf8ValidateSlice(str));
|
|
|
|
const quote = if (comptime !is_json)
|
|
bestQuoteCharForString(u8, str, allow_backtick)
|
|
else
|
|
'"';
|
|
|
|
p.print(quote);
|
|
p.printStringCharactersUTF8(str, quote);
|
|
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(.enum_literal)) void {
|
|
const name = p.renamer.nameForSymbol(item.name.ref.?);
|
|
|
|
if (comptime as == .import) {
|
|
if (strings.eql(name, item.alias)) {
|
|
p.printIdentifier(name);
|
|
} else {
|
|
p.printClauseAlias(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 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{");
|
|
p.fmt("{x}", .{cursor.c}) catch unreachable;
|
|
p.print("}");
|
|
},
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
if (is_ascii) {
|
|
p.print(bytes[ascii_start..]);
|
|
}
|
|
}
|
|
|
|
pub fn printExpr(p: *Printer, expr: Expr, level: Level, in_flags: ExprFlag.Set) void {
|
|
var flags = in_flags;
|
|
|
|
switch (expr.data) {
|
|
.e_missing => {},
|
|
.e_undefined => {
|
|
p.addSourceMapping(expr.loc);
|
|
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);
|
|
if (p.options.module_type == .internal_bake_dev) {
|
|
bun.assert(p.options.hmr_ref.isValid());
|
|
p.printSymbol(p.options.hmr_ref);
|
|
p.print(".importMeta");
|
|
} else if (!p.options.import_meta_ref.isValid()) {
|
|
// Most of the time, leave it in there
|
|
if (p.moduleInfo()) |mi| mi.flags.contains_import_meta = true;
|
|
p.print("import.meta");
|
|
} else {
|
|
// Note: The bundler will not hit this code path. The bundler will replace
|
|
// the ImportMeta AST node with a regular Identifier AST node.
|
|
//
|
|
// This is currently only used in Bun's runtime for CommonJS modules
|
|
// referencing import.meta
|
|
//
|
|
// TODO: This assertion trips when using `import.meta` with `--format=cjs`
|
|
bun.debugAssert(p.options.module_type == .cjs);
|
|
|
|
p.printSymbol(p.options.import_meta_ref);
|
|
}
|
|
},
|
|
.e_import_meta_main => |data| {
|
|
if (p.options.module_type == .esm and p.options.target != .node) {
|
|
// Node.js doesn't support import.meta.main
|
|
// Most of the time, leave it in there
|
|
if (data.inverted) {
|
|
p.addSourceMapping(expr.loc);
|
|
p.print("!");
|
|
} else {
|
|
p.printSpaceBeforeIdentifier();
|
|
p.addSourceMapping(expr.loc);
|
|
}
|
|
if (p.moduleInfo()) |mi| mi.flags.contains_import_meta = true;
|
|
p.print("import.meta.main");
|
|
} else {
|
|
bun.debugAssert(p.options.module_type != .internal_bake_dev);
|
|
|
|
p.printSpaceBeforeIdentifier();
|
|
p.addSourceMapping(expr.loc);
|
|
|
|
if (p.options.require_ref) |require|
|
|
p.printSymbol(require)
|
|
else
|
|
p.print("require");
|
|
|
|
if (data.inverted)
|
|
p.printWhitespacer(ws(".main != "))
|
|
else
|
|
p.printWhitespacer(ws(".main == "));
|
|
|
|
if (p.options.target == .node) {
|
|
// "__require.module"
|
|
if (p.options.require_ref) |require| {
|
|
p.printSymbol(require);
|
|
p.print(".module");
|
|
} else {
|
|
p.print("module");
|
|
}
|
|
} else if (p.options.commonjs_module_ref.isValid()) {
|
|
p.printSymbol(p.options.commonjs_module_ref);
|
|
} else {
|
|
p.print("module");
|
|
}
|
|
}
|
|
},
|
|
.e_special => |special| switch (special) {
|
|
.module_exports => {
|
|
p.printSpaceBeforeIdentifier();
|
|
p.addSourceMapping(expr.loc);
|
|
|
|
if (p.options.commonjs_module_exports_assigned_deoptimized) {
|
|
if (p.options.commonjs_module_ref.isValid()) {
|
|
p.printSymbol(p.options.commonjs_module_ref);
|
|
} else {
|
|
p.print("module");
|
|
}
|
|
p.print(".exports");
|
|
} else {
|
|
p.printSymbol(p.options.commonjs_named_exports_ref);
|
|
}
|
|
},
|
|
.hot_enabled => {
|
|
bun.debugAssert(p.options.module_type == .internal_bake_dev);
|
|
p.printSymbol(p.options.hmr_ref);
|
|
p.print(".indirectHot");
|
|
},
|
|
.hot_data => {
|
|
bun.debugAssert(p.options.module_type == .internal_bake_dev);
|
|
p.printSymbol(p.options.hmr_ref);
|
|
p.print(".data");
|
|
},
|
|
.hot_accept => {
|
|
bun.debugAssert(p.options.module_type == .internal_bake_dev);
|
|
p.printSymbol(p.options.hmr_ref);
|
|
p.print(".accept");
|
|
},
|
|
.hot_accept_visited => {
|
|
bun.debugAssert(p.options.module_type == .internal_bake_dev);
|
|
p.printSymbol(p.options.hmr_ref);
|
|
p.print(".acceptSpecifiers");
|
|
},
|
|
.hot_disabled => {
|
|
bun.debugAssert(p.options.module_type != .internal_bake_dev);
|
|
p.printExpr(.{ .data = .e_undefined, .loc = expr.loc }, level, in_flags);
|
|
},
|
|
.resolved_specifier_string => |index| {
|
|
bun.debugAssert(p.options.module_type == .internal_bake_dev);
|
|
p.printStringLiteralUTF8(p.importRecord(index.get()).path.pretty, true);
|
|
},
|
|
},
|
|
|
|
.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) {
|
|
if (p.options.commonjs_module_exports_assigned_deoptimized and
|
|
id.base == .module_dot_exports and
|
|
p.options.commonjs_module_ref.isValid())
|
|
{
|
|
p.printSymbol(p.options.commonjs_module_ref);
|
|
p.print(".exports");
|
|
} else {
|
|
p.printSymbol(p.options.commonjs_named_exports_ref);
|
|
}
|
|
|
|
if (js_lexer.isIdentifier(key)) {
|
|
p.print(".");
|
|
p.print(key);
|
|
} else {
|
|
p.print("[");
|
|
p.printStringLiteralUTF8(key, false);
|
|
p.print("]");
|
|
}
|
|
} else {
|
|
p.printSymbol(value.loc_ref.ref.?);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
.e_new => |e| {
|
|
const has_pure_comment = e.can_be_unwrapped_if_unused == .if_unused and p.options.print_dce_annotations;
|
|
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_unused and p.options.print_dce_annotations;
|
|
if (has_pure_comment and level.gte(.postfix)) {
|
|
wrap = true;
|
|
}
|
|
|
|
if (wrap) {
|
|
p.print("(");
|
|
}
|
|
|
|
if (has_pure_comment) {
|
|
const was_stmt_start = p.stmt_start == p.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;
|
|
|
|
const is_unbound_eval = (!e.is_direct_eval and
|
|
p.isUnboundEvalIdentifier(e.target) and
|
|
e.optional_chain == null);
|
|
|
|
if (is_unbound_eval) {
|
|
p.print("(0,");
|
|
p.printSpace();
|
|
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_main => {
|
|
p.printSpaceBeforeIdentifier();
|
|
p.addSourceMapping(expr.loc);
|
|
|
|
if (p.options.require_ref) |require_ref| {
|
|
p.printSymbol(require_ref);
|
|
p.print(".main");
|
|
} else if (p.options.module_type == .internal_bake_dev) {
|
|
p.print("false"); // there is no true main entry point
|
|
} else {
|
|
p.print("require.main");
|
|
}
|
|
},
|
|
.e_require_call_target => {
|
|
p.printSpaceBeforeIdentifier();
|
|
p.addSourceMapping(expr.loc);
|
|
|
|
if (p.options.require_ref) |require_ref| {
|
|
p.printSymbol(require_ref);
|
|
} else if (p.options.module_type == .internal_bake_dev) {
|
|
p.printSymbol(p.options.hmr_ref);
|
|
p.print(".require");
|
|
} else {
|
|
p.print("require");
|
|
}
|
|
},
|
|
.e_require_resolve_call_target => {
|
|
p.printSpaceBeforeIdentifier();
|
|
p.addSourceMapping(expr.loc);
|
|
|
|
if (p.options.require_ref) |require_ref| {
|
|
p.printSymbol(require_ref);
|
|
p.print(".resolve");
|
|
} else if (p.options.module_type == .internal_bake_dev) {
|
|
p.printSymbol(p.options.hmr_ref);
|
|
p.print(".requireResolve");
|
|
} else {
|
|
p.print("require.resolve");
|
|
}
|
|
},
|
|
.e_require_string => |e| {
|
|
if (!rewrite_esm_to_cjs) {
|
|
p.printRequireOrImportExpr(
|
|
e.import_record_index,
|
|
e.unwrapped_id != std.math.maxInt(u32),
|
|
&([_]G.Comment{}),
|
|
Expr.empty,
|
|
level,
|
|
flags,
|
|
);
|
|
}
|
|
},
|
|
.e_require_resolve_string => |e| {
|
|
const wrap = level.gte(.new) or flags.contains(.forbid_call);
|
|
if (wrap) {
|
|
p.print("(");
|
|
}
|
|
|
|
p.printSpaceBeforeIdentifier();
|
|
|
|
if (p.options.require_ref) |require_ref| {
|
|
p.printSymbol(require_ref);
|
|
p.print(".resolve");
|
|
} else if (p.options.module_type == .internal_bake_dev) {
|
|
p.printSymbol(p.options.hmr_ref);
|
|
p.print(".requireResolve");
|
|
} else {
|
|
p.print("require.resolve");
|
|
}
|
|
|
|
p.print("(");
|
|
p.printStringLiteralUTF8(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);
|
|
if (p.options.module_type == .internal_bake_dev) {
|
|
p.printSymbol(p.options.hmr_ref);
|
|
p.print(".dynamicImport(");
|
|
} else {
|
|
p.print("import(");
|
|
}
|
|
// TODO:
|
|
// if (e.leading_interior_comments.len > 0) {
|
|
// p.printNewline();
|
|
// p.indent();
|
|
// for (e.leading_interior_comments) |comment| {
|
|
// p.printIndentedComment(comment.text);
|
|
// }
|
|
// p.printIndent();
|
|
// }
|
|
p.printExpr(e.expr, .comma, ExprFlag.None());
|
|
|
|
if (!e.options.isMissing()) {
|
|
p.printWhitespacer(ws(", "));
|
|
p.printExpr(e.options, .comma, .{});
|
|
}
|
|
|
|
// TODO:
|
|
// if (e.leading_interior_comments.len > 0) {
|
|
// p.printNewline();
|
|
// p.unindent();
|
|
// p.printIndent();
|
|
// }
|
|
p.print(")");
|
|
if (wrap) {
|
|
p.print(")");
|
|
}
|
|
} else {
|
|
p.printRequireOrImportExpr(
|
|
e.import_record_index,
|
|
false,
|
|
&.{}, // e.leading_interior_comments,
|
|
e.options,
|
|
level,
|
|
flags,
|
|
);
|
|
}
|
|
},
|
|
.e_dot => |e| {
|
|
const isOptionalChain = e.optional_chain == .start;
|
|
|
|
var wrap = false;
|
|
if (e.optional_chain == null) {
|
|
flags.insert(.has_non_optional_chain_parent);
|
|
|
|
// Inline cross-module TypeScript enum references here
|
|
if (p.tryToGetImportedEnumValue(e.target, e.name)) |inlined| {
|
|
p.printInlinedEnum(inlined, e.name, level);
|
|
return;
|
|
}
|
|
} else {
|
|
if (flags.contains(.has_non_optional_chain_parent)) {
|
|
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 (js_lexer.isIdentifier(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.printStringLiteralUTF8(e.name, false);
|
|
|
|
p.print("]");
|
|
}
|
|
|
|
if (wrap) {
|
|
p.print(")");
|
|
}
|
|
},
|
|
.e_index => |e| {
|
|
var wrap = false;
|
|
if (e.optional_chain == null) {
|
|
flags.insert(.has_non_optional_chain_parent);
|
|
|
|
if (e.index.data.as(.e_string)) |str| {
|
|
str.resolveRopeIfNeeded(p.options.allocator);
|
|
|
|
if (str.isUTF8()) if (p.tryToGetImportedEnumValue(e.target, str.data)) |value| {
|
|
p.printInlinedEnum(value, str.data, level);
|
|
return;
|
|
};
|
|
}
|
|
} 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);
|
|
|
|
const is_optional_chain_start = e.optional_chain == .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| {
|
|
// Optimization: Convert `() => obj.method()` to `obj.method.bind(obj)`
|
|
// when the receiver symbol was never assigned to.
|
|
if (e.bind_call_target_ref) |target_ref| {
|
|
if (p.symbols().get(target_ref)) |symbol| {
|
|
// Only transform if the symbol was never assigned to.
|
|
// For const/unbound, has_been_assigned_to is always false.
|
|
// For hoisted (function params, var), it's true if assigned anywhere.
|
|
if (!symbol.has_been_assigned_to) {
|
|
// Get the call expression from the body
|
|
const call = e.body.stmts[0].data.s_return.value.?.data.e_call;
|
|
// Print: target.method.bind(target)
|
|
p.printExpr(call.target, .postfix, ExprFlag.Set{});
|
|
p.print(".bind(");
|
|
p.printSymbol(target_ref);
|
|
p.print(")");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
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, TopLevel.init(.no));
|
|
}
|
|
|
|
if (wrap) {
|
|
p.print(")");
|
|
}
|
|
},
|
|
.e_function => |e| {
|
|
const n = p.writer.written;
|
|
const wrap = p.stmt_start == n or p.export_default_start == n;
|
|
|
|
if (wrap) {
|
|
p.print("(");
|
|
}
|
|
|
|
p.printSpaceBeforeIdentifier();
|
|
p.addSourceMapping(expr.loc);
|
|
if (e.func.flags.contains(.is_async)) {
|
|
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 Output.panic("internal error: expected E.Function's name symbol to have a ref", .{}));
|
|
}
|
|
|
|
p.printFunc(e.func);
|
|
if (wrap) {
|
|
p.print(")");
|
|
}
|
|
},
|
|
.e_class => |e| {
|
|
const n = p.writer.written;
|
|
const 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 Output.panic("internal error: expected E.Class's name symbol to have a ref", .{}));
|
|
}
|
|
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.indent();
|
|
}
|
|
|
|
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.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) {
|
|
if (!e.is_single_line) {
|
|
p.indent();
|
|
}
|
|
|
|
if (e.is_single_line and !is_json) {
|
|
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 and !is_json) {
|
|
p.printSpace();
|
|
} else {
|
|
p.printNewline();
|
|
p.printIndent();
|
|
}
|
|
p.printProperty(property);
|
|
}
|
|
}
|
|
|
|
if (e.is_single_line and !is_json) {
|
|
p.printSpace();
|
|
} else {
|
|
p.unindent();
|
|
p.printNewline();
|
|
p.printIndent();
|
|
}
|
|
}
|
|
if (e.close_brace_loc.start > expr.loc.start) {
|
|
p.addSourceMapping(e.close_brace_loc);
|
|
}
|
|
p.print("}");
|
|
if (wrap) {
|
|
p.print(")");
|
|
}
|
|
},
|
|
.e_boolean, .e_branch_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.printStringCharactersEString(e, '`');
|
|
p.print("`");
|
|
return;
|
|
}
|
|
|
|
p.printStringLiteralEString(e, true);
|
|
},
|
|
.e_template => |e| {
|
|
if (e.tag == null and (p.options.minify_syntax or p.was_lazy_export)) {
|
|
var replaced = std.array_list.Managed(E.TemplatePart).init(p.options.allocator);
|
|
for (e.parts, 0..) |_part, i| {
|
|
var part = _part;
|
|
const inlined_value: ?js_ast.Expr = switch (part.value.data) {
|
|
.e_name_of_symbol => |e2| Expr.init(
|
|
E.String,
|
|
E.String.init(p.mangledPropName(e2.ref)),
|
|
part.value.loc,
|
|
),
|
|
.e_dot => brk: {
|
|
// TODO: handle inlining of dot properties
|
|
break :brk null;
|
|
},
|
|
else => null,
|
|
};
|
|
|
|
if (inlined_value) |value| {
|
|
if (replaced.items.len == 0) {
|
|
bun.handleOom(replaced.appendSlice(e.parts[0..i]));
|
|
}
|
|
part.value = value;
|
|
bun.handleOom(replaced.append(part));
|
|
} else if (replaced.items.len > 0) {
|
|
bun.handleOom(replaced.append(part));
|
|
}
|
|
}
|
|
|
|
if (replaced.items.len > 0) {
|
|
var copy = e.*;
|
|
copy.parts = replaced.items;
|
|
const e2 = copy.fold(p.options.allocator, expr.loc);
|
|
switch (e2.data) {
|
|
.e_string => {
|
|
p.print('"');
|
|
p.printStringCharactersUTF8(e2.data.e_string.data, '"');
|
|
p.print('"');
|
|
return;
|
|
},
|
|
.e_template => {
|
|
e.* = e2.data.e_template.*;
|
|
},
|
|
else => {},
|
|
}
|
|
}
|
|
|
|
// Convert no-substitution template literals into strings if it's smaller
|
|
if (e.parts.len == 0) {
|
|
p.addSourceMapping(expr.loc);
|
|
p.printStringCharactersEString(&e.head.cooked, '`');
|
|
return;
|
|
}
|
|
}
|
|
|
|
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.printStringCharactersEString(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.printStringCharactersEString(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| {
|
|
p.addSourceMapping(expr.loc);
|
|
p.printNumber(e.value, level);
|
|
},
|
|
.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 = if (p.options.module_type != .internal_bake_dev)
|
|
p.symbols().follow(e.ref)
|
|
else
|
|
e.ref;
|
|
const symbol = p.symbols().get(ref).?;
|
|
|
|
// if (bun.strings.eql(symbol.original_name, "registerClientReference")) {
|
|
// @breakpoint();
|
|
// }
|
|
|
|
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.flags.was_originally_require and import_record.path.is_disabled) {
|
|
p.addSourceMapping(expr.loc);
|
|
|
|
if (import_record.flags.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 (js_lexer.isIdentifier(alias)) {
|
|
p.print(".");
|
|
// TODO: addSourceMappingForName
|
|
p.printIdentifier(alias);
|
|
} else {
|
|
p.print("[");
|
|
// TODO: addSourceMappingForName
|
|
// p.addSourceMappingForName(alias);
|
|
p.printStringLiteralUTF8(alias, false);
|
|
p.print("]");
|
|
}
|
|
|
|
if (wrap) {
|
|
p.print(")");
|
|
}
|
|
}
|
|
}
|
|
// else 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.addSourceMapping(expr.loc);
|
|
p.print(entry.text);
|
|
p.printSpace();
|
|
} else {
|
|
p.printSpaceBeforeOperator(e.op);
|
|
if (e.op.isPrefix()) {
|
|
p.addSourceMapping(expr.loc);
|
|
}
|
|
p.print(entry.text);
|
|
p.prev_op = e.op;
|
|
p.prev_op_end = p.writer.written;
|
|
}
|
|
|
|
if (e.op.isPrefix()) {
|
|
// Never turn "typeof (0, x)" into "typeof x" or "delete (0, x)" into "delete x"
|
|
if ((e.op == .un_typeof and !e.flags.was_originally_typeof_identifier and p.isUnboundIdentifier(&e.value)) or
|
|
(e.op == .un_delete and !e.flags.was_originally_delete_of_identifier_or_property_access and isIdentifierOrNumericConstantOrPropertyAccess(&e.value)))
|
|
{
|
|
p.print("(0,");
|
|
p.printSpace();
|
|
p.printExpr(e.value, Op.Level.sub(.prefix, 1), ExprFlag.None());
|
|
p.print(")");
|
|
} else {
|
|
p.printExpr(e.value, Op.Level.sub(.prefix, 1), ExprFlag.None());
|
|
}
|
|
}
|
|
|
|
if (wrap) {
|
|
p.print(")");
|
|
}
|
|
},
|
|
.e_binary => |e| {
|
|
// The handling of binary expressions is convoluted because we're using
|
|
// iteration on the heap instead of recursion on the call stack to avoid
|
|
// stack overflow for deeply-nested ASTs. See the comments for the similar
|
|
// code in the JavaScript parser for details.
|
|
var v = BinaryExpressionVisitor{
|
|
.e = e,
|
|
.level = level,
|
|
.flags = flags,
|
|
.entry = Op.Table.getPtrConst(e.op),
|
|
};
|
|
|
|
// Use a single stack to reduce allocation overhead
|
|
const stack_bottom = p.binary_expression_stack.items.len;
|
|
|
|
while (true) {
|
|
if (!v.checkAndPrepare(p)) {
|
|
break;
|
|
}
|
|
|
|
const left = v.e.left;
|
|
const left_binary: ?*E.Binary = if (left.data == .e_binary) left.data.e_binary else null;
|
|
|
|
// Stop iterating if iteration doesn't apply to the left node
|
|
if (left_binary == null) {
|
|
p.printExpr(left, v.left_level, v.left_flags);
|
|
v.visitRightAndFinish(p);
|
|
break;
|
|
}
|
|
|
|
// Only allocate heap memory on the stack for nested binary expressions
|
|
bun.handleOom(p.binary_expression_stack.append(v));
|
|
v = BinaryExpressionVisitor{
|
|
.e = left_binary.?,
|
|
.level = v.left_level,
|
|
.flags = v.left_flags,
|
|
};
|
|
}
|
|
|
|
// Process all binary operations from the deepest-visited node back toward
|
|
// our original top-level binary operation
|
|
while (p.binary_expression_stack.items.len > stack_bottom) {
|
|
var last = p.binary_expression_stack.pop().?;
|
|
last.visitRightAndFinish(p);
|
|
}
|
|
},
|
|
.e_inlined_enum => |e| {
|
|
p.printExpr(e.value, level, flags);
|
|
if (!p.options.minify_whitespace and !p.options.minify_identifiers) {
|
|
p.print(" /* ");
|
|
p.print(e.comment);
|
|
p.print(" */");
|
|
}
|
|
},
|
|
.e_name_of_symbol => |e| {
|
|
const name = p.mangledPropName(e.ref);
|
|
p.addSourceMappingForName(expr.loc, name, e.ref);
|
|
|
|
if (!p.options.minify_whitespace and e.has_property_key_comment) {
|
|
p.print(" /* @__KEY__ */");
|
|
}
|
|
|
|
p.print('"');
|
|
p.printStringCharactersUTF8(name, '"');
|
|
p.print('"');
|
|
},
|
|
|
|
.e_jsx_element,
|
|
.e_private_identifier,
|
|
=> {
|
|
if (Environment.isDebug)
|
|
Output.panic("Unexpected expression of type .{s}", .{@tagName(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"
|
|
// "-- >" => "-- >"
|
|
// "< ! --" => "<! --"
|
|
if (((prev == Op.Code.bin_add or prev == Op.Code.un_pos) and (next == Op.Code.bin_add or next == Op.Code.un_pos or next == Op.Code.un_pre_inc)) or
|
|
((prev == Op.Code.bin_sub or prev == Op.Code.un_neg) and (next == Op.Code.bin_sub or next == Op.Code.un_neg or next == Op.Code.un_pre_dec)) or
|
|
(prev == Op.Code.un_post_dec and next == Op.Code.bin_gt) or
|
|
(prev == Op.Code.un_not and next == Op.Code.un_pre_dec and p.writer.written > 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 printStringCharactersEString(p: *Printer, str: *const E.String, c: u8) void {
|
|
if (!str.isUTF8()) {
|
|
p.printStringCharactersUTF16(str.slice16(), c);
|
|
} else {
|
|
p.printStringCharactersUTF8(str.data, c);
|
|
}
|
|
}
|
|
|
|
pub fn printNamespaceAlias(p: *Printer, _: ImportRecord, namespace: G.NamespaceAlias) void {
|
|
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 (js_lexer.isIdentifier(namespace.alias)) {
|
|
p.print(".");
|
|
p.printIdentifier(namespace.alias);
|
|
} else {
|
|
p.print("[");
|
|
p.printStringLiteralUTF8(namespace.alias, false);
|
|
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 => |c| {
|
|
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)));
|
|
|
|
p.print(&[_]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],
|
|
});
|
|
},
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
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_in: G.Property) void {
|
|
var item = item_in;
|
|
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;
|
|
}
|
|
|
|
// Handle key syntax compression for cross-module constant inlining of enums
|
|
if (p.options.minify_syntax and item.flags.contains(.is_computed)) {
|
|
if (item.key.?.data.as(.e_dot)) |dot| {
|
|
if (p.tryToGetImportedEnumValue(dot.target, dot.name)) |value| {
|
|
switch (value) {
|
|
.string => |str| {
|
|
item.key.?.data = .{ .e_string = str };
|
|
|
|
// Problematic key names must stay computed for correctness
|
|
if (!str.eqlComptime("__proto__") and !str.eqlComptime("constructor") and !str.eqlComptime("prototype")) {
|
|
item.flags.setPresent(.is_computed, false);
|
|
}
|
|
},
|
|
.number => |num| {
|
|
item.key.?.data = .{ .e_number = .{ .value = num } };
|
|
item.flags.setPresent(.is_computed, false);
|
|
},
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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 (!is_json and item.flags.contains(.is_computed)) {
|
|
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 (!is_json and js_lexer.isIdentifier(key.data)) {
|
|
p.printIdentifier(key.data);
|
|
} else {
|
|
allow_shorthand = false;
|
|
p.printStringLiteralEString(key, false);
|
|
}
|
|
|
|
// 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.input_files_for_dev_server != null)
|
|
break :inner;
|
|
// 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 (!is_json and 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: {
|
|
.e_import_identifier => |e| {
|
|
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.printStringCharactersUTF16(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", .{});
|
|
}
|
|
|
|
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) {
|
|
bun.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, tlm: TopLevelAndIsExport) void {
|
|
switch (binding.data) {
|
|
.b_missing => {},
|
|
.b_identifier => |b| {
|
|
p.printSpaceBeforeIdentifier();
|
|
p.addSourceMapping(binding.loc);
|
|
p.printSymbol(b.ref);
|
|
if (may_have_module_info) {
|
|
if (p.moduleInfo()) |mi| {
|
|
const local_name = p.renamer.nameForSymbol(b.ref);
|
|
const name_id = bun.handleOom(mi.str(local_name));
|
|
if (tlm.is_top_level) |vk| bun.handleOom(mi.addVar(name_id, vk));
|
|
if (tlm.is_export) bun.handleOom(mi.addExportInfoLocal(name_id, name_id));
|
|
}
|
|
}
|
|
},
|
|
.b_array => |b| {
|
|
p.print("[");
|
|
if (b.items.len > 0) {
|
|
if (!b.is_single_line) {
|
|
p.indent();
|
|
}
|
|
|
|
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, tlm);
|
|
|
|
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.unindent();
|
|
p.printNewline();
|
|
p.printIndent();
|
|
}
|
|
}
|
|
|
|
p.print("]");
|
|
},
|
|
.b_object => |b| {
|
|
p.print("{");
|
|
if (b.properties.len > 0) {
|
|
if (!b.is_single_line) {
|
|
p.indent();
|
|
}
|
|
|
|
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, tlm);
|
|
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 (js_lexer.isIdentifier(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))) {
|
|
if (may_have_module_info) {
|
|
if (p.moduleInfo()) |mi| {
|
|
const name_id = bun.handleOom(mi.str(str.data));
|
|
if (tlm.is_top_level) |vk| bun.handleOom(mi.addVar(name_id, vk));
|
|
if (tlm.is_export) bun.handleOom(mi.addExportInfoLocal(name_id, name_id));
|
|
}
|
|
}
|
|
p.maybePrintDefaultBindingValue(property);
|
|
continue;
|
|
}
|
|
},
|
|
else => {},
|
|
}
|
|
} else {
|
|
p.printStringLiteralUTF8(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))) {
|
|
if (may_have_module_info) {
|
|
if (p.moduleInfo()) |mi| {
|
|
const str8 = str.slice(p.options.allocator);
|
|
const name_id = bun.handleOom(mi.str(str8));
|
|
if (tlm.is_top_level) |vk| bun.handleOom(mi.addVar(name_id, vk));
|
|
if (tlm.is_export) bun.handleOom(mi.addExportInfoLocal(name_id, name_id));
|
|
}
|
|
}
|
|
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, tlm);
|
|
p.maybePrintDefaultBindingValue(property);
|
|
}
|
|
|
|
if (!b.is_single_line) {
|
|
p.unindent();
|
|
p.printNewline();
|
|
p.printIndent();
|
|
} else {
|
|
p.printSpace();
|
|
}
|
|
}
|
|
p.print("}");
|
|
},
|
|
}
|
|
}
|
|
|
|
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, tlmtlo: TopLevel) !void {
|
|
const prev_stmt_tag = p.prev_stmt_tag;
|
|
|
|
defer {
|
|
p.prev_stmt_tag = std.meta.activeTag(stmt.data);
|
|
}
|
|
|
|
switch (stmt.data) {
|
|
.s_comment => |s| {
|
|
p.printIndent();
|
|
p.addSourceMapping(stmt.loc);
|
|
p.printIndentedComment(s.text);
|
|
},
|
|
.s_function => |s| {
|
|
p.printIndent();
|
|
p.printSpaceBeforeIdentifier();
|
|
p.addSourceMapping(stmt.loc);
|
|
const name = s.func.name orelse Output.panic("Internal error: expected func to have a name ref", .{});
|
|
const nameRef = name.ref orelse Output.panic("Internal error: expected func to have a name", .{});
|
|
|
|
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();
|
|
} else {
|
|
p.printSpaceBeforeIdentifier();
|
|
}
|
|
|
|
p.addSourceMapping(name.loc);
|
|
const local_name = p.renamer.nameForSymbol(nameRef);
|
|
p.printIdentifier(local_name);
|
|
p.printFunc(s.func);
|
|
|
|
if (may_have_module_info) {
|
|
if (p.moduleInfo()) |mi| {
|
|
const name_id = bun.handleOom(mi.str(local_name));
|
|
// function declarations are lexical (block-scoped in modules);
|
|
// only record at true top-level, not inside blocks.
|
|
if (tlmtlo.is_top_level == .yes) bun.handleOom(mi.addVar(name_id, .lexical));
|
|
if (s.func.flags.contains(.is_export)) bun.handleOom(mi.addExportInfoLocal(name_id, name_id));
|
|
}
|
|
}
|
|
|
|
p.printNewline();
|
|
|
|
if (rewrite_esm_to_cjs and s.func.flags.contains(.is_export)) {
|
|
p.printIndent();
|
|
p.printBundledExport(local_name, local_name);
|
|
p.printSemicolonAfterStatement();
|
|
}
|
|
},
|
|
.s_class => |s| {
|
|
// Give an extra newline for readaiblity
|
|
if (prev_stmt_tag != .s_empty) {
|
|
p.printNewline();
|
|
}
|
|
|
|
p.printIndent();
|
|
p.printSpaceBeforeIdentifier();
|
|
p.addSourceMapping(stmt.loc);
|
|
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);
|
|
const nameStr = p.renamer.nameForSymbol(nameRef);
|
|
p.printIdentifier(nameStr);
|
|
p.printClass(s.class);
|
|
|
|
if (may_have_module_info) {
|
|
if (p.moduleInfo()) |mi| {
|
|
const name_id = bun.handleOom(mi.str(nameStr));
|
|
// class declarations are lexical (block-scoped in modules);
|
|
// only record at true top-level, not inside blocks.
|
|
if (tlmtlo.is_top_level == .yes) bun.handleOom(mi.addVar(name_id, .lexical));
|
|
if (s.is_export) bun.handleOom(mi.addExportInfoLocal(name_id, name_id));
|
|
}
|
|
}
|
|
|
|
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.count == 0) return;
|
|
|
|
p.printIndent();
|
|
p.addSourceMapping(stmt.loc);
|
|
p.print(";");
|
|
p.printNewline();
|
|
},
|
|
.s_export_default => |s| {
|
|
p.printIndent();
|
|
p.printSpaceBeforeIdentifier();
|
|
p.addSourceMapping(stmt.loc);
|
|
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();
|
|
|
|
if (may_have_module_info) {
|
|
if (p.moduleInfo()) |mi| {
|
|
bun.handleOom(mi.addExportInfoLocal(bun.handleOom(mi.str("default")), .star_default));
|
|
bun.handleOom(mi.addVar(.star_default, .lexical));
|
|
}
|
|
}
|
|
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();
|
|
}
|
|
|
|
const func_name: ?[]const u8 = if (func.func.name) |name| p.renamer.nameForSymbol(name.ref.?) else null;
|
|
if (func_name) |fn_name| {
|
|
p.printIdentifier(fn_name);
|
|
}
|
|
|
|
p.printFunc(func.func);
|
|
|
|
if (may_have_module_info) {
|
|
if (p.moduleInfo()) |mi| {
|
|
const local_name: analyze_transpiled_module.StringID = if (func_name) |f| bun.handleOom(mi.str(f)) else .star_default;
|
|
bun.handleOom(mi.addExportInfoLocal(bun.handleOom(mi.str("default")), local_name));
|
|
bun.handleOom(mi.addVar(local_name, .lexical));
|
|
}
|
|
}
|
|
|
|
p.printNewline();
|
|
},
|
|
.s_class => |class| {
|
|
p.printSpaceBeforeIdentifier();
|
|
|
|
const class_name: ?[]const u8 = if (class.class.class_name) |name| p.renamer.nameForSymbol(name.ref orelse Output.panic("Internal error: Expected class to have a name ref", .{})) else null;
|
|
if (class.class.class_name) |name| {
|
|
p.print("class ");
|
|
p.printIdentifier(p.renamer.nameForSymbol(name.ref.?));
|
|
} else {
|
|
p.print("class");
|
|
}
|
|
|
|
p.printClass(class.class);
|
|
|
|
if (may_have_module_info) {
|
|
if (p.moduleInfo()) |mi| {
|
|
const local_name: analyze_transpiled_module.StringID = if (class_name) |f| bun.handleOom(mi.str(f)) else .star_default;
|
|
bun.handleOom(mi.addExportInfoLocal(bun.handleOom(mi.str("default")), local_name));
|
|
bun.handleOom(mi.addVar(local_name, .lexical));
|
|
}
|
|
}
|
|
|
|
p.printNewline();
|
|
},
|
|
else => {
|
|
Output.panic("Internal error: unexpected export default stmt data", .{});
|
|
},
|
|
}
|
|
},
|
|
}
|
|
},
|
|
.s_export_star => |s| {
|
|
|
|
// Give an extra newline for readaiblity
|
|
if (!prev_stmt_tag.isExportLike()) {
|
|
p.printNewline();
|
|
}
|
|
p.printIndent();
|
|
p.printSpaceBeforeIdentifier();
|
|
p.addSourceMapping(stmt.loc);
|
|
|
|
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 "));
|
|
}
|
|
|
|
const irp = p.importRecord(s.import_record_index).path.text;
|
|
p.printImportRecordPath(p.importRecord(s.import_record_index));
|
|
p.printSemicolonAfterStatement();
|
|
|
|
if (may_have_module_info) {
|
|
if (p.moduleInfo()) |mi| {
|
|
const irp_id = bun.handleOom(mi.str(irp));
|
|
bun.handleOom(mi.requestModule(irp_id, .none));
|
|
if (s.alias) |alias| {
|
|
bun.handleOom(mi.addExportInfoNamespace(bun.handleOom(mi.str(alias.original_name)), irp_id));
|
|
} else {
|
|
bun.handleOom(mi.addExportInfoStar(irp_id));
|
|
}
|
|
}
|
|
}
|
|
},
|
|
.s_export_clause => |s| {
|
|
if (rewrite_esm_to_cjs) {
|
|
p.printIndent();
|
|
p.printSpaceBeforeIdentifier();
|
|
p.addSourceMapping(stmt.loc);
|
|
|
|
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.addSourceMapping(stmt.loc);
|
|
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.indent();
|
|
} 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();
|
|
}
|
|
|
|
const name = p.renamer.nameForSymbol(item.name.ref.?);
|
|
p.printExportClauseItem(item);
|
|
|
|
if (may_have_module_info) {
|
|
if (p.moduleInfo()) |mi| {
|
|
bun.handleOom(mi.addExportInfoLocal(bun.handleOom(mi.str(item.alias)), bun.handleOom(mi.str(name))));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!s.is_single_line) {
|
|
p.unindent();
|
|
p.printNewline();
|
|
p.printIndent();
|
|
} else {
|
|
p.printSpace();
|
|
}
|
|
|
|
p.print("}");
|
|
p.printSemicolonAfterStatement();
|
|
},
|
|
.s_export_from => |s| {
|
|
p.printIndent();
|
|
p.printSpaceBeforeIdentifier();
|
|
p.addSourceMapping(stmt.loc);
|
|
|
|
const import_record = p.importRecord(s.import_record_index);
|
|
|
|
p.printWhitespacer(ws("export {"));
|
|
|
|
if (!s.is_single_line) {
|
|
p.indent();
|
|
} 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.unindent();
|
|
p.printNewline();
|
|
p.printIndent();
|
|
} else {
|
|
p.printSpace();
|
|
}
|
|
|
|
p.printWhitespacer(ws("} from "));
|
|
const irp = import_record.path.text;
|
|
p.printImportRecordPath(import_record);
|
|
p.printSemicolonAfterStatement();
|
|
|
|
if (may_have_module_info) {
|
|
if (p.moduleInfo()) |mi| {
|
|
const irp_id = bun.handleOom(mi.str(irp));
|
|
bun.handleOom(mi.requestModule(irp_id, .none));
|
|
for (s.items) |item| {
|
|
const name = p.renamer.nameForSymbol(item.name.ref.?);
|
|
bun.handleOom(mi.addExportInfoIndirect(bun.handleOom(mi.str(item.alias)), bun.handleOom(mi.str(name)), irp_id));
|
|
}
|
|
}
|
|
}
|
|
},
|
|
.s_local => |s| {
|
|
p.printIndent();
|
|
p.printSpaceBeforeIdentifier();
|
|
p.addSourceMapping(stmt.loc);
|
|
switch (s.kind) {
|
|
.k_const => {
|
|
p.printDeclStmt(s.is_export, "const", s.decls.slice(), tlmtlo);
|
|
},
|
|
.k_let => {
|
|
p.printDeclStmt(s.is_export, "let", s.decls.slice(), tlmtlo);
|
|
},
|
|
.k_var => {
|
|
p.printDeclStmt(s.is_export, "var", s.decls.slice(), tlmtlo);
|
|
},
|
|
.k_using => {
|
|
p.printDeclStmt(s.is_export, "using", s.decls.slice(), tlmtlo);
|
|
},
|
|
.k_await_using => {
|
|
p.printDeclStmt(s.is_export, "await using", s.decls.slice(), tlmtlo);
|
|
},
|
|
}
|
|
},
|
|
.s_if => |s| {
|
|
p.printIndent();
|
|
p.printIf(s, stmt.loc, tlmtlo.subVar());
|
|
},
|
|
.s_do_while => |s| {
|
|
p.printIndent();
|
|
p.printSpaceBeforeIdentifier();
|
|
p.addSourceMapping(stmt.loc);
|
|
p.print("do");
|
|
const sub_var = tlmtlo.subVar();
|
|
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, sub_var);
|
|
p.printSpace();
|
|
},
|
|
else => {
|
|
p.printNewline();
|
|
p.indent();
|
|
p.printStmt(s.body, sub_var) catch unreachable;
|
|
p.printSemicolonIfNeeded();
|
|
p.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.addSourceMapping(stmt.loc);
|
|
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, tlmtlo.subVar());
|
|
},
|
|
.s_for_of => |s| {
|
|
p.printIndent();
|
|
p.printSpaceBeforeIdentifier();
|
|
p.addSourceMapping(stmt.loc);
|
|
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, tlmtlo.subVar());
|
|
},
|
|
.s_while => |s| {
|
|
p.printIndent();
|
|
p.printSpaceBeforeIdentifier();
|
|
p.addSourceMapping(stmt.loc);
|
|
p.print("while");
|
|
p.printSpace();
|
|
p.print("(");
|
|
p.printExpr(s.test_, .lowest, ExprFlag.None());
|
|
p.print(")");
|
|
p.printBody(s.body, tlmtlo.subVar());
|
|
},
|
|
.s_with => |s| {
|
|
p.printIndent();
|
|
p.printSpaceBeforeIdentifier();
|
|
p.addSourceMapping(stmt.loc);
|
|
p.print("with");
|
|
p.printSpace();
|
|
p.print("(");
|
|
p.printExpr(s.value, .lowest, ExprFlag.None());
|
|
p.print(")");
|
|
p.printBody(s.body, tlmtlo.subVar());
|
|
},
|
|
.s_label => |s| {
|
|
if (!p.options.minify_whitespace and p.options.indent.count > 0) {
|
|
p.printIndent();
|
|
}
|
|
p.printSpaceBeforeIdentifier();
|
|
p.addSourceMapping(stmt.loc);
|
|
p.printSymbol(s.name.ref orelse Output.panic("Internal error: expected label to have a name", .{}));
|
|
p.print(":");
|
|
p.printBody(s.stmt, tlmtlo.subVar());
|
|
},
|
|
.s_try => |s| {
|
|
p.printIndent();
|
|
p.printSpaceBeforeIdentifier();
|
|
p.addSourceMapping(stmt.loc);
|
|
p.print("try");
|
|
p.printSpace();
|
|
const sub_var_try = tlmtlo.subVar();
|
|
p.printBlock(s.body_loc, s.body, null, sub_var_try);
|
|
|
|
if (s.catch_) |catch_| {
|
|
p.printSpace();
|
|
p.addSourceMapping(catch_.loc);
|
|
p.print("catch");
|
|
if (catch_.binding) |binding| {
|
|
p.printSpace();
|
|
p.print("(");
|
|
p.printBinding(binding, .{});
|
|
p.print(")");
|
|
}
|
|
p.printSpace();
|
|
p.printBlock(catch_.body_loc, catch_.body, null, sub_var_try);
|
|
}
|
|
|
|
if (s.finally) |finally| {
|
|
p.printSpace();
|
|
p.print("finally");
|
|
p.printSpace();
|
|
p.printBlock(finally.loc, finally.stmts, null, sub_var_try);
|
|
}
|
|
|
|
p.printNewline();
|
|
},
|
|
.s_for => |s| {
|
|
p.printIndent();
|
|
p.printSpaceBeforeIdentifier();
|
|
p.addSourceMapping(stmt.loc);
|
|
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, tlmtlo.subVar());
|
|
},
|
|
.s_switch => |s| {
|
|
p.printIndent();
|
|
p.printSpaceBeforeIdentifier();
|
|
p.addSourceMapping(stmt.loc);
|
|
p.print("switch");
|
|
p.printSpace();
|
|
p.print("(");
|
|
|
|
p.printExpr(s.test_, .lowest, ExprFlag.None());
|
|
|
|
p.print(")");
|
|
p.printSpace();
|
|
p.print("{");
|
|
p.printNewline();
|
|
p.indent();
|
|
|
|
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(":");
|
|
|
|
const sub_var_case = tlmtlo.subVar();
|
|
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, sub_var_case);
|
|
p.printNewline();
|
|
continue;
|
|
},
|
|
else => {},
|
|
}
|
|
}
|
|
|
|
p.printNewline();
|
|
p.indent();
|
|
for (c.body) |st| {
|
|
p.printSemicolonIfNeeded();
|
|
p.printStmt(st, sub_var_case) catch unreachable;
|
|
}
|
|
p.unindent();
|
|
}
|
|
|
|
p.unindent();
|
|
p.printIndent();
|
|
p.print("}");
|
|
p.printNewline();
|
|
p.needs_semicolon = false;
|
|
},
|
|
.s_import => |s| {
|
|
bun.assert(s.import_record_index < p.import_records.len);
|
|
bun.debugAssert(p.options.module_type != .internal_bake_dev);
|
|
|
|
const record: *const ImportRecord = p.importRecord(s.import_record_index);
|
|
p.printIndent();
|
|
p.printSpaceBeforeIdentifier();
|
|
p.addSourceMapping(stmt.loc);
|
|
|
|
if (comptime is_bun_platform) {
|
|
switch (record.tag) {
|
|
.bun => {
|
|
p.printGlobalBunImportStatement(s.*);
|
|
return;
|
|
},
|
|
else => {},
|
|
}
|
|
}
|
|
|
|
if (record.path.is_disabled) {
|
|
if (record.flags.contains_import_star) {
|
|
p.print("var ");
|
|
p.printSymbol(s.namespace_ref);
|
|
p.@"print = "();
|
|
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.flags.contains_import_star) {
|
|
p.printSymbol(s.namespace_ref);
|
|
p.printSemicolonAfterStatement();
|
|
} else {
|
|
p.printDisabledImport();
|
|
p.printSemicolonAfterStatement();
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (record.flags.handles_import_errors and record.path.is_disabled and record.kind.isCommonJS()) {
|
|
return;
|
|
}
|
|
|
|
p.print("import");
|
|
|
|
var item_count: usize = 0;
|
|
|
|
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.indent();
|
|
} 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.printClauseItem(item);
|
|
}
|
|
|
|
if (!s.is_single_line) {
|
|
p.unindent();
|
|
p.printNewline();
|
|
p.printIndent();
|
|
} else {
|
|
p.printSpace();
|
|
}
|
|
p.print("}");
|
|
item_count += 1;
|
|
}
|
|
|
|
if (record.flags.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) {
|
|
if (!p.options.minify_whitespace or
|
|
record.flags.contains_import_star or
|
|
s.items.len == 0)
|
|
p.print(" ");
|
|
|
|
p.printWhitespacer(ws("from "));
|
|
}
|
|
|
|
p.printImportRecordPath(record);
|
|
|
|
// backwards compatibility: previously, we always stripped type
|
|
if (comptime is_bun_platform) if (record.loader) |loader| switch (loader) {
|
|
.jsx => p.printWhitespacer(ws(" with { type: \"jsx\" }")),
|
|
.js => p.printWhitespacer(ws(" with { type: \"js\" }")),
|
|
.ts => p.printWhitespacer(ws(" with { type: \"ts\" }")),
|
|
.tsx => p.printWhitespacer(ws(" with { type: \"tsx\" }")),
|
|
.css => p.printWhitespacer(ws(" with { type: \"css\" }")),
|
|
.file => p.printWhitespacer(ws(" with { type: \"file\" }")),
|
|
.json => p.printWhitespacer(ws(" with { type: \"json\" }")),
|
|
.jsonc => p.printWhitespacer(ws(" with { type: \"jsonc\" }")),
|
|
.toml => p.printWhitespacer(ws(" with { type: \"toml\" }")),
|
|
.yaml => p.printWhitespacer(ws(" with { type: \"yaml\" }")),
|
|
.json5 => p.printWhitespacer(ws(" with { type: \"json5\" }")),
|
|
.wasm => p.printWhitespacer(ws(" with { type: \"wasm\" }")),
|
|
.napi => p.printWhitespacer(ws(" with { type: \"napi\" }")),
|
|
.base64 => p.printWhitespacer(ws(" with { type: \"base64\" }")),
|
|
.dataurl => p.printWhitespacer(ws(" with { type: \"dataurl\" }")),
|
|
.text => p.printWhitespacer(ws(" with { type: \"text\" }")),
|
|
.bunsh => p.printWhitespacer(ws(" with { type: \"sh\" }")),
|
|
.sqlite, .sqlite_embedded => p.printWhitespacer(ws(" with { type: \"sqlite\" }")),
|
|
.html => p.printWhitespacer(ws(" with { type: \"html\" }")),
|
|
.md => p.printWhitespacer(ws(" with { type: \"md\" }")),
|
|
};
|
|
p.printSemicolonAfterStatement();
|
|
|
|
if (may_have_module_info) {
|
|
if (p.moduleInfo()) |mi| {
|
|
const import_record_path = record.path.text;
|
|
const irp_id = bun.handleOom(mi.str(import_record_path));
|
|
const fetch_parameters: analyze_transpiled_module.ModuleInfo.FetchParameters = if (comptime is_bun_platform) (if (record.loader) |loader| switch (loader) {
|
|
.json => .json,
|
|
.jsx => analyze_transpiled_module.ModuleInfo.FetchParameters.hostDefined(bun.handleOom(mi.str("jsx"))),
|
|
.js => analyze_transpiled_module.ModuleInfo.FetchParameters.hostDefined(bun.handleOom(mi.str("js"))),
|
|
.ts => analyze_transpiled_module.ModuleInfo.FetchParameters.hostDefined(bun.handleOom(mi.str("ts"))),
|
|
.tsx => analyze_transpiled_module.ModuleInfo.FetchParameters.hostDefined(bun.handleOom(mi.str("tsx"))),
|
|
.css => analyze_transpiled_module.ModuleInfo.FetchParameters.hostDefined(bun.handleOom(mi.str("css"))),
|
|
.file => analyze_transpiled_module.ModuleInfo.FetchParameters.hostDefined(bun.handleOom(mi.str("file"))),
|
|
.jsonc => analyze_transpiled_module.ModuleInfo.FetchParameters.hostDefined(bun.handleOom(mi.str("jsonc"))),
|
|
.toml => analyze_transpiled_module.ModuleInfo.FetchParameters.hostDefined(bun.handleOom(mi.str("toml"))),
|
|
.yaml => analyze_transpiled_module.ModuleInfo.FetchParameters.hostDefined(bun.handleOom(mi.str("yaml"))),
|
|
.wasm => analyze_transpiled_module.ModuleInfo.FetchParameters.hostDefined(bun.handleOom(mi.str("wasm"))),
|
|
.napi => analyze_transpiled_module.ModuleInfo.FetchParameters.hostDefined(bun.handleOom(mi.str("napi"))),
|
|
.base64 => analyze_transpiled_module.ModuleInfo.FetchParameters.hostDefined(bun.handleOom(mi.str("base64"))),
|
|
.dataurl => analyze_transpiled_module.ModuleInfo.FetchParameters.hostDefined(bun.handleOom(mi.str("dataurl"))),
|
|
.text => analyze_transpiled_module.ModuleInfo.FetchParameters.hostDefined(bun.handleOom(mi.str("text"))),
|
|
.bunsh => analyze_transpiled_module.ModuleInfo.FetchParameters.hostDefined(bun.handleOom(mi.str("sh"))),
|
|
.sqlite, .sqlite_embedded => analyze_transpiled_module.ModuleInfo.FetchParameters.hostDefined(bun.handleOom(mi.str("sqlite"))),
|
|
.html => analyze_transpiled_module.ModuleInfo.FetchParameters.hostDefined(bun.handleOom(mi.str("html"))),
|
|
.json5 => analyze_transpiled_module.ModuleInfo.FetchParameters.hostDefined(bun.handleOom(mi.str("json5"))),
|
|
.md => analyze_transpiled_module.ModuleInfo.FetchParameters.hostDefined(bun.handleOom(mi.str("md"))),
|
|
} else .none) else .none;
|
|
bun.handleOom(mi.requestModule(irp_id, fetch_parameters));
|
|
|
|
if (s.default_name) |name| {
|
|
const local_name = p.renamer.nameForSymbol(name.ref.?);
|
|
const local_name_id = bun.handleOom(mi.str(local_name));
|
|
bun.handleOom(mi.addVar(local_name_id, .lexical));
|
|
bun.handleOom(mi.addImportInfoSingle(irp_id, bun.handleOom(mi.str("default")), local_name_id, false));
|
|
}
|
|
|
|
for (s.items) |item| {
|
|
const local_name = p.renamer.nameForSymbol(item.name.ref.?);
|
|
const local_name_id = bun.handleOom(mi.str(local_name));
|
|
bun.handleOom(mi.addVar(local_name_id, .lexical));
|
|
// In bundled output, all surviving imports are value imports
|
|
// (tree-shaking already removed type-only ones). The finalize()
|
|
// step handles re-export type-script conversion separately.
|
|
bun.handleOom(mi.addImportInfoSingle(irp_id, bun.handleOom(mi.str(item.alias)), local_name_id, false));
|
|
}
|
|
|
|
if (record.flags.contains_import_star) {
|
|
const local_name = p.renamer.nameForSymbol(s.namespace_ref);
|
|
bun.handleOom(mi.addVar(bun.handleOom(mi.str(local_name)), .lexical));
|
|
bun.handleOom(mi.addImportInfoNamespace(irp_id, bun.handleOom(mi.str(local_name))));
|
|
}
|
|
}
|
|
}
|
|
},
|
|
.s_block => |s| {
|
|
p.printIndent();
|
|
p.printBlock(stmt.loc, s.stmts, s.close_brace_loc, tlmtlo.subVar());
|
|
p.printNewline();
|
|
},
|
|
.s_debugger => {
|
|
p.printIndent();
|
|
p.printSpaceBeforeIdentifier();
|
|
p.addSourceMapping(stmt.loc);
|
|
p.print("debugger");
|
|
p.printSemicolonAfterStatement();
|
|
},
|
|
.s_directive => |s| {
|
|
if (comptime is_json)
|
|
unreachable;
|
|
|
|
p.printIndent();
|
|
p.printSpaceBeforeIdentifier();
|
|
p.addSourceMapping(stmt.loc);
|
|
p.printStringLiteralUTF8(s.value, false);
|
|
p.printSemicolonAfterStatement();
|
|
},
|
|
.s_break => |s| {
|
|
p.printIndent();
|
|
p.printSpaceBeforeIdentifier();
|
|
p.addSourceMapping(stmt.loc);
|
|
p.print("break");
|
|
if (s.label) |label| {
|
|
p.print(" ");
|
|
p.printSymbol(label.ref.?);
|
|
}
|
|
|
|
p.printSemicolonAfterStatement();
|
|
},
|
|
.s_continue => |s| {
|
|
p.printIndent();
|
|
p.printSpaceBeforeIdentifier();
|
|
p.addSourceMapping(stmt.loc);
|
|
p.print("continue");
|
|
|
|
if (s.label) |label| {
|
|
p.print(" ");
|
|
p.printSymbol(label.ref.?);
|
|
}
|
|
p.printSemicolonAfterStatement();
|
|
},
|
|
.s_return => |s| {
|
|
p.printIndent();
|
|
p.printSpaceBeforeIdentifier();
|
|
p.addSourceMapping(stmt.loc);
|
|
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.addSourceMapping(stmt.loc);
|
|
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.count > 0) {
|
|
p.printIndent();
|
|
}
|
|
|
|
p.stmt_start = p.writer.written;
|
|
p.printExpr(s.value, .lowest, ExprFlag.ExprResultIsUnused());
|
|
p.printSemicolonAfterStatement();
|
|
},
|
|
else => |tag| {
|
|
Output.panic("Unexpected tag in printStmt: .{s}", .{@tagName(tag)});
|
|
},
|
|
}
|
|
}
|
|
|
|
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.flags.print_namespace_in_path and !import_record.path.isFile()) {
|
|
p.print(quote);
|
|
p.printStringCharactersUTF8(import_record.path.namespace, quote);
|
|
p.print(":");
|
|
p.printStringCharactersUTF8(import_record.path.text, quote);
|
|
p.print(quote);
|
|
} else {
|
|
p.print(quote);
|
|
p.printStringCharactersUTF8(import_record.path.text, quote);
|
|
p.print(quote);
|
|
}
|
|
}
|
|
|
|
pub fn printBundledImport(p: *Printer, record: ImportRecord, s: *S.Import) void {
|
|
if (record.flags.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.printWhitespacer(ws("(() => ({}))"));
|
|
}
|
|
|
|
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(bun.callmod_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 {
|
|
bun.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 printBundledRexport(p: *Printer, name: string, import_record_index: u32) void {
|
|
p.print("Object.defineProperty(");
|
|
p.printModuleExportSymbol();
|
|
p.print(",");
|
|
p.printStringLiteralUTF8(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.printStringLiteralUTF8(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 }), .{});
|
|
},
|
|
.k_using => {
|
|
p.printDecls("using", s.decls.slice(), ExprFlag.Set.init(.{ .forbid_in = true }), .{});
|
|
},
|
|
.k_await_using => {
|
|
p.printDecls("await using", s.decls.slice(), ExprFlag.Set.init(.{ .forbid_in = true }), .{});
|
|
},
|
|
}
|
|
},
|
|
// for(;)
|
|
.s_empty => {},
|
|
else => {
|
|
Output.panic("Internal error: Unexpected stmt in for loop", .{});
|
|
},
|
|
}
|
|
}
|
|
pub fn printIf(p: *Printer, s: *const S.If, loc: logger.Loc, tlmtlo: TopLevel) void {
|
|
p.printSpaceBeforeIdentifier();
|
|
p.addSourceMapping(loc);
|
|
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, tlmtlo);
|
|
|
|
if (s.no != null) {
|
|
p.printSpace();
|
|
} else {
|
|
p.printNewline();
|
|
}
|
|
},
|
|
else => {
|
|
if (wrapToAvoidAmbiguousElse(&s.yes.data)) {
|
|
p.printSpace();
|
|
p.print("{");
|
|
p.printNewline();
|
|
|
|
p.indent();
|
|
p.printStmt(s.yes, tlmtlo) catch unreachable;
|
|
p.unindent();
|
|
p.needs_semicolon = false;
|
|
|
|
p.printIndent();
|
|
p.print("}");
|
|
|
|
if (s.no != null) {
|
|
p.printSpace();
|
|
} else {
|
|
p.printNewline();
|
|
}
|
|
} else {
|
|
p.printNewline();
|
|
p.indent();
|
|
p.printStmt(s.yes, tlmtlo) catch unreachable;
|
|
p.unindent();
|
|
|
|
if (s.no != null) {
|
|
p.printIndent();
|
|
}
|
|
}
|
|
},
|
|
}
|
|
|
|
if (s.no) |no_block| {
|
|
p.printSemicolonIfNeeded();
|
|
p.printSpaceBeforeIdentifier();
|
|
p.addSourceMapping(no_block.loc);
|
|
p.print("else");
|
|
|
|
switch (no_block.data) {
|
|
.s_block => {
|
|
p.printSpace();
|
|
p.printBlock(no_block.loc, no_block.data.s_block.stmts, null, tlmtlo);
|
|
p.printNewline();
|
|
},
|
|
.s_if => {
|
|
p.printIf(no_block.data.s_if, no_block.loc, tlmtlo);
|
|
},
|
|
else => {
|
|
p.printNewline();
|
|
p.indent();
|
|
p.printStmt(no_block, tlmtlo) catch unreachable;
|
|
p.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 tryToGetImportedEnumValue(p: *Printer, target: Expr, name: []const u8) ?js_ast.InlinedEnumValue.Decoded {
|
|
if (target.data.as(.e_import_identifier)) |id| {
|
|
const ref = p.symbols().follow(id.ref);
|
|
if (p.symbols().get(ref)) |symbol| {
|
|
if (symbol.kind == .ts_enum) {
|
|
if (p.options.ts_enums.get(ref)) |enum_value| {
|
|
if (enum_value.get(name)) |value|
|
|
return value.decode();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
pub fn printInlinedEnum(
|
|
p: *Printer,
|
|
inlined: js_ast.InlinedEnumValue.Decoded,
|
|
comment: []const u8,
|
|
level: Level,
|
|
) void {
|
|
switch (inlined) {
|
|
.number => |num| p.printNumber(num, level),
|
|
|
|
// TODO: extract printString
|
|
.string => |str| p.printExpr(.{
|
|
.data = .{ .e_string = str },
|
|
.loc = logger.Loc.Empty,
|
|
}, level, .{}),
|
|
}
|
|
|
|
if (!p.options.minify_whitespace and !p.options.minify_identifiers) {
|
|
// TODO: rewrite this to handle </script>
|
|
if (!bun.strings.containsComptime(comment, "*/")) {
|
|
p.print(" /* ");
|
|
p.print(comment);
|
|
p.print(" */");
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn printDeclStmt(p: *Printer, is_export: bool, comptime keyword: string, decls: []G.Decl, tlmtlo: TopLevel) void {
|
|
if (!rewrite_esm_to_cjs and is_export) {
|
|
p.print("export ");
|
|
}
|
|
const tlm: TopLevelAndIsExport = if (may_have_module_info) .{
|
|
.is_export = is_export,
|
|
.is_top_level = if (comptime strings.eqlComptime(keyword, "var"))
|
|
(if (tlmtlo.isTopLevel()) .declared else null)
|
|
else
|
|
// let/const are block-scoped: only record at true top-level,
|
|
// not inside blocks where subVar() downgrades to .var_only.
|
|
(if (tlmtlo.is_top_level == .yes) .lexical else null),
|
|
} else .{};
|
|
p.printDecls(keyword, decls, ExprFlag.None(), tlm);
|
|
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.printIdentifierAsciiOnly(identifier);
|
|
} else {
|
|
p.print(identifier);
|
|
}
|
|
}
|
|
|
|
fn printIdentifierAsciiOnly(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{");
|
|
p.fmt("{x}", .{cursor.c}) 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 printNumber(p: *Printer, value: f64, level: Level) void {
|
|
const absValue = @abs(value);
|
|
if (std.math.isNan(value)) {
|
|
p.printSpaceBeforeIdentifier();
|
|
p.print("NaN");
|
|
} else if (std.math.isPositiveInf(value) or std.math.isNegativeInf(value)) {
|
|
const wrap = ((!p.options.has_run_symbol_renamer or p.options.minify_syntax) and level.gte(.multiply)) or
|
|
(std.math.isNegativeInf(value) and level.gte(.prefix));
|
|
|
|
if (wrap) {
|
|
p.print("(");
|
|
}
|
|
|
|
if (std.math.isNegativeInf(value)) {
|
|
p.printSpaceBeforeOperator(.un_neg);
|
|
p.print("-");
|
|
} else {
|
|
p.printSpaceBeforeIdentifier();
|
|
}
|
|
|
|
// If we are not running the symbol renamer, we must not print "Infinity".
|
|
// Some code may assign `Infinity` to another idenitifier.
|
|
//
|
|
// We do not want:
|
|
//
|
|
// const Infinity = 1 / 0
|
|
//
|
|
// to be transformed into:
|
|
//
|
|
// const Infinity = Infinity
|
|
//
|
|
if (is_json or (!p.options.minify_syntax and p.options.has_run_symbol_renamer)) {
|
|
p.print("Infinity");
|
|
} else if (p.options.minify_whitespace) {
|
|
p.print("1/0");
|
|
} else {
|
|
p.print("1 / 0");
|
|
}
|
|
|
|
if (wrap) {
|
|
p.print(")");
|
|
}
|
|
} else if (!std.math.signbit(value)) {
|
|
p.printSpaceBeforeIdentifier();
|
|
|
|
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.printNonNegativeFloat(absValue);
|
|
p.print(")");
|
|
} else {
|
|
p.printSpaceBeforeOperator(Op.Code.un_neg);
|
|
p.print("-");
|
|
p.printNonNegativeFloat(absValue);
|
|
|
|
// Remember the end of the latest number
|
|
p.prev_num_end = p.writer.written;
|
|
}
|
|
}
|
|
|
|
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| {
|
|
|
|
// Skip over \r if it precedes \n
|
|
if (newline_index > 0 and text[newline_index - 1] == '\r') {
|
|
p.print(text[0 .. newline_index - 1]);
|
|
p.print("\n");
|
|
} else {
|
|
p.print(text[0 .. newline_index + 1]);
|
|
}
|
|
p.printIndent();
|
|
|
|
text = text[newline_index + 1 ..];
|
|
}
|
|
p.print(text);
|
|
p.printNewline();
|
|
} else {
|
|
// Print a mandatory newline after single-line comments
|
|
if (text.len > 0 and text[text.len - 1] == '\r') {
|
|
text = text[0 .. text.len - 1];
|
|
}
|
|
|
|
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 {
|
|
var printer = Printer{
|
|
.import_records = import_records,
|
|
.options = opts,
|
|
.writer = writer,
|
|
.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;
|
|
}
|
|
|
|
fn printDevServerModule(
|
|
p: *Printer,
|
|
source: *const logger.Source,
|
|
ast: *const Ast,
|
|
part: *const js_ast.Part,
|
|
) void {
|
|
p.indent();
|
|
p.printIndent();
|
|
|
|
p.printStringLiteralUTF8(source.path.pretty, false);
|
|
|
|
const func = part.stmts[0].data.s_expr.value.data.e_function.func;
|
|
|
|
// Special-case lazy-export AST
|
|
if (ast.has_lazy_export) {
|
|
@branchHint(.unlikely);
|
|
p.printFnArgs(func.open_parens_loc, func.args, func.flags.contains(.has_rest_arg), false);
|
|
p.printSpace();
|
|
p.print("{\n");
|
|
if (func.body.stmts[0].data.s_lazy_export.* != .e_undefined) {
|
|
p.indent();
|
|
p.printIndent();
|
|
p.printSymbol(p.options.hmr_ref);
|
|
p.print(".cjs.exports = ");
|
|
p.printExpr(.{
|
|
.data = func.body.stmts[0].data.s_lazy_export.*,
|
|
.loc = func.body.stmts[0].loc,
|
|
}, .comma, .{});
|
|
p.print("; // bun .s_lazy_export\n");
|
|
p.unindent();
|
|
}
|
|
p.printIndent();
|
|
p.print("},\n");
|
|
return;
|
|
}
|
|
|
|
// ESM is represented by an array tuple [ dependencies, exports, starImports, load, async ];
|
|
else if (ast.exports_kind == .esm) {
|
|
p.print(": [ [");
|
|
// Print the dependencies.
|
|
if (part.stmts.len > 1) {
|
|
p.indent();
|
|
p.print("\n");
|
|
for (part.stmts[1..]) |stmt| {
|
|
p.printIndent();
|
|
const import = stmt.data.s_import;
|
|
const record = p.importRecord(import.import_record_index);
|
|
p.printStringLiteralUTF8(record.path.pretty, false);
|
|
|
|
const item_count = @as(u32, @intFromBool(import.default_name != null)) +
|
|
@as(u32, @intCast(import.items.len));
|
|
p.fmt(", {d},", .{item_count}) catch {};
|
|
if (item_count == 0) {
|
|
// Add a comment explaining why the number could be zero
|
|
p.print(if (import.star_name_loc != null) " // namespace import" else " // bare import");
|
|
} else {
|
|
if (import.default_name != null) {
|
|
p.print(" \"default\",");
|
|
}
|
|
for (import.items) |item| {
|
|
p.print(" ");
|
|
p.printStringLiteralUTF8(item.alias, false);
|
|
p.print(",");
|
|
}
|
|
}
|
|
p.print("\n");
|
|
}
|
|
p.unindent();
|
|
p.printIndent();
|
|
}
|
|
p.print("], [");
|
|
|
|
// Print the exports
|
|
if (ast.named_exports.count() > 0) {
|
|
p.indent();
|
|
var len: usize = std.math.maxInt(usize);
|
|
for (ast.named_exports.keys()) |key| {
|
|
if (len > 120) {
|
|
p.printNewline();
|
|
p.printIndent();
|
|
len = 0;
|
|
} else {
|
|
p.print(" ");
|
|
}
|
|
len += key.len;
|
|
p.printStringLiteralUTF8(key, false);
|
|
p.print(",");
|
|
}
|
|
p.unindent();
|
|
p.printNewline();
|
|
p.printIndent();
|
|
}
|
|
p.print("], [");
|
|
|
|
// Print export stars
|
|
p.indent();
|
|
var had_any_stars = false;
|
|
for (ast.export_star_import_records) |star| {
|
|
const record = p.importRecord(star);
|
|
if (record.path.is_disabled) continue;
|
|
had_any_stars = true;
|
|
p.printNewline();
|
|
p.printIndent();
|
|
p.printStringLiteralUTF8(record.path.pretty, false);
|
|
p.print(",");
|
|
}
|
|
p.unindent();
|
|
if (had_any_stars) {
|
|
p.printNewline();
|
|
p.printIndent();
|
|
}
|
|
p.print("], ");
|
|
|
|
// Print the code
|
|
if (!ast.top_level_await_keyword.isEmpty()) p.print("async");
|
|
p.printFnArgs(func.open_parens_loc, func.args, func.flags.contains(.has_rest_arg), false);
|
|
p.print(" => {\n");
|
|
p.indent();
|
|
p.printBlockBody(func.body.stmts, TopLevel.init(.no));
|
|
p.unindent();
|
|
p.printIndent();
|
|
p.print("}, ");
|
|
|
|
// Print isAsync
|
|
p.print(if (!ast.top_level_await_keyword.isEmpty()) "true" else "false");
|
|
p.print("],\n");
|
|
} else {
|
|
bun.assert(ast.exports_kind == .cjs);
|
|
p.printFunc(func);
|
|
p.print(",\n");
|
|
}
|
|
|
|
p.unindent();
|
|
}
|
|
};
|
|
}
|
|
|
|
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: u64) anyerror![*]u8,
|
|
comptime advanceBy: fn (ctx: *ContextType, count: u64) 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 stdWriter(self: *Self) std.Io.GenericWriter(*Self, error{}, stdWriterWrite) {
|
|
return .{ .context = self };
|
|
}
|
|
pub fn stdWriterWrite(self: *Self, bytes: []const u8) error{}!usize {
|
|
self.print([]const u8, bytes);
|
|
return bytes.len;
|
|
}
|
|
|
|
pub fn isCopyFileRangeSupported() bool {
|
|
return comptime std.meta.hasFn(ContextType, "copyFileRange");
|
|
}
|
|
|
|
pub fn copyFileRange(ctx: ContextType, in_file: StoredFileDescriptorType, start: usize, end: usize) !void {
|
|
ctx.sendfile(
|
|
in_file,
|
|
start,
|
|
end,
|
|
);
|
|
}
|
|
|
|
pub fn getMutableBuffer(this: *Self) *MutableString {
|
|
return this.ctx.getMutableBuffer();
|
|
}
|
|
|
|
pub fn takeBuffer(this: *Self) MutableString {
|
|
return this.ctx.takeBuffer();
|
|
}
|
|
|
|
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(bun.callmod_inline, getLastByte, .{&writer.ctx});
|
|
}
|
|
|
|
pub inline fn prevPrevChar(writer: *const Self) u8 {
|
|
return @call(bun.callmod_inline, getLastLastByte, .{&writer.ctx});
|
|
}
|
|
|
|
pub fn reserve(writer: *Self, count: u64) anyerror![*]u8 {
|
|
return try reserveNext(&writer.ctx, count);
|
|
}
|
|
|
|
pub fn advance(writer: *Self, count: u64) 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 {
|
|
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;
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn flush(writer: *Self) !void {
|
|
if (std.meta.hasFn(ContextType, "flush")) {
|
|
try writer.ctx.flush();
|
|
}
|
|
}
|
|
pub fn done(writer: *Self) !void {
|
|
if (std.meta.hasFn(ContextType, "done")) {
|
|
try writer.ctx.done();
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
pub const DirectWriter = struct {
|
|
handle: FileDescriptorType,
|
|
|
|
pub fn write(writer: *DirectWriter, buf: []const u8) !usize {
|
|
return try std.posix.write(writer.handle, buf);
|
|
}
|
|
|
|
pub fn writeAll(writer: *DirectWriter, buf: []const u8) !void {
|
|
_ = try std.posix.write(writer.handle, buf);
|
|
}
|
|
|
|
pub const Error = std.posix.WriteError;
|
|
};
|
|
|
|
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 getMutableBuffer(this: *BufferWriter) *MutableString {
|
|
return &this.buffer;
|
|
}
|
|
|
|
pub fn takeBuffer(this: *BufferWriter) MutableString {
|
|
defer this.buffer = .initEmpty(this.buffer.allocator);
|
|
return this.buffer;
|
|
}
|
|
|
|
pub fn getWritten(this: *BufferWriter) []u8 {
|
|
return this.buffer.list.items;
|
|
}
|
|
|
|
pub fn init(allocator: std.mem.Allocator) BufferWriter {
|
|
return BufferWriter{
|
|
.buffer = MutableString.initEmpty(allocator),
|
|
};
|
|
}
|
|
|
|
pub fn print(ctx: *BufferWriter, comptime fmt: string, args: anytype) anyerror!void {
|
|
try ctx.buffer.list.writer(ctx.buffer.allocator).print(fmt, args);
|
|
}
|
|
|
|
pub fn writeByteNTimes(ctx: *BufferWriter, byte: u8, n: usize) anyerror!void {
|
|
try ctx.buffer.appendCharNTimes(byte, n);
|
|
}
|
|
pub const splatByteAll = writeByteNTimes;
|
|
|
|
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: u64) 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: u64) void {
|
|
if (comptime Environment.isDebug) bun.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;
|
|
ctx.written = &.{};
|
|
}
|
|
|
|
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.sliceWithSentinel();
|
|
ctx.written = ctx.buffer.slice();
|
|
} else {
|
|
ctx.written = ctx.buffer.slice();
|
|
}
|
|
}
|
|
|
|
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 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 = .init(
|
|
opts.source_map_allocator orelse opts.allocator,
|
|
is_bun_platform and generate_source_map == .lazy,
|
|
),
|
|
.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.source_map_allocator orelse opts.allocator,
|
|
source.contents,
|
|
@as(
|
|
i32,
|
|
@intCast(tree.approximate_newline_count),
|
|
),
|
|
);
|
|
break :brk .empty;
|
|
},
|
|
};
|
|
}
|
|
|
|
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, opts.module_type);
|
|
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.pdq(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,
|
|
);
|
|
const 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 {
|
|
if (comptime generate_source_map) {
|
|
printer.source_map_builder.line_offset_tables.deinit(opts.allocator);
|
|
}
|
|
}
|
|
printer.was_lazy_export = tree.has_lazy_export;
|
|
if (PrinterType.may_have_module_info) {
|
|
printer.module_info = opts.module_info;
|
|
}
|
|
var bin_stack_heap = std.heap.stackFallback(1024, bun.default_allocator);
|
|
printer.binary_expression_stack = std.array_list.Managed(PrinterType.BinaryExpressionVisitor).init(bin_stack_heap.get());
|
|
defer printer.binary_expression_stack.clearAndFree();
|
|
|
|
if (!opts.bundling and
|
|
tree.uses_require_ref and
|
|
tree.exports_kind == .esm and
|
|
opts.target == .bun)
|
|
{
|
|
// Hoist the `var {require}=import.meta;` declaration. Previously,
|
|
// `import.meta.require` was inlined into transpiled files, which
|
|
// meant calling `func.toString()` on a function with `require`
|
|
// would observe `import.meta.require` inside of the source code.
|
|
// Normally, Bun doesn't guarantee `Function.prototype.toString`
|
|
// will match the untranspiled source code, but in this case the new
|
|
// code is not valid outside of an ES module (eg, in `new Function`)
|
|
// https://github.com/oven-sh/bun/issues/15738#issuecomment-2574283514
|
|
//
|
|
// This is never a symbol collision because `uses_require_ref` means
|
|
// `require` must be an unbound variable.
|
|
printer.print("var {require}=import.meta;");
|
|
|
|
if (PrinterType.may_have_module_info) {
|
|
if (printer.moduleInfo()) |mi| {
|
|
mi.flags.contains_import_meta = true;
|
|
bun.handleOom(mi.addVar(bun.handleOom(mi.str("require")), .declared));
|
|
}
|
|
}
|
|
}
|
|
|
|
for (tree.parts.slice()) |part| {
|
|
for (part.stmts) |stmt| {
|
|
try printer.printStmt(stmt, PrinterType.TopLevel.init(.yes));
|
|
if (printer.writer.getError()) {} else |err| {
|
|
return err;
|
|
}
|
|
printer.printSemicolonIfNeeded();
|
|
}
|
|
}
|
|
|
|
const have_module_info = PrinterType.may_have_module_info and opts.module_info != null;
|
|
if (have_module_info) {
|
|
try opts.module_info.?.finalize();
|
|
}
|
|
|
|
var source_maps_chunk: ?SourceMap.Chunk = if (comptime generate_source_map)
|
|
if (opts.source_map_handler != null)
|
|
printer.source_map_builder.generateChunk(printer.writer.ctx.getWritten())
|
|
else
|
|
null
|
|
else
|
|
null;
|
|
defer if (source_maps_chunk) |*chunk| chunk.deinit();
|
|
|
|
if (opts.runtime_transpiler_cache) |cache| {
|
|
var srlz_res = std.array_list.Managed(u8).init(bun.default_allocator);
|
|
defer srlz_res.deinit();
|
|
if (have_module_info) try opts.module_info.?.asDeserialized().serialize(srlz_res.writer());
|
|
cache.put(printer.writer.ctx.getWritten(), if (source_maps_chunk) |chunk| chunk.buffer.list.items else "", srlz_res.items);
|
|
}
|
|
|
|
if (comptime generate_source_map) {
|
|
if (opts.source_map_handler) |handler| {
|
|
try handler.onSourceMapChunk(source_maps_chunk.?, 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,
|
|
opts: Options,
|
|
) !usize {
|
|
const PrinterType = NewPrinter(false, Writer, false, false, true, false);
|
|
const writer = _writer;
|
|
var s_expr = S.SExpr{ .value = expr };
|
|
const 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);
|
|
const list = js_ast.Symbol.List.fromBorrowedSliceDangerous(ast.symbols.slice());
|
|
const nested_list = js_ast.Symbol.NestedList.fromBorrowedSliceDangerous(&.{list});
|
|
var renamer = rename.NoOpRenamer.init(js_ast.Symbol.Map.initList(nested_list), source);
|
|
|
|
var printer = PrinterType.init(
|
|
writer,
|
|
ast.import_records.slice(),
|
|
opts,
|
|
renamer.toRenamer(),
|
|
undefined,
|
|
);
|
|
var bin_stack_heap = std.heap.stackFallback(1024, bun.default_allocator);
|
|
printer.binary_expression_stack = std.array_list.Managed(PrinterType.BinaryExpressionVisitor).init(bin_stack_heap.get());
|
|
defer printer.binary_expression_stack.clearAndFree();
|
|
|
|
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.perf.trace("JSPrinter.print");
|
|
defer trace.end();
|
|
|
|
const buffer_writer = BufferWriter.init(allocator);
|
|
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 prev_action = bun.crash_handler.current_action;
|
|
defer bun.crash_handler.current_action = prev_action;
|
|
bun.crash_handler.current_action = .{ .print = source.path.text };
|
|
|
|
const PrinterType = NewPrinter(
|
|
// if it's bun, it is also ascii_only
|
|
is_bun_platform,
|
|
Writer,
|
|
false,
|
|
is_bun_platform,
|
|
false,
|
|
generate_source_maps,
|
|
);
|
|
var printer = PrinterType.init(
|
|
writer,
|
|
import_records,
|
|
opts,
|
|
renamer,
|
|
getSourceMapBuilder(if (generate_source_maps) .eager else .disable, is_bun_platform, opts, source, &ast),
|
|
);
|
|
printer.was_lazy_export = ast.has_lazy_export;
|
|
if (PrinterType.may_have_module_info) {
|
|
printer.module_info = opts.module_info;
|
|
}
|
|
var bin_stack_heap = std.heap.stackFallback(1024, bun.default_allocator);
|
|
printer.binary_expression_stack = std.array_list.Managed(PrinterType.BinaryExpressionVisitor).init(bin_stack_heap.get());
|
|
defer printer.binary_expression_stack.clearAndFree();
|
|
|
|
defer printer.temporary_bindings.deinit(bun.default_allocator);
|
|
defer writer.* = printer.writer.*;
|
|
|
|
if (opts.module_type == .internal_bake_dev and !source.index.isRuntime()) {
|
|
printer.printDevServerModule(source, &ast, &parts[0]);
|
|
} else {
|
|
// The IIFE wrapper is done in `postProcessJSChunk`, so we just manually
|
|
// trigger an indent.
|
|
if (opts.module_type == .iife) {
|
|
printer.indent();
|
|
}
|
|
|
|
for (parts) |part| {
|
|
for (part.stmts) |stmt| {
|
|
printer.printStmt(stmt, PrinterType.TopLevel.init(.yes)) catch |err| {
|
|
return .{ .err = err };
|
|
};
|
|
if (printer.writer.getError()) {} else |err| {
|
|
return .{ .err = err };
|
|
}
|
|
printer.printSemicolonIfNeeded();
|
|
}
|
|
}
|
|
}
|
|
|
|
printer.writer.done() catch |err| {
|
|
// In bundle_v2, this is backed by an arena, but incremental uses
|
|
// `dev.allocator` for this buffer, so it must be freed.
|
|
printer.source_map_builder.source_map.ctx.data.deinit();
|
|
|
|
return .{ .err = err };
|
|
};
|
|
|
|
const written = printer.writer.ctx.getWritten();
|
|
const source_map: ?SourceMap.Chunk = if (generate_source_maps) brk: {
|
|
if (written.len == 0 or printer.source_map_builder.source_map.shouldIgnore()) {
|
|
printer.source_map_builder.source_map.ctx.data.deinit();
|
|
break :brk null;
|
|
}
|
|
const chunk = printer.source_map_builder.generateChunk(written);
|
|
assert(!chunk.should_ignore);
|
|
break :brk chunk;
|
|
} else null;
|
|
|
|
var buffer: MutableString = printer.writer.takeBuffer();
|
|
|
|
return .{
|
|
.result = .{
|
|
.code = buffer.takeSlice(),
|
|
.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 prev_action = bun.crash_handler.current_action;
|
|
defer bun.crash_handler.current_action = prev_action;
|
|
bun.crash_handler.current_action = .{ .print = source.path.text };
|
|
|
|
const PrinterType = NewPrinter(ascii_only, Writer, true, false, false, generate_source_map);
|
|
const 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),
|
|
);
|
|
var bin_stack_heap = std.heap.stackFallback(1024, bun.default_allocator);
|
|
printer.binary_expression_stack = std.array_list.Managed(PrinterType.BinaryExpressionVisitor).init(bin_stack_heap.get());
|
|
defer printer.binary_expression_stack.clearAndFree();
|
|
|
|
for (tree.parts.slice()) |part| {
|
|
for (part.stmts) |stmt| {
|
|
try printer.printStmt(stmt, PrinterType.TopLevel.init(.yes));
|
|
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| {
|
|
var chunk = printer.source_map_builder.generateChunk(printer.writer.ctx.getWritten());
|
|
defer chunk.deinit();
|
|
try handler.onSourceMapChunk(chunk, source);
|
|
}
|
|
}
|
|
|
|
try printer.writer.done();
|
|
|
|
return @as(usize, @intCast(@max(printer.writer.written, 0)));
|
|
}
|
|
|
|
/// Serializes ModuleInfo to an owned byte slice. Returns null on failure.
|
|
/// The caller is responsible for freeing the returned slice with bun.default_allocator.
|
|
pub fn serializeModuleInfo(module_info: ?*analyze_transpiled_module.ModuleInfo) ?[]const u8 {
|
|
const mi = module_info orelse return null;
|
|
if (!mi.finalized) {
|
|
mi.finalize() catch return null;
|
|
}
|
|
const deserialized = mi.asDeserialized();
|
|
var buf: std.ArrayList(u8) = .empty;
|
|
defer buf.deinit(bun.default_allocator);
|
|
deserialized.serialize(buf.writer(bun.default_allocator)) catch return null;
|
|
return buf.toOwnedSlice(bun.default_allocator) catch null;
|
|
}
|
|
|
|
const string = []const u8;
|
|
|
|
const SourceMap = @import("./sourcemap/sourcemap.zig");
|
|
const analyze_transpiled_module = @import("./analyze_transpiled_module.zig");
|
|
const fs = @import("./fs.zig");
|
|
const importRecord = @import("./import_record.zig");
|
|
const options = @import("./options.zig");
|
|
const rename = @import("./renamer.zig");
|
|
const runtime = @import("./runtime.zig");
|
|
const std = @import("std");
|
|
|
|
const bun = @import("bun");
|
|
const Environment = bun.Environment;
|
|
const FeatureFlags = bun.FeatureFlags;
|
|
const FileDescriptorType = bun.FileDescriptor;
|
|
const ImportRecord = bun.ImportRecord;
|
|
const MutableString = bun.MutableString;
|
|
const Output = bun.Output;
|
|
const StoredFileDescriptorType = bun.StoredFileDescriptorType;
|
|
const assert = bun.assert;
|
|
const default_allocator = bun.default_allocator;
|
|
const js_lexer = bun.js_lexer;
|
|
const logger = bun.logger;
|
|
const api = bun.schema.api;
|
|
|
|
const js_ast = bun.ast;
|
|
const Ast = js_ast.Ast;
|
|
const B = js_ast.B;
|
|
const Binding = js_ast.Binding;
|
|
const E = js_ast.E;
|
|
const Expr = js_ast.Expr;
|
|
const G = js_ast.G;
|
|
const Ref = bun.ast.Ref;
|
|
const S = js_ast.S;
|
|
const Stmt = js_ast.Stmt;
|
|
const Symbol = js_ast.Symbol;
|
|
|
|
const Op = js_ast.Op;
|
|
const Level = js_ast.Op.Level;
|
|
|
|
const strings = bun.strings;
|
|
const CodepointIterator = bun.strings.UnsignedCodepointIterator;
|