From 90df99bc5f5e53cf0890596e5c578086eeda7c4e Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Wed, 8 Feb 2023 21:19:43 -0800 Subject: [PATCH 001/255] wip --- src/ast/base.zig | 173 +- src/baby_list.zig | 72 +- src/{install => }/bit_set.zig | 60 + src/bun.js/api/transpiler.zig | 4 +- src/bun.js/event_loop.zig | 216 +- src/bun.js/javascript_core_c_api.zig | 2 + src/bun.js/module_loader.zig | 18 +- src/bun.zig | 31 +- src/bundler.zig | 129 +- src/bundler/bundle_v2.zig | 4490 ++++++++++++++++++ src/bundler/generate_node_modules_bundle.zig | 11 +- src/c.zig | 2 + src/cache.zig | 2 +- src/cli.zig | 83 +- src/cli/bun_command.zig | 264 +- src/defines-table.zig | 16 + src/fs.zig | 27 +- src/import_record.zig | 40 +- src/install/lockfile.zig | 2 +- src/install/semver.zig | 2 +- src/js_ast.zig | 245 +- src/js_parser.zig | 1344 +++--- src/js_printer.zig | 10 +- src/json_parser.zig | 8 +- src/linker.zig | 20 +- src/logger.zig | 38 +- src/multi_array_list.zig | 720 +++ src/options.zig | 23 + src/output.zig | 44 +- src/renamer.zig | 23 + src/resolver/resolver.zig | 25 +- src/runtime.js | 93 +- src/string_builder.zig | 16 +- src/string_immutable.zig | 103 +- src/string_mutable.zig | 7 +- src/thread_pool.zig | 251 + src/util.zig | 331 ++ 37 files changed, 7807 insertions(+), 1138 deletions(-) rename src/{install => }/bit_set.zig (97%) create mode 100644 src/bundler/bundle_v2.zig create mode 100644 src/multi_array_list.zig create mode 100644 src/util.zig diff --git a/src/ast/base.zig b/src/ast/base.zig index f3cc34925abe..509f38c5b290 100644 --- a/src/ast/base.zig +++ b/src/ast/base.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const bun = @import("bun"); const unicode = std.unicode; pub const JavascriptString = []u16; @@ -126,11 +127,91 @@ pub inline fn getBits(comptime TargetType: type, target: anytype, comptime start return @truncate(TargetType, target >> start_bit); } -pub const Ref = enum(TotalSize) { - default = std.math.maxInt(TotalSize), - _, +/// In some parts of Bun, we have many different IDs pointing to different things. +/// It's easy for them to get mixed up, so we use this type to make sure we don't. +/// +pub const Index = packed struct(u32) { + value: Int, + + pub fn set(this: *Index, val: Int) void { + this.value = val; + } + + pub inline fn isRuntime(this: Index) bool { + return this.value == runtime.value; + } + + pub const invalid = Index{ .value = std.math.maxInt(Int) }; + pub const runtime = Index{ + .value = 0, + }; + + pub const Int = u32; + + pub inline fn source(num: anytype) Index { + return .{ .value = @truncate(Int, num) }; + } + + pub inline fn part(num: anytype) Index { + return .{ .value = @truncate(Int, num) }; + } + + pub fn init(num: anytype) Index { + const NumType = @TypeOf(num); + if (comptime @typeInfo(NumType) == .Pointer) { + return init(num.*); + } + + if (comptime bun.Environment.allow_assert) { + return .{ + .value = @intCast(Int, num), + }; + } + + return .{ + .value = @intCast(Int, num), + }; + } + + pub inline fn isValid(this: Index) bool { + return this.value != invalid.value; + } + + pub inline fn isInvalid(this: Index) bool { + return !this.isValid(); + } + + pub inline fn get(this: Index) Int { + return this.value; + } +}; + +pub const Ref = packed struct(u64) { + tag: enum(u2) { + invalid, + allocated_name, + source_contents_slice, + symbol, + } = .invalid, + + source_index: Int = 0, + inner_index: Int = 0, - pub const TotalSize = u62; + pub const ArrayHashCtx = RefHashCtx; + + pub const Int = std.meta.Int(.unsigned, (64 - 2) / 2); + + pub fn toInt(value: anytype) Int { + return @intCast(Int, value); + } + + pub fn isSourceIndexNull(this: anytype) bool { + return this == std.math.maxInt(Int); + } + + pub fn isSymbol(this: Ref) bool { + return this.tag == .symbol; + } pub fn format(ref: Ref, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { try std.fmt.format( @@ -144,76 +225,40 @@ pub const Ref = enum(TotalSize) { ); } - const max_ref_int = std.math.maxInt(Ref.Int); - pub const BitInt = std.meta.Int(.unsigned, @bitSizeOf(Ref)); - - pub inline fn asBitInt(this: Ref) BitInt { - return @bitCast(BitInt, this); + pub fn isValid(this: Ref) bool { + return this.tag != .invalid; } // 2 bits of padding for whatever is the parent - pub const Int = u30; - pub const None = Ref.init(std.math.maxInt(u30), std.math.maxInt(u30), false); - pub const RuntimeRef = Ref.init(std.math.maxInt(u30), std.math.maxInt(u30) - 1, false); - - const source_index_offset = 1; - const inner_index_offset = 1 + 30; + pub const None = Ref{ .inner_index = 0, .source_index = 0, .tag = .invalid }; + pub const RuntimeRef = Ref{ .inner_index = 0, .source_index = 0, .tag = .runtime }; pub inline fn sourceIndex(this: Ref) Int { - return @truncate(Int, getBits(TotalSize, @enumToInt(this), source_index_offset, 30)); + return this.source_index; } pub inline fn innerIndex(this: Ref) Int { - return @truncate(Int, getBits(TotalSize, @enumToInt(this), inner_index_offset, 30)); + return this.inner_index; } pub inline fn isSourceContentsSlice(this: Ref) bool { - return (getBits(TotalSize, @enumToInt(this), 0, 1) & 1) != 0; - } - - pub fn atIndex(value: anytype) Ref { - return @intToEnum(Ref, setBits(TotalSize, 0, inner_index_offset, 30, @truncate(Int, value))); - } - - pub fn init(inner_index: Int, source_index: Int, is_source_contents_slice: bool) Ref { - return @intToEnum( - Ref, - setBits( - TotalSize, - 0, - 0, - 1, - @as( - TotalSize, - @boolToInt(is_source_contents_slice), - ), - ) | setBits( - TotalSize, - 0, - source_index_offset, - 30, - source_index, - ) | setBits( - TotalSize, - 0, - inner_index_offset, - 30, - inner_index, - ), - ); + return this.tag == .source_contents_slice; } - const Old = struct { - inner_index: Int = 0, - source_index: Int = std.math.maxInt(Int), - is_source_contents_slice: bool = false, - }; - pub fn initSourceEnd(old: Old) Ref { - return init(old.inner_index, old.source_index, old.is_source_contents_slice); + pub fn init(inner_index: Int, source_index: usize, is_source_contents_slice: bool) Ref { + return .{ + .inner_index = inner_index, + + // if we overflow, we want a panic + .source_index = @intCast(Int, source_index), + + .tag = if (is_source_contents_slice) .source_contents_slice else .allocated_name, + }; } - pub fn toInt(int: anytype) Int { - return @intCast(Int, int); + pub fn initSourceEnd(old: Ref) Ref { + std.debug.assert(old.tag != .invalid); + return init(old.inner_index, old.source_index, old.tag == .source_contents_slice); } pub fn hash(key: Ref) u32 { @@ -227,22 +272,18 @@ pub const Ref = enum(TotalSize) { // but we want to ensure that the value of the unused bits in the u64 are 0 // i have not looked at the assembly to verify that the unused bits default to 0 // so we set it to u64 0 just to be sure - return @as(u64, @enumToInt(key)); + return @bitCast(u64, key); } pub inline fn hash64(key: Ref) u64 { - return std.hash.Wyhash.hash(0, &@bitCast([8]u8, key.asU64())); + return std.hash.Wyhash.hash(0, &@bitCast([8]u8, @bitCast(u64, key))); } pub fn eql(ref: Ref, b: Ref) bool { return asU64(ref) == b.asU64(); } pub inline fn isNull(self: Ref) bool { - return self.eql(Ref.None); - } - - pub fn isSourceIndexNull(int: anytype) bool { - return int == max_ref_int; + return self.tag == .invalid; } pub fn jsonStringify(self: *const Ref, options: anytype, writer: anytype) !void { diff --git a/src/baby_list.zig b/src/baby_list.zig index 99844d72e778..11aac67c1450 100644 --- a/src/baby_list.zig +++ b/src/baby_list.zig @@ -5,8 +5,6 @@ const bun = @import("bun"); /// This is like ArrayList except it stores the length and capacity as u32 /// In practice, it is very unusual to have lengths above 4 GB -/// -/// This lets us have array lists which occupy the same amount of space as a slice pub fn BabyList(comptime Type: type) type { return struct { const ListType = @This(); @@ -14,6 +12,14 @@ pub fn BabyList(comptime Type: type) type { len: u32 = 0, cap: u32 = 0, + pub const Elem = Type; + + pub fn set(this: *@This(), slice_: []Type) void { + this.ptr = slice_.ptr; + this.len = @truncate(u32, slice_.len); + this.cap = @truncate(u32, slice_.len); + } + pub fn available(this: *@This()) []Type { return this.ptr[this.len..this.cap]; } @@ -28,6 +34,7 @@ pub fn BabyList(comptime Type: type) type { } pub inline fn initConst(items: []const Type) ListType { + @setRuntimeSafety(false); return ListType{ // Remove the const qualifier from the items .ptr = @intToPtr([*]Type, @ptrToInt(items.ptr)), @@ -37,9 +44,48 @@ pub fn BabyList(comptime Type: type) type { }; } - pub inline fn init(items: []Type) ListType { + pub fn ensureUnusedCapacity(this: *@This(), allocator: std.mem.Allocator, count: usize) !void { + var list_ = this.listManaged(allocator); + try list_.ensureUnusedCapacity(count); + this.update(list_); + } + + pub fn popOrNull(this: *@This()) ?Type { + if (this.len == 0) return null; + this.len -= 1; + return this.ptr[this.len]; + } + + pub inline fn appendAssumeCapacity(this: *@This(), value: Type) void { + this.ptr[this.len] = value; + this.len += 1; + } + + pub inline fn appendSliceAssumeCapacity(this: *@This(), values: []const Type) void { + var tail = this.ptr[this.len .. this.len + values.len]; + std.debug.assert(this.cap >= this.len + @truncate(u32, values.len)); + for (values) |value| { + tail[0] = value; + tail = tail[1..]; + } + this.len += @truncate(u32, values.len); + } + + pub inline fn initCapacity(allocator: std.mem.Allocator, len: usize) !ListType { + var items = try allocator.alloc(Type, len); + return ListType{ + .ptr = @ptrCast([*]Type, items.ptr), + .len = 0, + .cap = @truncate(u32, len), + }; + } + + pub inline fn init(items: []const Type) ListType { + @setRuntimeSafety(false); return ListType{ - .ptr = items.ptr, + // Remove the const qualifier from the items + .ptr = @intToPtr([*]Type, @ptrToInt(items.ptr)), + .len = @truncate(u32, items.len), .cap = @truncate(u32, items.len), }; @@ -96,6 +142,16 @@ pub fn BabyList(comptime Type: type) type { return this.ptr[0]; } + pub inline fn at(this: ListType, index: usize) *const Type { + std.debug.assert(index < this.len); + return &this.ptr[index]; + } + + pub inline fn mut(this: ListType, index: usize) *Type { + std.debug.assert(index < this.len); + return &this.ptr[index]; + } + pub fn one(allocator: std.mem.Allocator, value: Type) !ListType { var items = try allocator.alloc(Type, 1); items[0] = value; @@ -117,10 +173,10 @@ pub fn BabyList(comptime Type: type) type { this.update(list_); } - pub fn append(this: *ListType, allocator: std.mem.Allocator, value: []const Type) OOM!void { - var list_ = this.list(); - try list_.appendSlice(allocator, value); - this.update(list_); + pub fn append(this: *@This(), allocator: std.mem.Allocator, value: []const Type) !void { + var list__ = this.listManaged(allocator); + try list__.appendSlice(value); + this.update(list__); } pub inline fn slice(this: ListType) []Type { diff --git a/src/install/bit_set.zig b/src/bit_set.zig similarity index 97% rename from src/install/bit_set.zig rename to src/bit_set.zig index bd6d71027f20..b18083cc4561 100644 --- a/src/install/bit_set.zig +++ b/src/bit_set.zig @@ -781,6 +781,10 @@ pub const DynamicBitSetUnmanaged = struct { return (self.masks[maskIndex(index)] & maskBit(index)) != 0; } + pub fn bytes(self: Self) []const u8 { + return std.mem.sliceAsBytes(self.masks[0 .. numMasks(self.bit_length) + 1]); + } + /// Returns the total number of set bits in this bit set. pub fn count(self: Self) usize { const num_masks = (self.bit_length + (@bitSizeOf(MaskInt) - 1)) / @bitSizeOf(MaskInt); @@ -1050,6 +1054,62 @@ pub const DynamicBitSetUnmanaged = struct { } }; +pub const AutoBitSet = union(enum) { + const Static = ArrayBitSet(usize, (@bitSizeOf(DynamicBitSetUnmanaged) - 1)); + + static: Static, + dynamic: DynamicBitSetUnmanaged, + + pub inline fn needsDynamic(bit_length: usize) bool { + return bit_length > Static.bit_length; + } + + pub fn initEmpty(allocator: Allocator, bit_length: usize) !AutoBitSet { + if (bit_length <= Static.bit_length) { + return AutoBitSet{ .static = Static.initEmpty() }; + } else { + return AutoBitSet{ .dynamic = try DynamicBitSetUnmanaged.initEmpty(allocator, bit_length) }; + } + } + + pub fn isSet(this: *const AutoBitSet, index: usize) bool { + return switch (std.meta.activeTag(this.*)) { + .static => this.static.isSet(index), + .dynamic => this.dynamic.*.isSet(index), + }; + } + + pub fn clone(this: *const AutoBitSet, allocator: std.mem.Allocator) AutoBitSet { + return switch (std.meta.activeTag(this.*)) { + .static => AutoBitSet{ .static = this.static }, + .dynamic => AutoBitSet{ .dynamic = this.dynamic.*.clone(allocator) }, + }; + } + + pub fn set(this: *const AutoBitSet, index: usize) void { + switch (std.meta.activeTag(this.*)) { + .static => this.static.set(index), + .dynamic => this.dynamic.set(index), + } + } + + pub fn bytes(this: *const AutoBitSet) []const u8 { + return switch (std.meta.activeTag(this.*)) { + .static => std.mem.asBytes(&this.static.masks), + .dynamic => this.dynamic.*.bytes(), + }; + } + + pub fn deinit(this: *AutoBitSet, allocator: std.mem.Allocator) void { + switch (std.meta.activeTag(this.*)) { + .static => {}, + .dynamic => { + this.dynamic.deinit(allocator); + }, + } + } +}; + /// A bit set with runtime-known size, backed by an allocated slice /// of usize. Thin wrapper around DynamicBitSetUnmanaged which keeps /// track of the allocator instance. diff --git a/src/bun.js/api/transpiler.zig b/src/bun.js/api/transpiler.zig index eb070b6bc486..e6b435ef0f9c 100644 --- a/src/bun.js/api/transpiler.zig +++ b/src/bun.js/api/transpiler.zig @@ -838,7 +838,7 @@ fn getParseResult(this: *Transpiler, allocator: std.mem.Allocator, code: []const // necessary because we don't run the linker if (parse_result) |*res| { - for (res.ast.import_records) |*import| { + for (res.ast.import_records.slice()) |*import| { if (import.kind.isCommonJS()) { import.do_commonjs_transform_in_printer = true; import.module_id = @truncate(u32, std.hash.Wyhash.hash(0, import.path.pretty)); @@ -923,7 +923,7 @@ pub fn scan( const imports_label = JSC.ZigString.static("imports"); const named_imports_value = namedImportsToJS( globalThis, - parse_result.ast.import_records, + parse_result.ast.import_records.slice(), exception, ); if (exception.* != null) { diff --git a/src/bun.js/event_loop.zig b/src/bun.js/event_loop.zig index 9c2627fdacb3..3576bcfac605 100644 --- a/src/bun.js/event_loop.zig +++ b/src/bun.js/event_loop.zig @@ -154,7 +154,9 @@ pub const AnyTask = struct { pub fn run(this: *AnyTask) void { @setRuntimeSafety(false); - this.callback(this.ctx.?); + var callback = this.callback; + var ctx = this.ctx; + callback(ctx.?); } pub fn New(comptime Type: type, comptime Callback: anytype) type { @@ -167,7 +169,42 @@ pub const AnyTask = struct { } pub fn wrap(this: ?*anyopaque) void { - Callback(@ptrCast(*Type, @alignCast(@alignOf(Type), this.?))); + @call(.always_inline, Callback, .{@ptrCast(*Type, @alignCast(@alignOf(Type), this.?))}); + } + }; + } +}; + +pub const AnyTaskWithExtraContext = struct { + ctx: ?*anyopaque, + callback: *const (fn (*anyopaque, *anyopaque) void), + next: ?*AnyTaskWithExtraContext = null, + + pub fn run(this: *AnyTaskWithExtraContext, extra: *anyopaque) void { + @setRuntimeSafety(false); + var callback = this.callback; + var ctx = this.ctx; + callback(ctx.?, extra); + } + + pub fn New(comptime Type: type, comptime ContextType: type, comptime Callback: anytype) type { + return struct { + pub fn init(ctx: *Type) AnyTaskWithExtraContext { + return AnyTaskWithExtraContext{ + .callback = wrap, + .ctx = ctx, + }; + } + + pub fn wrap(this: ?*anyopaque, extra: ?*anyopaque) void { + @call( + .always_inline, + Callback, + .{ + @ptrCast(*Type, @alignCast(@alignOf(Type), this.?)), + @ptrCast(*ContextType, @alignCast(@alignOf(ContextType), extra.?)), + }, + ); } }; } @@ -645,3 +682,178 @@ pub const EventLoop = struct { } } }; + +pub const MiniEventLoop = struct { + tasks: Queue, + concurrent_tasks: UnboundedQueue(AnyTaskWithExtraContext, .next) = .{}, + loop: *uws.Loop, + allocator: std.mem.Allocator, + + const Queue = std.fifo.LinearFifo(*AnyTaskWithExtraContext, .Dynamic); + + pub const Task = AnyTaskWithExtraContext; + + pub fn init( + allocator: std.mem.Allocator, + ) MiniEventLoop { + return .{ + .tasks = Queue.init(allocator), + .allocator = allocator, + .loop = uws.Loop.get().?, + }; + } + + pub fn deinit(this: *MiniEventLoop) void { + this.tasks.deinit(); + std.debug.assert(this.concurrent_tasks.isEmpty()); + } + + pub fn tickConcurrentWithCount(this: *MiniEventLoop) usize { + var concurrent = this.concurrent_tasks.popBatch(); + const count = concurrent.count; + if (count == 0) + return 0; + + var iter = concurrent.iterator(); + const start_count = this.tasks.count; + if (start_count == 0) { + this.tasks.head = 0; + } + + this.tasks.ensureUnusedCapacity(count) catch unreachable; + var writable = this.tasks.writableSlice(0); + while (iter.next()) |task| { + writable[0] = task; + writable = writable[1..]; + this.tasks.count += 1; + if (writable.len == 0) break; + } + + return this.tasks.count - start_count; + } + + pub fn tick( + this: *MiniEventLoop, + context: *anyopaque, + ) void { + while (true) { + _ = this.tickConcurrentWithCount(); + while (this.tasks.readItem()) |task| { + task.run(context); + } + + if (this.tickConcurrentWithCount() == 0) { + if (this.loop.active > 0 or this.loop.num_polls > 0) { + this.loop.run(); + continue; + } + + break; + } + } + } + + pub fn enqueueJSCTask(this: *MiniEventLoop, task: *MiniEventLoop.Task) void { + this.tasks.writeItem(task) catch unreachable; + } + + pub fn enqueueTask( + this: *MiniEventLoop, + comptime Context: type, + ctx: *Context, + comptime Callback: fn (*Context) void, + comptime field: std.meta.FieldEnum(Context), + ) void { + const TaskType = MiniEventLoop.Task.New(Context, Callback); + @field(ctx, @tagName(field)) = TaskType.init(ctx); + this.enqueueJSCTask(&@field(ctx, @tagName(field))); + } + + pub fn enqueueTaskConcurrent( + this: *MiniEventLoop, + comptime Context: type, + comptime ParentContext: type, + ctx: *Context, + comptime Callback: fn (*Context, *ParentContext) void, + comptime field: std.meta.FieldEnum(Context), + ) void { + JSC.markBinding(@src()); + const TaskType = MiniEventLoop.Task.New(Context, ParentContext, Callback); + @field(ctx, @tagName(field)) = TaskType.init(ctx); + + this.concurrent_tasks.push(&@field(ctx, @tagName(field))); + + this.loop.wakeup(); + } +}; + +pub const AnyEventLoop = union(enum) { + jsc: *EventLoop, + mini: MiniEventLoop, + + pub const Task = AnyTaskWithExtraContext; + + pub fn fromJSC( + this: *AnyEventLoop, + jsc: *EventLoop, + ) void { + this.* = .{ .jsc = jsc }; + } + + pub fn init( + allocator: std.mem.Allocator, + ) AnyEventLoop { + return .{ .mini = MiniEventLoop.init(allocator) }; + } + + // pub fn enqueueTask( + // this: *AnyEventLoop, + // comptime Context: type, + // ctx: *Context, + // comptime Callback: fn (*Context) void, + // comptime field: std.meta.FieldEnum(Context), + // ) void { + // const TaskType = MiniEventLoop.Task.New(Context, Callback); + // @field(ctx, field) = TaskType.init(ctx); + // this.enqueueTaskConcurrent(&@field(ctx, field)); + // } + + pub fn tick( + this: *AnyEventLoop, + context: *anyopaque, + ) void { + switch (this.*) { + .jsc => { + this.jsc.tick(); + this.jsc.autoTick(); + }, + .mini => { + this.mini.tick(context); + }, + } + } + + pub fn enqueueTaskConcurrent( + this: *AnyEventLoop, + comptime Context: type, + comptime ParentContext: type, + ctx: *Context, + comptime Callback: fn (*Context, *ParentContext) void, + comptime field: std.meta.FieldEnum(Context), + ) void { + switch (this.*) { + .jsc => { + unreachable; // TODO: + // const TaskType = AnyTask.New(Context, Callback); + // @field(ctx, field) = TaskType.init(ctx); + // var concurrent = bun.default_allocator.create(ConcurrentTask) catch unreachable; + // _ = concurrent.from(JSC.Task.init(&@field(ctx, field))); + // concurrent.auto_delete = true; + // this.jsc.enqueueTaskConcurrent(concurrent); + }, + .mini => { + this.mini.enqueueTaskConcurrent(Context, ParentContext, ctx, Callback, field); + }, + } + } +}; diff --git a/src/bun.js/javascript_core_c_api.zig b/src/bun.js/javascript_core_c_api.zig index a0c9fc29e46b..f21d5386f290 100644 --- a/src/bun.js/javascript_core_c_api.zig +++ b/src/bun.js/javascript_core_c_api.zig @@ -493,6 +493,8 @@ pub const CellType = enum(u8) { } }; pub const ExternalStringFinalizer = *const fn (finalize_ptr: ?*anyopaque, ref: JSStringRef, buffer: *anyopaque, byteLength: usize) callconv(.C) void; + +/// **DEPRECATED**: USE from JSValue instead! This whole file should be used sparingly. pub extern fn JSStringCreate(string: UTF8Ptr, length: usize) JSStringRef; pub extern fn JSStringCreateStatic(string: UTF8Ptr, length: usize) JSStringRef; pub extern fn JSStringCreateExternal(string: UTF8Ptr, length: usize, finalize_ptr: ?*anyopaque, finalizer: ExternalStringFinalizer) JSStringRef; diff --git a/src/bun.js/module_loader.zig b/src/bun.js/module_loader.zig index 373f546e066e..894575680607 100644 --- a/src/bun.js/module_loader.zig +++ b/src/bun.js/module_loader.zig @@ -637,7 +637,7 @@ pub const ModuleLoader = struct { error_instance.put(globalThis, ZigString.static("name"), ZigString.init(name).withEncoding().toValueGC(globalThis)); error_instance.put(globalThis, ZigString.static("pkg"), ZigString.init(result.name).withEncoding().toValueGC(globalThis)); error_instance.put(globalThis, ZigString.static("specifier"), ZigString.init(this.specifier).withEncoding().toValueGC(globalThis)); - const location = logger.rangeData(&this.parse_result.source, this.parse_result.ast.import_records[import_record_id].range, "").location.?; + const location = logger.rangeData(&this.parse_result.source, this.parse_result.ast.import_records.at(import_record_id).range, "").location.?; error_instance.put(globalThis, ZigString.static("sourceURL"), ZigString.init(this.parse_result.source.path.text).withEncoding().toValueGC(globalThis)); error_instance.put(globalThis, ZigString.static("line"), JSValue.jsNumber(location.line)); if (location.line_text) |line_text| { @@ -731,9 +731,9 @@ pub const ModuleLoader = struct { error_instance.put(globalThis, ZigString.static("referrer"), ZigString.init(this.specifier).withEncoding().toValueGC(globalThis)); } - const location = logger.rangeData(&this.parse_result.source, this.parse_result.ast.import_records[import_record_id].range, "").location.?; + const location = logger.rangeData(&this.parse_result.source, this.parse_result.ast.import_records.at(import_record_id).range, "").location.?; error_instance.put(globalThis, ZigString.static("specifier"), ZigString.init( - this.parse_result.ast.import_records[import_record_id].path.text, + this.parse_result.ast.import_records.at(import_record_id).path.text, ).withEncoding().toValueGC(globalThis)); error_instance.put(globalThis, ZigString.static("sourceURL"), ZigString.init(this.parse_result.source.path.text).withEncoding().toValueGC(globalThis)); error_instance.put(globalThis, ZigString.static("line"), JSValue.jsNumber(location.line)); @@ -1295,10 +1295,10 @@ pub const ModuleLoader = struct { if (!was_printing_plugin) jsc_vm.bundler.resolver.caches.fs.use_alternate_source_cache = !prev; // this is a bad idea, but it should work for now. - const original_name = parse_result.ast.symbols[parse_result.ast.bun_plugin.ref.innerIndex()].original_name; - parse_result.ast.symbols[parse_result.ast.bun_plugin.ref.innerIndex()].original_name = "globalThis.Bun.plugin"; + const original_name = parse_result.ast.symbols.mut(parse_result.ast.bun_plugin.ref.innerIndex()).original_name; + parse_result.ast.symbols.mut(parse_result.ast.bun_plugin.ref.innerIndex()).original_name = "globalThis.Bun.plugin"; defer { - parse_result.ast.symbols[parse_result.ast.bun_plugin.ref.innerIndex()].original_name = original_name; + parse_result.ast.symbols.mut(parse_result.ast.bun_plugin.ref.innerIndex()).original_name = original_name; } const hoisted_stmts = parse_result.ast.bun_plugin.hoisted_stmts.items; @@ -1308,9 +1308,9 @@ pub const ModuleLoader = struct { }, }; var ast_copy = parse_result.ast; - ast_copy.import_records = try jsc_vm.allocator.dupe(ImportRecord, ast_copy.import_records); - defer jsc_vm.allocator.free(ast_copy.import_records); - ast_copy.parts = &parts; + ast_copy.import_records.set(try jsc_vm.allocator.dupe(ImportRecord, ast_copy.import_records.slice())); + defer ast_copy.import_records.deinitWithAllocator(jsc_vm.allocator); + ast_copy.parts.set(&parts); ast_copy.prepend_part = null; var temporary_source = parse_result.source; var source_name = try std.fmt.allocPrint(jsc_vm.allocator, "{s}.plugin.{s}", .{ temporary_source.path.text, temporary_source.path.name.ext[1..] }); diff --git a/src/bun.zig b/src/bun.zig index 7659dd3fbbc7..b12ca169388a 100644 --- a/src/bun.zig +++ b/src/bun.zig @@ -444,6 +444,11 @@ pub fn hash(content: []const u8) u64 { return std.hash.Wyhash.hash(0, content); } +pub fn hash32(content: []const u8) u32 { + const res = hash(content); + return @truncate(u32, res); +} + pub const HiveArray = @import("./hive_array.zig").HiveArray; pub fn rand(bytes: []u8) void { @@ -606,6 +611,20 @@ pub const StringArrayHashMapContext = struct { pub fn eql(_: @This(), a: []const u8, b: []const u8, _: usize) bool { return strings.eqlLong(a, b, true); } + + pub const Prehashed = struct { + value: u32, + input: []const u8, + pub fn hash(this: @This(), s: []const u8) u32 { + if (s.ptr == this.input.ptr and s.len == this.input.len) + return this.value; + return @truncate(u32, std.hash.Wyhash.hash(0, s)); + } + + pub fn eql(_: @This(), a: []const u8, b: []const u8) bool { + return strings.eqlLong(a, b, true); + } + }; }; pub const StringHashMapContext = struct { @@ -743,7 +762,7 @@ pub const js_printer = @import("./js_printer.zig"); pub const js_lexer = @import("./js_lexer.zig"); pub const JSON = @import("./json_parser.zig"); pub const JSAst = @import("./js_ast.zig"); -pub const bit_set = @import("./install/bit_set.zig"); +pub const bit_set = @import("./bit_set.zig"); pub fn enumMap(comptime T: type, comptime args: anytype) (fn (T) []const u8) { const Map = struct { @@ -947,3 +966,13 @@ pub fn cstring(input: []const u8) [:0]const u8 { } pub const Semver = @import("./install/semver.zig"); +pub const ImportRecord = @import("./import_record.zig").ImportRecord; +pub const ImportKind = @import("./import_record.zig").ImportKind; + +pub usingnamespace @import("./util.zig"); +pub const fast_debug_build_mode = false and + Environment.isDebug; + +pub const MultiArrayList = @import("./multi_array_list.zig").MultiArrayList; + +pub const Joiner = @import("./string_joiner.zig"); diff --git a/src/bundler.zig b/src/bundler.zig index 26a75ec43cd9..0aab3d4b9a21 100644 --- a/src/bundler.zig +++ b/src/bundler.zig @@ -381,6 +381,7 @@ pub const Bundler = struct { to.log.* = logger.Log.init(allocator); to.setLog(to.log); to.macro_context = null; + to.linker.resolver = &to.resolver; } pub inline fn getPackageManager(this: *Bundler) *PackageManager { @@ -1137,7 +1138,7 @@ pub const Bundler = struct { comptime enable_source_map: bool, source_map_context: ?js_printer.SourceMapHandler, ) !usize { - var symbols: [][]js_ast.Symbol = &([_][]js_ast.Symbol{ast.symbols}); + var symbols = js_ast.Symbol.NestedList.init(&[_]js_ast.Symbol.List{ast.symbols}); return switch (format) { .cjs => try js_printer.printCommonJS( @@ -1148,7 +1149,6 @@ pub const Bundler = struct { source, false, js_printer.Options{ - .to_module_ref = Ref.RuntimeRef, .externals = ast.externals, .runtime_imports = ast.runtime_imports, .require_ref = ast.require_ref, @@ -1170,7 +1170,6 @@ pub const Bundler = struct { source, false, js_printer.Options{ - .to_module_ref = Ref.RuntimeRef, .externals = ast.externals, .runtime_imports = ast.runtime_imports, .require_ref = ast.require_ref, @@ -1183,94 +1182,42 @@ pub const Bundler = struct { &bundler.linker, enable_source_map, ), - .esm_ascii => if (bundler.options.platform.isBun()) - try js_printer.printAst( - Writer, - writer, - ast, - js_ast.Symbol.Map.initList(symbols), - source, - true, - js_printer.Options{ - .to_module_ref = Ref.RuntimeRef, - .externals = ast.externals, - .runtime_imports = ast.runtime_imports, - .require_ref = ast.require_ref, - .css_import_behavior = bundler.options.cssImportBehavior(), - .source_map_handler = source_map_context, - .rewrite_require_resolve = bundler.options.platform != .node, - .minify_whitespace = bundler.options.minify_whitespace, - }, - Linker, - &bundler.linker, - enable_source_map, - ) - else - try js_printer.printAst( - Writer, - writer, - ast, - js_ast.Symbol.Map.initList(symbols), - source, - false, - js_printer.Options{ - .to_module_ref = Ref.RuntimeRef, - .externals = ast.externals, - .runtime_imports = ast.runtime_imports, - .require_ref = ast.require_ref, - .css_import_behavior = bundler.options.cssImportBehavior(), - .source_map_handler = source_map_context, - .rewrite_require_resolve = bundler.options.platform != .node, - .minify_whitespace = bundler.options.minify_whitespace, - }, - Linker, - &bundler.linker, - enable_source_map, - ), - .cjs_ascii => if (bundler.options.platform.isBun()) - try js_printer.printCommonJS( - Writer, - writer, - ast, - js_ast.Symbol.Map.initList(symbols), - source, - true, - js_printer.Options{ - .to_module_ref = Ref.RuntimeRef, - .externals = ast.externals, - .runtime_imports = ast.runtime_imports, - .require_ref = ast.require_ref, - .css_import_behavior = bundler.options.cssImportBehavior(), - .source_map_handler = source_map_context, - .rewrite_require_resolve = bundler.options.platform != .node, - .minify_whitespace = bundler.options.minify_whitespace, - }, - Linker, - &bundler.linker, - enable_source_map, - ) - else - try js_printer.printCommonJS( - Writer, - writer, - ast, - js_ast.Symbol.Map.initList(symbols), - source, - false, - js_printer.Options{ - .to_module_ref = Ref.RuntimeRef, - .externals = ast.externals, - .runtime_imports = ast.runtime_imports, - .require_ref = ast.require_ref, - .css_import_behavior = bundler.options.cssImportBehavior(), - .source_map_handler = source_map_context, - .rewrite_require_resolve = bundler.options.platform != .node, - .minify_whitespace = bundler.options.minify_whitespace, - }, - Linker, - &bundler.linker, - enable_source_map, - ), + .esm_ascii => try js_printer.printAst( + Writer, + writer, + ast, + js_ast.Symbol.Map.initList(symbols), + source, + true, + js_printer.Options{ + .externals = ast.externals, + .runtime_imports = ast.runtime_imports, + .require_ref = ast.require_ref, + .css_import_behavior = bundler.options.cssImportBehavior(), + .source_map_handler = source_map_context, + }, + Linker, + &bundler.linker, + enable_source_map, + ), + .cjs_ascii => try js_printer.printCommonJS( + Writer, + writer, + ast, + js_ast.Symbol.Map.initList(symbols), + source, + true, + js_printer.Options{ + .externals = ast.externals, + .runtime_imports = ast.runtime_imports, + .require_ref = ast.require_ref, + .css_import_behavior = bundler.options.cssImportBehavior(), + .source_map_handler = source_map_context, + }, + Linker, + &bundler.linker, + enable_source_map, + ), }; } diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig new file mode 100644 index 000000000000..02d93204e394 --- /dev/null +++ b/src/bundler/bundle_v2.zig @@ -0,0 +1,4490 @@ +const Bundler = bun.Bundler; +const GenerateNodeModulesBundle = @This(); +const bun = @import("bun"); +const from = bun.from; +const string = bun.string; +const Output = bun.Output; +const Global = bun.Global; +const Environment = bun.Environment; +const strings = bun.strings; +const MutableString = bun.MutableString; +const stringZ = bun.stringZ; +const default_allocator = bun.default_allocator; +const StoredFileDescriptorType = bun.StoredFileDescriptorType; +const FeatureFlags = bun.FeatureFlags; +const C = bun.C; +const std = @import("std"); +const lex = @import("../js_lexer.zig"); +const Logger = @import("../logger.zig"); +const options = @import("../options.zig"); +const js_parser = bun.js_parser; +const json_parser = @import("../json_parser.zig"); +const js_printer = @import("../js_printer.zig"); +const js_ast = @import("../js_ast.zig"); +const linker = @import("../linker.zig"); +const Ref = @import("../ast/base.zig").Ref; +const Define = @import("../defines.zig").Define; +const DebugOptions = @import("../cli.zig").Command.DebugOptions; +const ThreadPoolLib = @import("../thread_pool.zig"); +const ThreadlocalArena = @import("../mimalloc_arena.zig").Arena; +const BabyList = @import("../baby_list.zig").BabyList; +const panicky = @import("../panic_handler.zig"); +const Fs = @import("../fs.zig"); +const schema = @import("../api/schema.zig"); +const Api = schema.Api; +const _resolver = @import("../resolver/resolver.zig"); +const sync = @import("../sync.zig"); +const ImportRecord = bun.ImportRecord; +const ImportKind = bun.ImportKind; +const allocators = @import("../allocators.zig"); +const MimeType = @import("../http/mime_type.zig"); +const resolve_path = @import("../resolver/resolve_path.zig"); +const runtime = @import("../runtime.zig"); +const Timer = @import("../system_timer.zig"); +const PackageJSON = @import("../resolver/package_json.zig").PackageJSON; +const MacroRemap = @import("../resolver/package_json.zig").MacroMap; +const DebugLogs = _resolver.DebugLogs; +const NodeModuleBundle = @import("../node_module_bundle.zig").NodeModuleBundle; +const Router = @import("../router.zig"); +const isPackagePath = _resolver.isPackagePath; +const Lock = @import("../lock.zig").Lock; +const NodeFallbackModules = @import("../node_fallbacks.zig"); +const CacheEntry = @import("../cache.zig").FsCacheEntry; +const Analytics = @import("../analytics/analytics_thread.zig"); +const URL = @import("../url.zig").URL; +const Report = @import("../report.zig"); +const Linker = linker.Linker; +const Resolver = _resolver.Resolver; +const TOML = @import("../toml/toml_parser.zig").TOML; +const Schema = @import("../api/bundle_v2.zig").BundleV2; +const EntryPoints = @import("./entry_points.zig"); +const ThisBundler = @import("../bundler.zig").Bundler; +const StringPointer = Schema.StringPointer; +const wyhash = std.hash.Wyhash.hash; +const Dependency = js_ast.Dependency; +const JSAst = js_ast.Ast; +const Loader = options.Loader; +const Index = @import("../ast/base.zig").Index; +const Batcher = bun.Batcher; +const Symbol = js_ast.Symbol; +const EventLoop = bun.JSC.AnyEventLoop; +const MultiArrayList = bun.MultiArrayList; +const Stmt = js_ast.Stmt; +const Expr = js_ast.Expr; +const Binding = js_ast.Binding; +const AutoBitSet = bun.bit_set.AutoBitSet; + +pub const ThreadPool = struct { + pool: ThreadPoolLib = undefined, + // Hardcode 512 as max number of threads for now. + workers: [512]Worker = undefined, + workers_used: std.atomic.Atomic(u32) = std.atomic.Atomic(u32).init(0), + cpu_count: u32 = 0, + started_workers: std.atomic.Atomic(u32) = std.atomic.Atomic(u32).init(0), + stopped_workers: std.atomic.Atomic(u32) = std.atomic.Atomic(u32).init(0), + completed_count: std.atomic.Atomic(u32) = std.atomic.Atomic(u32).init(0), + pending_count: std.atomic.Atomic(u32) = std.atomic.Atomic(u32).init(0), + + v2: *BundleV2 = undefined, + + const debug = Output.scoped(.ThreadPool, false); + + pub fn go(this: *ThreadPool, allocator: std.mem.Allocator, comptime Function: anytype) !ThreadPoolLib.ConcurrentFunction(Function) { + return this.pool.go(allocator, Function); + } + + pub fn start(this: *ThreadPool, v2: *BundleV2) !void { + v2.bundler.env.loadProcess(); + this.v2 = v2; + + this.cpu_count = @truncate(u32, @divFloor((try std.Thread.getCpuCount()) + 1, 2)); + + if (v2.bundler.env.map.get("GOMAXPROCS")) |max_procs| { + if (std.fmt.parseInt(u32, max_procs, 10)) |cpu_count| { + this.cpu_count = std.math.min(this.cpu_count, cpu_count); + } else |_| {} + } + + this.pool = ThreadPoolLib.init(.{ + .max_threads = this.cpu_count, + }); + this.pool.on_thread_spawn = Worker.onSpawn; + this.pool.threadpool_context = this; + var workers_used: u32 = 0; + while (workers_used < this.cpu_count) : (workers_used += 1) { + try this.workers[workers_used].init(v2); + } + + if (workers_used > 0) + this.pool.forceSpawn(); + debug("allocated {d} workers", .{this.cpu_count}); + } + + pub const Worker = struct { + thread_id: std.Thread.Id, + thread: std.Thread, + heap: ThreadlocalArena = ThreadlocalArena{}, + allocator: std.mem.Allocator, + ctx: *BundleV2, + + data: *WorkerData = undefined, + quit: bool = false, + + has_notify_started: bool = false, + + pub const WorkerData = struct { + log: *Logger.Log, + estimated_input_lines_of_code: usize = 0, + macro_context: js_ast.Macro.MacroContext, + bundler: Bundler = undefined, + + pub fn deinit(this: *WorkerData, allocator: std.mem.Allocator) void { + allocator.destroy(this); + } + }; + + pub fn init(worker: *Worker, v2: *BundleV2) !void { + worker.ctx = v2; + } + + pub fn onSpawn(ctx: ?*anyopaque) ?*anyopaque { + var pool = @ptrCast(*ThreadPool, @alignCast(@alignOf(*ThreadPool), ctx.?)); + + const id = pool.workers_used.fetchAdd(1, .Monotonic); + pool.workers[id].run(); + return &pool.workers[id]; + } + + pub fn notifyStarted(this: *Worker) void { + if (!this.has_notify_started) { + this.has_notify_started = true; + _ = this.v2.pool.started_workers.fetchAdd(1, .Release); + std.Thread.Futex.wake(&this.v2.pool.started_workers, std.math.maxInt(u32)); + } + } + + pub fn run(this: *Worker) void { + Output.Source.configureThread(); + this.thread_id = std.Thread.getCurrentId(); + this.heap = ThreadlocalArena.init() catch unreachable; + this.allocator = this.heap.allocator(); + var allocator = this.allocator; + if (Environment.isDebug) { + Output.prettyln("Thread started.\n", .{}); + } + + // Ensure we're using the same allocator for the worker's data. + js_ast.Expr.Data.Store.deinit(); + js_ast.Stmt.Data.Store.deinit(); + + js_ast.Expr.Data.Store.create(allocator); + js_ast.Stmt.Data.Store.create(allocator); + this.data = allocator.create(WorkerData) catch unreachable; + this.data.* = WorkerData{ + .log = allocator.create(Logger.Log) catch unreachable, + .estimated_input_lines_of_code = 0, + .macro_context = undefined, + }; + this.data.log.* = Logger.Log.init(allocator); + this.data.bundler = this.ctx.bundler.*; + this.data.bundler.setLog(this.data.log); + this.data.bundler.setAllocator(allocator); + this.data.bundler.linker.resolver = &this.data.bundler.resolver; + this.data.bundler.macro_context = js_ast.Macro.MacroContext.init(&this.data.bundler); + this.data.macro_context = this.data.bundler.macro_context.?; + + const CacheSet = @import("../cache.zig"); + + this.data.bundler.resolver.caches = CacheSet.Set.init(this.allocator); + + // no funny business mr. cache + + } + }; +}; + +pub const BundleV2 = struct { + bundler: *Bundler, + graph: Graph = Graph{}, + linker: LinkerContext = LinkerContext{ .loop = undefined }, + tmpfile: std.fs.File = undefined, + tmpfile_byte_offset: u32 = 0, + + const debug = Output.scoped(.Bundle, false); + + pub inline fn loop(this: *BundleV2) *EventLoop { + return &this.linker.loop; + } + + pub fn findReachableFiles(this: *BundleV2) ![]Index { + const Visitor = struct { + reachable: std.ArrayList(Index), + visited: bun.bit_set.DynamicBitSet = undefined, + all_import_records: []ImportRecord.List, + + // Find all files reachable from all entry points. This order should be + // deterministic given that the entry point order is deterministic, since the + // returned order is the postorder of the graph traversal and import record + // order within a given file is deterministic. + pub fn visit(v: *@This(), source_index: Index) void { + if (source_index.isInvalid()) return; + if (v.visited.isSet(source_index.get())) { + return; + } + v.visited.set(source_index.get()); + + const import_record_list_id = source_index; + // when there are no import records, v index will be invalid + if (import_record_list_id.get() < v.all_import_records.len) { + for (v.all_import_records[import_record_list_id.get()].slice()) |*import_record| { + const other_source = import_record.source_index; + if (other_source.isValid()) { + v.visit(other_source); + } + } + } + + // Each file must come after its dependencies + v.reachable.append(source_index) catch unreachable; + } + }; + + var visitor = Visitor{ + .reachable = try std.ArrayList(Index).initCapacity(this.graph.allocator, this.graph.entry_points.items.len + 1), + .visited = try bun.bit_set.DynamicBitSet.initEmpty(this.graph.allocator, this.graph.input_files.len), + .all_import_records = this.graph.ast.items(.import_records), + }; + defer visitor.visited.deinit(); + + for (this.graph.entry_points.items) |entry_point| { + visitor.visit(entry_point); + } + + if (comptime Environment.allow_assert) { + Output.prettyErrorln("Reachable count: {d} / {d}", .{ visitor.reachable.items.len, this.graph.input_files.len }); + } + + return visitor.reachable.toOwnedSlice(); + } + + pub fn appendBytes(generator: *BundleV2, bytes: anytype) !void { + try generator.tmpfile.writeAll(bytes); + generator.tmpfile_byte_offset += @truncate(u32, bytes.len); + } + + pub fn ensurePathIsAllocated(this: *BundleV2, path_: ?*Fs.Path) !void { + var path = path_ orelse return; + + const loader = this.bundler.options.loaders.get(path.name.ext) orelse .file; + if (!loader.isJavaScriptLikeOrJSON()) return; + path.* = try path.dupeAlloc(this.allocator); + } + + pub fn waitForParse(this: *BundleV2) void { + while (this.graph.parse_pending > 0) { + this.loop().tick(this); + } + + debug("Parsed {d} files, producing {d} ASTs", .{ this.graph.input_files.len, this.graph.ast.len }); + } + + pub fn enqueueItem(this: *BundleV2, hash: ?u64, batch: *ThreadPoolLib.Batch, resolve: _resolver.Result) !?Index.Int { + var result = resolve; + var path = result.path() orelse return null; + + const loader = this.bundler.options.loaders.get(path.name.ext) orelse .file; + if (!loader.isJavaScriptLikeOrJSON()) return null; + + var entry = try this.graph.path_to_source_index_map.getOrPut(this.graph.allocator, hash orelse wyhash(0, path.text)); + if (entry.found_existing) { + return null; + } + this.graph.parse_pending += 1; + const source_index = Index.source(this.graph.input_files.len); + path.* = try path.dupeAlloc(this.graph.allocator); + entry.value_ptr.* = source_index.get(); + this.graph.ast.append(this.graph.allocator, js_ast.Ast.empty) catch unreachable; + try this.graph.input_files.append(this.graph.allocator, .{ + .source = .{ + .path = path.*, + .key_path = path.*, + .contents = "", + .index = source_index, + }, + .loader = loader, + .side_effects = resolve.primary_side_effects_data, + }); + var task = try this.graph.allocator.create(ParseTask); + task.* = ParseTask.init(&result, source_index); + task.loader = loader; + task.task.node.next = null; + task.tree_shaking = this.bundler.options.tree_shaking; + batch.push(ThreadPoolLib.Batch.from(&task.task)); + return source_index.get(); + } + + pub fn generate( + bundler: *ThisBundler, + allocator: std.mem.Allocator, + framework_config: ?Api.LoadedFramework, + route_config: ?Api.LoadedRouteConfig, + destination: [*:0]const u8, + estimated_input_lines_of_code: *usize, + package_bundle_map: options.BundlePackage.Map, + event_loop: EventLoop, + unique_key: u64, + ) !?Schema.JavascriptBundleContainer { + _ = try bundler.fs.fs.openTmpDir(); + var tmpname_buf: [64]u8 = undefined; + bundler.resetStore(); + try bundler.configureDefines(); + _ = route_config; + _ = estimated_input_lines_of_code; + _ = package_bundle_map; + + const tmpname = try bundler.fs.tmpname( + ".bun", + std.mem.span(&tmpname_buf), + wyhash(@intCast(usize, std.time.milliTimestamp()) % std.math.maxInt(u32), std.mem.span(destination)), + ); + + var tmpfile = Fs.FileSystem.RealFS.Tmpfile{}; + try tmpfile.create(&bundler.fs.fs, tmpname); + + errdefer tmpfile.closeAndDelete(tmpname); + + var generator = try allocator.create(BundleV2); + + defer allocator.destroy(generator); + generator.* = BundleV2{ + .tmpfile = tmpfile.file(), + .bundler = bundler, + .graph = .{ + .pool = undefined, + .heap = try ThreadlocalArena.init(), + .allocator = undefined, + }, + .linker = .{ + .loop = event_loop, + .graph = .{ + .allocator = undefined, + }, + }, + }; + generator.linker.graph.allocator = generator.graph.heap.allocator(); + generator.graph.allocator = generator.linker.graph.allocator; + generator.bundler.allocator = generator.graph.allocator; + generator.bundler.resolver.allocator = generator.graph.allocator; + generator.bundler.linker.allocator = generator.graph.allocator; + generator.bundler.log.msgs.allocator = generator.graph.allocator; + generator.linker.resolver = &generator.bundler.resolver; + + var pool = try generator.graph.allocator.create(ThreadPool); + // errdefer pool.destroy(); + errdefer generator.graph.heap.deinit(); + + if (framework_config != null) { + defer bundler.resetStore(); + + try bundler.configureFramework(true); + if (bundler.options.framework) |framework| { + Analytics.Features.framework = true; + + if (framework.override_modules.keys.len > 0) { + bundler.options.framework.?.override_modules_hashes = allocator.alloc(u64, framework.override_modules.keys.len) catch unreachable; + for (framework.override_modules.keys) |key, i| { + bundler.options.framework.?.override_modules_hashes[i] = std.hash.Wyhash.hash(0, key); + } + } + } + } else {} + + pool.* = ThreadPool{}; + generator.graph.pool = pool; + + var batch = ThreadPoolLib.Batch{}; + + var this = generator; + try pool.start(this); + + if (framework_config != null) { + defer this.bundler.resetStore(); + + try this.bundler.configureFramework(true); + if (bundler.options.framework) |framework| { + Analytics.Features.framework = true; + + if (framework.override_modules.keys.len > 0) { + bundler.options.framework.?.override_modules_hashes = allocator.alloc(u64, framework.override_modules.keys.len) catch unreachable; + for (framework.override_modules.keys) |key, i| { + bundler.options.framework.?.override_modules_hashes[i] = wyhash(0, key); + } + } + } + } else {} + + { + // Add the runtime + try this.graph.input_files.append(allocator, Graph.InputFile{ + .source = ParseTask.runtime_source, + .loader = .js, + .side_effects = _resolver.SideEffects.no_side_effects__package_json, + }); + + // try this.graph.entry_points.append(allocator, Index.runtime); + this.graph.ast.append(this.graph.allocator, js_ast.Ast.empty) catch unreachable; + this.graph.path_to_source_index_map.put(this.graph.allocator, bun.hash("bun:wrap"), Index.runtime.get()) catch unreachable; + var runtime_parse_task = try this.graph.allocator.create(ParseTask); + runtime_parse_task.* = ParseTask.runtime; + runtime_parse_task.task.node.next = null; + runtime_parse_task.loader = .js; + this.graph.parse_pending += 1; + batch.push(ThreadPoolLib.Batch.from(&runtime_parse_task.task)); + } + + if (bundler.options.framework) |framework| { + if (bundler.options.platform.isBun()) { + if (framework.server.isEnabled()) { + Analytics.Features.bunjs = true; + const resolved = try bundler.resolver.resolve( + bundler.fs.top_level_dir, + framework.server.path, + .entry_point, + ); + if (try this.enqueueItem(null, &batch, resolved)) |source_index| { + this.graph.entry_points.append(this.graph.allocator, Index.source(source_index)) catch unreachable; + } else {} + } + } else { + if (framework.client.isEnabled()) { + const resolved = try bundler.resolver.resolve( + bundler.fs.top_level_dir, + framework.client.path, + .entry_point, + ); + if (try this.enqueueItem(null, &batch, resolved)) |source_index| { + this.graph.entry_points.append(this.graph.allocator, Index.source(source_index)) catch unreachable; + } else {} + } + + if (framework.fallback.isEnabled()) { + const resolved = try bundler.resolver.resolve( + bundler.fs.top_level_dir, + framework.fallback.path, + .entry_point, + ); + if (try this.enqueueItem(null, &batch, resolved)) |source_index| { + this.graph.entry_points.append(this.graph.allocator, Index.source(source_index)) catch unreachable; + } else {} + } + } + } + + if (bundler.router) |router| { + defer this.bundler.resetStore(); + Analytics.Features.filesystem_router = true; + + const entry_points = try router.getEntryPoints(); + try this.graph.entry_points.ensureUnusedCapacity(this.graph.allocator, entry_points.len); + try this.graph.input_files.ensureUnusedCapacity(this.graph.allocator, entry_points.len); + try this.graph.path_to_source_index_map.ensureUnusedCapacity(this.graph.allocator, @truncate(u32, entry_points.len)); + + for (entry_points) |entry_point| { + const resolved = bundler.resolveEntryPoint(entry_point) catch continue; + if (try this.enqueueItem(null, &batch, resolved)) |source_index| { + this.graph.entry_points.append(this.graph.allocator, Index.source(source_index)) catch unreachable; + } else {} + } + } else {} + + { + // Setup entry points + try this.graph.entry_points.ensureUnusedCapacity(this.graph.allocator, bundler.options.entry_points.len); + try this.graph.input_files.ensureUnusedCapacity(this.graph.allocator, bundler.options.entry_points.len); + try this.graph.path_to_source_index_map.ensureUnusedCapacity(this.graph.allocator, @truncate(u32, bundler.options.entry_points.len)); + + defer this.bundler.resetStore(); + for (bundler.options.entry_points) |entry_point| { + const resolved = bundler.resolveEntryPoint(entry_point) catch continue; + if (try this.enqueueItem(null, &batch, resolved)) |source_index| { + this.graph.entry_points.append(this.graph.allocator, Index.source(source_index)) catch unreachable; + } else {} + } + } + + this.graph.pool.pool.schedule(batch); + this.waitForParse(); + + this.linker.allocator = this.bundler.allocator; + this.linker.graph.allocator = this.bundler.allocator; + this.linker.graph.ast = try this.graph.ast.clone(this.linker.allocator); + + try this.linker.link( + this, + this.graph.entry_points.items, + try this.findReachableFiles(), + std.mem.asBytes(&unique_key), + ); + + return null; + } + + pub fn onParseTaskComplete(parse_result: *ParseTask.Result, this: *BundleV2) void { + var graph = &this.graph; + var batch = ThreadPoolLib.Batch{}; + var diff: isize = -1; + defer graph.parse_pending = if (diff > 0) + graph.parse_pending + @intCast(usize, diff) + else + graph.parse_pending - @intCast(usize, -diff); + switch (parse_result.value) { + .empty => |source_index| { + var input_files = graph.input_files.slice(); + var side_effects = input_files.items(.side_effects); + side_effects[source_index.get()] = .no_side_effects__empty_ast; + if (comptime Environment.allow_assert) { + debug("onParse({d}, {s}) = empty", .{ + source_index.get(), + input_files.items(.source)[source_index.get()].path.text, + }); + } + }, + .success => |*result| { + result.log.appendTo(this.bundler.log) catch unreachable; + { + var input_files = graph.input_files.slice(); + input_files.items(.source)[result.source.index.get()] = result.source; + debug("onParse({d}, {s}) = {d} imports, {d} exports", .{ + result.source.index.get(), + result.source.path.text, + result.ast.import_records.len, + result.ast.named_exports.count(), + }); + } + + var iter = result.resolve_queue.iterator(); + + while (iter.next()) |entry| { + const hash = entry.key_ptr.*; + const value = entry.value_ptr.*; + const loader = value.loader orelse options.Loader.file; + if (!loader.isJavaScriptLikeOrJSON()) { + // TODO: + continue; + } + var existing = graph.path_to_source_index_map.getOrPut(graph.allocator, hash) catch unreachable; + if (!existing.found_existing) { + var new_input_file = Graph.InputFile{ + .source = Logger.Source.initEmptyFile(entry.value_ptr.path.text), + .side_effects = value.side_effects, + }; + new_input_file.source.index = Index.source(graph.input_files.len); + new_input_file.source.path = entry.value_ptr.path; + new_input_file.source.key_path = new_input_file.source.path; + // graph.source_index_map.put(graph.allocator, new_input_file.source.index.get, new_input_file.source) catch unreachable; + existing.value_ptr.* = new_input_file.source.index.get(); + entry.value_ptr.source_index = new_input_file.source.index; + graph.input_files.append(graph.allocator, new_input_file) catch unreachable; + graph.ast.append(graph.allocator, js_ast.Ast.empty) catch unreachable; + batch.push(ThreadPoolLib.Batch.from(&entry.value_ptr.task)); + diff += 1; + } + } + + var import_records = result.ast.import_records.slice(); + for (import_records) |*record| { + if (graph.path_to_source_index_map.get(wyhash(0, record.path.text))) |source_index| { + record.source_index.set(source_index); + } + } + + graph.ast.set(result.source.index.get(), result.ast); + // schedule as early as possible + graph.pool.pool.schedule(batch); + }, + .err => |*err| { + if (comptime Environment.allow_assert) { + debug("onParse() = err", .{}); + } + + if (err.log.msgs.items.len > 0) { + err.log.appendTo(this.bundler.log) catch unreachable; + } else { + this.bundler.log.addErrorFmt( + null, + Logger.Loc.Empty, + this.bundler.allocator, + "{s} while {s}", + .{ @errorName(err.err), @tagName(err.step) }, + ) catch unreachable; + } + }, + } + } +}; + +const ParseTask = struct { + path: Fs.Path, + contents_or_fd: union(enum) { + fd: struct { + dir: StoredFileDescriptorType, + file: StoredFileDescriptorType, + }, + contents: string, + }, + side_effects: _resolver.SideEffects, + loader: ?Loader = null, + jsx: options.JSX.Pragma, + source_index: Index = Index.invalid, + task: ThreadPoolLib.Task = .{ .callback = &callback }, + tree_shaking: bool = false, + + const debug = Output.scoped(.ParseTask, false); + + pub const ResolveQueue = std.AutoArrayHashMap(u64, ParseTask); + + pub fn init(resolve_result: *const _resolver.Result, source_index: ?Index) ParseTask { + return .{ + .path = resolve_result.path_pair.primary, + .contents_or_fd = .{ + .fd = .{ + .dir = resolve_result.dirname_fd, + .file = resolve_result.file_fd, + }, + }, + .side_effects = resolve_result.primary_side_effects_data, + .jsx = resolve_result.jsx, + .source_index = source_index orelse Index.invalid, + }; + } + + pub const runtime = ParseTask{ + .path = Fs.Path.initWithNamespace("runtime", "bun:runtime"), + .side_effects = _resolver.SideEffects.no_side_effects__pure_data, + .jsx = options.JSX.Pragma{ + .parse = false, + // .supports_react_refresh = false, + }, + .contents_or_fd = .{ + .contents = @as(string, @embedFile("../runtime.js")), + }, + .source_index = Index.runtime, + .loader = Loader.js, + }; + pub const runtime_source = Logger.Source{ + .path = ParseTask.runtime.path, + .key_path = ParseTask.runtime.path, + .contents = ParseTask.runtime.contents_or_fd.contents, + .index = Index.runtime, + }; + + pub const Result = struct { + task: EventLoop.Task = undefined, + + value: union(Tag) { + err: Error, + success: Success, + empty: Index, + }, + + pub const Success = struct { + ast: js_ast.Ast, + resolve_queue: ResolveQueue, + source: Logger.Source, + log: Logger.Log, + }; + + pub const Error = struct { + err: anyerror, + step: Step, + log: Logger.Log, + + pub const Step = enum { + pending, + read_file, + parse, + resolve, + }; + }; + + pub const Tag = enum { + success, + err, + empty, + }; + }; + + threadlocal var override_file_path_buf: [bun.MAX_PATH_BYTES]u8 = undefined; + + fn run_( + task: *ParseTask, + this: *ThreadPool.Worker, + step: *ParseTask.Result.Error.Step, + log: *Logger.Log, + ) anyerror!?Result.Success { + var allocator = this.allocator; + + var data = this.data; + var bundler = &data.bundler; + errdefer bundler.resetStore(); + var resolver: *Resolver = &bundler.resolver; + var file_path = task.path; + step.* = .read_file; + + var entry: CacheEntry = switch (task.contents_or_fd) { + .fd => brk: { + if (bundler.options.framework) |framework| { + if (framework.override_modules_hashes.len > 0) { + const package_relative_path_hash = wyhash(0, file_path.pretty); + if (std.mem.indexOfScalar( + u64, + framework.override_modules_hashes, + package_relative_path_hash, + )) |index| { + const relative_path = [_]string{ + framework.resolved_dir, + framework.override_modules.values[index], + }; + var override_path = bundler.fs.absBuf( + &relative_path, + &override_file_path_buf, + ); + override_file_path_buf[override_path.len] = 0; + var override_pathZ = override_file_path_buf[0..override_path.len :0]; + debug("{s} -> {s}", .{ file_path.text, override_path }); + break :brk try resolver.caches.fs.readFile( + bundler.fs, + override_pathZ, + 0, + false, + null, + ); + } + } + } + + if (strings.eqlComptime(file_path.namespace, "node")) + break :brk CacheEntry{ + .contents = NodeFallbackModules.contentsFromPath(file_path.text) orelse "", + }; + + break :brk try resolver.caches.fs.readFile( + bundler.fs, + file_path.text, + task.contents_or_fd.fd.dir, + false, + if (task.contents_or_fd.fd.file > 0) + task.contents_or_fd.fd.file + else + null, + ); + }, + .contents => |contents| CacheEntry{ + .contents = contents, + .fd = 0, + }, + }; + + errdefer if (task.contents_or_fd == .fd) entry.deinit(allocator); + + if (entry.fd > 2) task.contents_or_fd = .{ + .fd = .{ + .file = entry.fd, + .dir = bun.invalid_fd, + }, + }; + step.* = .parse; + + if (entry.contents.len == 0 or (entry.contents.len < 33 and strings.trim(entry.contents, " \n\r").len == 0)) { + debug("skipping empty file: {s}", .{file_path.text}); + return null; + } + + var source = Logger.Source{ + .path = file_path, + .key_path = file_path, + .index = task.source_index, + .contents = entry.contents, + .contents_is_recycled = false, + }; + + const source_dir = file_path.sourceDir(); + const loader = task.loader orelse bundler.options.loader(file_path.name.ext); + const platform = bundler.options.platform; + var resolve_queue = ResolveQueue.init(bun.default_allocator); + errdefer resolve_queue.clearAndFree(); + + switch (loader) { + .jsx, .tsx, .js, .ts => { + task.jsx.parse = loader.isJSX(); + + var opts = js_parser.Parser.Options.init(task.jsx, loader); + opts.transform_require_to_import = false; + opts.can_import_from_bundle = false; + opts.features.allow_runtime = !source.index.isRuntime(); + opts.warn_about_unbundled_modules = false; + opts.macro_context = &this.data.macro_context; + opts.bundle = true; + opts.features.auto_import_jsx = task.jsx.parse and bundler.options.auto_import_jsx; + opts.features.trim_unused_imports = bundler.options.trim_unused_imports orelse loader.isTypeScript(); + opts.tree_shaking = task.tree_shaking; + + var ast = (try resolver.caches.js.parse( + bundler.allocator, + opts, + bundler.options.define, + log, + &source, + )) orelse return error.EmptyAST; + + step.* = .resolve; + var estimated_resolve_queue_count: usize = 0; + for (ast.import_records.slice()) |*import_record| { + // Don't resolve the runtime + if (import_record.is_internal or import_record.is_unused) { + continue; + } + estimated_resolve_queue_count += 1; + } + + try resolve_queue.ensureUnusedCapacity(estimated_resolve_queue_count); + var last_error: ?anyerror = null; + for (ast.import_records.slice()) |*import_record| { + // Don't resolve the runtime + if (import_record.is_internal or import_record.is_unused) { + import_record.source_index = Index.invalid; + continue; + } + + if (resolver.resolve(source_dir, import_record.path.text, import_record.kind)) |_resolved_import| { + var resolve_result = _resolved_import; + // if there were errors, lets go ahead and collect them all + if (last_error != null) continue; + + var path: *Fs.Path = resolve_result.path() orelse { + import_record.path.is_disabled = true; + import_record.source_index = Index.invalid; + + continue; + }; + + if (resolve_result.is_external) { + continue; + } + + var resolve_entry = try resolve_queue.getOrPut(wyhash(0, path.text)); + if (resolve_entry.found_existing) { + import_record.path = resolve_entry.value_ptr.path; + + continue; + } + + path.* = try path.dupeAlloc(allocator); + import_record.path = path.*; + debug("created ParseTask: {s}", .{path.text}); + + resolve_entry.value_ptr.* = ParseTask.init(&resolve_result, null); + if (resolve_entry.value_ptr.loader == null) { + resolve_entry.value_ptr.loader = bundler.options.loader(path.name.ext); + resolve_entry.value_ptr.tree_shaking = task.tree_shaking; + } + } else |err| { + // Disable failing packages from being printed. + // This may cause broken code to write. + // However, doing this means we tell them all the resolve errors + // Rather than just the first one. + import_record.path.is_disabled = true; + + switch (err) { + error.ModuleNotFound => { + const addError = Logger.Log.addResolveErrorWithTextDupeMaybeWarn; + + if (!import_record.handles_import_errors) { + last_error = err; + if (isPackagePath(import_record.path.text)) { + if (platform.isWebLike() and options.ExternalModules.isNodeBuiltin(import_record.path.text)) { + try addError( + log, + &source, + import_record.range, + this.allocator, + "Could not resolve Node.js builtin: \"{s}\".", + .{import_record.path.text}, + import_record.kind, + platform.isBun(), + ); + } else { + try addError( + log, + &source, + import_record.range, + this.allocator, + "Could not resolve: \"{s}\". Maybe you need to \"bun install\"?", + .{import_record.path.text}, + import_record.kind, + platform.isBun(), + ); + } + } else if (!platform.isBun()) { + try addError( + log, + &source, + import_record.range, + this.allocator, + "Could not resolve: \"{s}\"", + .{ + import_record.path.text, + }, + import_record.kind, + platform.isBun(), + ); + } + } + }, + // assume other errors are already in the log + else => { + last_error = err; + }, + } + } + } + + if (last_error) |err| { + debug("failed with error: {s}", .{@errorName(err)}); + return err; + } + + // Allow the AST to outlive this call + _ = js_ast.Expr.Data.Store.toOwnedSlice(); + _ = js_ast.Stmt.Data.Store.toOwnedSlice(); + + return Result.Success{ + .ast = ast, + .source = source, + .resolve_queue = resolve_queue, + .log = log.*, + }; + }, + else => return null, + } + } + + pub fn callback(this: *ThreadPoolLib.Task) void { + run(@fieldParentPtr(ParseTask, "task", this)); + } + + fn run(this: *ParseTask) void { + var worker = @ptrCast( + *ThreadPool.Worker, + @alignCast( + @alignOf(*ThreadPool.Worker), + ThreadPoolLib.Thread.current.?.ctx.?, + ), + ); + var step: ParseTask.Result.Error.Step = .pending; + var log = Logger.Log.init(worker.allocator); + std.debug.assert(this.source_index.isValid()); // forgot to set source_index + + var result = bun.default_allocator.create(Result) catch unreachable; + result.* = .{ + .value = brk: { + if (run_( + this, + worker, + &step, + &log, + )) |ast_or_null| { + if (ast_or_null) |ast| { + break :brk .{ .success = ast }; + } else { + log.deinit(); + break :brk .{ .empty = this.source_index }; + } + } else |err| { + if (err == error.EmptyAST) { + log.deinit(); + break :brk .{ .empty = this.source_index }; + } + break :brk .{ + .err = .{ + .err = err, + .step = step, + .log = log, + }, + }; + } + }, + }; + + worker.ctx.loop().enqueueTaskConcurrent( + Result, + BundleV2, + result, + BundleV2.onParseTaskComplete, + .task, + ); + } +}; + +const IdentityContext = @import("../identity_context.zig").IdentityContext; + +const RefVoidMap = std.ArrayHashMapUnmanaged(Ref, void, Ref.ArrayHashCtx, false); +const RefImportData = std.ArrayHashMapUnmanaged(Ref, ImportData, Ref.ArrayHashCtx, false); +const RefExportData = bun.StringArrayHashMapUnmanaged(ExportData); +const TopLevelSymbolToParts = js_ast.Ast.TopLevelSymbolToParts; + +pub const WrapKind = enum { + none, + cjs, + esm, +}; + +pub const ImportData = struct { + // This is an array of intermediate statements that re-exported this symbol + // in a chain before getting to the final symbol. This can be done either with + // "export * from" or "export {} from". If this is done with "export * from" + // then this may not be the result of a single chain but may instead form + // a diamond shape if this same symbol was re-exported multiple times from + // different files. + re_exports: Dependency.List = Dependency.List{}, + + data: ImportTracker = .{}, +}; + +pub const ExportData = struct { + // Export star resolution happens first before import resolution. That means + // it cannot yet determine if duplicate names from export star resolution are + // ambiguous (point to different symbols) or not (point to the same symbol). + // This issue can happen in the following scenario: + // + // // entry.js + // export * from './a' + // export * from './b' + // + // // a.js + // export * from './c' + // + // // b.js + // export {x} from './c' + // + // // c.js + // export let x = 1, y = 2 + // + // In this case "entry.js" should have two exports "x" and "y", neither of + // which are ambiguous. To handle this case, ambiguity resolution must be + // deferred until import resolution time. That is done using this array. + potentially_ambiguous_export_star_refs: BabyList(ImportData) = .{}, + + // This is the file that the named export above came from. This will be + // different from the file that contains this object if this is a re-export. + data: ImportTracker = .{}, +}; + +pub const JSMeta = struct { + /// This is only for TypeScript files. If an import symbol is in this map, it + /// means the import couldn't be found and doesn't actually exist. This is not + /// an error in TypeScript because the import is probably just a type. + /// + /// Normally we remove all unused imports for TypeScript files during parsing, + /// which automatically removes type-only imports. But there are certain re- + /// export situations where it's impossible to tell if an import is a type or + /// not: + /// + /// import {typeOrNotTypeWhoKnows} from 'path'; + /// export {typeOrNotTypeWhoKnows}; + /// + /// Really people should be using the TypeScript "isolatedModules" flag with + /// bundlers like this one that compile TypeScript files independently without + /// type checking. That causes the TypeScript type checker to emit the error + /// "Re-exporting a type when the '--isolatedModules' flag is provided requires + /// using 'export type'." But we try to be robust to such code anyway. + probably_typescript_type: RefVoidMap = .{}, + + /// Imports are matched with exports in a separate pass from when the matched + /// exports are actually bound to the imports. Here "binding" means adding non- + /// local dependencies on the parts in the exporting file that declare the + /// exported symbol to all parts in the importing file that use the imported + /// symbol. + /// + /// This must be a separate pass because of the "probably TypeScript type" + /// check above. We can't generate the part for the export namespace until + /// we've matched imports with exports because the generated code must omit + /// type-only imports in the export namespace code. And we can't bind exports + /// to imports until the part for the export namespace is generated since that + /// part needs to participate in the binding. + /// + /// This array holds the deferred imports to bind so the pass can be split + /// into two separate passes. + imports_to_bind: RefImportData = .{}, + + /// This includes both named exports and re-exports. + /// + /// Named exports come from explicit export statements in the original file, + /// and are copied from the "NamedExports" field in the AST. + /// + /// Re-exports come from other files and are the result of resolving export + /// star statements (i.e. "export * from 'foo'"). + resolved_exports: RefExportData = .{}, + resolved_export_star: ExportData = ExportData{}, + + /// Never iterate over "resolvedExports" directly. Instead, iterate over this + /// array. Some exports in that map aren't meant to end up in generated code. + /// This array excludes these exports and is also sorted, which avoids non- + /// determinism due to random map iteration order. + sorted_and_filtered_export_aliases: []const string = &[_]string{}, + + /// This is merged on top of the corresponding map from the parser in the AST. + /// You should call "TopLevelSymbolToParts" to access this instead of accessing + /// it directly. + top_level_symbol_to_parts_overlay: TopLevelSymbolToParts = .{}, + + /// If this is an entry point, this array holds a reference to one free + /// temporary symbol for each entry in "sortedAndFilteredExportAliases". + /// These may be needed to store copies of CommonJS re-exports in ESM. + cjs_export_copies: []const Ref = &[_]Ref{}, + + /// The index of the automatically-generated part used to represent the + /// CommonJS or ESM wrapper. This part is empty and is only useful for tree + /// shaking and code splitting. The wrapper can't be inserted into the part + /// because the wrapper contains other parts, which can't be represented by + /// the current part system. Only wrapped files have one of these. + wrapper_part_index: Index = Index.invalid, + + /// The index of the automatically-generated part used to handle entry point + /// specific stuff. If a certain part is needed by the entry point, it's added + /// as a dependency of this part. This is important for parts that are marked + /// as removable when unused and that are not used by anything else. Only + /// entry point files have one of these. + entry_point_part_index: Index = Index.invalid, + + flags: Flags = .{}, + + pub const Flags = packed struct { + wrap: WrapKind = WrapKind.none, + + /// This is true if this file is affected by top-level await, either by having + /// a top-level await inside this file or by having an import/export statement + /// that transitively imports such a file. It is forbidden to call "require()" + /// on these files since they are evaluated asynchronously. + is_async_or_has_async_dependency: bool = false, + + /// If true, we need to insert "var exports = {};". This is the case for ESM + /// files when the import namespace is captured via "import * as" and also + /// when they are the target of a "require()" call. + needs_exports_variable: bool = false, + + /// If true, the "__export(exports, { ... })" call will be force-included even + /// if there are no parts that reference "exports". Otherwise this call will + /// be removed due to the tree shaking pass. This is used when for entry point + /// files when code related to the current output format needs to reference + /// the "exports" variable. + force_include_exports_for_entry_point: bool = false, + + /// This is set when we need to pull in the "__export" symbol in to the part + /// at "nsExportPartIndex". This can't be done in "createExportsForFile" + /// because of concurrent map hazards. Instead, it must be done later. + needs_export_symbol_from_runtime: bool = false, + + /// Wrapped files must also ensure that their dependencies are wrapped. This + /// flag is used during the traversal that enforces this invariant, and is used + /// to detect when the fixed point has been reached. + did_wrap_dependencies: bool = false, + }; +}; + +pub const Graph = struct { + entry_points: std.ArrayListUnmanaged(Index) = .{}, + ast: MultiArrayList(JSAst) = .{}, + + input_files: InputFile.List = .{}, + + code_splitting: bool = false, + + pool: *ThreadPool = undefined, + + heap: ThreadlocalArena = ThreadlocalArena{}, + /// Main thread only!! + allocator: std.mem.Allocator = undefined, + + parse_pending: usize = 0, + + /// Stable source index mapping + source_index_map: std.AutoArrayHashMapUnmanaged(Index.Int, Ref.Int) = .{}, + + /// Stable source index mapping + path_to_source_index_map: std.HashMapUnmanaged(u64, Index.Int, IdentityContext(u64), 80) = .{}, + + pub const InputFile = struct { + source: Logger.Source, + loader: options.Loader = options.Loader.file, + side_effects: _resolver.SideEffects = _resolver.SideEffects.has_side_effects, + + pub const List = MultiArrayList(InputFile); + }; +}; + +const EntryPoint = struct { + // This may be an absolute path or a relative path. If absolute, it will + // eventually be turned into a relative path by computing the path relative + // to the "outbase" directory. Then this relative path will be joined onto + // the "outdir" directory to form the final output path for this entry point. + output_path: bun.PathString = bun.PathString.empty, + + // This is the source index of the entry point. This file must have a valid + // entry point kind (i.e. not "none"). + source_index: Index.Int = 0, + + // Manually specified output paths are ignored when computing the default + // "outbase" directory, which is computed as the lowest common ancestor of + // all automatically generated output paths. + output_path_was_auto_generated: bool = false, + + pub const List = MultiArrayList(EntryPoint); + + pub const Kind = enum(u2) { + none = 0, + user_specified = 1, + dynamic_import = 2, + + pub inline fn isEntryPoint(this: Kind) bool { + return this != .none; + } + + pub inline fn isUserSpecifiedEntryPoint(this: Kind) bool { + return this == .user_specified; + } + }; +}; + +const AstSourceIDMapping = struct { + id: Index.Int, + source_index: Index.Int, +}; + +const LinkerGraph = struct { + const debug = Output.scoped(.LinkerGraph, false); + + files: File.List = .{}, + files_live: bun.bit_set.DynamicBitSetUnmanaged = undefined, + entry_points: EntryPoint.List = .{}, + symbols: js_ast.Symbol.Map = .{}, + + allocator: std.mem.Allocator, + + code_splitting: bool = false, + + // This is an alias from Graph + // it is not a clone! + ast: MultiArrayList(js_ast.Ast) = .{}, + meta: MultiArrayList(JSMeta) = .{}, + + reachable_files: []Index = &[_]Index{}, + + stable_source_indices: []const u32 = &[_]u32{}, + + pub fn init(allocator: std.mem.Allocator, file_count: usize) !LinkerGraph { + return LinkerGraph{ .allocator = allocator, .files_live = try bun.bit_set.DynamicBitSetUnmanaged.initEmpty(allocator, file_count) }; + } + + pub fn generateNewSymbol(this: *LinkerGraph, source_index: u32, kind: Symbol.Kind, original_name: string) Ref { + var source_symbols = &this.symbols.symbols_for_source.slice()[source_index]; + + const ref = Ref.init( + @truncate(Ref.Int, source_symbols.len), + @truncate(Ref.Int, source_index), + false, + ); + + // TODO: will this crash on resize due to using threadlocal mimalloc heap? + source_symbols.push( + this.allocator, + .{ + .kind = kind, + .original_name = original_name, + }, + ) catch unreachable; + + this.ast.items(.module_scope)[source_index].?.generated.push(this.allocator, ref) catch unreachable; + return ref; + } + + pub fn generateRuntimeSymbolImportAndUse( + graph: *LinkerGraph, + source_index: Index.Int, + entry_point_part_index: Index, + name: []const u8, + count: u32, + ) !void { + const ref = graph.ast.items(.module_scope)[Index.runtime.get()].?.members.get(name).?.ref; + try graph.generateSymbolImportAndUse( + source_index, + entry_point_part_index.get(), + ref, + count, + Index.runtime, + ); + } + + pub fn addPartToFile( + graph: *LinkerGraph, + id: u32, + part: js_ast.Part, + ) !u32 { + var parts: *js_ast.Part.List = &graph.ast.items(.parts)[id]; + const part_id = parts.len; + try parts.push(graph.allocator, part); + var top_level_symbol_to_parts_overlay: ?*TopLevelSymbolToParts = null; + + var ctx = .{ + .graph = graph, + .id = id, + .part_id = part_id, + .top_level_symbol_to_parts_overlay = &top_level_symbol_to_parts_overlay, + }; + const Ctx = @TypeOf(ctx); + + const Iterator = struct { + pub fn next(self: *Ctx, ref: Ref) void { + if (self.top_level_symbol_to_parts_overlay.* == null) { + self.top_level_symbol_to_parts_overlay.* = &graph.meta.items(.top_level_symbol_to_parts_overlay)[self.id]; + } + + var entry = try self.top_level_symbol_to_parts_overlay.getOrPut(self.graph.allocator, ref); + if (!entry.found_existing) { + entry.value_ptr.* = try bun.from( + BabyList(u32), + self.graph.allocator, + &[_]u32{ + self.part_id, + }, + ); + } else { + try entry.value_ptr.push(self.graph.allocator, self.part_id); + } + } + }; + + js_ast.DeclaredSymbol.forEachTopLevelSymbol(part.declared_symbols, &ctx, Iterator.next); + + return part_id; + } + pub fn generateSymbolImportAndUse( + g: *LinkerGraph, + id: u32, + part_index: u32, + ref: Ref, + use_count: u32, + source_index_to_import_from: Index, + ) !void { + if (use_count == 0) return; + + // Mark this symbol as used by this part + var part: *js_ast.Part = &g.ast.items(.parts)[id].slice()[part_index]; + + var uses = part.symbol_uses.getOrPut(g.allocator, ref) catch unreachable; + if (uses.found_existing) { + uses.value_ptr.count_estimate += use_count; + } else { + uses.value_ptr.* = .{ .count_estimate = use_count }; + } + + const exports_ref = g.ast.items(.exports_ref)[id]; + const module_ref = g.ast.items(.module_ref)[id].?; + if (ref.eql(exports_ref)) { + g.ast.items(.uses_exports_ref)[id] = true; + } + + if (ref.eql(module_ref)) { + g.ast.items(.uses_module_ref)[id] = true; + } + + // Track that this specific symbol was imported + if (source_index_to_import_from.get() != id) { + try g.meta.items(.imports_to_bind)[id].put(g.allocator, ref, .{ + .data = .{ + .source_index = source_index_to_import_from, + .import_ref = ref, + }, + }); + } + + // Pull in all parts that declare this symbol + var dependencies = &part.dependencies; + const part_ids = g.topLevelSymbolToParts(id, ref); + try dependencies.ensureUnusedCapacity(g.allocator, part_ids.len); + for (part_ids) |_, part_id| { + dependencies.appendAssumeCapacity(.{ + .source_index = source_index_to_import_from, + .part_index = @truncate(u32, part_id), + }); + } + } + + pub fn topLevelSymbolToParts(g: *LinkerGraph, id: u32, ref: Ref) []u32 { + var list: BabyList(u32) = g.meta.items(.top_level_symbol_to_parts_overlay)[id].get(ref) orelse + g.ast.items(.top_level_symbols_to_parts)[id].get(ref) orelse + return &.{}; + + return list.slice(); + } + + pub fn load(this: *LinkerGraph, entry_points: []const Index, sources: []const Logger.Source) !void { + try this.files.ensureTotalCapacity(this.allocator, sources.len); + this.files.zero(); + this.files_live = try bun.bit_set.DynamicBitSetUnmanaged.initEmpty( + this.allocator, + sources.len, + ); + this.files.len = sources.len; + var files = this.files.slice(); + + var entry_point_kinds = files.items(.entry_point_kind); + { + var kinds = std.mem.sliceAsBytes(entry_point_kinds); + @memset(kinds.ptr, 0, kinds.len); + } + + // Setup entry points + { + try this.entry_points.ensureTotalCapacity(this.allocator, entry_points.len); + this.entry_points.len = entry_points.len; + var source_indices = this.entry_points.items(.source_index); + + var path_strings: []bun.PathString = this.entry_points.items(.output_path); + { + var output_was_auto_generated = std.mem.sliceAsBytes(this.entry_points.items(.output_path_was_auto_generated)); + @memset(output_was_auto_generated.ptr, 0, output_was_auto_generated.len); + } + + for (entry_points) |i, j| { + const source = sources[i.get()]; + if (comptime Environment.allow_assert) { + std.debug.assert(source.index.get() == i.get()); + } + entry_point_kinds[source.index.get()] = EntryPoint.Kind.user_specified; + path_strings[j] = bun.PathString.init(source.path.text); + source_indices[j] = source.index.get(); + } + } + + // Setup files + { + var stable_source_indices = try this.allocator.alloc(Index, sources.len); + for (this.reachable_files) |_, i| { + stable_source_indices[i] = Index.source(i); + } + + const file = comptime LinkerGraph.File{}; + // TODO: verify this outputs efficient code + std.mem.set( + @TypeOf(file.distance_from_entry_point), + files.items(.distance_from_entry_point), + comptime file.distance_from_entry_point, + ); + } + + this.symbols = js_ast.Symbol.Map.initList(js_ast.Symbol.NestedList.init(this.ast.items(.symbols))); + } + + pub const File = struct { + entry_bits: AutoBitSet = undefined, + + input_file: Index = Index.source(0), + + /// The minimum number of links in the module graph to get from an entry point + /// to this file + distance_from_entry_point: u32 = std.math.maxInt(u32), + + /// If "entryPointKind" is not "entryPointNone", this is the index of the + /// corresponding entry point chunk. + entry_point_chunk_index: u32 = 0, + + /// This file is an entry point if and only if this is not "entryPointNone". + /// Note that dynamically-imported files are allowed to also be specified by + /// the user as top-level entry points, so some dynamically-imported files + /// may be "entryPointUserSpecified" instead of "entryPointDynamicImport". + entry_point_kind: EntryPoint.Kind = .none, + + pub fn isEntryPoint(this: *const File) bool { + return this.entry_point_kind.isEntryPoint(); + } + + pub fn isUserSpecifiedEntryPoint(this: *const File) bool { + return this.entry_point_kind.isUserSpecifiedEntryPoint(); + } + + pub const List = MultiArrayList(File); + }; +}; + +const LinkerContext = struct { + const debug = Output.scoped(.LinkerCtx, false); + + parse_graph: *Graph = undefined, + graph: LinkerGraph = undefined, + allocator: std.mem.Allocator = undefined, + log: *Logger.Log = undefined, + + resolver: *Resolver = undefined, + cycle_detector: std.ArrayList(ImportTracker) = undefined, + swap_cycle_detector: std.ArrayList(ImportTracker) = undefined, + + /// We may need to refer to the "__esm" and/or "__commonJS" runtime symbols + cjs_runtime_ref: Ref = Ref.None, + esm_runtime_ref: Ref = Ref.None, + + /// We may need to refer to the CommonJS "module" symbol for exports + unbound_module_ref: Ref = Ref.None, + + options: LinkerOptions = LinkerOptions{}, + + wait_group: ThreadPoolLib.WaitGroup = undefined, + + ambiguous_result_pool: std.ArrayList(MatchImport) = undefined, + + loop: EventLoop, + unique_key_buf: []u8 = "", + + pub const LinkerOptions = struct { + output_format: options.OutputFormat = .esm, + ignore_dce_annotations: bool = false, + tree_shaking: bool = true, + }; + + fn isExternalDynamicImport(this: *LinkerContext, record: *const ImportRecord, source_index: u32) bool { + return record.kind == .dynamic and this.graph.files.items(.entry_point_kind)[source_index].isEntryPoint() and record.source_index.get() != source_index; + } + + inline fn shouldCallRuntimeRequire(format: options.OutputFormat) bool { + return format != .cjs; + } + + fn load(this: *LinkerContext, bundle: *BundleV2, entry_points: []Index, reachable: []Index) !void { + this.parse_graph = &bundle.graph; + + this.graph.code_splitting = bundle.bundler.options.code_splitting; + this.log = bundle.bundler.log; + + this.resolver = &bundle.bundler.resolver; + this.cycle_detector = std.ArrayList(ImportTracker).init(this.allocator); + this.swap_cycle_detector = std.ArrayList(ImportTracker).init(this.allocator); + + this.graph.reachable_files = reachable; + + const sources: []const Logger.Source = this.parse_graph.input_files.items(.source); + + try this.graph.load(entry_points, sources); + try this.wait_group.init(); + this.ambiguous_result_pool = std.ArrayList(MatchImport).init(this.allocator); + } + + pub noinline fn link( + this: *LinkerContext, + bundle: *BundleV2, + entry_points: []Index, + reachable: []Index, + unique_key: u64, + ) !void { + try this.load(bundle, entry_points, reachable); + + try this.scanImportsAndExports(); + + // Stop now if there were errors + if (this.log.hasErrors()) { + return; + } + + try this.treeShakingAndCodeSplitting(); + + const chunks = try this.computeChunks(unique_key); + + try this.computeCrossChunkDependencies(chunks); + + this.graph.symbols.followAll(); + } + + pub noinline fn computeChunks( + this: *LinkerContext, + unique_key: u64, + ) ![]Chunk { + var stack_fallback = std.heap.stackFallback(4096, this.allocator); + var stack_all = stack_fallback.get(); + var arena = std.heap.ArenaAllocator.init(stack_all); + defer arena.deinit(); + + var temp_allocator = arena.allocator(); + var js_chunks = bun.StringArrayHashMap(Chunk).init(this.allocator); + try js_chunks.ensureUnusedCapacity(this.graph.entry_points.len); + + var entry_source_indices = this.graph.entry_points.items(.source_index); + + // Create chunks for entry points + for (entry_source_indices) |source_index, entry_id_| { + const entry_bit = @truncate(Chunk.EntryPoint.ID, entry_id_); + + var entry_bits = try bun.bit_set.DynamicBitSetUnmanaged.initEmpty(this.allocator, this.graph.entry_points.len); + entry_bits.set(entry_bit); + + // Create a chunk for the entry point here to ensure that the chunk is + // always generated even if the resulting file is empty + var js_chunk_entry = try js_chunks.getOrPut(try temp_allocator.dupe(u8, entry_bits.bytes())); + + js_chunk_entry.value_ptr.* = .{ + .entry_point = .{ + .id = entry_bit, + .source_index = source_index, + .is_entry_point = true, + }, + .entry_bits = entry_bits, + .source_index = source_index.get(), + .content = .{ + .javascript = .{}, + }, + }; + } + var file_entry_bits: []AutoBitSet = this.graph.files.items(.entry_bits); + + // Figure out which JS files are in which chunk + for (this.graph.reachable_files) |source_index| { + if (this.graph.files_live.isSet(source_index.get())) { + var entry_bits: *AutoBitSet = &file_entry_bits[source_index.get()]; + var js_chunk_entry = try js_chunks.getOrPut( + try temp_allocator.dupe(u8, entry_bits.bytes()), + ); + + if (!js_chunk_entry.found_existing) { + js_chunk_entry.value_ptr.* = .{ + .entry_bits = try entry_bits.clone(this.allocator), + .source_index = source_index.get(), + .content = .{ + .javascript = .{}, + }, + }; + } + + try js_chunk_entry.value_ptr.files_with_parts_in_chunk.getOrPut(this.allocator, source_index.get()); + } + } + + js_chunks.sort(strings.StringArrayByIndexSorter.init(js_chunks.keys())); + + var chunks: []Chunk = js_chunks.values(); + + var entry_point_chunk_indices: []u32 = this.graph.files.items(.entry_point_chunk_index); + // Map from the entry point file to this chunk. We will need this later if + // a file contains a dynamic import to this entry point, since we'll need + // to look up the path for this chunk to use with the import. + for (chunks) |*chunk, chunk_id| { + if (chunk.entry_point.is_entry_point) { + entry_point_chunk_indices[chunk.entry_point.source_index] = @truncate(u32, chunk_id); + } + } + + // Determine the order of JS files (and parts) within the chunk ahead of time + try this.findImportedAllPartsInJSOrder(temp_allocator, chunks); + + const unique_key_item_len = std.fmt.count("{any}C{d:8}", .{ bun.fmt.hexIntLower(unique_key), chunks.len }); + var unique_key_builder = try bun.StringBuilder.initCapacity(this.allocator, unique_key_item_len); + this.unique_key_buf = unique_key_builder.allocatedSlice(); + errdefer { + unique_key_builder.deinit(this.allocator); + this.unique_key_buf = ""; + } + + var chunk_id: usize = 0; + for (chunks) |*chunk| { + defer chunk_id += 1; + + // Assign a unique key to each chunk. This key encodes the index directly so + // we can easily recover it later without needing to look it up in a map. The + // last 8 numbers of the key are the chunk index. + chunk.unique_key = unique_key_builder.fmt("{any}C{d:8}", .{ bun.fmt.hexIntLower(unique_key), chunk_id }); + + if (chunk.entry_point.is_entry_point) { + chunk.template = PathTemplate.file; + const pathname = Fs.PathName.init(this.graph.entry_points.items(.output_path)[chunk.entry_point.source_index].slice()); + chunk.template.placeholder.name = pathname.base; + chunk.template.placeholder.ext = "js"; + chunk.template.placeholder.dir = pathname.dir; + } else { + chunk.template = PathTemplate.chunk; + } + } + + return chunks; + } + + pub fn findAllImportedPartsInJSOrder(this: *LinkerContext, temp_allocator: std.mem.Allocator, chunks: []Chunk) !void { + var part_ranges_shared = std.ArrayList(PartRange).init(this.allocator); + var parts_prefix_shared = std.ArrayList(PartRange).init(this.allocator); + defer part_ranges_shared.deinit(); + defer parts_prefix_shared.deinit(); + for (chunks) |*chunk| { + try this.findImportedPartsInJSOrder( + temp_allocator, + chunk, + &part_ranges_shared, + &parts_prefix_shared, + ); + } + } + + pub fn findImportedPartsInJSOrder( + this: *LinkerContext, + chunk: *Chunk, + part_ranges_shared: *std.ArrayList(PartRange), + parts_prefix_shared: *std.ArrayList(PartRange), + ) !void { + var chunk_order_array = try std.ArrayList(Chunk.Order).initCapacity(this.allocator, chunk.files_with_parts_in_chunk.count()); + defer chunk_order_array.deinit(); + var distances = this.graph.files.items(.distance_from_entry_point); + for (chunk.files_with_parts_in_chunk.keys()) |source_index| { + chunk_order_array.appendAssumeCapacity( + .{ + .source_index = source_index, + .distance = distances[source_index], + + .tie_breaker = this.graph.stable_source_indices[source_index], + }, + ); + } + + Chunk.Order.sort(chunk_order_array.items); + + const Visitor = struct { + entry_bits: *const AutoBitSet, + wraps: []const WrapKind, + parts: [][]js_ast.Part, + import_records: [][]ImportRecord, + files: std.ArrayList(Index.Int) = undefined, + part_ranges: std.ArrayList(PartRange) = undefined, + visited: std.AutoHashMap(Index.Int, void) = undefined, + parts_prefix: std.ArrayList(PartRange) = undefined, + c: *LinkerContext, + + fn appendOrExtendRange( + ranges: *std.ArrayList(PartRange), + source_index: Index.Int, + part_index: Index.Int, + ) void { + if (ranges.items.len > 0) { + var last_range = &ranges.items[ranges.items.len - 1]; + if (last_range.source_index == source_index and last_range.part_index_end == part_index) { + last_range.part_index_end += 1; + return; + } + } + + ranges.append(.{ + .source_index = source_index, + .part_index_start = part_index, + .part_index_end = part_index + 1, + }) catch unreachable; + } + + pub fn visit(v: *@This(), source_index: Index.Int) void { + if (source_index.isInvalid()) return; + const visited_entry = v.visited.getOrPut(source_index.get()) catch unreachable; + if (visited_entry.found_existing) return; + + var is_file_in_chunk = v.entry_bits.isSet( + source_index, + ); + + // Wrapped files can't be split because they are all inside the wrapper + const can_be_split = v.wraps[source_index] == .none; + + const parts = v.parts[source_index]; + if (can_be_split and is_file_in_chunk and parts[js_ast.namespace_export_part_index].is_live) { + appendOrExtendRange(&v.part_ranges, source_index, js_ast.namespace_export_part_index); + } + + const records = v.import_records[source_index]; + + for (parts) |*part, part_index_| { + const part_index = @truncate(u32, part_index_); + const is_part_in_this_chunk = is_file_in_chunk and part.is_live; + + for (part.import_record_indices.slice()) |record_id| { + const record = &records[record_id]; + if (record.source_index.isValid() and (record.kind == .stmt or is_part_in_this_chunk)) { + if (v.c.isExternalDynamicImport(record, source_index)) { + // Don't follow import() dependencies + + continue; + } + + v.visit(record.source_index.get()); + } + } + + // Then include this part after the files it imports + if (is_part_in_this_chunk) { + is_file_in_chunk = true; + + // if (can_be_split and part_index != js_ast.namespace_export_part_index and v.c.shouldIncludePart()) + var js_parts = if (source_index == Index.runtime.value) + &v.parts_prefix + else + &v.part_ranges; + + appendOrExtendRange(js_parts, source_index, part_index); + } + } + + if (is_file_in_chunk) { + v.files.append(source_index) catch unreachable; + + // CommonJS files are all-or-nothing so all parts must be contiguous + if (!can_be_split) { + v.parts_prefix.append( + .{ + .source_index = source_index, + .part_index_begin = 0, + .part_index_end = @truncate(u32, parts.len), + }, + ) catch unreachable; + } + } + } + }; + + part_ranges_shared.clearRetainingCapacity(); + parts_prefix_shared.clearRetainingCapacity(); + + var visitor = Visitor{ + .files = std.ArrayList(Index.Int).init(this.allocator), + .part_ranges = part_ranges_shared.*, + .parts_prefix = parts_prefix_shared.*, + .visited = std.AutoHashMap(Index.Int, void).init(this.allocator), + .wraps = this.graph.meta.items(.flags), + .parts = this.graph.ast.items(.parts), + .import_records = this.graph.ast.items(.import_records), + .entry_bits = chunk.entryBits(), + .c = this, + }; + defer { + part_ranges_shared.* = visitor.part_ranges_shared; + parts_prefix_shared.* = visitor.parts_prefix_shared; + visitor.visited.deinit(); + visitor.parts_prefix.deinit(); + } + + visitor.visit(Index.runtime.value); + for (chunk_order_array.items) |order| { + visitor.visit(order.source_index); + } + + chunk.content.javascript.files_in_chunk_order = visitor.files.items; + var parts_in_chunk_order = try this.allocator.alloc(PartRange, visitor.part_ranges.items.len + visitor.parts_prefix.items.len); + std.mem.copy(PartRange, parts_in_chunk_order, visitor.parts_prefix.items); + std.mem.copy(PartRange, parts_in_chunk_order[visitor.parts_prefix.items.len..], visitor.part_ranges.items); + chunk.content.javascript.parts_in_chunk_order = parts_in_chunk_order; + } + + pub fn scanImportsAndExports(this: *LinkerContext) !void { + const reachable = this.graph.reachable_files; + const output_format = this.options.output_format; + const max_id = reachable.len; + + { + var import_records_list: []ImportRecord.List = this.graph.ast.items(.import_records); + try this.graph.meta.ensureTotalCapacity(this.graph.allocator, import_records_list.len); + this.graph.meta.len = this.graph.ast.len; + this.graph.meta.zero(); + + // var parts_list: [][]js_ast.Part = this.graph.ast.items(.parts); + var exports_kind: []js_ast.ExportsKind = this.graph.ast.items(.exports_kind); + var entry_point_kinds: []EntryPoint.Kind = this.graph.files.items(.entry_point_kind); + var named_imports: []js_ast.Ast.NamedImports = this.graph.ast.items(.named_imports); + var flags: []JSMeta.Flags = this.graph.meta.items(.flags); + + var export_star_import_records: [][]u32 = this.graph.ast.items(.export_star_import_records); + var exports_refs: []Ref = this.graph.ast.items(.exports_ref); + var module_refs: []?Ref = this.graph.ast.items(.module_ref); + var symbols = &this.graph.symbols; + defer this.graph.symbols = symbols.*; + + // Step 1: Figure out what modules must be CommonJS + for (reachable) |source_index_| { + const id = source_index_.get(); + + // does it have a JS AST? + if (!(id < import_records_list.len)) continue; + + var import_records: []ImportRecord = import_records_list[id].slice(); + for (import_records) |record| { + if (!record.source_index.isValid()) { + continue; + } + + const other_file = record.source_index.get(); + // other file is empty + if (other_file >= exports_kind.len) continue; + const other_kind = exports_kind[other_file]; + const other_wrap = flags[other_file].wrap; + + switch (record.kind) { + ImportKind.stmt => { + // Importing using ES6 syntax from a file without any ES6 syntax + // causes that module to be considered CommonJS-style, even if it + // doesn't have any CommonJS exports. + // + // That means the ES6 imports will become undefined instead of + // causing errors. This is for compatibility with older CommonJS- + // style bundlers. + // + // We emit a warning in this case but try to avoid turning the module + // into a CommonJS module if possible. This is possible with named + // imports (the module stays an ECMAScript module but the imports are + // rewritten with undefined) but is not possible with star or default + // imports: + // + // import * as ns from './empty-file' + // import defVal from './empty-file' + // console.log(ns, defVal) + // + // In that case the module *is* considered a CommonJS module because + // the namespace object must be created. + if ((record.contains_import_star or record.contains_default_alias) and + // TODO: hasLazyExport + (other_wrap == .none)) + { + exports_kind[other_file] = .cjs; + flags[other_file].wrap = .cjs; + } + }, + ImportKind.require => + // Files that are imported with require() must be CommonJS modules + { + if (other_kind == .esm) { + flags[other_file].wrap = .esm; + } else { + flags[other_file].wrap = .cjs; + exports_kind[other_file] = .cjs; + } + }, + ImportKind.dynamic => { + if (!this.graph.code_splitting) { + // If we're not splitting, then import() is just a require() that + // returns a promise, so the imported file must be a CommonJS module + if (exports_kind[other_file] == .esm) { + flags[other_file].wrap = .esm; + } else { + flags[other_file].wrap = .cjs; + exports_kind[other_file] = .cjs; + } + } + }, + else => {}, + } + } + + const kind = exports_kind[id]; + + // If the output format doesn't have an implicit CommonJS wrapper, any file + // that uses CommonJS features will need to be wrapped, even though the + // resulting wrapper won't be invoked by other files. An exception is made + // for entry point files in CommonJS format (or when in pass-through mode). + if (kind == .cjs and (!entry_point_kinds[id].isEntryPoint() or output_format == .iife or output_format == .esm)) { + flags[id].wrap = .cjs; + } + } + + if (comptime Environment.allow_assert) { + var cjs_count: usize = 0; + var esm_count: usize = 0; + for (exports_kind) |kind| { + cjs_count += @boolToInt(kind == .cjs); + esm_count += @boolToInt(kind == .esm); + } + + debug("Step 1: {d} CommonJS modules, {d} ES modules", .{ cjs_count, esm_count }); + } + + // Step 2: Propagate dynamic export status for export star statements that + // are re-exports from a module whose exports are not statically analyzable. + // In this case the export star must be evaluated at run time instead of at + // bundle time. + + { + var dependency_wrapper = DependencyWrapper{ + .linker = this, + .flags = flags, + .import_records = import_records_list, + .exports_kind = exports_kind, + .entry_point_kinds = entry_point_kinds, + .export_star_map = std.AutoHashMap(u32, void).init(this.allocator), + .export_star_records = export_star_import_records, + .output_format = output_format, + }; + defer dependency_wrapper.export_star_map.deinit(); + + for (reachable) |source_index_| { + const source_index = source_index_.get(); + const id = source_index; + + // does it have a JS AST? + if (!(id < import_records_list.len)) continue; + + if (flags[id].wrap != .none) { + dependency_wrapper.wrap(id); + } + + if (export_star_import_records[id].len > 0) { + dependency_wrapper.export_star_map.clearRetainingCapacity(); + _ = dependency_wrapper.hasDynamicExportsDueToExportStar(id); + } + + // Even if the output file is CommonJS-like, we may still need to wrap + // CommonJS-style files. Any file that imports a CommonJS-style file will + // cause that file to need to be wrapped. This is because the import + // method, whatever it is, will need to invoke the wrapper. Note that + // this can include entry points (e.g. an entry point that imports a file + // that imports that entry point). + for (import_records_list[id].slice()) |record| { + if (record.source_index.isValid()) { + if (exports_kind[record.source_index.get()] == .cjs) { + dependency_wrapper.wrap(record.source_index.get()); + } + } + } + } + } + + // Step 3: Resolve "export * from" statements. This must be done after we + // discover all modules that can have dynamic exports because export stars + // are ignored for those modules. + { + var export_star_ctx: ?ExportStarContext = null; + var resolved_exports: []RefExportData = this.graph.meta.items(.resolved_exports); + var resolved_export_stars: []ExportData = this.graph.meta.items(.resolved_export_star); + + for (reachable) |source_index_| { + const source_index = source_index_.get(); + const id = source_index; + + // -- + // TODO: generateCodeForLazyExport here! + // -- + + // Propagate exports for export star statements + var export_star_ids = export_star_import_records[id]; + if (export_star_ids.len > 0) { + if (export_star_ctx == null) { + export_star_ctx = ExportStarContext{ + .allocator = this.allocator, + .resolved_exports = resolved_exports, + .import_records_list = import_records_list, + .export_star_records = export_star_import_records, + + // TODO: + .imports_to_bind = &.{}, + + .source_index_stack = std.ArrayList(u32).initCapacity(this.allocator, 32) catch unreachable, + .exports_kind = exports_kind, + .named_exports = this.graph.ast.items(.named_exports), + }; + } else { + export_star_ctx.?.source_index_stack.clearRetainingCapacity(); + } + export_star_ctx.?.addExports(&resolved_exports[id], source_index); + } + + // Also add a special export so import stars can bind to it. This must be + // done in this step because it must come after CommonJS module discovery + // but before matching imports with exports. + resolved_export_stars[id] = ExportData{ + .data = .{ + .source_index = Index.source(source_index), + .import_ref = exports_refs[id], + }, + }; + } + } + + // Step 4: Match imports with exports. This must be done after we process all + // export stars because imports can bind to export star re-exports. + { + this.cycle_detector.clearRetainingCapacity(); + var wrapper_part_indices = this.graph.meta.items(.wrapper_part_index); + var imports_to_bind = this.graph.meta.items(.imports_to_bind); + + for (reachable) |source_index_| { + const source_index = source_index_.get(); + const id = source_index; + + // ignore the runtime + if (source_index == Index.runtime.get()) + continue; + + // not a JS ast or empty + if (id >= named_imports.len) { + continue; + } + + var named_imports_ = &named_imports[id]; + if (named_imports_.count() > 0) { + this.matchImportsWithExportsForFile( + named_imports_, + &imports_to_bind[id], + source_index, + ); + } + const export_kind = exports_kind[id]; + + // If we're exporting as CommonJS and this file was originally CommonJS, + // then we'll be using the actual CommonJS "exports" and/or "module" + // symbols. In that case make sure to mark them as such so they don't + // get minified. + if ((output_format == .cjs or output_format == .preserve) and + entry_point_kinds[source_index].isEntryPoint() and + export_kind == .cjs and flags[id].wrap == .none) + { + const exports_ref = symbols.follow(exports_refs[id]); + const module_ref = symbols.follow(module_refs[id].?); + symbols.get(exports_ref).?.kind = .unbound; + symbols.get(module_ref).?.kind = .unbound; + } else if (flags[id].force_include_exports_for_entry_points or export_kind != .cjs) { + flags[id].needs_exports_variable = true; + } + + // Create the wrapper part for wrapped files. This is needed by a later step. + this.createWrapperForFile( + flags[id].wrap, + // if this one is null, the AST does not need to be wrapped. + this.graph.ast.items(.wrapper_ref)[id] orelse continue, + &wrapper_part_indices[id], + source_index, + id, + ); + } + } + + // Step 5: Create namespace exports for every file. This is always necessary + // for CommonJS files, and is also necessary for other files if they are + // imported using an import star statement. + // Note: `do` will wait for all to finish before moving forward + try this.parse_graph.pool.pool.do(this.allocator, &this.wait_group, this, doStep5, this.graph.reachable_files); + } + // Step 6: Bind imports to exports. This adds non-local dependencies on the + // parts that declare the export to all parts that use the import. Also + // generate wrapper parts for wrapped files. + { + const bufPrint = std.fmt.bufPrint; + var parts_list: []js_ast.Part.List = this.graph.ast.items(.parts); + var wrapper_refs = this.graph.ast.items(.wrapper_ref); + // const needs_export_symbol_from_runtime: []const bool = this.graph.meta.items(.needs_export_symbol_from_runtime); + var imports_to_bind_list: []RefImportData = this.graph.meta.items(.imports_to_bind); + var runtime_export_symbol_ref: Ref = Ref.None; + var entry_point_kinds: []EntryPoint.Kind = this.graph.files.items(.entry_point_kind); + const flags: []const JSMeta.Flags = this.graph.meta.items(.flags); + const exports_kind = this.graph.ast.items(.exports_kind); + const exports_refs = this.graph.ast.items(.exports_ref); + const module_refs = this.graph.ast.items(.module_ref); + const named_imports = this.graph.ast.items(.named_imports); + const import_records_list = this.graph.ast.items(.import_records); + const export_star_import_records = this.graph.ast.items(.export_star_import_records); + for (reachable) |source_index_| { + const source_index = source_index_.get(); + const id = source_index; + if (id >= max_id) { + continue; + } + + const is_entry_point = entry_point_kinds[source_index].isEntryPoint(); + const aliases = this.graph.meta.items(.sorted_and_filtered_export_aliases)[id]; + const flag = flags[id]; + const wrap = flag.wrap; + const export_kind = exports_kind[id]; + const source: *const Logger.Source = &this.parse_graph.input_files.items(.source)[source_index]; + + const exports_ref = exports_refs[id]; + var exports_symbol: ?*js_ast.Symbol = if (exports_ref.isValid()) + this.graph.symbols.get(exports_ref) + else + null; + const module_ref = module_refs[id] orelse Ref.None; + var module_symbol: ?*js_ast.Symbol = if (module_ref.isValid()) + this.graph.symbols.get(module_ref) + else + null; + + // TODO: see if counting and batching into a single large allocation instead of per-file improves perf + const string_buffer_len: usize = brk: { + var count: usize = 0; + if (is_entry_point and this.options.output_format == .esm) { + for (aliases) |alias| { + count += std.fmt.count("{}", .{strings.fmtIdentifier(alias)}); + } + count *= "export_".len; + } + + var ident_fmt_len: usize = 0; + if (wrap == .esm or (wrap != .cjs and export_kind != .cjs)) { + ident_fmt_len += if (source.identifier_name.len > 0) + source.identifier_name.len + else + std.fmt.count("{}", .{source.fmtIdentifier()}); + } + + if (wrap == .esm) { + count += "init_".len + ident_fmt_len; + } + + if (wrap != .cjs and export_kind != .cjs) { + count += "exports_".len + ident_fmt_len; + count += "module_".len + ident_fmt_len; + } + + break :brk count; + }; + + var string_buffer = this.allocator.alloc(u8, string_buffer_len) catch unreachable; + var buf = string_buffer; + + defer std.debug.assert(buf.len == 0); // ensure we used all of it + + // Pre-generate symbols for re-exports CommonJS symbols in case they + // are necessary later. This is done now because the symbols map cannot be + // mutated later due to parallelism. + if (is_entry_point and this.options.output_format == .esm) { + var copies = this.allocator.alloc(Ref, aliases.len) catch unreachable; + + for (aliases) |alias, i| { + const original_name = bufPrint(buf, "export_{}", .{strings.fmtIdentifier(alias)}) catch unreachable; + buf = buf[original_name.len..]; + copies[i] = this.graph.generateNewSymbol(source_index, .other, original_name); + } + this.graph.meta.items(.cjs_export_copies)[id] = copies; + } + + // Use "init_*" for ESM wrappers instead of "require_*" + if (wrap == .esm) { + const original_name = bufPrint( + buf, + "init_{}", + .{ + source.fmtIdentifier(), + }, + ) catch unreachable; + + buf = buf[original_name.len..]; + this.graph.symbols.get(wrapper_refs[id].?).?.original_name = original_name; + } + + // If this isn't CommonJS, then rename the unused "exports" and "module" + // variables to avoid them causing the identically-named variables in + // actual CommonJS files from being renamed. This is purely about + // aesthetics and is not about correctness. This is done here because by + // this point, we know the CommonJS status will not change further. + if (wrap != .cjs and export_kind != .cjs) { + const exports_name = bufPrint(buf, "exports_{s}", .{source.fmtIdentifier()}) catch unreachable; + buf = buf[exports_name.len..]; + const module_name = bufPrint(buf, "module_{s}", .{source.fmtIdentifier()}) catch unreachable; + buf = buf[module_name.len..]; + if (exports_symbol != null) + exports_symbol.?.original_name = exports_name; + if (module_symbol != null) + module_symbol.?.original_name = module_name; + } + + // Include the "__export" symbol from the runtime if it was used in the + // previous step. The previous step can't do this because it's running in + // parallel and can't safely mutate the "importsToBind" map of another file. + if (flag.needs_exports_variable) { + if (!runtime_export_symbol_ref.isValid()) { + runtime_export_symbol_ref = this.graph.ast.items(.module_scope)[Index.runtime.get()].?.members.get("__export").?.ref; + } + + std.debug.assert(runtime_export_symbol_ref.isValid()); + + this.graph.generateSymbolImportAndUse( + id, + js_ast.namespace_export_part_index, + runtime_export_symbol_ref, + 1, + Index.runtime, + ) catch unreachable; + } + + var imports_to_bind = imports_to_bind_list[id]; + var imports_to_bind_iter = imports_to_bind.iterator(); + + var parts: []js_ast.Part = parts_list[id].slice(); + while (imports_to_bind_iter.next()) |import| { + const import_source_index = import.value_ptr.data.source_index.get(); + const import_id = import_source_index; + + const import_ref = import.key_ptr.*; + var named_import = named_imports[import_id].getPtr(import_ref) orelse continue; + const parts_declaring_symbol = this.topLevelSymbolsToParts(import_id, import_ref); + + for (named_import.local_parts_with_uses.slice()) |part_index| { + var part: *js_ast.Part = &parts[part_index]; + + part.dependencies.ensureUnusedCapacity( + this.allocator, + parts_declaring_symbol.len + @as(usize, import.value_ptr.re_exports.len), + ) catch unreachable; + + // Depend on the file containing the imported symbol + for (parts_declaring_symbol) |resolved_part_index| { + part.dependencies.appendAssumeCapacity( + .{ + .source_index = Index.source(import_source_index), + .part_index = resolved_part_index, + }, + ); + } + + // Also depend on any files that re-exported this symbol in between the + // file containing the import and the file containing the imported symbol + part.dependencies.appendSliceAssumeCapacity(import.value_ptr.re_exports.slice()); + } + + // Merge these symbols so they will share the same name + _ = this.graph.symbols.merge(import_ref, import.value_ptr.data.import_ref); + } + + // If this is an entry point, depend on all exports so they are included + if (is_entry_point) { + const force_include_exports = flag.force_include_exports_for_entry_point; + const add_wrapper = wrap != .none; + var dependencies = std.ArrayList(js_ast.Dependency).initCapacity( + this.allocator, + @as(usize, @boolToInt(force_include_exports)) + @as(usize, @boolToInt(add_wrapper)), + ) catch unreachable; + var resolved_exports_list: *RefExportData = &this.graph.meta.items(.resolved_exports)[id]; + for (aliases) |alias| { + var export_ = resolved_exports_list.get(alias).?; + var target_source_index = export_.data.source_index.get(); + var target_id = target_source_index; + var target_ref = export_.data.import_ref; + + // If this is an import, then target what the import points to + + if (imports_to_bind.get(target_ref)) |import_data| { + target_source_index = import_data.data.source_index.get(); + target_id = target_source_index; + target_ref = import_data.data.import_ref; + dependencies.appendSlice(import_data.re_exports.slice()) catch unreachable; + } + + const top_to_parts = this.topLevelSymbolsToParts(target_id, target_ref); + dependencies.ensureUnusedCapacity(top_to_parts.len) catch unreachable; + // Pull in all declarations of this symbol + for (top_to_parts) |part_index| { + dependencies.appendAssumeCapacity( + .{ + .source_index = Index.source(target_source_index), + .part_index = part_index, + }, + ); + } + } + + dependencies.ensureUnusedCapacity(@as(usize, @boolToInt(force_include_exports)) + @as(usize, @boolToInt(add_wrapper))) catch unreachable; + + // Ensure "exports" is included if the current output format needs it + if (force_include_exports) { + dependencies.appendAssumeCapacity( + .{ .source_index = Index.source(source_index), .part_index = js_ast.namespace_export_part_index }, + ); + } + + if (add_wrapper) { + dependencies.appendAssumeCapacity( + .{ + .source_index = Index.source(source_index), + .part_index = this.graph.meta.items(.wrapper_part_index)[id].get(), + }, + ); + } + + // Represent these constraints with a dummy part + const entry_point_part_index = this.graph.addPartToFile( + id, + .{ + .dependencies = js_ast.Dependency.List.fromList(dependencies), + .can_be_removed_if_unused = false, + }, + ) catch unreachable; + parts = parts_list[id].slice(); + this.graph.meta.items(.entry_point_part_index)[id] = Index.part(entry_point_part_index); + + // Pull in the "__toCommonJS" symbol if we need it due to being an entry point + if (force_include_exports) { + this.graph.generateRuntimeSymbolImportAndUse( + source_index, + Index.part(entry_point_part_index), + "__toCommonJS", + 1, + ) catch unreachable; + } + } + + // Encode import-specific constraints in the dependency graph + var import_records = import_records_list[id].slice(); + debug("Binding {d} imports for file {s} (#{d})", .{ import_records.len, source.path.text, id }); + + for (parts) |*part, part_index| { + var to_esm_uses: u32 = 0; + var to_common_js_uses: u32 = 0; + var runtime_require_uses: u32 = 0; + + for (part.import_record_indices.slice()) |import_record_index| { + var record = &import_records[import_record_index]; + const kind = record.kind; + + // Don't follow external imports (this includes import() expressions) + if (!record.source_index.isValid() or this.isExternalDynamicImport(record, source_index)) { + // This is an external import. Check if it will be a "require()" call. + if (kind == .require or !output_format.keepES6ImportExportSyntax() or + (kind == .dynamic)) + { + // We should use "__require" instead of "require" if we're not + // generating a CommonJS output file, since it won't exist otherwise + if (shouldCallRuntimeRequire(output_format)) { + record.calls_runtime_require = true; + runtime_require_uses += 1; + } + + // If this wasn't originally a "require()" call, then we may need + // to wrap this in a call to the "__toESM" wrapper to convert from + // CommonJS semantics to ESM semantics. + // + // Unfortunately this adds some additional code since the conversion + // is somewhat complex. As an optimization, we can avoid this if the + // following things are true: + // + // - The import is an ES module statement (e.g. not an "import()" expression) + // - The ES module namespace object must not be captured + // - The "default" and "__esModule" exports must not be accessed + // + if (kind != .require and + (kind != .stmt or + record.contains_import_star or + record.contains_default_alias or + record.contains_es_module_alias)) + { + record.wrap_with_to_esm = true; + to_esm_uses += 1; + } + } + continue; + } + + const other_source_index = record.source_index.get(); + const other_id = other_source_index; + std.debug.assert(@intCast(usize, other_id) < this.graph.meta.len); + + const other_export_kind = exports_kind[other_id]; + + switch (wrap) { + else => { + + // Depend on the automatically-generated require wrapper symbol + const wrapper_ref = wrapper_refs[other_id].?; + this.graph.generateSymbolImportAndUse( + source_index, + @intCast(u32, part_index), + wrapper_ref, + 1, + Index.source(other_source_index), + ) catch unreachable; + + // This is an ES6 import of a CommonJS module, so it needs the + // "__toESM" wrapper as long as it's not a bare "require()" + if (kind != .require and other_export_kind == .cjs) { + record.wrap_with_to_esm = true; + to_esm_uses += 1; + } + }, + .none => { + if (kind == .stmt and other_export_kind == .esm_with_dynamic_fallback) { + // This is an import of a module that has a dynamic export fallback + // object. In that case we need to depend on that object in case + // something ends up needing to use it later. This could potentially + // be omitted in some cases with more advanced analysis if this + // dynamic export fallback object doesn't end up being needed. + this.graph.generateSymbolImportAndUse( + source_index, + @intCast(u32, part_index), + this.graph.ast.items(.exports_ref)[other_id], + 1, + Index.source(other_source_index), + ) catch unreachable; + } + }, + } + } + + // If there's an ES6 import of a non-ES6 module, then we're going to need the + // "__toESM" symbol from the runtime to wrap the result of "require()" + this.graph.generateRuntimeSymbolImportAndUse( + source_index, + Index.part(part_index), + + // TODO: implement this runtime symbol + "__toESM", + to_esm_uses, + ) catch unreachable; + + // If there's a CommonJS require of an ES6 module, then we're going to need the + // "__toCommonJS" symbol from the runtime to wrap the exports object + this.graph.generateRuntimeSymbolImportAndUse( + source_index, + Index.part(part_index), + "__toCommonJS", + to_common_js_uses, + ) catch unreachable; + + // If there are unbundled calls to "require()" and we're not generating + // code for node, then substitute a "__require" wrapper for "require". + this.graph.generateRuntimeSymbolImportAndUse( + source_index, + Index.part(part_index), + + // TODO: refactor this runtime symbol + "__require", + runtime_require_uses, + ) catch unreachable; + + // If there's an ES6 export star statement of a non-ES6 module, then we're + // going to need the "__reExport" symbol from the runtime + var re_export_uses: u32 = 0; + + for (export_star_import_records[id]) |import_record_index| { + var record = &import_records[import_record_index]; + + var happens_at_runtime = record.source_index.isInvalid() and (!is_entry_point or !output_format.keepES6ImportExportSyntax()); + if (record.source_index.isValid()) { + var other_source_index = record.source_index.get(); + const other_id = other_source_index; + std.debug.assert(@intCast(usize, other_id) < this.graph.meta.len); + const other_export_kind = exports_kind[other_id]; + if (other_source_index != source_index and other_export_kind.isDynamic()) { + happens_at_runtime = true; + } + + if (other_export_kind == .esm_with_dynamic_fallback) { + // This looks like "__reExport(exports_a, exports_b)". Make sure to + // pull in the "exports_b" symbol into this export star. This matters + // in code splitting situations where the "export_b" symbol might live + // in a different chunk than this export star. + this.graph.generateSymbolImportAndUse( + source_index, + @intCast(u32, part_index), + this.graph.ast.items(.exports_ref)[other_id], + 1, + Index.source(other_source_index), + ) catch unreachable; + } + } + + if (happens_at_runtime) { + // Depend on this file's "exports" object for the first argument to "__reExport" + this.graph.generateSymbolImportAndUse( + source_index, + @intCast(u32, part_index), + this.graph.ast.items(.exports_ref)[id], + 1, + Index.source(source_index), + ) catch unreachable; + this.graph.ast.items(.uses_exports_ref)[id] = true; + record.calls_runtime_re_export_fn = true; + re_export_uses += 1; + } + } + + this.graph.generateRuntimeSymbolImportAndUse( + source_index, + Index.part(part_index), + + "__reExport", + re_export_uses, + ) catch unreachable; + } + } + } + } + + pub fn createExportsForFile(c: *LinkerContext, allocator_: std.mem.Allocator, id: u32, resolved_exports: *RefExportData, imports_to_bind: []RefImportData, export_aliases: []const string, re_exports_count: usize) void { + //////////////////////////////////////////////////////////////////////////////// + // WARNING: This method is run in parallel over all files. Do not mutate data + // for other files within this method or you will create a data race. + //////////////////////////////////////////////////////////////////////////////// + + // 1 property per export + var properties = std.ArrayList(js_ast.G.Property) + .initCapacity(allocator_, export_aliases.len) catch unreachable; + + var ns_export_symbol_uses = js_ast.Part.SymbolUseMap{}; + ns_export_symbol_uses.ensureTotalCapacity(allocator_, export_aliases.len) catch unreachable; + + const needs_exports_variable = c.graph.meta.items(.flags)[id].needs_exports_variable; + + const stmts_count = + // 3 statements for every export + export_aliases.len * 3 + + // + 1 if there are non-zero exports + @as(usize, @boolToInt(export_aliases.len > 0)) + + // + 1 if we need to inject the exports variable + @as(usize, @boolToInt(needs_exports_variable)); + + var stmts = js_ast.Stmt.Batcher.init(allocator_, stmts_count) catch unreachable; + defer stmts.done(); + const loc = Logger.Loc.Empty; + // todo: investigate if preallocating this array is faster + var ns_export_dependencies = std.ArrayList(js_ast.Dependency).initCapacity(allocator_, re_exports_count) catch unreachable; + + for (export_aliases) |alias| { + var export_ = resolved_exports.getPtr(alias).?; + + const other_id = export_.data.source_index.get(); + + // If this is an export of an import, reference the symbol that the import + // was eventually resolved to. We need to do this because imports have + // already been resolved by this point, so we can't generate a new import + // and have that be resolved later. + if (imports_to_bind[other_id].get(export_.data.import_ref)) |import_data| { + export_.data = import_data.data; + ns_export_dependencies.appendSlice(import_data.re_exports.slice()) catch unreachable; + } + + // Exports of imports need EImportIdentifier in case they need to be re- + // written to a property access later on + // note: this is stack allocated + var value: js_ast.Expr = undefined; + if (c.graph.symbols.getConst(export_.data.import_ref).?.namespace_alias != null) { + value = js_ast.Expr.init( + js_ast.E.ImportIdentifier, + js_ast.E.ImportIdentifier{ + .ref = export_.data.import_ref, + }, + loc, + ); + } else { + value = js_ast.Expr.init( + js_ast.E.Identifier, + js_ast.E.Identifier{ + .ref = export_.data.import_ref, + }, + loc, + ); + } + + var block = stmts.eat1( + js_ast.Stmt.alloc(js_ast.S.Block, .{ + .stmts = stmts.eat1( + js_ast.Stmt.alloc( + js_ast.S.Return, + .{ .value = value }, + loc, + ), + ), + }, loc), + ); + const fn_body = js_ast.G.FnBody{ + .stmts = block, + .loc = loc, + }; + properties.appendAssumeCapacity( + .{ + .key = js_ast.Expr.init( + js_ast.E.String, + .{ + // TODO: test emoji work as expected + // relevant for WASM exports + .data = alias, + }, + loc, + ), + .value = js_ast.Expr.init( + js_ast.E.Arrow, + .{ .prefer_expr = true, .body = fn_body }, + loc, + ), + }, + ); + ns_export_symbol_uses.putAssumeCapacity(export_.data.import_ref, .{ .count_estimate = 1 }); + + // Make sure the part that declares the export is included + const parts = c.topLevelSymbolsToParts(other_id, export_.data.import_ref); + ns_export_dependencies.ensureUnusedCapacity(parts.len) catch unreachable; + var ptr = ns_export_dependencies.items.ptr + ns_export_dependencies.items.len; + ns_export_dependencies.items.len += parts.len; + + for (parts) |part_id| { + // Use a non-local dependency since this is likely from a different + // file if it came in through an export star + ptr[0] = .{ + .source_index = export_.data.source_index, + .part_index = part_id, + }; + ptr += 1; + } + } + + var declared_symbols = js_ast.DeclaredSymbol.List{}; + var exports_ref = c.graph.ast.items(.exports_ref)[id]; + var export_stmts: []js_ast.Stmt = stmts.head; + std.debug.assert(stmts.head.len <= 2); // assert we allocated exactly the right amount + stmts.head.len = 0; + + // Prefix this part with "var exports = {}" if this isn't a CommonJS entry point + if (needs_exports_variable) { + var decls = allocator_.alloc(js_ast.G.Decl, 1) catch unreachable; + decls[0] = .{ + .binding = js_ast.Binding.alloc( + allocator_, + js_ast.B.Identifier{ + .ref = exports_ref, + }, + loc, + ), + .value = js_ast.Expr.init(js_ast.E.Object, .{}, loc), + }; + export_stmts[0] = js_ast.Stmt.alloc( + js_ast.S.Local, + .{ + .decls = decls, + }, + export_stmts[0].loc, + ); + declared_symbols.append(allocator_, .{ .ref = exports_ref, .is_top_level = true }) catch unreachable; + } + + // "__export(exports, { foo: () => foo })" + var export_ref = Ref.None; + if (properties.items.len > 0) { + export_ref = c.graph.ast.items(.module_scope)[Index.runtime.get()].?.members.get("__export").?.ref; + var args = allocator_.alloc(js_ast.Expr, 2) catch unreachable; + args[0..2].* = [_]js_ast.Expr{ + js_ast.Expr.initIdentifier(exports_ref, loc), + js_ast.Expr.init(js_ast.E.Object, .{ .properties = js_ast.G.Property.List.fromList(properties) }, loc), + }; + // the end incase we somehow get into a state where needs_export_variable is false but properties.len > 0 + export_stmts[export_stmts.len - 1] = js_ast.Stmt.alloc( + js_ast.S.SExpr, + .{ + .value = js_ast.Expr.init( + js_ast.E.Call, + .{ + .target = js_ast.Expr.initIdentifier(export_ref, loc), + .args = js_ast.ExprNodeList.init(args), + }, + loc, + ), + }, + loc, + ); + + // Make sure this file depends on the "__export" symbol + const parts = c.topLevelSymbolsToPartsForRuntime(export_ref); + ns_export_dependencies.ensureUnusedCapacity(parts.len) catch unreachable; + for (parts) |part_index| { + ns_export_dependencies.appendAssumeCapacity( + .{ .source_index = Index.runtime, .part_index = part_index }, + ); + } + + // Make sure the CommonJS closure, if there is one, includes "exports" + c.graph.ast.items(.uses_exports_ref)[id] = true; + } + + // No need to generate a part if it'll be empty + if (export_stmts.len > 0) { + // - we must already have preallocated the parts array + // - if the parts list is completely empty, we shouldn't have gotten here in the first place + + // Initialize the part that was allocated for us earlier. The information + // here will be used after this during tree shaking. + c.graph.ast.items(.parts)[id].slice()[js_ast.namespace_export_part_index] = .{ + .stmts = export_stmts, + .symbol_uses = ns_export_symbol_uses, + .dependencies = js_ast.Dependency.List.fromList(ns_export_dependencies), + .declared_symbols = declared_symbols, + + // This can be removed if nothing uses it + .can_be_removed_if_unused = true, + + // Make sure this is trimmed if unused even if tree shaking is disabled + .force_tree_shaking = true, + }; + + // Pull in the "__export" symbol if it was used + if (export_ref.isValid()) { + c.graph.meta.items(.needs_export_symbol_from_runtime)[id] = true; + } + } + } + + /// Step 5: Create namespace exports for every file. This is always necessary + /// for CommonJS files, and is also necessary for other files if they are + /// imported using an import star statement. + pub fn doStep5(c: *LinkerContext, source_index_: Index, _: usize) void { + const source_index = source_index_.get(); + + const id = source_index; + if (id > c.graph.meta.len) return; + + var worker: *ThreadPool.Worker = @ptrCast( + *ThreadPool.Worker, + @alignCast( + @alignOf(*ThreadPool.Worker), + ThreadPoolLib.Thread.current.?.ctx.?, + ), + ); + // we must use this allocator here + const allocator_ = worker.allocator; + + var resolved_exports: *RefExportData = &c.graph.meta.items(.resolved_exports)[id]; + + // Now that all exports have been resolved, sort and filter them to create + // something we can iterate over later. + var aliases = std.ArrayList(string).initCapacity(allocator_, resolved_exports.count()) catch unreachable; + var alias_iter = resolved_exports.iterator(); + var imports_to_bind = c.graph.meta.items(.imports_to_bind); + var probably_typescript_type = c.graph.meta.items(.probably_typescript_type); + + // counting in here saves us an extra pass through the array + var re_exports_count: usize = 0; + + next_alias: while (alias_iter.next()) |entry| { + var export_ = entry.value_ptr.*; + var alias = entry.key_ptr.*; + const this_id = export_.data.source_index.get(); + var inner_count: usize = 0; + // Re-exporting multiple symbols with the same name causes an ambiguous + // export. These names cannot be used and should not end up in generated code. + if (export_.potentially_ambiguous_export_star_refs.len > 0) { + const main = imports_to_bind[this_id].get(export_.data.import_ref) orelse ImportData{ .data = export_.data }; + for (export_.potentially_ambiguous_export_star_refs.slice()) |ambig| { + const _id = ambig.data.source_index.get(); + const ambig_ref = if (imports_to_bind[_id].get(ambig.data.import_ref)) |bound| + bound.data.import_ref + else + ambig.data.import_ref; + if (!main.data.import_ref.eql(ambig_ref)) { + continue :next_alias; + } + inner_count += @as(usize, ambig.re_exports.len); + } + } + + // Ignore re-exported imports in TypeScript files that failed to be + // resolved. These are probably just type-only imports so the best thing to + // do is to silently omit them from the export list. + if (probably_typescript_type[this_id].contains(export_.data.import_ref)) { + continue; + } + re_exports_count += inner_count; + + aliases.appendAssumeCapacity(alias); + } + // TODO: can this be u32 instead of a string? + // if yes, we could just move all the hidden exports to the end of the array + // and only store a count instead of an array + strings.sortDesc(aliases.items); + const export_aliases = aliases.toOwnedSlice() catch unreachable; + c.graph.meta.items(.sorted_and_filtered_export_aliases)[id] = export_aliases; + + // Export creation uses "sortedAndFilteredExportAliases" so this must + // come second after we fill in that array + c.createExportsForFile( + allocator_, + id, + resolved_exports, + imports_to_bind, + export_aliases, + re_exports_count, + ); + + // Each part tracks the other parts it depends on within this file + var local_dependencies = std.AutoHashMap(u32, u32).init(allocator_); + defer local_dependencies.deinit(); + var parts = &c.graph.ast.items(.parts)[id]; + var parts_slice: []js_ast.Part = parts.slice(); + var named_imports: *js_ast.Ast.NamedImports = &c.graph.ast.items(.named_imports)[id]; + for (parts_slice) |*part, part_index| { + + // TODO: inline const TypeScript enum here + + // TODO: inline function calls here + + // note: if we crash on append, it is due to threadlocal heaps in mimalloc + const symbol_uses = part.symbol_uses.keys(); + for (symbol_uses) |ref, j| { + if (comptime Environment.allow_assert) { + std.debug.assert(part.symbol_uses.values()[j].count_estimate > 0); + } + + // TODO: inline const values from an import + + const other_parts = c.topLevelSymbolsToParts(id, ref); + + for (other_parts) |other_part_index| { + var local = local_dependencies.getOrPutValue(@intCast(u32, other_part_index), @intCast(u32, part_index)) catch unreachable; + if (local.value_ptr.* != @intCast(u32, part_index)) { + local.value_ptr.* = @intCast(u32, part_index); + // note: if we crash on append, it is due to threadlocal heaps in mimalloc + part.dependencies.push( + allocator_, + .{ + .source_index = Index.source(source_index), + .part_index = other_part_index, + }, + ) catch unreachable; + } + } + + // Also map from imports to parts that use them + if (named_imports.getPtr(ref)) |existing| { + existing.local_parts_with_uses.push(allocator_, @intCast(u32, part_index)) catch unreachable; + } + } + } + } + + const MatchImport = struct { + alias: string = "", + kind: MatchImport.Kind = MatchImport.Kind.ignore, + namespace_ref: Ref = Ref.None, + source_index: u32 = 0, + name_loc: Logger.Loc = Logger.Loc.Empty, // Optional, goes with sourceIndex, ignore if zero, + other_source_index: u32 = 0, + other_name_loc: Logger.Loc = Logger.Loc.Empty, // Optional, goes with otherSourceIndex, ignore if zero, + ref: Ref = Ref.None, + + pub const Kind = enum { + /// The import is either external or undefined + ignore, + + /// "sourceIndex" and "ref" are in use + normal, + + /// "namespaceRef" and "alias" are in use + namespace, + + /// Both "normal" and "namespace" + normal_and_namespace, + + /// The import could not be evaluated due to a cycle + cycle, + + /// The import is missing but came from a TypeScript file + probably_typescript_type, + + /// The import resolved to multiple symbols via "export * from" + ambiguous, + }; + }; + pub fn source_(c: *LinkerContext, index: anytype) *const Logger.Source { + return &c.parse_graph.input_files.items(.source)[index]; + } + + pub fn treeShakingAndCodeSplitting(c: *LinkerContext) !void { + var parts = c.graph.ast.items(.parts); + var import_records = c.graph.ast.items(.import_records); + var side_effects = c.parse_graph.input_files.items(.side_effects); + var entry_point_kinds = c.graph.files.items(.entry_point_kind); + const entry_points = c.graph.entry_points.items(.source_index); + var distances = c.graph.files.items(.distance_from_entry_point); + + // Tree shaking: Each entry point marks all files reachable from itself + for (entry_points) |entry_point| { + c.markFileLiveForTreeShaking( + entry_point, + side_effects, + parts, + import_records, + entry_point_kinds, + ); + } + + var file_entry_bits: []AutoBitSet = c.graph.files.items(.entry_bits); + // AutoBitSet needs to be initialized if it is dynamic + if (AutoBitSet.needsDynamic(entry_points.len)) { + for (file_entry_bits) |*bits| { + bits.* = try AutoBitSet.initEmpty(c.allocator, entry_points.len); + } + } else if (file_entry_bits.len > 0) { + // assert that the tag is correct + std.debug.assert(file_entry_bits[0] == .static); + } + + // Code splitting: Determine which entry points can reach which files. This + // has to happen after tree shaking because there is an implicit dependency + // between live parts within the same file. All liveness has to be computed + // first before determining which entry points can reach which files. + for (entry_points) |entry_point, i| { + c.markFileReachableForCodeSplitting( + entry_point, + i, + distances, + 0, + parts, + import_records, + file_entry_bits, + ); + } + } + + pub noinline fn computeCrossChunkDependencies(c: *LinkerContext, chunks: []Chunk) !void { + var js_chunks_count: usize = 0; + for (chunks) |*chunk| { + js_chunks_count += chunk.content == .javascript; + } + + // TODO: remove this branch before release. We are keeping it to assert this code is correct + if (comptime !Environment.allow_assert) { + // No need to compute cross-chunk dependencies if there can't be any + if (js_chunks_count < 2) + return; + } + + const ChunkMeta = struct { + imports: std.ArrayHashMap(Ref, void, Ref.ArrayHashCtx, false), + exports: std.ArrayHashMap(Ref, void, Ref.ArrayHashCtx, false), + dynamic_imports: std.ArrayHashMap(Index.Int, void, Ref.ArrayHashCtx, false), + }; + var chunk_metas = try c.allocator.alloc(ChunkMeta, chunks.len); + for (chunks) |*meta| { + // these must be global allocator + meta.* = .{ + .imports = try std.ArrayHashMap(Ref, void, Ref.ArrayHashCtx, false).init(bun.default_allocator), + .exports = try std.ArrayHashMap(Ref, void, Ref.ArrayHashCtx, false).init(bun.default_allocator), + .dynamic_imports = try std.ArrayHashMap(Index.Int, void, Ref.ArrayHashCtx, false).init(bun.default_allocator), + }; + } + defer { + for (chunk_metas) |meta| { + meta.imports.deinit(); + meta.exports.deinit(); + meta.dynamic_imports.deinit(); + } + c.allocators.free(chunk_metas); + } + + const CrossChunkDependencies = struct { + chunk_meta: []ChunkMeta, + parts: [][]js_ast.Part, + import_records: [][]bun.ImportRecord, + flags: []const JSMeta.Flags, + entry_point_chunk_indices: [][]Index.Int, + imports_to_bind: []RefImportData, + wrapper_refs: []const Ref, + sorted_and_filtered_export_aliases: []const string, + resolved_exports: []const RefExportData, + ctx: *LinkerContext, + symbols: *Symbol.Map, + + pub fn walk(deps: *@This(), chunk: *Chunk, chunk_index: usize) void { + var chunk_meta = &deps.chunk_meta[chunk_index]; + const entry_point_chunk_indices = deps.entry_point_chunk_indices[chunk_index]; + + // Go over each file in this chunk + for (chunk.files_with_parts_in_chunk.keys()) |source_index| { + if (chunk.content != .javascript) continue; + + // Go over each part in this file that's marked for inclusion in this chunk + var parts = deps.parts[source_index]; + var import_records = deps.import_records[source_index]; + const imports_to_bind = deps.imports_to_bind[source_index]; + const wrap = deps.flags[source_index].wrap; + const wrapper_ref = deps.wrapper_refs[source_index]; + + for (parts) |*part| { + if (!part.is_live) + continue; + + // Rewrite external dynamic imports to point to the chunk for that entry point + for (part.import_record_indices.slice()) |import_record_id| { + var import_record = &import_records[import_record_id]; + if (import_record.source_index.isValid() and deps.ctx.isExternalDynamicImport(import_record, source_index)) { + const other_chunk_index = entry_point_chunk_indices[import_record.source_index.get()]; + import_record.path.text = chunks[other_chunk_index].unique_key; + import_record.source_index = Index.invalid; + + // Track this cross-chunk dynamic import so we make sure to + // include its hash when we're calculating the hashes of all + // dependencies of this chunk. + if (other_chunk_index != chunk_index) + chunk_meta.dynamic_imports.put(other_chunk_index) catch unreachable; + } + } + + // Remember what chunk each top-level symbol is declared in. Symbols + // with multiple declarations such as repeated "var" statements with + // the same name should already be marked as all being in a single + // chunk. In that case this will overwrite the same value below which + // is fine. + deps.symbols.assignChunkIndex(part.declared_symbols, chunk_index); + + for (part.symbol_uses.keys()) |ref_| { + var ref = ref_; + var symbol = deps.symbols.get(ref).?; + + // Ignore unbound symbols + if (symbol.kind == .unbound) + continue; + + // Ignore symbols that are going to be replaced by undefined + if (symbol.import_item_status == .missing) { + continue; + } + + // If this is imported from another file, follow the import + // reference and reference the symbol in that file instead + if (imports_to_bind.get(ref)) |import_data| { + ref = import_data.ref; + symbol = deps.symbols.get(ref).?; + } else if (wrap == .cjs and ref.eql(wrapper_ref)) { + // The only internal symbol that wrapped CommonJS files export + // is the wrapper itself. + continue; + } + + // If this is an ES6 import from a CommonJS file, it will become a + // property access off the namespace symbol instead of a bare + // identifier. In that case we want to pull in the namespace symbol + // instead. The namespace symbol stores the result of "require()". + if (symbol.namespace_alias) |*namespace_alias| { + ref = namespace_alias.ref; + } + + // We must record this relationship even for symbols that are not + // imports. Due to code splitting, the definition of a symbol may + // be moved to a separate chunk than the use of a symbol even if + // the definition and use of that symbol are originally from the + // same source file. + chunk_meta.imports.put(ref, void{}) catch unreachable; + } + } + } + + // Include the exports if this is an entry point chunk + if (chunk.content == .javascript) { + if (chunk.entry_point.is_entry_point) { + const flags = deps.flags[chunk.entry_point.source_index.get()]; + if (flags.wrap != .cjs) { + for (deps.sorted_and_filtered_export_aliases[chunk.entry_point.source_index]) |alias| { + const export_ = deps.resolved_exports[chunk.entry_point.source_index].get(alias).?; + var target_ref = export_.data.import_ref; + + // If this is an import, then target what the import points to + if (deps.imports_to_bind[export_.data.source_index.get()].get(target_ref)) |import_data| { + target_ref = import_data.data.import_ref; + } + + // If this is an ES6 import from a CommonJS file, it will become a + // property access off the namespace symbol instead of a bare + // identifier. In that case we want to pull in the namespace symbol + // instead. The namespace symbol stores the result of "require()". + if (deps.symbols.get(target_ref).?.namespace_alias) |namespace_alias| { + target_ref = namespace_alias.ref; + } + + chunk_meta.imports.put(target_ref, void{}) catch unreachable; + } + } + + // Ensure "exports" is included if the current output format needs it + if (flags.force_include_exports_for_entry_point) { + chunk_meta.imports.put(deps.wrapper_refs[chunk.entry_point.source_index.get()], void{}) catch unreachable; + } + + // Include the wrapper if present + if (flags.wrap != .none) { + chunk_meta.imports.put(deps.wrapper_refs[chunk.entry_point.source_index], void{}) catch unreachable; + } + } + } + } + }; + + var cross_chunk_dependencies = CrossChunkDependencies{ + .chunks = chunks, + .chunk_meta = chunk_metas, + + .parts = c.graph.ast.items(.parts), + .import_records = c.graph.ast.items(.import_records), + .flags = c.graph.meta.items(.flags), + .entry_point_chunk_indices = c.graph.files.items(.entry_point_chunk_index), + .imports_to_bind = c.graph.meta.items(.imports_to_bind), + .wrapper_refs = c.graph.ast.items(.wrapper_ref), + .sorted_and_filtered_export_aliases = c.graph.meta.items(.sorted_and_filtered_export_aliases), + .resolved_exports = c.graph.meta.items(.resolved_exports), + .ctx = c, + .symbols = &c.graph.symbols, + }; + + c.parse_graph.pool.pool.doPtr( + c.allocator, + &c.wait_group, + cross_chunk_dependencies, + chunks, + ); + + // Mark imported symbols as exported in the chunk from which they are declared + for (chunks) |*chunk, chunk_index| { + if (chunk.content != .javascript) { + continue; + } + + var js = &chunk.content.javascript; + + var chunk_meta = &chunk_metas[chunk_index]; + // Find all uses in this chunk of symbols from other chunks + for (chunk_meta.imports.keys()) |import_ref| { + const symbol = c.graph.symbols.get(import_ref).?; + + // Ignore uses that aren't top-level symbols + if (symbol.chunk_index) |other_chunk_index| { + if (@as(usize, other_chunk_index) != chunk_index) { + { + var entry = try js.imports_from_other_chunks.getOrPutValue(c.allocator, .{}); + try entry.value_ptr.push(c.allocator, .{ + .ref = import_ref, + }); + } + + chunk_metas[other_chunk_index].exports.put(import_ref, void{}) catch unreachable; + } + } + } + + // If this is an entry point, make sure we import all chunks belonging to + // this entry point, even if there are no imports. We need to make sure + // these chunks are evaluated for their side effects too. + if (chunk.entry_point.is_entry_point) { + for (chunks) |*other_chunk, other_chunk_index| { + if (other_chunk_index == chunk_index or other_chunk.content != .javascript) continue; + + if (other_chunk.entry_bits.isSet(chunk.entry_point.entry_point_id)) { + var imports = js.imports_from_other_chunks.get(c.allocator, @truncate(u32, other_chunk_index)) orelse .{}; + try js.imports_from_other_chunks.put(c.allocator, other_chunk_index, imports); + } + } + } + } + } + + pub fn markFileReachableForCodeSplitting( + c: *LinkerContext, + source_index: Index.Int, + entry_points_count: usize, + distances: []u32, + distance: u32, + parts: []bun.BabyList(js_ast.Part), + import_records: []bun.BabyList(bun.ImportRecord), + file_entry_bits: []AutoBitSet, + ) void { + if (!c.graph.files_live.isSet(source_index)) + return; + + const cur_dist = distances[source_index]; + const traverse_again = distance < cur_dist; + if (traverse_again) { + distances[source_index] = distance; + } + const out_dist = distance + 1; + + // Don't mark this file more than once + if (file_entry_bits[source_index].isSet(source_index, entry_points_count) and !traverse_again) + return; + + file_entry_bits[source_index].set(source_index, entry_points_count); + + if (comptime bun.Environment.allow_assert) + debug( + "markFileReachableForCodeSplitting: {s} ({d})", + .{ + c.parse_graph.input_files.get(source_index).source.path.text, + out_dist, + }, + ); + + // TODO: CSS AST + + for (import_records[source_index].slice()) |*record| { + if (record.source_index.isValid() and !c.isExternalDynamicImport(record, source_index)) { + c.markFileReachableForCodeSplitting( + record.source_index.get(), + entry_points_count, + distances, + out_dist, + parts, + import_records, + file_entry_bits, + ); + } + } + + for (parts[source_index].slice()) |*part| { + for (part.dependencies.slice()) |dependency| { + if (dependency.source_index.get() != source_index) { + c.markFileReachableForCodeSplitting( + dependency.source_index.get(), + entry_points_count, + distances, + out_dist, + parts, + import_records, + file_entry_bits, + ); + } + } + } + } + + pub fn markFileLiveForTreeShaking( + c: *LinkerContext, + source_index: Index.Int, + side_effects: []_resolver.SideEffects, + parts: []bun.BabyList(js_ast.Part), + import_records: []bun.BabyList(bun.ImportRecord), + entry_point_kinds: []EntryPoint.Kind, + ) void { + if (c.graph.files_live.isSet(source_index)) + return; + + c.graph.files_live.set(source_index); + if (comptime bun.Environment.allow_assert) + debug("markFileLiveForTreeShaking: {s}", .{c.parse_graph.input_files.get(source_index).source.path.text}); + + // TODO: CSS source index + + const id = source_index; + if (@as(usize, id) >= c.graph.ast.len) + return; + var _parts = parts[id].slice(); + for (_parts) |*part, part_index| { + var can_be_removed_if_unused = part.can_be_removed_if_unused; + + // Also include any statement-level imports + for (part.import_record_indices.slice()) |import_record_Index| { + var record: *ImportRecord = &import_records[source_index].slice()[import_record_Index]; + + if (record.kind != .stmt) + continue; + + if (record.source_index.isValid()) { + const other_source_index = record.source_index.get(); + + // Don't include this module for its side effects if it can be + // considered to have no side effects + if (side_effects[other_source_index] != .has_side_effects and !c.options.ignore_dce_annotations) { + continue; + } + + // Otherwise, include this module for its side effects + c.markFileLiveForTreeShaking( + other_source_index, + side_effects, + parts, + import_records, + entry_point_kinds, + ); + } else if (record.is_external_without_side_effects) { + // This can be removed if it's unused + continue; + } + + // If we get here then the import was included for its side effects, so + // we must also keep this part + can_be_removed_if_unused = false; + } + + // Include all parts in this file with side effects, or just include + // everything if tree-shaking is disabled. Note that we still want to + // perform tree-shaking on the runtime even if tree-shaking is disabled. + if (!can_be_removed_if_unused or + (!part.force_tree_shaking and + !c.options.tree_shaking and + entry_point_kinds[id].isEntryPoint())) + { + _ = c.markPartLiveForTreeShaking( + @intCast(u32, part_index), + id, + side_effects, + parts, + import_records, + entry_point_kinds, + ); + } + } + } + + pub fn markPartLiveForTreeShaking( + c: *LinkerContext, + part_index: Index.Int, + id: Index.Int, + side_effects: []_resolver.SideEffects, + parts: []bun.BabyList(js_ast.Part), + import_records: []bun.BabyList(bun.ImportRecord), + entry_point_kinds: []EntryPoint.Kind, + ) bool { + var part: *js_ast.Part = &parts[id].slice()[part_index]; + // only once + if (part.is_live) { + return false; + } + + part.is_live = true; + if (comptime bun.Environment.allow_assert) + debug("markPartLiveForTreeShaking({d}): {s}:{d}", .{ id, c.parse_graph.input_files.get(id).source.path.text, part_index }); + + for (part.dependencies.slice()) |dependency| { + const _id = dependency.source_index.get(); + if (c.markPartLiveForTreeShaking( + dependency.part_index, + _id, + side_effects, + parts, + import_records, + entry_point_kinds, + )) { + c.markFileLiveForTreeShaking( + _id, + side_effects, + parts, + import_records, + entry_point_kinds, + ); + } + } + + return true; + } + + pub fn matchImportWithExport( + c: *LinkerContext, + init_tracker: *ImportTracker, + re_exports: *std.ArrayList(js_ast.Dependency), + ) MatchImport { + var tracker = init_tracker; + var ambiguous_results = std.ArrayList(MatchImport).init(c.allocator); + defer ambiguous_results.clearAndFree(); + var result: MatchImport = MatchImport{}; + const named_imports = c.graph.ast.items(.named_imports); + var top_level_symbols_to_parts: []js_ast.Ast.TopLevelSymbolToParts = c.graph.ast.items(.top_level_symbols_to_parts); + + loop: while (true) { + // Make sure we avoid infinite loops trying to resolve cycles: + // + // // foo.js + // export {a as b} from './foo.js' + // export {b as c} from './foo.js' + // export {c as a} from './foo.js' + // + // This uses a O(n^2) array scan instead of a O(n) map because the vast + // majority of cases have one or two elements + for (c.cycle_detector.items) |prev_tracker| { + if (std.meta.eql(tracker.*, prev_tracker)) { + result = .{ .kind = .cycle }; + break :loop; + } + } + c.cycle_detector.append(tracker.*) catch unreachable; + + // Resolve the import by one step + var advanced = c.advanceImportTracker(tracker); + const next_tracker = advanced.tracker.*; + const status = advanced.status; + const potentially_ambiguous_export_star_refs = advanced.import_data; + const other_id = tracker.source_index.get(); + + switch (status) { + .cjs, .cjs_without_exports, .disabled, .external => { + if (status == .external and c.options.output_format.keepES6ImportExportSyntax()) { + // Imports from external modules should not be converted to CommonJS + // if the output format preserves the original ES6 import statements + break; + } + + // If it's a CommonJS or external file, rewrite the import to a + // property access. Don't do this if the namespace reference is invalid + // though. This is the case for star imports, where the import is the + // namespace. + const named_import: js_ast.NamedImport = named_imports[other_id].get(tracker.import_ref).?; + + if (named_import.namespace_ref != null and named_import.namespace_ref.?.isValid()) { + if (result.kind == .normal) { + result.kind = .normal_and_namespace; + result.namespace_ref = named_import.namespace_ref.?; + result.alias = named_import.alias.?; + } else { + result = .{ + .kind = .namespace, + .namespace_ref = named_import.namespace_ref.?, + .alias = named_import.alias.?, + }; + } + } + + // Warn about importing from a file that is known to not have any exports + if (status == .cjs_without_exports) { + const source = c.source_(tracker.source_index.get()); + c.log.addRangeWarningFmt( + source, + source.rangeOfIdentifier(named_import.alias_loc.?), + c.allocator, + "Import \"{s}\" will always be undefined because the file \"{s}\" has no exports", + .{ + named_import.alias.?, + source.path.pretty, + }, + ) catch unreachable; + } + }, + + .dynamic_fallback => { + // If it's a file with dynamic export fallback, rewrite the import to a property access + const named_import: js_ast.NamedImport = named_imports[other_id].get(tracker.import_ref).?; + if (named_import.namespace_ref != null and named_import.namespace_ref.?.isValid()) { + if (result.kind == .normal) { + result.kind = .normal_and_namespace; + result.namespace_ref = named_import.namespace_ref.?; + result.alias = named_import.alias.?; + } else { + result = .{ + .kind = .namespace, + .namespace_ref = named_import.namespace_ref.?, + .alias = named_import.alias.?, + }; + } + } + }, + .no_match => { + // Report mismatched imports and exports + const symbol = c.graph.symbols.get(tracker.import_ref).?; + const named_import: js_ast.NamedImport = named_imports[other_id].get(tracker.import_ref).?; + + const source = c.source_(tracker.source_index.get()); + const next_source = c.source_(next_tracker.source_index.get()); + const r = source.rangeOfIdentifier(named_import.alias_loc.?); + + // Report mismatched imports and exports + if (symbol.import_item_status == .generated) { + // This is a debug message instead of an error because although it + // appears to be a named import, it's actually an automatically- + // generated named import that was originally a property access on an + // import star namespace object. Normally this property access would + // just resolve to undefined at run-time instead of failing at binding- + // time, so we emit a debug message and rewrite the value to the literal + // "undefined" instead of emitting an error. + symbol.import_item_status = .missing; + c.log.addRangeWarningFmt( + source, + r, + c.allocator, + "Import \"{s}\" will always be undefined because there is no matching export in \"{s}\"", + .{ + named_import.alias.?, + next_source.path.pretty, + }, + ) catch unreachable; + } else { + c.log.addRangeErrorFmt( + source, + r, + c.allocator, + "No matching export in \"{s}\" for import \"{s}\"", + .{ + next_source.path.pretty, + named_import.alias.?, + }, + ) catch unreachable; + } + }, + .probably_typescript_type => { + // Omit this import from any namespace export code we generate for + // import star statements (i.e. "import * as ns from 'path'") + result = .{ .kind = .probably_typescript_type }; + }, + .found => { + + // If there are multiple ambiguous results due to use of "export * from" + // statements, trace them all to see if they point to different things. + for (potentially_ambiguous_export_star_refs) |*ambiguous_tracker| { + // If this is a re-export of another import, follow the import + if (named_imports[ambiguous_tracker.data.source_index.get()].contains(ambiguous_tracker.data.import_ref)) { + + // TODO: not fully confident this will work + // test with nested ambiguous re-exports + var old_cycle_detector = c.cycle_detector; + c.cycle_detector = c.swap_cycle_detector; + c.cycle_detector.clearRetainingCapacity(); + var ambig = c.matchImportWithExport(&ambiguous_tracker.data, re_exports); + c.cycle_detector.clearRetainingCapacity(); + c.swap_cycle_detector = c.cycle_detector; + c.cycle_detector = old_cycle_detector; + ambiguous_results.append(ambig) catch unreachable; + } else { + ambiguous_results.append(.{ + .kind = .normal, + .source_index = ambiguous_tracker.data.source_index.get(), + .ref = ambiguous_tracker.data.import_ref, + .name_loc = ambiguous_tracker.data.name_loc, + }) catch unreachable; + } + } + + // Defer the actual binding of this import until after we generate + // namespace export code for all files. This has to be done for all + // import-to-export matches, not just the initial import to the final + // export, since all imports and re-exports must be merged together + // for correctness. + result = .{ + .kind = .normal, + .source_index = next_tracker.source_index.get(), + .ref = next_tracker.import_ref, + .name_loc = next_tracker.name_loc, + }; + + // Depend on the statement(s) that declared this import symbol in the + // original file + { + var deps = top_level_symbols_to_parts[other_id].get(tracker.import_ref).?.slice(); + re_exports.ensureUnusedCapacity(deps.len) catch unreachable; + for (deps) |dep| { + re_exports.appendAssumeCapacity( + .{ + .part_index = dep, + .source_index = tracker.source_index, + }, + ); + } + } + + // If this is a re-export of another import, continue for another + // iteration of the loop to resolve that import as well + const next_id = next_tracker.source_index.get(); + if (named_imports[next_id].contains(next_tracker.import_ref)) { + tracker.* = next_tracker; + continue :loop; + } + }, + } + + break :loop; + } + + // If there is a potential ambiguity, all results must be the same + for (ambiguous_results.items) |ambig| { + if (!std.meta.eql(ambig, result)) { + if (result.kind == ambig.kind and + ambig.kind == .normal and + ambig.name_loc.start != 0 and + result.name_loc.start != 0) + { + return .{ + .kind = .ambiguous, + .source_index = result.source_index, + .name_loc = result.name_loc, + .other_source_index = ambig.source_index, + .other_name_loc = ambig.name_loc, + }; + } + + return .{ .kind = .ambiguous }; + } + } + + return result; + } + + pub fn topLevelSymbolsToParts(c: *LinkerContext, id: u32, ref: Ref) []u32 { + return if (c.graph.ast.items(.top_level_symbols_to_parts)[id].get(ref)) |list| + list.slice() + else + &[_]u32{}; + } + + pub fn topLevelSymbolsToPartsForRuntime(c: *LinkerContext, ref: Ref) []u32 { + return topLevelSymbolsToParts(c, Index.runtime.get(), ref); + } + + pub fn createWrapperForFile( + c: *LinkerContext, + wrap: WrapKind, + wrapper_ref: Ref, + wrapper_part_index: *Index, + source_index: Index.Int, + id: u32, + ) void { + switch (wrap) { + // If this is a CommonJS file, we're going to need to generate a wrapper + // for the CommonJS closure. That will end up looking something like this: + // + // var require_foo = __commonJS((exports, module) => { + // ... + // }); + // + // However, that generation is special-cased for various reasons and is + // done later on. Still, we're going to need to ensure that this file + // both depends on the "__commonJS" symbol and declares the "require_foo" + // symbol. Instead of special-casing this during the reachablity analysis + // below, we just append a dummy part to the end of the file with these + // dependencies and let the general-purpose reachablity analysis take care + // of it. + .cjs => { + const common_js_parts = c.topLevelSymbolsToPartsForRuntime(c.cjs_runtime_ref); + + // generate a dummy part that depends on the "__commonJS" symbol + var dependencies = c.allocator.alloc(js_ast.Dependency, common_js_parts.len) catch unreachable; + for (common_js_parts) |part, i| { + dependencies[i] = .{ + .part_index = part, + .source_index = Index.runtime, + }; + } + const part_index = c.graph.addPartToFile( + source_index, + .{ + .stmts = &.{}, + .symbol_uses = bun.from( + js_ast.Part.SymbolUseMap, + c.allocator, + .{ + .{ wrapper_ref, .{ .count_estimate = 1 } }, + }, + ) catch unreachable, + .declared_symbols = bun.from( + js_ast.DeclaredSymbol.List, + c.allocator, + [_]js_ast.DeclaredSymbol{ + .{ .ref = c.graph.ast.items(.exports_ref)[id], .is_top_level = true }, + .{ .ref = c.graph.ast.items(.module_ref)[id].?, .is_top_level = true }, + .{ .ref = c.graph.ast.items(.wrapper_ref)[id].?, .is_top_level = true }, + }, + ) catch unreachable, + .dependencies = Dependency.List.init(dependencies), + }, + ) catch unreachable; + wrapper_part_index.* = Index.part(part_index); + c.graph.generateSymbolImportAndUse( + source_index, + part_index, + c.cjs_runtime_ref, + 1, + Index.runtime, + ) catch unreachable; + }, + + .esm => { + // If this is a lazily-initialized ESM file, we're going to need to + // generate a wrapper for the ESM closure. That will end up looking + // something like this: + // + // var init_foo = __esm(() => { + // ... + // }); + // + // This depends on the "__esm" symbol and declares the "init_foo" symbol + // for similar reasons to the CommonJS closure above. + const esm_parts = c.topLevelSymbolsToPartsForRuntime(c.esm_runtime_ref); + + // generate a dummy part that depends on the "__esm" symbol + var dependencies = c.allocator.alloc(js_ast.Dependency, esm_parts.len) catch unreachable; + for (esm_parts) |part, i| { + dependencies[i] = .{ + .part_index = part, + .source_index = Index.runtime, + }; + } + + const part_index = c.graph.addPartToFile( + source_index, + .{ + .symbol_uses = bun.from( + js_ast.Part.SymbolUseMap, + c.allocator, + .{ + .{ wrapper_ref, .{ .count_estimate = 1 } }, + }, + ) catch unreachable, + .declared_symbols = bun.from( + js_ast.DeclaredSymbol.List, + c.allocator, + [_]js_ast.DeclaredSymbol{ + .{ .ref = wrapper_ref, .is_top_level = true }, + }, + ) catch unreachable, + .dependencies = Dependency.List.init(dependencies), + }, + ) catch unreachable; + wrapper_part_index.* = Index.part(part_index); + c.graph.generateSymbolImportAndUse( + source_index, + part_index, + c.esm_runtime_ref, + 1, + Index.runtime, + ) catch unreachable; + }, + else => {}, + } + } + + pub fn advanceImportTracker(c: *LinkerContext, tracker: *ImportTracker) ImportTracker.Iterator { + const id = tracker.source_index.get(); + var named_imports: *JSAst.NamedImports = &c.graph.ast.items(.named_imports)[id]; + var import_records = c.graph.ast.items(.import_records)[id]; + const exports_kind: []js_ast.ExportsKind = c.graph.ast.items(.exports_kind); + + const named_import = named_imports.get(tracker.import_ref) orelse + // TODO: investigate if this is a bug + // It implies there are imports being added without being resolved + return .{ + .value = .{}, + .status = .external, + .tracker = tracker, + }; + + // Is this an external file? + const record = import_records.at(named_import.import_record_index); + if (!record.source_index.isValid()) { + return .{ + .value = .{}, + .status = .external, + .tracker = tracker, + }; + } + + // Is this a disabled file? + const other_source_index = record.source_index.get(); + const other_id = other_source_index; + + if (other_id > c.graph.ast.len or c.parse_graph.input_files.items(.source)[other_source_index].key_path.is_disabled) { + return .{ + .value = .{ + .source_index = record.source_index, + }, + .status = .disabled, + .tracker = tracker, + }; + } + + // Is this a named import of a file without any exports? + if (!named_import.alias_is_star and + // TODO hasLazyExport + + // CommonJS exports + c.graph.ast.items(.export_keyword)[other_id].len == 0 and !strings.eqlComptime(named_import.alias orelse "", "default") and + // ESM exports + !c.graph.ast.items(.uses_exports_ref)[other_id] and !c.graph.ast.items(.uses_module_ref)[other_id]) + { + // Just warn about it and replace the import with "undefined" + return .{ + .value = .{ + .source_index = Index.source(other_source_index), + .import_ref = Ref.None, + }, + .status = .cjs_without_exports, + .tracker = tracker, + }; + } + const other_kind = exports_kind[other_id]; + // Is this a CommonJS file? + if (other_kind == .cjs) { + return .{ + .value = .{ + .source_index = Index.source(other_source_index), + .import_ref = Ref.None, + }, + .status = .cjs, + .tracker = tracker, + }; + } + + // Match this import star with an export star from the imported file + if (named_import.alias_is_star) { + const matching_export = c.graph.meta.items(.resolved_export_star)[other_id]; + if (matching_export.data.import_ref.isValid()) { + // Check to see if this is a re-export of another import + return .{ + .value = matching_export.data, + .status = .found, + .import_data = matching_export.potentially_ambiguous_export_star_refs.slice(), + .tracker = tracker, + }; + } + } + + // Match this import up with an export from the imported file + if (c.graph.meta.items(.resolved_exports)[other_id].get(named_import.alias.?)) |matching_export| { + // Check to see if this is a re-export of another import + return .{ + .value = .{ + .source_index = matching_export.data.source_index, + .import_ref = matching_export.data.import_ref, + .name_loc = matching_export.data.name_loc, + }, + .status = .found, + .import_data = matching_export.potentially_ambiguous_export_star_refs.slice(), + .tracker = tracker, + }; + } + + // Is this a file with dynamic exports? + if (other_kind == .esm_with_dynamic_fallback) { + return .{ + .value = .{ + .source_index = Index.source(other_source_index), + .import_ref = c.graph.ast.items(.exports_ref)[other_id], + }, + .status = .dynamic_fallback, + .tracker = tracker, + }; + } + + // Missing re-exports in TypeScript files are indistinguishable from types + if (named_import.is_exported and c.parse_graph.input_files.items(.loader)[other_source_index].isTypeScript()) { + return .{ + .value = .{}, + .status = .probably_typescript_type, + .tracker = tracker, + }; + } + + return .{ + .value = .{ + .source_index = Index.source(other_source_index), + }, + .status = .no_match, + .tracker = tracker, + }; + } + + pub fn matchImportsWithExportsForFile(c: *LinkerContext, named_imports: *JSAst.NamedImports, imports_to_bind: *RefImportData, source_index: Index.Int) void { + var iter = named_imports.iterator(); + // TODO: do we need to sort here? I don't think so + // because NamedImports is an ArrayHashMap, it's order should naturally be deterministic + + // Pair imports with their matching exports + const Sorter = struct { + imports: *JSAst.NamedImports, + + pub fn lessThan(self: @This(), a_index: usize, b_index: usize) bool { + const a_ref = self.imports.keys()[a_index]; + const b_ref = self.imports.keys()[b_index]; + + return std.math.order(a_ref.innerIndex(), b_ref.innerIndex()) == .lt; + } + }; + var sorter = Sorter{ + .imports = named_imports, + }; + named_imports.sort(sorter); + + while (iter.next()) |entry| { + // Re-use memory for the cycle detector + c.cycle_detector.clearRetainingCapacity(); + const ref = entry.key_ptr.*; + + const import_ref = Ref.init(ref.innerIndex(), @truncate(Ref.Int, source_index), ref.isSourceContentsSlice()); + var import_tracker = ImportData{ + .data = .{ + .source_index = Index.source(source_index), + .import_ref = import_ref, + }, + }; + var re_exports = std.ArrayList(js_ast.Dependency).init(c.allocator); + var result = c.matchImportWithExport( + &import_tracker.data, + &re_exports, + ); + + switch (result.kind) { + .normal => { + imports_to_bind.put( + c.allocator, + import_ref, + .{ + .re_exports = bun.BabyList(js_ast.Dependency).init(re_exports.items), + .data = .{ + .source_index = Index.source(result.source_index), + .import_ref = result.ref, + }, + }, + ) catch unreachable; + }, + .namespace => { + c.graph.symbols.get(import_ref).?.namespace_alias = js_ast.G.NamespaceAlias{ + .namespace_ref = result.namespace_ref, + .alias = result.alias, + }; + }, + .normal_and_namespace => { + imports_to_bind.put( + c.allocator, + import_ref, + .{ + .re_exports = bun.BabyList(js_ast.Dependency).init(re_exports.items), + .data = .{ + .source_index = Index.source(result.source_index), + .import_ref = result.ref, + }, + }, + ) catch unreachable; + + c.graph.symbols.get(import_ref).?.namespace_alias = js_ast.G.NamespaceAlias{ + .namespace_ref = result.namespace_ref, + .alias = result.alias, + }; + }, + .cycle => { + const source = &c.parse_graph.input_files.items(.source)[source_index]; + const r = lex.rangeOfIdentifier(source, entry.value_ptr.alias_loc orelse Logger.Loc{}); + c.log.addRangeErrorFmt( + source, + r, + c.allocator, + "Detected cycle while resolving import {s}", + .{ + entry.value_ptr.alias.?, + }, + ) catch unreachable; + }, + .probably_typescript_type => { + c.graph.meta.items(.probably_typescript_type)[source_index].put( + c.allocator, + import_ref, + void{}, + ) catch unreachable; + }, + .ambiguous => { + var named_import = entry.value_ptr.*; + const source = &c.parse_graph.input_files.items(.source)[source_index]; + + const r = lex.rangeOfIdentifier(source, entry.value_ptr.alias_loc orelse Logger.Loc{}); + + // if (result.name_loc.start != 0) + // TODO: better error + c.log.addRangeErrorFmt( + source, + r, + c.allocator, + "Ambiguous import: {s}", + .{ + named_import.alias.?, + }, + ) catch unreachable; + }, + .ignore => {}, + } + } + } + + const ExportStarContext = struct { + import_records_list: []const ImportRecord.List, + source_index_stack: std.ArrayList(Index.Int), + exports_kind: []js_ast.ExportsKind, + named_exports: []js_ast.Ast.NamedExports, + resolved_exports: []RefExportData, + imports_to_bind: []RefImportData, + export_star_records: []const []const Index.Int, + allocator: std.mem.Allocator, + + pub fn addExports( + this: *ExportStarContext, + resolved_exports: *RefExportData, + source_index: Index.Int, + ) void { + // Avoid infinite loops due to cycles in the export star graph + for (this.source_index_stack.items) |i| { + if (i == source_index) + return; + } + + this.source_index_stack.append(source_index) catch unreachable; + const id = source_index; + + const import_records = this.import_records_list[id].slice(); + + for (this.export_star_records[id]) |import_id| { + const other_source_index = import_records[import_id].source_index.get(); + + const other_id = other_source_index; + if (other_id >= this.named_exports.len) + // this AST was empty or it wasn't a JS AST + continue; + + // Export stars from a CommonJS module don't work because they can't be + // statically discovered. Just silently ignore them in this case. + // + // We could attempt to check whether the imported file still has ES6 + // exports even though it still uses CommonJS features. However, when + // doing this we'd also have to rewrite any imports of these export star + // re-exports as property accesses off of a generated require() call. + if (this.exports_kind[other_id] == .cjs) + continue; + var iter = this.named_exports[other_id].iterator(); + next_export: while (iter.next()) |entry| { + const alias = entry.key_ptr.*; + + // ES6 export star statements ignore exports named "default" + if (strings.eqlComptime(alias, "default")) + continue; + + // This export star is shadowed if any file in the stack has a matching real named export + for (this.source_index_stack.items) |prev| { + if (this.named_exports[prev].contains(alias)) { + continue :next_export; + } + } + const ref = entry.value_ptr.ref; + var resolved = resolved_exports.getOrPut(this.allocator, entry.key_ptr.*) catch unreachable; + if (!resolved.found_existing) { + resolved.value_ptr.* = .{ + .data = .{ + .import_ref = ref, + .source_index = Index.source(other_source_index), + .name_loc = entry.value_ptr.alias_loc, + }, + }; + + // Make sure the symbol is marked as imported so that code splitting + // imports it correctly if it ends up being shared with another chunk + this.imports_to_bind[id].put(this.allocator, entry.value_ptr.ref, .{ + .data = .{ + .import_ref = ref, + .source_index = Index.source(other_source_index), + }, + }) catch unreachable; + } else if (resolved.value_ptr.data.source_index.get() != other_source_index) { + // Two different re-exports colliding makes it potentially ambiguous + resolved.value_ptr.potentially_ambiguous_export_star_refs.push(this.allocator, .{ + .data = .{ + .source_index = Index.source(other_source_index), + .import_ref = ref, + .name_loc = entry.value_ptr.alias_loc, + }, + }) catch unreachable; + } + } + + // Search further through this file's export stars + this.addExports(resolved_exports, other_source_index); + } + } + }; + + const DependencyWrapper = struct { + linker: *LinkerContext, + flags: []JSMeta.Flags, + exports_kind: []js_ast.ExportsKind, + import_records: []ImportRecord.List, + export_star_map: std.AutoHashMap(Index.Int, void), + entry_point_kinds: []EntryPoint.Kind, + export_star_records: [][]u32, + output_format: options.OutputFormat, + + pub fn hasDynamicExportsDueToExportStar(this: *DependencyWrapper, source_index: Index.Int) bool { + // Terminate the traversal now if this file already has dynamic exports + const export_kind = this.exports_kind[source_index]; + switch (export_kind) { + .cjs, .esm_with_dynamic_fallback => return true, + else => {}, + } + + // Avoid infinite loops due to cycles in the export star graph + const has_visited = this.export_star_map.getOrPut(source_index) catch unreachable; + if (has_visited.found_existing) { + return false; + } + + for (this.export_star_records[source_index]) |id| { + const records: []const ImportRecord = this.import_records[id].slice(); + for (records) |record| { + // This file has dynamic exports if the exported imports are from a file + // that either has dynamic exports directly or transitively by itself + // having an export star from a file with dynamic exports. + const kind = this.entry_point_kinds[record.source_index.get()]; + if ((record.source_index.get() >= this.import_records.len and (!kind.isEntryPoint() or !this.output_format.keepES6ImportExportSyntax())) or + (record.source_index.get() < this.import_records.len and record.source_index.get() != source_index and this.hasDynamicExportsDueToExportStar(record.source_index.get()))) + { + this.exports_kind[source_index] = .esm_with_dynamic_fallback; + return true; + } + } + } + + return false; + } + + pub fn wrap(this: *DependencyWrapper, source_index: Index.Int) void { + + // Never wrap the runtime file since it always comes first + if (source_index == Index.runtime.get()) { + return; + } + + var flags = this.flags[source_index]; + + if (flags.did_wrap_dependencies) return; + flags.did_wrap_dependencies = true; + + // This module must be wrapped + if (flags.wrap == .none) { + flags.wrap = switch (this.exports_kind[source_index]) { + .cjs => .cjs, + else => .esm, + }; + } + this.flags[source_index] = flags; + + const records = this.import_records[source_index].slice(); + for (records) |record| { + if (!record.source_index.isValid()) { + continue; + } + this.wrap(record.source_index.get()); + } + } + }; +}; + +pub const PartRange = struct { + source_index: Index = Index.invalid, + part_index_begin: u32 = 0, + part_index_end: u32 = 0, +}; + +pub const ImportTracker = struct { + source_index: Index = Index.invalid, + name_loc: Logger.Loc = Logger.Loc.Empty, + import_ref: Ref = Ref.None, + + pub const Status = enum { + /// The imported file has no matching export + no_match, + + /// The imported file has a matching export + found, + + /// The imported file is CommonJS and has unknown exports + cjs, + + /// The import is missing but there is a dynamic fallback object + dynamic_fallback, + + /// The import was treated as a CommonJS import but the file is known to have no exports + cjs_without_exports, + + /// The imported file was disabled by mapping it to false in the "browser" + /// field of package.json + disabled, + + /// The imported file is external and has unknown exports + external, + + /// This is a missing re-export in a TypeScript file, so it's probably a type + probably_typescript_type, + }; + + pub const Iterator = struct { + status: Status = Status.no_match, + value: ImportTracker = .{}, + import_data: []ImportData = &.{}, + tracker: *ImportTracker, + }; +}; + +pub const PathTemplate = struct { + data: string = "", + placeholder: Placeholder, + + pub const Placeholder = struct { + dir: []const u8 = "", + name: []const u8 = "", + ext: []const u8 = "", + hash: ?u64 = null, + }; + + pub const chunk = PathTemplate{ + .data = "./chunk-[hash].[ext]", + .placeholder = .{ + .name = "chunk", + .ext = "js", + .dir = "", + }, + }; + + pub const file = PathTemplate{ + .data = "./[name]-[hash].[ext]", + .placeholder = .{}, + }; +}; + +pub const Chunk = struct { + /// This is a random string and is used to represent the output path of this + /// chunk before the final output path has been computed. + unique_key: string = "", + + files_with_parts_in_chunk: std.AutoArrayHashMapUnmanaged(Index.Int, void) = .{}, + + /// We must not keep pointers to this type until all chunks have been allocated. + entry_bits: AutoBitSet = .{}, + + final_rel_path: string = "", + template: PathTemplate = .{}, + + /// For code splitting + cross_chunk_imports: []CrossChunkImport = &.{}, + + content: Content, + + entry_point: Chunk.EntryPoint = .{}, + + is_executable: bool = false, + + pub inline fn entryBits(this: *const Chunk) *const AutoBitSet { + return &this.entry_bits; + } + + pub const Order = struct { + source_index: Index.Int = 0, + distance: u32 = 0, + tie_breaker: u32 = 0, + + pub fn lessThan(_: @This(), a: Order, b: Order) bool { + if (a.distance < b.distance) return true; + + return a.tie_breaker < b.tie_breaker; + } + + /// Sort so files closest to an entry point come first. If two files are + /// equidistant to an entry point, then break the tie by sorting on the + /// stable source index derived from the DFS over all entry points. + pub fn sort(a: []Order) void { + std.sort.sort(Order, a, Order{}, lessThan); + } + }; + + pub const IntermediateOutput = union(enum) { + /// If the chunk has references to other chunks, then "pieces" contains the + /// contents of the chunk. Another joiner + /// will have to be constructed later when merging the pieces together. + pieces: bun.BabyList(OutputPiece), + + /// If the chunk doesn't have any references to other chunks, then + /// "joiner" contains the contents of the chunk. This is more efficient + /// because it avoids doing a join operation twice. + joiner: *bun.Joiner, + }; + + pub const OutputPiece = struct { + // layed out like this so it takes up the same amount of space as a []const u8 + data_ptr: [*]const u8 = undefined, + data_len: u32 = 0, + + index: OutputPieceIndex = .{}, + + pub inline fn data(this: OutputPiece) []const u8 { + return this.data_ptr[0..this.data_len]; + } + }; + + pub const OutputPieceIndex = packed struct { + index: u30 = 0, + + kind: Kind = Kind.none, + + pub const Kind = enum(u2) { + /// The "kind" may be "none" in which case there is one piece + /// with data and no chunk index. For example, the chunk may not contain any + /// imports. + none, + + asset, + chunk, + }; + }; + + pub const EntryPoint = packed struct(u64) { + source_index: Index.Int = 0, + entry_point_id: ID = 0, + is_entry_point: bool = false, + + // so it fits in a 64-bit integer + pub const ID = u31; + }; + + pub const JavaScriptChunk = struct { + files_in_chunk_order: []const Index.Int = &.{}, + parts_in_chunk_in_order: []const PartRange = &.{}, + + // for code splitting + exports_to_other_chunks: std.ArrayHashMapUnmanaged(Ref, string, Ref.ArrayHashCtx, false) = .{}, + imports_from_other_chunks: ImportsFromOtherChunks = .{}, + cross_chunk_prefix_stmts: BabyList(Stmt) = .{}, + cross_chunk_suffix_stmts: BabyList(Stmt) = .{}, + }; + + pub const ImportsFromOtherChunks = std.ArrayHashMapUnmanaged(Index.Int, CrossChunkImport.Item.List, Index.ArrayHashCtx, false); + + pub const Content = union(enum) { + javascript: JavaScriptChunk, + }; +}; + +pub const CrossChunkImport = struct { + chunk_index: Index.Int = 0, + sorted_import_Items: List = &.{}, + + pub const Item = struct { + export_alias: string = "", + ref: Ref = Ref.None, + + pub const List = bun.BabyList(Item); + + pub fn lessThan(_: void, a: CrossChunkImport.Item, b: CrossChunkImport.Item) bool { + return strings.order(a.export_alias, b.export_alias) == .lt; + } + }; + + pub fn lessThan(_: void, a: CrossChunkImport, b: CrossChunkImport) bool { + return std.mem.order(a.chunk_index, b.chunk_index) == .lt; + } + + pub const List = std.ArrayList(CrossChunkImport); + + pub fn sortedCrossChunkImports(list: *List, chunks: []Chunk, imports_from_other_chunks: *Chunk.ImportsFromOtherChunks) !void { + var result = list.*; + defer { + list.* = result; + } + + try result.ensureTotalCapacity(imports_from_other_chunks.count()); + + var import_items_list = imports_from_other_chunks.values(); + var chunk_indices = imports_from_other_chunks.keys(); + var i: usize = 0; + while (i < chunk_indices.len) : (i += 1) { + const chunk_index = chunk_indices[i]; + var chunk = &chunks[chunk_index]; + + // Sort imports from a single chunk by alias for determinism + const exports_to_other_chunks = &chunk.content.javascript.exports_to_other_chunks; + // TODO: do we need to clone this array? + var import_items = import_items_list[i]; + for (import_items.slice()) |*item| { + item.export_alias = exports_to_other_chunks.get(item.ref).?; + std.debug.assert(item.export_alias.len > 0); + } + std.sort.sort(void, import_items.slice(), void{}, CrossChunkImport.Item.lessThan); + + result.append(CrossChunkImport{ + .chunk_index = chunk_index, + .sorted_import_items = import_items.slice(), + }) catch unreachable; + } + + std.sort.sort(void, result.items, void{}, CrossChunkImport.lessThan); + } +}; diff --git a/src/bundler/generate_node_modules_bundle.zig b/src/bundler/generate_node_modules_bundle.zig index 8a965df08179..76026fd47677 100644 --- a/src/bundler/generate_node_modules_bundle.zig +++ b/src/bundler/generate_node_modules_bundle.zig @@ -1358,7 +1358,7 @@ pub fn processFile(this: *GenerateNodeModuleBundle, worker: *ThreadPool.Worker, } var path = _resolved_import.path() orelse { import_record.path.is_disabled = true; - import_record.is_bundled = true; + continue; }; @@ -1366,7 +1366,7 @@ pub fn processFile(this: *GenerateNodeModuleBundle, worker: *ThreadPool.Worker, if (!loader_.isJavaScriptLikeOrJSON()) { import_record.path.is_disabled = true; - import_record.is_bundled = true; + continue; } @@ -1384,13 +1384,12 @@ pub fn processFile(this: *GenerateNodeModuleBundle, worker: *ThreadPool.Worker, // we just silently disable it // because...we need some kind of hook to say "don't bundle this" import_record.path.is_disabled = true; - import_record.is_bundled = false; + import_record.tag = .macro; continue; }; import_record.module_id = _module_data.module_id; std.debug.assert(import_record.module_id != 0); - import_record.is_bundled = true; path.* = try path.dupeAlloc(this.allocator); @@ -1526,7 +1525,7 @@ pub fn processFile(this: *GenerateNodeModuleBundle, worker: *ThreadPool.Worker, ast.runtime_imports = runtime.Runtime.Imports{}; ast.runtime_imports.@"$$m" = .{ .ref = Ref.atIndex(0), .primary = Ref.None, .backup = Ref.None }; ast.runtime_imports.__export = .{ .ref = Ref.atIndex(1), .primary = Ref.None, .backup = Ref.None }; - ast.symbols = &json_ast_symbols; + ast.symbols = js_ast.Symbol.List.init(&json_ast_symbols); ast.module_ref = Ref.atIndex(2); ast.exports_ref = ast.runtime_imports.__export.?.ref; ast.bundle_export_ref = Ref.atIndex(3); @@ -1676,7 +1675,6 @@ pub fn processFile(this: *GenerateNodeModuleBundle, worker: *ThreadPool.Worker, &source, false, js_printer.Options{ - .to_module_ref = Ref.RuntimeRef, .bundle_export_ref = ast.runtime_imports.@"$$m".?.ref, .source_path = file_path, .externals = ast.externals, @@ -1704,7 +1702,6 @@ pub fn processFile(this: *GenerateNodeModuleBundle, worker: *ThreadPool.Worker, &source, true, js_printer.Options{ - .to_module_ref = Ref.RuntimeRef, .bundle_export_ref = ast.runtime_imports.@"$$m".?.ref, .source_path = file_path, .externals = ast.externals, diff --git a/src/c.zig b/src/c.zig index 54be0878c697..57a4b65489da 100644 --- a/src/c.zig +++ b/src/c.zig @@ -38,6 +38,8 @@ pub extern "c" fn truncate([*:0]const u8, i64) c_int; // note: truncate64 is not pub extern "c" fn lutimes(path: [*:0]const u8, times: *const [2]std.os.timeval) c_int; pub extern "c" fn mkdtemp(template: [*c]u8) ?[*:0]u8; +pub extern "c" fn memcmp(s1: [*c]const u8, s2: [*c]const u8, n: usize) c_int; + pub const lstat = lstat64; pub const fstat = fstat64; pub const stat = stat64; diff --git a/src/cache.zig b/src/cache.zig index 284ca254be35..a5d73e0fd709 100644 --- a/src/cache.zig +++ b/src/cache.zig @@ -148,7 +148,7 @@ pub const Fs = struct { var file_handle: std.fs.File = if (_file_handle) |__file| std.fs.File{ .handle = __file } else undefined; if (_file_handle == null) { - if (FeatureFlags.store_file_descriptors and dirname_fd > 0) { + if (FeatureFlags.store_file_descriptors and dirname_fd != bun.invalid_fd and dirname_fd > 0) { file_handle = std.fs.Dir.openFile(std.fs.Dir{ .fd = dirname_fd }, std.fs.path.basename(path), .{ .mode = .read_only }) catch |err| brk: { switch (err) { error.FileNotFound => { diff --git a/src/cli.zig b/src/cli.zig index 5b0b47edd2c8..e459598d93f1 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -36,25 +36,6 @@ const Router = @import("./router.zig"); const NodeModuleBundle = @import("./node_module_bundle.zig").NodeModuleBundle; -const AddCommand = @import("./cli/add_command.zig").AddCommand; -const BuildCommand = @import("./cli/build_command.zig").BuildCommand; -const BunCommand = @import("./cli/bun_command.zig").BunCommand; -const CreateCommand = @import("./cli/create_command.zig").CreateCommand; -const CreateListExamplesCommand = @import("./cli/create_command.zig").CreateListExamplesCommand; -const DevCommand = @import("./cli/dev_command.zig").DevCommand; -const DiscordCommand = @import("./cli/discord_command.zig").DiscordCommand; -const InstallCommand = @import("./cli/install_command.zig").InstallCommand; -const LinkCommand = @import("./cli/link_command.zig").LinkCommand; -const UnlinkCommand = @import("./cli/unlink_command.zig").UnlinkCommand; -const InstallCompletionsCommand = @import("./cli/install_completions_command.zig").InstallCompletionsCommand; -const PackageManagerCommand = @import("./cli/package_manager_command.zig").PackageManagerCommand; -const RemoveCommand = @import("./cli/remove_command.zig").RemoveCommand; -const RunCommand = @import("./cli/run_command.zig").RunCommand; -const ShellCompletions = @import("./cli/shell_completions.zig"); -const TestCommand = @import("./cli/test_command.zig").TestCommand; -const UpgradeCommand = @import("./cli/upgrade_command.zig").UpgradeCommand; -const BunxCommand = @import("./cli/bunx_command.zig").BunxCommand; - const MacroMap = @import("./resolver/package_json.zig").MacroMap; const Reporter = @import("./report.zig"); @@ -938,7 +919,7 @@ pub const Command = struct { }, RootCommandMatcher.case("c"), RootCommandMatcher.case("create") => .CreateCommand, - RootCommandMatcher.case(TestCommand.name) => .TestCommand, + RootCommandMatcher.case("wiptest") => .TestCommand, RootCommandMatcher.case("pm") => .PackageManagerCommand, @@ -978,12 +959,52 @@ pub const Command = struct { }; pub fn start(allocator: std.mem.Allocator, log: *logger.Log) !void { + const BunCommand = @import("./cli/bun_command.zig").BunCommand; + + const AddCommand = @import("./cli/add_command.zig").AddCommand; + const BuildCommand = @import("./cli/build_command.zig").BuildCommand; + const CreateCommand = @import("./cli/create_command.zig").CreateCommand; + const CreateListExamplesCommand = @import("./cli/create_command.zig").CreateListExamplesCommand; + const DevCommand = @import("./cli/dev_command.zig").DevCommand; + const DiscordCommand = @import("./cli/discord_command.zig").DiscordCommand; + const InstallCommand = @import("./cli/install_command.zig").InstallCommand; + const LinkCommand = @import("./cli/link_command.zig").LinkCommand; + const UnlinkCommand = @import("./cli/unlink_command.zig").UnlinkCommand; + const InstallCompletionsCommand = @import("./cli/install_completions_command.zig").InstallCompletionsCommand; + const PackageManagerCommand = @import("./cli/package_manager_command.zig").PackageManagerCommand; + const RemoveCommand = @import("./cli/remove_command.zig").RemoveCommand; + const RunCommand = @import("./cli/run_command.zig").RunCommand; + const ShellCompletions = @import("./cli/shell_completions.zig"); + const TestCommand = @import("./cli/test_command.zig").TestCommand; + const UpgradeCommand = @import("./cli/upgrade_command.zig").UpgradeCommand; + const BunxCommand = @import("./cli/bunx_command.zig").BunxCommand; + + if (comptime bun.fast_debug_build_mode) { + // _ = AddCommand; + // _ = BuildCommand; + // _ = CreateCommand; + _ = CreateListExamplesCommand; + // _ = DevCommand; + _ = DiscordCommand; + // _ = InstallCommand; + // _ = LinkCommand; + // _ = UnlinkCommand; + // _ = InstallCompletionsCommand; + // _ = PackageManagerCommand; + // _ = RemoveCommand; + // _ = RunCommand; + // _ = ShellCompletions; + // _ = TestCommand; + // _ = UpgradeCommand; + // _ = BunxCommand; + } + const tag = which(); switch (tag) { - .DiscordCommand => return try DiscordCommand.exec(allocator), - .HelpCommand => return try HelpCommand.exec(allocator), - .InitCommand => return try InitCommand.exec(allocator, std.os.argv), + // .DiscordCommand => return try DiscordCommand.exec(allocator), + // .HelpCommand => return try HelpCommand.exec(allocator), + // .InitCommand => return try InitCommand.exec(allocator, std.os.argv), else => {}, } @@ -994,68 +1015,80 @@ pub const Command = struct { try BunCommand.exec(ctx); }, .DevCommand => { + if (comptime bun.fast_debug_build_mode) unreachable; const ctx = try Command.Context.create(allocator, log, .DevCommand); try DevCommand.exec(ctx); }, .BuildCommand => { + if (comptime bun.fast_debug_build_mode) unreachable; const ctx = try Command.Context.create(allocator, log, .BuildCommand); try BuildCommand.exec(ctx); }, .InstallCompletionsCommand => { + if (comptime bun.fast_debug_build_mode) unreachable; try InstallCompletionsCommand.exec(allocator); return; }, .InstallCommand => { + if (comptime bun.fast_debug_build_mode) unreachable; const ctx = try Command.Context.create(allocator, log, .InstallCommand); try InstallCommand.exec(ctx); return; }, .AddCommand => { + if (comptime bun.fast_debug_build_mode) unreachable; const ctx = try Command.Context.create(allocator, log, .AddCommand); try AddCommand.exec(ctx); return; }, .BunxCommand => { + if (comptime bun.fast_debug_build_mode) unreachable; const ctx = try Command.Context.create(allocator, log, .BunxCommand); try BunxCommand.exec(ctx); return; }, .RemoveCommand => { + if (comptime bun.fast_debug_build_mode) unreachable; const ctx = try Command.Context.create(allocator, log, .RemoveCommand); try RemoveCommand.exec(ctx); return; }, .LinkCommand => { + if (comptime bun.fast_debug_build_mode) unreachable; const ctx = try Command.Context.create(allocator, log, .LinkCommand); try LinkCommand.exec(ctx); return; }, .UnlinkCommand => { + if (comptime bun.fast_debug_build_mode) unreachable; const ctx = try Command.Context.create(allocator, log, .UnlinkCommand); try UnlinkCommand.exec(ctx); return; }, .PackageManagerCommand => { + if (comptime bun.fast_debug_build_mode) unreachable; const ctx = try Command.Context.create(allocator, log, .PackageManagerCommand); try PackageManagerCommand.exec(ctx); return; }, .TestCommand => { + if (comptime bun.fast_debug_build_mode) unreachable; const ctx = try Command.Context.create(allocator, log, .TestCommand); try TestCommand.exec(ctx); return; }, .GetCompletionsCommand => { + if (comptime bun.fast_debug_build_mode) unreachable; const ctx = try Command.Context.create(allocator, log, .GetCompletionsCommand); var filter = ctx.positionals; @@ -1142,6 +1175,7 @@ pub const Command = struct { return; }, .CreateCommand => { + if (comptime bun.fast_debug_build_mode) unreachable; const ctx = try Command.Context.create(allocator, log, .CreateCommand); var positionals: [2]string = undefined; var positional_i: usize = 0; @@ -1165,6 +1199,7 @@ pub const Command = struct { return; }, .RunCommand => { + if (comptime bun.fast_debug_build_mode) unreachable; const ctx = try Command.Context.create(allocator, log, .RunCommand); if (ctx.positionals.len > 0) { if (try RunCommand.exec(ctx, false, true)) { @@ -1175,11 +1210,13 @@ pub const Command = struct { } }, .UpgradeCommand => { + if (comptime bun.fast_debug_build_mode) unreachable; const ctx = try Command.Context.create(allocator, log, .UpgradeCommand); try UpgradeCommand.exec(ctx); return; }, .AutoCommand => { + if (comptime bun.fast_debug_build_mode) unreachable; var ctx = Command.Context.create(allocator, log, .AutoCommand) catch |e| { switch (e) { error.MissingEntryPoint => { diff --git a/src/cli/bun_command.zig b/src/cli/bun_command.zig index 0a8894905af2..186b68afa3fa 100644 --- a/src/cli/bun_command.zig +++ b/src/cli/bun_command.zig @@ -32,65 +32,65 @@ const DotEnv = @import("../env_loader.zig"); const fs = @import("../fs.zig"); const Router = @import("../router.zig"); - +const BundleV2 = @import("../bundler/bundle_v2.zig").BundleV2; var estimated_input_lines_of_code_: usize = undefined; -const ServerBundleGeneratorThread = struct { - inline fn _generate( - logs: *logger.Log, - env_loader_: *DotEnv.Loader, - allocator_: std.mem.Allocator, - ctx: Command.Context, - _filepath: [*:0]const u8, - server_conf: Api.LoadedFramework, - route_conf_: ?Api.LoadedRouteConfig, - router: ?Router, - ) anyerror!void { - var server_bundler = try bundler.Bundler.init( - allocator_, - logs, - try configureTransformOptionsForBun(allocator_, ctx.args), - null, - env_loader_, - ); - server_bundler.configureLinker(); - server_bundler.options.jsx.supports_fast_refresh = false; - - server_bundler.router = router; - server_bundler.configureDefines() catch |err| { - Output.prettyErrorln("{s} loading --define or .env values for node_modules.server.bun\n", .{@errorName(err)}); - return err; - }; - - if (ctx.debug.macros) |macros| { - server_bundler.options.macro_remap = macros; - } - - var estimated_input_lines_of_code: usize = 0; - _ = try GenerateNodeModuleBundle.generate( - &server_bundler, - allocator_, - server_conf, - route_conf_, - _filepath, - &estimated_input_lines_of_code, - ctx.debug.package_bundle_map, - ); - std.mem.doNotOptimizeAway(&server_bundler); - } - pub fn generate( - logs: *logger.Log, - env_loader_: *DotEnv.Loader, - ctx: Command.Context, - _filepath: [*:0]const u8, - server_conf: Api.LoadedFramework, - route_conf_: ?Api.LoadedRouteConfig, - router: ?Router, - ) void { - defer Output.flush(); - - _generate(logs, env_loader_, default_allocator, ctx, _filepath, server_conf, route_conf_, router) catch return; - } -}; +// const ServerBundleGeneratorThread = struct { +// inline fn _generate( +// logs: *logger.Log, +// env_loader_: *DotEnv.Loader, +// allocator_: std.mem.Allocator, +// ctx: Command.Context, +// _filepath: [*:0]const u8, +// server_conf: Api.LoadedFramework, +// route_conf_: ?Api.LoadedRouteConfig, +// router: ?Router, +// ) !void { +// var server_bundler = try bundler.Bundler.init( +// allocator_, +// logs, +// try configureTransformOptionsForBun(allocator_, ctx.args), +// null, +// env_loader_, +// ); +// server_bundler.configureLinker(); +// server_bundler.options.jsx.supports_fast_refresh = false; + +// server_bundler.router = router; +// server_bundler.configureDefines() catch |err| { +// Output.prettyErrorln("{s} loading --define or .env values for node_modules.server.bun\n", .{@errorName(err)}); +// return err; +// }; + +// if (ctx.debug.macros) |macros| { +// server_bundler.options.macro_remap = macros; +// } + +// var estimated_input_lines_of_code: usize = 0; +// _ = try GenerateNodeModuleBundle.generate( +// &server_bundler, +// allocator_, +// server_conf, +// route_conf_, +// _filepath, +// &estimated_input_lines_of_code, +// ctx.debug.package_bundle_map, +// ); +// std.mem.doNotOptimizeAway(&server_bundler); +// } +// pub fn generate( +// logs: *logger.Log, +// env_loader_: *DotEnv.Loader, +// ctx: Command.Context, +// _filepath: [*:0]const u8, +// server_conf: Api.LoadedFramework, +// route_conf_: ?Api.LoadedRouteConfig, +// router: ?Router, +// ) void { +// defer Output.flush(); + +// _generate(logs, env_loader_, default_allocator, ctx, _filepath, server_conf, route_conf_, router) catch return; +// } +// }; pub const BunCommand = struct { pub fn exec( @@ -104,7 +104,7 @@ pub const BunCommand = struct { var this_bundler = try bundler.Bundler.init(allocator, log, ctx.args, null, null); this_bundler.configureLinker(); var filepath: [*:0]const u8 = "node_modules.bun"; - var server_bundle_filepath: [*:0]const u8 = "node_modules.server.bun"; + // var server_bundle_filepath: [*:0]const u8 = "node_modules.server.bun"; // This step is optional // If it fails for any reason, ignore it and continue bundling @@ -139,7 +139,7 @@ pub const BunCommand = struct { } break :brk null; }; - var env_loader = this_bundler.env; + // var env_loader = this_bundler.env; if (ctx.debug.dump_environment_variables) { this_bundler.dumpEnvironmentVariables(); @@ -152,33 +152,33 @@ pub const BunCommand = struct { return; } - var generated_server = false; - if (this_bundler.options.framework) |*framework| { - if (framework.toAPI(allocator, this_bundler.fs.top_level_dir) catch null) |_server_conf| { - ServerBundleGeneratorThread.generate( - log, - env_loader, - ctx, - server_bundle_filepath, - _server_conf, - loaded_route_config, - this_bundler.router, - ); - generated_server = true; - - if (log.msgs.items.len > 0) { - try log.printForLogLevel(Output.errorWriter()); - log.* = logger.Log.init(allocator); - Output.flush(); - } - } - } + // var generated_server = false; + // if (this_bundler.options.framework) |*framework| { + // if (framework.toAPI(allocator, this_bundler.fs.top_level_dir) catch null) |_server_conf| { + // ServerBundleGeneratorThread.generate( + // log, + // env_loader, + // ctx, + // server_bundle_filepath, + // _server_conf, + // loaded_route_config, + // this_bundler.router, + // ); + // generated_server = true; + + // if (log.msgs.items.len > 0) { + // try log.printForLogLevel(Output.errorWriter()); + // log.* = logger.Log.init(allocator); + // Output.flush(); + // } + // } + // } { // Always generate the client-only bundle // we can revisit this decision if people ask - var node_modules_ = try GenerateNodeModuleBundle.generate( + _ = try BundleV2.generate( &this_bundler, allocator, loaded_framework, @@ -186,57 +186,59 @@ pub const BunCommand = struct { filepath, &estimated_input_lines_of_code_, ctx.debug.package_bundle_map, + bun.JSC.AnyEventLoop.init(ctx.allocator), + std.crypto.random.int(u64), ); - const estimated_input_lines_of_code = estimated_input_lines_of_code_; - - if (node_modules_) |node_modules| { - if (log.errors > 0) { - try log.printForLogLevel(Output.errorWriter()); - } else { - var elapsed = @divTrunc(@truncate(i64, std.time.nanoTimestamp() - ctx.start_time), @as(i64, std.time.ns_per_ms)); - const print_summary = !(ctx.args.no_summary orelse false); - if (print_summary) { - var bundle = NodeModuleBundle.init(node_modules, allocator); - bundle.printSummary(); - } - const indent = comptime " "; - - switch (estimated_input_lines_of_code) { - 0...99999 => { - if (generated_server) { - Output.prettyln(indent ++ "{d:<5} LOC parsed x2", .{estimated_input_lines_of_code}); - } else { - Output.prettyln(indent ++ "{d:<5} LOC parsed", .{estimated_input_lines_of_code}); - } - }, - else => { - const formatted_loc: f32 = @floatCast(f32, @intToFloat(f64, estimated_input_lines_of_code) / 1000); - if (generated_server) { - Output.prettyln(indent ++ "{d:<5.2}k LOC parsed x2", .{formatted_loc}); - } else { - Output.prettyln(indent ++ "{d:<5.2}k LOC parsed", .{ - formatted_loc, - }); - } - }, - } - - Output.prettyln(indent ++ "{d:6}ms elapsed", .{@intCast(u32, elapsed)}); - - if (generated_server) { - Output.prettyln(indent ++ "Saved to ./{s}, ./{s}", .{ filepath, server_bundle_filepath }); - } else { - Output.prettyln(indent ++ "Saved to ./{s}", .{filepath}); - } - - Output.flush(); - - try log.printForLogLevel(Output.errorWriter()); - } - } else { - try log.printForLogLevel(Output.errorWriter()); - } + // const estimated_input_lines_of_code = estimated_input_lines_of_code_; + + // if (node_modules_) |node_modules| { + // if (log.errors > 0) { + // try log.printForLogLevel(Output.errorWriter()); + // } else { + // var elapsed = @divTrunc(std.time.nanoTimestamp() - ctx.start_time, @as(i128, std.time.ns_per_ms)); + // const print_summary = !(ctx.args.no_summary orelse false); + // if (print_summary) { + // var bundle = NodeModuleBundle.init(node_modules, allocator); + // bundle.printSummary(); + // } + // const indent = comptime " "; + + // switch (estimated_input_lines_of_code) { + // 0...99999 => { + // if (generated_server) { + // Output.prettyln(indent ++ "{d:<5} LOC parsed x2", .{estimated_input_lines_of_code}); + // } else { + // Output.prettyln(indent ++ "{d:<5} LOC parsed", .{estimated_input_lines_of_code}); + // } + // }, + // else => { + // const formatted_loc: f32 = @floatCast(f32, @intToFloat(f128, estimated_input_lines_of_code) / 1000); + // if (generated_server) { + // Output.prettyln(indent ++ "{d:<5.2}k LOC parsed x2", .{formatted_loc}); + // } else { + // Output.prettyln(indent ++ "{d:<5.2}k LOC parsed", .{ + // formatted_loc, + // }); + // } + // }, + // } + + // Output.prettyln(indent ++ "{d:6}ms elapsed", .{@intCast(u32, elapsed)}); + + // if (generated_server) { + // Output.prettyln(indent ++ "Saved to ./{s}, ./{s}", .{ filepath, server_bundle_filepath }); + // } else { + // Output.prettyln(indent ++ "Saved to ./{s}", .{filepath}); + // } + + // Output.flush(); + + // try log.printForLogLevel(Output.errorWriter()); + // } + // } else { + try log.printForLogLevel(Output.errorWriter()); + // } } } }; diff --git a/src/defines-table.zig b/src/defines-table.zig index 4c458006f9c4..86fe6344defb 100644 --- a/src/defines-table.zig +++ b/src/defines-table.zig @@ -187,6 +187,22 @@ pub const GlobalDefinesKey = [_][]const string{ &[_]string{"setTimeout"}, &[_]string{"unescape"}, + // Reflect: Static methods + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect#static_methods + &[_]string{ "Reflect", "apply" }, + &[_]string{ "Reflect", "construct" }, + &[_]string{ "Reflect", "defineProperty" }, + &[_]string{ "Reflect", "deleteProperty" }, + &[_]string{ "Reflect", "get" }, + &[_]string{ "Reflect", "getOwnPropertyDescriptor" }, + &[_]string{ "Reflect", "getPrototypeOf" }, + &[_]string{ "Reflect", "has" }, + &[_]string{ "Reflect", "isExtensible" }, + &[_]string{ "Reflect", "ownKeys" }, + &[_]string{ "Reflect", "preventExtensions" }, + &[_]string{ "Reflect", "set" }, + &[_]string{ "Reflect", "setPrototypeOf" }, + // Console method references are assumed to have no side effects // https://developer.mozilla.org/en-US/docs/Web/API/console &[_]string{ "console", "assert" }, diff --git a/src/fs.zig b/src/fs.zig index 05903fac2f81..95f47498aa2a 100644 --- a/src/fs.zig +++ b/src/fs.zig @@ -1090,6 +1090,25 @@ pub const PathName = struct { ext: string, filename: string, + pub fn nonUniqueNameStringBase(self: *const PathName) string { + // /bar/foo/index.js -> foo + if (self.dir.len > 0 and strings.eqlComptime(self.base, "index")) { + // "/index" -> "index" + return Fs.PathName.init(self.dir).base; + } + + if (comptime Environment.allow_assert) { + std.debug.assert(!strings.includes(self.base, "/")); + } + + // /bar/foo.js -> foo + return self.base; + } + + pub fn fmtIdentifier(self: *const PathName) strings.FormatValidIdentifier { + return strings.fmtIdentifier(self.nonUniqueNameStringBase()); + } + // For readability, the names of certain automatically-generated symbols are // derived from the file name. For example, instead of the CommonJS wrapper for // a file being called something like "require273" it can be called something @@ -1102,13 +1121,7 @@ pub const PathName = struct { // through the renaming logic that all other symbols go through to avoid name // collisions. pub fn nonUniqueNameString(self: *const PathName, allocator: std.mem.Allocator) !string { - if (strings.eqlComptime(self.base, "index")) { - if (self.dir.len > 0) { - return MutableString.ensureValidIdentifier(PathName.init(self.dir).base, allocator); - } - } - - return MutableString.ensureValidIdentifier(self.base, allocator); + return MutableString.ensureValidIdentifier(self.nonUniqueNameStringBase(), allocator); } pub inline fn dirWithTrailingSlash(this: *const PathName) string { diff --git a/src/import_record.zig b/src/import_record.zig index c2db96b09d06..a1eece6c926c 100644 --- a/src/import_record.zig +++ b/src/import_record.zig @@ -1,7 +1,9 @@ -const fs = @import("fs.zig"); +const fs = @import("bun").fs; +const bun = @import("bun"); const logger = @import("bun").logger; const std = @import("std"); const Ref = @import("ast/base.zig").Ref; +const Index = @import("ast/base.zig").Index; const Api = @import("./api/schema.zig").Api; pub const ImportKind = enum(u8) { @@ -34,7 +36,7 @@ pub const ImportKind = enum(u8) { pub const Label = std.EnumArray(ImportKind, []const u8); pub const all_labels: Label = brk: { - var labels = Label.initFill("internal"); + var labels = Label.initFill(""); labels.set(ImportKind.entry_point, "entry-point"); labels.set(ImportKind.stmt, "import-statement"); labels.set(ImportKind.require, "require-call"); @@ -85,7 +87,7 @@ pub const ImportRecord = struct { /// 0 is invalid module_id: u32 = 0, - source_index: Ref.Int = std.math.maxInt(Ref.Int), + source_index: Index = Index.invalid, print_mode: PrintMode = .normal, @@ -103,6 +105,8 @@ pub const ImportRecord = struct { is_internal: bool = false, + calls_runtime_require: bool = false, + /// This tells the printer that we should print as export var $moduleID = ... /// Instead of using the path. is_bundled: bool = false, @@ -121,9 +125,11 @@ pub const ImportRecord = struct { /// either via the "import x from" or "import {default as x} from" syntax. contains_default_alias: bool = false, + contains_es_module_alias: bool = false, + /// If true, this "export * from 'path'" statement is evaluated at run-time by /// calling the "__reExport()" helper function - calls_run_time_re_export_fn: bool = false, + calls_runtime_re_export_fn: bool = false, /// Tell the printer to use runtime code to resolve this import/export do_commonjs_transform_in_printer: bool = false, @@ -140,6 +146,9 @@ pub const ImportRecord = struct { /// If a macro used , it will be tracked here. was_injected_by_macro: bool = false, + /// If true, this import can be removed if it's unused + is_external_without_side_effects: bool = false, + kind: ImportKind, tag: Tag = Tag.none, @@ -150,14 +159,37 @@ pub const ImportRecord = struct { /// Used to prevent running resolve plugins multiple times for the same path print_namespace_in_path: bool = false, + wrap_with_to_esm: bool = false, + + pub const List = bun.BabyList(ImportRecord); + pub const Tag = enum { none, + /// JSX auto-import for React Fast Refresh react_refresh, + /// JSX auto-import for jsxDEV or jsx jsx_import, + /// JSX auto-import for Fragment or createElement jsx_classic, + /// Uses the `bun` import specifier + /// import {foo} from "bun"; bun, + /// Uses the `bun:test` import specifier + /// import {expect} from "bun:test"; bun_test, + runtime, hardcoded, + /// A macro: import specifier OR a macro import + macro, + internal, + + pub inline fn isRuntime(this: Tag) bool { + return this == .runtime; + } + + pub inline fn isInternal(this: Tag) bool { + return @enumToInt(this) >= @enumToInt(Tag.runtime); + } }; pub const PrintMode = enum { diff --git a/src/install/lockfile.zig b/src/install/lockfile.zig index 8ff74527226c..f804986356fe 100644 --- a/src/install/lockfile.zig +++ b/src/install/lockfile.zig @@ -46,7 +46,7 @@ const Integrity = @import("./integrity.zig").Integrity; const clap = @import("bun").clap; const ExtractTarball = @import("./extract_tarball.zig"); const Npm = @import("./npm.zig"); -const Bitset = @import("./bit_set.zig").DynamicBitSetUnmanaged; +const Bitset = bun.bit_set.DynamicBitSetUnmanaged; const z_allocator = @import("../memory_allocator.zig").z_allocator; const Lockfile = @This(); diff --git a/src/install/semver.zig b/src/install/semver.zig index f9e16968f278..05ec817faab4 100644 --- a/src/install/semver.zig +++ b/src/install/semver.zig @@ -94,7 +94,7 @@ pub const String = extern struct { lhs_buf: []const u8, rhs_buf: []const u8, ) std.math.Order { - return std.mem.order(u8, lhs.slice(lhs_buf), rhs.slice(rhs_buf)); + return strings.order(lhs.slice(lhs_buf), rhs.slice(rhs_buf)); } pub inline fn canInline(buf: []const u8) bool { diff --git a/src/js_ast.zig b/src/js_ast.zig index a3225ccceea6..21d0f890f787 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -13,6 +13,7 @@ const stringZ = bun.stringZ; const default_allocator = bun.default_allocator; const C = bun.C; const Ref = @import("ast/base.zig").Ref; +const Index = @import("ast/base.zig").Index; const RefHashCtx = @import("ast/base.zig").RefHashCtx; const ObjectPool = @import("./pool.zig").ObjectPool; const ImportRecord = @import("import_record.zig").ImportRecord; @@ -23,7 +24,16 @@ const RefCtx = @import("./ast/base.zig").RefCtx; const JSONParser = bun.JSON; const is_bindgen = std.meta.globalOption("bindgen", bool) orelse false; const ComptimeStringMap = bun.ComptimeStringMap; -const JSPrinter = bun.js_printer; +const JSPrinter = @import("./js_printer.zig"); +const ThreadlocalArena = @import("./mimalloc_arena.zig").Arena; + +/// This is the index to the automatically-generated part containing code that +/// calls "__export(exports, { ... getters ... })". This is used to generate +/// getters on an exports object for ES6 export statements, and is both for +/// ES6 star imports and CommonJS-style modules. All files have one of these, +/// although it may contain no statements if there is nothing to export. +pub const namespace_export_part_index = 0; + pub fn NewBaseStore(comptime Union: anytype, comptime count: usize) type { var max_size = 0; var max_align = 1; @@ -150,20 +160,21 @@ pub fn NewBaseStore(comptime Union: anytype, comptime count: usize) type { fn deinit() void { var sliced = _self.overflow.slice(); + var allocator = _self.overflow.allocator; if (sliced.len > 1) { var i: usize = 1; const end = sliced.len; while (i < end) { var ptrs = @ptrCast(*[2]Block, sliced[i]); - default_allocator.free(ptrs); + allocator.free(ptrs); i += 2; } _self.overflow.allocated = 1; } var base_store = @fieldParentPtr(WithBase, "store", _self); if (_self.overflow.ptrs[0] == &base_store.head) { - default_allocator.destroy(base_store); + allocator.destroy(base_store); } _self = undefined; } @@ -219,7 +230,7 @@ pub fn NewBaseStore(comptime Union: anytype, comptime count: usize) type { pub const BindingNodeIndex = Binding; pub const StmtNodeIndex = Stmt; pub const ExprNodeIndex = Expr; -pub const BabyList = @import("./baby_list.zig").BabyList; +pub const BabyList = bun.BabyList; /// Slice that stores capacity and length in the same space as a regular slice. pub const ExprNodeList = BabyList(Expr); @@ -841,6 +852,19 @@ pub const Symbol = struct { count_estimate: u32 = 0, }; + pub const List = BabyList(Symbol); + pub const NestedList = BabyList(List); + + pub fn mergeContentsWith(this: *Symbol, old: *Symbol) void { + this.use_count_estimate += old.use_count_estimate; + if (old.must_not_be_renamed) { + this.original_name = old.original_name; + this.must_not_be_renamed = true; + } + + // TODO: MustStartWithCapitalLetterForJSX + } + pub const Map = struct { // This could be represented as a "map[Ref]Symbol" but a two-level array was // more efficient in profiles. This appears to be because it doesn't involve @@ -849,30 +873,68 @@ pub const Symbol = struct { // single inner array, so you can join the maps together by just make a // single outer array containing all of the inner arrays. See the comment on // "Ref" for more detail. - symbols_for_source: [][]Symbol, + symbols_for_source: NestedList = NestedList{}, + + pub fn assignChunkIndex(this: *Map, decls_: DeclaredSymbol.List, chunk_index: u32) void { + const Iterator = struct { + map: *Map, + chunk_index: u32, + + pub fn next(self: @This(), ref: Ref) void { + var symbol = self.map.get(ref).?; + symbol.chunk_index = self.chunk_index; + } + }; + var decls = decls_; + + DeclaredSymbol.forEachTopLevelSymbol(&decls, Iterator{ .map = this, .chunk_index = chunk_index }, Iterator.next); + } + + pub fn merge(this: *Map, old: Ref, new: Ref) Ref { + if (old.eql(new)) { + return new; + } + + var old_symbol = this.get(old).?; + if (old_symbol.hasLink()) { + old_symbol.link = this.merge(old_symbol.link, new); + return old_symbol.link; + } + + var new_symbol = this.get(new).?; + + if (new_symbol.hasLink()) { + new_symbol.link = this.merge(old, new_symbol.link); + return new_symbol.link; + } + + old_symbol.link = new; + new_symbol.mergeContentsWith(old_symbol); + return new; + } pub fn get(self: *Map, ref: Ref) ?*Symbol { if (Ref.isSourceIndexNull(ref.sourceIndex()) or ref.isSourceContentsSlice()) { return null; } - return &self.symbols_for_source[ref.sourceIndex()][ref.innerIndex()]; + return self.symbols_for_source.at(ref.sourceIndex()).mut(ref.innerIndex()); } - pub fn getConst(self: *Map, ref: Ref) ?*const Symbol { + pub fn getConst(self: *const Map, ref: Ref) ?*const Symbol { if (Ref.isSourceIndexNull(ref.sourceIndex()) or ref.isSourceContentsSlice()) { return null; } - return &self.symbols_for_source[ref.sourceIndex()][ref.innerIndex()]; + return self.symbols_for_source.at(ref.sourceIndex()).at(ref.innerIndex()); } pub fn init(sourceCount: usize, allocator: std.mem.Allocator) !Map { - var symbols_for_source: [][]Symbol = try allocator.alloc([]Symbol, sourceCount); + var symbols_for_source: NestedList = NestedList.init(try allocator.alloc([]Symbol, sourceCount)); return Map{ .symbols_for_source = symbols_for_source }; } - pub fn initList(list: [][]Symbol) Map { + pub fn initList(list: NestedList) Map { return Map{ .symbols_for_source = list }; } @@ -892,6 +954,16 @@ pub const Symbol = struct { return symbol; } + pub fn followAll(symbols: *Map) void { + for (symbols.symbols_for_source.slice()) |list| { + for (list.slice()) |*symbol| { + if (symbol.hasLink()) { + symbol.link = follow(symbols, symbol.link); + } + } + } + } + pub fn follow(symbols: *Map, ref: Ref) Ref { if (symbols.get(ref)) |symbol| { const link = symbol.link; @@ -1095,13 +1167,13 @@ pub const E = struct { target: ExprNodeIndex, optional_chain: ?OptionalChain = null, - pub fn hasSameFlagsAs(a: *Index, b: *Index) bool { + pub fn hasSameFlagsAs(a: *E.Index, b: *E.Index) bool { return (a.optional_chain == b.optional_chain); } }; pub const Arrow = struct { - args: []G.Arg, + args: []G.Arg = &[_]G.Arg{}, body: G.FnBody, is_async: bool = false, @@ -1930,6 +2002,8 @@ pub const Stmt = struct { loc: logger.Loc, data: Data, + pub const Batcher = bun.Batcher(Stmt); + const Serializable = struct { type: Tag, object: string, @@ -2010,6 +2084,8 @@ pub const Stmt = struct { } pub fn alloc(comptime StatementData: type, origData: StatementData, loc: logger.Loc) Stmt { + Stmt.Data.Store.assert(); + icount += 1; return switch (StatementData) { S.Block => Stmt.comptime_alloc("s_block", S.Block, origData, loc), @@ -2190,7 +2266,7 @@ pub const Stmt = struct { has_inited = false; } - pub fn assert() void { + pub inline fn assert() void { if (comptime Environment.allow_assert) { if (!has_inited) bun.unreachablePanic("Store must be init'd", .{}); @@ -2589,6 +2665,7 @@ pub const Expr = struct { pub fn init(comptime Type: type, st: Type, loc: logger.Loc) Expr { icount += 1; + Data.Store.assert(); switch (Type) { E.Array => { @@ -3997,7 +4074,7 @@ pub const Expr = struct { _ = All.init(allocator); } - pub fn assert() void { + pub inline fn assert() void { if (comptime Environment.allow_assert) { if (!has_inited) bun.unreachablePanic("Store must be init'd", .{}); @@ -4534,6 +4611,8 @@ pub const ArrayBinding = struct { }; pub const Ast = struct { + pub const TopLevelSymbolToParts = std.ArrayHashMapUnmanaged(Ref, BabyList(u32), Ref.ArrayHashCtx, false); + approximate_newline_count: usize = 0, has_lazy_export: bool = false, runtime_imports: Runtime.Imports, @@ -4554,22 +4633,23 @@ pub const Ast = struct { // This is a list of ES6 features. They are ranges instead of booleans so // that they can be used in log messages. Check to see if "Len > 0". - import_keyword: ?logger.Range = null, // Does not include TypeScript-specific syntax or "import()" - export_keyword: ?logger.Range = null, // Does not include TypeScript-specific syntax - top_level_await_keyword: ?logger.Range = null, + import_keyword: logger.Range = logger.Range.None, // Does not include TypeScript-specific syntax or "import()" + export_keyword: logger.Range = logger.Range.None, // Does not include TypeScript-specific syntax + top_level_await_keyword: logger.Range = logger.Range.None, // These are stored at the AST level instead of on individual AST nodes so // they can be manipulated efficiently without a full AST traversal - import_records: []ImportRecord = &([_]ImportRecord{}), + import_records: ImportRecord.List = .{}, hashbang: ?string = null, directive: ?string = null, url_for_css: ?string = null, - parts: []Part, - symbols: []Symbol = &([_]Symbol{}), + parts: Part.List = Part.List{}, + // This list may be mutated later, so we should store the capacity + symbols: Symbol.List = Symbol.List{}, module_scope: ?Scope = null, // char_freq: *CharFreq, - exports_ref: ?Ref = null, + exports_ref: Ref = Ref.None, module_ref: ?Ref = null, wrapper_ref: ?Ref = null, require_ref: Ref = Ref.None, @@ -4587,17 +4667,21 @@ pub const Ast = struct { named_exports: NamedExports = undefined, export_star_import_records: []u32 = &([_]u32{}), + allocator: std.mem.Allocator, + top_level_symbols_to_parts: TopLevelSymbolToParts = .{}, + pub const NamedImports = std.ArrayHashMap(Ref, NamedImport, RefHashCtx, true); pub const NamedExports = bun.StringArrayHashMap(NamedExport); pub fn initTest(parts: []Part) Ast { return Ast{ - .parts = parts, + .parts = Part.List.init(parts), + .allocator = bun.default_allocator, .runtime_imports = .{}, }; } - pub const empty = Ast{ .parts = &[_]Part{}, .runtime_imports = undefined }; + pub const empty = Ast{ .parts = Part.List{}, .runtime_imports = .{}, .allocator = bun.default_allocator }; pub fn toJSON(self: *const Ast, _: std.mem.Allocator, stream: anytype) !void { const opts = std.json.StringifyOptions{ .whitespace = std.json.StringifyOptions.Whitespace{ @@ -4609,10 +4693,10 @@ pub const Ast = struct { /// Do not call this if it wasn't globally allocated! pub fn deinit(this: *Ast) void { // TODO: assert mimalloc-owned memory - if (this.parts.len > 0) bun.default_allocator.free(this.parts); + if (this.parts.len > 0) this.parts.deinitWithAllocator(bun.default_allocator); if (this.externals.len > 0) bun.default_allocator.free(this.externals); - if (this.symbols.len > 0) bun.default_allocator.free(this.symbols); - if (this.import_records.len > 0) bun.default_allocator.free(this.import_records); + if (this.symbols.len > 0) this.symbols.deinitWithAllocator(bun.default_allocator); + if (this.import_records.len > 0) this.import_records.deinitWithAllocator(bun.default_allocator); } }; @@ -4646,7 +4730,11 @@ pub const ExportsKind = enum { // "exports") with getters for the export names. All named imports to this // module are allowed. Direct named imports reference the corresponding export // directly. Other imports go through property accesses on "exports". - esm_with_dyn, + esm_with_dynamic_fallback, + + pub fn isDynamic(self: ExportsKind) bool { + return self == .esm_with_dynamic_fallback or self == .cjs; + } pub fn jsonStringify(self: @This(), opts: anytype, o: anytype) !void { return try std.json.stringify(@tagName(self), opts, o); @@ -4656,11 +4744,82 @@ pub const ExportsKind = enum { pub const DeclaredSymbol = struct { ref: Ref, is_top_level: bool = false, + + pub const List = bun.MultiArrayList(DeclaredSymbol); + + pub fn forEachTopLevelSymbol(decls_: *List, ctx: anytype, comptime Fn: anytype) void { + const FnType = @TypeOf(Fn); + const ReturnType = bun.meta.ReturnOfType(FnType); + var decls = decls_.slice(); + var is_top_levels_bool = decls.items(.is_top_level); + var refs = decls.items(.ref); + + if (comptime Environment.enableSIMD) { + const vector_size = 16; + if (refs.len >= vector_size) { + const refs_end = refs.ptr + (refs.len - refs.len % vector_size); + while (refs.ptr != refs_end) { + const vec = @as(@Vector(vector_size, bool), @bitCast([vector_size]bool, is_top_levels_bool.ptr[0..vector_size])); + + if (@reduce(.Max, vec) > 0) { + var j = 0; + while (j < vector_size) : (j += 1) { + if (vec[j]) { + if (comptime ReturnType == void) { + Fn(ctx, refs[j]); + } else if (Fn(ctx, refs[j])) |result| { + refs[j] = result; + } + } + } + } + + is_top_levels_bool = is_top_levels_bool[vector_size..]; + refs = refs[vector_size..]; + } + } + } + + const scalar = 8; + while (refs.len >= scalar) : (refs = refs[scalar..]) { + const eight: u64 = @as(u64, is_top_levels_bool[0..scalar].*); + if (eight == 0) { + is_top_levels_bool = is_top_levels_bool[scalar..]; + continue; + } + + comptime var j: usize = 0; + + inline while (j < scalar) : (j += 1) { + if (is_top_levels_bool[j]) { + if (comptime ReturnType == void) { + Fn(ctx, refs[j]); + } else if (Fn(ctx, refs[j])) |result| { + refs[j] = result; + } + } + } + + is_top_levels_bool = is_top_levels_bool[scalar..]; + } + + while (refs.len > 0) : (refs = refs[1..]) { + if (comptime ReturnType == void) { + Fn(ctx, refs[0]); + } else if (Fn(ctx, refs[0])) |result| { + refs[0] = result; + } + + is_top_levels_bool = is_top_levels_bool[1..]; + } + } }; -pub const Dependency = packed struct { - source_index: u32 = 0, - part_index: u32 = 0, +pub const Dependency = struct { + source_index: Index = Index.invalid, + part_index: Index.Int = 0, + + pub const List = BabyList(Dependency); }; pub const ExprList = std.ArrayList(Expr); @@ -4673,24 +4832,27 @@ pub const BindingList = std.ArrayList(Binding); // shaking and can be assigned to separate chunks (i.e. output files) by code // splitting. pub const Part = struct { - stmts: []Stmt, + pub const ImportRecordIndices = BabyList(u32); + pub const List = BabyList(Part); + + stmts: []Stmt = &([_]Stmt{}), scopes: []*Scope = &([_]*Scope{}), // Each is an index into the file-level import record list - import_record_indices: []u32 = &([_]u32{}), + import_record_indices: ImportRecordIndices = .{}, // All symbols that are declared in this part. Note that a given symbol may // have multiple declarations, and so may end up being declared in multiple // parts (e.g. multiple "var" declarations with the same name). Also note // that this list isn't deduplicated and may contain duplicates. - declared_symbols: []DeclaredSymbol = &([_]DeclaredSymbol{}), + declared_symbols: DeclaredSymbol.List = .{}, // An estimate of the number of uses of all symbols used within this part. symbol_uses: SymbolUseMap = SymbolUseMap{}, // The indices of the other parts in this file that are needed if this part // is needed. - dependencies: []Dependency = &([_]Dependency{}), + dependencies: Dependency.List = .{}, // If true, this part can be removed if none of the declared symbols are // used. If the file containing this part is imported, then all parts that @@ -4708,6 +4870,8 @@ pub const Part = struct { tag: Tag = Tag.none, + valid_in_development: if (bun.Environment.allow_assert) bool else void = bun.DebugOnlyDefault(true), + pub const Tag = enum { none, jsx_import, @@ -4737,10 +4901,10 @@ pub const StmtOrExpr = union(enum) { pub const NamedImport = struct { // Parts within this file that use this import - local_parts_with_uses: []u32 = &([_]u32{}), + local_parts_with_uses: BabyList(u32) = BabyList(u32){}, alias: ?string, - alias_loc: ?logger.Loc, + alias_loc: ?logger.Loc = null, namespace_ref: ?Ref, import_record_index: u32, @@ -4760,7 +4924,7 @@ pub const NamedExport = struct { alias_loc: logger.Loc, }; -pub const StrictModeKind = enum(u7) { +pub const StrictModeKind = enum(u4) { sloppy_mode, explicit_strict_mode, implicit_strict_mode_import, @@ -4778,9 +4942,9 @@ pub const Scope = struct { id: usize = 0, kind: Kind = Kind.block, parent: ?*Scope, - children: std.ArrayListUnmanaged(*Scope) = .{}, + children: BabyList(*Scope) = .{}, members: MemberHashMap = .{}, - generated: std.ArrayListUnmanaged(Ref) = .{}, + generated: BabyList(Ref) = .{}, // This is used to store the ref of the label symbol for ScopeLabel scopes. label_ref: ?Ref = null, @@ -4958,7 +5122,7 @@ pub const Scope = struct { pub fn recursiveSetStrictMode(s: *Scope, kind: StrictModeKind) void { if (s.strict_mode == .sloppy_mode) { s.strict_mode = kind; - for (s.children.items) |child| { + for (s.children.slice()) |child| { child.recursiveSetStrictMode(kind); } } @@ -8525,3 +8689,4 @@ pub const Macro = struct { // Stmt | 192 // STry | 384 // -- ESBuild bit sizes + diff --git a/src/js_parser.zig b/src/js_parser.zig index a8861875aa9a..0c6e83095f1d 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -37,6 +37,7 @@ pub const ExprNodeIndex = js_ast.ExprNodeIndex; pub const ExprNodeList = js_ast.ExprNodeList; pub const StmtNodeList = js_ast.StmtNodeList; pub const BindingNodeList = js_ast.BindingNodeList; +const DeclaredSymbol = js_ast.DeclaredSymbol; const ComptimeStringMap = @import("./comptime_string_map.zig").ComptimeStringMap; const JSC = @import("bun").JSC; @@ -67,7 +68,7 @@ const Ref = @import("./ast/base.zig").Ref; const RefHashCtx = @import("./ast/base.zig").RefHashCtx; pub const StringHashMap = bun.StringHashMap; -pub const AutoHashMap = bun.AutoHashMap; +pub const AutoHashMap = std.AutoHashMap; const StringHashMapUnamanged = bun.StringHashMapUnmanaged; const ObjectPool = @import("./pool.zig").ObjectPool; const NodeFallbackModules = @import("./node_fallbacks.zig"); @@ -119,7 +120,9 @@ fn foldStringAddition(lhs: Expr, rhs: Expr) ?Expr { if (rhs.data == .e_string and left.isUTF8() and rhs.data.e_string.isUTF8()) { var orig = lhs.data.e_string.*; const rhs_clone = Expr.init(E.String, rhs.data.e_string.*, rhs.loc); - orig.push(rhs_clone.data.e_string); + orig.push( + rhs_clone.data.e_string, + ); return Expr.init(E.String, orig, lhs.loc); } @@ -2247,6 +2250,7 @@ pub const Parser = struct { features: RuntimeFeatures = RuntimeFeatures{}, tree_shaking: bool = false, + bundle: bool = false, macro_context: *MacroContextType() = undefined, @@ -2317,7 +2321,7 @@ pub const Parser = struct { import_record.is_unused = import_record.is_unused or (import_record.kind == .stmt and !import_record.was_originally_bare_import and - !import_record.calls_run_time_re_export_fn); + !import_record.calls_runtime_re_export_fn); } var iter = scan_pass.used_symbols.iterator(); @@ -2483,47 +2487,47 @@ pub const Parser = struct { return error.SyntaxError; } - const uses_dirname = p.symbols.items[p.dirname_ref.innerIndex()].use_count_estimate > 0; - const uses_filename = p.symbols.items[p.filename_ref.innerIndex()].use_count_estimate > 0; - - if (uses_dirname or uses_filename) { - const count = @as(usize, @boolToInt(uses_dirname)) + @as(usize, @boolToInt(uses_filename)); - var declared_symbols = try p.allocator.alloc(js_ast.DeclaredSymbol, count); - var decls = p.allocator.alloc(G.Decl, count) catch unreachable; - if (uses_dirname) { - decls[0] = .{ - .binding = p.b(B.Identifier{ .ref = p.dirname_ref }, logger.Loc.Empty), - .value = p.newExpr( - // TODO: test UTF-8 file paths - E.String.init(p.source.path.name.dir), - logger.Loc.Empty, - ), - }; - declared_symbols[0] = .{ .ref = p.dirname_ref, .is_top_level = true }; - } - if (uses_filename) { - decls[@as(usize, @boolToInt(uses_dirname))] = .{ - .binding = p.b(B.Identifier{ .ref = p.filename_ref }, logger.Loc.Empty), - .value = p.newExpr( - E.String.init(p.source.path.text), - logger.Loc.Empty, - ), - }; - declared_symbols[@as(usize, @boolToInt(uses_dirname))] = .{ .ref = p.filename_ref, .is_top_level = true }; - } + // const uses_dirname = p.symbols.items[p.dirname_ref.innerIndex()].use_count_estimate > 0; + // const uses_filename = p.symbols.items[p.filename_ref.innerIndex()].use_count_estimate > 0; + + // if (uses_dirname or uses_filename) { + // const count = @as(usize, @boolToInt(uses_dirname)) + @as(usize, @boolToInt(uses_filename)); + // var declared_symbols = try p.allocator.alloc(DeclaredSymbol, count); + // var decls = p.allocator.alloc(G.Decl, count) catch unreachable; + // if (uses_dirname) { + // decls[0] = .{ + // .binding = p.b(B.Identifier{ .ref = p.dirname_ref }, logger.Loc.Empty), + // .value = p.newExpr( + // // TODO: test UTF-8 file paths + // E.String.init(p.source.path.name.dir), + // logger.Loc.Empty, + // ), + // }; + // declared_symbols[0] = .{ .ref = p.dirname_ref, .is_top_level = true }; + // } + // if (uses_filename) { + // decls[@as(usize, @boolToInt(uses_dirname))] = .{ + // .binding = p.b(B.Identifier{ .ref = p.filename_ref }, logger.Loc.Empty), + // .value = p.newExpr( + // E.String.init(p.source.path.text), + // logger.Loc.Empty, + // ), + // }; + // declared_symbols[@as(usize, @boolToInt(uses_dirname))] = .{ .ref = p.filename_ref, .is_top_level = true }; + // } - // TODO: DeclaredSymbol - var part_stmts = p.allocator.alloc(Stmt, 1) catch unreachable; - part_stmts[0] = p.s(S.Local{ - .kind = .k_var, - .decls = decls, - }, logger.Loc.Empty); - before.append(js_ast.Part{ - .stmts = part_stmts, - .declared_symbols = declared_symbols, - .tag = .dirname_filename, - }) catch unreachable; - } + // // TODO: DeclaredSymbol + // var part_stmts = p.allocator.alloc(Stmt, 1) catch unreachable; + // part_stmts[0] = p.s(S.Local{ + // .kind = .k_var, + // .decls = decls, + // }, logger.Loc.Empty); + // before.append(js_ast.Part{ + // .stmts = part_stmts, + // .declared_symbols = declared_symbols, + // .tag = .dirname_filename, + // }) catch unreachable; + // } var did_import_fast_refresh = false; @@ -2538,26 +2542,28 @@ pub const Parser = struct { exports_kind = .esm; } else if (uses_exports_ref or uses_module_ref or p.has_top_level_return) { exports_kind = .cjs; - if (p.options.transform_require_to_import or (p.options.features.dynamic_require and !p.options.enable_bundling)) { - var args = p.allocator.alloc(Expr, 2) catch unreachable; - - if (p.runtime_imports.__exportDefault == null and p.has_export_default) { - p.runtime_imports.__exportDefault = try p.declareGeneratedSymbol(.other, "__exportDefault"); - p.resolveGeneratedSymbol(&p.runtime_imports.__exportDefault.?); + if (!p.options.bundle) { + if (p.options.transform_require_to_import or (p.options.features.dynamic_require and !p.options.enable_bundling)) { + var args = p.allocator.alloc(Expr, 2) catch unreachable; + + if (p.runtime_imports.__exportDefault == null and p.has_export_default) { + p.runtime_imports.__exportDefault = try p.declareGeneratedSymbol(.other, "__exportDefault"); + p.resolveGeneratedSymbol(&p.runtime_imports.__exportDefault.?); + } + + wrapper_expr = p.callRuntime(logger.Loc.Empty, "__cJS2eSM", args); + p.resolveGeneratedSymbol(&p.runtime_imports.__cJS2eSM.?); + + // Disable HMR if we're wrapping it in CommonJS + // It's technically possible to support this. + // But we need to cut scope for the v0. + p.options.features.hot_module_reloading = false; + p.options.features.react_fast_refresh = false; + p.runtime_imports.__HMRModule = null; + p.runtime_imports.__FastRefreshModule = null; + p.runtime_imports.__FastRefreshRuntime = null; + p.runtime_imports.__HMRClient = null; } - - wrapper_expr = p.callRuntime(logger.Loc.Empty, "__cJS2eSM", args); - p.resolveGeneratedSymbol(&p.runtime_imports.__cJS2eSM.?); - - // Disable HMR if we're wrapping it in CommonJS - // It's technically possible to support this. - // But we need to cut scope for the v0. - p.options.features.hot_module_reloading = false; - p.options.features.react_fast_refresh = false; - p.runtime_imports.__HMRModule = null; - p.runtime_imports.__FastRefreshModule = null; - p.runtime_imports.__FastRefreshRuntime = null; - p.runtime_imports.__HMRClient = null; } } else { exports_kind = .esm; @@ -2628,558 +2634,553 @@ pub const Parser = struct { } // Auto-import & post-process JSX - switch (comptime ParserType.jsx_transform_type) { - .react => { - // const jsx_filename_symbol = if (p.options.jsx.development) - // p.symbols.items[p.jsx_filename.ref.innerIndex()] - // else - // Symbol{ .original_name = "" }; - - { - const jsx_symbol = p.symbols.items[p.jsx_runtime.ref.innerIndex()]; - - const jsx_fragment_symbol = p.symbols.items[p.jsx_fragment.ref.innerIndex()]; - const jsx_factory_symbol = p.symbols.items[p.jsx_factory.ref.innerIndex()]; - - // Currently, React (and most node_modules) ship a CJS version or a UMD version - // but we should assume that it'll pretty much always be CJS - // Given that, we can't directly call import {jsxDEV} from 'react'; - // Instead, we must call require("react").default.jsxDEV - // So a jsx_symbol usage means a jsx_factory_symbol usage - // This is kind of a broken way of doing it because it wouldn't work if it was more than one level deep - if (FeatureFlags.jsx_runtime_is_cjs) { - if (jsx_symbol.use_count_estimate > 0) { - p.recordUsage(p.jsx_automatic.ref); - } - - if (FeatureFlags.support_jsxs_in_jsx_transform) { - const jsx_static_symbol = p.symbols.items[p.jsxs_runtime.ref.innerIndex()]; - - if (jsx_static_symbol.use_count_estimate > 0) { - p.recordUsage(p.jsx_automatic.ref); - } - } - - if (jsx_fragment_symbol.use_count_estimate > 0) { - p.recordUsage(p.jsx_classic.ref); - } - - if (jsx_factory_symbol.use_count_estimate > 0) { - p.recordUsage(p.jsx_classic.ref); - } - } - } - - p.resolveStaticJSXSymbols(); - - if (p.options.features.auto_import_jsx) { - const jsx_classic_symbol = p.symbols.items[p.jsx_classic.ref.innerIndex()]; - const jsx_automatic_symbol = p.symbols.items[p.jsx_automatic.ref.innerIndex()]; - const react_element_symbol = if (p.options.features.jsx_optimization_inline) p.symbols.items[p.react_element_type.ref.innerIndex()] else Symbol{ - .original_name = "IF_YOU_SEE_THIS_ITS_A_BUG_IN_BUN_WHERE_REACT_ELEMENT_SYMBOL_IS_BEING_ADDED_WHEN_IT_SHOULDNT_BE_PLEASE_REPORT_IT", - }; + if (!p.options.bundle) { + switch (comptime ParserType.jsx_transform_type) { + .react => { + // const jsx_filename_symbol = if (p.options.jsx.development) + // p.symbols.items[p.jsx_filename.ref.innerIndex()] + // else + // Symbol{ .original_name = "" }; - // JSX auto-imports - // The classic runtime is a different import than the main import - // There are cases where you can use both JSX runtimes in the same file. - // 1. If you use a spread operator like this:
- // 2. If you use a React.Fragment - // So we have to support both. - if (jsx_classic_symbol.use_count_estimate > 0 or jsx_automatic_symbol.use_count_estimate > 0 or react_element_symbol.use_count_estimate > 0) { - // These must unfortunately be copied - // p.symbols may grow during this scope - // if it grows, the previous pointers are invalidated + { const jsx_symbol = p.symbols.items[p.jsx_runtime.ref.innerIndex()]; - const jsx_static_symbol: Symbol = if (!FeatureFlags.support_jsxs_in_jsx_transform) - undefined - else - p.symbols.items[p.jsxs_runtime.ref.innerIndex()]; const jsx_fragment_symbol = p.symbols.items[p.jsx_fragment.ref.innerIndex()]; const jsx_factory_symbol = p.symbols.items[p.jsx_factory.ref.innerIndex()]; - const classic_namespace_ref = p.jsx_classic.ref; - const automatic_namespace_ref = p.jsx_automatic.ref; - - const decls_count: u32 = - // "REACT_ELEMENT_TYPE" - // "Symbol.for('react.element')" - @intCast(u32, @boolToInt(react_element_symbol.use_count_estimate > 0)) * 2 + - - // "JSX" - @intCast(u32, @boolToInt(jsx_symbol.use_count_estimate > 0)) * 2 + - @intCast(u32, @boolToInt(FeatureFlags.support_jsxs_in_jsx_transform and jsx_static_symbol.use_count_estimate > 0)) * 2 + - @intCast(u32, @boolToInt(jsx_factory_symbol.use_count_estimate > 0)) + - @intCast(u32, @boolToInt(jsx_fragment_symbol.use_count_estimate > 0)); - // @intCast(u32, @boolToInt(jsx_filename_symbol.use_count_estimate > 0)); - - const imports_count = - @intCast(u32, @boolToInt(jsx_symbol.use_count_estimate > 0)) + - @intCast(u32, @boolToInt(jsx_classic_symbol.use_count_estimate > 0)) + - @intCast(u32, @boolToInt(jsx_fragment_symbol.use_count_estimate > 0)) + - @intCast(u32, @boolToInt(p.options.features.react_fast_refresh)) + - @intCast(u32, @boolToInt(FeatureFlags.support_jsxs_in_jsx_transform and jsx_static_symbol.use_count_estimate > 0)); - const stmts_count = imports_count + 1; - const symbols_count: u32 = imports_count + decls_count; - const loc = logger.Loc{ .start = 0 }; - - // Preallocate everything we'll need here - var declared_symbols = try p.allocator.alloc(js_ast.DeclaredSymbol, symbols_count); - var decls = try p.allocator.alloc(G.Decl, decls_count); - var jsx_part_stmts = try p.allocator.alloc(Stmt, stmts_count); - // Use the same array for storing the require call target of potentially both JSX runtimes - var require_call_args_base = p.allocator.alloc(Expr, if (p.options.can_import_from_bundle) 0 else imports_count) catch unreachable; - var import_records = try p.allocator.alloc(u32, imports_count); - - var decl_i: usize = 0; - var declared_symbols_i: usize = 0; - var import_record_i: usize = 0; - var require_call_args_i: usize = 0; - var stmt_i: usize = 0; + // Currently, React (and most node_modules) ship a CJS version or a UMD version + // but we should assume that it'll pretty much always be CJS + // Given that, we can't directly call import {jsxDEV} from 'react'; + // Instead, we must call require("react").default.jsxDEV + // So a jsx_symbol usage means a jsx_factory_symbol usage + // This is kind of a broken way of doing it because it wouldn't work if it was more than one level deep + if (FeatureFlags.jsx_runtime_is_cjs) { + if (jsx_symbol.use_count_estimate > 0) { + p.recordUsage(p.jsx_automatic.ref); + } - if (react_element_symbol.use_count_estimate > 0) { - declared_symbols[declared_symbols_i] = .{ .ref = p.react_element_type.ref, .is_top_level = true }; - declared_symbols_i += 1; - p.recordUsage(p.es6_symbol_global.ref); - var call_args = p.allocator.alloc(Expr, 1) catch unreachable; - call_args[0] = Expr{ .data = Prefill.Data.REACT_ELEMENT_TYPE, .loc = logger.Loc.Empty }; + if (FeatureFlags.support_jsxs_in_jsx_transform) { + const jsx_static_symbol = p.symbols.items[p.jsxs_runtime.ref.innerIndex()]; - decls[decl_i] = G.Decl{ - .binding = p.b( - B.Identifier{ - .ref = p.react_element_type.ref, - }, - loc, - ), - .value = p.newExpr( - E.Call{ - // Symbol.for - .target = p.newExpr( - E.Dot{ - .name = "for", - .name_loc = logger.Loc.Empty, - .target = p.newExpr( - E.Identifier{ - .ref = p.es6_symbol_global.ref, - .can_be_removed_if_unused = true, - .call_can_be_unwrapped_if_unused = true, - }, - logger.Loc.Empty, - ), - .can_be_removed_if_unused = true, - .call_can_be_unwrapped_if_unused = true, - }, - logger.Loc.Empty, - ), - .args = ExprNodeList.init(call_args), - .close_paren_loc = logger.Loc.Empty, - .can_be_unwrapped_if_unused = true, - }, - logger.Loc.Empty, - ), - }; - decl_i += 1; - } + if (jsx_static_symbol.use_count_estimate > 0) { + p.recordUsage(p.jsx_automatic.ref); + } + } - if (jsx_symbol.use_count_estimate > 0 or (FeatureFlags.support_jsxs_in_jsx_transform and jsx_static_symbol.use_count_estimate > 0)) { - declared_symbols[declared_symbols_i] = .{ .ref = automatic_namespace_ref, .is_top_level = true }; - declared_symbols_i += 1; + if (jsx_fragment_symbol.use_count_estimate > 0) { + p.recordUsage(p.jsx_classic.ref); + } - const automatic_identifier = p.newExpr(E.ImportIdentifier{ .ref = automatic_namespace_ref }, loc); + if (jsx_factory_symbol.use_count_estimate > 0) { + p.recordUsage(p.jsx_classic.ref); + } + } + } - // We do not mark this as .require becuase we are already wrapping it manually. - // unless it's bun and you're not bundling - const use_automatic_identifier = (p.options.can_import_from_bundle or p.options.enable_bundling or !p.options.features.allow_runtime); - const import_record_kind = if (use_automatic_identifier) ImportKind.internal else ImportKind.require; - const import_record_id = p.addImportRecord(import_record_kind, loc, p.options.jsx.import_source); + p.resolveStaticJSXSymbols(); - const dot_call_target = brk: { - if (use_automatic_identifier) { - break :brk automatic_identifier; - } else if (p.options.features.dynamic_require) { - break :brk p.newExpr(E.Require{ .import_record_index = import_record_id }, loc); - } else { - require_call_args_base[require_call_args_i] = automatic_identifier; - require_call_args_i += 1; - break :brk p.callUnbundledRequire(require_call_args_base[0..require_call_args_i]); - } - }; + if (p.options.features.auto_import_jsx) { + const jsx_classic_symbol = p.symbols.items[p.jsx_classic.ref.innerIndex()]; + const jsx_automatic_symbol = p.symbols.items[p.jsx_automatic.ref.innerIndex()]; + const react_element_symbol = if (p.options.features.jsx_optimization_inline) p.symbols.items[p.react_element_type.ref.innerIndex()] else Symbol{ + .original_name = "IF_YOU_SEE_THIS_ITS_A_BUG_IN_BUN_WHERE_REACT_ELEMENT_SYMBOL_IS_BEING_ADDED_WHEN_IT_SHOULDNT_BE_PLEASE_REPORT_IT", + }; - if (jsx_symbol.use_count_estimate > 0) { - declared_symbols[declared_symbols_i] = .{ .ref = p.jsx_runtime.ref, .is_top_level = true }; - declared_symbols_i += 1; + // JSX auto-imports + // The classic runtime is a different import than the main import + // There are cases where you can use both JSX runtimes in the same file. + // 1. If you use a spread operator like this:
+ // 2. If you use a React.Fragment + // So we have to support both. + if (jsx_classic_symbol.use_count_estimate > 0 or jsx_automatic_symbol.use_count_estimate > 0 or react_element_symbol.use_count_estimate > 0) { + // These must unfortunately be copied + // p.symbols may grow during this scope + // if it grows, the previous pointers are invalidated + const jsx_symbol = p.symbols.items[p.jsx_runtime.ref.innerIndex()]; + const jsx_static_symbol: Symbol = if (!FeatureFlags.support_jsxs_in_jsx_transform) + undefined + else + p.symbols.items[p.jsxs_runtime.ref.innerIndex()]; + + const jsx_fragment_symbol = p.symbols.items[p.jsx_fragment.ref.innerIndex()]; + const jsx_factory_symbol = p.symbols.items[p.jsx_factory.ref.innerIndex()]; + + const classic_namespace_ref = p.jsx_classic.ref; + const automatic_namespace_ref = p.jsx_automatic.ref; + + const decls_count: u32 = + // "REACT_ELEMENT_TYPE" + // "Symbol.for('react.element')" + @intCast(u32, @boolToInt(react_element_symbol.use_count_estimate > 0)) * 2 + + + // "JSX" + @intCast(u32, @boolToInt(jsx_symbol.use_count_estimate > 0)) * 2 + + @intCast(u32, @boolToInt(FeatureFlags.support_jsxs_in_jsx_transform and jsx_static_symbol.use_count_estimate > 0)) * 2 + + @intCast(u32, @boolToInt(jsx_factory_symbol.use_count_estimate > 0)) + + @intCast(u32, @boolToInt(jsx_fragment_symbol.use_count_estimate > 0)); + // @intCast(u32, @boolToInt(jsx_filename_symbol.use_count_estimate > 0)); + + const imports_count = + @intCast(u32, @boolToInt(jsx_symbol.use_count_estimate > 0)) + + @intCast(u32, @boolToInt(jsx_classic_symbol.use_count_estimate > 0)) + + @intCast(u32, @boolToInt(jsx_fragment_symbol.use_count_estimate > 0)) + + @intCast(u32, @boolToInt(p.options.features.react_fast_refresh)) + + @intCast(u32, @boolToInt(FeatureFlags.support_jsxs_in_jsx_transform and jsx_static_symbol.use_count_estimate > 0)); + const stmts_count = imports_count + 1; + const symbols_count: u32 = imports_count + decls_count; + const loc = logger.Loc{ .start = 0 }; + + // Preallocate everything we'll need here + var declared_symbols = DeclaredSymbol.List{}; + try declared_symbols.ensureTotalCapacity(p.allocator, symbols_count); + var decls = try p.allocator.alloc(G.Decl, decls_count); + var jsx_part_stmts = try p.allocator.alloc(Stmt, stmts_count); + // Use the same array for storing the require call target of potentially both JSX runtimes + var require_call_args_base = p.allocator.alloc(Expr, if (p.options.can_import_from_bundle) 0 else imports_count) catch unreachable; + var import_records = try p.allocator.alloc(u32, imports_count); + + var decl_i: usize = 0; + var import_record_i: usize = 0; + var require_call_args_i: usize = 0; + var stmt_i: usize = 0; + + if (react_element_symbol.use_count_estimate > 0) { + declared_symbols.appendAssumeCapacity(.{ .ref = p.react_element_type.ref, .is_top_level = true }); + p.recordUsage(p.es6_symbol_global.ref); + var call_args = p.allocator.alloc(Expr, 1) catch unreachable; + call_args[0] = Expr{ .data = Prefill.Data.REACT_ELEMENT_TYPE, .loc = logger.Loc.Empty }; decls[decl_i] = G.Decl{ .binding = p.b( B.Identifier{ - .ref = p.jsx_runtime.ref, + .ref = p.react_element_type.ref, }, loc, ), .value = p.newExpr( - E.Dot{ - .target = dot_call_target, - .name = p.options.jsx.jsx, - .name_loc = loc, - .can_be_removed_if_unused = true, + E.Call{ + // Symbol.for + .target = p.newExpr( + E.Dot{ + .name = "for", + .name_loc = logger.Loc.Empty, + .target = p.newExpr( + E.Identifier{ + .ref = p.es6_symbol_global.ref, + .can_be_removed_if_unused = true, + .call_can_be_unwrapped_if_unused = true, + }, + logger.Loc.Empty, + ), + .can_be_removed_if_unused = true, + .call_can_be_unwrapped_if_unused = true, + }, + logger.Loc.Empty, + ), + .args = ExprNodeList.init(call_args), + .close_paren_loc = logger.Loc.Empty, + .can_be_unwrapped_if_unused = true, }, - loc, + logger.Loc.Empty, ), }; decl_i += 1; } - if (FeatureFlags.support_jsxs_in_jsx_transform) { - if (jsx_static_symbol.use_count_estimate > 0) { - declared_symbols[declared_symbols_i] = .{ .ref = p.jsxs_runtime.ref, .is_top_level = true }; - declared_symbols_i += 1; + if (jsx_symbol.use_count_estimate > 0 or (FeatureFlags.support_jsxs_in_jsx_transform and jsx_static_symbol.use_count_estimate > 0)) { + declared_symbols.appendAssumeCapacity(.{ .ref = automatic_namespace_ref, .is_top_level = true }); + + const automatic_identifier = p.newExpr(E.ImportIdentifier{ .ref = automatic_namespace_ref }, loc); + + // We do not mark this as .require becuase we are already wrapping it manually. + // unless it's bun and you're not bundling + const use_automatic_identifier = (p.options.can_import_from_bundle or p.options.enable_bundling or !p.options.features.allow_runtime); + const import_record_kind = if (use_automatic_identifier) ImportKind.internal else ImportKind.require; + const import_record_id = p.addImportRecord(import_record_kind, loc, p.options.jsx.import_source); + + const dot_call_target = brk: { + if (use_automatic_identifier) { + break :brk automatic_identifier; + } else if (p.options.features.dynamic_require) { + break :brk p.newExpr(E.Require{ .import_record_index = import_record_id }, loc); + } else { + require_call_args_base[require_call_args_i] = automatic_identifier; + require_call_args_i += 1; + break :brk p.callUnbundledRequire(require_call_args_base[0..require_call_args_i]); + } + }; + + if (jsx_symbol.use_count_estimate > 0) { + declared_symbols.appendAssumeCapacity(.{ .ref = p.jsx_runtime.ref, .is_top_level = true }); decls[decl_i] = G.Decl{ .binding = p.b( B.Identifier{ - .ref = p.jsxs_runtime.ref, + .ref = p.jsx_runtime.ref, }, loc, ), .value = p.newExpr( E.Dot{ .target = dot_call_target, - .name = p.options.jsx.jsx_static, + .name = p.options.jsx.jsx, .name_loc = loc, .can_be_removed_if_unused = true, }, loc, ), }; - decl_i += 1; } - } - // if (jsx_filename_symbol.use_count_estimate > 0) { - // declared_symbols[declared_symbols_i] = .{ .ref = p.jsx_filename.ref, .is_top_level = true }; - // declared_symbols_i += 1; - // decls[decl_i] = G.Decl{ - // .binding = p.b( - // B.Identifier{ - // .ref = p.jsx_filename.ref, - // }, - // loc, - // ), - // .value = p.newExpr(E.String{ .data = p.source.path.pretty }, loc), - // }; - // decl_i += 1; - // } - - p.import_records.items[import_record_id].tag = .jsx_import; - if (dot_call_target.data != .e_require) { - // When everything is CommonJS - // We import JSX like this: - // var {jsxDev} = require("react/jsx-dev") - jsx_part_stmts[stmt_i] = p.s(S.Import{ - .namespace_ref = automatic_namespace_ref, - .star_name_loc = loc, - .is_single_line = true, - .import_record_index = import_record_id, - }, loc); - stmt_i += 1; - } - - p.named_imports.put( - automatic_namespace_ref, - js_ast.NamedImport{ - .alias = jsx_automatic_symbol.original_name, - .alias_is_star = true, - .alias_loc = loc, - .namespace_ref = automatic_namespace_ref, - .import_record_index = import_record_id, - }, - ) catch unreachable; - p.is_import_item.put(p.allocator, automatic_namespace_ref, {}) catch unreachable; - import_records[import_record_i] = import_record_id; - import_record_i += 1; - } + if (FeatureFlags.support_jsxs_in_jsx_transform) { + if (jsx_static_symbol.use_count_estimate > 0) { + declared_symbols.appendAssumeCapacity(.{ .ref = p.jsxs_runtime.ref, .is_top_level = true }); - if (jsx_classic_symbol.use_count_estimate > 0) { - const classic_identifier = p.newExpr(E.ImportIdentifier{ .ref = classic_namespace_ref }, loc); - const import_record_id = p.addImportRecord(.require, loc, p.options.jsx.classic_import_source); - const dot_call_target = brk: { - // var react = $aopaSD123(); + decls[decl_i] = G.Decl{ + .binding = p.b( + B.Identifier{ + .ref = p.jsxs_runtime.ref, + }, + loc, + ), + .value = p.newExpr( + E.Dot{ + .target = dot_call_target, + .name = p.options.jsx.jsx_static, + .name_loc = loc, + .can_be_removed_if_unused = true, + }, + loc, + ), + }; - if (p.options.can_import_from_bundle or p.options.enable_bundling or !p.options.features.allow_runtime) { - break :brk classic_identifier; - } else if (p.options.features.dynamic_require) { - break :brk p.newExpr(E.Require{ .import_record_index = import_record_id }, loc); - } else { - const require_call_args_start = require_call_args_i; - require_call_args_base[require_call_args_i] = classic_identifier; - require_call_args_i += 1; - break :brk p.callUnbundledRequire(require_call_args_base[require_call_args_start..][0..1]); + decl_i += 1; + } } - }; + // if (jsx_filename_symbol.use_count_estimate > 0) { + // declared_symbols.appendAssumeCapacity(.{ .ref = p.jsx_filename.ref, .is_top_level = true }); + // decls[decl_i] = G.Decl{ + // .binding = p.b( + // B.Identifier{ + // .ref = p.jsx_filename.ref, + // }, + // loc, + // ), + // .value = p.newExpr(E.String{ .data = p.source.path.pretty }, loc), + // }; + // decl_i += 1; + // } + + p.import_records.items[import_record_id].tag = .jsx_import; + if (dot_call_target.data != .e_require) { + // When everything is CommonJS + // We import JSX like this: + // var {jsxDev} = require("react/jsx-dev") + jsx_part_stmts[stmt_i] = p.s(S.Import{ + .namespace_ref = automatic_namespace_ref, + .star_name_loc = loc, + .is_single_line = true, + .import_record_index = import_record_id, + }, loc); - if (jsx_factory_symbol.use_count_estimate > 0) { - declared_symbols[declared_symbols_i] = .{ .ref = p.jsx_factory.ref, .is_top_level = true }; - declared_symbols_i += 1; - decls[decl_i] = G.Decl{ - .binding = p.b( - B.Identifier{ - .ref = p.jsx_factory.ref, - }, - loc, - ), - .value = p.memberExpression( - loc, - dot_call_target, - if (p.options.jsx.factory.len > 1) p.options.jsx.factory[1..] else p.options.jsx.factory, - ), - }; - decl_i += 1; - } + stmt_i += 1; + } - if (jsx_fragment_symbol.use_count_estimate > 0) { - declared_symbols[declared_symbols_i] = .{ .ref = p.jsx_fragment.ref, .is_top_level = true }; - declared_symbols_i += 1; - decls[decl_i] = G.Decl{ - .binding = p.b( - B.Identifier{ - .ref = p.jsx_fragment.ref, - }, - loc, - ), - .value = p.memberExpression( - loc, - dot_call_target, - if (p.options.jsx.fragment.len > 1) p.options.jsx.fragment[1..] else p.options.jsx.fragment, - ), - }; - decl_i += 1; + p.named_imports.put( + automatic_namespace_ref, + js_ast.NamedImport{ + .alias = jsx_automatic_symbol.original_name, + .alias_is_star = true, + .alias_loc = loc, + .namespace_ref = automatic_namespace_ref, + .import_record_index = import_record_id, + }, + ) catch unreachable; + p.is_import_item.put(p.allocator, automatic_namespace_ref, {}) catch unreachable; + import_records[import_record_i] = import_record_id; + import_record_i += 1; } - if (dot_call_target.data != .e_require) { - jsx_part_stmts[stmt_i] = p.s(S.Import{ - .namespace_ref = classic_namespace_ref, - .star_name_loc = loc, - .is_single_line = true, - .import_record_index = import_record_id, - }, loc); - stmt_i += 1; - } + if (jsx_classic_symbol.use_count_estimate > 0) { + const classic_identifier = p.newExpr(E.ImportIdentifier{ .ref = classic_namespace_ref }, loc); + const import_record_id = p.addImportRecord(.require, loc, p.options.jsx.classic_import_source); + const dot_call_target = brk: { + // var react = $aopaSD123(); - p.import_records.items[import_record_id].tag = .jsx_classic; + if (p.options.can_import_from_bundle or p.options.enable_bundling or !p.options.features.allow_runtime) { + break :brk classic_identifier; + } else if (p.options.features.dynamic_require) { + break :brk p.newExpr(E.Require{ .import_record_index = import_record_id }, loc); + } else { + const require_call_args_start = require_call_args_i; + require_call_args_base[require_call_args_i] = classic_identifier; + require_call_args_i += 1; + break :brk p.callUnbundledRequire(require_call_args_base[require_call_args_start..][0..1]); + } + }; - p.named_imports.put( - classic_namespace_ref, - js_ast.NamedImport{ - .alias = jsx_classic_symbol.original_name, - .alias_is_star = true, - .alias_loc = loc, - .namespace_ref = classic_namespace_ref, - .import_record_index = import_record_id, - }, - ) catch unreachable; - p.is_import_item.put(p.allocator, classic_namespace_ref, {}) catch unreachable; - import_records[import_record_i] = import_record_id; - declared_symbols[declared_symbols_i] = .{ .ref = classic_namespace_ref, .is_top_level = true }; - declared_symbols_i += 1; - } + if (jsx_factory_symbol.use_count_estimate > 0) { + declared_symbols.appendAssumeCapacity(.{ .ref = p.jsx_factory.ref, .is_top_level = true }); + decls[decl_i] = G.Decl{ + .binding = p.b( + B.Identifier{ + .ref = p.jsx_factory.ref, + }, + loc, + ), + .value = p.memberExpression( + loc, + dot_call_target, + if (p.options.jsx.factory.len > 1) p.options.jsx.factory[1..] else p.options.jsx.factory, + ), + }; + decl_i += 1; + } - if (p.options.features.react_fast_refresh) { - defer did_import_fast_refresh = true; - p.resolveGeneratedSymbol(&p.jsx_refresh_runtime); - if (!p.options.jsx.use_embedded_refresh_runtime) { - const refresh_runtime_symbol: *const Symbol = &p.symbols.items[p.jsx_refresh_runtime.ref.innerIndex()]; + if (jsx_fragment_symbol.use_count_estimate > 0) { + declared_symbols.appendAssumeCapacity(.{ .ref = p.jsx_fragment.ref, .is_top_level = true }); + decls[decl_i] = G.Decl{ + .binding = p.b( + B.Identifier{ + .ref = p.jsx_fragment.ref, + }, + loc, + ), + .value = p.memberExpression( + loc, + dot_call_target, + if (p.options.jsx.fragment.len > 1) p.options.jsx.fragment[1..] else p.options.jsx.fragment, + ), + }; + decl_i += 1; + } - declared_symbols[declared_symbols_i] = .{ .ref = p.jsx_refresh_runtime.ref, .is_top_level = true }; - declared_symbols_i += 1; + if (dot_call_target.data != .e_require) { + jsx_part_stmts[stmt_i] = p.s(S.Import{ + .namespace_ref = classic_namespace_ref, + .star_name_loc = loc, + .is_single_line = true, + .import_record_index = import_record_id, + }, loc); + stmt_i += 1; + } - const import_record_id = p.addImportRecord(.require, loc, p.options.jsx.refresh_runtime); - p.import_records.items[import_record_id].tag = .react_refresh; - jsx_part_stmts[stmt_i] = p.s(S.Import{ - .namespace_ref = p.jsx_refresh_runtime.ref, - .star_name_loc = loc, - .is_single_line = true, - .import_record_index = import_record_id, - }, loc); + p.import_records.items[import_record_id].tag = .jsx_classic; - stmt_i += 1; p.named_imports.put( - p.jsx_refresh_runtime.ref, + classic_namespace_ref, js_ast.NamedImport{ - .alias = refresh_runtime_symbol.original_name, + .alias = jsx_classic_symbol.original_name, .alias_is_star = true, .alias_loc = loc, - .namespace_ref = p.jsx_refresh_runtime.ref, + .namespace_ref = classic_namespace_ref, .import_record_index = import_record_id, }, ) catch unreachable; - p.is_import_item.put(p.allocator, p.jsx_refresh_runtime.ref, {}) catch unreachable; + p.is_import_item.put(p.allocator, classic_namespace_ref, {}) catch unreachable; import_records[import_record_i] = import_record_id; + declared_symbols.appendAssumeCapacity(.{ .ref = classic_namespace_ref, .is_top_level = true }); } - p.recordUsage(p.jsx_refresh_runtime.ref); - } - jsx_part_stmts[stmt_i] = p.s(S.Local{ .kind = .k_var, .decls = decls[0..decl_i] }, loc); - stmt_i += 1; + if (p.options.features.react_fast_refresh) { + defer did_import_fast_refresh = true; + p.resolveGeneratedSymbol(&p.jsx_refresh_runtime); + if (!p.options.jsx.use_embedded_refresh_runtime) { + const refresh_runtime_symbol: *const Symbol = &p.symbols.items[p.jsx_refresh_runtime.ref.innerIndex()]; - before.append(js_ast.Part{ - .stmts = jsx_part_stmts[0..stmt_i], - .declared_symbols = declared_symbols, - .import_record_indices = import_records, - .tag = .jsx_import, - }) catch unreachable; - } - } else if (p.options.features.jsx_optimization_inline) { - const react_element_symbol = p.symbols.items[p.react_element_type.ref.innerIndex()]; + declared_symbols.appendAssumeCapacity(.{ .ref = p.jsx_refresh_runtime.ref, .is_top_level = true }); - if (react_element_symbol.use_count_estimate > 0) { - var declared_symbols = try p.allocator.alloc(js_ast.DeclaredSymbol, 1); - var decls = try p.allocator.alloc(G.Decl, 1); - var part_stmts = try p.allocator.alloc(Stmt, 1); + const import_record_id = p.addImportRecord(.require, loc, p.options.jsx.refresh_runtime); + p.import_records.items[import_record_id].tag = .react_refresh; + jsx_part_stmts[stmt_i] = p.s(S.Import{ + .namespace_ref = p.jsx_refresh_runtime.ref, + .star_name_loc = loc, + .is_single_line = true, + .import_record_index = import_record_id, + }, loc); + + stmt_i += 1; + p.named_imports.put( + p.jsx_refresh_runtime.ref, + js_ast.NamedImport{ + .alias = refresh_runtime_symbol.original_name, + .alias_is_star = true, + .alias_loc = loc, + .namespace_ref = p.jsx_refresh_runtime.ref, + .import_record_index = import_record_id, + }, + ) catch unreachable; + p.is_import_item.put(p.allocator, p.jsx_refresh_runtime.ref, {}) catch unreachable; + import_records[import_record_i] = import_record_id; + } + p.recordUsage(p.jsx_refresh_runtime.ref); + } - declared_symbols[0] = .{ .ref = p.react_element_type.ref, .is_top_level = true }; - p.recordUsage(p.es6_symbol_global.ref); - var call_args = p.allocator.alloc(Expr, 1) catch unreachable; - call_args[0] = Expr{ .data = Prefill.Data.REACT_ELEMENT_TYPE, .loc = logger.Loc.Empty }; + jsx_part_stmts[stmt_i] = p.s(S.Local{ .kind = .k_var, .decls = decls[0..decl_i] }, loc); + stmt_i += 1; - decls[0] = G.Decl{ - .binding = p.b( - B.Identifier{ - .ref = p.react_element_type.ref, - }, - logger.Loc.Empty, - ), - .value = p.newExpr( - E.Call{ - // Symbol.for - .target = p.newExpr( - E.Dot{ - .name = "for", - .name_loc = logger.Loc.Empty, - .target = p.newExpr( - E.Identifier{ - .ref = p.es6_symbol_global.ref, - .can_be_removed_if_unused = true, - .call_can_be_unwrapped_if_unused = true, - }, - logger.Loc.Empty, - ), - .can_be_removed_if_unused = true, - .call_can_be_unwrapped_if_unused = true, - }, - logger.Loc.Empty, - ), - .args = ExprNodeList.init(call_args), - .close_paren_loc = logger.Loc.Empty, - .can_be_unwrapped_if_unused = true, - }, - logger.Loc.Empty, - ), - }; - part_stmts[0] = p.s(S.Local{ .kind = .k_var, .decls = decls }, logger.Loc.Empty); - before.append(js_ast.Part{ - .stmts = part_stmts, - .declared_symbols = declared_symbols, - .tag = .jsx_import, - }) catch unreachable; - } - } else { - const jsx_fragment_symbol: Symbol = p.symbols.items[p.jsx_fragment.ref.innerIndex()]; - const jsx_factory_symbol: Symbol = p.symbols.items[p.jsx_factory.ref.innerIndex()]; - - // inject - // var jsxFrag = - if (jsx_fragment_symbol.use_count_estimate + jsx_factory_symbol.use_count_estimate > 0) { - const total = @as(usize, @boolToInt(jsx_fragment_symbol.use_count_estimate > 0)) + @as(usize, @boolToInt(jsx_factory_symbol.use_count_estimate > 0)); - var declared_symbols = try std.ArrayList(js_ast.DeclaredSymbol).initCapacity(p.allocator, total); - var decls = try std.ArrayList(G.Decl).initCapacity(p.allocator, total); - var part_stmts = try p.allocator.alloc(Stmt, 1); - - if (jsx_fragment_symbol.use_count_estimate > 0) declared_symbols.appendAssumeCapacity(.{ .ref = p.jsx_fragment.ref, .is_top_level = true }); - if (jsx_factory_symbol.use_count_estimate > 0) declared_symbols.appendAssumeCapacity(.{ .ref = p.jsx_factory.ref, .is_top_level = true }); - - if (jsx_fragment_symbol.use_count_estimate > 0) - decls.appendAssumeCapacity(G.Decl{ + before.append(js_ast.Part{ + .stmts = jsx_part_stmts[0..stmt_i], + .declared_symbols = declared_symbols, + .import_record_indices = bun.BabyList(u32).init(import_records), + .tag = .jsx_import, + }) catch unreachable; + } + } else if (p.options.features.jsx_optimization_inline) { + const react_element_symbol = p.symbols.items[p.react_element_type.ref.innerIndex()]; + + if (react_element_symbol.use_count_estimate > 0) { + var declared_symbols = DeclaredSymbol.List{}; + try declared_symbols.ensureTotalCapacity(p.allocator, 1); + var decls = try p.allocator.alloc(G.Decl, 1); + var part_stmts = try p.allocator.alloc(Stmt, 1); + + declared_symbols.appendAssumeCapacity(.{ .ref = p.react_element_type.ref, .is_top_level = true }); + p.recordUsage(p.es6_symbol_global.ref); + var call_args = p.allocator.alloc(Expr, 1) catch unreachable; + call_args[0] = Expr{ .data = Prefill.Data.REACT_ELEMENT_TYPE, .loc = logger.Loc.Empty }; + + decls[0] = G.Decl{ .binding = p.b( B.Identifier{ - .ref = p.jsx_fragment.ref, + .ref = p.react_element_type.ref, }, logger.Loc.Empty, ), - .value = try p.jsxStringsToMemberExpression(logger.Loc.Empty, p.options.jsx.fragment), - }); - - if (jsx_factory_symbol.use_count_estimate > 0) - decls.appendAssumeCapacity(G.Decl{ - .binding = p.b( - B.Identifier{ - .ref = p.jsx_factory.ref, + .value = p.newExpr( + E.Call{ + // Symbol.for + .target = p.newExpr( + E.Dot{ + .name = "for", + .name_loc = logger.Loc.Empty, + .target = p.newExpr( + E.Identifier{ + .ref = p.es6_symbol_global.ref, + .can_be_removed_if_unused = true, + .call_can_be_unwrapped_if_unused = true, + }, + logger.Loc.Empty, + ), + .can_be_removed_if_unused = true, + .call_can_be_unwrapped_if_unused = true, + }, + logger.Loc.Empty, + ), + .args = ExprNodeList.init(call_args), + .close_paren_loc = logger.Loc.Empty, + .can_be_unwrapped_if_unused = true, }, logger.Loc.Empty, ), - .value = try p.jsxStringsToMemberExpression(logger.Loc.Empty, p.options.jsx.factory), - }); - part_stmts[0] = p.s(S.Local{ .kind = .k_var, .decls = decls.items }, logger.Loc.Empty); - before.append(js_ast.Part{ - .stmts = part_stmts, - .declared_symbols = declared_symbols.items, - .tag = .jsx_import, - }) catch unreachable; + }; + part_stmts[0] = p.s(S.Local{ .kind = .k_var, .decls = decls }, logger.Loc.Empty); + before.append(js_ast.Part{ + .stmts = part_stmts, + .declared_symbols = declared_symbols, + .tag = .jsx_import, + }) catch unreachable; + } + } else { + const jsx_fragment_symbol: Symbol = p.symbols.items[p.jsx_fragment.ref.innerIndex()]; + const jsx_factory_symbol: Symbol = p.symbols.items[p.jsx_factory.ref.innerIndex()]; + + // inject + // var jsxFrag = + if (jsx_fragment_symbol.use_count_estimate + jsx_factory_symbol.use_count_estimate > 0) { + const total = @as(usize, @boolToInt(jsx_fragment_symbol.use_count_estimate > 0)) + @as(usize, @boolToInt(jsx_factory_symbol.use_count_estimate > 0)); + var declared_symbols = DeclaredSymbol.List{}; + try declared_symbols.ensureTotalCapacity(p.allocator, total); + var decls = try std.ArrayList(G.Decl).initCapacity(p.allocator, total); + var part_stmts = try p.allocator.alloc(Stmt, 1); + + if (jsx_fragment_symbol.use_count_estimate > 0) declared_symbols.appendAssumeCapacity(.{ .ref = p.jsx_fragment.ref, .is_top_level = true }); + if (jsx_factory_symbol.use_count_estimate > 0) declared_symbols.appendAssumeCapacity(.{ .ref = p.jsx_factory.ref, .is_top_level = true }); + + if (jsx_fragment_symbol.use_count_estimate > 0) + decls.appendAssumeCapacity(G.Decl{ + .binding = p.b( + B.Identifier{ + .ref = p.jsx_fragment.ref, + }, + logger.Loc.Empty, + ), + .value = try p.jsxStringsToMemberExpression(logger.Loc.Empty, p.options.jsx.fragment), + }); + + if (jsx_factory_symbol.use_count_estimate > 0) + decls.appendAssumeCapacity(G.Decl{ + .binding = p.b( + B.Identifier{ + .ref = p.jsx_factory.ref, + }, + logger.Loc.Empty, + ), + .value = try p.jsxStringsToMemberExpression(logger.Loc.Empty, p.options.jsx.factory), + }); + part_stmts[0] = p.s(S.Local{ .kind = .k_var, .decls = decls.items }, logger.Loc.Empty); + before.append(js_ast.Part{ + .stmts = part_stmts, + .declared_symbols = declared_symbols, + .tag = .jsx_import, + }) catch unreachable; + } } - } - if (!did_import_fast_refresh and p.options.features.react_fast_refresh) { - p.resolveGeneratedSymbol(&p.jsx_refresh_runtime); - p.recordUsage(p.jsx_refresh_runtime.ref); + if (!did_import_fast_refresh and p.options.features.react_fast_refresh) { + p.resolveGeneratedSymbol(&p.jsx_refresh_runtime); + p.recordUsage(p.jsx_refresh_runtime.ref); - if (!p.options.jsx.use_embedded_refresh_runtime) { - if (comptime Environment.allow_assert) - assert(!p.options.enable_bundling); - var declared_symbols = try p.allocator.alloc(js_ast.DeclaredSymbol, 1); - const loc = logger.Loc.Empty; - const import_record_id = p.addImportRecord(.require, loc, p.options.jsx.refresh_runtime); - p.import_records.items[import_record_id].tag = .react_refresh; - - var import_stmt = p.s(S.Import{ - .namespace_ref = p.jsx_refresh_runtime.ref, - .star_name_loc = loc, - .is_single_line = true, - .import_record_index = import_record_id, - }, loc); - - const refresh_runtime_symbol: *const Symbol = &p.symbols.items[p.jsx_refresh_runtime.ref.innerIndex()]; + if (!p.options.jsx.use_embedded_refresh_runtime) { + if (comptime Environment.allow_assert) + assert(!p.options.enable_bundling); + var declared_symbols = DeclaredSymbol.List{}; + try declared_symbols.ensureTotalCapacity(p.allocator, 1); + const loc = logger.Loc.Empty; + const import_record_id = p.addImportRecord(.require, loc, p.options.jsx.refresh_runtime); + p.import_records.items[import_record_id].tag = .react_refresh; - p.named_imports.put( - p.jsx_refresh_runtime.ref, - js_ast.NamedImport{ - .alias = refresh_runtime_symbol.original_name, - .alias_is_star = true, - .alias_loc = loc, + var import_stmt = p.s(S.Import{ .namespace_ref = p.jsx_refresh_runtime.ref, + .star_name_loc = loc, + .is_single_line = true, .import_record_index = import_record_id, - }, - ) catch unreachable; - p.is_import_item.put(p.allocator, p.jsx_refresh_runtime.ref, {}) catch unreachable; - var import_records = try p.allocator.alloc(@TypeOf(import_record_id), 1); - import_records[0] = import_record_id; - declared_symbols[0] = .{ .ref = p.jsx_refresh_runtime.ref, .is_top_level = true }; - var part_stmts = try p.allocator.alloc(Stmt, 1); - part_stmts[0] = import_stmt; - - before.append(js_ast.Part{ - .stmts = part_stmts, - .declared_symbols = declared_symbols, - .import_record_indices = import_records, - .tag = .react_fast_refresh, - }) catch unreachable; - } - } - }, - else => {}, - } + }, loc); - if (p.options.enable_bundling) p.resolveBundlingSymbols(); + const refresh_runtime_symbol: *const Symbol = &p.symbols.items[p.jsx_refresh_runtime.ref.innerIndex()]; + p.named_imports.put( + p.jsx_refresh_runtime.ref, + js_ast.NamedImport{ + .alias = refresh_runtime_symbol.original_name, + .alias_is_star = true, + .alias_loc = loc, + .namespace_ref = p.jsx_refresh_runtime.ref, + .import_record_index = import_record_id, + }, + ) catch unreachable; + p.is_import_item.put(p.allocator, p.jsx_refresh_runtime.ref, {}) catch unreachable; + var import_records = try p.allocator.alloc(@TypeOf(import_record_id), 1); + import_records[0] = import_record_id; + declared_symbols.appendAssumeCapacity(.{ .ref = p.jsx_refresh_runtime.ref, .is_top_level = true }); + var part_stmts = try p.allocator.alloc(Stmt, 1); + part_stmts[0] = import_stmt; + + before.append(js_ast.Part{ + .stmts = part_stmts, + .declared_symbols = declared_symbols, + .import_record_indices = bun.BabyList(u32).init(import_records), + .tag = .react_fast_refresh, + }) catch unreachable; + } + } + }, + else => {}, + } + + if (p.options.enable_bundling) p.resolveBundlingSymbols(); + } var runtime_imports_iter = p.runtime_imports.iter(); const has_cjs_imports = p.cjs_import_stmts.items.len > 0 and p.options.transform_require_to_import; @@ -3276,16 +3277,17 @@ pub const Parser = struct { } if (has_cjs_imports) { - var import_records = try p.allocator.alloc(u32, p.cjs_import_stmts.items.len); - var declared_symbols = try p.allocator.alloc(js_ast.DeclaredSymbol, p.cjs_import_stmts.items.len); + var import_records = try bun.BabyList(u32).initCapacity(p.allocator, p.cjs_import_stmts.items.len); + var declared_symbols = DeclaredSymbol.List{}; + try declared_symbols.ensureTotalCapacity(p.allocator, p.cjs_import_stmts.items.len); - for (p.cjs_import_stmts.items) |entry, i| { + for (p.cjs_import_stmts.items) |entry| { const import_statement: *S.Import = entry.data.s_import; - import_records[i] = import_statement.import_record_index; - declared_symbols[i] = .{ + import_records.appendAssumeCapacity(import_statement.import_record_index); + declared_symbols.appendAssumeCapacity(.{ .ref = import_statement.namespace_ref, .is_top_level = true, - }; + }); } before.append(js_ast.Part{ @@ -3677,8 +3679,8 @@ fn NewParser_( injected_define_symbols: List(Ref) = .{}, symbol_uses: js_ast.Part.SymbolUseMap = .{}, - declared_symbols: List(js_ast.DeclaredSymbol) = .{}, - declared_symbols_for_reuse: List(js_ast.DeclaredSymbol) = .{}, + declared_symbols: DeclaredSymbol.List = .{}, + declared_symbols_for_reuse: DeclaredSymbol.List = .{}, runtime_imports: RuntimeImports = RuntimeImports{}, parse_pass_symbol_uses: ParsePassSymbolUsageType = undefined, @@ -4216,9 +4218,9 @@ fn NewParser_( for (symbol_use_refs) |ref, i| { symbols[ref.innerIndex()].use_count_estimate -|= symbol_use_values[i].count_estimate; } - - for (part.declared_symbols) |declared| { - symbols[declared.ref.innerIndex()].use_count_estimate = 0; + const declared_refs = part.declared_symbols.items(.ref); + for (declared_refs) |declared| { + symbols[declared.innerIndex()].use_count_estimate = 0; // } } } @@ -4581,7 +4583,8 @@ fn NewParser_( var namespace_identifier = try allocator.alloc(u8, import_path_identifier.len + suffix.len); var clause_items = try allocator.alloc(js_ast.ClauseItem, imports.len); var stmts = try allocator.alloc(Stmt, 1 + if (additional_stmt != null) @as(usize, 1) else @as(usize, 0)); - var declared_symbols = try allocator.alloc(js_ast.DeclaredSymbol, imports.len); + var declared_symbols = DeclaredSymbol.List{}; + try declared_symbols.ensureTotalCapacity(allocator, imports.len); std.mem.copy(u8, namespace_identifier[0..suffix.len], suffix); std.mem.copy( u8, @@ -4590,7 +4593,7 @@ fn NewParser_( ); const namespace_ref = try p.newSymbol(.other, namespace_identifier); - try p.module_scope.generated.append(allocator, namespace_ref); + try p.module_scope.generated.push(allocator, namespace_ref); for (imports) |alias, i| { const ref = symbols.get(alias) orelse unreachable; const alias_name = if (@TypeOf(symbols) == RuntimeImports) RuntimeImports.all[alias] else alias; @@ -4600,7 +4603,7 @@ fn NewParser_( .alias_loc = logger.Loc{}, .name = LocRef{ .ref = ref, .loc = logger.Loc{} }, }; - declared_symbols[i] = js_ast.DeclaredSymbol{ .ref = ref, .is_top_level = true }; + declared_symbols.appendAssumeCapacity(DeclaredSymbol{ .ref = ref, .is_top_level = true }); try p.is_import_item.put(allocator, ref, {}); try p.named_imports.put(ref, js_ast.NamedImport{ .alias = alias_name, @@ -4627,7 +4630,7 @@ fn NewParser_( parts.append(js_ast.Part{ .stmts = stmts, .declared_symbols = declared_symbols, - .import_record_indices = import_records, + .import_record_indices = bun.BabyList(u32).init(import_records), .tag = .runtime, }) catch unreachable; } @@ -5455,8 +5458,8 @@ fn NewParser_( } } - for (scope.children.items) |_, i| { - p.hoistSymbols(scope.children.items[i]); + for (scope.children.slice()) |_, i| { + p.hoistSymbols(scope.children.mut(i).*); } } @@ -5497,7 +5500,7 @@ fn NewParser_( .generated = .{}, }; - try parent.children.append(allocator, scope); + try parent.children.push(allocator, scope); scope.strict_mode = parent.strict_mode; p.current_scope = scope; @@ -5838,8 +5841,8 @@ fn NewParser_( var children = parent.children; // Remove the last child from the parent scope - var last = children.items.len - 1; - if (children.items[last] != to_discard) { + var last = children.len - 1; + if (children.slice()[last] != to_discard) { p.panic("Internal error", .{}); } @@ -6678,7 +6681,7 @@ fn NewParser_( const name = try path_name.nonUniqueNameString(p.allocator); stmt.namespace_ref = try p.newSymbol(.other, name); var scope: *Scope = p.current_scope; - try scope.generated.append(p.allocator, stmt.namespace_ref); + try scope.generated.push(p.allocator, stmt.namespace_ref); } var item_refs = ImportItemForNamespaceMap.init(p.allocator); @@ -6842,13 +6845,13 @@ fn NewParser_( var scope = p.current_scope; - try scope.generated.append(p.allocator, name.ref.?); + try scope.generated.push(p.allocator, name.ref.?); return name; } pub fn newSymbol(p: *P, kind: Symbol.Kind, identifier: string) !Ref { - const inner_index = Ref.toInt(p.symbols.items.len); + const inner_index = @truncate(Ref.Int, p.symbols.items.len); try p.symbols.append(Symbol{ .kind = kind, .original_name = identifier, @@ -6858,7 +6861,11 @@ fn NewParser_( try p.ts_use_counts.append(p.allocator, 0); } - return Ref.init(inner_index, Ref.toInt(p.source.index), false); + return Ref{ + .inner_index = inner_index, + .source_index = Ref.toInt(p.source.index.get()), + .tag = .symbol, + }; } fn parseLabelName(p: *P) !?js_ast.LocRef { @@ -7319,7 +7326,7 @@ fn NewParser_( if (comptime track_symbol_usage_during_parse_pass) { // In the scan pass, we need _some_ way of knowing *not* to mark as unused - p.import_records.items[import_record_index].calls_run_time_re_export_fn = true; + p.import_records.items[import_record_index].calls_runtime_re_export_fn = true; } try p.lexer.expectOrInsertSemicolon(); @@ -7358,7 +7365,7 @@ fn NewParser_( if (comptime track_symbol_usage_during_parse_pass) { // In the scan pass, we need _some_ way of knowing *not* to mark as unused - p.import_records.items[import_record_index].calls_run_time_re_export_fn = true; + p.import_records.items[import_record_index].calls_runtime_re_export_fn = true; } return p.s(S.ExportFrom{ .items = export_clause.clauses, .is_single_line = export_clause.is_single_line, .namespace_ref = namespace_ref, .import_record_index = import_record_index }, loc); @@ -8257,10 +8264,12 @@ fn NewParser_( const child = _child orelse continue; if (child.scope.parent == p.current_scope) { - var i: usize = children.items.len - 1; + var i: usize = children.len - 1; while (i >= 0) { - if (children.items[i] == child.scope) { - _ = children.orderedRemove(i); + if (children.mut(i).* == child.scope) { + var list = children.listManaged(p.allocator); + _ = list.orderedRemove(i); + children.update(list); break; } i -= 1; @@ -8394,7 +8403,7 @@ fn NewParser_( // run the renamer. For external-facing things the renamer will avoid // collisions automatically so this isn't important for correctness. arg_ref = p.newSymbol(.hoisted, strings.cat(p.allocator, "_", name_text) catch unreachable) catch unreachable; - p.current_scope.generated.append(p.allocator, arg_ref.?) catch unreachable; + p.current_scope.generated.push(p.allocator, arg_ref.?) catch unreachable; } else { arg_ref = p.newSymbol(.hoisted, name_text) catch unreachable; } @@ -9129,7 +9138,7 @@ fn NewParser_( // run the renamer. For external-facing things the renamer will avoid // collisions automatically so this isn't important for correctness. arg_ref = p.newSymbol(.hoisted, strings.cat(p.allocator, "_", name_text) catch unreachable) catch unreachable; - p.current_scope.generated.append(p.allocator, arg_ref) catch unreachable; + p.current_scope.generated.push(p.allocator, arg_ref) catch unreachable; } else { arg_ref = p.declareSymbol(.hoisted, name_loc, name_text) catch unreachable; } @@ -9566,7 +9575,7 @@ fn NewParser_( // this module will be unable to reference this symbol. However, we must // still add the symbol to the scope so it gets minified (automatically- // generated code may still reference the symbol). - try p.module_scope.generated.append(p.allocator, ref); + try p.module_scope.generated.push(p.allocator, ref); return ref; } @@ -9659,7 +9668,7 @@ fn NewParser_( entry.key_ptr.* = name; entry.value_ptr.* = js_ast.Scope.Member{ .ref = ref, .loc = loc }; if (comptime is_generated) { - try p.module_scope.generated.append(p.allocator, ref); + try p.module_scope.generated.push(p.allocator, ref); } return ref; } @@ -9848,24 +9857,21 @@ fn NewParser_( if (@ptrToInt(p.source.contents.ptr) <= @ptrToInt(name.ptr) and (@ptrToInt(name.ptr) + name.len) <= (@ptrToInt(p.source.contents.ptr) + p.source.contents.len)) { const start = Ref.toInt(@ptrToInt(name.ptr) - @ptrToInt(p.source.contents.ptr)); const end = Ref.toInt(name.len); - return Ref.initSourceEnd(.{ .source_index = start, .inner_index = end, .is_source_contents_slice = true }); + return Ref.initSourceEnd(.{ .source_index = start, .inner_index = end, .tag = .source_contents_slice }); } else { const inner_index = Ref.toInt(p.allocated_names.items.len); try p.allocated_names.append(p.allocator, name); - return Ref.initSourceEnd(.{ .source_index = std.math.maxInt(Ref.Int), .inner_index = inner_index, .is_source_contents_slice = false }); + return Ref.init(inner_index, p.source.index.get(), false); } } pub fn loadNameFromRef(p: *P, ref: Ref) string { - if (ref.isSourceContentsSlice()) { - return p.source.contents[ref.sourceIndex() .. ref.sourceIndex() + ref.innerIndex()]; - } else if (ref.sourceIndex() == std.math.maxInt(Ref.Int)) { - if (comptime Environment.allow_assert) - assert(ref.innerIndex() < p.allocated_names.items.len); - return p.allocated_names.items[ref.innerIndex()]; - } else { - return p.symbols.items[ref.innerIndex()].original_name; - } + return switch (ref.tag) { + .symbol => p.symbols.items[ref.innerIndex()].original_name, + .source_contents_slice => p.source.contents[ref.sourceIndex() .. ref.sourceIndex() + ref.innerIndex()], + .allocated_name => p.allocated_names.items[ref.innerIndex()], + else => @panic("Internal error: JS parser tried to load an invalid name from a Ref"), + }; } // This parses an expression. This assumes we've already parsed the "async" @@ -12850,16 +12856,18 @@ fn NewParser_( for (previous_parts) |*previous_part, j| { if (previous_part.stmts.len == 0) continue; - const declared_symbols = previous_part.declared_symbols; + const declared_symbols = previous_part.declared_symbols.slice(); + var refs = declared_symbols.items(.ref); - for (declared_symbols) |decl| { - if (p.symbol_uses.contains(decl.ref)) { + for (refs) |ref| { + if (p.symbol_uses.contains(ref)) { // we move this part to our other file for (previous_parts[0..j]) |*this_part| { if (this_part.stmts.len == 0) continue; const this_declared_symbols = this_part.declared_symbols; - for (this_declared_symbols) |this_decl| { - if (previous_part.symbol_uses.contains(this_decl.ref)) { + var other_refs = this_declared_symbols.items(.ref); + for (other_refs) |other_ref| { + if (previous_part.symbol_uses.contains(other_ref)) { try p.bun_plugin.hoisted_stmts.appendSlice(p.allocator, this_part.stmts); this_part.stmts = &.{}; break; @@ -12876,8 +12884,10 @@ fn NewParser_( // Single-statement part which uses Bun.plugin() // It's effectively an unrelated file - if (p.declared_symbols.items.len > 0 or p.symbol_uses.count() > 0) { - p.clearSymbolUsagesFromDeadPart(.{ .stmts = undefined, .declared_symbols = p.declared_symbols.items, .symbol_uses = p.symbol_uses }); + if (p.declared_symbols.len > 0 or p.symbol_uses.count() > 0) { + p.clearSymbolUsagesFromDeadPart(.{ .stmts = undefined, .declared_symbols = p.declared_symbols, .symbol_uses = p.symbol_uses }); + p.declared_symbols.len = 0; + p.import_records_for_current_part.items.len = 0; } return; } @@ -12887,19 +12897,23 @@ fn NewParser_( try parts.append(js_ast.Part{ .stmts = _stmts, .symbol_uses = p.symbol_uses, - .declared_symbols = try p.declared_symbols.toOwnedSlice( - p.allocator, - ), - .import_record_indices = try p.import_records_for_current_part.toOwnedSlice( - p.allocator, + .declared_symbols = p.declared_symbols.clone(p.allocator) catch unreachable, + .import_record_indices = bun.BabyList(u32).init( + p.import_records_for_current_part.toOwnedSlice( + p.allocator, + ) catch unreachable, ), .scopes = try p.scopes_for_current_part.toOwnedSlice(p.allocator), .can_be_removed_if_unused = p.stmtsCanBeRemovedIfUnused(_stmts), }); p.symbol_uses = .{}; - } else if (p.declared_symbols.items.len > 0 or p.symbol_uses.count() > 0) { + p.declared_symbols.len = 0; + } else if (p.declared_symbols.len > 0 or p.symbol_uses.count() > 0) { + // if the part is dead, invalidate all the usage counts - p.clearSymbolUsagesFromDeadPart(.{ .stmts = undefined, .declared_symbols = p.declared_symbols.items, .symbol_uses = p.symbol_uses }); + p.clearSymbolUsagesFromDeadPart(.{ .stmts = undefined, .declared_symbols = p.declared_symbols, .symbol_uses = p.symbol_uses }); + p.declared_symbols.len = 0; + p.import_records_for_current_part.clearRetainingCapacity(); } } @@ -13054,7 +13068,7 @@ fn NewParser_( } fn recordDeclaredSymbol(p: *P, ref: Ref) !void { - try p.declared_symbols.append(p.allocator, js_ast.DeclaredSymbol{ + try p.declared_symbols.append(p.allocator, DeclaredSymbol{ .ref = ref, .is_top_level = p.current_scope == p.module_scope, }); @@ -13266,7 +13280,7 @@ fn NewParser_( } } - if (!p.options.enable_bundling and p.options.features.dynamic_require) { + if (!p.options.bundle and p.options.features.dynamic_require) { const is_call_target = @as(Expr.Tag, p.call_target) == .e_identifier and expr.data.e_identifier.ref.eql(p.call_target.e_identifier.ref); if (!is_call_target and p.require_ref.eql(e_.ref)) { // Substitute "require" for import.meta.require @@ -15672,7 +15686,7 @@ fn NewParser_( const name = p.loadNameFromRef(data.namespace_ref); data.namespace_ref = try p.newSymbol(.other, name); - try p.current_scope.generated.append(p.allocator, data.namespace_ref); + try p.current_scope.generated.push(p.allocator, data.namespace_ref); try p.recordDeclaredSymbol(data.namespace_ref); if (p.options.features.replace_exports.count() > 0) { @@ -15692,7 +15706,7 @@ fn NewParser_( const _name = p.loadNameFromRef(old_ref); const ref = try p.newSymbol(.other, _name); - try p.current_scope.generated.append(p.allocator, data.namespace_ref); + try p.current_scope.generated.push(p.allocator, data.namespace_ref); try p.recordDeclaredSymbol(data.namespace_ref); data.items[j] = item; data.items[j].name.ref = ref; @@ -15710,7 +15724,7 @@ fn NewParser_( for (data.items) |*item| { const _name = p.loadNameFromRef(item.name.ref.?); const ref = try p.newSymbol(.other, _name); - try p.current_scope.generated.append(p.allocator, data.namespace_ref); + try p.current_scope.generated.push(p.allocator, data.namespace_ref); try p.recordDeclaredSymbol(data.namespace_ref); item.name.ref = ref; } @@ -15721,7 +15735,7 @@ fn NewParser_( // "export * from 'path'" const name = p.loadNameFromRef(data.namespace_ref); data.namespace_ref = try p.newSymbol(.other, name); - try p.current_scope.generated.append(p.allocator, data.namespace_ref); + try p.current_scope.generated.push(p.allocator, data.namespace_ref); try p.recordDeclaredSymbol(data.namespace_ref); // "export * as ns from 'path'" @@ -17167,7 +17181,7 @@ fn NewParser_( if (class.extends != null) { const target = p.newExpr(E.Super{}, stmt.loc); const arguments_ref = p.newSymbol(.unbound, "arguments") catch unreachable; - p.current_scope.generated.append(p.allocator, arguments_ref) catch unreachable; + p.current_scope.generated.push(p.allocator, arguments_ref) catch unreachable; const super = p.newExpr(E.Spread{ .value = p.newExpr(E.Identifier{ .ref = arguments_ref }, stmt.loc) }, stmt.loc); const args = ExprNodeList.one(p.allocator, super) catch unreachable; @@ -17753,7 +17767,7 @@ fn NewParser_( // (e.g. there was a static property initializer that referenced "this"), // store our generated name so the class expression ends up with a name. class.class_name = LocRef{ .loc = name_scope_loc, .ref = class_name_ref }; - p.current_scope.generated.append(p.allocator, class_name_ref) catch unreachable; + p.current_scope.generated.push(p.allocator, class_name_ref) catch unreachable; p.recordDeclaredSymbol(class_name_ref) catch unreachable; } } @@ -17795,7 +17809,7 @@ fn NewParser_( break :brk generated_symbol.ref; }; - p.module_scope.generated.append(p.allocator, ref) catch unreachable; + p.module_scope.generated.push(p.allocator, ref) catch unreachable; } else { ref = p.runtime_imports.at(name).?; } @@ -18285,13 +18299,13 @@ fn NewParser_( // by the time we get here. p.scopes_in_order.items[scope_index] = null; // Remove the last child from the parent scope - const last = parent.children.items.len - 1; - if (comptime Environment.allow_assert) assert(parent.children.items[last] == to_flatten); + const last = parent.children.len - 1; + if (comptime Environment.allow_assert) assert(parent.children.ptr[last] == to_flatten); _ = parent.children.popOrNull(); - for (to_flatten.children.items) |item| { + for (to_flatten.children.slice()) |item| { item.parent = parent; - parent.children.append(p.allocator, item) catch unreachable; + parent.children.push(p.allocator, item) catch unreachable; } } @@ -18330,19 +18344,31 @@ fn NewParser_( var result = try ImportScanner.scan(P, p, part.stmts, commonjs_wrapper_expr != null); kept_import_equals = kept_import_equals or result.kept_import_equals; removed_import_equals = removed_import_equals or result.removed_import_equals; - part.import_record_indices = part.import_record_indices; - part.declared_symbols = try p.declared_symbols.toOwnedSlice(allocator); + part.stmts = result.stmts; - if (part.stmts.len > 0) { + if (part.stmts.len > 0 or (i == js_ast.namespace_export_part_index and p.options.bundle)) { if (p.module_scope.contains_direct_eval and part.declared_symbols.len > 0) { // If this file contains a direct call to "eval()", all parts that // declare top-level symbols must be kept since the eval'd code may // reference those symbols. part.can_be_removed_if_unused = false; } + if (part.declared_symbols.len == 0) { + part.declared_symbols = p.declared_symbols.clone(p.allocator) catch unreachable; + } else { + part.declared_symbols.appendList(p.allocator, p.declared_symbols) catch unreachable; + } + + if (part.import_record_indices.len == 0) { + part.import_record_indices.update(p.import_records_for_current_part.clone(p.allocator) catch unreachable); + } else { + part.import_record_indices.append(p.allocator, p.import_records_for_current_part.items) catch unreachable; + } + parts[parts_end] = part; parts_end += 1; } + p.declared_symbols.len = 0; } // We need to iterate multiple times if an import-equals statement was @@ -18860,57 +18886,66 @@ fn NewParser_( part.stmts = _stmts[0 .. imports_list.len + toplevel_stmts.len + exports_from.len]; } else if (p.options.features.hot_module_reloading) {} - - { + var top_level_symbols_to_parts = js_ast.Ast.TopLevelSymbolToParts{}; + if (p.options.bundle) { // Each part tracks the other parts it depends on within this file - // var local_dependencies = AutoHashMap(u32, u32).init(p.allocator); - - // while (i < parts.len) : (i += 1) { - // const part = parts[i]; - // if (part.symbol_uses.count() > 0) { - // var iter = part.symbol_uses.iterator(); - // var dependencies = List(js_ast.Dependency).init(p.allocator); - // while (iter.next()) |entry| { - // const ref = entry.key; - - // if (p.top_level_symbol_to_parts.get(ref)) |tlstp| { - // for (tlstp.items) |other_part_index| { - // if (!local_dependencies.contains(other_part_index) or other_part_index != i) { - // try local_dependencies.put(other_part_index, @intCast(u32, i)); - // try dependencies.append(js_ast.Dependency{ - // .source_index = p.source.index, - // .part_index = other_part_index, - // }); - // } - // } - // } - - // // Also map from imports to parts that use them - // // TODO: will appending to this list like this be a perf issue? - // if (p.named_imports.getEntry(ref)) |named_import_entry| { - // const named_import = named_import_entry.value; - // var buf = try p.allocator.alloc(u32, named_import.local_parts_with_uses.len + 1); - // if (named_import.local_parts_with_uses.len > 0) { - // std.mem.copy(u32, buf, named_import.local_parts_with_uses); - // } - // buf[buf.len - 1] = @intCast(u32, i); - // named_import_entry.value.local_parts_with_uses = buf; - // } - // } - // } - // } + var i: u32 = 0; + const count = @truncate(u32, parts.len); + while (i < count) : (i += 1) { + var decls = &parts[i].declared_symbols; + var ctx_ = .{ + .allocator = p.allocator, + .top_level_symbols_to_parts = &top_level_symbols_to_parts, + .symbols = p.symbols.items, + .part_index = i, + }; + const Ctx = @TypeOf(ctx_); + const Iterator = struct { + pub fn next(ctx: Ctx, input: Ref) void { + // If this symbol was merged, use the symbol at the end of the + // linked list in the map. This is the case for multiple "var" + // declarations with the same name, for example. + var ref = input; + while (ctx.symbols[ref.innerIndex()].link.isValid()) { + ref = ctx.symbols[ref.innerIndex()].link; + } + + var entry = ctx.top_level_symbols_to_parts.getOrPut(ctx.allocator, ref) catch unreachable; + if (!entry.found_existing) { + entry.value_ptr.* = .{}; + } + + entry.value_ptr.push(ctx.allocator, @truncate(u32, ctx.part_index)) catch unreachable; + } + }; + + DeclaredSymbol.forEachTopLevelSymbol(&decls, ctx_, Iterator.next); + } + + // Pulling in the exports of this module always pulls in the export part + + { + var entry = top_level_symbols_to_parts.getOrPut(p.allocator, p.exports_ref) catch unreachable; + + if (!entry.found_existing) { + entry.value_ptr.* = .{}; + } + + entry.value_ptr.push(p.allocator, js_ast.namespace_export_part_index) catch unreachable; + } } return .{ + .allocator = p.allocator, .runtime_imports = p.runtime_imports, - .parts = parts, + .parts = bun.BabyList(js_ast.Part).init(parts), .module_scope = p.module_scope.*, - .symbols = p.symbols.items, + .symbols = js_ast.Symbol.List.init(p.symbols.items), .exports_ref = p.exports_ref, - .wrapper_ref = null, + .wrapper_ref = Ref.None, .module_ref = p.module_ref, - .import_records = p.import_records.items, + .import_records = ImportRecord.List.init(p.import_records.items), .export_star_import_records = p.export_star_import_records.items, .approximate_newline_count = p.lexer.approximate_newline_count, .exports_kind = exports_kind, @@ -18918,6 +18953,7 @@ fn NewParser_( .named_exports = p.named_exports, .import_keyword = p.es6_import_keyword, .export_keyword = p.es6_export_keyword, + .top_level_symbols_to_parts = top_level_symbols_to_parts, .require_ref = if (p.runtime_imports.__require != null) p.runtime_imports.__require.?.ref else @@ -19020,22 +19056,30 @@ fn NewParser_( // Range (min … max): 24.1 ms … 39.7 ms 500 runs // '../../build/macos-x86_64/bun node_modules/react-dom/cjs/react-dom.development.js --resolve=disable' ran // 1.02 ± 0.07 times faster than '../../bun.before-comptime-js-parser node_modules/react-dom/cjs/react-dom.development.js --resolve=disable' -const JavaScriptParser = NewParser(.{}); -const JSXParser = NewParser(.{ .jsx = .react }); +const JavaScriptParser = if (bun.fast_debug_build_mode) + TSXParser +else + NewParser(.{}); +const JSXParser = if (bun.fast_debug_build_mode) + TSXParser +else + NewParser(.{ .jsx = .react }); const TSXParser = NewParser(.{ .jsx = .react, .typescript = true }); const TypeScriptParser = NewParser(.{ .typescript = true }); -const JSParserMacro = NewParser(.{ - .jsx = .macro, -}); +const JSParserMacro = if (bun.fast_debug_build_mode) + TSParserMacro +else + NewParser(.{ + .jsx = .macro, + }); const TSParserMacro = NewParser(.{ .jsx = .macro, .typescript = true, }); - -const JavaScriptImportScanner = NewParser(.{ .scan_only = true }); -const JSXImportScanner = NewParser(.{ .jsx = .react, .scan_only = true }); +const JavaScriptImportScanner = if (bun.fast_debug_build_mode) TSXImportScanner else NewParser(.{ .scan_only = true }); +const JSXImportScanner = if (bun.fast_debug_build_mode) TSXImportScanner else NewParser(.{ .jsx = .react, .scan_only = true }); const TSXImportScanner = NewParser(.{ .jsx = .react, .typescript = true, .scan_only = true }); -const TypeScriptImportScanner = NewParser(.{ .typescript = true, .scan_only = true }); +const TypeScriptImportScanner = if (bun.fast_debug_build_mode) TSXImportScanner else NewParser(.{ .typescript = true, .scan_only = true }); // The "await" and "yield" expressions are never allowed in argument lists but // may or may not be allowed otherwise depending on the details of the enclosing diff --git a/src/js_printer.zig b/src/js_printer.zig index 48e4c626004e..c2a1a44df7bc 100644 --- a/src/js_printer.zig +++ b/src/js_printer.zig @@ -4800,7 +4800,7 @@ pub fn NewPrinter( } return Printer{ - .import_records = tree.import_records, + .import_records = tree.import_records.slice(), .options = opts, .symbols = symbols, .writer = writer, @@ -5252,7 +5252,7 @@ pub fn printAst( } } - for (tree.parts) |part| { + for (tree.parts.slice()) |part| { for (part.stmts) |stmt| { printer.printSemicolonIfNeeded(); try printer.printStmt(stmt); @@ -5295,7 +5295,7 @@ pub fn printJSON( writer, &ast, source, - std.mem.zeroes(Symbol.Map), + .{}, .{}, null, allocator, @@ -5346,7 +5346,7 @@ pub fn printCommonJS( } } } - for (tree.parts) |part| { + for (tree.parts.slice()) |part| { for (part.stmts) |stmt| { printer.printSemicolonIfNeeded(); try printer.printStmt(stmt); @@ -5417,7 +5417,7 @@ pub fn printCommonJSThreaded( } } - for (tree.parts) |part| { + for (tree.parts.slice()) |part| { for (part.stmts) |stmt| { printer.printSemicolonIfNeeded(); try printer.printStmt(stmt); diff --git a/src/json_parser.zig b/src/json_parser.zig index b420cbd43b1d..02db43c31460 100644 --- a/src/json_parser.zig +++ b/src/json_parser.zig @@ -4,7 +4,7 @@ const js_lexer = bun.js_lexer; const importRecord = @import("import_record.zig"); const js_ast = bun.JSAst; const options = @import("options.zig"); - +const BabyList = @import("./baby_list.zig").BabyList; const fs = @import("fs.zig"); const bun = @import("bun"); const string = bun.string; @@ -605,7 +605,7 @@ pub fn toAST( return Expr.init( js_ast.E.Object, js_ast.E.Object{ - .properties = js_ast.BabyList(G.Property).init(properties[0..property_i]), + .properties = BabyList(G.Property).init(properties[0..property_i]), .is_single_line = property_i <= 1, }, logger.Loc.Empty, @@ -671,8 +671,8 @@ pub fn toAST( } } -const JSONParser = JSONLikeParser(js_lexer.JSONOptions{ .is_json = true }); -const RemoteJSONParser = JSONLikeParser(js_lexer.JSONOptions{ .is_json = true, .json_warn_duplicate_keys = false }); +const JSONParser = if (bun.fast_debug_build_mode) TSConfigParser else JSONLikeParser(js_lexer.JSONOptions{ .is_json = true }); +const RemoteJSONParser = if (bun.fast_debug_build_mode) TSConfigParser else JSONLikeParser(js_lexer.JSONOptions{ .is_json = true, .json_warn_duplicate_keys = false }); const DotEnvJSONParser = JSONLikeParser(js_lexer.JSONOptions{ .ignore_leading_escape_sequences = true, .ignore_trailing_escape_sequences = true, diff --git a/src/linker.zig b/src/linker.zig index 8e4daf5b9d14..f9dc27bcae1a 100644 --- a/src/linker.zig +++ b/src/linker.zig @@ -223,18 +223,18 @@ pub const Linker = struct { const is_deferred = result.pending_imports.len > 0; - var import_records = result.ast.import_records; + var import_records = result.ast.import_records.listManaged(linker.allocator); defer { - result.ast.import_records = import_records; + result.ast.import_records = ImportRecord.List.fromList(import_records); } // Step 1. Resolve imports & requires switch (result.loader) { .jsx, .js, .ts, .tsx => { var record_i: u32 = 0; - const record_count = @truncate(u32, import_records.len); + const record_count = @truncate(u32, import_records.items.len); outer: while (record_i < record_count) : (record_i += 1) { - var import_record = &import_records[record_i]; + var import_record = &import_records.items[record_i]; if (import_record.is_unused or (is_bun and is_deferred and !result.isPendingImport(record_i))) continue; @@ -373,7 +373,6 @@ pub const Linker = struct { if (node_modules_bundle.getPackage(package_name)) |pkg| { const import_path = text[@min(text.len, package_name.len + 1)..]; if (node_modules_bundle.findModuleIDInPackageIgnoringExtension(pkg, import_path)) |found_module| { - import_record.is_bundled = true; node_module_bundle_import_path = node_module_bundle_import_path orelse linker.nodeModuleBundleImportPath(origin); @@ -401,7 +400,6 @@ pub const Linker = struct { if (node_modules_bundle.getPackage(package_name)) |pkg| { const import_path = runtime[@min(runtime.len, package_name.len + 1)..]; if (node_modules_bundle.findModuleInPackage(pkg, import_path)) |found_module| { - import_record.is_bundled = true; node_module_bundle_import_path = node_module_bundle_import_path orelse linker.nodeModuleBundleImportPath(origin); @@ -531,7 +529,6 @@ pub const Linker = struct { ); } - import_record.is_bundled = true; node_module_bundle_import_path = node_module_bundle_import_path orelse linker.nodeModuleBundleImportPath(origin); import_record.path.text = node_module_bundle_import_path.?; @@ -800,9 +797,9 @@ pub const Linker = struct { if (had_resolve_errors) return error.ResolveError; result.ast.externals = try externals.toOwnedSlice(); - if (result.ast.needs_runtime and (result.ast.runtime_import_record_id == null or import_records.len == 0)) { - var new_import_records = try linker.allocator.alloc(ImportRecord, import_records.len + 1); - std.mem.copy(ImportRecord, new_import_records, import_records); + if (result.ast.needs_runtime and (result.ast.runtime_import_record_id == null or import_records.items.len == 0)) { + var new_import_records = try linker.allocator.alloc(ImportRecord, import_records.items.len + 1); + std.mem.copy(ImportRecord, new_import_records, import_records.items); new_import_records[new_import_records.len - 1] = ImportRecord{ .kind = .stmt, @@ -815,8 +812,7 @@ pub const Linker = struct { .range = logger.Range{ .loc = logger.Loc{ .start = 0 }, .len = 0 }, }; - result.ast.runtime_import_record_id = @truncate(u32, new_import_records.len - 1); - import_records = new_import_records; + result.ast.runtime_import_record_id = @truncate(u32, import_records.items.len - 1); } // We _assume_ you're importing ESM. diff --git a/src/logger.zig b/src/logger.zig index bd2c81c31553..d0468ffb5587 100644 --- a/src/logger.zig +++ b/src/logger.zig @@ -15,12 +15,12 @@ const C = bun.C; const JSC = @import("bun").JSC; const fs = @import("fs.zig"); const unicode = std.unicode; - +const Ref = @import("./ast/base.zig").Ref; const expect = std.testing.expect; const assert = std.debug.assert; const ArrayList = std.ArrayList; const StringBuilder = @import("./string_builder.zig"); - +const Index = @import("./ast/base.zig").Index; pub const Kind = enum(i8) { err, warn, @@ -582,6 +582,10 @@ pub const Log = struct { msgs: ArrayList(Msg), level: Level = if (Environment.isDebug) Level.info else Level.warn, + pub inline fn hasErrors(this: *const Log) bool { + return this.errors > 0; + } + pub fn reset(this: *Log) void { this.msgs.clearRetainingCapacity(); this.warnings = 0; @@ -1140,10 +1144,36 @@ pub inline fn usize2Loc(loc: usize) Loc { pub const Source = struct { path: fs.Path, key_path: fs.Path, - index: u32 = 0, + contents: string, contents_is_recycled: bool = false, + /// Lazily-generated human-readable identifier name that is non-unique + /// Avoid accessing this directly most of the time + identifier_name: string = "", + + index: Index = Index.source(0), + + pub fn fmtIdentifier(this: *const Source) strings.FormatValidIdentifier { + return this.path.name.fmtIdentifier(); + } + + pub fn identifierName(this: *Source, allocator: std.mem.Allocator) !string { + if (this.identifier_name.len > 0) { + return this.identifier_name; + } + + std.debug.assert(this.path.text.len > 0); + const name = try this.path.name.nonUniqueNameString(allocator); + this.identifier_name = name; + return name; + } + + pub fn rangeOfIdentifier(this: *const Source, loc: Loc) Range { + const js_lexer = @import("./js_lexer.zig"); + return js_lexer.rangeOfIdentifier(this, loc); + } + pub fn isWebAssembly(this: *const Source) bool { if (this.contents.len < 4) return false; @@ -1160,7 +1190,7 @@ pub const Source = struct { pub fn initEmptyFile(filepath: string) Source { const path = fs.Path.init(filepath); - return Source{ .path = path, .key_path = path, .index = 0, .contents = "" }; + return Source{ .path = path, .key_path = path, .contents = "" }; } pub fn initFile(file: fs.File, _: std.mem.Allocator) !Source { diff --git a/src/multi_array_list.zig b/src/multi_array_list.zig new file mode 100644 index 000000000000..4638b2d0b567 --- /dev/null +++ b/src/multi_array_list.zig @@ -0,0 +1,720 @@ +// This fork adds a zero() function + +const std = @import("std"); +const builtin = @import("builtin"); +const assert = std.debug.assert; +const meta = std.meta; +const mem = std.mem; +const Allocator = mem.Allocator; +const testing = std.testing; + +/// A MultiArrayList stores a list of a struct type. +/// Instead of storing a single list of items, MultiArrayList +/// stores separate lists for each field of the struct. +/// This allows for memory savings if the struct has padding, +/// and also improves cache usage if only some fields are needed +/// for a computation. The primary API for accessing fields is +/// the `slice()` function, which computes the start pointers +/// for the array of each field. From the slice you can call +/// `.items(.)` to obtain a slice of field values. +pub fn MultiArrayList(comptime S: type) type { + return struct { + bytes: [*]align(@alignOf(S)) u8 = undefined, + len: usize = 0, + capacity: usize = 0, + + pub const Elem = S; + + pub const Field = meta.FieldEnum(S); + + /// A MultiArrayList.Slice contains cached start pointers for each field in the list. + /// These pointers are not normally stored to reduce the size of the list in memory. + /// If you are accessing multiple fields, call slice() first to compute the pointers, + /// and then get the field arrays from the slice. + pub const Slice = struct { + /// This array is indexed by the field index which can be obtained + /// by using @enumToInt() on the Field enum + ptrs: [fields.len][*]u8, + len: usize, + capacity: usize, + + pub fn items(self: Slice, comptime field: Field) []FieldType(field) { + const F = FieldType(field); + if (self.capacity == 0) { + return &[_]F{}; + } + const byte_ptr = self.ptrs[@enumToInt(field)]; + const casted_ptr: [*]F = if (@sizeOf(F) == 0) + undefined + else + @ptrCast([*]F, @alignCast(@alignOf(F), byte_ptr)); + return casted_ptr[0..self.len]; + } + + pub fn toMultiArrayList(self: Slice) Self { + if (self.ptrs.len == 0) { + return .{}; + } + const unaligned_ptr = self.ptrs[sizes.fields[0]]; + const aligned_ptr = @alignCast(@alignOf(S), unaligned_ptr); + const casted_ptr = @ptrCast([*]align(@alignOf(S)) u8, aligned_ptr); + return .{ + .bytes = casted_ptr, + .len = self.len, + .capacity = self.capacity, + }; + } + + pub fn deinit(self: *Slice, gpa: Allocator) void { + var other = self.toMultiArrayList(); + other.deinit(gpa); + self.* = undefined; + } + }; + + const Self = @This(); + + const fields = meta.fields(S); + /// `sizes.bytes` is an array of @sizeOf each S field. Sorted by alignment, descending. + /// `sizes.fields` is an array mapping from `sizes.bytes` array index to field index. + const sizes = blk: { + const Data = struct { + size: usize, + size_index: usize, + alignment: usize, + }; + var data: [fields.len]Data = undefined; + for (fields) |field_info, i| { + data[i] = .{ + .size = @sizeOf(field_info.type), + .size_index = i, + .alignment = if (@sizeOf(field_info.type) == 0) 1 else field_info.alignment, + }; + } + const Sort = struct { + fn lessThan(context: void, lhs: Data, rhs: Data) bool { + _ = context; + return lhs.alignment > rhs.alignment; + } + }; + std.sort.sort(Data, &data, {}, Sort.lessThan); + var sizes_bytes: [fields.len]usize = undefined; + var field_indexes: [fields.len]usize = undefined; + for (data) |elem, i| { + sizes_bytes[i] = elem.size; + field_indexes[i] = elem.size_index; + } + break :blk .{ + .bytes = sizes_bytes, + .fields = field_indexes, + }; + }; + + /// Release all allocated memory. + pub fn deinit(self: *Self, gpa: Allocator) void { + gpa.free(self.allocatedBytes()); + self.* = undefined; + } + + /// The caller owns the returned memory. Empties this MultiArrayList. + pub fn toOwnedSlice(self: *Self) Slice { + const result = self.slice(); + self.* = .{}; + return result; + } + + /// Compute pointers to the start of each field of the array. + /// If you need to access multiple fields, calling this may + /// be more efficient than calling `items()` multiple times. + pub fn slice(self: Self) Slice { + var result: Slice = .{ + .ptrs = undefined, + .len = self.len, + .capacity = self.capacity, + }; + var ptr: [*]u8 = self.bytes; + for (sizes.bytes) |field_size, i| { + result.ptrs[sizes.fields[i]] = ptr; + ptr += field_size * self.capacity; + } + return result; + } + + /// Get the slice of values for a specified field. + /// If you need multiple fields, consider calling slice() + /// instead. + pub fn items(self: Self, comptime field: Field) []FieldType(field) { + return self.slice().items(field); + } + + /// Overwrite one array element with new data. + pub fn set(self: *Self, index: usize, elem: S) void { + const slices = self.slice(); + inline for (fields) |field_info, i| { + slices.items(@intToEnum(Field, i))[index] = @field(elem, field_info.name); + } + } + + /// Obtain all the data for one array element. + pub fn get(self: Self, index: usize) S { + const slices = self.slice(); + var result: S = undefined; + inline for (fields) |field_info, i| { + @field(result, field_info.name) = slices.items(@intToEnum(Field, i))[index]; + } + return result; + } + + /// Extend the list by 1 element. Allocates more memory as necessary. + pub fn append(self: *Self, gpa: Allocator, elem: S) !void { + try self.ensureUnusedCapacity(gpa, 1); + self.appendAssumeCapacity(elem); + } + + /// Extend the list by 1 element, but asserting `self.capacity` + /// is sufficient to hold an additional item. + pub fn appendAssumeCapacity(self: *Self, elem: S) void { + assert(self.len < self.capacity); + self.len += 1; + self.set(self.len - 1, elem); + } + + /// Extend the list by 1 element, returning the newly reserved + /// index with uninitialized data. + /// Allocates more memory as necesasry. + pub fn addOne(self: *Self, allocator: Allocator) Allocator.Error!usize { + try self.ensureUnusedCapacity(allocator, 1); + return self.addOneAssumeCapacity(); + } + + /// Extend the list by 1 element, asserting `self.capacity` + /// is sufficient to hold an additional item. Returns the + /// newly reserved index with uninitialized data. + pub fn addOneAssumeCapacity(self: *Self) usize { + assert(self.len < self.capacity); + const index = self.len; + self.len += 1; + return index; + } + + /// Remove and return the last element from the list. + /// Asserts the list has at least one item. + /// Invalidates pointers to fields of the removed element. + pub fn pop(self: *Self) S { + const val = self.get(self.len - 1); + self.len -= 1; + return val; + } + + /// Remove and return the last element from the list, or + /// return `null` if list is empty. + /// Invalidates pointers to fields of the removed element, if any. + pub fn popOrNull(self: *Self) ?S { + if (self.len == 0) return null; + return self.pop(); + } + + /// Inserts an item into an ordered list. Shifts all elements + /// after and including the specified index back by one and + /// sets the given index to the specified element. May reallocate + /// and invalidate iterators. + pub fn insert(self: *Self, gpa: Allocator, index: usize, elem: S) !void { + try self.ensureUnusedCapacity(gpa, 1); + self.insertAssumeCapacity(index, elem); + } + + /// Inserts an item into an ordered list which has room for it. + /// Shifts all elements after and including the specified index + /// back by one and sets the given index to the specified element. + /// Will not reallocate the array, does not invalidate iterators. + pub fn insertAssumeCapacity(self: *Self, index: usize, elem: S) void { + assert(self.len < self.capacity); + assert(index <= self.len); + self.len += 1; + const slices = self.slice(); + inline for (fields) |field_info, field_index| { + const field_slice = slices.items(@intToEnum(Field, field_index)); + var i: usize = self.len - 1; + while (i > index) : (i -= 1) { + field_slice[i] = field_slice[i - 1]; + } + field_slice[index] = @field(elem, field_info.name); + } + } + + /// Remove the specified item from the list, swapping the last + /// item in the list into its position. Fast, but does not + /// retain list ordering. + pub fn swapRemove(self: *Self, index: usize) void { + const slices = self.slice(); + inline for (fields) |_, i| { + const field_slice = slices.items(@intToEnum(Field, i)); + field_slice[index] = field_slice[self.len - 1]; + field_slice[self.len - 1] = undefined; + } + self.len -= 1; + } + + /// Remove the specified item from the list, shifting items + /// after it to preserve order. + pub fn orderedRemove(self: *Self, index: usize) void { + const slices = self.slice(); + inline for (fields) |_, field_index| { + const field_slice = slices.items(@intToEnum(Field, field_index)); + var i = index; + while (i < self.len - 1) : (i += 1) { + field_slice[i] = field_slice[i + 1]; + } + field_slice[i] = undefined; + } + self.len -= 1; + } + + /// Adjust the list's length to `new_len`. + /// Does not initialize added items, if any. + pub fn resize(self: *Self, gpa: Allocator, new_len: usize) !void { + try self.ensureTotalCapacity(gpa, new_len); + self.len = new_len; + } + + /// Attempt to reduce allocated capacity to `new_len`. + /// If `new_len` is greater than zero, this may fail to reduce the capacity, + /// but the data remains intact and the length is updated to new_len. + pub fn shrinkAndFree(self: *Self, gpa: Allocator, new_len: usize) void { + if (new_len == 0) { + gpa.free(self.allocatedBytes()); + self.* = .{}; + return; + } + assert(new_len <= self.capacity); + assert(new_len <= self.len); + + const other_bytes = gpa.alignedAlloc( + u8, + @alignOf(S), + capacityInBytes(new_len), + ) catch { + const self_slice = self.slice(); + inline for (fields) |field_info, i| { + if (@sizeOf(field_info.type) != 0) { + const field = @intToEnum(Field, i); + const dest_slice = self_slice.items(field)[new_len..]; + const byte_count = dest_slice.len * @sizeOf(field_info.type); + // We use memset here for more efficient codegen in safety-checked, + // valgrind-enabled builds. Otherwise the valgrind client request + // will be repeated for every element. + @memset(@ptrCast([*]u8, dest_slice.ptr), undefined, byte_count); + } + } + self.len = new_len; + return; + }; + var other = Self{ + .bytes = other_bytes.ptr, + .capacity = new_len, + .len = new_len, + }; + self.len = new_len; + const self_slice = self.slice(); + const other_slice = other.slice(); + inline for (fields) |field_info, i| { + if (@sizeOf(field_info.type) != 0) { + const field = @intToEnum(Field, i); + mem.copy(field_info.type, other_slice.items(field), self_slice.items(field)); + } + } + gpa.free(self.allocatedBytes()); + self.* = other; + } + + /// Reduce length to `new_len`. + /// Invalidates pointers to elements `items[new_len..]`. + /// Keeps capacity the same. + pub fn shrinkRetainingCapacity(self: *Self, new_len: usize) void { + self.len = new_len; + } + + /// Modify the array so that it can hold at least `new_capacity` items. + /// Implements super-linear growth to achieve amortized O(1) append operations. + /// Invalidates pointers if additional memory is needed. + pub fn ensureTotalCapacity(self: *Self, gpa: Allocator, new_capacity: usize) !void { + var better_capacity = self.capacity; + if (better_capacity >= new_capacity) return; + + while (true) { + better_capacity += better_capacity / 2 + 8; + if (better_capacity >= new_capacity) break; + } + + return self.setCapacity(gpa, better_capacity); + } + + /// Modify the array so that it can hold at least `additional_count` **more** items. + /// Invalidates pointers if additional memory is needed. + pub fn ensureUnusedCapacity(self: *Self, gpa: Allocator, additional_count: usize) !void { + return self.ensureTotalCapacity(gpa, self.len + additional_count); + } + + /// Modify the array so that it can hold exactly `new_capacity` items. + /// Invalidates pointers if additional memory is needed. + /// `new_capacity` must be greater or equal to `len`. + pub fn setCapacity(self: *Self, gpa: Allocator, new_capacity: usize) !void { + assert(new_capacity >= self.len); + const new_bytes = try gpa.alignedAlloc( + u8, + @alignOf(S), + capacityInBytes(new_capacity), + ); + if (self.len == 0) { + gpa.free(self.allocatedBytes()); + self.bytes = new_bytes.ptr; + self.capacity = new_capacity; + return; + } + var other = Self{ + .bytes = new_bytes.ptr, + .capacity = new_capacity, + .len = self.len, + }; + const self_slice = self.slice(); + const other_slice = other.slice(); + inline for (fields) |field_info, i| { + if (@sizeOf(field_info.type) != 0) { + const field = @intToEnum(Field, i); + mem.copy(field_info.type, other_slice.items(field), self_slice.items(field)); + } + } + gpa.free(self.allocatedBytes()); + self.* = other; + } + + /// Create a copy of this list with a new backing store, + /// using the specified allocator. + pub fn clone(self: Self, gpa: Allocator) !Self { + var result = Self{}; + errdefer result.deinit(gpa); + try result.ensureTotalCapacity(gpa, self.len); + result.len = self.len; + const self_slice = self.slice(); + const result_slice = result.slice(); + inline for (fields) |field_info, i| { + if (@sizeOf(field_info.type) != 0) { + const field = @intToEnum(Field, i); + mem.copy(field_info.type, result_slice.items(field), self_slice.items(field)); + } + } + return result; + } + + pub fn clearRetainingCapacity(this: *Self) void { + this.len = 0; + } + + /// `ctx` has the following method: + /// `fn lessThan(ctx: @TypeOf(ctx), a_index: usize, b_index: usize) bool` + pub fn sort(self: Self, ctx: anytype) void { + const SortContext = struct { + sub_ctx: @TypeOf(ctx), + slice: Slice, + + pub fn swap(sc: @This(), a_index: usize, b_index: usize) void { + inline for (fields) |field_info, i| { + if (@sizeOf(field_info.type) != 0) { + const field = @intToEnum(Field, i); + const ptr = sc.slice.items(field); + mem.swap(field_info.type, &ptr[a_index], &ptr[b_index]); + } + } + } + + pub fn lessThan(sc: @This(), a_index: usize, b_index: usize) bool { + return sc.sub_ctx.lessThan(a_index, b_index); + } + }; + + std.sort.sortContext(self.len, SortContext{ + .sub_ctx = ctx, + .slice = self.slice(), + }); + } + + fn capacityInBytes(capacity: usize) usize { + if (builtin.zig_backend == .stage2_c) { + var bytes: usize = 0; + for (sizes.bytes) |size| bytes += size * capacity; + return bytes; + } else { + const sizes_vector: @Vector(sizes.bytes.len, usize) = sizes.bytes; + const capacity_vector = @splat(sizes.bytes.len, capacity); + return @reduce(.Add, capacity_vector * sizes_vector); + } + } + + pub fn appendList(this: *Self, allocator: std.mem.Allocator, other: Self) !void { + try this.ensureUnusedCapacity(allocator, other.len); + const offset = this.len; + this.len += other.len; + const other_slice = other.slice(); + const this_slice = this.slice(); + inline for (fields) |field_info, i| { + if (@sizeOf(field_info.type) != 0) { + const field = @intToEnum(Field, i); + mem.copy(field_info.type, this_slice.items(field)[offset..], other_slice.items(field)); + } + } + } + + fn allocatedBytes(self: Self) []align(@alignOf(S)) u8 { + return self.bytes[0..capacityInBytes(self.capacity)]; + } + + pub fn zero(this: *Self) void { + var allocated = this.allocatedBytes(); + if (allocated.len > 0) { + @memset(allocated.ptr, 0, allocated.len); + } + } + + fn FieldType(comptime field: Field) type { + return meta.fieldInfo(S, field).type; + } + + /// This function is used in tools/zig-gdb.py to fetch the child type to facilitate + /// fancy debug printing for this type. + fn gdbHelper(self: *Self, child: *S) void { + _ = self; + _ = child; + } + + comptime { + if (builtin.mode == .Debug) { + _ = gdbHelper; + } + } + }; +} + +test "basic usage" { + const ally = testing.allocator; + + const Foo = struct { + a: u32, + b: []const u8, + c: u8, + }; + + var list = MultiArrayList(Foo){}; + defer list.deinit(ally); + + try testing.expectEqual(@as(usize, 0), list.items(.a).len); + + try list.ensureTotalCapacity(ally, 2); + + list.appendAssumeCapacity(.{ + .a = 1, + .b = "foobar", + .c = 'a', + }); + + list.appendAssumeCapacity(.{ + .a = 2, + .b = "zigzag", + .c = 'b', + }); + + try testing.expectEqualSlices(u32, list.items(.a), &[_]u32{ 1, 2 }); + try testing.expectEqualSlices(u8, list.items(.c), &[_]u8{ 'a', 'b' }); + + try testing.expectEqual(@as(usize, 2), list.items(.b).len); + try testing.expectEqualStrings("foobar", list.items(.b)[0]); + try testing.expectEqualStrings("zigzag", list.items(.b)[1]); + + try list.append(ally, .{ + .a = 3, + .b = "fizzbuzz", + .c = 'c', + }); + + try testing.expectEqualSlices(u32, list.items(.a), &[_]u32{ 1, 2, 3 }); + try testing.expectEqualSlices(u8, list.items(.c), &[_]u8{ 'a', 'b', 'c' }); + + try testing.expectEqual(@as(usize, 3), list.items(.b).len); + try testing.expectEqualStrings("foobar", list.items(.b)[0]); + try testing.expectEqualStrings("zigzag", list.items(.b)[1]); + try testing.expectEqualStrings("fizzbuzz", list.items(.b)[2]); + + // Add 6 more things to force a capacity increase. + var i: usize = 0; + while (i < 6) : (i += 1) { + try list.append(ally, .{ + .a = @intCast(u32, 4 + i), + .b = "whatever", + .c = @intCast(u8, 'd' + i), + }); + } + + try testing.expectEqualSlices( + u32, + &[_]u32{ 1, 2, 3, 4, 5, 6, 7, 8, 9 }, + list.items(.a), + ); + try testing.expectEqualSlices( + u8, + &[_]u8{ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i' }, + list.items(.c), + ); + + list.shrinkAndFree(ally, 3); + + try testing.expectEqualSlices(u32, list.items(.a), &[_]u32{ 1, 2, 3 }); + try testing.expectEqualSlices(u8, list.items(.c), &[_]u8{ 'a', 'b', 'c' }); + + try testing.expectEqual(@as(usize, 3), list.items(.b).len); + try testing.expectEqualStrings("foobar", list.items(.b)[0]); + try testing.expectEqualStrings("zigzag", list.items(.b)[1]); + try testing.expectEqualStrings("fizzbuzz", list.items(.b)[2]); + + list.set(try list.addOne(ally), .{ + .a = 4, + .b = "xnopyt", + .c = 'd', + }); + try testing.expectEqualStrings("xnopyt", list.pop().b); + try testing.expectEqual(@as(?u8, 'c'), if (list.popOrNull()) |elem| elem.c else null); + try testing.expectEqual(@as(u32, 2), list.pop().a); + try testing.expectEqual(@as(u8, 'a'), list.pop().c); + try testing.expectEqual(@as(?Foo, null), list.popOrNull()); +} + +// This was observed to fail on aarch64 with LLVM 11, when the capacityInBytes +// function used the @reduce code path. +test "regression test for @reduce bug" { + const ally = testing.allocator; + var list = MultiArrayList(struct { + tag: std.zig.Token.Tag, + start: u32, + }){}; + defer list.deinit(ally); + + try list.ensureTotalCapacity(ally, 20); + + try list.append(ally, .{ .tag = .keyword_const, .start = 0 }); + try list.append(ally, .{ .tag = .identifier, .start = 6 }); + try list.append(ally, .{ .tag = .equal, .start = 10 }); + try list.append(ally, .{ .tag = .builtin, .start = 12 }); + try list.append(ally, .{ .tag = .l_paren, .start = 19 }); + try list.append(ally, .{ .tag = .string_literal, .start = 20 }); + try list.append(ally, .{ .tag = .r_paren, .start = 25 }); + try list.append(ally, .{ .tag = .semicolon, .start = 26 }); + try list.append(ally, .{ .tag = .keyword_pub, .start = 29 }); + try list.append(ally, .{ .tag = .keyword_fn, .start = 33 }); + try list.append(ally, .{ .tag = .identifier, .start = 36 }); + try list.append(ally, .{ .tag = .l_paren, .start = 40 }); + try list.append(ally, .{ .tag = .r_paren, .start = 41 }); + try list.append(ally, .{ .tag = .identifier, .start = 43 }); + try list.append(ally, .{ .tag = .bang, .start = 51 }); + try list.append(ally, .{ .tag = .identifier, .start = 52 }); + try list.append(ally, .{ .tag = .l_brace, .start = 57 }); + try list.append(ally, .{ .tag = .identifier, .start = 63 }); + try list.append(ally, .{ .tag = .period, .start = 66 }); + try list.append(ally, .{ .tag = .identifier, .start = 67 }); + try list.append(ally, .{ .tag = .period, .start = 70 }); + try list.append(ally, .{ .tag = .identifier, .start = 71 }); + try list.append(ally, .{ .tag = .l_paren, .start = 75 }); + try list.append(ally, .{ .tag = .string_literal, .start = 76 }); + try list.append(ally, .{ .tag = .comma, .start = 113 }); + try list.append(ally, .{ .tag = .period, .start = 115 }); + try list.append(ally, .{ .tag = .l_brace, .start = 116 }); + try list.append(ally, .{ .tag = .r_brace, .start = 117 }); + try list.append(ally, .{ .tag = .r_paren, .start = 118 }); + try list.append(ally, .{ .tag = .semicolon, .start = 119 }); + try list.append(ally, .{ .tag = .r_brace, .start = 121 }); + try list.append(ally, .{ .tag = .eof, .start = 123 }); + + const tags = list.items(.tag); + try testing.expectEqual(tags[1], .identifier); + try testing.expectEqual(tags[2], .equal); + try testing.expectEqual(tags[3], .builtin); + try testing.expectEqual(tags[4], .l_paren); + try testing.expectEqual(tags[5], .string_literal); + try testing.expectEqual(tags[6], .r_paren); + try testing.expectEqual(tags[7], .semicolon); + try testing.expectEqual(tags[8], .keyword_pub); + try testing.expectEqual(tags[9], .keyword_fn); + try testing.expectEqual(tags[10], .identifier); + try testing.expectEqual(tags[11], .l_paren); + try testing.expectEqual(tags[12], .r_paren); + try testing.expectEqual(tags[13], .identifier); + try testing.expectEqual(tags[14], .bang); + try testing.expectEqual(tags[15], .identifier); + try testing.expectEqual(tags[16], .l_brace); + try testing.expectEqual(tags[17], .identifier); + try testing.expectEqual(tags[18], .period); + try testing.expectEqual(tags[19], .identifier); + try testing.expectEqual(tags[20], .period); + try testing.expectEqual(tags[21], .identifier); + try testing.expectEqual(tags[22], .l_paren); + try testing.expectEqual(tags[23], .string_literal); + try testing.expectEqual(tags[24], .comma); + try testing.expectEqual(tags[25], .period); + try testing.expectEqual(tags[26], .l_brace); + try testing.expectEqual(tags[27], .r_brace); + try testing.expectEqual(tags[28], .r_paren); + try testing.expectEqual(tags[29], .semicolon); + try testing.expectEqual(tags[30], .r_brace); + try testing.expectEqual(tags[31], .eof); +} + +test "ensure capacity on empty list" { + const ally = testing.allocator; + + const Foo = struct { + a: u32, + b: u8, + }; + + var list = MultiArrayList(Foo){}; + defer list.deinit(ally); + + try list.ensureTotalCapacity(ally, 2); + list.appendAssumeCapacity(.{ .a = 1, .b = 2 }); + list.appendAssumeCapacity(.{ .a = 3, .b = 4 }); + + try testing.expectEqualSlices(u32, &[_]u32{ 1, 3 }, list.items(.a)); + try testing.expectEqualSlices(u8, &[_]u8{ 2, 4 }, list.items(.b)); + + list.len = 0; + list.appendAssumeCapacity(.{ .a = 5, .b = 6 }); + list.appendAssumeCapacity(.{ .a = 7, .b = 8 }); + + try testing.expectEqualSlices(u32, &[_]u32{ 5, 7 }, list.items(.a)); + try testing.expectEqualSlices(u8, &[_]u8{ 6, 8 }, list.items(.b)); + + list.len = 0; + try list.ensureTotalCapacity(ally, 16); + + list.appendAssumeCapacity(.{ .a = 9, .b = 10 }); + list.appendAssumeCapacity(.{ .a = 11, .b = 12 }); + + try testing.expectEqualSlices(u32, &[_]u32{ 9, 11 }, list.items(.a)); + try testing.expectEqualSlices(u8, &[_]u8{ 10, 12 }, list.items(.b)); +} + +test "insert elements" { + const ally = testing.allocator; + + const Foo = struct { + a: u8, + b: u32, + }; + + var list = MultiArrayList(Foo){}; + defer list.deinit(ally); + + try list.insert(ally, 0, .{ .a = 1, .b = 2 }); + try list.ensureUnusedCapacity(ally, 1); + list.insertAssumeCapacity(1, .{ .a = 2, .b = 3 }); + + try testing.expectEqualSlices(u8, &[_]u8{ 1, 2 }, list.items(.a)); + try testing.expectEqualSlices(u32, &[_]u32{ 2, 3 }, list.items(.b)); +} diff --git a/src/options.zig b/src/options.zig index 863e12e14a45..455c087fcfdb 100644 --- a/src/options.zig +++ b/src/options.zig @@ -1181,6 +1181,25 @@ pub const SourceMapOption = enum { }); }; +pub const OutputFormat = enum { + preserve, + + /// ES module format + /// This is the default format + esm, + /// Immediately-invoked function expression + /// ( + /// function(){} + /// )(); + iife, + /// CommonJS + cjs, + + pub fn keepES6ImportExportSyntax(this: OutputFormat) bool { + return this == .esm; + } +}; + /// BundleOptions is used when ResolveMode is not set to "disable". /// BundleOptions is effectively webpack + babel pub const BundleOptions = struct { @@ -1212,6 +1231,9 @@ pub const BundleOptions = struct { production: bool = false, serve: bool = false, + // only used by bundle_v2 + output_format: OutputFormat = .esm, + append_package_version_in_query_string: bool = false, jsx_optimization_inline: ?bool = null, @@ -1241,6 +1263,7 @@ pub const BundleOptions = struct { conditions: ESMConditions = undefined, tree_shaking: bool = false, + code_splitting: bool = false, sourcemap: SourceMapOption = SourceMapOption.none, disable_transpilation: bool = false, diff --git a/src/output.zig b/src/output.zig index ec073baa1c2c..85df7ac13a8a 100644 --- a/src/output.zig +++ b/src/output.zig @@ -269,7 +269,7 @@ inline fn printElapsedToWithCtx(elapsed: f64, comptime printerFn: anytype, compt } } -pub fn printElapsedTo(elapsed: f64, comptime printerFn: anytype, ctx: anytype) void { +pub noinline fn printElapsedTo(elapsed: f64, comptime printerFn: anytype, ctx: anytype) void { printElapsedToWithCtx(elapsed, printerFn, true, ctx); } @@ -297,7 +297,7 @@ pub fn printTimer(timer: *SystemTimer) void { printElapsed(@intToFloat(f64, elapsed)); } -pub fn printErrorable(comptime fmt: string, args: anytype) !void { +pub noinline fn printErrorable(comptime fmt: string, args: anytype) !void { if (comptime Environment.isWasm) { try source.stream.seekTo(0); try source.stream.writer().print(fmt, args); @@ -310,7 +310,7 @@ pub fn printErrorable(comptime fmt: string, args: anytype) !void { /// Print to stdout /// This will appear in the terminal, including in production. /// Text automatically buffers -pub fn println(comptime fmt: string, args: anytype) void { +pub noinline fn println(comptime fmt: string, args: anytype) void { if (fmt.len == 0 or fmt[fmt.len - 1] != '\n') { return print(fmt ++ "\n", args); } @@ -326,7 +326,7 @@ pub inline fn debug(comptime fmt: string, args: anytype) void { flush(); } -pub fn _debug(comptime fmt: string, args: anytype) void { +pub inline fn _debug(comptime fmt: string, args: anytype) void { std.debug.assert(source_set); if (fmt.len == 0 or fmt[fmt.len - 1] != '\n') { return print(fmt ++ "\n", args); @@ -335,13 +335,14 @@ pub fn _debug(comptime fmt: string, args: anytype) void { return print(fmt, args); } -pub fn print(comptime fmt: string, args: anytype) void { +pub noinline fn print(comptime fmt: string, args: anytype) void { if (comptime Environment.isWasm) { source.stream.pos = 0; std.fmt.format(source.stream.writer(), fmt, args) catch unreachable; root.console_log(root.Uint8Array.fromSlice(source.stream.buffer[0..source.stream.pos])); } else { - std.debug.assert(source_set); + if (comptime Environment.allow_assert) + std.debug.assert(source_set); if (enable_buffering) { std.fmt.format(source.buffered_stream.writer(), fmt, args) catch unreachable; @@ -448,6 +449,9 @@ pub const color_map = ComptimeStringMap(string, .{ }); pub const RESET = "\x1b[0m"; pub fn prettyFmt(comptime fmt: string, comptime is_enabled: bool) string { + if (comptime @import("bun").fast_debug_build_mode) + return fmt; + comptime var new_fmt: [fmt.len * 4]u8 = undefined; comptime var new_fmt_i: usize = 0; @@ -531,11 +535,14 @@ pub fn prettyFmt(comptime fmt: string, comptime is_enabled: bool) string { return comptime new_fmt[0..new_fmt_i]; } -pub fn prettyWithPrinter(comptime fmt: string, args: anytype, comptime printer: anytype, comptime l: Level) void { +pub noinline fn prettyWithPrinter(comptime fmt: string, args: anytype, comptime printer: anytype, comptime l: Level) void { if (comptime l == .Warn) { if (level == .Error) return; } + if (comptime @import("bun").fast_debug_build_mode) + return printer(comptime prettyFmt(fmt, false), args); + if (if (comptime l == Level.stdout) enable_ansi_colors_stdout else enable_ansi_colors_stderr) { printer(comptime prettyFmt(fmt, true), args); } else { @@ -543,7 +550,10 @@ pub fn prettyWithPrinter(comptime fmt: string, args: anytype, comptime printer: } } -pub fn prettyWithPrinterFn(comptime fmt: string, args: anytype, comptime printFn: anytype, ctx: anytype) void { +pub noinline fn prettyWithPrinterFn(comptime fmt: string, args: anytype, comptime printFn: anytype, ctx: anytype) void { + if (comptime @import("bun").fast_debug_build_mode) + return printFn(ctx, comptime prettyFmt(fmt, false), args); + if (enable_ansi_colors) { printFn(ctx, comptime prettyFmt(fmt, true), args); } else { @@ -551,13 +561,13 @@ pub fn prettyWithPrinterFn(comptime fmt: string, args: anytype, comptime printFn } } -pub fn pretty(comptime fmt: string, args: anytype) void { +pub noinline fn pretty(comptime fmt: string, args: anytype) void { prettyWithPrinter(fmt, args, print, .stdout); } /// Like Output.println, except it will automatically strip ansi color codes if /// the terminal doesn't support them. -pub fn prettyln(comptime fmt: string, args: anytype) void { +pub noinline fn prettyln(comptime fmt: string, args: anytype) void { if (enable_ansi_colors) { println(comptime prettyFmt(fmt, true), args); } else { @@ -565,7 +575,7 @@ pub fn prettyln(comptime fmt: string, args: anytype) void { } } -pub fn printErrorln(comptime fmt: string, args: anytype) void { +pub noinline fn printErrorln(comptime fmt: string, args: anytype) void { if (fmt.len == 0 or fmt[fmt.len - 1] != '\n') { return printError(fmt ++ "\n", args); } @@ -573,13 +583,13 @@ pub fn printErrorln(comptime fmt: string, args: anytype) void { return printError(fmt, args); } -pub fn prettyError(comptime fmt: string, args: anytype) void { +pub noinline fn prettyError(comptime fmt: string, args: anytype) void { prettyWithPrinter(fmt, args, printError, .Error); } /// Print to stderr with ansi color codes automatically stripped out if the /// terminal doesn't support them. Text is buffered -pub fn prettyErrorln(comptime fmt: string, args: anytype) void { +pub noinline fn prettyErrorln(comptime fmt: string, args: anytype) void { if (fmt.len == 0 or fmt[fmt.len - 1] != '\n') { return prettyWithPrinter( fmt ++ "\n", @@ -605,11 +615,11 @@ pub const Level = enum(u8) { pub var level = if (Environment.isDebug) Level.Warn else Level.Error; -pub fn prettyWarn(comptime fmt: string, args: anytype) void { +pub noinline fn prettyWarn(comptime fmt: string, args: anytype) void { prettyWithPrinter(fmt, args, printError, .Warn); } -pub fn prettyWarnln(comptime fmt: string, args: anytype) void { +pub noinline fn prettyWarnln(comptime fmt: string, args: anytype) void { if (fmt.len == 0 or fmt[fmt.len - 1] != '\n') { return prettyWithPrinter(fmt ++ "\n", args, printError, .Warn); } @@ -617,11 +627,11 @@ pub fn prettyWarnln(comptime fmt: string, args: anytype) void { return prettyWithPrinter(fmt, args, printError, .Warn); } -pub fn errorLn(comptime fmt: string, args: anytype) void { +pub inline fn errorLn(comptime fmt: string, args: anytype) void { return printErrorln(fmt, args); } -pub fn printError(comptime fmt: string, args: anytype) void { +pub noinline fn printError(comptime fmt: string, args: anytype) void { if (comptime Environment.isWasm) { source.error_stream.seekTo(0) catch return; source.error_stream.writer().print(fmt, args) catch unreachable; diff --git a/src/renamer.zig b/src/renamer.zig index bce2d3030b4a..f3826f45048c 100644 --- a/src/renamer.zig +++ b/src/renamer.zig @@ -39,6 +39,29 @@ pub const Renamer = struct { } }; +pub const BundledRenamer = struct { + symbols: js_ast.Symbol.Map, + sources: []const []const u8, + + pub fn init(symbols: js_ast.Symbol.Map, sources: []const []const u8) Renamer { + return Renamer{ .symbols = symbols, .source = sources }; + } + + pub fn nameForSymbol(renamer: *Renamer, ref: Ref) string { + if (ref.isSourceContentsSlice()) { + unreachable; + } + + const resolved = renamer.symbols.follow(ref); + + if (renamer.symbols.getConst(resolved)) |symbol| { + return symbol.original_name; + } else { + Global.panic("Invalid symbol {s} in {s}", .{ ref, renamer.source.path.text }); + } + } +}; + pub const DisabledRenamer = struct { pub fn init(_: js_ast.Symbol.Map) DisabledRenamer {} pub inline fn nameForSymbol(_: *Renamer, _: js_ast.Ref) string { diff --git a/src/resolver/resolver.zig b/src/resolver/resolver.zig index 7be97a291580..e17aec964fe6 100644 --- a/src/resolver/resolver.zig +++ b/src/resolver/resolver.zig @@ -102,6 +102,29 @@ pub const PathPair = struct { } }; +// this is ripped from esbuild, comments included +pub const SideEffects = enum { + /// The default value conservatively considers all files to have side effects. + has_side_effects, + + /// This file was listed as not having side effects by a "package.json" + /// file in one of our containing directories with a "sideEffects" field. + no_side_effects__package_json, + + /// This file is considered to have no side effects because the AST was empty + /// after parsing finished. This should be the case for ".d.ts" files. + no_side_effects__empty_ast, + + /// This file was loaded using a data-oriented loader (e.g. "text") that is + /// known to not have side effects. + no_side_effects__pure_data, + + // / Same as above but it came from a plugin. We don't want to warn about + // / unused imports to these files since running the plugin is a side effect. + // / Removing the import would not call the plugin which is observable. + // no_side_effects__pure_data_from_plugin, +}; + pub const Result = struct { path_pair: PathPair, @@ -118,7 +141,7 @@ pub const Result = struct { // If present, any ES6 imports to this file can be considered to have no side // effects. This means they should be removed if unused. - primary_side_effects_data: ?SideEffectsData = null, + primary_side_effects_data: SideEffects = SideEffects.has_side_effects, // If true, unused imports are retained in TypeScript code. This matches the // behavior of the "importsNotUsedAsValues" field in "tsconfig.json" when the diff --git a/src/runtime.js b/src/runtime.js index b39eaed9d3f8..3db6afc34309 100644 --- a/src/runtime.js +++ b/src/runtime.js @@ -7,22 +7,7 @@ var __hasOwnProp = Object.prototype.hasOwnProperty; var __getOwnPropNames = Object.getOwnPropertyNames; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; -// We're disabling Object.freeze because it breaks CJS => ESM and can cause -// issues with Suspense and other things that expect the CJS module namespace -// to be mutable when the ESM module namespace is NOT mutable -// var __objectFreezePolyfill = new WeakSet(); - -// globalThis.Object.freeze = function freeze(obj) { -// __objectFreezePolyfill.add(obj); -// return obj; -// }; - -// globalThis.Object.isFrozen = function isFrozen(obj) { -// return __objectFreezePolyfill.has(obj); -// }; - -export var __markAsModule = (target) => - __defProp(target, "__esModule", { value: true, configurable: true }); +export var __markAsModule = target => __defProp(target, "__esModule", { value: true, configurable: true }); // lazy require to prevent loading one icon from a design system export var $$lzy = (target, module, props) => { @@ -37,7 +22,7 @@ export var $$lzy = (target, module, props) => { return target; }; -export var __toModule = (module) => { +export var __toModule = module => { return __reExport( __markAsModule( __defProp( @@ -81,15 +66,9 @@ export var __commonJS = (cb, name) => { return origExports.apply(this, arguments); }; Object.setPrototypeOf(mod_exports, __getProtoOf(origExports)); - Object.defineProperties( - mod_exports, - Object.getOwnPropertyDescriptors(origExports), - ); + Object.defineProperties(mod_exports, Object.getOwnPropertyDescriptors(origExports)); } else { - mod_exports = __create( - __getProtoOf(mod_exports), - Object.getOwnPropertyDescriptors(mod_exports), - ); + mod_exports = __create(__getProtoOf(mod_exports), Object.getOwnPropertyDescriptors(mod_exports)); } } @@ -134,14 +113,13 @@ export var __commonJS = (cb, name) => { export var __cJS2eSM = __commonJS; -export var __internalIsCommonJSNamespace = (namespace) => +export var __internalIsCommonJSNamespace = namespace => namespace != null && typeof namespace === "object" && - ((namespace.default && namespace.default[cjsRequireSymbol]) || - namespace[cjsRequireSymbol]); + ((namespace.default && namespace.default[cjsRequireSymbol]) || namespace[cjsRequireSymbol]); // require() -export var __require = (namespace) => { +export var __require = namespace => { if (__internalIsCommonJSNamespace(namespace)) { return namespace.default(); } @@ -152,7 +130,7 @@ export var __require = (namespace) => { // require().default // this currently does nothing // get rid of this wrapper once we're more confident we do not need special handling for default -__require.d = (namespace) => { +__require.d = namespace => { return namespace; }; @@ -176,7 +154,7 @@ export var __export = (target, all) => { get: all[name], enumerable: true, configurable: true, - set: (newValue) => (all[name] = () => newValue), + set: newValue => (all[name] = () => newValue), }); }; @@ -184,7 +162,7 @@ export var __exportValue = (target, all) => { for (var name in all) { __defProp(target, name, { get: () => all[name], - set: (newValue) => (all[name] = newValue), + set: newValue => (all[name] = newValue), enumerable: true, configurable: true, }); @@ -194,21 +172,20 @@ export var __exportValue = (target, all) => { export var __exportDefault = (target, value) => { __defProp(target, "default", { get: () => value, - set: (newValue) => (value = newValue), + set: newValue => (value = newValue), enumerable: true, configurable: true, }); }; -export var __reExport = (target, module, desc) => { +export var __reExport = (target, module, copyDefault, desc) => { if ((module && typeof module === "object") || typeof module === "function") for (let key of __getOwnPropNames(module)) - if (!__hasOwnProp.call(target, key) && key !== "default") + if (!__hasOwnProp.call(target, key) && (copyDefault || key !== "default")) __defProp(target, key, { get: () => module[key], configurable: true, - enumerable: - !(desc = __getOwnPropDesc(module, key)) || desc.enumerable, + enumerable: !(desc = __getOwnPropDesc(module, key)) || desc.enumerable, }); return target; }; @@ -237,15 +214,43 @@ export var __merge = (props, defaultProps) => { }; export var __decorateClass = (decorators, target, key, kind) => { - var result = - kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target; + var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target; for (var i = decorators.length - 1, decorator; i >= 0; i--) - if ((decorator = decorators[i])) - result = - (kind ? decorator(target, key, result) : decorator(result)) || result; + if ((decorator = decorators[i])) result = (kind ? decorator(target, key, result) : decorator(result)) || result; if (kind && result) __defProp(target, key, result); return result; }; -export var __decorateParam = (index, decorator) => (target, key) => - decorator(target, key, index); +export var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index); + +// Converts the module from CommonJS to ESM +export var __toESM = (module, isNodeMode) => { + return __reExport( + __markAsModule( + __defProp( + module != null ? __create(__getProtoOf(module)) : {}, + "default", + + // If the importer is not in node compatibility mode and this is an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has been set), then forward + // "default" to the export named "default". Otherwise set "default" to + // "module.exports" for node compatibility. + !isNodeMode && module && module.__esModule + ? { get: () => module.default, enumerable: true } + : { value: module, enumerable: true }, + ), + ), + module, + ); +}; + +// Converts the module from ESM to CommonJS +export var __toCommonJS = /* @__PURE__ */ (cache => { + return (module, temp) => { + return ( + (cache && cache.get(module)) || + ((temp = __reExport(__markAsModule({}), module, /* copyDefault */ 1)), cache && cache.set(module, temp), temp) + ); + }; +})(typeof WeakMap !== "undefined" ? new WeakMap() : 0); diff --git a/src/string_builder.zig b/src/string_builder.zig index 23e83917babf..01447242be60 100644 --- a/src/string_builder.zig +++ b/src/string_builder.zig @@ -13,6 +13,18 @@ ptr: ?[*]u8 = null, debug_only_checker: DebugHashTable = DebugHashTable{}, +pub fn initCapacity( + allocator: std.mem.Allocator, + cap: usize, +) !StringBuilder { + return StringBuilder{ + .cap = cap, + .len = 0, + .ptr = (try allocator.alloc(u8, cap)).ptr, + .debug_only_checker = if (comptime Env.allow_assert) DebugHashTable.init(bun.default_allocator) else DebugHashTable{}, + }; +} + pub fn count(this: *StringBuilder, slice: string) void { this.cap += slice.len; if (comptime Env.allow_assert) { @@ -29,6 +41,7 @@ pub fn allocate(this: *StringBuilder, allocator: Allocator) !void { pub fn deinit(this: *StringBuilder, allocator: Allocator) void { if (this.ptr == null or this.cap == 0) return; allocator.free(this.ptr.?[0..this.cap]); + this.ptr = null; if (comptime Env.allow_assert) { this.debug_only_checker.deinit(bun.default_allocator); this.debug_only_checker = .{}; @@ -42,7 +55,8 @@ pub fn append(this: *StringBuilder, slice: string) string { } if (comptime Env.allow_assert) { - assert(this.debug_only_checker.contains(bun.hash(slice))); + if (this.debug_only_checker.count() > 0) + assert(this.debug_only_checker.contains(bun.hash(slice))); } bun.copy(u8, this.ptr.?[this.len..this.cap], slice); diff --git a/src/string_immutable.zig b/src/string_immutable.zig index e012b6e5a187..eb51defae62b 100644 --- a/src/string_immutable.zig +++ b/src/string_immutable.zig @@ -146,6 +146,79 @@ pub fn indexOfCharNeg(self: string, char: u8) i32 { return -1; } +/// Format a string to an ECMAScript identifier. +/// Unlike the string_mutable.zig version, this always allocate/copy +pub fn fmtIdentifier(name: string) FormatValidIdentifier { + return FormatValidIdentifier{ .name = name }; +} + +/// Format a string to an ECMAScript identifier. +/// Different implementation than string_mutable because string_mutable may avoid allocating +/// This will always allocate +pub const FormatValidIdentifier = struct { + name: string, + const js_lexer = @import("./js_lexer.zig"); + pub fn format(self: FormatValidIdentifier, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + var iterator = strings.CodepointIterator.init(self.name); + var cursor = strings.CodepointIterator.Cursor{}; + + var has_needed_gap = false; + var needs_gap = false; + var start_i: usize = 0; + + if (!iterator.next(&cursor)) { + try writer.writeAll("_"); + return; + } + + // Common case: no gap necessary. No allocation necessary. + needs_gap = !js_lexer.isIdentifierStart(cursor.c); + if (!needs_gap) { + // Are there any non-alphanumeric chars at all? + while (iterator.next(&cursor)) { + if (!js_lexer.isIdentifierContinue(cursor.c) or cursor.width > 1) { + needs_gap = true; + start_i = cursor.i; + break; + } + } + } + + if (needs_gap) { + needs_gap = false; + + var slice = self.name[start_i..]; + iterator = strings.CodepointIterator.init(slice); + cursor = strings.CodepointIterator.Cursor{}; + + while (iterator.next(&cursor)) { + if (js_lexer.isIdentifierContinue(cursor.c) and cursor.width == 1) { + if (needs_gap) { + try writer.writeAll("_"); + needs_gap = false; + has_needed_gap = true; + } + try writer.writeAll(slice[cursor.i .. cursor.i + @as(u32, cursor.width)]); + } else if (!needs_gap) { + needs_gap = true; + // skip the code point, replace it with a single _ + } + } + + // If it ends with an emoji + if (needs_gap) { + try writer.writeAll("_"); + needs_gap = false; + has_needed_gap = true; + } + + return; + } + + try writer.writeAll(self.name); + } +}; + pub fn indexOfSigned(self: string, str: string) i32 { const i = std.mem.indexOf(u8, self, str) orelse return -1; return @intCast(i32, i); @@ -3778,25 +3851,51 @@ pub fn join(slices: []const string, delimiter: string, allocator: std.mem.Alloca return try std.mem.join(allocator, delimiter, slices); } +pub fn order(a: []const u8, b: []const u8) std.math.Order { + const len = @min(a.len, b.len); + const cmp = bun.C.memcmp(a.ptr, b.ptr, len); + return switch (cmp) { + 0 => std.math.order(a.len, b.len), + 1 => .gt, + -1 => .lt, + else => unreachable, + }; +} + pub fn cmpStringsAsc(_: void, a: string, b: string) bool { - return std.mem.order(u8, a, b) == .lt; + return order(a, b) == .lt; } pub fn cmpStringsDesc(_: void, a: string, b: string) bool { - return std.mem.order(u8, a, b) == .gt; + return order(a, b) == .gt; } const sort_asc = std.sort.asc(u8); const sort_desc = std.sort.desc(u8); pub fn sortAsc(in: []string) void { + // TODO: experiment with simd to see if it's faster std.sort.sort([]const u8, in, {}, cmpStringsAsc); } pub fn sortDesc(in: []string) void { + // TODO: experiment with simd to see if it's faster std.sort.sort([]const u8, in, {}, cmpStringsDesc); } +pub const StringArrayByIndexSorter = struct { + keys: []const []const u8, + pub fn lessThan(sorter: *@This(), a: usize, b: usize) bool { + return strings.order(sorter.keys[a], sorter.keys[b]) == .lt; + } + + pub fn init(keys: []const []const u8) @This() { + return .{ + .keys = keys, + }; + } +}; + pub fn isASCIIHexDigit(c: u8) bool { return std.ascii.isHex(c); } diff --git a/src/string_mutable.zig b/src/string_mutable.zig index 0d20fed6afc9..2371ae01a846 100644 --- a/src/string_mutable.zig +++ b/src/string_mutable.zig @@ -68,10 +68,9 @@ pub const MutableString = struct { return mutable; } - // Convert it to an ASCII identifier. Note: If you change this to a non-ASCII - // identifier, you're going to potentially cause trouble with non-BMP code - // points in target environments that don't support bracketed Unicode escapes. - + /// Convert it to an ASCII identifier. Note: If you change this to a non-ASCII + /// identifier, you're going to potentially cause trouble with non-BMP code + /// points in target environments that don't support bracketed Unicode escapes. pub fn ensureValidIdentifier(str: string, allocator: std.mem.Allocator) !string { if (str.len == 0) { return "_"; diff --git a/src/thread_pool.zig b/src/thread_pool.zig index 47feae0dfa93..807bebc6ca92 100644 --- a/src/thread_pool.zig +++ b/src/thread_pool.zig @@ -125,6 +125,257 @@ pub const Batch = struct { } }; +pub const WaitGroup = struct { + mutex: std.Thread.Mutex = .{}, + counter: u32 = 0, + event: std.Thread.ResetEvent, + + pub fn init(self: *WaitGroup) !void { + self.* = .{ + .mutex = .{}, + .counter = 0, + .event = undefined, + }; + } + + pub fn deinit(self: *WaitGroup) void { + self.event.reset(); + self.* = undefined; + } + + pub fn start(self: *WaitGroup) void { + self.mutex.lock(); + defer self.mutex.unlock(); + + self.counter += 1; + } + + pub fn finish(self: *WaitGroup) void { + self.mutex.lock(); + defer self.mutex.unlock(); + + self.counter -= 1; + + if (self.counter == 0) { + self.event.set(); + } + } + + pub fn wait(self: *WaitGroup) void { + while (true) { + self.mutex.lock(); + + if (self.counter == 0) { + self.mutex.unlock(); + return; + } + + self.mutex.unlock(); + self.event.wait(); + } + } + + pub fn reset(self: *WaitGroup) void { + self.event.reset(); + } +}; + +pub fn ConcurrentFunction( + comptime Function: anytype, +) type { + return struct { + const Fn = Function; + const Args = std.meta.ArgsTuple(@TypeOf(Fn)); + const Runner = @This(); + thread_pool: *ThreadPool, + states: []Routine = undefined, + batch: Batch = .{}, + allocator: std.mem.Allocator, + + pub fn init(allocator: std.mem.Allocator, thread_pool: *ThreadPool, count: usize) !Runner { + return Runner{ + .allocator = allocator, + .thread_pool = thread_pool, + .states = try allocator.alloc(Routine, count), + .batch = .{}, + }; + } + + pub fn call(this: *@This(), args: Args) void { + this.states[this.batch.len] = .{ + .args = args, + }; + this.batch.push(Batch.from(&this.states[this.batch.len].task)); + } + + pub fn run(this: *@This()) void { + this.thread_pool.schedule(this.batch); + } + + pub const Routine = struct { + args: Args, + task: Task = .{ .callback = callback }, + + pub fn callback(task: *Task) void { + var routine = @fieldParentPtr(@This(), "task", task); + @call(.always_inline, Fn, routine.args); + } + }; + + pub fn deinit(this: *@This()) void { + this.allocator.free(this.states); + } + }; +} + +pub fn runner( + this: *ThreadPool, + allocator: std.mem.Allocator, + comptime Function: anytype, + count: usize, +) !ConcurrentFunction(Function) { + return try ConcurrentFunction(Function).init(allocator, this, count); +} + +/// Loop over an array of tasks and invoke `Run` on each one in a different thread +/// **Blocks the calling thread** until all tasks are completed. +pub fn do( + this: *ThreadPool, + allocator: std.mem.Allocator, + wg: ?*WaitGroup, + ctx: anytype, + comptime Run: anytype, + values: anytype, +) !void { + return try Do(this, allocator, wg, @TypeOf(ctx), ctx, Run, @TypeOf(values), values, false); +} + +pub fn doPtr( + this: *ThreadPool, + allocator: std.mem.Allocator, + wg: ?*WaitGroup, + ctx: anytype, + comptime Run: anytype, + values: anytype, +) !void { + return try Do(this, allocator, wg, @TypeOf(ctx), ctx, Run, @TypeOf(values), values, true); +} + +pub fn Do( + this: *ThreadPool, + allocator: std.mem.Allocator, + wg: ?*WaitGroup, + comptime Context: type, + ctx: Context, + comptime Function: anytype, + comptime ValuesType: type, + values: ValuesType, + comptime as_ptr: bool, +) !void { + if (values.len == 0) + return; + var allocated_wait_group: ?*WaitGroup = null; + defer { + if (allocated_wait_group) |group| { + group.deinit(); + allocator.destroy(group); + } + } + + var wait_group = wg orelse brk: { + allocated_wait_group = try allocator.create(WaitGroup); + try allocated_wait_group.?.init(); + break :brk allocated_wait_group.?; + }; + const WaitContext = struct { + wait_group: *WaitGroup = undefined, + ctx: Context, + }; + + const Runner = struct { + pub fn call(ctx_: WaitContext, values_: ValuesType, i: usize) void { + if (comptime as_ptr) { + for (values_) |*v, j| { + Function(ctx_.ctx, v, i + j); + } + } else { + for (values_) |v, j| { + Function(ctx_.ctx, v, i + j); + } + } + + ctx_.wait_group.finish(); + } + }; + + const tasks_per_worker = @max(try std.math.divFloor(u32, @intCast(u32, values.len), this.max_threads), 1); + const count = @truncate(u32, values.len / tasks_per_worker); + var runny = try runner(this, allocator, Runner.call, count + 1); + defer runny.deinit(); + + var i: usize = 0; + const context_ = WaitContext{ + .ctx = ctx, + .wait_group = wait_group, + }; + var remain = values; + while (remain.len > 0) { + var slice = remain[0..@min(remain.len, tasks_per_worker)]; + + runny.call(.{ + context_, + slice, + i, + }); + i += 1; + remain = remain[slice.len..]; + wait_group.counter += 1; + } + runny.run(); + wait_group.wait(); +} + +test "parallel for loop" { + Output.initTest(); + var thread_pool = ThreadPool.init(.{ .max_threads = 12 }); + var sleepy_time: u32 = 100; + var huge_array = &[_]u32{ + sleepy_time + std.rand.DefaultPrng.init(1).random().uintAtMost(u32, 20), + sleepy_time + std.rand.DefaultPrng.init(2).random().uintAtMost(u32, 20), + sleepy_time + std.rand.DefaultPrng.init(3).random().uintAtMost(u32, 20), + sleepy_time + std.rand.DefaultPrng.init(4).random().uintAtMost(u32, 20), + sleepy_time + std.rand.DefaultPrng.init(5).random().uintAtMost(u32, 20), + sleepy_time + std.rand.DefaultPrng.init(6).random().uintAtMost(u32, 20), + sleepy_time + std.rand.DefaultPrng.init(7).random().uintAtMost(u32, 20), + sleepy_time + std.rand.DefaultPrng.init(8).random().uintAtMost(u32, 20), + sleepy_time + std.rand.DefaultPrng.init(9).random().uintAtMost(u32, 20), + sleepy_time + std.rand.DefaultPrng.init(10).random().uintAtMost(u32, 20), + sleepy_time + std.rand.DefaultPrng.init(11).random().uintAtMost(u32, 20), + sleepy_time + std.rand.DefaultPrng.init(12).random().uintAtMost(u32, 20), + sleepy_time + std.rand.DefaultPrng.init(13).random().uintAtMost(u32, 20), + sleepy_time + std.rand.DefaultPrng.init(14).random().uintAtMost(u32, 20), + sleepy_time + std.rand.DefaultPrng.init(15).random().uintAtMost(u32, 20), + sleepy_time + std.rand.DefaultPrng.init(16).random().uintAtMost(u32, 20), + sleepy_time + std.rand.DefaultPrng.init(17).random().uintAtMost(u32, 20), + sleepy_time + std.rand.DefaultPrng.init(18).random().uintAtMost(u32, 20), + sleepy_time + std.rand.DefaultPrng.init(19).random().uintAtMost(u32, 20), + sleepy_time + std.rand.DefaultPrng.init(20).random().uintAtMost(u32, 20), + }; + const Runner = struct { + completed: usize = 0, + total: usize = 0, + pub fn run(ctx: *@This(), value: u32, _: usize) void { + std.time.sleep(value); + ctx.completed += 1; + std.debug.assert(ctx.completed <= ctx.total); + } + }; + var runny = try std.heap.page_allocator.create(Runner); + runny.* = .{ .total = huge_array.len }; + try thread_pool.doAndWait(std.heap.page_allocator, null, runny, Runner.run, std.mem.span(huge_array)); + try std.testing.expectEqual(huge_array.len, runny.completed); +} + /// Schedule a batch of tasks to be executed by some thread on the thread pool. pub fn schedule(self: *ThreadPool, batch: Batch) void { // Sanity check diff --git a/src/util.zig b/src/util.zig new file mode 100644 index 000000000000..5ce61e42f61b --- /dev/null +++ b/src/util.zig @@ -0,0 +1,331 @@ +// Things that maybe should go in Zig standard library at some point +const std = @import("std"); + +pub fn Key(comptime Map: type) type { + return FieldType(Map.KV, "key").?; +} + +pub fn Value(comptime Map: type) type { + return FieldType(Map.KV, "value").?; +} + +pub fn fromEntries( + comptime Map: type, + allocator: std.mem.Allocator, + comptime EntryType: type, + entries: EntryType, +) !Map { + var map: Map = undefined; + if (@hasField(Map, "allocator")) { + map = Map.init(allocator); + } else { + map = Map{}; + } + + if (comptime std.meta.trait.isIndexable(EntryType)) { + if (comptime !needsAllocator(Map.ensureUnusedCapacity)) { + try map.ensureUnusedCapacity(entries.len); + } else { + try map.ensureUnusedCapacity(allocator, entries.len); + } + + comptime var i: usize = 0; + + inline while (i < std.meta.fields(EntryType).len) : (i += 1) { + map.putAssumeCapacity(entries[i].@"0", entries[i].@"1"); + } + + return map; + } else if (comptime std.meta.trait.isContainer(EntryType) and @hasDecl(EntryType, "count")) { + if (comptime !needsAllocator(Map.ensureUnusedCapacity)) { + try map.ensureUnusedCapacity(entries.count()); + } else { + try map.ensureUnusedCapacity(allocator, entries.count()); + } + + if (comptime @hasDecl(EntryType, "iterator")) { + var iter = entries.iterator(); + while (iter.next()) |entry| { + map.putAssumeCapacity(entry.@"0", entry.@"1"); + } + + return map; + } + } else if (comptime std.meta.trait.isContainer(EntryType) and std.meta.fields(EntryType).len > 0) { + if (comptime !needsAllocator(Map.ensureUnusedCapacity)) { + try map.ensureUnusedCapacity(std.meta.fields(EntryType).len); + } else { + try map.ensureUnusedCapacity(allocator, std.meta.fields(EntryType).len); + } + + inline for (comptime std.meta.fieldNames(@TypeOf(EntryType))) |entry| { + map.putAssumeCapacity(entry.@"0", entry.@"1"); + } + + return map; + } else if (comptime std.meta.trait.isConstPtr(EntryType) and std.meta.fields(std.meta.Child(EntryType)).len > 0) { + if (comptime !needsAllocator(Map.ensureUnusedCapacity)) { + try map.ensureUnusedCapacity(std.meta.fields(std.meta.Child(EntryType)).len); + } else { + try map.ensureUnusedCapacity(allocator, std.meta.fields(std.meta.Child(EntryType)).len); + } + + comptime var i: usize = 0; + + inline while (i < std.meta.fields(std.meta.Child(EntryType)).len) : (i += 1) { + map.putAssumeCapacity(entries.*[i].@"0", entries.*[i].@"1"); + } + + return map; + } + + @compileError("Cannot construct Map from entries of type " ++ @typeName(EntryType)); +} + +pub fn fromMapLike( + comptime Map: type, + allocator: std.mem.Allocator, + entries: anytype, +) !Map { + var map: Map = undefined; + if (comptime @hasField(Map, "allocator")) { + map = Map.init(allocator); + } else { + map = Map{}; + } + + try map.ensureUnusedCapacity(entries.count()); + + var iter = entries.iterator(); + while (iter.next()) |entry| { + map.putAssumeCapacityNoClobber(entry.key_ptr.*, entry.value_ptr.*); + } + + return map; +} + +pub fn FieldType(comptime Map: type, comptime name: []const u8) ?type { + const i = std.meta.fieldIndex(Map, name) orelse return null; + const field = std.meta.fields(Map)[i]; + return field.field_type; +} + +pub fn Of(comptime ArrayLike: type) type { + if (std.meta.trait.isSlice(ArrayLike)) { + return std.meta.Child(ArrayLike); + } + + if (comptime @hasDecl(ArrayLike, "Elem")) { + return ArrayLike.Elem; + } + + if (comptime @hasField(ArrayLike, "items")) { + return std.meta.Child(FieldType(ArrayLike, "items").?); + } + + if (comptime @hasField(ArrayLike, "ptr")) { + return std.meta.Child(FieldType(ArrayLike, "ptr").?); + } + + @compileError("Cannot infer type within " ++ @typeName(ArrayLike)); +} + +pub inline fn from( + comptime Array: type, + allocator: std.mem.Allocator, + default: anytype, +) !Array { + const DefaultType = @TypeOf(default); + if (comptime std.meta.trait.isSlice(DefaultType)) { + return fromSlice(Array, allocator, DefaultType, default); + } + + if (comptime std.meta.trait.isContainer(DefaultType)) { + if (comptime std.meta.trait.isContainer(Array) and @hasDecl(DefaultType, "put")) { + return fromMapLike(Array, allocator, default); + } + + if (comptime @hasField(DefaultType, "items")) { + if (Of(FieldType(DefaultType, "items").?) == Of(Array)) { + return fromSlice(Array, allocator, @TypeOf(default.items), default.items); + } + } + } + + if (comptime std.meta.trait.isContainer(Array) and @hasDecl(Array, "put")) { + if (comptime std.meta.trait.isConstPtr(DefaultType) and std.meta.fields(std.meta.Child(DefaultType)).len > 0) { + return fromEntries(Array, allocator, @TypeOf(default.*), default.*); + } + return fromEntries(Array, allocator, DefaultType, default); + } + + if (comptime @typeInfo(DefaultType) == .Struct) { + return fromSlice(Array, allocator, DefaultType, default); + } + + if (comptime @typeInfo(DefaultType) == .Array) { + return fromSlice(Array, allocator, []const Of(Array), @as([]const Of(Array), &default)); + } + + return fromSlice(Array, allocator, []const Of(Array), @as([]const Of(Array), default)); +} + +pub fn fromSlice( + comptime Array: type, + allocator: std.mem.Allocator, + comptime DefaultType: type, + default: DefaultType, +) !Array { + var map: Array = undefined; + if (comptime std.meta.trait.isSlice(Array)) {} else if (comptime @hasField(Array, "allocator")) { + map = Array.init(allocator); + } else { + map = Array{}; + } + + // is it a MultiArrayList? + if (comptime !std.meta.trait.isSlice(Array) and @hasField(Array, "bytes")) { + try map.ensureUnusedCapacity(allocator, default.len); + for (default) |elem| { + map.appendAssumeCapacity(elem); + } + + return map; + } else { + var slice: []Of(Array) = undefined; + if (comptime !std.meta.trait.isSlice(Array)) { + // is it an ArrayList with an allocator? + if (comptime !needsAllocator(Array.ensureUnusedCapacity)) { + try map.ensureUnusedCapacity(default.len); + // is it an ArrayList without an allocator? + } else { + try map.ensureUnusedCapacity(allocator, default.len); + } + if (comptime @hasField(Array, "items")) { + map.items.len = default.len; + slice = map.items; + } else if (comptime @hasField(Array, "len")) { + map.len = @intCast(u32, default.len); + slice = map.slice(); + } else { + @compileError("Cannot set length of " ++ @typeName(Array)); + } + } else if (comptime std.meta.trait.isSlice(Array)) { + slice = try allocator.alloc(Of(Array), default.len); + } else if (comptime @hasField(map, "ptr")) { + slice = try allocator.alloc(Of(Array), default.len); + map = .{ + .ptr = slice.ptr, + .len = @truncate(u32, default.len), + .cap = @truncate(u32, default.len), + }; + } + + if (comptime std.meta.trait.isIndexable(DefaultType) and (std.meta.trait.isSlice(DefaultType) or std.meta.trait.is(.Array)(DefaultType))) { + var in = std.mem.sliceAsBytes(default); + var out = std.mem.sliceAsBytes(slice); + @memcpy(out.ptr, in.ptr, in.len); + } else { + @compileError("Needs a more specific type to copy from"); + } + + if (comptime std.meta.trait.isSlice(Array)) { + return @as(Array, slice); + } + + return map; + } +} + +/// Say you need to allocate a bunch of tiny arrays +/// You could just do separate allocations for each, but that is slow +/// With std.ArrayList, pointers invalidate on resize and that means it will crash. +/// So a better idea is to batch up your allocations into one larger allocation +/// and then just make all the arrays point to different parts of the larger allocation +pub fn Batcher(comptime Type: type) type { + return struct { + head: []Type, + + pub fn init(allocator: std.mem.Allocator, count: usize) !@This() { + var all = try allocator.alloc(Type, count); + return @This(){ .head = all }; + } + + pub inline fn done(this: *@This()) void { + std.debug.assert(this.head.len == 0); + } + + pub inline fn eat(this: *@This(), value: Type) *Type { + this.head[0] = value; + var prev = &this.head[0]; + this.head = this.head[1..]; + return prev; + } + + pub inline fn eat1(this: *@This(), value: Type) []Type { + this.head[0] = value; + var prev = this.head[0..1]; + this.head = this.head[1..]; + return prev; + } + + pub inline fn next(this: *@This(), values: anytype) []Type { + this.head[0..values.len].* = values; + var prev = this.head[0..values.len]; + this.head = this.head[values.len..]; + return prev; + } + }; +} + +test "fromEntries" { + const values = try from(std.AutoHashMap(u32, u32), std.heap.page_allocator, .{ + .{ 123, 456 }, + .{ 789, 101112 }, + }); + const mapToMap = try from(std.AutoHashMap(u32, u32), std.heap.page_allocator, values); + try std.testing.expectEqual(values.get(123).?, 456); + try std.testing.expectEqual(values.get(789).?, 101112); + try std.testing.expectEqual(mapToMap.get(123).?, 456); + try std.testing.expectEqual(mapToMap.get(789).?, 101112); +} + +test "from" { + const values = try from( + []const u32, + std.heap.page_allocator, + &.{ 1, 2, 3, 4, 5, 6 }, + ); + try std.testing.expectEqualSlices(u32, &.{ 1, 2, 3, 4, 5, 6 }, values); +} + +test "from arraylist" { + const values = try from( + std.ArrayList(u32), + std.heap.page_allocator, + &.{ 1, 2, 3, 4, 5, 6 }, + ); + try std.testing.expectEqualSlices(u32, &.{ 1, 2, 3, 4, 5, 6 }, values.items); + + const cloned = try from( + std.ArrayListUnmanaged(u32), + std.heap.page_allocator, + values, + ); + + try std.testing.expectEqualSlices(u32, &.{ 1, 2, 3, 4, 5, 6 }, cloned.items); +} + +test "from arraylist with struct" { + const Entry = std.meta.Tuple(&.{ u32, u32 }); + const values = try from( + std.ArrayList(Entry), + std.heap.page_allocator, + &.{ Entry{ 123, 456 }, Entry{ 123, 456 }, Entry{ 123, 456 }, Entry{ 123, 456 } }, + ); + try std.testing.expectEqualSlices(Entry, &[_]Entry{ .{ 123, 456 }, .{ 123, 456 }, .{ 123, 456 }, .{ 123, 456 } }, values.items); +} + +fn needsAllocator(comptime Fn: anytype) bool { + return std.meta.fields(std.meta.ArgsTuple(@TypeOf(Fn))).len > 2; +} From f25867561ccb76490ffc516d5faee299e785395e Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Wed, 8 Feb 2023 21:20:27 -0800 Subject: [PATCH 002/255] Delete bundle_v2.peechy --- src/api/bundle_v2.peechy | 75 ---------------------------------------- 1 file changed, 75 deletions(-) delete mode 100644 src/api/bundle_v2.peechy diff --git a/src/api/bundle_v2.peechy b/src/api/bundle_v2.peechy deleted file mode 100644 index db83d257b906..000000000000 --- a/src/api/bundle_v2.peechy +++ /dev/null @@ -1,75 +0,0 @@ -struct Export { - uint32 part_id; - StringPointer name; -} - - -struct JavascriptBundledPart { - StringPointer code; - - uint32 dependencies_offset; - uint32 dependencies_length; - - uint32 exports_offset; - uint32 exports_length; - - uint32 from_module; - - // The ESM export is this id ("$" + number.toString(16)) - uint32 id; -} - -struct JavascriptBundledModule { - // package-relative path including file extension - StringPointer path; - - uint32 parts_offset; - uint32 parts_length; - - uint32 exports_offset; - uint32 exports_length; - - // index into JavascriptBundle.packages - uint32 package_id; - - // This lets us efficiently compare strings ignoring the extension - byte path_extname_length; -} - -struct JavascriptBundledPackage { - StringPointer name; - StringPointer version; - uint32 hash; - - uint32 modules_offset; - uint32 modules_length; -} - -struct JavascriptBundle { - // These are sorted alphabetically so you can do binary search - JavascriptBundledModule[] modules; - JavascriptBundledPackage[] packages; - - // This is ASCII-encoded so you can send it directly over HTTP - byte[] etag; - - uint32 generated_at; - - byte[] import_from_name; - - // This is what StringPointer refers to - byte[] manifest_string; -} - -message JavascriptBundleContainer { - uint32 bundle_format_version = 1; - - // These go first so if we change JavaScriptBundle we can still read these - LoadedRouteConfig routes = 3; - LoadedFramework framework = 2; - - JavascriptBundle bundle = 4; - - // Don't technically need to store this, but it may be helpful as a sanity check - uint32 code_length = 5; -} From 3bac65ab8b1edea02732cb51da61a3a9a14fb5cc Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Wed, 8 Feb 2023 22:15:35 -0800 Subject: [PATCH 003/255] wip --- src/bundler/bundle_v2.zig | 42 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 02d93204e394..fb49c0bdae5b 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -3269,6 +3269,46 @@ const LinkerContext = struct { } } } + + // Make sure we also track dynamic cross-chunk imports. These need to be + // tracked so we count them as dependencies of this chunk for the purpose + // of hash calculation. + if (chunk_meta.dynamic_imports.count() > 0) { + var dynamic_chunk_indices = chunk_meta.dynamic_imports.keys(); + std.sort.sort(Index.Int, dynamic_chunk_indices, std.sort.asc(Index.Int)); + + var list = chunk.cross_chunk_imports.listManaged(c.allocator); + defer chunk.cross_chunk_imports.update(list); + try list.ensureTotalCapacity(dynamic_chunk_indices.len); + for (dynamic_chunk_indices) |dynamic_chunk_index| { + list.appendAssumeCapacity( + .{ + .import_kind = .dynamic, + .chunk_index = dynamic_chunk_index, + }, + ); + } + } + } + + // Generate cross-chunk exports. These must be computed before cross-chunk + // imports because of export alias renaming, which must consider all export + // aliases simultaneously to avoid collisions. + { + var chunk_metas_ptr = chunk_metas.ptr; + std.debug.assert(chunk_metas.len == chunks.len); + for (chunks) |*chunk| { + var chunk_meta = chunk_metas_ptr[0]; + chunk_metas_ptr += 1; + + if (chunk.content != .javascript) continue; + + var repr = &chunk.content.javascript; + + // TODO: + _ = repr; + _ = chunk_meta; + } } } @@ -4335,7 +4375,7 @@ pub const Chunk = struct { template: PathTemplate = .{}, /// For code splitting - cross_chunk_imports: []CrossChunkImport = &.{}, + cross_chunk_imports: BabyList(CrossChunkImport) = .{}, content: Content, From 5b8f33df16696f6dec7489677ef9b705ce5648b7 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Wed, 8 Feb 2023 23:40:21 -0800 Subject: [PATCH 004/255] wip --- src/bundler/bundle_v2.zig | 2 -- src/install/install.zig | 2 +- src/js_ast.zig | 3 +-- src/js_parser.zig | 2 +- 4 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index fb49c0bdae5b..062db90978c9 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -56,10 +56,8 @@ const Report = @import("../report.zig"); const Linker = linker.Linker; const Resolver = _resolver.Resolver; const TOML = @import("../toml/toml_parser.zig").TOML; -const Schema = @import("../api/bundle_v2.zig").BundleV2; const EntryPoints = @import("./entry_points.zig"); const ThisBundler = @import("../bundler.zig").Bundler; -const StringPointer = Schema.StringPointer; const wyhash = std.hash.Wyhash.hash; const Dependency = js_ast.Dependency; const JSAst = js_ast.Ast; diff --git a/src/install/install.zig b/src/install/install.zig index 5b5346294253..2076003cded0 100644 --- a/src/install/install.zig +++ b/src/install/install.zig @@ -46,7 +46,7 @@ const Integrity = @import("./integrity.zig").Integrity; const clap = @import("bun").clap; const ExtractTarball = @import("./extract_tarball.zig"); const Npm = @import("./npm.zig"); -const Bitset = @import("./bit_set.zig").DynamicBitSetUnmanaged; +const Bitset = bun.bit_set.DynamicBitSetUnmanaged; const z_allocator = @import("../memory_allocator.zig").z_allocator; const Syscall = @import("bun").JSC.Node.Syscall; const RunCommand = @import("../cli/run_command.zig").RunCommand; diff --git a/src/js_ast.zig b/src/js_ast.zig index 21d0f890f787..bf6ef5c5cf9a 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -4748,8 +4748,7 @@ pub const DeclaredSymbol = struct { pub const List = bun.MultiArrayList(DeclaredSymbol); pub fn forEachTopLevelSymbol(decls_: *List, ctx: anytype, comptime Fn: anytype) void { - const FnType = @TypeOf(Fn); - const ReturnType = bun.meta.ReturnOfType(FnType); + const ReturnType = bun.meta.ReturnOfType(Fn); var decls = decls_.slice(); var is_top_levels_bool = decls.items(.is_top_level); var refs = decls.items(.ref); diff --git a/src/js_parser.zig b/src/js_parser.zig index 0c6e83095f1d..ea177f2ce068 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -18920,7 +18920,7 @@ fn NewParser_( } }; - DeclaredSymbol.forEachTopLevelSymbol(&decls, ctx_, Iterator.next); + DeclaredSymbol.forEachTopLevelSymbol(decls, ctx_, Iterator.next); } // Pulling in the exports of this module always pulls in the export part From 50cb5acd92121277856cb8f3fe310516c7ac2d90 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Thu, 9 Feb 2023 21:26:31 -0800 Subject: [PATCH 005/255] wip --- src/bit_set.zig | 17 +++-- src/bundler/bundle_v2.zig | 74 ++++++++++---------- src/cli/bun_command.zig | 2 +- src/js_ast.zig | 138 +++++++++++++++++++++++--------------- src/js_parser.zig | 40 +++++------ src/meta.zig | 2 +- src/string_immutable.zig | 2 +- 7 files changed, 159 insertions(+), 116 deletions(-) diff --git a/src/bit_set.zig b/src/bit_set.zig index b18083cc4561..562a49489596 100644 --- a/src/bit_set.zig +++ b/src/bit_set.zig @@ -879,6 +879,13 @@ pub const DynamicBitSetUnmanaged = struct { } } + pub fn setAll(self: *Self, value: bool) void { + const num_masks = numMasks(self.bit_length); + for (self.masks[0..num_masks]) |*mask| { + mask.* = std.math.boolMask(MaskInt, value); + } + } + /// Flips every bit in the bit set. pub fn toggleAll(self: *Self) void { const bit_length = self.bit_length; @@ -1075,18 +1082,18 @@ pub const AutoBitSet = union(enum) { pub fn isSet(this: *const AutoBitSet, index: usize) bool { return switch (std.meta.activeTag(this.*)) { .static => this.static.isSet(index), - .dynamic => this.dynamic.*.isSet(index), + .dynamic => this.dynamic.isSet(index), }; } - pub fn clone(this: *const AutoBitSet, allocator: std.mem.Allocator) AutoBitSet { + pub fn clone(this: *const AutoBitSet, allocator: std.mem.Allocator) !AutoBitSet { return switch (std.meta.activeTag(this.*)) { .static => AutoBitSet{ .static = this.static }, - .dynamic => AutoBitSet{ .dynamic = this.dynamic.*.clone(allocator) }, + .dynamic => AutoBitSet{ .dynamic = try this.dynamic.clone(allocator) }, }; } - pub fn set(this: *const AutoBitSet, index: usize) void { + pub fn set(this: *AutoBitSet, index: usize) void { switch (std.meta.activeTag(this.*)) { .static => this.static.set(index), .dynamic => this.dynamic.set(index), @@ -1096,7 +1103,7 @@ pub const AutoBitSet = union(enum) { pub fn bytes(this: *const AutoBitSet) []const u8 { return switch (std.meta.activeTag(this.*)) { .static => std.mem.asBytes(&this.static.masks), - .dynamic => this.dynamic.*.bytes(), + .dynamic => this.dynamic.bytes(), }; } diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 062db90978c9..a301414dceb5 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -331,7 +331,7 @@ pub const BundleV2 = struct { package_bundle_map: options.BundlePackage.Map, event_loop: EventLoop, unique_key: u64, - ) !?Schema.JavascriptBundleContainer { + ) !void { _ = try bundler.fs.fs.openTmpDir(); var tmpname_buf: [64]u8 = undefined; bundler.resetStore(); @@ -521,10 +521,10 @@ pub const BundleV2 = struct { this, this.graph.entry_points.items, try this.findReachableFiles(), - std.mem.asBytes(&unique_key), + unique_key, ); - return null; + // return null; } pub fn onParseTaskComplete(parse_result: *ParseTask.Result, this: *BundleV2) void { @@ -1343,26 +1343,33 @@ const LinkerGraph = struct { const Iterator = struct { pub fn next(self: *Ctx, ref: Ref) void { - if (self.top_level_symbol_to_parts_overlay.* == null) { - self.top_level_symbol_to_parts_overlay.* = &graph.meta.items(.top_level_symbol_to_parts_overlay)[self.id]; - } + var overlay = brk: { + if (self.top_level_symbol_to_parts_overlay.*) |out| { + break :brk out; + } + + var out = &self.graph.meta.items(.top_level_symbol_to_parts_overlay)[self.id]; + + self.top_level_symbol_to_parts_overlay.* = out; + break :brk out; + }; - var entry = try self.top_level_symbol_to_parts_overlay.getOrPut(self.graph.allocator, ref); + var entry = overlay.getOrPut(self.graph.allocator, ref) catch unreachable; if (!entry.found_existing) { - entry.value_ptr.* = try bun.from( + entry.value_ptr.* = bun.from( BabyList(u32), self.graph.allocator, &[_]u32{ self.part_id, }, - ); + ) catch unreachable; } else { - try entry.value_ptr.push(self.graph.allocator, self.part_id); + entry.value_ptr.push(self.graph.allocator, self.part_id) catch unreachable; } } }; - js_ast.DeclaredSymbol.forEachTopLevelSymbol(part.declared_symbols, &ctx, Iterator.next); + js_ast.DeclaredSymbol.forEachTopLevelSymbol(&parts.ptr[part_id].declared_symbols, &ctx, Iterator.next); return part_id; } @@ -1620,7 +1627,7 @@ const LinkerContext = struct { for (entry_source_indices) |source_index, entry_id_| { const entry_bit = @truncate(Chunk.EntryPoint.ID, entry_id_); - var entry_bits = try bun.bit_set.DynamicBitSetUnmanaged.initEmpty(this.allocator, this.graph.entry_points.len); + var entry_bits = try bun.bit_set.AutoBitSet.initEmpty(this.allocator, this.graph.entry_points.len); entry_bits.set(entry_bit); // Create a chunk for the entry point here to ensure that the chunk is @@ -1629,12 +1636,11 @@ const LinkerContext = struct { js_chunk_entry.value_ptr.* = .{ .entry_point = .{ - .id = entry_bit, + .entry_point_id = entry_bit, .source_index = source_index, .is_entry_point = true, }, .entry_bits = entry_bits, - .source_index = source_index.get(), .content = .{ .javascript = .{}, }, @@ -1653,14 +1659,17 @@ const LinkerContext = struct { if (!js_chunk_entry.found_existing) { js_chunk_entry.value_ptr.* = .{ .entry_bits = try entry_bits.clone(this.allocator), - .source_index = source_index.get(), + .entry_point = .{ + .source_index = source_index.get(), + }, .content = .{ .javascript = .{}, }, }; } - try js_chunk_entry.value_ptr.files_with_parts_in_chunk.getOrPut(this.allocator, source_index.get()); + var files = try js_chunk_entry.value_ptr.files_with_parts_in_chunk.getOrPut(this.allocator, source_index.get()); + _ = files; } } @@ -2143,7 +2152,7 @@ const LinkerContext = struct { const module_ref = symbols.follow(module_refs[id].?); symbols.get(exports_ref).?.kind = .unbound; symbols.get(module_ref).?.kind = .unbound; - } else if (flags[id].force_include_exports_for_entry_points or export_kind != .cjs) { + } else if (flags[id].force_include_exports_for_entry_point or export_kind != .cjs) { flags[id].needs_exports_variable = true; } @@ -2814,7 +2823,7 @@ const LinkerContext = struct { // Pull in the "__export" symbol if it was used if (export_ref.isValid()) { - c.graph.meta.items(.needs_export_symbol_from_runtime)[id] = true; + c.graph.meta.items(.flags)[id].needs_export_symbol_from_runtime = true; } } } @@ -3331,10 +3340,10 @@ const LinkerContext = struct { const out_dist = distance + 1; // Don't mark this file more than once - if (file_entry_bits[source_index].isSet(source_index, entry_points_count) and !traverse_again) + if (file_entry_bits[source_index].isSet(entry_points_count) and !traverse_again) return; - file_entry_bits[source_index].set(source_index, entry_points_count); + file_entry_bits[source_index].set(entry_points_count); if (comptime bun.Environment.allow_assert) debug( @@ -3791,10 +3800,9 @@ const LinkerContext = struct { .{ wrapper_ref, .{ .count_estimate = 1 } }, }, ) catch unreachable, - .declared_symbols = bun.from( - js_ast.DeclaredSymbol.List, + .declared_symbols = js_ast.DeclaredSymbol.List.fromSlice( c.allocator, - [_]js_ast.DeclaredSymbol{ + &[_]js_ast.DeclaredSymbol{ .{ .ref = c.graph.ast.items(.exports_ref)[id], .is_top_level = true }, .{ .ref = c.graph.ast.items(.module_ref)[id].?, .is_top_level = true }, .{ .ref = c.graph.ast.items(.wrapper_ref)[id].?, .is_top_level = true }, @@ -3845,13 +3853,9 @@ const LinkerContext = struct { .{ wrapper_ref, .{ .count_estimate = 1 } }, }, ) catch unreachable, - .declared_symbols = bun.from( - js_ast.DeclaredSymbol.List, - c.allocator, - [_]js_ast.DeclaredSymbol{ - .{ .ref = wrapper_ref, .is_top_level = true }, - }, - ) catch unreachable, + .declared_symbols = js_ast.DeclaredSymbol.List.fromSlice(c.allocator, &[_]js_ast.DeclaredSymbol{ + .{ .ref = wrapper_ref, .is_top_level = true }, + }) catch unreachable, .dependencies = Dependency.List.init(dependencies), }, ) catch unreachable; @@ -4335,7 +4339,7 @@ pub const ImportTracker = struct { pub const PathTemplate = struct { data: string = "", - placeholder: Placeholder, + placeholder: Placeholder = .{}, pub const Placeholder = struct { dir: []const u8 = "", @@ -4367,7 +4371,7 @@ pub const Chunk = struct { files_with_parts_in_chunk: std.AutoArrayHashMapUnmanaged(Index.Int, void) = .{}, /// We must not keep pointers to this type until all chunks have been allocated. - entry_bits: AutoBitSet = .{}, + entry_bits: AutoBitSet = undefined, final_rel_path: string = "", template: PathTemplate = .{}, @@ -4464,7 +4468,7 @@ pub const Chunk = struct { cross_chunk_suffix_stmts: BabyList(Stmt) = .{}, }; - pub const ImportsFromOtherChunks = std.ArrayHashMapUnmanaged(Index.Int, CrossChunkImport.Item.List, Index.ArrayHashCtx, false); + pub const ImportsFromOtherChunks = std.ArrayHashMapUnmanaged(Index.Int, CrossChunkImport.Item.List, Ref.ArrayHashCtx, false); pub const Content = union(enum) { javascript: JavaScriptChunk, @@ -4473,7 +4477,7 @@ pub const Chunk = struct { pub const CrossChunkImport = struct { chunk_index: Index.Int = 0, - sorted_import_Items: List = &.{}, + sorted_import_Items: List = undefined, pub const Item = struct { export_alias: string = "", @@ -4519,7 +4523,7 @@ pub const CrossChunkImport = struct { result.append(CrossChunkImport{ .chunk_index = chunk_index, - .sorted_import_items = import_items.slice(), + .sorted_import_items = List.fromOwnedSlice(result.allocator, import_items.slice()), }) catch unreachable; } diff --git a/src/cli/bun_command.zig b/src/cli/bun_command.zig index 186b68afa3fa..f2673491fa6d 100644 --- a/src/cli/bun_command.zig +++ b/src/cli/bun_command.zig @@ -178,7 +178,7 @@ pub const BunCommand = struct { // Always generate the client-only bundle // we can revisit this decision if people ask - _ = try BundleV2.generate( + try BundleV2.generate( &this_bundler, allocator, loaded_framework, diff --git a/src/js_ast.zig b/src/js_ast.zig index bf6ef5c5cf9a..4878797e9654 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -4745,71 +4745,103 @@ pub const DeclaredSymbol = struct { ref: Ref, is_top_level: bool = false, - pub const List = bun.MultiArrayList(DeclaredSymbol); - - pub fn forEachTopLevelSymbol(decls_: *List, ctx: anytype, comptime Fn: anytype) void { - const ReturnType = bun.meta.ReturnOfType(Fn); - var decls = decls_.slice(); - var is_top_levels_bool = decls.items(.is_top_level); - var refs = decls.items(.ref); - - if (comptime Environment.enableSIMD) { - const vector_size = 16; - if (refs.len >= vector_size) { - const refs_end = refs.ptr + (refs.len - refs.len % vector_size); - while (refs.ptr != refs_end) { - const vec = @as(@Vector(vector_size, bool), @bitCast([vector_size]bool, is_top_levels_bool.ptr[0..vector_size])); - - if (@reduce(.Max, vec) > 0) { - var j = 0; - while (j < vector_size) : (j += 1) { - if (vec[j]) { - if (comptime ReturnType == void) { - Fn(ctx, refs[j]); - } else if (Fn(ctx, refs[j])) |result| { - refs[j] = result; - } - } - } - } + pub const List = struct { + refs: std.ArrayListUnmanaged(Ref) = .{}, + is_top_level: bun.bit_set.DynamicBitSetUnmanaged = .{}, - is_top_levels_bool = is_top_levels_bool[vector_size..]; - refs = refs[vector_size..]; - } - } + pub fn clone(this: *const List, allocator: std.mem.Allocator) !List { + var refs = try this.refs.clone(allocator); + errdefer refs.deinit(allocator); + + return List{ + .refs = refs, + .is_top_level = try this.is_top_level.clone(allocator), + }; } - const scalar = 8; - while (refs.len >= scalar) : (refs = refs[scalar..]) { - const eight: u64 = @as(u64, is_top_levels_bool[0..scalar].*); - if (eight == 0) { - is_top_levels_bool = is_top_levels_bool[scalar..]; - continue; + pub inline fn len(this: List) usize { + return this.refs.items.len; + } + + pub fn append(this: *List, allocator: std.mem.Allocator, entry: DeclaredSymbol) !void { + try this.ensureUnusedCapacity(allocator, 1); + this.appendAssumeCapacity(entry); + } + + pub fn appendList(this: *List, allocator: std.mem.Allocator, other: List) !void { + try this.ensureUnusedCapacity(allocator, other.len()); + this.appendListAssumeCapacity(other); + } + + pub fn appendListAssumeCapacity(this: *List, other: List) void { + var j = other.refs.items.len; + this.refs.appendSliceAssumeCapacity(other.refs.items); + var is_top_level = &this.is_top_level; + var iter = other.is_top_level.iterator(.{}); + + // TODO: optimize this + while (iter.next()) |i| { + is_top_level.set(j + i); } + } - comptime var j: usize = 0; + pub fn appendAssumeCapacity(this: *List, entry: DeclaredSymbol) void { + this.refs.appendAssumeCapacity(entry.ref); + this.is_top_level.setValue(this.refs.items.len - 1, entry.is_top_level); + } - inline while (j < scalar) : (j += 1) { - if (is_top_levels_bool[j]) { - if (comptime ReturnType == void) { - Fn(ctx, refs[j]); - } else if (Fn(ctx, refs[j])) |result| { - refs[j] = result; - } - } + pub fn ensureTotalCapacity(this: *List, allocator: std.mem.Allocator, count: usize) !void { + try this.ensureUnusedCapacity(allocator, this.refs.capacity -| this.refs.items.len +| count); + } + + pub fn ensureUnusedCapacity(this: *List, allocator: std.mem.Allocator, count: usize) !void { + try this.refs.ensureUnusedCapacity(allocator, count); + if (this.is_top_level.capacity() < this.refs.capacity) { + var prev = this.is_top_level; + this.is_top_level = try bun.bit_set.DynamicBitSetUnmanaged.initEmpty(allocator, this.refs.capacity); + prev.copyInto(this.is_top_level); + prev.deinit(allocator); } + } - is_top_levels_bool = is_top_levels_bool[scalar..]; + pub fn clearRetainingCapacity(this: *List) void { + this.refs.clearRetainingCapacity(); + this.is_top_level.setAll(false); } - while (refs.len > 0) : (refs = refs[1..]) { - if (comptime ReturnType == void) { - Fn(ctx, refs[0]); - } else if (Fn(ctx, refs[0])) |result| { - refs[0] = result; + pub fn deinit(this: *List, allocator: std.mem.Allocator) void { + this.refs.deinit(allocator); + this.is_top_level.deinit(allocator); + } + + pub fn initCapacity(allocator: std.mem.Allocator, capacity: usize) !List { + var refs = try std.ArrayListUnmanaged(Ref).initCapacity(allocator, capacity); + errdefer refs.deinit(allocator); + var is_top_level = try bun.bit_set.DynamicBitSetUnmanaged.initEmpty(allocator, refs.capacity); + return List{ + .refs = refs, + .is_top_level = is_top_level, + }; + } + + pub fn fromSlice(allocator: std.mem.Allocator, entries: []const DeclaredSymbol) !List { + var this = try List.initCapacity(allocator, entries.len); + errdefer this.deinit(allocator); + for (entries) |entry| { + this.appendAssumeCapacity(entry); } - is_top_levels_bool = is_top_levels_bool[1..]; + return this; + } + }; + + pub fn forEachTopLevelSymbol(decls: *List, ctx: anytype, comptime Fn: anytype) void { + var refs = decls.refs.items; + std.debug.assert(refs.len <= decls.is_top_level.capacity()); + var iter = decls.is_top_level.iterator(.{ .direction = .forward }); + while (iter.next()) |index| { + const ref = refs[index]; + Fn(ctx, ref); } } }; diff --git a/src/js_parser.zig b/src/js_parser.zig index ea177f2ce068..d4ad5dd02fba 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -2214,7 +2214,7 @@ pub const ScanPassResult = struct { pub fn reset(scan_pass: *ScanPassResult) void { scan_pass.named_imports.clearRetainingCapacity(); - scan_pass.import_records.shrinkRetainingCapacity(0); + scan_pass.import_records.clearRetainingCapacity(); scan_pass.used_symbols.clearRetainingCapacity(); scan_pass.approximate_newline_count = 0; } @@ -2596,7 +2596,8 @@ pub const Parser = struct { var import_record: *ImportRecord = &p.import_records.items[import_record_id]; import_record.tag = .bun_test; - var declared_symbols = try p.allocator.alloc(js_ast.DeclaredSymbol, items_count); + var declared_symbols = js_ast.DeclaredSymbol.List{}; + try declared_symbols.ensureTotalCapacity(p.allocator, items_count); var clauses: []js_ast.ClauseItem = p.allocator.alloc(js_ast.ClauseItem, items_count) catch unreachable; var clause_i: usize = 0; inline for (comptime std.meta.fieldNames(Jest)) |symbol_name| { @@ -2607,7 +2608,7 @@ pub const Parser = struct { .alias_loc = logger.Loc.Empty, .original_name = "", }; - declared_symbols[clause_i] = .{ .ref = @field(jest, symbol_name), .is_top_level = true }; + declared_symbols.appendAssumeCapacity(.{ .ref = @field(jest, symbol_name), .is_top_level = true }); clause_i += 1; } } @@ -2628,7 +2629,7 @@ pub const Parser = struct { before.append(js_ast.Part{ .stmts = part_stmts, .declared_symbols = declared_symbols, - .import_record_indices = import_record_indices, + .import_record_indices = bun.BabyList(u32).init(import_record_indices), .tag = .bun_test, }) catch unreachable; } @@ -4218,7 +4219,7 @@ fn NewParser_( for (symbol_use_refs) |ref, i| { symbols[ref.innerIndex()].use_count_estimate -|= symbol_use_values[i].count_estimate; } - const declared_refs = part.declared_symbols.items(.ref); + const declared_refs = part.declared_symbols.refs.items; for (declared_refs) |declared| { symbols[declared.innerIndex()].use_count_estimate = 0; // } @@ -9396,7 +9397,7 @@ fn NewParser_( .text = comment.text, }, p.lexer.loc())); } - p.lexer.comments_to_preserve_before.shrinkRetainingCapacity(0); + p.lexer.comments_to_preserve_before.clearRetainingCapacity(); if (p.lexer.token == eend) { break; @@ -12856,16 +12857,15 @@ fn NewParser_( for (previous_parts) |*previous_part, j| { if (previous_part.stmts.len == 0) continue; - const declared_symbols = previous_part.declared_symbols.slice(); - var refs = declared_symbols.items(.ref); + var refs = previous_part.declared_symbols.refs.items; for (refs) |ref| { if (p.symbol_uses.contains(ref)) { // we move this part to our other file for (previous_parts[0..j]) |*this_part| { if (this_part.stmts.len == 0) continue; - const this_declared_symbols = this_part.declared_symbols; - var other_refs = this_declared_symbols.items(.ref); + const other_refs = this_part.declared_symbols.refs.items; + for (other_refs) |other_ref| { if (previous_part.symbol_uses.contains(other_ref)) { try p.bun_plugin.hoisted_stmts.appendSlice(p.allocator, this_part.stmts); @@ -12884,9 +12884,9 @@ fn NewParser_( // Single-statement part which uses Bun.plugin() // It's effectively an unrelated file - if (p.declared_symbols.len > 0 or p.symbol_uses.count() > 0) { + if (p.declared_symbols.len() > 0 or p.symbol_uses.count() > 0) { p.clearSymbolUsagesFromDeadPart(.{ .stmts = undefined, .declared_symbols = p.declared_symbols, .symbol_uses = p.symbol_uses }); - p.declared_symbols.len = 0; + p.declared_symbols.clearRetainingCapacity(); p.import_records_for_current_part.items.len = 0; } return; @@ -12907,12 +12907,12 @@ fn NewParser_( .can_be_removed_if_unused = p.stmtsCanBeRemovedIfUnused(_stmts), }); p.symbol_uses = .{}; - p.declared_symbols.len = 0; - } else if (p.declared_symbols.len > 0 or p.symbol_uses.count() > 0) { + p.declared_symbols.clearRetainingCapacity(); + } else if (p.declared_symbols.len() > 0 or p.symbol_uses.count() > 0) { // if the part is dead, invalidate all the usage counts p.clearSymbolUsagesFromDeadPart(.{ .stmts = undefined, .declared_symbols = p.declared_symbols, .symbol_uses = p.symbol_uses }); - p.declared_symbols.len = 0; + p.declared_symbols.clearRetainingCapacity(); p.import_records_for_current_part.clearRetainingCapacity(); } } @@ -18338,8 +18338,8 @@ fn NewParser_( // with no statements while (i < parts.len) : (i += 1) { var part = parts[i]; - p.import_records_for_current_part.shrinkRetainingCapacity(0); - p.declared_symbols.shrinkRetainingCapacity(0); + p.import_records_for_current_part.clearRetainingCapacity(); + p.declared_symbols.clearRetainingCapacity(); var result = try ImportScanner.scan(P, p, part.stmts, commonjs_wrapper_expr != null); kept_import_equals = kept_import_equals or result.kept_import_equals; @@ -18347,13 +18347,13 @@ fn NewParser_( part.stmts = result.stmts; if (part.stmts.len > 0 or (i == js_ast.namespace_export_part_index and p.options.bundle)) { - if (p.module_scope.contains_direct_eval and part.declared_symbols.len > 0) { + if (p.module_scope.contains_direct_eval and part.declared_symbols.len() > 0) { // If this file contains a direct call to "eval()", all parts that // declare top-level symbols must be kept since the eval'd code may // reference those symbols. part.can_be_removed_if_unused = false; } - if (part.declared_symbols.len == 0) { + if (part.declared_symbols.len() == 0) { part.declared_symbols = p.declared_symbols.clone(p.allocator) catch unreachable; } else { part.declared_symbols.appendList(p.allocator, p.declared_symbols) catch unreachable; @@ -18368,7 +18368,7 @@ fn NewParser_( parts[parts_end] = part; parts_end += 1; } - p.declared_symbols.len = 0; + p.declared_symbols.clearRetainingCapacity(); } // We need to iterate multiple times if an import-equals statement was diff --git a/src/meta.zig b/src/meta.zig index cd171cc29c80..5bf99bbaaa6e 100644 --- a/src/meta.zig +++ b/src/meta.zig @@ -7,7 +7,7 @@ pub fn ReturnOf(comptime function: anytype) type { } pub fn ReturnOfType(comptime Type: type) type { - const typeinfo: std.builtin.Type.Fn = @typeInfo(Type); + const typeinfo: std.builtin.Type.Fn = @typeInfo(Type).Fn; return typeinfo.return_type orelse void; } diff --git a/src/string_immutable.zig b/src/string_immutable.zig index eb51defae62b..443a233dc173 100644 --- a/src/string_immutable.zig +++ b/src/string_immutable.zig @@ -3885,7 +3885,7 @@ pub fn sortDesc(in: []string) void { pub const StringArrayByIndexSorter = struct { keys: []const []const u8, - pub fn lessThan(sorter: *@This(), a: usize, b: usize) bool { + pub fn lessThan(sorter: *const @This(), a: usize, b: usize) bool { return strings.order(sorter.keys[a], sorter.keys[b]) == .lt; } From 4ea923a38f840398417014df5d63bf914dd7ff0b Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Thu, 9 Feb 2023 22:11:57 -0800 Subject: [PATCH 006/255] wip --- src/bundler/bundle_v2.zig | 121 ++++++++++++++++++++------------------ src/string_builder.zig | 16 ----- 2 files changed, 65 insertions(+), 72 deletions(-) diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index a301414dceb5..10cb727d0cbc 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -1688,7 +1688,7 @@ const LinkerContext = struct { } // Determine the order of JS files (and parts) within the chunk ahead of time - try this.findImportedAllPartsInJSOrder(temp_allocator, chunks); + try this.findAllImportedPartsInJSOrder(temp_allocator, chunks); const unique_key_item_len = std.fmt.count("{any}C{d:8}", .{ bun.fmt.hexIntLower(unique_key), chunks.len }); var unique_key_builder = try bun.StringBuilder.initCapacity(this.allocator, unique_key_item_len); @@ -1722,13 +1722,12 @@ const LinkerContext = struct { } pub fn findAllImportedPartsInJSOrder(this: *LinkerContext, temp_allocator: std.mem.Allocator, chunks: []Chunk) !void { - var part_ranges_shared = std.ArrayList(PartRange).init(this.allocator); - var parts_prefix_shared = std.ArrayList(PartRange).init(this.allocator); + var part_ranges_shared = std.ArrayList(PartRange).init(temp_allocator); + var parts_prefix_shared = std.ArrayList(PartRange).init(temp_allocator); defer part_ranges_shared.deinit(); defer parts_prefix_shared.deinit(); for (chunks) |*chunk| { try this.findImportedPartsInJSOrder( - temp_allocator, chunk, &part_ranges_shared, &parts_prefix_shared, @@ -1760,9 +1759,9 @@ const LinkerContext = struct { const Visitor = struct { entry_bits: *const AutoBitSet, - wraps: []const WrapKind, - parts: [][]js_ast.Part, - import_records: [][]ImportRecord, + flags: []const JSMeta.Flags, + parts: []BabyList(js_ast.Part), + import_records: []BabyList(ImportRecord), files: std.ArrayList(Index.Int) = undefined, part_ranges: std.ArrayList(PartRange) = undefined, visited: std.AutoHashMap(Index.Int, void) = undefined, @@ -1776,22 +1775,22 @@ const LinkerContext = struct { ) void { if (ranges.items.len > 0) { var last_range = &ranges.items[ranges.items.len - 1]; - if (last_range.source_index == source_index and last_range.part_index_end == part_index) { + if (last_range.source_index.get() == source_index and last_range.part_index_end == part_index) { last_range.part_index_end += 1; return; } } ranges.append(.{ - .source_index = source_index, - .part_index_start = part_index, + .source_index = Index.init(source_index), + .part_index_begin = part_index, .part_index_end = part_index + 1, }) catch unreachable; } pub fn visit(v: *@This(), source_index: Index.Int) void { - if (source_index.isInvalid()) return; - const visited_entry = v.visited.getOrPut(source_index.get()) catch unreachable; + if (source_index == Index.invalid.value) return; + const visited_entry = v.visited.getOrPut(source_index) catch unreachable; if (visited_entry.found_existing) return; var is_file_in_chunk = v.entry_bits.isSet( @@ -1799,14 +1798,14 @@ const LinkerContext = struct { ); // Wrapped files can't be split because they are all inside the wrapper - const can_be_split = v.wraps[source_index] == .none; + const can_be_split = v.flags[source_index].wrap == .none; - const parts = v.parts[source_index]; + const parts = v.parts[source_index].slice(); if (can_be_split and is_file_in_chunk and parts[js_ast.namespace_export_part_index].is_live) { appendOrExtendRange(&v.part_ranges, source_index, js_ast.namespace_export_part_index); } - const records = v.import_records[source_index]; + const records = v.import_records[source_index].slice(); for (parts) |*part, part_index_| { const part_index = @truncate(u32, part_index_); @@ -1846,7 +1845,7 @@ const LinkerContext = struct { if (!can_be_split) { v.parts_prefix.append( .{ - .source_index = source_index, + .source_index = Index.init(source_index), .part_index_begin = 0, .part_index_end = @truncate(u32, parts.len), }, @@ -1864,17 +1863,16 @@ const LinkerContext = struct { .part_ranges = part_ranges_shared.*, .parts_prefix = parts_prefix_shared.*, .visited = std.AutoHashMap(Index.Int, void).init(this.allocator), - .wraps = this.graph.meta.items(.flags), + .flags = this.graph.meta.items(.flags), .parts = this.graph.ast.items(.parts), .import_records = this.graph.ast.items(.import_records), .entry_bits = chunk.entryBits(), .c = this, }; defer { - part_ranges_shared.* = visitor.part_ranges_shared; - parts_prefix_shared.* = visitor.parts_prefix_shared; + part_ranges_shared.* = visitor.part_ranges; + parts_prefix_shared.* = visitor.parts_prefix; visitor.visited.deinit(); - visitor.parts_prefix.deinit(); } visitor.visit(Index.runtime.value); @@ -1886,7 +1884,7 @@ const LinkerContext = struct { var parts_in_chunk_order = try this.allocator.alloc(PartRange, visitor.part_ranges.items.len + visitor.parts_prefix.items.len); std.mem.copy(PartRange, parts_in_chunk_order, visitor.parts_prefix.items); std.mem.copy(PartRange, parts_in_chunk_order[visitor.parts_prefix.items.len..], visitor.part_ranges.items); - chunk.content.javascript.parts_in_chunk_order = parts_in_chunk_order; + chunk.content.javascript.parts_in_chunk_in_order = parts_in_chunk_order; } pub fn scanImportsAndExports(this: *LinkerContext) !void { @@ -3042,7 +3040,7 @@ const LinkerContext = struct { pub noinline fn computeCrossChunkDependencies(c: *LinkerContext, chunks: []Chunk) !void { var js_chunks_count: usize = 0; for (chunks) |*chunk| { - js_chunks_count += chunk.content == .javascript; + js_chunks_count += @boolToInt(chunk.content == .javascript); } // TODO: remove this branch before release. We are keeping it to assert this code is correct @@ -3055,53 +3053,55 @@ const LinkerContext = struct { const ChunkMeta = struct { imports: std.ArrayHashMap(Ref, void, Ref.ArrayHashCtx, false), exports: std.ArrayHashMap(Ref, void, Ref.ArrayHashCtx, false), - dynamic_imports: std.ArrayHashMap(Index.Int, void, Ref.ArrayHashCtx, false), + dynamic_imports: std.AutoArrayHashMap(Index.Int, void), }; var chunk_metas = try c.allocator.alloc(ChunkMeta, chunks.len); - for (chunks) |*meta| { + for (chunk_metas) |*meta| { // these must be global allocator - meta.* = .{ - .imports = try std.ArrayHashMap(Ref, void, Ref.ArrayHashCtx, false).init(bun.default_allocator), - .exports = try std.ArrayHashMap(Ref, void, Ref.ArrayHashCtx, false).init(bun.default_allocator), - .dynamic_imports = try std.ArrayHashMap(Index.Int, void, Ref.ArrayHashCtx, false).init(bun.default_allocator), + meta.* = comptime ChunkMeta{ + .imports = std.ArrayHashMap(Ref, void, Ref.ArrayHashCtx, false).init(bun.default_allocator), + .exports = std.ArrayHashMap(Ref, void, Ref.ArrayHashCtx, false).init(bun.default_allocator), + .dynamic_imports = std.AutoArrayHashMap(Index.Int, void).init(bun.default_allocator), }; } defer { - for (chunk_metas) |meta| { + for (chunk_metas) |*meta| { meta.imports.deinit(); meta.exports.deinit(); meta.dynamic_imports.deinit(); } - c.allocators.free(chunk_metas); + c.allocator.free(chunk_metas); } const CrossChunkDependencies = struct { chunk_meta: []ChunkMeta, - parts: [][]js_ast.Part, - import_records: [][]bun.ImportRecord, + chunks: []Chunk, + parts: []BabyList(js_ast.Part), + import_records: []BabyList(bun.ImportRecord), flags: []const JSMeta.Flags, - entry_point_chunk_indices: [][]Index.Int, + entry_point_chunk_indices: []Index.Int, imports_to_bind: []RefImportData, - wrapper_refs: []const Ref, - sorted_and_filtered_export_aliases: []const string, + wrapper_refs: []const ?Ref, + sorted_and_filtered_export_aliases: []const []const string, resolved_exports: []const RefExportData, ctx: *LinkerContext, symbols: *Symbol.Map, pub fn walk(deps: *@This(), chunk: *Chunk, chunk_index: usize) void { var chunk_meta = &deps.chunk_meta[chunk_index]; - const entry_point_chunk_indices = deps.entry_point_chunk_indices[chunk_index]; + const entry_point_chunk_indices = deps.entry_point_chunk_indices; // Go over each file in this chunk for (chunk.files_with_parts_in_chunk.keys()) |source_index| { if (chunk.content != .javascript) continue; // Go over each part in this file that's marked for inclusion in this chunk - var parts = deps.parts[source_index]; - var import_records = deps.import_records[source_index]; + var parts = deps.parts[source_index].slice(); + var import_records = deps.import_records[source_index].slice(); const imports_to_bind = deps.imports_to_bind[source_index]; const wrap = deps.flags[source_index].wrap; - const wrapper_ref = deps.wrapper_refs[source_index]; + const wrapper_ref = deps.wrapper_refs[source_index].?; + var _chunks = deps.chunks; for (parts) |*part| { if (!part.is_live) @@ -3112,14 +3112,14 @@ const LinkerContext = struct { var import_record = &import_records[import_record_id]; if (import_record.source_index.isValid() and deps.ctx.isExternalDynamicImport(import_record, source_index)) { const other_chunk_index = entry_point_chunk_indices[import_record.source_index.get()]; - import_record.path.text = chunks[other_chunk_index].unique_key; + import_record.path.text = _chunks[other_chunk_index].unique_key; import_record.source_index = Index.invalid; // Track this cross-chunk dynamic import so we make sure to // include its hash when we're calculating the hashes of all // dependencies of this chunk. if (other_chunk_index != chunk_index) - chunk_meta.dynamic_imports.put(other_chunk_index) catch unreachable; + chunk_meta.dynamic_imports.put(other_chunk_index, void{}) catch unreachable; } } @@ -3128,7 +3128,7 @@ const LinkerContext = struct { // the same name should already be marked as all being in a single // chunk. In that case this will overwrite the same value below which // is fine. - deps.symbols.assignChunkIndex(part.declared_symbols, chunk_index); + deps.symbols.assignChunkIndex(part.declared_symbols, @truncate(u32, chunk_index)); for (part.symbol_uses.keys()) |ref_| { var ref = ref_; @@ -3146,7 +3146,7 @@ const LinkerContext = struct { // If this is imported from another file, follow the import // reference and reference the symbol in that file instead if (imports_to_bind.get(ref)) |import_data| { - ref = import_data.ref; + ref = import_data.data.import_ref; symbol = deps.symbols.get(ref).?; } else if (wrap == .cjs and ref.eql(wrapper_ref)) { // The only internal symbol that wrapped CommonJS files export @@ -3159,7 +3159,7 @@ const LinkerContext = struct { // identifier. In that case we want to pull in the namespace symbol // instead. The namespace symbol stores the result of "require()". if (symbol.namespace_alias) |*namespace_alias| { - ref = namespace_alias.ref; + ref = namespace_alias.namespace_ref; } // We must record this relationship even for symbols that are not @@ -3175,7 +3175,7 @@ const LinkerContext = struct { // Include the exports if this is an entry point chunk if (chunk.content == .javascript) { if (chunk.entry_point.is_entry_point) { - const flags = deps.flags[chunk.entry_point.source_index.get()]; + const flags = deps.flags[chunk.entry_point.source_index]; if (flags.wrap != .cjs) { for (deps.sorted_and_filtered_export_aliases[chunk.entry_point.source_index]) |alias| { const export_ = deps.resolved_exports[chunk.entry_point.source_index].get(alias).?; @@ -3191,7 +3191,7 @@ const LinkerContext = struct { // identifier. In that case we want to pull in the namespace symbol // instead. The namespace symbol stores the result of "require()". if (deps.symbols.get(target_ref).?.namespace_alias) |namespace_alias| { - target_ref = namespace_alias.ref; + target_ref = namespace_alias.namespace_ref; } chunk_meta.imports.put(target_ref, void{}) catch unreachable; @@ -3200,12 +3200,12 @@ const LinkerContext = struct { // Ensure "exports" is included if the current output format needs it if (flags.force_include_exports_for_entry_point) { - chunk_meta.imports.put(deps.wrapper_refs[chunk.entry_point.source_index.get()], void{}) catch unreachable; + chunk_meta.imports.put(deps.wrapper_refs[chunk.entry_point.source_index].?, void{}) catch unreachable; } // Include the wrapper if present if (flags.wrap != .none) { - chunk_meta.imports.put(deps.wrapper_refs[chunk.entry_point.source_index], void{}) catch unreachable; + chunk_meta.imports.put(deps.wrapper_refs[chunk.entry_point.source_index].?, void{}) catch unreachable; } } } @@ -3228,10 +3228,11 @@ const LinkerContext = struct { .symbols = &c.graph.symbols, }; - c.parse_graph.pool.pool.doPtr( + try c.parse_graph.pool.pool.doPtr( c.allocator, &c.wait_group, - cross_chunk_dependencies, + &cross_chunk_dependencies, + CrossChunkDependencies.walk, chunks, ); @@ -3252,7 +3253,7 @@ const LinkerContext = struct { if (symbol.chunk_index) |other_chunk_index| { if (@as(usize, other_chunk_index) != chunk_index) { { - var entry = try js.imports_from_other_chunks.getOrPutValue(c.allocator, .{}); + var entry = try js.imports_from_other_chunks.getOrPutValue(c.allocator, other_chunk_index, .{}); try entry.value_ptr.push(c.allocator, .{ .ref = import_ref, }); @@ -3271,8 +3272,11 @@ const LinkerContext = struct { if (other_chunk_index == chunk_index or other_chunk.content != .javascript) continue; if (other_chunk.entry_bits.isSet(chunk.entry_point.entry_point_id)) { - var imports = js.imports_from_other_chunks.get(c.allocator, @truncate(u32, other_chunk_index)) orelse .{}; - try js.imports_from_other_chunks.put(c.allocator, other_chunk_index, imports); + _ = js.imports_from_other_chunks.getOrPutValue( + c.allocator, + @truncate(u32, other_chunk_index), + CrossChunkImport.Item.List{}, + ) catch unreachable; } } } @@ -3282,7 +3286,7 @@ const LinkerContext = struct { // of hash calculation. if (chunk_meta.dynamic_imports.count() > 0) { var dynamic_chunk_indices = chunk_meta.dynamic_imports.keys(); - std.sort.sort(Index.Int, dynamic_chunk_indices, std.sort.asc(Index.Int)); + std.sort.sort(Index.Int, dynamic_chunk_indices, void{}, std.sort.asc(Index.Int)); var list = chunk.cross_chunk_imports.listManaged(c.allocator); defer chunk.cross_chunk_imports.update(list); @@ -4377,7 +4381,7 @@ pub const Chunk = struct { template: PathTemplate = .{}, /// For code splitting - cross_chunk_imports: BabyList(CrossChunkImport) = .{}, + cross_chunk_imports: BabyList(ChunkImport) = .{}, content: Content, @@ -4468,13 +4472,18 @@ pub const Chunk = struct { cross_chunk_suffix_stmts: BabyList(Stmt) = .{}, }; - pub const ImportsFromOtherChunks = std.ArrayHashMapUnmanaged(Index.Int, CrossChunkImport.Item.List, Ref.ArrayHashCtx, false); + pub const ImportsFromOtherChunks = std.AutoArrayHashMapUnmanaged(Index.Int, CrossChunkImport.Item.List); pub const Content = union(enum) { javascript: JavaScriptChunk, }; }; +pub const ChunkImport = struct { + chunk_index: u32, + import_kind: ImportKind, +}; + pub const CrossChunkImport = struct { chunk_index: Index.Int = 0, sorted_import_Items: List = undefined, diff --git a/src/string_builder.zig b/src/string_builder.zig index 01447242be60..2a4495ac1967 100644 --- a/src/string_builder.zig +++ b/src/string_builder.zig @@ -5,14 +5,11 @@ const copy = @import("std").mem.copy; const Env = @import("./env.zig"); const bun = @import("bun"); const StringBuilder = @This(); -const DebugHashTable = if (Env.allow_assert) std.AutoHashMapUnmanaged(u64, void) else void; len: usize = 0, cap: usize = 0, ptr: ?[*]u8 = null, -debug_only_checker: DebugHashTable = DebugHashTable{}, - pub fn initCapacity( allocator: std.mem.Allocator, cap: usize, @@ -21,15 +18,11 @@ pub fn initCapacity( .cap = cap, .len = 0, .ptr = (try allocator.alloc(u8, cap)).ptr, - .debug_only_checker = if (comptime Env.allow_assert) DebugHashTable.init(bun.default_allocator) else DebugHashTable{}, }; } pub fn count(this: *StringBuilder, slice: string) void { this.cap += slice.len; - if (comptime Env.allow_assert) { - _ = this.debug_only_checker.getOrPut(bun.default_allocator, bun.hash(slice)) catch unreachable; - } } pub fn allocate(this: *StringBuilder, allocator: Allocator) !void { @@ -42,10 +35,6 @@ pub fn deinit(this: *StringBuilder, allocator: Allocator) void { if (this.ptr == null or this.cap == 0) return; allocator.free(this.ptr.?[0..this.cap]); this.ptr = null; - if (comptime Env.allow_assert) { - this.debug_only_checker.deinit(bun.default_allocator); - this.debug_only_checker = .{}; - } } pub fn append(this: *StringBuilder, slice: string) string { @@ -54,11 +43,6 @@ pub fn append(this: *StringBuilder, slice: string) string { assert(this.ptr != null); // must call allocate first } - if (comptime Env.allow_assert) { - if (this.debug_only_checker.count() > 0) - assert(this.debug_only_checker.contains(bun.hash(slice))); - } - bun.copy(u8, this.ptr.?[this.len..this.cap], slice); const result = this.ptr.?[this.len..this.cap][0..slice.len]; this.len += slice.len; From 148c1ae152cff6ea3b23122c14d59631f58ba924 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Thu, 9 Feb 2023 23:27:03 -0800 Subject: [PATCH 007/255] wip --- src/bundler/bundle_v2.zig | 141 +++++++++++++++++++++++++++++++++++++- src/renamer.zig | 45 ++++++++++++ 2 files changed, 183 insertions(+), 3 deletions(-) diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 10cb727d0cbc..e049ef792e2f 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -71,6 +71,7 @@ const Stmt = js_ast.Stmt; const Expr = js_ast.Expr; const Binding = js_ast.Binding; const AutoBitSet = bun.bit_set.AutoBitSet; +const renamer = @import("../renamer.zig"); pub const ThreadPool = struct { pool: ThreadPoolLib = undefined, @@ -3308,6 +3309,12 @@ const LinkerContext = struct { { var chunk_metas_ptr = chunk_metas.ptr; std.debug.assert(chunk_metas.len == chunks.len); + var r = renamer.ExportRenamer.init(c.allocator); + defer r.deinit(); + + var stable_ref_list = std.ArrayList(StableRef).init(c.allocator); + defer stable_ref_list.deinit(); + for (chunks) |*chunk| { var chunk_meta = chunk_metas_ptr[0]; chunk_metas_ptr += 1; @@ -3316,13 +3323,131 @@ const LinkerContext = struct { var repr = &chunk.content.javascript; - // TODO: - _ = repr; - _ = chunk_meta; + switch (c.options.output_format) { + .esm => { + stable_ref_list = c.sortedCrossChunkExportItems(chunk_meta.exports, stable_ref_list); + var clause_items = BabyList(js_ast.ClauseItem).initCapacity(c.allocator, stable_ref_list.items.len) catch unreachable; + for (stable_ref_list.items) |stable_ref| { + r.clearRetainingCapacity(); + const alias = r.nextRenamedName(c.graph.symbols.get(stable_ref.ref).?.original_name); + + clause_items.appendAssumeCapacity( + .{ + .name = .{ + .ref = stable_ref.ref, + .loc = Logger.Loc.Empty{}, + }, + .alias = alias, + }, + ); + repr.exports_to_other_chunks.put( + c.allocator, + stable_ref.ref, + alias, + ) catch unreachable; + } + + if (clause_items.items.len > 0) { + var stmts = BabyList(js_ast.Stmt).initCapacity(c.allocator, 1) catch unreachable; + var export_clause = c.allocator.create(js_ast.S.ExportClause) catch unreachable; + export_clause.* = .{ + .items = clause_items.items, + .is_single_line = false, + }; + stmts.appendAssumeCapacity(.{ + .data = .{ + .s_export_clause = export_clause, + }, + .loc = Logger.Loc.Empty, + }); + repr.cross_chunk_suffix_stmts = stmts; + } + }, + else => bun.unreachablePanic("Unexpected output format", .{}), + } + } + } + + // Generate cross-chunk imports. These must be computed after cross-chunk + // exports because the export aliases must already be finalized so they can + // be embedded in the generated import statements. + { + var list = CrossChunkImport.List.init(c.allocator); + defer list.deinit(); + + var cross_chunk_prefix_stmts = BabyList(js_ast.Stmt){}; + + for (chunks) |*chunk| { + if (chunk.content != .javascript) continue; + var repr = &chunk.content.javascript; + + list.clearRetainingCapacity(); + CrossChunkImport.sortedCrossChunkImports(&list, chunks, &repr.imports_from_other_chunks) catch unreachable; + var cross_chunk_imports: []CrossChunkImport = list.items; + for (cross_chunk_imports) |cross_chunk_import| { + switch (c.options.output_format) { + .esm => { + const import_record_index = @truncate(u32, chunk.cross_chunk_imports.len); + + var clauses = std.ArrayList(js_ast.ClauseItem).initCapacity(c.allocator, cross_chunk_import.sorted_import_Items.items.len) catch unreachable; + for (cross_chunk_import.sorted_import_Items.items) |item| { + clauses.appendAssumeCapacity(.{ + .name = .{ + .ref = item.ref, + .loc = Logger.Loc.Empty{}, + }, + .alias = item.export_alias, + }); + } + + chunk.cross_chunk_imports.append(c.allocator, .{ + .import_kind = .stmt, + .chunk_index = cross_chunk_import.chunk_index, + }) catch unreachable; + + var import = c.allocator.create(js_ast.S.Import) catch unreachable; + import.* = .{ + .items = clauses.items, + .import_record_index = import_record_index, + }; + cross_chunk_prefix_stmts.append( + c.allocator, + .{ + .data = .{ + .s_import = import, + }, + .loc = Logger.Loc.Empty, + }, + ); + }, + else => bun.unreachablePanic("Unexpected output format", .{}), + } + } + + repr.cross_chunk_prefix_stmts = cross_chunk_prefix_stmts; } } } + // Sort cross-chunk exports by chunk name for determinism + fn sortedCrossChunkExportItems( + c: *LinkerContext, + export_refs: RefVoidMap, + list: std.ArrayList(StableRef), + ) std.ArrayList(StableRef) { + var result = list; + result.clearRetainingCapacity(); + result.ensureTotalCapacity(export_refs.count()) catch unreachable; + for (export_refs.keys()) |export_ref| { + result.appendAssumeCapacity(.{ + .source_index = c.graph.stable_source_indices[export_ref.sourceIndex()], + .ref = export_ref, + }); + } + std.sort.sort(StableRef, result.items, void{}, StableRef.isLessThan); + return result; + } + pub fn markFileReachableForCodeSplitting( c: *LinkerContext, source_index: Index.Int, @@ -4301,6 +4426,16 @@ pub const PartRange = struct { part_index_end: u32 = 0, }; +const StableRef = packed struct { + stable_source_index: Index.Int, + ref: Ref, + + pub fn isLessThan(a: StableRef, b: StableRef) bool { + return a.stable_source_index < b.stable_source_index or + (a.stable_source_index == b.stable_source_index and a.ref.innerIndex() < b.ref.innerIndex()); + } +}; + pub const ImportTracker = struct { source_index: Index = Index.invalid, name_loc: Logger.Loc = Logger.Loc.Empty, diff --git a/src/renamer.zig b/src/renamer.zig index f3826f45048c..37087b9f8740 100644 --- a/src/renamer.zig +++ b/src/renamer.zig @@ -68,3 +68,48 @@ pub const DisabledRenamer = struct { @compileError("DisabledRunner called"); } }; + +pub const ExportRenamer = struct { + string_buffer: bun.MutableString = undefined, + used: bun.StringHashMap(u32) = undefined, + + pub fn init(allocator: *std.mem.Allocator) ExportRenamer { + return ExportRenamer{ + .string_buffer = MutableString.initEmpty(allocator), + .used = bun.StringHashMap(u32).init(allocator), + }; + } + + pub fn clearRetainingCapacity(this: *ExportRenamer) void { + this.used.clearRetainingCapacity(); + this.string_buffer.reset(); + } + + pub fn nextRenamedName(this: *ExportRenamer, input: []const u8) string { + var entry = this.used.getOrPut(input) catch unreachable; + var tries: u32 = 1; + if (entry.found_existing) { + while (true) { + this.string_buffer.reset(); + var writer = this.string_buffer.writer(); + writer.print("{s}{d}", .{ input, tries }); + tries += 1; + var attempt = this.string_buffer.toOwnedSliceLeaky(); + entry = this.used.getOrPut() catch unreachable; + if (!entry.found_existing) { + const to_use = this.string_buffer.allocator.dupe(u8, attempt) catch unreachable; + entry.key_ptr.* = to_use; + entry.value_ptr.* = tries; + + entry = this.used.getOrPut(input) catch unreachable; + entry.value_ptr.* = tries; + return to_use; + } + } + } else { + entry.value_ptr.* = tries; + } + + return entry.key_ptr.*; + } +}; From 2ebe686d8e27d11c369d7cca6fd97691e879fe34 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Fri, 10 Feb 2023 00:01:06 -0800 Subject: [PATCH 008/255] wip --- src/bundler/bundle_v2.zig | 45 +++++++++++++++++++++++---------------- src/js_ast.zig | 2 +- src/renamer.zig | 15 ++++++++----- 3 files changed, 38 insertions(+), 24 deletions(-) diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index e049ef792e2f..74ea90a146a2 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -1028,6 +1028,7 @@ const ParseTask = struct { const IdentityContext = @import("../identity_context.zig").IdentityContext; const RefVoidMap = std.ArrayHashMapUnmanaged(Ref, void, Ref.ArrayHashCtx, false); +const RefVoidMapManaged = std.ArrayHashMap(Ref, void, Ref.ArrayHashCtx, false); const RefImportData = std.ArrayHashMapUnmanaged(Ref, ImportData, Ref.ArrayHashCtx, false); const RefExportData = bun.StringArrayHashMapUnmanaged(ExportData); const TopLevelSymbolToParts = js_ast.Ast.TopLevelSymbolToParts; @@ -3335,9 +3336,11 @@ const LinkerContext = struct { .{ .name = .{ .ref = stable_ref.ref, - .loc = Logger.Loc.Empty{}, + .loc = Logger.Loc.Empty, }, .alias = alias, + .alias_loc = Logger.Loc.Empty, + .original_name = "", }, ); repr.exports_to_other_chunks.put( @@ -3347,11 +3350,11 @@ const LinkerContext = struct { ) catch unreachable; } - if (clause_items.items.len > 0) { + if (clause_items.len > 0) { var stmts = BabyList(js_ast.Stmt).initCapacity(c.allocator, 1) catch unreachable; var export_clause = c.allocator.create(js_ast.S.ExportClause) catch unreachable; export_clause.* = .{ - .items = clause_items.items, + .items = clause_items.slice(), .is_single_line = false, }; stmts.appendAssumeCapacity(.{ @@ -3389,18 +3392,19 @@ const LinkerContext = struct { .esm => { const import_record_index = @truncate(u32, chunk.cross_chunk_imports.len); - var clauses = std.ArrayList(js_ast.ClauseItem).initCapacity(c.allocator, cross_chunk_import.sorted_import_Items.items.len) catch unreachable; - for (cross_chunk_import.sorted_import_Items.items) |item| { + var clauses = std.ArrayList(js_ast.ClauseItem).initCapacity(c.allocator, cross_chunk_import.sorted_import_items.len) catch unreachable; + for (cross_chunk_import.sorted_import_items.slice()) |item| { clauses.appendAssumeCapacity(.{ .name = .{ .ref = item.ref, - .loc = Logger.Loc.Empty{}, + .loc = Logger.Loc.Empty, }, .alias = item.export_alias, + .alias_loc = Logger.Loc.Empty, }); } - chunk.cross_chunk_imports.append(c.allocator, .{ + chunk.cross_chunk_imports.push(c.allocator, .{ .import_kind = .stmt, .chunk_index = cross_chunk_import.chunk_index, }) catch unreachable; @@ -3409,8 +3413,9 @@ const LinkerContext = struct { import.* = .{ .items = clauses.items, .import_record_index = import_record_index, + .namespace_ref = Ref.None, }; - cross_chunk_prefix_stmts.append( + cross_chunk_prefix_stmts.push( c.allocator, .{ .data = .{ @@ -3418,7 +3423,7 @@ const LinkerContext = struct { }, .loc = Logger.Loc.Empty, }, - ); + ) catch unreachable; }, else => bun.unreachablePanic("Unexpected output format", .{}), } @@ -3432,7 +3437,7 @@ const LinkerContext = struct { // Sort cross-chunk exports by chunk name for determinism fn sortedCrossChunkExportItems( c: *LinkerContext, - export_refs: RefVoidMap, + export_refs: RefVoidMapManaged, list: std.ArrayList(StableRef), ) std.ArrayList(StableRef) { var result = list; @@ -3440,7 +3445,7 @@ const LinkerContext = struct { result.ensureTotalCapacity(export_refs.count()) catch unreachable; for (export_refs.keys()) |export_ref| { result.appendAssumeCapacity(.{ - .source_index = c.graph.stable_source_indices[export_ref.sourceIndex()], + .stable_source_index = c.graph.stable_source_indices[export_ref.sourceIndex()], .ref = export_ref, }); } @@ -4430,7 +4435,7 @@ const StableRef = packed struct { stable_source_index: Index.Int, ref: Ref, - pub fn isLessThan(a: StableRef, b: StableRef) bool { + pub fn isLessThan(_: void, a: StableRef, b: StableRef) bool { return a.stable_source_index < b.stable_source_index or (a.stable_source_index == b.stable_source_index and a.ref.innerIndex() < b.ref.innerIndex()); } @@ -4621,7 +4626,7 @@ pub const ChunkImport = struct { pub const CrossChunkImport = struct { chunk_index: Index.Int = 0, - sorted_import_Items: List = undefined, + sorted_import_items: CrossChunkImport.Item.List = undefined, pub const Item = struct { export_alias: string = "", @@ -4635,12 +4640,16 @@ pub const CrossChunkImport = struct { }; pub fn lessThan(_: void, a: CrossChunkImport, b: CrossChunkImport) bool { - return std.mem.order(a.chunk_index, b.chunk_index) == .lt; + return std.math.order(a.chunk_index, b.chunk_index) == .lt; } pub const List = std.ArrayList(CrossChunkImport); - pub fn sortedCrossChunkImports(list: *List, chunks: []Chunk, imports_from_other_chunks: *Chunk.ImportsFromOtherChunks) !void { + pub fn sortedCrossChunkImports( + list: *List, + chunks: []Chunk, + imports_from_other_chunks: *Chunk.ImportsFromOtherChunks, + ) !void { var result = list.*; defer { list.* = result; @@ -4663,14 +4672,14 @@ pub const CrossChunkImport = struct { item.export_alias = exports_to_other_chunks.get(item.ref).?; std.debug.assert(item.export_alias.len > 0); } - std.sort.sort(void, import_items.slice(), void{}, CrossChunkImport.Item.lessThan); + std.sort.sort(CrossChunkImport.Item, import_items.slice(), void{}, CrossChunkImport.Item.lessThan); result.append(CrossChunkImport{ .chunk_index = chunk_index, - .sorted_import_items = List.fromOwnedSlice(result.allocator, import_items.slice()), + .sorted_import_items = import_items, }) catch unreachable; } - std.sort.sort(void, result.items, void{}, CrossChunkImport.lessThan); + std.sort.sort(CrossChunkImport, result.items, void{}, CrossChunkImport.lessThan); } }; diff --git a/src/js_ast.zig b/src/js_ast.zig index 4878797e9654..b868227e70e1 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -521,7 +521,7 @@ pub const ClauseItem = struct { /// In this case both "foo" and "bar" are aliases because it's a re-export. /// We need to preserve both aliases in case the symbol is renamed. In this /// example, "foo" is "OriginalName" and "bar" is "Alias". - original_name: string, + original_name: string = "", pub const default_alias: string = "default"; }; diff --git a/src/renamer.zig b/src/renamer.zig index 37087b9f8740..aa104f2cafb0 100644 --- a/src/renamer.zig +++ b/src/renamer.zig @@ -70,10 +70,10 @@ pub const DisabledRenamer = struct { }; pub const ExportRenamer = struct { - string_buffer: bun.MutableString = undefined, - used: bun.StringHashMap(u32) = undefined, + string_buffer: bun.MutableString, + used: bun.StringHashMap(u32), - pub fn init(allocator: *std.mem.Allocator) ExportRenamer { + pub fn init(allocator: std.mem.Allocator) ExportRenamer { return ExportRenamer{ .string_buffer = MutableString.initEmpty(allocator), .used = bun.StringHashMap(u32).init(allocator), @@ -85,6 +85,11 @@ pub const ExportRenamer = struct { this.string_buffer.reset(); } + pub fn deinit(this: *ExportRenamer) void { + this.used.deinit(); + this.string_buffer.deinit(); + } + pub fn nextRenamedName(this: *ExportRenamer, input: []const u8) string { var entry = this.used.getOrPut(input) catch unreachable; var tries: u32 = 1; @@ -92,10 +97,10 @@ pub const ExportRenamer = struct { while (true) { this.string_buffer.reset(); var writer = this.string_buffer.writer(); - writer.print("{s}{d}", .{ input, tries }); + writer.print("{s}{d}", .{ input, tries }) catch unreachable; tries += 1; var attempt = this.string_buffer.toOwnedSliceLeaky(); - entry = this.used.getOrPut() catch unreachable; + entry = this.used.getOrPut(attempt) catch unreachable; if (!entry.found_existing) { const to_use = this.string_buffer.allocator.dupe(u8, attempt) catch unreachable; entry.key_ptr.* = to_use; From 5e8a9575b74467f4d9b93242e0b6e8b8d3a1ade4 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Fri, 10 Feb 2023 22:23:38 -0800 Subject: [PATCH 009/255] delete some stuff --- src/bundler.zig | 8 ------ src/bundler/bundle_v2.zig | 46 ++++++++++++++++++++++++++++++ src/js_printer.zig | 60 +++++++++++++++++++++------------------ src/sync.zig | 8 ++++-- 4 files changed, 85 insertions(+), 37 deletions(-) diff --git a/src/bundler.zig b/src/bundler.zig index 0aab3d4b9a21..2b6dd364a3ae 100644 --- a/src/bundler.zig +++ b/src/bundler.zig @@ -1157,8 +1157,6 @@ pub const Bundler = struct { .rewrite_require_resolve = bundler.options.platform != .node, .minify_whitespace = bundler.options.minify_whitespace, }, - Linker, - &bundler.linker, enable_source_map, ), @@ -1178,8 +1176,6 @@ pub const Bundler = struct { .rewrite_require_resolve = bundler.options.platform != .node, .minify_whitespace = bundler.options.minify_whitespace, }, - Linker, - &bundler.linker, enable_source_map, ), .esm_ascii => try js_printer.printAst( @@ -1196,8 +1192,6 @@ pub const Bundler = struct { .css_import_behavior = bundler.options.cssImportBehavior(), .source_map_handler = source_map_context, }, - Linker, - &bundler.linker, enable_source_map, ), .cjs_ascii => try js_printer.printCommonJS( @@ -1214,8 +1208,6 @@ pub const Bundler = struct { .css_import_behavior = bundler.options.cssImportBehavior(), .source_map_handler = source_map_context, }, - Linker, - &bundler.linker, enable_source_map, ), }; diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 74ea90a146a2..6e1a347ef93d 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -131,6 +131,16 @@ pub const ThreadPool = struct { has_notify_started: bool = false, + pub fn get() *Worker { + return @ptrCast( + *ThreadPool.Worker, + @alignCast( + @alignOf(*ThreadPool.Worker), + ThreadPoolLib.Thread.current.?.ctx.?, + ), + ); + } + pub const WorkerData = struct { log: *Logger.Log, estimated_input_lines_of_code: usize = 0, @@ -3434,6 +3444,42 @@ const LinkerContext = struct { } } + const GenerateChunkCtx = struct { + wg: *sync.WaitGroup, + c: *LinkerContext, + chunks: []Chunk, + }; + fn generateChunkJS(ctx: GenerateChunkCtx, chunk: *Chunk, chunk_index: usize) void { + generateChunkJS_(ctx, chunk, chunk_index) catch |err| std.debug.panic("TODO: handle error: {s}", .{@errorName(err)}); + } + fn generateChunkJS_(_: GenerateChunkCtx, _: *Chunk, _: usize) !void { + // defer ctx.wg.done(); + // var worker = ThreadPool.Worker.get(); + // const allocator = worker.allocator; + // const c = ctx.c; + // std.debug.assert(chunk.content == .javascript); + // var repr = &chunk.content.javascript; + + // var module_scopes = try allocator.alloc( + // *js_ast.Scope, + + // ); + } + + pub fn generateChunksInParallel(c: *LinkerContext, chunks_: []Chunk) ![]options.OutputFile { + { + var wait_group = try c.allocator.create(sync.WaitGroup); + wait_group.* = sync.WaitGroup.init(); + defer { + wait_group.deinit(); + c.allocator.destroy(wait_group); + } + wait_group.addN(chunks_.len); + var ctx = GenerateChunkCtx{ .wg = wait_group, .c = c, .chunks = chunks_ }; + try c.parse_graph.pool.pool.doPtr(c.allocator, wait_group, ctx, generateChunkJS, chunks_); + } + } + // Sort cross-chunk exports by chunk name for determinism fn sortedCrossChunkExportItems( c: *LinkerContext, diff --git a/src/js_printer.zig b/src/js_printer.zig index c2a1a44df7bc..76a2ba2a49c6 100644 --- a/src/js_printer.zig +++ b/src/js_printer.zig @@ -515,9 +515,12 @@ pub const Options = struct { } }; -pub const PrintResult = struct { - js: string, - source_map: ?SourceMap.Chunk = null, +pub const PrintResult = union(enum) { + result: struct { + code: []u8, + source_map: ?SourceMap.Chunk = null, + }, + err: anyerror, }; // do not make this a packed struct @@ -615,10 +618,9 @@ const ImportVariant = enum { } }; -pub fn NewPrinter( +fn NewPrinter( comptime ascii_only: bool, comptime Writer: type, - comptime Linker: type, comptime rewrite_esm_to_cjs: bool, comptime is_bun_platform: bool, comptime is_inside_bundle: bool, @@ -628,7 +630,6 @@ pub fn NewPrinter( return struct { symbols: Symbol.Map, import_records: []importRecord.ImportRecord, - linker: ?*Linker, needs_semicolon: bool = false, stmt_start: i32 = -1, @@ -4778,7 +4779,6 @@ pub fn NewPrinter( source: *const logger.Source, symbols: Symbol.Map, opts: Options, - linker: ?*Linker, allocator: std.mem.Allocator, ) !Printer { if (imported_module_ids_list_unset) { @@ -4804,7 +4804,6 @@ pub fn NewPrinter( .options = opts, .symbols = symbols, .writer = writer, - .linker = linker, .imported_module_ids = imported_module_ids_list, .renamer = rename.Renamer.init(symbols, source), .source_map_builder = source_map_builder, @@ -4813,6 +4812,12 @@ pub fn NewPrinter( }; } +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, @@ -5213,14 +5218,11 @@ pub fn printAst( source: *const logger.Source, comptime ascii_only: bool, opts: Options, - comptime LinkerType: type, - linker: ?*LinkerType, comptime generate_source_map: bool, ) !usize { const PrinterType = NewPrinter( ascii_only, Writer, - LinkerType, false, // if it's ascii_only, it is also bun ascii_only, @@ -5236,7 +5238,6 @@ pub fn printAst( source, symbols, opts, - linker, opts.allocator, ); defer { @@ -5279,7 +5280,7 @@ pub fn printJSON( expr: Expr, source: *const logger.Source, ) !usize { - const PrinterType = NewPrinter(false, Writer, void, false, false, false, true, false); + const PrinterType = NewPrinter(false, Writer, false, false, false, true, false); var writer = _writer; var s_expr = S.SExpr{ .value = expr }; var stmt = Stmt{ .loc = logger.Loc.Empty, .data = .{ @@ -5297,7 +5298,6 @@ pub fn printJSON( source, .{}, .{}, - null, allocator, ); @@ -5318,11 +5318,9 @@ pub fn printCommonJS( source: *const logger.Source, comptime ascii_only: bool, opts: Options, - comptime LinkerType: type, - linker: ?*LinkerType, comptime generate_source_map: bool, ) !usize { - const PrinterType = NewPrinter(ascii_only, Writer, LinkerType, true, false, false, false, generate_source_map); + const PrinterType = NewPrinter(ascii_only, Writer, true, false, false, false, generate_source_map); var writer = _writer; var printer = try PrinterType.init( writer, @@ -5330,7 +5328,6 @@ pub fn printCommonJS( source, symbols, opts, - linker, opts.allocator, ); defer { @@ -5370,11 +5367,23 @@ pub fn printCommonJS( return @intCast(usize, @max(printer.writer.written, 0)); } -pub const WriteResult = struct { - off: u32, - len: usize, - end_off: u32, -}; +// pub fn printChunk( +// comptime Writer: type, +// _writer: Writer, +// tree: Ast, +// symbols: js_ast.Symbol.Map, +// opts: Options, +// ) PrintResult { +// var writer = _writer; +// var printer = try PrinterType.init( +// writer, +// &tree, +// source, +// symbols, +// opts, +// undefined, +// ); +// } pub fn printCommonJSThreaded( comptime Writer: type, @@ -5384,15 +5393,13 @@ pub fn printCommonJSThreaded( source: *const logger.Source, comptime ascii_only: bool, opts: Options, - comptime LinkerType: type, - linker: ?*LinkerType, lock: *Lock, comptime GetPosType: type, getter: GetPosType, comptime getPos: fn (ctx: GetPosType) anyerror!u64, end_off_ptr: *u32, ) !WriteResult { - const PrinterType = NewPrinter(ascii_only, Writer, LinkerType, true, ascii_only, true, false, false); + const PrinterType = NewPrinter(ascii_only, Writer, true, ascii_only, true, false, false); var writer = _writer; var printer = try PrinterType.init( writer, @@ -5400,7 +5407,6 @@ pub fn printCommonJSThreaded( source, symbols, opts, - linker, undefined, ); diff --git a/src/sync.zig b/src/sync.zig index 0fad16736fee..ef9f058b3294 100644 --- a/src/sync.zig +++ b/src/sync.zig @@ -771,11 +771,15 @@ pub const WaitGroup = struct { self.* = undefined; } - pub fn add(self: *WaitGroup) void { + pub fn addN(self: *WaitGroup, n: usize) void { self.mutex.lock(); defer self.mutex.unlock(); - self.active += 1; + self.active += n; + } + + pub fn add(self: *WaitGroup) void { + return self.addN(1); } pub fn done(self: *WaitGroup) void { From 532597ea36326b7f22b448b560671abae222c203 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Sun, 12 Feb 2023 01:27:38 -0800 Subject: [PATCH 010/255] Rename symbols! --- src/bun.zig | 17 +- src/bundler/bundle_v2.zig | 207 +++++++++++++++++++-- src/comptime_string_map.zig | 12 ++ src/js_ast.zig | 82 ++++++--- src/renamer.zig | 349 +++++++++++++++++++++++++++++++++--- src/string_mutable.zig | 18 +- 6 files changed, 622 insertions(+), 63 deletions(-) diff --git a/src/bun.zig b/src/bun.zig index b12ca169388a..bf5448711b40 100644 --- a/src/bun.zig +++ b/src/bun.zig @@ -612,6 +612,13 @@ pub const StringArrayHashMapContext = struct { return strings.eqlLong(a, b, true); } + pub fn pre(input: []const u8) Prehashed { + return Prehashed{ + .value = @This().hash(.{}, input), + .input = input, + }; + } + pub const Prehashed = struct { value: u32, input: []const u8, @@ -635,13 +642,20 @@ pub const StringHashMapContext = struct { return strings.eqlLong(a, b, true); } + pub fn pre(input: []const u8) Prehashed { + return Prehashed{ + .value = @This().hash(.{}, input), + .input = input, + }; + } + pub const Prehashed = struct { value: u64, input: []const u8, pub fn hash(this: @This(), s: []const u8) u64 { if (s.ptr == this.input.ptr and s.len == this.input.len) return this.value; - return std.hash.Wyhash.hash(0, s); + return StringHashMapContext.hash(.{}, s); } pub fn eql(_: @This(), a: []const u8, b: []const u8) bool { @@ -976,3 +990,4 @@ pub const fast_debug_build_mode = false and pub const MultiArrayList = @import("./multi_array_list.zig").MultiArrayList; pub const Joiner = @import("./string_joiner.zig"); +pub const renamer = @import("./renamer.zig"); diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 6e1a347ef93d..fe5773ced8f9 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -71,7 +71,8 @@ const Stmt = js_ast.Stmt; const Expr = js_ast.Expr; const Binding = js_ast.Binding; const AutoBitSet = bun.bit_set.AutoBitSet; -const renamer = @import("../renamer.zig"); +const renamer = bun.renamer; +const Scope = js_ast.Scope; pub const ThreadPool = struct { pool: ThreadPoolLib = undefined, @@ -3452,18 +3453,198 @@ const LinkerContext = struct { fn generateChunkJS(ctx: GenerateChunkCtx, chunk: *Chunk, chunk_index: usize) void { generateChunkJS_(ctx, chunk, chunk_index) catch |err| std.debug.panic("TODO: handle error: {s}", .{@errorName(err)}); } - fn generateChunkJS_(_: GenerateChunkCtx, _: *Chunk, _: usize) !void { - // defer ctx.wg.done(); - // var worker = ThreadPool.Worker.get(); - // const allocator = worker.allocator; - // const c = ctx.c; - // std.debug.assert(chunk.content == .javascript); - // var repr = &chunk.content.javascript; - - // var module_scopes = try allocator.alloc( - // *js_ast.Scope, - - // ); + + fn renameSymbolsInChunk( + c: *LinkerContext, + allocator: std.mem.Allocator, + chunk: *Chunk, + files_in_order: []u32, + ) !renamer.Renamer { + + // TODO: minify identifiers + const all_module_scopes = c.graph.ast.items(.module_scope); + const all_flags: []const JSMeta.Flags = c.graph.meta.items(.flags); + const all_parts: []const js_ast.Part.List = c.graph.ast.items(.parts); + const all_wrapper_refs: []const ?Ref = c.graph.ast.items(.wrapper_ref); + const all_import_records: []const ImportRecord.List = c.graph.ast.items(.import_records); + + var r = try renamer.NumberRenamer.init( + allocator, + allocator, + c.graph.symbols, + brk: { + var reserved_names = try renamer.computeInitialReservedNames(allocator); + + for (files_in_order) |source_index| { + renamer.computeReservedNamesForScope(all_module_scopes[source_index], &c.graph.symbols, &reserved_names, allocator); + } + + break :brk reserved_names; + }, + ); + + { + var sorted_imports_from_other_chunks: std.ArrayList(StableRef) = brk: { + var list = std.ArrayList(StableRef).init(allocator); + var count: u32 = 0; + var imports_from_other_chunks = chunk.content.javascript.imports_from_other_chunks.values(); + for (imports_from_other_chunks) |item| { + count += item.len; + } + + list.ensureTotalCapacityPrecise(count) catch unreachable; + list.items.len = count; + var remain = list.items; + const stable_source_indices = c.graph.stable_source_indices; + for (imports_from_other_chunks) |item| { + for (item.slice()) |ref| { + remain[0] = StableRef{ + .stable_source_index = stable_source_indices[ref.ref.sourceIndex()], + .ref = ref.ref, + }; + remain = remain[1..]; + } + } + + std.sort.sort(StableRef, list.items, void{}, StableRef.isLessThan); + break :brk list; + }; + defer sorted_imports_from_other_chunks.deinit(); + + for (sorted_imports_from_other_chunks.items) |stable_ref| { + r.addTopLevelSymbol(stable_ref.ref); + } + } + + var child_number_scope_ = renamer.NumberRenamer.NumberScope{ + .parent = &r.root, + }; + var child_number_scope = &child_number_scope_; + defer child_number_scope.deinit(r.temp_allocator); + var sorted_ = std.ArrayList(u32).init(r.temp_allocator); + var sorted = &sorted_; + defer sorted.deinit(); + + for (files_in_order) |source_index| { + const wrap = all_flags[source_index].wrap; + const parts: []const js_ast.Part = all_parts[source_index].slice(); + + switch (wrap) { + // Modules wrapped in a CommonJS closure look like this: + // + // // foo.js + // var require_foo = __commonJS((exports, module) => { + // exports.foo = 123; + // }); + // + // The symbol "require_foo" is stored in "file.ast.WrapperRef". We want + // to be able to minify everything inside the closure without worrying + // about collisions with other CommonJS modules. Set up the scopes such + // that it appears as if the file was structured this way all along. It's + // not completely accurate (e.g. we don't set the parent of the module + // scope to this new top-level scope) but it's good enough for the + // renaming code. + .cjs => { + r.addTopLevelSymbol(all_wrapper_refs[source_index].?); + + // External import statements will be hoisted outside of the CommonJS + // wrapper if the output format supports import statements. We need to + // add those symbols to the top-level scope to avoid causing name + // collisions. This code special-cases only those symbols. + if (c.options.output_format.keepES6ImportExportSyntax()) { + const import_records = all_import_records[source_index].slice(); + for (parts) |*part| { + for (part.stmts) |stmt| { + switch (stmt.data) { + .s_import => |import| { + if (!import_records[import.import_record_index].source_index.isValid()) { + r.addTopLevelSymbol(import.namespace_ref); + if (import.default_name) |default_name| { + if (default_name.ref) |ref| { + r.addTopLevelSymbol(ref); + } + } + + for (import.items) |*item| { + if (item.name.ref) |ref| { + r.addTopLevelSymbol(ref); + } + } + } + }, + .s_export_star => |export_| { + if (!import_records[export_.import_record_index].source_index.isValid()) { + r.addTopLevelSymbol(export_.namespace_ref); + } + }, + .s_export_from => |export_| { + if (!import_records[export_.import_record_index].source_index.isValid()) { + r.addTopLevelSymbol(export_.namespace_ref); + + for (export_.items) |*item| { + if (item.name.ref) |ref| { + r.addTopLevelSymbol(ref); + } + } + } + }, + else => {}, + } + } + } + } + r.assignNamesRecursiveWithNumberScope(child_number_scope, all_module_scopes[source_index], source_index, sorted); + continue; + }, + + // Modules wrapped in an ESM closure look like this: + // + // // foo.js + // var foo, foo_exports = {}; + // __export(foo_exports, { + // foo: () => foo + // }); + // let init_foo = __esm(() => { + // foo = 123; + // }); + // + // The symbol "init_foo" is stored in "file.ast.WrapperRef". We need to + // minify everything inside the closure without introducing a new scope + // since all top-level variables will be hoisted outside of the closure. + .esm => { + r.addTopLevelSymbol(all_wrapper_refs[source_index].?); + }, + + else => {}, + } + + for (parts) |*part| { + if (!part.is_live) continue; + + r.addTopLevelDeclaredSymbols(part.declared_symbols); + for (part.scopes) |scope| { + r.assignNamesRecursiveWithNumberScope(child_number_scope, scope, source_index, sorted); + } + } + } + + return r.toRenamer(); + } + + fn generateChunkJS_(ctx: GenerateChunkCtx, chunk: *Chunk, chunk_index: usize) !void { + defer ctx.wg.done(); + var worker = ThreadPool.Worker.get(); + const allocator = worker.allocator; + const c = ctx.c; + std.debug.assert(chunk.content == .javascript); + + var repr = &chunk.content.javascript; + + var runtime_scope: *Scope = c.graph.ast.items(.module_scope)[c.graph.files.items(.input_file)[Ref.RuntimeRef.sourceIndex()]]; + var runtime_members = &runtime_scope.members; + const toCommonJSRef = c.graph.symbols.follow(runtime_members.get("toCommonJS").?.ref); + const toESMRef = c.graph.symbols.follow(runtime_members.get("toESM").?.ref); + const runtimeRequireRef = c.graph.symbols.follow(runtime_members.get("__require").?.ref); } pub fn generateChunksInParallel(c: *LinkerContext, chunks_: []Chunk) ![]options.OutputFile { diff --git a/src/comptime_string_map.zig b/src/comptime_string_map.zig index bb2602ce3cbe..2a09c5acc5fa 100644 --- a/src/comptime_string_map.zig +++ b/src/comptime_string_map.zig @@ -69,6 +69,18 @@ pub fn ComptimeStringMapWithKeyType(comptime KeyType: type, comptime V: type, co const len_indexes = precomputed.len_indexes; pub const kvs = precomputed.sorted_kvs; + const keys_list: []const []const KeyType = blk: { + var k: [kvs.len][]const KeyType = undefined; + for (kvs) |kv, i| { + k[i] = kv.key; + } + break :blk k[0..]; + }; + + pub fn keys() []const []const KeyType { + return keys_list; + } + pub fn has(str: []const KeyType) bool { return get(str) != null; } diff --git a/src/js_ast.zig b/src/js_ast.zig index b868227e70e1..e55d92f21c3d 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -757,6 +757,31 @@ pub const Symbol = struct { // private_symbol_must_be_lowered: bool = false, + pub const SlotNamespace = enum { + default, + label, + private_name, + mangled_prop, + must_not_be_renamed, + }; + + pub fn slotNamespace(this: *const Symbol) SlotNamespace { + if (this.must_not_be_renamed) { + return .must_not_be_renamed; + } + + const kind = this.kind; + if (kind.isPrivate()) { + return .private_name; + } + + return switch (kind) { + .mangled_prop => .mangled_prop, + .label => .label, + else => .default, + }; + } + pub inline fn hasLink(this: *const Symbol) bool { return !this.link.isNull(); } @@ -846,8 +871,38 @@ pub const Symbol = struct { pub fn jsonStringify(self: @This(), opts: anytype, o: anytype) !void { return try std.json.stringify(@tagName(self), opts, o); } + + pub inline fn isPrivate(kind: Symbol.Kind) bool { + return @enumToInt(kind) >= @enumToInt(Symbol.Kind.private_field) and @enumToInt(kind) <= @enumToInt(Symbol.Kind.private_static_get_set_pair); + } + + pub inline fn isHoisted(kind: Symbol.Kind) bool { + return switch (kind) { + .hoisted, .hoisted_function => true, + else => false, + }; + } + + pub inline fn isHoistedOrFunction(kind: Symbol.Kind) bool { + return switch (kind) { + .hoisted, .hoisted_function, .generator_or_async_function => true, + else => false, + }; + } + + pub inline fn isFunction(kind: Symbol.Kind) bool { + return switch (kind) { + .hoisted_function, .generator_or_async_function => true, + else => false, + }; + } }; + pub const isKindPrivate = Symbol.Kind.isPrivate; + pub const isKindHoisted = Symbol.Kind.isHoisted; + pub const isKindHoistedOrFunction = Symbol.Kind.isHoistedOrFunction; + pub const isKindFunction = Symbol.Kind.isFunction; + pub const Use = struct { count_estimate: u32 = 0, }; @@ -981,35 +1036,10 @@ pub const Symbol = struct { } }; - pub inline fn isKindPrivate(kind: Symbol.Kind) bool { - return @enumToInt(kind) >= @enumToInt(Symbol.Kind.private_field) and @enumToInt(kind) <= @enumToInt(Symbol.Kind.private_static_get_set_pair); - } - - pub inline fn isKindHoisted(kind: Symbol.Kind) bool { - return switch (kind) { - .hoisted, .hoisted_function => true, - else => false, - }; - } - pub inline fn isHoisted(self: *const Symbol) bool { return Symbol.isKindHoisted(self.kind); } - pub inline fn isKindHoistedOrFunction(kind: Symbol.Kind) bool { - return switch (kind) { - .hoisted, .hoisted_function, .generator_or_async_function => true, - else => false, - }; - } - - pub inline fn isKindFunction(kind: Symbol.Kind) bool { - return switch (kind) { - .hoisted_function, .generator_or_async_function => true, - else => false, - }; - } - pub fn isReactComponentishName(symbol: *const Symbol) bool { switch (symbol.kind) { .hoisted, .hoisted_function, .cconst, .class, .other => { @@ -4993,6 +5023,8 @@ pub const Scope = struct { is_after_const_local_prefix: bool = false, + pub const NestedScopeMap = std.AutoArrayHashMap(u32, bun.BabyList(*Scope)); + pub fn getMemberHash(name: []const u8) u64 { return bun.StringHashMapContext.hash(.{}, name); } diff --git a/src/renamer.zig b/src/renamer.zig index aa104f2cafb0..ac8dae64acf6 100644 --- a/src/renamer.zig +++ b/src/renamer.zig @@ -12,19 +12,17 @@ const C = bun.C; const std = @import("std"); const Ref = @import("./ast/base.zig").Ref; const logger = @import("bun").logger; +const JSLexer = @import("./js_lexer.zig"); -// This is...poorly named -// It does not rename -// It merely names -pub const Renamer = struct { +pub const NoOpRenamer = struct { symbols: js_ast.Symbol.Map, source: *const logger.Source, - pub fn init(symbols: js_ast.Symbol.Map, source: *const logger.Source) Renamer { - return Renamer{ .symbols = symbols, .source = source }; + pub fn init(symbols: js_ast.Symbol.Map, source: *const logger.Source) NoOpRenamer { + return NoOpRenamer{ .symbols = symbols, .source = source }; } - pub fn nameForSymbol(renamer: *Renamer, ref: Ref) string { + pub fn nameForSymbol(renamer: *NoOpRenamer, ref: Ref) string { if (ref.isSourceContentsSlice()) { return renamer.source.contents[ref.sourceIndex() .. ref.sourceIndex() + ref.innerIndex()]; } @@ -39,34 +37,276 @@ pub const Renamer = struct { } }; -pub const BundledRenamer = struct { +pub const Renamer = union(enum) { + NumberRenamer: *NumberRenamer, + NoOpRenamer: *NoOpRenamer, + + pub fn nameForSymbol(renamer: Renamer, ref: Ref) string { + return switch (renamer) { + inline else => |r| r.nameForSymbol(ref), + }; + } + + pub fn deinit(renamer: Renamer) void { + switch (renamer) { + .NumberRenamer => |r| r.deinit(), + else => {}, + } + } +}; + +pub const NumberRenamer = struct { symbols: js_ast.Symbol.Map, - sources: []const []const u8, + names: bun.BabyList(bun.BabyList(string)) = .{}, + allocator: std.mem.Allocator, + temp_allocator: std.mem.Allocator, + root: NumberScope = .{}, + + pub fn deinit(self: *NumberRenamer) void { + self.names.deinit(self.allocator); + self.root.deinit(self.temp_allocator); + } + + pub fn toRenamer(this: *NumberRenamer) Renamer { + return .{ + .NumberRenamer = this, + }; + } + + pub fn assignName(r: *NumberRenamer, scope: *NumberScope, input_ref: Ref) void { + const ref = r.symbols.follow(input_ref); + + // Don't rename the symbol more than once + var inner: *bun.BabyList(string) = r.names.mut(input_ref.sourceIndex()); + if (inner.len > ref.innerIndex() and inner.at(ref.innerIndex()).len > 0) return; + + // Don't rename unbound symbols, symbols marked as reserved names, labels, or private names + const symbol = r.symbols.get(ref).?; + if (symbol.slotNamespace() != .default) { + return; + } + + // TODO: MustStartWithCapitalLetterForJSX + + const name = scope.findUnusedName(r.allocator, symbol.original_name); + + if (inner.cap < ref.innerIndex() + 1) { + inner.ensureUnusedCapacity(r.allocator, ref.innerIndex() + 1) catch unreachable; + } + inner.mut(ref.innerIndex()).* = name; + } + + pub fn init( + allocator: std.mem.Allocator, + temp_allocator: std.mem.Allocator, + symbols: js_ast.Symbol.Map, + root_names: bun.StringHashMapUnmanaged(u32), + ) !*NumberRenamer { + var renamer = try allocator.create(NumberRenamer); + renamer.* = NumberRenamer{ + .symbols = symbols, + .allocator = allocator, + .temp_allocator = temp_allocator, + .names = try bun.BabyList(bun.BabyList(string)).initCapacity(allocator, symbols.symbols_for_source.len), + }; + renamer.root.name_counts = root_names; + return renamer; + } + + pub fn assignNamesRecursive(r: *NumberRenamer, scope: *js_ast.Scope, source_index: u32, parent: ?*NumberScope, sorted: *std.ArrayList(u32)) void { + var s_ = NumberScope{ + .parent = parent, + .name_counts = .{}, + }; + var s = &s_; + defer s.deinit(r.temp_allocator); + + assignNamesRecursiveWithNumberScope(r, s, scope, source_index, sorted); + } + + pub fn assignNamesRecursiveWithNumberScope(r: *NumberRenamer, s: *NumberScope, scope: *js_ast.Scope, source_index: u32, sorted: *std.ArrayList(u32)) void { + sorted.clearRetainingCapacity(); + sorted.ensureUnusedCapacity(scope.members.count()) catch unreachable; + var value_iter = scope.members.valueIterator(); + while (value_iter.next()) |value_ref| { + if (comptime Environment.allow_assert) + std.debug.assert(!value_ref.ref.isSourceContentsSlice()); + + sorted.appendAssumeCapacity(value_ref.ref.innerIndex()); + } + std.sort.sort(u32, sorted.items, void{}, std.sort.asc(u32)); + + for (sorted.items) |inner_index| { + r.assignName(s, Ref.init(@intCast(Ref.Int, inner_index), source_index, false)); + } + + for (scope.generated.slice()) |ref| { + r.assignName(s, ref); + } + + // We only need one number scope per scope level, so we can reuse the same one for all children + var child_scope_ = NumberScope{ + .parent = s, + .name_counts = .{}, + }; + var child_scope = &child_scope_; + defer child_scope.deinit(r.temp_allocator); + + // Symbols in child scopes may also have to be renamed to avoid conflicts + for (scope.children.slice()) |child| { + child_scope.name_counts.clearRetainingCapacity(); + r.assignNamesRecursiveWithNumberScope(child_scope, child, source_index, s, sorted); + } + } + + pub fn addTopLevelSymbol(r: *NumberRenamer, ref: Ref) void { + r.assignName(&r.root, ref); + } - pub fn init(symbols: js_ast.Symbol.Map, sources: []const []const u8) Renamer { - return Renamer{ .symbols = symbols, .source = sources }; + pub fn addTopLevelDeclaredSymbols(r: *NumberRenamer, declared_symbols: js_ast.DeclaredSymbol.List) void { + var decls = declared_symbols; + js_ast.DeclaredSymbol.forEachTopLevelSymbol(&decls, r, addTopLevelSymbol); } - pub fn nameForSymbol(renamer: *Renamer, ref: Ref) string { + pub fn assignNamesByScope(r: *NumberRenamer, nested_scopes: *js_ast.Scope.NestedScopeMap) void { + // TODO: parallelize this + var sorted_ = std.ArrayList(u32).init(r.temp_allocator); + var sorted = &sorted_; + defer sorted.deinit(); + + var scope_iter = nested_scopes.iterator(); + var s_ = NumberScope{ + .parent = &r.root, + .name_counts = .{}, + }; + var s = &s_; + + while (scope_iter.next()) |entry| { + var scopes_list = entry.value_ptr.*; + defer entry.value_ptr.deinitWithAllocator(nested_scopes.allocator); + const scopes = scopes_list.slice(); + const source_index = entry.key_ptr.*; + + for (scopes) |scope| { + s.name_counts.clearRetainingCapacity(); + r.assignNamesRecursiveWithNumberScope(s, scope, source_index, sorted); + } + } + } + + pub fn nameForSymbol(renamer: *NumberRenamer, ref: Ref) string { if (ref.isSourceContentsSlice()) { unreachable; } const resolved = renamer.symbols.follow(ref); - if (renamer.symbols.getConst(resolved)) |symbol| { - return symbol.original_name; - } else { - Global.panic("Invalid symbol {s} in {s}", .{ ref, renamer.source.path.text }); - } + return renamer + .names + .at(resolved.sourceIndex()) + .at(resolved.innerIndex()).*; } -}; -pub const DisabledRenamer = struct { - pub fn init(_: js_ast.Symbol.Map) DisabledRenamer {} - pub inline fn nameForSymbol(_: *Renamer, _: js_ast.Ref) string { - @compileError("DisabledRunner called"); - } + pub const NumberScope = struct { + parent: ?*NumberScope = null, + name_counts: bun.StringHashMapUnmanaged(u32) = .{}, + + pub fn deinit(this: *NumberScope, allocator: std.mem.Allocator) void { + this.name_counts.deinit(allocator); + this.* = undefined; + } + + pub const NameUse = union(enum) { + unused: void, + same_scope: u32, + used: void, + + pub fn find(this: *NumberScope, name: []const u8) NameUse { + // This version doesn't allocate + if (comptime Environment.allow_assert) + std.debug.assert(JSLexer.isIdentifier(name)); + + var s: ?*NumberScope = this; + + // avoid rehashing the same string over for each scope + const ctx = bun.StringHashMapContext.pre(name); + + while (s) |*scope| : (s = scope.parent) { + if (scope.name_counts.getAdapted(name, ctx)) |count| { + if (scope == this) { + return .{ .same_scope = count }; + } + return .{ .used = void{} }; + } + } + + return .{ .unused = void{} }; + } + }; + + /// Caller must use an arena allocator + pub fn findUnusedName(this: *NumberScope, allocator: std.mem.Allocator, input_name: []const u8) string { + var name = bun.MutableString.ensureValidIdentifier(input_name, allocator) catch unreachable; + + switch (NameUse.find(this, name)) { + .unused => {}, + else => |use| { + var tries: u32 = if (use == .used) + 1 + else + // To avoid O(n^2) behavior, the number must start off being the number + // that we used last time there was a collision with this name. Otherwise + // if there are many collisions with the same name, each name collision + // would have to increment the counter past all previous name collisions + // which is a O(n^2) time algorithm. Only do this if this symbol comes + // from the same scope as the previous one since sibling scopes can reuse + // the same name without problems. + use.same_scope; + + const prefix = name; + + var mutable_name = MutableString.initEmpty(allocator); + mutable_name.growIfNeeded(prefix.len + 10) catch unreachable; + mutable_name.appendSlice(prefix) catch unreachable; + mutable_name.appendInt(tries) catch unreachable; + + tries += 1; + + switch (NameUse.find(this, mutable_name.toOwnedSliceLeaky())) { + .unused => { + name = mutable_name.toOwnedSliceLeaky(); + }, + else => |cur_use| { + while (true) { + mutable_name.resetTo(prefix.len); + mutable_name.appendInt(tries) catch unreachable; + + tries += 1; + + switch (NameUse.find(this, mutable_name.toOwnedSliceLeaky())) { + .unused => { + if (cur_use == .same_scope) { + this.name_counts.put(allocator, prefix, tries) catch unreachable; + } + + name = mutable_name.toOwnedSliceLeaky(); + break; + }, + else => {}, + } + } + }, + } + }, + } + + // Each name starts off with a count of 1 so that the first collision with + // "name" is called "name2" + this.name_counts.put(allocator, name, 1) catch unreachable; + + return name; + } + }; }; pub const ExportRenamer = struct { @@ -118,3 +358,66 @@ pub const ExportRenamer = struct { return entry.key_ptr.*; } }; + +pub fn computeInitialReservedNames( + allocator: std.mem.Allocator, +) !bun.StringHashMapUnmanaged(u32) { + var names = bun.StringHashMapUnmanaged(u32){}; + + const extras = &.{ + "Promise", + "Require", + }; + + try names.ensureTotalCapacity(allocator, JSLexer.Keywords.keys().len + JSLexer.StrictModeReservedWords.keys().len + 1 + extras.len); + + for (JSLexer.Keywords.keys()) |keyword| { + names.putAssumeCapacity(keyword, 1); + } + + for (JSLexer.StrictModeReservedWords.keys()) |keyword| { + names.putAssumeCapacity(keyword, 1); + } + + for (extras) |extra| { + names.putAssumeCapacity(extra, 1); + } + + return names; +} + +pub fn computeReservedNamesForScope( + scope: *js_ast.Scope, + symbols: *const js_ast.Symbol.Map, + names_: *bun.StringHashMapUnmanaged(u32), + allocator: std.mem.Allocator, +) void { + var names = names_.*; + defer names_.* = names; + + var member_iter = scope.members.valueIterator(); + while (member_iter.next()) |member| { + const symbol = symbols.get(member.ref).?; + if (symbol.kind == .unbound or symbol.must_not_be_renamed) { + names.put(allocator, symbol.original_name, 1) catch unreachable; + } + } + + for (scope.generated.slice()) |ref| { + const symbol = symbols.get(ref).?; + if (symbol.kind == .unbound or symbol.must_not_be_renamed) { + names.put(allocator, symbol.original_name, 1) catch unreachable; + } + } + + // If there's a direct "eval" somewhere inside the current scope, continue + // traversing down the scope tree until we find it to get all reserved names + if (scope.contains_direct_eval) { + for (scope.children.slice()) |child| { + if (child.contains_direct_eval) { + names_.* = names; + computeReservedNamesForScope(child, symbols, &names, allocator); + } + } + } +} diff --git a/src/string_mutable.zig b/src/string_mutable.zig index 2371ae01a846..14d08f164e38 100644 --- a/src/string_mutable.zig +++ b/src/string_mutable.zig @@ -173,7 +173,15 @@ pub const MutableString = struct { pub inline fn reset( self: *MutableString, ) void { - self.list.shrinkRetainingCapacity(0); + self.list.clearRetainingCapacity(); + } + + pub inline fn resetTo( + self: *MutableString, + index: usize, + ) void { + std.debug.assert(index <= self.list.capacity); + self.list.items.len = index; } pub fn inflate(self: *MutableString, amount: usize) !void { @@ -189,6 +197,14 @@ pub const MutableString = struct { pub inline fn append(self: *MutableString, char: []const u8) !void { try self.list.appendSlice(self.allocator, char); } + pub inline fn appendInt(self: *MutableString, int: u64) !void { + const count = bun.fmt.fastDigitCount(int); + try self.list.ensureUnusedCapacity(self.allocator, count); + const old = self.list.items.len; + self.list.items.len += count; + std.debug.assert(count == bun.fmt.formatIntBuf(self.list.items.ptr[old .. old + count], int, 10, .lower, .{})); + } + pub inline fn appendAssumeCapacity(self: *MutableString, char: []const u8) void { self.list.appendSliceAssumeCapacity( char, From b65718095ae5b4623e6667719a97b59a3613ae5a Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Sun, 12 Feb 2023 03:36:00 -0800 Subject: [PATCH 011/255] wip --- src/bundler/bundle_v2.zig | 17 +++ src/js_printer.zig | 211 +++++++++++++++++++++++++------------- src/renamer.zig | 12 +++ 3 files changed, 169 insertions(+), 71 deletions(-) diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index fe5773ced8f9..323971cede23 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -3454,6 +3454,8 @@ const LinkerContext = struct { generateChunkJS_(ctx, chunk, chunk_index) catch |err| std.debug.panic("TODO: handle error: {s}", .{@errorName(err)}); } + // TODO: investigate if we need to parallelize this function + // esbuild does parallelize it. fn renameSymbolsInChunk( c: *LinkerContext, allocator: std.mem.Allocator, @@ -3645,6 +3647,21 @@ const LinkerContext = struct { const toCommonJSRef = c.graph.symbols.follow(runtime_members.get("toCommonJS").?.ref); const toESMRef = c.graph.symbols.follow(runtime_members.get("toESM").?.ref); const runtimeRequireRef = c.graph.symbols.follow(runtime_members.get("__require").?.ref); + + var r = try c.renameSymbolsInChunk(allocator, chunk, chunk.content.javascript.files_in_chunk_order); + defer r.deinit(); + const part_ranges = chunk.content.javascript.parts_in_chunk_in_order; + for (part_ranges) |part_range| { + c.generateCodeForFileInChunkJS( + allocator, + chunk, + part_range, + toCommonJSRef, + toESMRef, + runtimeRequireRef, + &chunk.entry_bits, + ); + } } pub fn generateChunksInParallel(c: *LinkerContext, chunks_: []Chunk) ![]options.OutputFile { diff --git a/src/js_printer.zig b/src/js_printer.zig index 76a2ba2a49c6..0a26a9741ece 100644 --- a/src/js_printer.zig +++ b/src/js_printer.zig @@ -56,7 +56,7 @@ const assert = std.debug.assert; threadlocal var imported_module_ids_list: std.ArrayList(u32) = undefined; threadlocal var imported_module_ids_list_unset: bool = true; -const ImportRecord = importRecord.ImportRecord; +const ImportRecord = bun.ImportRecord; const SourceMap = @import("./sourcemap/sourcemap.zig"); /// For support JavaScriptCore @@ -591,7 +591,7 @@ const ImportVariant = enum { }; } - pub fn determine(record: *const importRecord.ImportRecord, s_import: *const S.Import) ImportVariant { + pub fn determine(record: *const ImportRecord, s_import: *const S.Import) ImportVariant { var variant = ImportVariant.path_only; if (record.contains_import_star) { @@ -628,8 +628,7 @@ fn NewPrinter( comptime generate_source_map: bool, ) type { return struct { - symbols: Symbol.Map, - import_records: []importRecord.ImportRecord, + import_records: []const ImportRecord, needs_semicolon: bool = false, stmt_start: i32 = -1, @@ -1443,7 +1442,7 @@ fn NewPrinter( .e_identifier => |ident| { if (ident.ref.isSourceContentsSlice()) return false; - const symbol = p.symbols.get(p.symbols.follow(ident.ref)) orelse 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 => { @@ -1452,12 +1451,23 @@ fn NewPrinter( } } + inline fn symbols(p: *const Printer) js_ast.Symbol.Map { + return p.renamer.symbols(); + } + pub fn printRequireError(p: *Printer, text: string) void { p.print("(() => { throw (new Error(`Cannot require module "); p.printQuotedUTF8(text, false); p.print("`)); } )()"); } + pub inline fn importRecord( + p: *const Printer, + import_record_index: usize, + ) *const ImportRecord { + return &p.import_records[import_record_index]; + } + pub fn printRequireOrImportExpr( p: *Printer, import_record_index: u32, @@ -1470,7 +1480,7 @@ fn NewPrinter( defer if (wrap) p.print(")"); assert(p.import_records.len > import_record_index); - const record = &p.import_records[import_record_index]; + const record = p.importRecord(import_record_index); if (comptime is_bun_platform) { // "bun" is not a real module. It's just globalThis.Bun. @@ -1780,14 +1790,14 @@ fn NewPrinter( } }, .e_require => |e| { - if (rewrite_esm_to_cjs and p.import_records[e.import_record_index].is_bundled) { + if (rewrite_esm_to_cjs and p.importRecord(e.import_record_index).is_bundled) { p.printIndent(); p.printBundledRequire(e); p.printSemicolonIfNeeded(); return; } - if (!rewrite_esm_to_cjs or !p.import_records[e.import_record_index].is_bundled) { + if (!rewrite_esm_to_cjs or !p.importRecord(e.import_record_index).is_bundled) { p.printRequireOrImportExpr(e.import_record_index, &([_]G.Comment{}), level, flags); } }, @@ -1795,7 +1805,7 @@ fn NewPrinter( if (p.options.rewrite_require_resolve) { // require.resolve("../src.js") => "../src.js" p.printSpaceBeforeIdentifier(); - p.printQuotedUTF8(p.import_records[e.import_record_index].path.text, true); + p.printQuotedUTF8(p.importRecord(e.import_record_index).path.text, true); } else { const wrap = level.gte(.new) or flags.contains(.forbid_call); if (wrap) { @@ -1804,7 +1814,7 @@ fn NewPrinter( p.printSpaceBeforeIdentifier(); p.print("require.resolve("); - p.printQuotedUTF8(p.import_records[e.import_record_index].path.text, true); + p.printQuotedUTF8(p.importRecord(e.import_record_index).path.text, true); p.print(")"); if (wrap) { @@ -2286,13 +2296,13 @@ fn NewPrinter( // Potentially use a property access instead of an identifier var didPrint = false; - if (p.symbols.getWithLink(e.ref)) |symbol| { + if (p.symbols().getWithLink(e.ref)) |symbol| { if (symbol.import_item_status == .missing) { p.addSourceMapping(expr.loc); p.printUndefined(level); didPrint = true; } else if (symbol.namespace_alias) |namespace| { - const import_record = p.import_records[namespace.import_record_index]; + const import_record = p.importRecord(namespace.import_record_index); if ((comptime is_inside_bundle) or import_record.is_bundled or namespace.was_originally_property_access) { var wrap = false; didPrint = true; @@ -2794,8 +2804,8 @@ fn NewPrinter( } }, .e_import_identifier => |e| { - const ref = p.symbols.follow(e.ref); - if (p.symbols.get(ref)) |symbol| { + const ref = p.symbols().follow(e.ref); + 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); @@ -2831,8 +2841,8 @@ fn NewPrinter( // if (strings) {} }, .e_import_identifier => |e| { - const ref = p.symbols.follow(e.ref); - if (p.symbols.get(ref)) |symbol| { + const ref = p.symbols().follow(e.ref); + 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); @@ -3317,7 +3327,7 @@ fn NewPrinter( } p.printWhitespacer(ws("from ")); - p.printImportRecordPath(&p.import_records[s.import_record_index]); + p.printImportRecordPath(p.importRecord(s.import_record_index)); p.printSemicolonAfterStatement(); }, .s_export_clause => |s| { @@ -3347,12 +3357,12 @@ fn NewPrinter( p.printSpace(); const last = s.items.len - 1; for (s.items) |item, i| { - const symbol = p.symbols.getWithLink(item.name.ref.?).?; + 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.import_records[namespace.import_record_index]; + const import_record = p.importRecord(namespace.import_record_index); if (import_record.is_bundled or (comptime is_inside_bundle) or namespace.was_originally_property_access) { p.printIdentifier(name); p.print(": () => "); @@ -3421,9 +3431,9 @@ fn NewPrinter( const item: js_ast.ClauseItem = array.items[i]; if (item.original_name.len > 0) { - if (p.symbols.get(item.name.ref.?)) |symbol| { + if (p.symbols().get(item.name.ref.?)) |symbol| { if (symbol.namespace_alias) |namespace| { - const import_record = p.import_records[namespace.import_record_index]; + const import_record = p.importRecord(namespace.import_record_index); if (import_record.is_bundled or (comptime is_inside_bundle) or namespace.was_originally_property_access) { p.print("var "); p.printSymbol(item.name.ref.?); @@ -3537,7 +3547,7 @@ fn NewPrinter( p.printIndent(); p.printSpaceBeforeIdentifier(); - const import_record = p.import_records[s.import_record_index]; + const import_record = p.importRecord(s.import_record_index); if (comptime is_bun_platform) { if (import_record.do_commonjs_transform_in_printer) { @@ -3849,7 +3859,7 @@ fn NewPrinter( .s_import => |s| { std.debug.assert(s.import_record_index < p.import_records.len); - const record: *const ImportRecord = &p.import_records[s.import_record_index]; + const record: *const ImportRecord = p.importRecord(s.import_record_index); switch (record.print_mode) { .css => { @@ -4260,12 +4270,12 @@ fn NewPrinter( } } - pub fn printBundledImport(p: *Printer, record: importRecord.ImportRecord, s: *S.Import) void { + pub fn printBundledImport(p: *Printer, record: ImportRecord, s: *S.Import) void { if (record.is_internal) { return; } - const import_record = p.import_records[s.import_record_index]; + const import_record = p.importRecord(s.import_record_index); const is_disabled = import_record.path.is_disabled; const module_id = import_record.module_id; @@ -4273,7 +4283,7 @@ fn NewPrinter( // we can skip it if (record.path.is_disabled) { - if (p.symbols.get(s.namespace_ref) == null) + if (p.symbols().get(s.namespace_ref) == null) return; } @@ -4371,13 +4381,13 @@ fn NewPrinter( } pub fn printLoadFromBundleWithoutCall(p: *Printer, import_record_index: u32) void { - const record = p.import_records[import_record_index]; + const record = p.importRecord(import_record_index); if (record.path.is_disabled) { p.printDisabledImport(); return; } - @call(.always_inline, printModuleId, .{ p, p.import_records[import_record_index].module_id }); + @call(.always_inline, printModuleId, .{ p, p.importRecord(import_record_index).module_id }); } pub fn printCallModuleID(p: *Printer, module_id: u32) void { @@ -4396,7 +4406,7 @@ fn NewPrinter( } pub fn printBundledRequire(p: *Printer, require: E.Require) void { - if (p.import_records[require.import_record_index].is_internal) { + if (p.importRecord(require.import_record_index).is_internal) { return; } @@ -4775,12 +4785,11 @@ fn NewPrinter( pub fn init( writer: Writer, - tree: *const Ast, - source: *const logger.Source, - symbols: Symbol.Map, + import_records: []const ImportRecord, opts: Options, - allocator: std.mem.Allocator, - ) !Printer { + renamer: bun.renamer.Renamer, + source_map_builder: SourceMap.Chunk.Builder, + ) Printer { if (imported_module_ids_list_unset) { imported_module_ids_list = std.ArrayList(u32).init(default_allocator); imported_module_ids_list_unset = false; @@ -4788,24 +4797,12 @@ fn NewPrinter( imported_module_ids_list.clearRetainingCapacity(); - var source_map_builder: SourceMap.Chunk.Builder = undefined; - - if (comptime generate_source_map) { - source_map_builder = SourceMap.Chunk.Builder{ - .source_map = SourceMap.Chunk.Builder.SourceMapper.init(allocator, is_bun_platform), - .cover_lines_without_mappings = true, - .prepend_count = is_bun_platform, - }; - source_map_builder.line_offset_tables = SourceMap.LineOffsetTable.generate(allocator, source.contents, @intCast(i32, tree.approximate_newline_count)); - } - return Printer{ - .import_records = tree.import_records.slice(), + .import_records = import_records, .options = opts, - .symbols = symbols, .writer = writer, .imported_module_ids = imported_module_ids_list, - .renamer = rename.Renamer.init(symbols, source), + .renamer = renamer, .source_map_builder = source_map_builder, }; } @@ -5210,6 +5207,34 @@ pub const Format = enum { cjs_ascii, }; +fn getSourceMapBuilder( + comptime generate_source_map: bool, + comptime is_bun_platform: bool, + opts: Options, + source: *const logger.Source, + tree: *const Ast, +) SourceMap.Chunk.Builder { + if (comptime !generate_source_map) + return undefined; + + return .{ + .source_map = SourceMap.Chunk.Builder.SourceMapper.init( + opts.allocator, + is_bun_platform, + ), + .cover_lines_without_mappings = true, + .prepend_count = is_bun_platform, + .line_offset_tables = SourceMap.LineOffsetTable.generate( + opts.allocator, + source.contents, + @intCast( + i32, + tree.approximate_newline_count, + ), + ), + }; +} + pub fn printAst( comptime Writer: type, _writer: Writer, @@ -5220,6 +5245,8 @@ pub fn printAst( opts: Options, comptime generate_source_map: bool, ) !usize { + var renamer = rename.NoOpRenamer.init(symbols, source); + const PrinterType = NewPrinter( ascii_only, Writer, @@ -5232,13 +5259,12 @@ pub fn printAst( ); var writer = _writer; - var printer = try PrinterType.init( + var printer = PrinterType.init( writer, - &tree, - source, - symbols, + tree.import_records.slice(), opts, - opts.allocator, + renamer.toRenamer(), + getSourceMapBuilder(generate_source_map, ascii_only, opts, source, tree), ); defer { imported_module_ids_list = printer.imported_module_ids; @@ -5289,16 +5315,15 @@ pub fn printJSON( var stmts = &[_]js_ast.Stmt{stmt}; var parts = &[_]js_ast.Part{.{ .stmts = stmts }}; const ast = Ast.initTest(parts); - var arena = std.heap.ArenaAllocator.init(bun.default_allocator); - defer arena.deinit(); - var allocator = arena.allocator(); - var printer = try PrinterType.init( + + var renamer = rename.NoOpRenamer.init(ast.symbols, source); + + var printer = PrinterType.init( writer, - &ast, - source, - .{}, + ast.import_records.slice(), .{}, - allocator, + renamer.toRenamer(), + undefined, ); printer.printExpr(expr, Level.lowest, ExprFlag.Set{}); @@ -5310,6 +5335,50 @@ pub fn printJSON( return @intCast(usize, @max(printer.writer.written, 0)); } +pub fn print( + comptime Writer: type, + _writer: Writer, + comptime is_bun_platform: bool, + opts: Options, + import_records: []const ImportRecord, + parts: []const js_ast.Part, + renamer: bun.renamer.Renamer, +) PrintResult { + const PrinterType = NewPrinter( + false, + Writer, + false, + is_bun_platform, + false, + false, + false, + ); + var writer = _writer; + var printer = PrinterType.init( + writer, + import_records, + opts, + renamer, + undefined, + ); + + defer { + imported_module_ids_list = printer.imported_module_ids; + } + + for (parts) |part| { + for (part.stmts) |stmt| { + printer.printSemicolonIfNeeded(); + printer.printStmt(stmt) catch |err| { + return .{ .err = err }; + }; + if (printer.writer.getError()) {} else |err| { + return .{ .err = err }; + } + } + } +} + pub fn printCommonJS( comptime Writer: type, _writer: Writer, @@ -5322,13 +5391,13 @@ pub fn printCommonJS( ) !usize { const PrinterType = NewPrinter(ascii_only, Writer, true, false, false, false, generate_source_map); var writer = _writer; - var printer = try PrinterType.init( + var renamer = rename.NoOpRenamer.init(symbols, source); + var printer = PrinterType.init( writer, - &tree, - source, - symbols, + tree.import_records.slice(), opts, - opts.allocator, + renamer.toRenamer(), + getSourceMapBuilder(generate_source_map, false, opts, source, tree), ); defer { imported_module_ids_list = printer.imported_module_ids; @@ -5375,7 +5444,7 @@ pub fn printCommonJS( // opts: Options, // ) PrintResult { // var writer = _writer; -// var printer = try PrinterType.init( +// var printer = PrinterType.init( // writer, // &tree, // source, @@ -5401,12 +5470,12 @@ pub fn printCommonJSThreaded( ) !WriteResult { const PrinterType = NewPrinter(ascii_only, Writer, true, ascii_only, true, false, false); var writer = _writer; - var printer = try PrinterType.init( + var renamer = rename.NoOpRenamer.init(symbols, source); + var printer = PrinterType.init( writer, - &tree, - source, - symbols, + tree.import_records.slice(), opts, + renamer.toRenamer(), undefined, ); diff --git a/src/renamer.zig b/src/renamer.zig index ac8dae64acf6..51a8ef7f5323 100644 --- a/src/renamer.zig +++ b/src/renamer.zig @@ -35,12 +35,24 @@ pub const NoOpRenamer = struct { Global.panic("Invalid symbol {s} in {s}", .{ ref, renamer.source.path.text }); } } + + pub fn toRenamer(this: *NoOpRenamer) Renamer { + return .{ + .NoOpRenamer = this, + }; + } }; pub const Renamer = union(enum) { NumberRenamer: *NumberRenamer, NoOpRenamer: *NoOpRenamer, + pub fn symbols(this: Renamer) js_ast.Symbol.Map { + return switch (this) { + inline else => |r| r.symbols, + }; + } + pub fn nameForSymbol(renamer: Renamer, ref: Ref) string { return switch (renamer) { inline else => |r| r.nameForSymbol(ref), From 0f23cbc3f6be4a0e45bcd328c8e1f54175297d6d Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Tue, 14 Feb 2023 21:19:07 -0800 Subject: [PATCH 012/255] wip --- src/bundler/bundle_v2.zig | 434 ++++++++++++++++++++++++++++++++++++-- src/js_printer.zig | 40 ++++ 2 files changed, 462 insertions(+), 12 deletions(-) diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 323971cede23..acb60927f378 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -69,6 +69,10 @@ const EventLoop = bun.JSC.AnyEventLoop; const MultiArrayList = bun.MultiArrayList; const Stmt = js_ast.Stmt; const Expr = js_ast.Expr; +const E = js_ast.E; +const S = js_ast.S; +const G = js_ast.G; +const B = js_ast.B; const Binding = js_ast.Binding; const AutoBitSet = bun.bit_set.AutoBitSet; const renamer = bun.renamer; @@ -1041,7 +1045,7 @@ const IdentityContext = @import("../identity_context.zig").IdentityContext; const RefVoidMap = std.ArrayHashMapUnmanaged(Ref, void, Ref.ArrayHashCtx, false); const RefVoidMapManaged = std.ArrayHashMap(Ref, void, Ref.ArrayHashCtx, false); const RefImportData = std.ArrayHashMapUnmanaged(Ref, ImportData, Ref.ArrayHashCtx, false); -const RefExportData = bun.StringArrayHashMapUnmanaged(ExportData); +const ResolvedExports = bun.StringArrayHashMapUnmanaged(ExportData); const TopLevelSymbolToParts = js_ast.Ast.TopLevelSymbolToParts; pub const WrapKind = enum { @@ -1135,7 +1139,7 @@ pub const JSMeta = struct { /// /// Re-exports come from other files and are the result of resolving export /// star statements (i.e. "export * from 'foo'"). - resolved_exports: RefExportData = .{}, + resolved_exports: ResolvedExports = .{}, resolved_export_star: ExportData = ExportData{}, /// Never iterate over "resolvedExports" directly. Instead, iterate over this @@ -2075,7 +2079,7 @@ const LinkerContext = struct { // are ignored for those modules. { var export_star_ctx: ?ExportStarContext = null; - var resolved_exports: []RefExportData = this.graph.meta.items(.resolved_exports); + var resolved_exports: []ResolvedExports = this.graph.meta.items(.resolved_exports); var resolved_export_stars: []ExportData = this.graph.meta.items(.resolved_export_star); for (reachable) |source_index_| { @@ -2373,7 +2377,7 @@ const LinkerContext = struct { this.allocator, @as(usize, @boolToInt(force_include_exports)) + @as(usize, @boolToInt(add_wrapper)), ) catch unreachable; - var resolved_exports_list: *RefExportData = &this.graph.meta.items(.resolved_exports)[id]; + var resolved_exports_list: *ResolvedExports = &this.graph.meta.items(.resolved_exports)[id]; for (aliases) |alias| { var export_ = resolved_exports_list.get(alias).?; var target_source_index = export_.data.source_index.get(); @@ -2628,7 +2632,15 @@ const LinkerContext = struct { } } - pub fn createExportsForFile(c: *LinkerContext, allocator_: std.mem.Allocator, id: u32, resolved_exports: *RefExportData, imports_to_bind: []RefImportData, export_aliases: []const string, re_exports_count: usize) void { + pub fn createExportsForFile( + c: *LinkerContext, + allocator_: std.mem.Allocator, + id: u32, + resolved_exports: *ResolvedExports, + imports_to_bind: []RefImportData, + export_aliases: []const string, + re_exports_count: usize, + ) void { //////////////////////////////////////////////////////////////////////////////// // WARNING: This method is run in parallel over all files. Do not mutate data // for other files within this method or you will create a data race. @@ -2858,7 +2870,7 @@ const LinkerContext = struct { // we must use this allocator here const allocator_ = worker.allocator; - var resolved_exports: *RefExportData = &c.graph.meta.items(.resolved_exports)[id]; + var resolved_exports: *ResolvedExports = &c.graph.meta.items(.resolved_exports)[id]; // Now that all exports have been resolved, sort and filter them to create // something we can iterate over later. @@ -3096,7 +3108,7 @@ const LinkerContext = struct { imports_to_bind: []RefImportData, wrapper_refs: []const ?Ref, sorted_and_filtered_export_aliases: []const []const string, - resolved_exports: []const RefExportData, + resolved_exports: []const ResolvedExports, ctx: *LinkerContext, symbols: *Symbol.Map, @@ -3648,22 +3660,413 @@ const LinkerContext = struct { const toESMRef = c.graph.symbols.follow(runtime_members.get("toESM").?.ref); const runtimeRequireRef = c.graph.symbols.follow(runtime_members.get("__require").?.ref); - var r = try c.renameSymbolsInChunk(allocator, chunk, chunk.content.javascript.files_in_chunk_order); + var r = try c.renameSymbolsInChunk(allocator, chunk, repr.files_in_chunk_order); defer r.deinit(); - const part_ranges = chunk.content.javascript.parts_in_chunk_in_order; - for (part_ranges) |part_range| { + const part_ranges = repr.parts_in_chunk_in_order; + var stmts = StmtList.init(allocator); + defer stmts.deinit(); + + js_ast.Expr.Data.Store.create(allocator); + js_ast.Stmt.Data.Store.create(allocator); + var arena = std.heap.ArenaAllocator.init(allocator); + defer arena.deinit(); + for (part_ranges) |part_range, i| { + if (i > 0) arena.reset(.retain_capacity); c.generateCodeForFileInChunkJS( allocator, + r, chunk, part_range, toCommonJSRef, toESMRef, runtimeRequireRef, &chunk.entry_bits, + &stmts, + allocator, + arena.allocator(), ); } } + const StmtList = struct { + inside_wrapper_prefix: std.ArrayList(Stmt), + outside_wrapper_prefix: std.ArrayList(Stmt), + inside_wrapper_suffix: std.ArrayList(Stmt), + + all_stmts: std.ArrayList(Stmt), + + pub fn reset(this: *StmtList) void { + this.inside_wrapper_prefix.clearRetainingCapacity(); + this.outside_wrapper_prefix.clearRetainingCapacity(); + this.inside_wrapper_suffix.clearRetainingCapacity(); + this.all_stmts.clearRetainingCapacity(); + } + + pub fn deinit(this: *StmtList) void { + this.inside_wrapper_prefix.deinit(); + this.outside_wrapper_prefix.deinit(); + this.inside_wrapper_suffix.deinit(); + this.all_stmts.deinit(); + } + + pub fn init(allocator: std.mem.Allocator) StmtList { + return .{ + .inside_wrapper_prefix = std.ArrayList(Stmt).init(allocator), + .outside_wrapper_prefix = std.ArrayList(Stmt).init(allocator), + .inside_wrapper_suffix = std.ArrayList(Stmt).init(allocator), + .all_stmts = std.ArrayList(Stmt).init(allocator), + }; + } + }; + + fn generateCodeForFileInChunkJS( + c: *LinkerContext, + r: renamer.Renamer, + chunk: *Chunk, + part_range: PartRange, + toCommonJSRef: Ref, + toESMRef: Ref, + runtimeRequireRef: Ref, + entry_bits: *AutoBitSet, + stmts: *StmtList, + allocator: std.mem.Allocator, + temp_allocator: std.mem.Allocator, + ) js_printer.PrintResult { + var repr = &chunk.content.javascript; + // var file = &c.graph.files.items(.input_file)[part.source_index.get()]; + var parts: []js_ast.Part = c.graph.ast.items(.parts)[part_range.source_index.get()][part_range.part_index_begin..part_range.part_index_end]; + const resolved_exports: []ResolvedExports = c.graph.meta.items(.resolved_exports); + const flags: JSMeta.Flags = c.graph.meta.items(.flags)[part_range.source_index.get()]; + const wrapper_part_index = c.graph.meta.items(.wrapper_part_index)[part_range.source_index.get()]; + const uses_exports_ref = c.graph.ast.items(.uses_exports_ref)[part_range.source_index.get()]; + const uses_module_ref = c.graph.ast.items(.uses_module_ref)[part_range.source_index.get()]; + const exports_ref = c.graph.ast.items(.exports_ref)[part_range.source_index.get()]; + const module_ref = c.graph.ast.items(.module_ref)[part_range.source_index.get()]; + const wrapper_ref = c.graph.ast.items(.wrapper_ref)[part_range.source_index.get()]; + + js_ast.Expr.Data.Store.reset(); + js_ast.Stmt.Data.Store.reset(); + + var needs_wrapper = false; + + const namespace_export_part_index = js_ast.namespace_export_part_index; + + stmts.reset(); + + // TODO: handle directive + if (namespace_export_part_index >= part_range.part_index_begin and + namespace_export_part_index < part_range.part_index_end and + parts[namespace_export_part_index].is_live) + { + c.convertStmtsForChunk(part_range.source_index, stmts, parts[namespace_export_part_index].stmts); + + switch (flags.wrap) { + .esm => { + stmts.outside_wrapper_prefix.appendSlice(stmts.inside_wrapper_suffix.items) catch unreachable; + }, + .cjs => { + stmts.inside_wrapper_prefix.appendSlice(stmts.inside_wrapper_suffix.items) catch unreachable; + }, + } + stmts.inside_wrapper_suffix.clearRetainingCapacity(); + } + + // TODO: defaultLazyExport + + // Add all other parts in this chunk + for (parts) |part| { + if (!part.is_live) { + // Skip the part if it's not in this chunk + + continue; + } + + if (part.part_index == namespace_export_part_index) { + // Skip the namespace export part because we already handled it above + continue; + } + + if (part.part_index == wrapper_part_index) { + // Skip the wrapper part because we already handled it above + needs_wrapper = true; + continue; + } + + // TODO: lazy default export + + // convert + c.convertStmtsForChunk(part_range.source_index, stmts, part.stmts); + } + + // Hoist all import statements before any normal statements. ES6 imports + // are different than CommonJS imports. All modules imported via ES6 import + // statements are evaluated before the module doing the importing is + // evaluated (well, except for cyclic import scenarios). We need to preserve + // these semantics even when modules imported via ES6 import statements end + // up being CommonJS modules. + + stmts.all_stmts.appendSlice(stmts.inside_wrapper_prefix.items) catch unreachable; + + var out_stmts = stmts.all_stmts.items; + // Optionally wrap all statements in a closure + if (needs_wrapper) { + switch (flags.wrap) { + .cjs => { + // Only include the arguments that are actually used + var args = std.ArrayList(js_ast.G.Arg).initCapacity( + temp_allocator, + @boolToInt(uses_exports_ref) + @boolToInt(uses_module_ref), + ) catch unreachable; + + if (uses_exports_ref) { + args.appendAssumeCapacity( + js_ast.G.Arg{ + .binding = js_ast.Binding.alloc( + temp_allocator, + js_ast.B.Identifier{ + .ref = exports_ref, + }, + Logger.Loc.Empty, + ), + }, + ); + } + + if (uses_module_ref) { + args.appendAssumeCapacity( + js_ast.G.Arg{ + .binding = js_ast.Binding.alloc( + temp_allocator, + js_ast.B.Identifier{ + .ref = module_ref, + }, + Logger.Loc.Empty, + ), + }, + ); + } + + // TODO: variants of the runtime functions + var cjs_args = temp_allocator.alloc(Expr, 1) catch unreachable; + cjs_args[0] = Expr.init( + E.Arrow, + E.Arrow{ + .args = args.items, + .body = .{ + .block = Stmt.init( + S.Block, + .{ + .stmts = stmts.all_stmts.items, + }, + Logger.Loc.Empty, + ), + }, + }, + Logger.Loc.Empty, + ); + + const commonjs_wrapper_definition = Expr.init( + E.Call, + E.Call{ + .target = Expr.init( + E.Identifier, + E.Identifier{ + .ref = toCommonJSRef, + }, + Logger.Loc.Empty, + ), + .args = cjs_args, + }, + Logger.Loc.Empty, + ); + + // "var require_foo = __commonJS(...);" + { + var decls = temp_allocator.alloc(G.Decl, 1) catch unreachable; + decls[0] = G.Decl{ + .binding = Binding.alloc( + temp_allocator, + B.Identifier{ + .ref = wrapper_ref.?, + }, + Logger.Loc.Empty, + ), + .value = commonjs_wrapper_definition, + }; + + stmts.outside_wrapper_prefix.append( + Stmt.init( + S.Local, + S.Local{ + .decls = decls, + }, + Logger.Loc.Empty, + ), + ) catch unreachable; + } + }, + .esm => { + // The wrapper only needs to be "async" if there is a transitive async + // dependency. For correctness, we must not use "async" if the module + // isn't async because then calling "require()" on that module would + // swallow any exceptions thrown during module initialization. + const is_async = flags.is_async_or_has_async_dependency; + + // Hoist all top-level "var" and "function" declarations out of the closure + { + const Hoisty = struct { + decls: std.ArrayList(G.Decl), + + pub fn wrapIdentifier(w: *@This(), loc: Logger.Loc, ref: Ref) Expr { + w.decls.append( + G.Decl{ + .binding = Binding.alloc( + w.decls.allocator, + B.Identifier{ + .ref = ref, + }, + loc, + ), + .value = Expr.init( + E.Identifier, + E.Identifier{ + .ref = ref, + }, + loc, + ), + }, + ) catch unreachable; + return Expr.init( + E.Identifier, + E.Identifier{ + .ref = ref, + }, + loc, + ); + } + }; + var hoisty = Hoisty{ + .decls = std.ArrayList(G.Decl).init(temp_allocator), + }; + var end: usize = 0; + for (stmts.all_stmts.items) |stmt_| { + var stmt: Stmt = stmt_; + switch (stmt.data) { + .s_local => |local| { + var value: Expr = Expr.init(E.Missing, E.Missing{}, Logger.Loc.Empty); + for (local.decls) |*decl| { + const binding = decl.binding.toExpr(hoisty); + if (decl.value) |other| { + value = value.joinWithComma( + binding.assign( + other, + temp_allocator, + ), + temp_allocator, + ); + } + } + + if (value.isEmpty()) { + continue; + } + stmt = Stmt.init( + S.Expr, + S.Expr{ + .expr = value, + }, + stmt.loc, + ); + }, + .s_function => { + stmts.outside_wrapper_prefix.append(stmt) catch unreachable; + continue; + }, + } + stmts.all_stmts.items[end] = stmt; + end += 1; + } + stmts.all_stmts.items.len = end; + } + + // TODO: variants of the runtime functions + + // "__esm(() => { ... })" + var esm_args = temp_allocator.alloc(Expr, 1) catch unreachable; + esm_args[0] = Expr.init( + E.Arrow, + E.Arrow{ + .args = &.{}, + .is_async = is_async, + .body = .{ + .block = Stmt.init( + S.Block, + .{ + .stmts = stmts.all_stmts.items, + }, + Logger.Loc.Empty, + ), + }, + }, + Logger.Loc.Empty, + ); + + // "var init_foo = __esm(...);" + { + const value = Expr.init( + E.Call, + E.Call{ + .target = Expr.init( + E.Identifier, + E.Identifier{ + .ref = toESMRef, + }, + Logger.Loc.Empty, + ), + .args = esm_args, + }, + Logger.Loc.Empty, + ); + + var decls = temp_allocator.alloc(G.Decl, 1) catch unreachable; + decls[0] = G.Decl{ + .binding = Binding.alloc( + temp_allocator, + B.Identifier{ + .ref = wrapper_ref.?, + }, + Logger.Loc.Empty, + ), + .value = value, + }; + + stmts.outside_wrapper_prefix.append( + Stmt.init( + S.Local, + S.Local{ + .decls = decls, + }, + Logger.Loc.Empty, + ), + ) catch unreachable; + } + }, + else => {}, + } + + out_stmts = stmts.outside_wrapper_prefix.items; + } + + const print_options = js_printer.Options{ + // TODO: IIFE + .indent = 0, + + .allocator = allocator, + .to_module_ref = toESMRef, + .require_ref = runtimeRequireRef, + }; + } + pub fn generateChunksInParallel(c: *LinkerContext, chunks_: []Chunk) ![]options.OutputFile { { var wait_group = try c.allocator.create(sync.WaitGroup); @@ -4505,14 +4908,14 @@ const LinkerContext = struct { source_index_stack: std.ArrayList(Index.Int), exports_kind: []js_ast.ExportsKind, named_exports: []js_ast.Ast.NamedExports, - resolved_exports: []RefExportData, + resolved_exports: []ResolvedExports, imports_to_bind: []RefImportData, export_star_records: []const []const Index.Int, allocator: std.mem.Allocator, pub fn addExports( this: *ExportStarContext, - resolved_exports: *RefExportData, + resolved_exports: *ResolvedExports, source_index: Index.Int, ) void { // Avoid infinite loops due to cycles in the export star graph @@ -4927,3 +5330,10 @@ pub const CrossChunkImport = struct { std.sort.sort(CrossChunkImport, result.items, void{}, CrossChunkImport.lessThan); } }; + +const CompileResult = union(enum) { + javascript: struct { + source_index: Index.Int, + reslt: js_printer.PrintResult, + }, +}; diff --git a/src/js_printer.zig b/src/js_printer.zig index 0a26a9741ece..730164169afa 100644 --- a/src/js_printer.zig +++ b/src/js_printer.zig @@ -490,6 +490,8 @@ pub const Options = struct { minify_whitespace: bool = false, + require_or_import_meta_for_source_callback: RequireOrImportMeta.Callback = .{}, + // TODO: remove this // The reason for this is: // 1. You're bundling a React component @@ -513,6 +515,44 @@ pub const Options = struct { pub inline fn unindent(self: *Options) void { self.indent -|= 1; } + + pub fn requireOrImportMetaForSource(self: *const Options, id: u32) RequireOrImportMeta { + if (self.require_or_import_meta_for_source_callback.ctx == null) + return .{}; + + return self.require_or_import_meta_for_source_callback.call(id); + } +}; + +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, + + pub const Callback = struct { + const Fn = fn (*anyopaque, u32) RequireOrImportMeta; + + ctx: ?*anyopaque = null, + callback: *const Fn = undefined, + + pub fn call(self: Callback, id: u32) RequireOrImportMeta { + return self.callback(self.ctx, id); + } + + pub fn init( + comptime Type: type, + comptime callback: (fn (t: *Type, id: u32) RequireOrImportMeta), + ctx: *Type, + ) Callback { + return Callback{ + .ctx = bun.cast(*anyopaque, ctx), + .callback = @ptrCast(*const Fn, callback), + }; + } + }; }; pub const PrintResult = union(enum) { From 688bd8a0cf632f62059300d011c2a2aa3cfc3df4 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Thu, 16 Feb 2023 03:10:32 -0800 Subject: [PATCH 013/255] Most of the printing code --- src/bundler/bundle_v2.zig | 733 +++++++++++++++++++++++++++++++++++++- 1 file changed, 715 insertions(+), 18 deletions(-) diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index acb60927f378..5c510cc2c8aa 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -1571,6 +1571,13 @@ const LinkerContext = struct { output_format: options.OutputFormat = .esm, ignore_dce_annotations: bool = false, tree_shaking: bool = true, + + mode: Mode = Mode.bundle, + + pub const Mode = enum { + passthrough, + bundle, + }; }; fn isExternalDynamicImport(this: *LinkerContext, record: *const ImportRecord, source_index: u32) bool { @@ -3719,30 +3726,651 @@ const LinkerContext = struct { } }; + fn shouldRemoveImportExportStmt( + c: *LinkerContext, + stmts: *StmtList, + loc: Logger.Loc, + namespace_ref: Ref, + import_record_index: u32, + allocator: std.mem.Allocator, + ast: *const js_ast.Ast, + ) !bool { + const record = ast.import_records.at(import_record_index); + + // Is this an external import? + if (!record.source_index.isValid()) { + // Keep the "import" statement if import statements are supported + if (c.options.output_format.keepES6ImportExportSyntax()) { + return false; + } + + // Otherwise, replace this statement with a call to "require()" + stmts.inside_wrapper_prefix.append( + Stmt.alloc( + S.Local, + S.Local{ + .decls = try bun.fromSlice( + []G.Decl, + allocator, + &.{ + .{ + .binding = Binding.alloc( + allocator, + B.Identifier{ + .ref = namespace_ref, + }, + loc, + ), + .value = Expr.init( + E.Require, + E.Require{ + .import_record_index = import_record_index, + }, + loc, + ), + }, + }, + ), + }, + record.range.loc, + ), + ) catch unreachable; + return true; + } + + // We don't need a call to "require()" if this is a self-import inside a + // CommonJS-style module, since we can just reference the exports directly. + if (ast.exports_kind == .cjs and c.graph.symbols.follow(namespace_ref).eql(ast.exports_ref)) { + return true; + } + + const other_flags = c.graph.meta.items(.flags)[record.source_index.get()]; + switch (other_flags.wrap) { + .none => { + // Remove the statement entirely if this module is not wrapped + }, + .cjs => { + // Replace the statement with a call to "require()" if this module is not wrapped + try stmts.inside_wrapper_prefix.append( + Stmt.alloc( + S.Local, + S.Local{ + .decls = try bun.fromSlice( + []G.Decl, + allocator, + &.{ + .{ + .binding = Binding.alloc( + allocator, + B.Identifier{ + .ref = namespace_ref, + }, + loc, + ), + .value = Expr.init( + E.Require, + E.Require{ + .import_record_index = import_record_index, + }, + loc, + ), + }, + }, + ), + }, + loc, + ), + ); + }, + .esm => { + // Ignore this file if it's not included in the bundle. This can happen for + // wrapped ESM files but not for wrapped CommonJS files because we allow + // tree shaking inside wrapped ESM files. + if (!c.graph.files_live.isSet(record.source_index.get())) { + return true; + } + + // Replace the statement with a call to "init()" + const value: Expr = brk: { + const default = Expr.init( + E.Call, + E.Call{ + .target = Expr.initIdentifier( + c.graph.ast.items(.wrapper_ref)[record.source_index.get()].?, + loc, + ), + }, + loc, + ); + + if (other_flags.is_async_or_has_async_dependency) { + // This currently evaluates sibling dependencies in serial instead of in + // parallel, which is incorrect. This should be changed to store a promise + // and await all stored promises after all imports but before any code. + break :brk Expr.init(E.Await, E.Await{ + .value = default, + }); + } + + break :brk default; + }; + + try stmts.inside_wrapper_prefix.append( + Stmt.alloc( + S.SExpr, + S.SExpr{ + .value = value, + }, + loc, + ), + ); + }, + } + } + + fn convertStmtsForChunk( + c: *LinkerContext, + source_index: u32, + stmts: *StmtList, + part_stmts: []const js_ast.Stmt, + chunk: *Chunk, + allocator: std.mem.Allocator, + wrap: WrapKind, + ast: *const js_ast.Ast, + ) !void { + const shouldExtractESMStmtsForWrap = wrap != .none; + const shouldStripExports = c.options.mode != .passthrough or !chunk.isEntryPoint(); + + const flags = c.graph.meta.items(.flag); + + // If this file is a CommonJS entry point, double-write re-exports to the + // external CommonJS "module.exports" object in addition to our internal ESM + // export namespace object. The difference between these two objects is that + // our internal one must not have the "__esModule" marker while the external + // one must have the "__esModule" marker. This is done because an ES module + // importing itself should not see the "__esModule" marker but a CommonJS module + // importing us should see the "__esModule" marker. + var module_exports_for_export: ?Expr = null; + if (c.options.output_format == .cjs and chunk.isEntryPoint()) { + module_exports_for_export = Expr.init( + E.Dot, + E.Dot{ + .target = Expr.init( + E.Identifier, + E.Identifier{ + .ref = c.unbound_module_ref, + }, + Logger.Loc.Empty, + ), + .name = "exports", + }, + Logger.Loc.Empty, + ); + } + + for (part_stmts) |_stmt| { + var stmt = _stmt; + switch (stmt.data) { + .s_import => |s| { + // "import * as ns from 'path'" + // "import {foo} from 'path'" + if (c.shouldRemoveImportExportStmt( + source_index, + stmts, + stmt.loc, + s.namespace_ref, + s.import_record_index, + allocator, + ast, + )) { + continue; + } + + // Make sure these don't end up in the wrapper closure + if (shouldExtractESMStmtsForWrap) { + try stmts.outside_wrapper_prefix.append(stmt); + continue; + } + }, + .s_export_star => |s| { + // "export * as ns from 'path'" + if (s.alias) |alias| { + if (c.shouldRemoveImportExportStmt( + source_index, + stmts, + stmt.loc, + s.namespace_ref, + s.import_record_index, + chunk, + allocator, + ast, + )) { + continue; + } + + if (shouldStripExports) { + // Turn this statement into "import * as ns from 'path'" + stmt = Stmt.alloc( + S.Import, + S.Import{ + .namespace_ref = s.namespace_ref, + .import_record_index = s.import_record_index, + .star_name_loc = alias.loc, + }, + stmt.loc, + ); + } + + // Make sure these don't end up in the wrapper closure + if (shouldExtractESMStmtsForWrap) { + try stmts.outside_wrapper_prefix.append(stmt); + continue; + } + + break; + } + + // "export * from 'path'" + if (!shouldStripExports) { + break; + } + + const record = ast.import_records.at(s.import_record_index); + + // Is this export star evaluated at run time? + if (!record.source_index.isValid() and c.options.output_format.keepES6ImportExportSyntax()) { + if (record.calls_runtime_re_export_fn) { + // Turn this statement into "import * as ns from 'path'" + stmt = Stmt.alloc( + S.Import, + S.Import{ + .namespace_ref = s.namespace_ref, + .import_record_index = s.import_record_index, + .star_name_loc = stmt.loc, + }, + stmt.loc, + ); + + // Prefix this module with "__reExport(exports, ns, module.exports)" + const export_star_ref = c.runtimeFunction("__reExport"); + var args = try allocator.alloc(Expr, 2 + @boolToInt(module_exports_for_export != null)); + args[0..2].* = .{ + Expr.init( + E.Identifier, + E.Identifier{ + .ref = ast.exports_ref, + }, + stmt.loc, + ), + Expr.init( + E.Identifier, + E.Identifier{ + .ref = s.namespace_ref, + }, + stmt.loc, + ), + }; + + if (module_exports_for_export) |mod| { + args[3] = mod; + } + + try stmts.inside_wrapper_prefix.append( + Stmt.alloc( + S.SExpr, + S.SExpr{ + .expr = Expr.init( + E.Call, + E.Call{ + .target = Expr.init( + E.Identifier, + E.Identifier{ + .ref = export_star_ref, + }, + stmt.loc, + ), + .args = args, + }, + stmt.loc, + ), + }, + stmt.loc, + ), + ); + + // Make sure these don't end up in the wrapper closure + if (shouldExtractESMStmtsForWrap) { + try stmts.outside_wrapper_prefix.append(stmt); + continue; + } + } + } else { + if (record.source_index.isValid()) { + const flag = flags[record.source_index.get()]; + if (flag.wrap == .esm) { + try stmts.inside_wrapper_prefix.append( + Stmt.alloc( + S.SExpr, + .{ + .value = Expr.init( + E.Call, + E.Call{ + .target = Expr.init( + E.Identifier, + E.Identifier{ + .ref = c.graph.ast.items(.wrapper_ref)[record.source_index.get()].?, + }, + stmt.loc, + ), + }, + stmt.loc, + ), + }, + stmt.loc, + ), + ); + } + } + + if (record.calls_runtime_re_export_fn) { + const target: Expr = brk: { + if (c.graph.ast.items(.exports_kind)[source_index] == .esm_with_dynamic_fallback) { + // Prefix this module with "__reExport(exports, otherExports, module.exports)" + break :brk Expr.initIdentifier(c.graph.ast.items(.exports_ref)[source_index], stmt.loc); + } + + break :brk Expr.init( + E.Require, + E.Require{ + .import_record_index = s.import_record_index, + }, + stmt.loc, + .{}, + ); + }; + + // Prefix this module with "__reExport(exports, require(path), module.exports)" + const export_star_ref = c.runtimeFunction("__reExport"); + var args = try allocator.alloc(Expr, 2 + @boolToInt(module_exports_for_export != null)); + args[0..2].* = .{ + Expr.init( + E.Identifier, + E.Identifier{ + .ref = ast.exports_ref, + }, + stmt.loc, + ), + target, + }; + + if (module_exports_for_export) |mod| { + args[3] = mod; + } + + try stmts.inside_wrapper_prefix.append( + Stmt.alloc( + S.SExpr, + S.SExpr{ + .expr = Expr.init( + E.Call, + E.Call{ + .target = Expr.init( + E.Identifier, + E.Identifier{ + .ref = export_star_ref, + }, + stmt.loc, + ), + .args = args, + }, + stmt.loc, + ), + }, + stmt.loc, + ), + ); + + // Remove the export star statement + continue; + } + } + }, + + .s_export_from => |s| { + // "export {foo} from 'path'" + + if (try c.shouldRemoveImportExportStmt( + source_index, + stmts, + stmt.loc, + s.namespace_ref, + s.import_record_index, + allocator, + ast, + )) { + continue; + } + + if (shouldStripExports) { + // Turn this statement into "import {foo} from 'path'" + + for (s.items) |*item| { + item.alias = item.original_name; + } + + stmt = Stmt.alloc( + S.Import, + S.Import{ + .items = s.items, + .import_record_index = s.import_record_index, + .star_name_loc = stmt.loc, + .namespace_ref = s.namespace_ref, + .is_single_line = s.is_single_line, + }, + stmt.loc, + ); + } + + // Make sure these don't end up in the wrapper closure + if (shouldExtractESMStmtsForWrap) { + try stmts.outside_wrapper_prefix.append(stmt); + continue; + } + }, + + .s_export_clause => { + // "export {foo}" + + if (shouldStripExports) { + // Remove export statements entirely + + continue; + } + + // Make sure these don't end up in the wrapper closure + if (shouldExtractESMStmtsForWrap) { + try stmts.outside_wrapper_prefix.append(stmt); + continue; + } + }, + + .s_function => |s| { + + // Strip the "export" keyword while bundling + if (shouldStripExports and s.func.flags.contains(.is_export)) { + // Be c areful to not modify the original statement + stmt = Stmt.alloc( + S.Function, + S.Function{ + .func = s.func, + }, + stmt.loc, + ); + stmt.data.s_function.func.flags.remove(.is_export); + } + }, + + .s_class => |s| { + + // Strip the "export" keyword while bundling + if (shouldStripExports and s.is_export) { + // Be c areful to not modify the original statement + stmt = Stmt.alloc( + S.Class, + S.Class{ + .class = s.class, + .is_export = false, + }, + stmt.loc, + ); + } + }, + + .s_local => |s| { + // Strip the "export" keyword while bundling + if (shouldStripExports and s.is_export) { + // Be c areful to not modify the original statement + stmt = Stmt.alloc( + S.Local, + s.*, + stmt.loc, + ); + stmt.data.s_local.is_export = false; + } + }, + + .s_export_default => |s| { + // "export default foo" + + if (shouldStripExports) { + switch (s.value) { + .stmt => { + var stmt2 = s.value.stmt; + switch (stmt2.data) { + .s_expr => |s2| { + // "export default foo;" => "var default = foo;" + stmt = Stmt.alloc( + S.Local, + S.Local{ + .decls = try bun.from( + []js_ast.Decl, + allocator, + &.{ + .{ + .binding = Binding.alloc( + allocator, + B.Identifier{ + .ref = s.default_name.ref.?, + }, + s2.loc, + ), + .value = s2.value, + }, + }, + ), + }, + stmt.loc, + ); + }, + .s_function => |s2| { + // "export default function() {}" => "function default() {}" + // "export default function foo() {}" => "function foo() {}" + + // Be careful to not modify the original statement + stmt = Stmt.alloc( + S.Function, + S.Function{ + .func = s2.func, + }, + stmt.loc, + ); + stmt.data.s_function.func.name = s.default_name; + }, + + .s_class => |s2| { + // "export default class {}" => "class default {}" + // "export default class foo {}" => "class foo {}" + + // Be careful to not modify the original statement + stmt = Stmt.alloc( + S.Class, + S.Class{ + .class = s2.class, + .is_export = false, + }, + stmt.loc, + ); + stmt.data.s_class.class.class_name = s.default_name; + }, + + else => bun.unreachablePanic( + "Unexpected type {any}", + .{stmt2.data}, + ), + } + }, + .expr => |e| { + stmt = Stmt.alloc( + S.Local, + S.Local{ + .decls = try bun.from( + []js_ast.Decl, + allocator, + &.{ + .{ + .binding = Binding.alloc( + allocator, + B.Identifier{ + .ref = s.default_name.ref.?, + }, + e.loc, + ), + .value = e, + }, + }, + ), + }, + stmt.loc, + ); + }, + } + } + }, + + else => {}, + } + + try stmts.inside_wrapper_suffix.append(stmt); + } + } + + fn runtimeFunction(c: *LinkerContext, name: []const u8) Ref { + return c.graph.ast.items(.module_scope)[Ref.RuntimeRef.source_index].members.get(name).?.ref; + } + fn generateCodeForFileInChunkJS( c: *LinkerContext, + writer: *js_printer.BufferWriter, r: renamer.Renamer, chunk: *Chunk, part_range: PartRange, toCommonJSRef: Ref, toESMRef: Ref, runtimeRequireRef: Ref, - entry_bits: *AutoBitSet, stmts: *StmtList, allocator: std.mem.Allocator, temp_allocator: std.mem.Allocator, ) js_printer.PrintResult { - var repr = &chunk.content.javascript; // var file = &c.graph.files.items(.input_file)[part.source_index.get()]; var parts: []js_ast.Part = c.graph.ast.items(.parts)[part_range.source_index.get()][part_range.part_index_begin..part_range.part_index_end]; const resolved_exports: []ResolvedExports = c.graph.meta.items(.resolved_exports); - const flags: JSMeta.Flags = c.graph.meta.items(.flags)[part_range.source_index.get()]; + _ = resolved_exports; + const all_flags: []const JSMeta.Flags = c.graph.meta.items(.flags); + const flags = all_flags[part_range.source_index.get()]; const wrapper_part_index = c.graph.meta.items(.wrapper_part_index)[part_range.source_index.get()]; - const uses_exports_ref = c.graph.ast.items(.uses_exports_ref)[part_range.source_index.get()]; - const uses_module_ref = c.graph.ast.items(.uses_module_ref)[part_range.source_index.get()]; - const exports_ref = c.graph.ast.items(.exports_ref)[part_range.source_index.get()]; - const module_ref = c.graph.ast.items(.module_ref)[part_range.source_index.get()]; - const wrapper_ref = c.graph.ast.items(.wrapper_ref)[part_range.source_index.get()]; + + // referencing everything by array makes the code a lot more annoying :( + const ast: js_ast.Ast = c.graph.ast.get(part_range.source_index.get()); js_ast.Expr.Data.Store.reset(); js_ast.Stmt.Data.Store.reset(); @@ -3758,7 +4386,17 @@ const LinkerContext = struct { namespace_export_part_index < part_range.part_index_end and parts[namespace_export_part_index].is_live) { - c.convertStmtsForChunk(part_range.source_index, stmts, parts[namespace_export_part_index].stmts); + c.convertStmtsForChunk( + part_range.source_index, + stmts, + parts[namespace_export_part_index].stmts, + chunk, + temp_allocator, + flags.wrap, + ast, + ) catch |err| return .{ + .err = err, + }; switch (flags.wrap) { .esm => { @@ -3795,7 +4433,17 @@ const LinkerContext = struct { // TODO: lazy default export // convert - c.convertStmtsForChunk(part_range.source_index, stmts, part.stmts); + c.convertStmtsForChunk( + part_range.source_index, + stmts, + part.stmts, + chunk, + temp_allocator, + flags.wrap, + ast, + ) catch |err| return .{ + .err = err, + }; } // Hoist all import statements before any normal statements. ES6 imports @@ -3807,7 +4455,7 @@ const LinkerContext = struct { stmts.all_stmts.appendSlice(stmts.inside_wrapper_prefix.items) catch unreachable; - var out_stmts = stmts.all_stmts.items; + var out_stmts: []js_ast.Stmt = stmts.all_stmts.items; // Optionally wrap all statements in a closure if (needs_wrapper) { switch (flags.wrap) { @@ -3815,16 +4463,16 @@ const LinkerContext = struct { // Only include the arguments that are actually used var args = std.ArrayList(js_ast.G.Arg).initCapacity( temp_allocator, - @boolToInt(uses_exports_ref) + @boolToInt(uses_module_ref), + @boolToInt(ast.uses_exports_ref) + @boolToInt(ast.uses_module_ref), ) catch unreachable; - if (uses_exports_ref) { + if (ast.uses_exports_ref) { args.appendAssumeCapacity( js_ast.G.Arg{ .binding = js_ast.Binding.alloc( temp_allocator, js_ast.B.Identifier{ - .ref = exports_ref, + .ref = ast.exports_ref, }, Logger.Loc.Empty, ), @@ -3832,13 +4480,13 @@ const LinkerContext = struct { ); } - if (uses_module_ref) { + if (ast.uses_module_ref) { args.appendAssumeCapacity( js_ast.G.Arg{ .binding = js_ast.Binding.alloc( temp_allocator, js_ast.B.Identifier{ - .ref = module_ref, + .ref = ast.module_ref, }, Logger.Loc.Empty, ), @@ -3887,7 +4535,7 @@ const LinkerContext = struct { .binding = Binding.alloc( temp_allocator, B.Identifier{ - .ref = wrapper_ref.?, + .ref = ast.wrapper_ref.?, }, Logger.Loc.Empty, ), @@ -4033,7 +4681,7 @@ const LinkerContext = struct { .binding = Binding.alloc( temp_allocator, B.Identifier{ - .ref = wrapper_ref.?, + .ref = ast.wrapper_ref.?, }, Logger.Loc.Empty, ), @@ -4057,6 +4705,13 @@ const LinkerContext = struct { out_stmts = stmts.outside_wrapper_prefix.items; } + const parts_to_print = &[_]js_ast.Part{ + js_ast.Part{ + .kind = .stmts, + .stmts = out_stmts, + }, + }; + const print_options = js_printer.Options{ // TODO: IIFE .indent = 0, @@ -4064,6 +4719,44 @@ const LinkerContext = struct { .allocator = allocator, .to_module_ref = toESMRef, .require_ref = runtimeRequireRef, + .require_or_import_meta_for_source_callback = js_printer.RequireOrImportMeta.Callback.init(LinkerContext, requireOrImportMetaForSource, c), + }; + + writer.buffer.reset(); + var printer = js_printer.BufferPrinter.init( + writer.*, + ); + defer writer.* = printer.ctx; + + switch (c.resolver.opts.platform.isBun()) { + inline else => |is_bun| { + return js_printer.print( + *js_printer.BufferPrinter, + &printer, + is_bun, + print_options, + ast.import_records, + parts_to_print, + r, + ); + }, + } + + unreachable; + } + + fn requireOrImportMetaForSource( + c: *LinkerContext, + source_index: Index.Int, + ) js_printer.RequireOrImportMeta { + const flags = c.graph.meta.items(.flags)[source_index]; + return .{ + .exports_ref = if (flags.wrap == .esm) + c.graph.ast.items(.exports_ref)[source_index] + else + Ref.None, + .is_wrapper_async = flags.is_async_or_has_async_dependency, + .wrapper_ref = c.graph.ast.items(.wrapper_ref)[source_index] orelse Ref.None, }; } @@ -5176,6 +5869,10 @@ pub const Chunk = struct { is_executable: bool = false, + pub inline fn isEntryPoint(this: *const Chunk) bool { + return this.entry_point.is_entry_point; + } + pub inline fn entryBits(this: *const Chunk) *const AutoBitSet { return &this.entry_bits; } From 0eb85be3164feb5b3d25744ac421f1efb7025449 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Sun, 26 Feb 2023 01:40:12 -0800 Subject: [PATCH 014/255] update the branch --- src/bit_set.zig | 16 ++++---- src/bun.js/node/node_os.zig | 78 +++++++++++++++++-------------------- src/bundler/bundle_v2.zig | 39 ++++++++++--------- src/comptime_string_map.zig | 2 +- src/darwin_c.zig | 6 +-- src/multi_array_list.zig | 28 ++++++------- src/thread_pool.zig | 4 +- 7 files changed, 82 insertions(+), 91 deletions(-) diff --git a/src/bit_set.zig b/src/bit_set.zig index de81eab58ddd..cda34152d060 100644 --- a/src/bit_set.zig +++ b/src/bit_set.zig @@ -500,14 +500,14 @@ pub fn ArrayBitSet(comptime MaskIntType: type, comptime size: usize) type { /// in the toggles bit set. pub fn toggleSet(self: *Self, toggles: *const Self) void { const other = &toggles.masks; - for (&self.masks, 0..) |*mask, i| { - mask.* ^= other[i]; + for (self.masks, other) |*mask, b| { + mask.* ^= b; } } /// Flips every bit in the bit set. pub fn toggleAll(self: *Self) void { - for (&self.masks) |*mask| { + for (self.masks) |*mask| { mask.* = ~mask.*; } @@ -521,8 +521,8 @@ pub fn ArrayBitSet(comptime MaskIntType: type, comptime size: usize) type { /// result in the first one. Bits in the result are /// set if the corresponding bits were set in either input. pub fn setUnion(self: *Self, other: *const Self) void { - for (&self.masks, 0..) |*mask, i| { - mask.* |= other.masks[i]; + for (self.masks, other[0..self.masks.len]) |*mask, alt| { + mask.* |= alt; } } @@ -530,8 +530,8 @@ pub fn ArrayBitSet(comptime MaskIntType: type, comptime size: usize) type { /// the result in the first one. Bits in the result are /// set if the corresponding bits were set in both inputs. pub fn setIntersection(self: *Self, other: *const Self) void { - for (&self.masks, 0..) |*mask, i| { - mask.* &= other.masks[i]; + for (self.masks, other[0..self.masks.len]) |*mask, alt| { + mask.* &= alt; } } @@ -770,7 +770,7 @@ pub const DynamicBitSetUnmanaged = struct { const num_masks = numMasks(self.bit_length); var copy = Self{}; try copy.resize(new_allocator, self.bit_length, false); - bun.copy(MaskInt, copy.masks, self.masks[0..num_masks]); + bun.copy(MaskInt, copy.masks[0..num_masks], self.masks[0..num_masks]); return copy; } diff --git a/src/bun.js/node/node_os.zig b/src/bun.js/node/node_os.zig index eb7ae3c2d4e6..63794e602567 100644 --- a/src/bun.js/node/node_os.zig +++ b/src/bun.js/node/node_os.zig @@ -67,8 +67,7 @@ pub const Os = struct { const fields = comptime std.meta.fieldNames(CPUTimes); const ret = JSC.JSValue.createEmptyObject(globalThis, fields.len); inline for (fields) |fieldName| { - ret.put(globalThis, JSC.ZigString.static(fieldName), - JSC.JSValue.jsNumberFromUint64(@field(self, fieldName))); + ret.put(globalThis, JSC.ZigString.static(fieldName), JSC.JSValue.jsNumberFromUint64(@field(self, fieldName))); } return ret; } @@ -78,27 +77,27 @@ pub const Os = struct { JSC.markBinding(@src()); return if (comptime Environment.isLinux) - cpusImplLinux(globalThis) catch { - const err = JSC.SystemError{ - .message = JSC.ZigString.init("Failed to get cpu information"), - .code = JSC.ZigString.init(@as(string, @tagName(JSC.Node.ErrorCode.ERR_SYSTEM_ERROR))), - }; - - globalThis.vm().throwError(globalThis, err.toErrorInstance(globalThis)); - return JSC.JSValue.jsUndefined(); - } - else if (comptime Environment.isMac) - cpusImplDarwin(globalThis) catch { - const err = JSC.SystemError{ - .message = JSC.ZigString.init("Failed to get cpu information"), - .code = JSC.ZigString.init(@as(string, @tagName(JSC.Node.ErrorCode.ERR_SYSTEM_ERROR))), - }; - - globalThis.vm().throwError(globalThis, err.toErrorInstance(globalThis)); - return JSC.JSValue.jsUndefined(); - } - else - JSC.JSValue.createEmptyArray(globalThis, 0); + cpusImplLinux(globalThis) catch { + const err = JSC.SystemError{ + .message = JSC.ZigString.init("Failed to get cpu information"), + .code = JSC.ZigString.init(@as(string, @tagName(JSC.Node.ErrorCode.ERR_SYSTEM_ERROR))), + }; + + globalThis.vm().throwError(globalThis, err.toErrorInstance(globalThis)); + return JSC.JSValue.jsUndefined(); + } + else if (comptime Environment.isMac) + cpusImplDarwin(globalThis) catch { + const err = JSC.SystemError{ + .message = JSC.ZigString.init("Failed to get cpu information"), + .code = JSC.ZigString.init(@as(string, @tagName(JSC.Node.ErrorCode.ERR_SYSTEM_ERROR))), + }; + + globalThis.vm().throwError(globalThis, err.toErrorInstance(globalThis)); + return JSC.JSValue.jsUndefined(); + } + else + JSC.JSValue.createEmptyArray(globalThis, 0); } fn cpusImplLinux(globalThis: *JSC.JSGlobalObject) !JSC.JSValue { @@ -107,7 +106,7 @@ pub const Os = struct { var num_cpus: u32 = 0; // Use a large line buffer because the /proc/stat file can have a very long list of interrupts - var line_buffer: [1024*8]u8 = undefined; + var line_buffer: [1024 * 8]u8 = undefined; // Read /proc/stat to get number of CPUs and times if (std.fs.openFileAbsolute("/proc/stat", .{})) |file| { @@ -131,10 +130,10 @@ pub const Os = struct { var times = CPUTimes{}; times.user = scale * try std.fmt.parseInt(u64, toks.next() orelse return error.eol, 10); times.nice = scale * try std.fmt.parseInt(u64, toks.next() orelse return error.eol, 10); - times.sys = scale * try std.fmt.parseInt(u64, toks.next() orelse return error.eol, 10); + times.sys = scale * try std.fmt.parseInt(u64, toks.next() orelse return error.eol, 10); times.idle = scale * try std.fmt.parseInt(u64, toks.next() orelse return error.eol, 10); _ = try (toks.next() orelse error.eol); // skip iowait - times.irq = scale * try std.fmt.parseInt(u64, toks.next() orelse return error.eol, 10); + times.irq = scale * try std.fmt.parseInt(u64, toks.next() orelse return error.eol, 10); // Actually create the JS object representing the CPU const cpu = JSC.JSValue.createEmptyObject(globalThis, 3); @@ -161,13 +160,11 @@ pub const Os = struct { const digits = std.mem.trim(u8, line[key_processor.len..], " \t\n"); cpu_index = try std.fmt.parseInt(u32, digits, 10); if (cpu_index >= num_cpus) return error.too_may_cpus; - } else if (std.mem.startsWith(u8, line, key_model_name)) { // If this is the model name, extract it and store on the current cpu const model_name = line[key_model_name.len..]; const cpu = JSC.JSObject.getIndex(values, globalThis, cpu_index); - cpu.put(globalThis, JSC.ZigString.static("model"), - JSC.ZigString.init(model_name).withEncoding().toValueGC(globalThis)); + cpu.put(globalThis, JSC.ZigString.static("model"), JSC.ZigString.init(model_name).withEncoding().toValueGC(globalThis)); } //TODO: special handling for ARM64 (no model name)? } @@ -175,8 +172,7 @@ pub const Os = struct { // Initialize model name to "unknown" var it = values.arrayIterator(globalThis); while (it.next()) |cpu| { - cpu.put(globalThis, JSC.ZigString.static("model"), - JSC.ZigString.static("unknown").withEncoding().toValue(globalThis)); + cpu.put(globalThis, JSC.ZigString.static("model"), JSC.ZigString.static("unknown").withEncoding().toValue(globalThis)); } } @@ -212,11 +208,7 @@ pub const Os = struct { var num_cpus: c.natural_t = 0; var info: [*]local_bindings.processor_cpu_load_info = undefined; var info_size: std.c.mach_msg_type_number_t = 0; - if (local_bindings.host_processor_info( - std.c.mach_host_self(), - local_bindings.PROCESSOR_CPU_LOAD_INFO, - &num_cpus, @ptrCast(*local_bindings.processor_info_array_t, &info), - &info_size) != .SUCCESS) { + if (local_bindings.host_processor_info(std.c.mach_host_self(), local_bindings.PROCESSOR_CPU_LOAD_INFO, &num_cpus, @ptrCast(*local_bindings.processor_info_array_t, &info), &info_size) != .SUCCESS) { return error.no_processor_info; } defer _ = std.c.vm_deallocate(std.c.mach_task_self(), @ptrToInt(info), info_size); @@ -226,13 +218,13 @@ pub const Os = struct { return error.broken_process_info; } - // Get CPU model name var model_name_buf: [512]u8 = undefined; var len: usize = model_name_buf.len; // Try brand_string first and if it fails try hw.model if (!(std.c.sysctlbyname("machdep.cpu.brand_string", &model_name_buf, &len, null, 0) == 0 or - std.c.sysctlbyname("hw.model", &model_name_buf, &len, null, 0) == 0)) { + std.c.sysctlbyname("hw.model", &model_name_buf, &len, null, 0) == 0)) + { return error.no_processor_info; } //NOTE: sysctlbyname doesn't update len if it was large enough, so we @@ -240,7 +232,6 @@ pub const Os = struct { // model name. const model_name = JSC.ZigString.init(std.mem.sliceTo(&model_name_buf, 0)).withEncoding().toValueGC(globalThis); - // Get CPU speed var speed: u64 = 0; len = @sizeOf(@TypeOf(speed)); @@ -252,9 +243,10 @@ pub const Os = struct { speed = 2_400_000_000; } - // Get the multiplier; this is the number of ms/tick - const unistd = @cImport({@cInclude("unistd.h");}); + const unistd = @cImport({ + @cInclude("unistd.h"); + }); const ticks: i64 = unistd.sysconf(unistd._SC_CLK_TCK); const multiplier = 1000 / @intCast(u64, ticks); @@ -265,9 +257,9 @@ pub const Os = struct { const times = CPUTimes{ .user = info[cpu_index].cpu_ticks[0] * multiplier, .nice = info[cpu_index].cpu_ticks[3] * multiplier, - .sys = info[cpu_index].cpu_ticks[1] * multiplier, + .sys = info[cpu_index].cpu_ticks[1] * multiplier, .idle = info[cpu_index].cpu_ticks[2] * multiplier, - .irq = 0, // not available + .irq = 0, // not available }; const cpu = JSC.JSValue.createEmptyObject(globalThis, 3); diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 5c510cc2c8aa..4619ee027309 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -406,7 +406,7 @@ pub const BundleV2 = struct { if (framework.override_modules.keys.len > 0) { bundler.options.framework.?.override_modules_hashes = allocator.alloc(u64, framework.override_modules.keys.len) catch unreachable; - for (framework.override_modules.keys) |key, i| { + for (framework.override_modules.keys, 0..) |key, i| { bundler.options.framework.?.override_modules_hashes[i] = std.hash.Wyhash.hash(0, key); } } @@ -430,7 +430,7 @@ pub const BundleV2 = struct { if (framework.override_modules.keys.len > 0) { bundler.options.framework.?.override_modules_hashes = allocator.alloc(u64, framework.override_modules.keys.len) catch unreachable; - for (framework.override_modules.keys) |key, i| { + for (framework.override_modules.keys, 0..) |key, i| { bundler.options.framework.?.override_modules_hashes[i] = wyhash(0, key); } } @@ -1434,7 +1434,7 @@ const LinkerGraph = struct { var dependencies = &part.dependencies; const part_ids = g.topLevelSymbolToParts(id, ref); try dependencies.ensureUnusedCapacity(g.allocator, part_ids.len); - for (part_ids) |_, part_id| { + for (part_ids, 0..) |_, part_id| { dependencies.appendAssumeCapacity(.{ .source_index = source_index_to_import_from, .part_index = @truncate(u32, part_id), @@ -1478,7 +1478,7 @@ const LinkerGraph = struct { @memset(output_was_auto_generated.ptr, 0, output_was_auto_generated.len); } - for (entry_points) |i, j| { + for (entry_points, 0..) |i, j| { const source = sources[i.get()]; if (comptime Environment.allow_assert) { std.debug.assert(source.index.get() == i.get()); @@ -1492,7 +1492,7 @@ const LinkerGraph = struct { // Setup files { var stable_source_indices = try this.allocator.alloc(Index, sources.len); - for (this.reachable_files) |_, i| { + for (this.reachable_files, 0..) |_, i| { stable_source_indices[i] = Index.source(i); } @@ -1648,7 +1648,7 @@ const LinkerContext = struct { var entry_source_indices = this.graph.entry_points.items(.source_index); // Create chunks for entry points - for (entry_source_indices) |source_index, entry_id_| { + for (entry_source_indices, 0..) |source_index, entry_id_| { const entry_bit = @truncate(Chunk.EntryPoint.ID, entry_id_); var entry_bits = try bun.bit_set.AutoBitSet.initEmpty(this.allocator, this.graph.entry_points.len); @@ -1705,7 +1705,7 @@ const LinkerContext = struct { // Map from the entry point file to this chunk. We will need this later if // a file contains a dynamic import to this entry point, since we'll need // to look up the path for this chunk to use with the import. - for (chunks) |*chunk, chunk_id| { + for (chunks, 0..) |*chunk, chunk_id| { if (chunk.entry_point.is_entry_point) { entry_point_chunk_indices[chunk.entry_point.source_index] = @truncate(u32, chunk_id); } @@ -1831,7 +1831,7 @@ const LinkerContext = struct { const records = v.import_records[source_index].slice(); - for (parts) |*part, part_index_| { + for (parts, 0..) |*part, part_index_| { const part_index = @truncate(u32, part_index_); const is_part_in_this_chunk = is_file_in_chunk and part.is_live; @@ -2280,7 +2280,7 @@ const LinkerContext = struct { if (is_entry_point and this.options.output_format == .esm) { var copies = this.allocator.alloc(Ref, aliases.len) catch unreachable; - for (aliases) |alias, i| { + for (aliases, 0..) |alias, i| { const original_name = bufPrint(buf, "export_{}", .{strings.fmtIdentifier(alias)}) catch unreachable; buf = buf[original_name.len..]; copies[i] = this.graph.generateNewSymbol(source_index, .other, original_name); @@ -2457,7 +2457,7 @@ const LinkerContext = struct { var import_records = import_records_list[id].slice(); debug("Binding {d} imports for file {s} (#{d})", .{ import_records.len, source.path.text, id }); - for (parts) |*part, part_index| { + for (parts, 0..) |*part, part_index| { var to_esm_uses: u32 = 0; var to_common_js_uses: u32 = 0; var runtime_require_uses: u32 = 0; @@ -2945,7 +2945,7 @@ const LinkerContext = struct { var parts = &c.graph.ast.items(.parts)[id]; var parts_slice: []js_ast.Part = parts.slice(); var named_imports: *js_ast.Ast.NamedImports = &c.graph.ast.items(.named_imports)[id]; - for (parts_slice) |*part, part_index| { + for (parts_slice, 0..) |*part, part_index| { // TODO: inline const TypeScript enum here @@ -2953,7 +2953,7 @@ const LinkerContext = struct { // note: if we crash on append, it is due to threadlocal heaps in mimalloc const symbol_uses = part.symbol_uses.keys(); - for (symbol_uses) |ref, j| { + for (symbol_uses, 0..) |ref, j| { if (comptime Environment.allow_assert) { std.debug.assert(part.symbol_uses.values()[j].count_estimate > 0); } @@ -3056,7 +3056,7 @@ const LinkerContext = struct { // has to happen after tree shaking because there is an implicit dependency // between live parts within the same file. All liveness has to be computed // first before determining which entry points can reach which files. - for (entry_points) |entry_point, i| { + for (entry_points, 0..) |entry_point, i| { c.markFileReachableForCodeSplitting( entry_point, i, @@ -3269,7 +3269,7 @@ const LinkerContext = struct { ); // Mark imported symbols as exported in the chunk from which they are declared - for (chunks) |*chunk, chunk_index| { + for (chunks, 0..) |*chunk, chunk_index| { if (chunk.content != .javascript) { continue; } @@ -3300,7 +3300,7 @@ const LinkerContext = struct { // this entry point, even if there are no imports. We need to make sure // these chunks are evaluated for their side effects too. if (chunk.entry_point.is_entry_point) { - for (chunks) |*other_chunk, other_chunk_index| { + for (chunks, 0..) |*other_chunk, other_chunk_index| { if (other_chunk_index == chunk_index or other_chunk.content != .javascript) continue; if (other_chunk.entry_bits.isSet(chunk.entry_point.entry_point_id)) { @@ -3653,6 +3653,7 @@ const LinkerContext = struct { } fn generateChunkJS_(ctx: GenerateChunkCtx, chunk: *Chunk, chunk_index: usize) !void { + _ = chunk_index; defer ctx.wg.done(); var worker = ThreadPool.Worker.get(); const allocator = worker.allocator; @@ -3677,7 +3678,7 @@ const LinkerContext = struct { js_ast.Stmt.Data.Store.create(allocator); var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); - for (part_ranges) |part_range, i| { + for (part_ranges, 0..) |part_range, i| { if (i > 0) arena.reset(.retain_capacity); c.generateCodeForFileInChunkJS( allocator, @@ -4882,7 +4883,7 @@ const LinkerContext = struct { if (@as(usize, id) >= c.graph.ast.len) return; var _parts = parts[id].slice(); - for (_parts) |*part, part_index| { + for (_parts, 0..) |*part, part_index| { var can_be_removed_if_unused = part.can_be_removed_if_unused; // Also include any statement-level imports @@ -5257,7 +5258,7 @@ const LinkerContext = struct { // generate a dummy part that depends on the "__commonJS" symbol var dependencies = c.allocator.alloc(js_ast.Dependency, common_js_parts.len) catch unreachable; - for (common_js_parts) |part, i| { + for (common_js_parts, 0..) |part, i| { dependencies[i] = .{ .part_index = part, .source_index = Index.runtime, @@ -5310,7 +5311,7 @@ const LinkerContext = struct { // generate a dummy part that depends on the "__esm" symbol var dependencies = c.allocator.alloc(js_ast.Dependency, esm_parts.len) catch unreachable; - for (esm_parts) |part, i| { + for (esm_parts, 0..) |part, i| { dependencies[i] = .{ .part_index = part, .source_index = Index.runtime, diff --git a/src/comptime_string_map.zig b/src/comptime_string_map.zig index 9368451ec5a5..1963fc8deece 100644 --- a/src/comptime_string_map.zig +++ b/src/comptime_string_map.zig @@ -71,7 +71,7 @@ pub fn ComptimeStringMapWithKeyType(comptime KeyType: type, comptime V: type, co const keys_list: []const []const KeyType = blk: { var k: [kvs.len][]const KeyType = undefined; - for (kvs) |kv, i| { + for (kvs, 0..) |kv, i| { k[i] = kv.key; } break :blk k[0..]; diff --git a/src/darwin_c.zig b/src/darwin_c.zig index f2e22c85e20f..b19dd3f865da 100644 --- a/src/darwin_c.zig +++ b/src/darwin_c.zig @@ -559,13 +559,11 @@ pub const CPU_STATE_MAX = 4; pub const processor_cpu_load_info = extern struct { cpu_ticks: [CPU_STATE_MAX]c_uint, }; -pub const PROCESSOR_CPU_LOAD_INFO_COUNT = @as(std.c.mach_msg_type_number_t, - @sizeOf(processor_cpu_load_info)/@sizeOf(std.c.natural_t)); +pub const PROCESSOR_CPU_LOAD_INFO_COUNT = @as(std.c.mach_msg_type_number_t, @sizeOf(processor_cpu_load_info) / @sizeOf(std.c.natural_t)); pub const processor_info_array_t = [*]c_int; pub const PROCESSOR_INFO_MAX = 1024; -pub extern fn host_processor_info(host: std.c.host_t , flavor: processor_flavor_t , out_processor_count: *std.c.natural_t , out_processor_info: *processor_info_array_t, out_processor_infoCnt: *std.c.mach_msg_type_number_t) std.c.E; - +pub extern fn host_processor_info(host: std.c.host_t, flavor: processor_flavor_t, out_processor_count: *std.c.natural_t, out_processor_info: *processor_info_array_t, out_processor_infoCnt: *std.c.mach_msg_type_number_t) std.c.E; pub extern fn getuid(...) std.os.uid_t; pub extern fn getgid(...) std.os.gid_t; diff --git a/src/multi_array_list.zig b/src/multi_array_list.zig index 4638b2d0b567..85a6789f482f 100644 --- a/src/multi_array_list.zig +++ b/src/multi_array_list.zig @@ -84,7 +84,7 @@ pub fn MultiArrayList(comptime S: type) type { alignment: usize, }; var data: [fields.len]Data = undefined; - for (fields) |field_info, i| { + for (fields, 0..) |field_info, i| { data[i] = .{ .size = @sizeOf(field_info.type), .size_index = i, @@ -100,7 +100,7 @@ pub fn MultiArrayList(comptime S: type) type { std.sort.sort(Data, &data, {}, Sort.lessThan); var sizes_bytes: [fields.len]usize = undefined; var field_indexes: [fields.len]usize = undefined; - for (data) |elem, i| { + for (data, 0..) |elem, i| { sizes_bytes[i] = elem.size; field_indexes[i] = elem.size_index; } @@ -133,7 +133,7 @@ pub fn MultiArrayList(comptime S: type) type { .capacity = self.capacity, }; var ptr: [*]u8 = self.bytes; - for (sizes.bytes) |field_size, i| { + for (sizes.bytes, 0..) |field_size, i| { result.ptrs[sizes.fields[i]] = ptr; ptr += field_size * self.capacity; } @@ -150,7 +150,7 @@ pub fn MultiArrayList(comptime S: type) type { /// Overwrite one array element with new data. pub fn set(self: *Self, index: usize, elem: S) void { const slices = self.slice(); - inline for (fields) |field_info, i| { + inline for (fields, 0..) |field_info, i| { slices.items(@intToEnum(Field, i))[index] = @field(elem, field_info.name); } } @@ -159,7 +159,7 @@ pub fn MultiArrayList(comptime S: type) type { pub fn get(self: Self, index: usize) S { const slices = self.slice(); var result: S = undefined; - inline for (fields) |field_info, i| { + inline for (fields, 0..) |field_info, i| { @field(result, field_info.name) = slices.items(@intToEnum(Field, i))[index]; } return result; @@ -232,7 +232,7 @@ pub fn MultiArrayList(comptime S: type) type { assert(index <= self.len); self.len += 1; const slices = self.slice(); - inline for (fields) |field_info, field_index| { + inline for (fields, 0..) |field_info, field_index| { const field_slice = slices.items(@intToEnum(Field, field_index)); var i: usize = self.len - 1; while (i > index) : (i -= 1) { @@ -247,7 +247,7 @@ pub fn MultiArrayList(comptime S: type) type { /// retain list ordering. pub fn swapRemove(self: *Self, index: usize) void { const slices = self.slice(); - inline for (fields) |_, i| { + inline for (fields, 0..) |_, i| { const field_slice = slices.items(@intToEnum(Field, i)); field_slice[index] = field_slice[self.len - 1]; field_slice[self.len - 1] = undefined; @@ -259,7 +259,7 @@ pub fn MultiArrayList(comptime S: type) type { /// after it to preserve order. pub fn orderedRemove(self: *Self, index: usize) void { const slices = self.slice(); - inline for (fields) |_, field_index| { + inline for (fields, 0..) |_, field_index| { const field_slice = slices.items(@intToEnum(Field, field_index)); var i = index; while (i < self.len - 1) : (i += 1) { @@ -295,7 +295,7 @@ pub fn MultiArrayList(comptime S: type) type { capacityInBytes(new_len), ) catch { const self_slice = self.slice(); - inline for (fields) |field_info, i| { + inline for (fields, 0..) |field_info, i| { if (@sizeOf(field_info.type) != 0) { const field = @intToEnum(Field, i); const dest_slice = self_slice.items(field)[new_len..]; @@ -317,7 +317,7 @@ pub fn MultiArrayList(comptime S: type) type { self.len = new_len; const self_slice = self.slice(); const other_slice = other.slice(); - inline for (fields) |field_info, i| { + inline for (fields, 0..) |field_info, i| { if (@sizeOf(field_info.type) != 0) { const field = @intToEnum(Field, i); mem.copy(field_info.type, other_slice.items(field), self_slice.items(field)); @@ -378,7 +378,7 @@ pub fn MultiArrayList(comptime S: type) type { }; const self_slice = self.slice(); const other_slice = other.slice(); - inline for (fields) |field_info, i| { + inline for (fields, 0..) |field_info, i| { if (@sizeOf(field_info.type) != 0) { const field = @intToEnum(Field, i); mem.copy(field_info.type, other_slice.items(field), self_slice.items(field)); @@ -397,7 +397,7 @@ pub fn MultiArrayList(comptime S: type) type { result.len = self.len; const self_slice = self.slice(); const result_slice = result.slice(); - inline for (fields) |field_info, i| { + inline for (fields, 0..) |field_info, i| { if (@sizeOf(field_info.type) != 0) { const field = @intToEnum(Field, i); mem.copy(field_info.type, result_slice.items(field), self_slice.items(field)); @@ -418,7 +418,7 @@ pub fn MultiArrayList(comptime S: type) type { slice: Slice, pub fn swap(sc: @This(), a_index: usize, b_index: usize) void { - inline for (fields) |field_info, i| { + inline for (fields, 0..) |field_info, i| { if (@sizeOf(field_info.type) != 0) { const field = @intToEnum(Field, i); const ptr = sc.slice.items(field); @@ -456,7 +456,7 @@ pub fn MultiArrayList(comptime S: type) type { this.len += other.len; const other_slice = other.slice(); const this_slice = this.slice(); - inline for (fields) |field_info, i| { + inline for (fields, 0..) |field_info, i| { if (@sizeOf(field_info.type) != 0) { const field = @intToEnum(Field, i); mem.copy(field_info.type, this_slice.items(field)[offset..], other_slice.items(field)); diff --git a/src/thread_pool.zig b/src/thread_pool.zig index 807bebc6ca92..70068b92a83a 100644 --- a/src/thread_pool.zig +++ b/src/thread_pool.zig @@ -295,11 +295,11 @@ pub fn Do( const Runner = struct { pub fn call(ctx_: WaitContext, values_: ValuesType, i: usize) void { if (comptime as_ptr) { - for (values_) |*v, j| { + for (values_, 0..) |*v, j| { Function(ctx_.ctx, v, i + j); } } else { - for (values_) |v, j| { + for (values_, 0..) |v, j| { Function(ctx_.ctx, v, i + j); } } From e06fc197a7fb2f135d195e32c3d480600bceed95 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Sun, 26 Feb 2023 14:52:04 -0800 Subject: [PATCH 015/255] Update bundle_v2.zig --- src/bundler/bundle_v2.zig | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 4619ee027309..83b4a760ac7a 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -6004,15 +6004,12 @@ pub const CrossChunkImport = struct { var import_items_list = imports_from_other_chunks.values(); var chunk_indices = imports_from_other_chunks.keys(); - var i: usize = 0; - while (i < chunk_indices.len) : (i += 1) { - const chunk_index = chunk_indices[i]; + for (chunk_indices, import_items_list) |chunk_index, import_items| { var chunk = &chunks[chunk_index]; // Sort imports from a single chunk by alias for determinism const exports_to_other_chunks = &chunk.content.javascript.exports_to_other_chunks; // TODO: do we need to clone this array? - var import_items = import_items_list[i]; for (import_items.slice()) |*item| { item.export_alias = exports_to_other_chunks.get(item.ref).?; std.debug.assert(item.export_alias.len > 0); From 80de5515c0a762853d93c5ab4a2a475a981d4b88 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Sun, 26 Feb 2023 17:32:41 -0800 Subject: [PATCH 016/255] wip --- src/bun.zig | 1 + src/bundler/bundle_v2.zig | 132 ++++++++++++++++++++++++++++++-------- src/js_printer.zig | 71 ++++++++++++++++++++ 3 files changed, 177 insertions(+), 27 deletions(-) diff --git a/src/bun.zig b/src/bun.zig index 4a7d21ae535a..4cf9dabcaed8 100644 --- a/src/bun.zig +++ b/src/bun.zig @@ -1028,6 +1028,7 @@ pub const MultiArrayList = @import("./multi_array_list.zig").MultiArrayList; pub const Joiner = @import("./string_joiner.zig"); pub const renamer = @import("./renamer.zig"); +pub const sourcemap = @import("./sourcemap/sourcemap.zig"); pub fn asByteSlice(buffer: anytype) []const u8 { return switch (@TypeOf(buffer)) { diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 83b4a760ac7a..536a507d7f60 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -1571,6 +1571,7 @@ const LinkerContext = struct { output_format: options.OutputFormat = .esm, ignore_dce_annotations: bool = false, tree_shaking: bool = true, + minify_whitespace: bool = false, mode: Mode = Mode.bundle, @@ -3678,22 +3679,105 @@ const LinkerContext = struct { js_ast.Stmt.Data.Store.create(allocator); var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); - for (part_ranges, 0..) |part_range, i| { - if (i > 0) arena.reset(.retain_capacity); - c.generateCodeForFileInChunkJS( + var compile_results = std.ArrayList(CompileResult).initCapacity(allocator, part_ranges.len) catch unreachable; + { + defer arena.reset(.retain_capacity); + + var buffer_writer = js_printer.BufferWriter.init(allocator) catch unreachable; + + for (part_ranges, 0..) |part_range, i| { + if (i > 0) arena.reset(.retain_capacity); + const result = c.generateCodeForFileInChunkJS( + &buffer_writer, + r, + chunk, + part_range, + toCommonJSRef, + toESMRef, + runtimeRequireRef, + &chunk.entry_bits, + &stmts, + allocator, + arena.allocator(), + ); + + if (i < part_ranges.len - 1) { + compile_results.appendAssumeCapacity( + // we reuse the memory buffer up until the final chunk to minimize reallocations + result.clone(allocator) catch unreachable, + ); + } else { + if (comptime Environment.allow_assert) { + if (result == .result) { + if (buffer_writer.buffer.list.capacity > result.result.code.len) { + // add a 0 to make it easier to view the code in a debugger + // but only if room + buffer_writer.buffer.list.items.ptr[result.result.code.len] = 0; + } + } + } + + // the final chunk owns the memory buffer + compile_results.appendAssumeCapacity(result); + } + } + } + + // Also generate the cross-chunk binding code + var cross_chunk_prefix: []u8 = &.{}; + var cross_chunk_suffix: []u8 = &.{}; + + { + const indent: usize = 0; + // TODO: IIFE indent + + const print_options = js_printer.Options{ + // TODO: IIFE + .indent = indent, + + .allocator = allocator, + .require_ref = runtimeRequireRef, + .minify_whitespace = c.options.minify_whitespace, + }; + + var cross_chunk_import_records = ImportRecord.List.initCapacity(allocator, chunk.cross_chunk_imports.len) catch unreachable; + defer cross_chunk_import_records.deinit(); + for (chunk.cross_chunk_imports.slice()) |import_record| { + cross_chunk_import_records.appendAssumeCapacity( + .{ + .kind = import_record.import_kind, + .path = Fs.Path.init(chunk.unique_key), + }, + ); + } + + cross_chunk_prefix = js_printer.print( allocator, + c.resolver.opts.platform, + print_options, + cross_chunk_import_records.slice(), + &[_]js_ast.Part{ + .{ .stmts = chunk.content.javascript.cross_chunk_prefix_stmts.slice() }, + }, r, - chunk, - part_range, - toCommonJSRef, - toESMRef, - runtimeRequireRef, - &chunk.entry_bits, - &stmts, + ).result.code; + cross_chunk_suffix = js_printer.print( allocator, - arena.allocator(), - ); + c.resolver.opts.platform, + print_options, + &.{}, + &[_]js_ast.Part{ + .{ .stmts = chunk.content.javascript.cross_chunk_suffix_stmts.slice() }, + }, + r, + ).result.code; } + + // Generate the exports for the entry point, if there are any + var j = bun.Joiner{ + .use_pool = false, + .node_allocator = allocator, + }; } const StmtList = struct { @@ -4729,21 +4813,15 @@ const LinkerContext = struct { ); defer writer.* = printer.ctx; - switch (c.resolver.opts.platform.isBun()) { - inline else => |is_bun| { - return js_printer.print( - *js_printer.BufferPrinter, - &printer, - is_bun, - print_options, - ast.import_records, - parts_to_print, - r, - ); - }, - } - - unreachable; + return js_printer.printWithWriter( + *js_printer.BufferPrinter, + &printer, + c.resolver.opts.platform, + print_options, + ast.import_records, + parts_to_print, + r, + ); } fn requireOrImportMetaForSource( diff --git a/src/js_printer.zig b/src/js_printer.zig index b75deca066f0..94963763ca70 100644 --- a/src/js_printer.zig +++ b/src/js_printer.zig @@ -561,6 +561,23 @@ pub const PrintResult = union(enum) { source_map: ?SourceMap.Chunk = null, }, err: anyerror, + + pub fn clone( + this: PrintResult, + allocator: std.mem.Allocator, + ) !PrintResult { + return switch (this) { + .result => PrintResult{ + .result = .{ + .code = try allocator.dupe(u8, this.result.code), + .source_map = this.result.source_map, + }, + }, + .err => PrintResult{ + .err = this.err, + }, + }; + } }; // do not make this a packed struct @@ -5423,6 +5440,51 @@ pub fn printJSON( } pub fn print( + allocator: std.mem.Allocator, + platform: options.Platform, + opts: Options, + import_records: []const ImportRecord, + parts: []const js_ast.Part, + renamer: bun.renamer.Renamer, +) PrintResult { + var buffer_writer = BufferWriter.init(allocator) catch |err| return .{ .err = err }; + var buffer_printer = BufferPrinter.init(&buffer_writer); + + return printWithWriter( + *BufferPrinter, + buffer_printer, + platform, + opts, + import_records, + parts, + renamer, + ); +} + +pub fn printWithWriter( + comptime Writer: type, + _writer: Writer, + platform: options.Platform, + opts: Options, + import_records: []const ImportRecord, + parts: []const js_ast.Part, + renamer: bun.renamer.Renamer, +) PrintResult { + return switch (platform.isBun()) { + inline else => |is_bun_platform| printWithWriter( + Writer, + _writer, + is_bun_platform, + opts, + import_records, + parts, + renamer, + ), + }; +} + +/// The real one +pub fn printWithWriterAndPlatform( comptime Writer: type, _writer: Writer, comptime is_bun_platform: bool, @@ -5464,6 +5526,15 @@ pub fn print( } } } + + printer.writer.done() catch |err| + return .{ .err = err }; + + return .{ + .result = .{ + .code = writer.ctx.getWritten(), + }, + }; } pub fn printCommonJS( From d19e8a830531ef1f3ada9959753bad5c83b44f0e Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Mon, 27 Feb 2023 02:54:58 -0800 Subject: [PATCH 017/255] Faster `strings.indexOfChar` and `strings.indexOfAny` --- src/c.zig | 1 + src/string_immutable.zig | 47 +++++++++++----------------------------- 2 files changed, 14 insertions(+), 34 deletions(-) diff --git a/src/c.zig b/src/c.zig index bc961773a442..4698f7a65776 100644 --- a/src/c.zig +++ b/src/c.zig @@ -39,6 +39,7 @@ pub extern "c" fn lutimes(path: [*:0]const u8, times: *const [2]std.os.timeval) pub extern "c" fn mkdtemp(template: [*c]u8) ?[*:0]u8; pub extern "c" fn memcmp(s1: [*c]const u8, s2: [*c]const u8, n: usize) c_int; +pub extern "c" fn memchr(s: [*]const u8, c: u8, n: usize) ?[*]const u8; pub const lstat = lstat64; pub const fstat = fstat64; diff --git a/src/string_immutable.zig b/src/string_immutable.zig index 45f3ef94b314..0b9ab5d65ae3 100644 --- a/src/string_immutable.zig +++ b/src/string_immutable.zig @@ -20,7 +20,7 @@ pub inline fn containsChar(self: string, char: u8) bool { } pub inline fn contains(self: string, str: string) bool { - return std.mem.indexOf(u8, self, str) != null; + return indexOf(self, str) != null; } pub fn toUTF16Literal(comptime str: []const u8) []const u16 { @@ -41,15 +41,11 @@ pub fn toUTF16Literal(comptime str: []const u8) []const u16 { const OptionalUsize = std.meta.Int(.unsigned, @bitSizeOf(usize) - 1); pub fn indexOfAny(self: string, comptime str: anytype) ?OptionalUsize { - for (self, 0..) |c, i| { - inline for (str) |a| { - if (c == a) { - return @intCast(OptionalUsize, i); - } + inline for (str) |a| { + if (indexOf(self, a)) |i| { + return @intCast(OptionalUsize, i); } } - - return null; } pub fn indexOfAny16(self: []const u16, comptime str: anytype) ?OptionalUsize { for (self, 0..) |c, i| { @@ -250,7 +246,9 @@ pub inline fn indexOf(self: string, str: string) ?usize { const start = bun.C.memmem(self_ptr, self_len, str_ptr, str_len) orelse return null; - return @ptrToInt(start) - @ptrToInt(self_ptr); + const i = @ptrToInt(start) - @ptrToInt(self_ptr); + std.debug.assert(i < self_len); + return @intCast(usize, i); } pub fn split(self: string, delimiter: string) SplitIterator { @@ -3234,34 +3232,15 @@ test "indexOfNeedsEscape" { } pub fn indexOfChar(slice: []const u8, char: u8) ?u32 { - var remaining = slice; - if (remaining.len == 0) + if (slice.len == 0) return null; - if (remaining[0] == char) - return 0; - - if (comptime Environment.enableSIMD) { - while (remaining.len >= ascii_vector_size) { - const vec: AsciiVector = remaining[0..ascii_vector_size].*; - const cmp = vec == @splat(ascii_vector_size, char); - - if (@reduce(.Max, @bitCast(AsciiVectorU1, cmp)) > 0) { - const bitmask = @bitCast(AsciiVectorInt, cmp); - const first = @ctz(bitmask); - return @intCast(u32, @as(u32, first) + @intCast(u32, slice.len - remaining.len)); - } - remaining = remaining[ascii_vector_size..]; - } - } - - for (remaining, 0..) |c, i| { - if (c == char) { - return @truncate(u32, i + (slice.len - remaining.len)); - } - } + const ptr = bun.C.memchr(slice.ptr, char, slice.len) orelse return null; + const i = @ptrToInt(ptr) - @ptrToInt(slice.ptr); + std.debug.assert(i < slice.len); + std.debug.assert(slice[i] == char); - return null; + return @truncate(u32, i); } test "indexOfChar" { From 4b27c3036a65f7203ae1a912039ea3ea17564a4f Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Mon, 27 Feb 2023 02:56:23 -0800 Subject: [PATCH 018/255] prepare for hashing --- src/bundler/bundle_v2.zig | 541 +++++++++++++++++++++++++++++++++++- src/js_ast.zig | 20 +- src/js_parser.zig | 16 +- src/sourcemap/sourcemap.zig | 52 +++- src/string_joiner.zig | 34 ++- 5 files changed, 631 insertions(+), 32 deletions(-) diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 536a507d7f60..2f65c2b5cfb3 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -3774,9 +3774,502 @@ const LinkerContext = struct { } // Generate the exports for the entry point, if there are any - var j = bun.Joiner{ - .use_pool = false, - .node_allocator = allocator, + const entry_point_tail = brk: { + if (chunk.isEntryPoint()) { + break :brk c.generateEntryPointTailJS( + c, + toCommonJSRef, + toESMRef, + chunk.entry_point.source_index, + allocator, + arena.allocator(), + r, + ); + } + + break :brk CompileResult.empty; + }; + + var j = bun.Joiner{ .use_pool = false, .node_allocator = allocator, .watcher = .{ + .input = chunk.unique_key, + } }; + var line_offset: bun.sourcemap.LineColumnOffset.Optional = .{ .null = {} }; + + // Concatenate the generated JavaScript chunks together + + var newline_before_comment = false; + var is_executable = false; + + // Start with the hashbang if there is one. This must be done before the + // banner because it only works if it's literally the first character. + if (chunk.isEntryPoint()) { + if (c.graph.ast.items(.hashbang)[chunk.entry_point.source_index]) |hashbang| { + std.debug.assert(hashbang.len > 0); + j.push(hashbang); + j.push("\n"); + line_offset.advance(hashbang); + newline_before_comment = true; + is_executable = true; + } + } + + // TODO: banner + + // TODO: directive + + // TODO: IIFE wrap + + if (cross_chunk_prefix.len > 0) { + newline_before_comment = true; + line_offset.advance(cross_chunk_prefix); + j.push(cross_chunk_prefix); + } + + // Concatenate the generated JavaScript chunks together + + var prev_filename_comment: Index.Int = 0; + + const sources: []const Logger.Source = c.graph.files.items(.source); + for (@as([]CompileResult, compile_results.items)) |compile_result| { + const source_index = compile_result.sourceIndex(); + const is_runtime = source_index == Index.runtime.value; + + // TODO: extracated legal comments + + // Add a comment with the file path before the file contents + if (c.options.mode == .bundle and !c.options.minify_whitespace and source_index != prev_filename_comment and compile_result.code().len > 0) { + prev_filename_comment = source_index; + if (newline_before_comment) { + j.push("\n"); + line_offset.advance("\n"); + } + + // Make sure newlines in the path can't cause a syntax error. This does + // not minimize allocations because it's expected that this case never + // comes up in practice. + const CommentType = enum { + multiline, + single, + }; + + const pretty = sources[source_index].path.pretty; + + // TODO: quote this. This is really janky. + const comment_type = if (strings.indexOfNewlineOrNonASCII(pretty) != null) + CommentType.multiline + else + CommentType.single; + + switch (comment_type) { + .multiline => { + j.push("/* "); + line_offset.advance("/* "); + }, + .single => { + j.push("// "); + line_offset.advance("// "); + }, + } + + j.push(pretty); + line_offset.advance(pretty); + + switch (comment_type) { + .multiline => { + j.push(" */\n"); + line_offset.advance(" */"); + }, + .single => { + j.push("\n"); + line_offset.advance("\n"); + }, + } + prev_filename_comment = source_index; + } + + if (is_runtime) { + line_offset.advance(compile_result.code()); + j.append(compile_result.code(), 0, allocator); + } else { + line_offset.advance(compile_result.code()); + j.append(compile_result.code(), 0, allocator); + + // TODO: sourcemap + } + + // TODO: metafile + newline_before_comment = compile_result.code().len > 0; + } + + if (entry_point_tail.code().len > 0) { + // Stick the entry point tail at the end of the file. Deliberately don't + // include any source mapping information for this because it's automatically + // generated and doesn't correspond to a location in the input file. + j.push(entry_point_tail.code()); + } + + // Put the cross-chunk suffix inside the IIFE + if (cross_chunk_suffix.len > 0) { + if (newline_before_comment) { + j.push("\n"); + line_offset.advance("\n"); + } + + j.push(cross_chunk_suffix); + } + + if (c.options.output_format == .iife) { + const without_newline = "})();"; + + const with_newline = if (newline_before_comment) + without_newline ++ "\n" + else + without_newline; + + j.push(with_newline); + } + + j.ensureNewlineAtEnd(); + // TODO: maybeAppendLegalComments + + // TODO: footer + + } + + pub fn generateEntryPointTailJS( + c: *LinkerContext, + toCommonJSRef: Ref, + toESMRef: Ref, + source_index: Index.Int, + allocator: std.mem.Allocator, + temp_allocator: std.mem.Allocator, + r: renamer.Renamer, + ) CompileResult { + const flags: JSMeta.Flags = c.graph.meta.items(.flags)[source_index]; + var stmts = std.ArrayList(Stmt).init(temp_allocator); + defer stmts.deinit(); + const ast: js_ast.Ast = c.graph.ast.get(source_index); + + switch (c.options.output_format) { + // TODO: + .preserve => {}, + + .esm => { + switch (flags.wrap) { + .cjs => { + stmts.append( + Stmt.alloc( + // "export default require_foo();" + S.ExportDefault, + .{ + .value = Stmt.alloc( + S.SExpr, + .{ + .value = Expr.init( + E.Call, + E.Call{ + .target = Expr.initIdentifier( + ast.wrapper_ref.?, + Logger.Loc.Empty, + ), + }, + Logger.Loc.Empty, + ), + }, + ), + }, + Logger.Loc.Empty, + ), + ) catch unreachable; + }, + else => { + if (flags.wrap == .esm) { + if (flags.is_async_or_has_async_dependency) { + // "await init_foo();" + stmts.append( + Stmt.alloc( + S.SExpr, + .{ + .value = Expr.init( + E.Await, + E.Await{ + .value = Expr.init( + E.Call, + E.Call{ + .target = Expr.initIdentifier( + ast.wrapper_ref.?, + Logger.Loc.Empty, + ), + }, + Logger.Loc.Empty, + ), + }, + Logger.Loc.Empty, + ), + }, + Logger.Loc.Empty, + ), + ) catch unreachable; + } else { + // "init_foo();" + stmts.append( + Stmt.alloc( + S.SExpr, + .{ + .value = Expr.init( + E.Call, + E.Call{ + .target = Expr.initIdentifier( + ast.wrapper_ref.?, + Logger.Loc.Empty, + ), + }, + Logger.Loc.Empty, + ), + }, + Logger.Loc.Empty, + ), + ) catch unreachable; + } + } + + const sorted_and_filtered_export_aliases = c.graph.meta.items(.sorted_and_filtered_export_aliases)[source_index]; + + if (sorted_and_filtered_export_aliases.len > 0) { + const resolved_exports: ResolvedExports = c.graph.meta.items(.resolved_exports)[source_index]; + const imports_to_bind: RefImportData = c.graph.meta.items(.imports_to_bind)[source_index]; + + // If the output format is ES6 modules and we're an entry point, generate an + // ES6 export statement containing all exports. Except don't do that if this + // entry point is a CommonJS-style module, since that would generate an ES6 + // export statement that's not top-level. Instead, we will export the CommonJS + // exports as a default export later on. + var items = std.ArrayList(js_ast.ClauseItem).init(temp_allocator); + const cjs_export_copies = c.graph.meta.items(.cjs_export_copies)[source_index]; + + for (sorted_and_filtered_export_aliases, cjs_export_copies) |alias, temp_ref| { + var resolved_export = resolved_exports.get(alias).?; + + // If this is an export of an import, reference the symbol that the import + // was eventually resolved to. We need to do this because imports have + // already been resolved by this point, so we can't generate a new import + // and have that be resolved later. + if (imports_to_bind.get(resolved_export.data.import_ref)) |import_data| { + resolved_export.data.import_ref = import_data.import_ref; + resolved_export.data.source_index = import_data.data.source_index; + } + + // Exports of imports need EImportIdentifier in case they need to be re- + // written to a property access later on + + if (c.graph.symbols.get(resolved_export.data.import_ref).?.namespace_alias != null) { + // Create both a local variable and an export clause for that variable. + // The local variable is initialized with the initial value of the + // export. This isn't fully correct because it's a "dead" binding and + // doesn't update with the "live" value as it changes. But ES6 modules + // don't have any syntax for bare named getter functions so this is the + // best we can do. + // + // These input files: + // + // // entry_point.js + // export {foo} from './cjs-format.js' + // + // // cjs-format.js + // Object.defineProperty(exports, 'foo', { + // enumerable: true, + // get: () => Math.random(), + // }) + // + // Become this output file: + // + // // cjs-format.js + // var require_cjs_format = __commonJS((exports) => { + // Object.defineProperty(exports, "foo", { + // enumerable: true, + // get: () => Math.random() + // }); + // }); + // + // // entry_point.js + // var cjs_format = __toESM(require_cjs_format()); + // var export_foo = cjs_format.foo; + // export { + // export_foo as foo + // }; + // + stmts.append( + Stmt.alloc( + S.Local, + .{ + .decls = bun.fromSlice( + []js_ast.G.Decl, + temp_allocator, + &.{ + .{ + .binding = Binding.alloc( + temp_allocator, + B.Identifier{ + .ref = temp_ref, + }, + ), + }, + }, + ) catch unreachable, + }, + Logger.Loc.Empty, + ), + ) catch unreachable; + + items.append( + .{ + .name = js_ast.LocRef{ + .ref = temp_ref, + }, + .alias = alias, + }, + ) catch unreachable; + } else { + // Local identifiers can be exported using an export clause. This is done + // this way instead of leaving the "export" keyword on the local declaration + // itself both because it lets the local identifier be minified and because + // it works transparently for re-exports across files. + // + // These input files: + // + // // entry_point.js + // export * from './esm-format.js' + // + // // esm-format.js + // export let foo = 123 + // + // Become this output file: + // + // // esm-format.js + // let foo = 123; + // + // // entry_point.js + // export { + // foo + // }; + // + items.append(.{ + .name = js_ast.LocRef{ + .ref = resolved_export.data.import_ref, + }, + .alias = alias, + }) catch unreachable; + } + } + + stmts.append( + Stmt.alloc( + S.ExportClause, + .{ + .items = items.items, + }, + Logger.Loc.Empty, + ), + ) catch unreachable; + } + }, + } + }, + + // TODO: iife + .iife => {}, + + .cjs => { + switch (flags.wrap) { + .cjs => { + // "module.exports = require_foo();" + stmts.append( + Stmt.assign( + Expr.init(E.Dot, .{ + .target = .{ + .target = Expr.initIdentifier(c.unbound_module_ref, Logger.Loc.Empty), + .name = "exports", + }, + }, Logger.Loc.Empty), + Expr.init( + E.Call, + .{ + .target = Expr.initIdentifier(ast.wrapper_ref.?, Logger.Loc.Empty), + }, + Logger.Loc.Empty, + ), + temp_allocator, + ), + ) catch unreachable; + }, + .esm => { + // "init_foo();" + stmts.append( + Stmt.alloc( + S.Expr, + .{ + .value = Expr.init( + E.Call, + .{ + .target = Expr.initIdentifier(ast.wrapper_ref.?, Logger.Loc.Empty), + }, + Logger.Loc.Empty, + ), + }, + Logger.Loc.Empty, + ), + ) catch unreachable; + }, + else => {}, + } + + // TODO: + // If we are generating CommonJS for node, encode the known export names in + // a form that node can understand them. This relies on the specific behavior + // of this parser, which the node project uses to detect named exports in + // CommonJS files: https://github.com/guybedford/cjs-module-lexer. Think of + // this code as an annotation for that parser. + }, + } + + if (stmts.items.len == 0) { + return .{ + .javascript = .{ + .source_index = source_index, + .code = "", + }, + }; + } + + _ = toCommonJSRef; + const print_options = js_printer.Options{ + // TODO: IIFE + .indent = 0, + + .allocator = allocator, + .to_module_ref = toESMRef, + // .require_ref = toCommonJSRef, + .require_or_import_meta_for_source_callback = js_printer.RequireOrImportMeta.Callback.init(LinkerContext, requireOrImportMetaForSource, c), + + .minify_whitespace = c.options.minify_whitespace, + }; + + return .{ + .javascript = .{ + .result = js_printer.print( + allocator, + c.resolver.opts.platform, + print_options, + ast.import_records.slice(), + &[_]js_ast.Part{ + .{ + .stmts = stmts.items, + }, + }, + r, + ), + .source_index = source_index, + }, }; } @@ -4586,7 +5079,7 @@ const LinkerContext = struct { E.Arrow{ .args = args.items, .body = .{ - .block = Stmt.init( + .block = Stmt.alloc( S.Block, .{ .stmts = stmts.all_stmts.items, @@ -4628,7 +5121,7 @@ const LinkerContext = struct { }; stmts.outside_wrapper_prefix.append( - Stmt.init( + Stmt.alloc( S.Local, S.Local{ .decls = decls, @@ -4703,7 +5196,7 @@ const LinkerContext = struct { if (value.isEmpty()) { continue; } - stmt = Stmt.init( + stmt = Stmt.alloc( S.Expr, S.Expr{ .expr = value, @@ -4732,7 +5225,7 @@ const LinkerContext = struct { .args = &.{}, .is_async = is_async, .body = .{ - .block = Stmt.init( + .block = Stmt.alloc( S.Block, .{ .stmts = stmts.all_stmts.items, @@ -4774,7 +5267,7 @@ const LinkerContext = struct { }; stmts.outside_wrapper_prefix.append( - Stmt.init( + Stmt.alloc( S.Local, S.Local{ .decls = decls, @@ -5984,7 +6477,7 @@ pub const Chunk = struct { /// If the chunk doesn't have any references to other chunks, then /// "joiner" contains the contents of the chunk. This is more efficient /// because it avoids doing a join operation twice. - joiner: *bun.Joiner, + joiner: bun.Joiner, }; pub const OutputPiece = struct { @@ -6107,6 +6600,34 @@ pub const CrossChunkImport = struct { const CompileResult = union(enum) { javascript: struct { source_index: Index.Int, - reslt: js_printer.PrintResult, + result: js_printer.PrintResult, }, + + pub const empty = CompileResult{ + .javascript = .{ + .source_index = 0, + .result = js_printer.PrintResult{ + .result = .{ + .code = "", + }, + }, + }, + }; + + pub fn code(this: *const CompileResult) []const u8 { + return switch (this.*) { + .javascript => |r| switch (r.result) { + .code => |c| c, + else => "", + }, + else => "", + }; + } + + pub fn sourceIndex(this: *const CompileResult) Index.Int { + return switch (this.*) { + .javascript => |r| r.source_index, + else => 0, + }; + } }; diff --git a/src/js_ast.zig b/src/js_ast.zig index 603fa3a25b92..33ae4615bca0 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -2034,6 +2034,16 @@ pub const Stmt = struct { pub const Batcher = bun.Batcher(Stmt); + pub fn assign(a: Expr, b: Expr, allocator: std.mem.Allocator) Stmt { + return Stmt.alloc( + S.SExpr, + S.SExpr{ + .value = Expr.assign(a, b, allocator), + }, + a.loc, + ); + } + const Serializable = struct { type: Tag, object: string, @@ -3611,16 +3621,6 @@ pub const Expr = struct { return null; } - pub fn assignStmt(a: Expr, b: Expr, allocator: std.mem.Allocator) Stmt { - return Stmt.alloc( - S.SExpr, - S.SExpr{ - .value = Expr.assign(a, b, allocator), - }, - a.loc, - ); - } - pub fn isOptionalChain(self: *const @This()) bool { return switch (self.data) { .e_dot => self.data.e_dot.optional_chain != null, diff --git a/src/js_parser.zig b/src/js_parser.zig index 5eae3961437d..f95a10a9028c 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -15957,7 +15957,7 @@ fn NewParser_( // "module.exports = value" stmts.append( - Expr.assignStmt( + Stmt.assign( p.@"module.exports"( stmt.loc, ), @@ -16358,7 +16358,7 @@ fn NewParser_( const enclosing_namespace_arg_ref = p.enclosing_namespace_arg_ref orelse unreachable; stmts.ensureUnusedCapacity(3) catch unreachable; stmts.appendAssumeCapacity(stmt.*); - stmts.appendAssumeCapacity(Expr.assignStmt(p.newExpr(E.Dot{ + stmts.appendAssumeCapacity(Stmt.assign(p.newExpr(E.Dot{ .target = p.newExpr(E.Identifier{ .ref = enclosing_namespace_arg_ref }, stmt.loc), .name = p.loadNameFromRef(data.func.name.?.ref.?), .name_loc = data.func.name.?.loc, @@ -16419,7 +16419,7 @@ fn NewParser_( // Handle exporting this class from a namespace if (was_export_inside_namespace) { - stmts.appendAssumeCapacity(Expr.assignStmt(p.newExpr(E.Dot{ + stmts.appendAssumeCapacity(Stmt.assign(p.newExpr(E.Dot{ .target = p.newExpr(E.Identifier{ .ref = p.enclosing_namespace_arg_ref.? }, stmt.loc), .name = p.symbols.items[data.class.class_name.?.ref.?.innerIndex()].original_name, .name_loc = data.class.class_name.?.loc, @@ -17157,9 +17157,9 @@ fn NewParser_( // remove fields with decorators from class body. Move static members outside of class. if (prop.flags.contains(.is_static)) { - static_members.append(Expr.assignStmt(target, initializer, p.allocator)) catch unreachable; + static_members.append(Stmt.assign(target, initializer, p.allocator)) catch unreachable; } else { - instance_members.append(Expr.assignStmt(target, initializer, p.allocator)) catch unreachable; + instance_members.append(Stmt.assign(target, initializer, p.allocator)) catch unreachable; } continue; } @@ -17229,7 +17229,7 @@ fn NewParser_( args[0] = p.newExpr(E.Array{ .items = class.ts_decorators }, stmt.loc); args[1] = p.newExpr(E.Identifier{ .ref = class.class_name.?.ref.? }, class.class_name.?.loc); - stmts.appendAssumeCapacity(Expr.assignStmt( + stmts.appendAssumeCapacity(Stmt.assign( p.newExpr(E.Identifier{ .ref = class.class_name.?.ref.? }, class.class_name.?.loc), p.callRuntime(stmt.loc, "__decorateClass", args), p.allocator, @@ -17727,7 +17727,7 @@ fn NewParser_( const name = p.symbols.items[id.ref.innerIndex()].original_name; const ident = p.newExpr(E.Identifier{ .ref = id.ref }, arg.binding.loc); - stmts.insert(if (super_index) |k| j + k + 1 else j, Expr.assignStmt( + stmts.insert(if (super_index) |k| j + k + 1 else j, Stmt.assign( p.newExpr(E.Dot{ .target = p.newExpr(E.This{}, arg.binding.loc), .name = name, @@ -18707,7 +18707,7 @@ fn NewParser_( .value = decl_value, }; - update_function_stmts[named_export_i] = Expr.assignStmt( + update_function_stmts[named_export_i] = Stmt.assign( p.newExpr( E.Identifier{ .ref = name_ref }, logger.Loc.Empty, diff --git a/src/sourcemap/sourcemap.zig b/src/sourcemap/sourcemap.zig index 3fa063e747d2..971341cdc744 100644 --- a/src/sourcemap/sourcemap.zig +++ b/src/sourcemap/sourcemap.zig @@ -315,10 +315,60 @@ pub const Mapping = struct { }; }; -pub const LineColumnOffset = struct { +pub const LineColumnOffset = packed struct { lines: i32 = 0, columns: i32 = 0, + pub const Optional = union(enum) { + null: void, + value: LineColumnOffset, + + pub fn advance(this: *Optional, input: []const u8) void { + switch (this.*) { + .none => {}, + .value => this.offset.advance(input), + } + } + }; + + pub fn advance(this: *LineColumnOffset, input: []const u8) void { + var columns = this.columns; + defer this.columns = columns; + var offset: u32 = 0; + while (strings.indexOfNewlineOrNonASCII(input, offset)) |i| { + std.debug.assert(i >= offset); + std.debug.assert(i < input.len); + + columns += @intCast(i32, i - offset); + offset = i; + + var cp = strings.CodepointIterator.initOffset(input, offset); + _ = cp.next(); + switch (cp.c) { + '\r', '\n', 0x2028, 0x2029 => { + // Handle Windows-specific "\r\n" newlines + if (cp.c == '\r' and input.len > offset + 1 and input[offset + 1] == '\n') { + columns += 1; + offset += 1; + continue; + } + + this.lines += 1; + columns = 0; + }, + else => |c| { + // Mozilla's "source-map" library counts columns using UTF-16 code units + columns += switch (c) { + 0...0xFFFF => 1, + else => 2, + }; + }, + } + } + + columns += @intCast(i32, input.len - offset); + } + pub fn cmp(_: void, a: LineColumnOffset, b: LineColumnOffset) std.math.Order { if (a.lines != b.lines) { return std.math.order(a.lines, b.lines); diff --git a/src/string_joiner.zig b/src/string_joiner.zig index 490989c9ac67..373d24633d60 100644 --- a/src/string_joiner.zig +++ b/src/string_joiner.zig @@ -1,7 +1,8 @@ /// Rope-like data structure for joining many small strings into one big string. const std = @import("std"); -const default_allocator = @import("bun").default_allocator; -const string = @import("string_types.zig").string; +const default_allocator = bun.default_allocator; +const bun = @import("bun"); +const string = bun.string; const Allocator = std.mem.Allocator; const ObjectPool = @import("./pool.zig").ObjectPool; const Joiner = @This(); @@ -15,7 +16,6 @@ const Joinable = struct { pub const Pool = ObjectPool(Joinable, null, true, 4); }; -last_byte: u8 = 0, len: usize = 0, use_pool: bool = true, node_allocator: Allocator = undefined, @@ -23,6 +23,15 @@ node_allocator: Allocator = undefined, head: ?*Joinable.Pool.Node = null, tail: ?*Joinable.Pool.Node = null, +/// Avoid an extra pass over the list when joining +watcher: Watcher = .{}, + +pub const Watcher = struct { + input: []const u8 = "", + estimated_count: u32 = 0, + needs_newline: bool = false, +}; + pub fn done(this: *Joiner, allocator: Allocator) ![]u8 { if (this.head == null) { var out: []u8 = &[_]u8{}; @@ -60,6 +69,17 @@ pub fn lastByte(this: *const Joiner) u8 { return 0; } +pub fn push(this: *Joiner, slice: string) void { + this.append(slice, 0, null); +} + +pub fn ensureNewlineAtEnd(this: *Joiner) void { + if (this.watcher.needs_newline) { + this.watcher.needs_newline = false; + this.push("\n"); + } +} + pub fn append(this: *Joiner, slice: string, offset: u32, allocator: ?Allocator) void { const data = slice[offset..]; this.len += @truncate(u32, data.len); @@ -69,6 +89,14 @@ pub fn append(this: *Joiner, slice: string, offset: u32, allocator: ?Allocator) else (this.node_allocator.create(Joinable.Pool.Node) catch unreachable); + this.watcher.estimated_count += @boolToInt( + this.watcher.input.len > 0 and + bun.strings.contains(data, this.watcher.input), + ); + + this.watcher.needs_newline = this.watcher.input.len > 0 and data.len > 0 and + data[data.len - 1] != '\n'; + new_tail.* = .{ .allocator = default_allocator, .data = Joinable{ From d2a75baadb58c62b1dda6840adb51bb28dfcf891 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Tue, 28 Feb 2023 15:56:07 -0800 Subject: [PATCH 019/255] fixup --- src/sha.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sha.zig b/src/sha.zig index 45e37cb4e1a7..214ca0445588 100644 --- a/src/sha.zig +++ b/src/sha.zig @@ -242,7 +242,7 @@ pub fn main() anyerror!void { comptime labels[i] ++ ":\n\n", .{}, ); - const DigestType = if (BoringHasher != void) BoringHasher.Digest else [512]u8; + const DigestType = if (BoringHasher != void) BoringHasher.Digest else [32]u8; var digest1: DigestType = undefined; var digest2: DigestType = undefined; var digest3: DigestType = undefined; From 977777292e7301a2c40778c39ad6debb4d863820 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Tue, 28 Feb 2023 20:09:25 -0800 Subject: [PATCH 020/255] Update bundle_v2.zig --- src/bundler/bundle_v2.zig | 223 +++++++++++++++++++++++++++++++++++++- 1 file changed, 218 insertions(+), 5 deletions(-) diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index d2b61bfb21a6..2e86383c2ca7 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -1575,6 +1575,8 @@ const LinkerContext = struct { mode: Mode = Mode.bundle, + public_path: []const u8 = "", + pub const Mode = enum { passthrough, bundle, @@ -3938,7 +3940,83 @@ const LinkerContext = struct { // TODO: footer - chunk.content.javascript.intermediate_output; + chunk.intermediate_output = c.breakOutputIntoPieces( + allocator, + &j, + ctx.chunks.len, + ) catch @panic("Unhandled out of memory error in breakOutputIntoPieces()"); + + // TODO: meta contents + + chunk.isolated_hash = c.generateIsolatedHash(chunk); + chunk.is_executable = is_executable; + } + + pub fn generateIsolatedHash(c: *LinkerContext, chunk: *const Chunk) u64 { + var hasher = ContentHasher{}; + + // Mix the file names and part ranges of all of the files in this chunk into + // the hash. Objects that appear identical but that live in separate files or + // that live in separate parts in the same file must not be merged. This only + // needs to be done for JavaScript files, not CSS files. + if (chunk.content == .javascript) { + const sources = c.graph.files.items(.source); + for (chunk.content.javascript.parts_in_chunk_in_order) |part_range| { + const source: Logger.Source = sources[part_range.source_index.get()]; + + const file_path = brk: { + if (strings.eqlComptime(source.path.namespace, "file")) { + // Use the pretty path as the file name since it should be platform- + // independent (relative paths and the "/" path separator) + break :brk source.path.pretty; + } else { + // If this isn't in the "file" namespace, just use the full path text + // verbatim. This could be a source of cross-platform differences if + // plugins are storing platform-specific information in here, but then + // that problem isn't caused by esbuild itself. + break :brk source.path.text; + } + }; + + // Include the path namespace in the hash so that files with the same + hasher.write(source.key_path.namespace); + + // Then include the file path + hasher.write(file_path); + + // Then include the part range + hasher.writeInts(&[_]u32{ + part_range.part_index_begin, + part_range.part_index_end, + }); + } + } + + // Hash the output path template as part of the content hash because we want + // any import to be considered different if the import's output path has changed. + hasher.write(chunk.template.data); + + // Also hash the public path. If provided, this is used whenever files + // reference each other such as cross-chunk imports, asset file references, + // and source map comments. We always include the hash in all chunks instead + // of trying to figure out which chunks will include the public path for + // simplicity and for robustness to code changes in the future. + if (c.options.public_path.len > 0) { + hasher.write(c.options.public_path); + } + + // Include the generated output content in the hash. This excludes the + // randomly-generated import paths (the unique keys) and only includes the + // data in the spans between them. + if (chunk.intermediate_output == .pieces) { + for (chunk.intermediate_output.pieces.slice()) |piece| { + hasher.write(piece.data); + } + } else { + hasher.write(chunk.intermediate_output.single_piece.data); + } + + return hasher.digest(); } pub fn generateEntryPointTailJS( @@ -4430,9 +4508,12 @@ const LinkerContext = struct { // This currently evaluates sibling dependencies in serial instead of in // parallel, which is incorrect. This should be changed to store a promise // and await all stored promises after all imports but before any code. - break :brk Expr.init(E.Await, E.Await{ - .value = default, - }); + break :brk Expr.init( + E.Await, + E.Await{ + .value = default, + }, + ); } break :brk default; @@ -5302,7 +5383,11 @@ const LinkerContext = struct { .allocator = allocator, .to_module_ref = toESMRef, .require_ref = runtimeRequireRef, - .require_or_import_meta_for_source_callback = js_printer.RequireOrImportMeta.Callback.init(LinkerContext, requireOrImportMetaForSource, c), + .require_or_import_meta_for_source_callback = js_printer.RequireOrImportMeta.Callback.init( + LinkerContext, + requireOrImportMetaForSource, + c, + ), }; writer.buffer.reset(); @@ -6267,6 +6352,107 @@ const LinkerContext = struct { } }; + pub fn breakOutputIntoPieces( + c: *LinkerContext, + allocator: std.mem.Allocator, + j: *bun.Joiner, + count: u32, + ) !Chunk.IntermediateOutput { + // Optimization: If there can be no substitutions, just reuse the initial + // joiner that was used when generating the intermediate chunk output + // instead of creating another one and copying the whole file into it. + if (j.watcher.estimated_count == 0) { + return Chunk.IntermediateOutput{ + .joiner = j.*, + }; + } + + var pieces = try std.ArrayList(Chunk.OutputPiece).initCapacity(allocator, count); + const complete_output = try j.done(allocator); + var output = complete_output; + + const prefix = c.unique_key_buf; + + while (true) { + const invalid_boundary = std.math.maxInt(usize); + // Scan for the next piece boundary + const boundary = strings.indexOf(output, prefix) orelse invalid_boundary; + + var output_piece_index = Chunk.OutputPieceIndex{}; + + // Try to parse the piece boundary + if (boundary != invalid_boundary) { + const start = boundary + prefix.len; + if (start + 9 > output.len) { + // Not enough bytes to parse the piece index + boundary = invalid_boundary; + } else { + switch (output[start]) { + 'A' => { + output_piece_index.kind = .asset; + }, + 'C' => { + output_piece_index.kind = .chunk; + }, + else => { + for (output[start..][1..9].*) |char| { + if (char <= '0' or char > '9') { + boundary = invalid_boundary; + break; + } + + output_piece_index.index = @truncate( + u30, + @as(u32, output_piece_index.index) * 10 + (@as(u32, output[start]) - '0'), + ); + } + }, + } + } + } + + // Validate the boundary + switch (output_piece_index.kind) { + .asset => { + if (@as(u32, output_piece_index.index) >= @truncate(u32, c.graph.files.len)) { + boundary = invalid_boundary; + } + }, + .chunk => { + if (@as(usize, output_piece_index.index) >= count) { + boundary = invalid_boundary; + } + }, + else => { + boundary = invalid_boundary; + }, + } + + // If we're at the end, generate one final piece + if (boundary == invalid_boundary) { + try pieces.append(Chunk.OutputPiece{ + .index = output_piece_index, + .data = output, + }); + break; + } + + // Otherwise, generate an interior piece and continue + try pieces.append(Chunk.OutputPiece{ + .index = output_piece_index, + .data_ptr = output.ptr, + + // sliced this way to panic if out of bounds + .data_len = @truncate(u32, output[0..boundary].len), + }); + output = output[boundary + prefix.len + 9 ..]; + } + + return Chunk.IntermediateOutput{ + .pieces = bun.BabyList(Chunk.OutputPiece).init(pieces.items), + }; + } + const DependencyWrapper = struct { linker: *LinkerContext, flags: []JSMeta.Flags, @@ -6446,6 +6632,9 @@ pub const Chunk = struct { is_executable: bool = false, + intermediate_output: IntermediateOutput = .{ .empty = {} }, + isolated_hash: u64 = std.math.maxInt(u64), + pub inline fn isEntryPoint(this: *const Chunk) bool { return this.entry_point.is_entry_point; } @@ -6473,6 +6662,10 @@ pub const Chunk = struct { } }; + /// TODO: rewrite this + /// This implementation is just slow. + /// Can we make the JSPrinter itself track this without increasing + /// complexity a lot? pub const IntermediateOutput = union(enum) { /// If the chunk has references to other chunks, then "pieces" contains the /// contents of the chunk. Another joiner @@ -6483,6 +6676,8 @@ pub const Chunk = struct { /// "joiner" contains the contents of the chunk. This is more efficient /// because it avoids doing a join operation twice. joiner: bun.Joiner, + + empty: void, }; pub const OutputPiece = struct { @@ -6636,3 +6831,21 @@ const CompileResult = union(enum) { }; } }; + +const ContentHasher = struct { + hasher: std.hash.XxHash64 = std.hash.XxHash64.init(0), + + pub fn write(self: *ContentHasher, bytes: []const u8) void { + self.hasher.update(std.mem.asBytes(&bytes.len)); + self.hasher.update(bytes); + } + + pub fn writeInts(self: *ContentHasher, i: []const u32) void { + // TODO: BigEndian + self.hasher.update(std.mem.sliceAsBytes(i)); + } + + pub fn digest(self: *ContentHasher) u64 { + return self.hasher.final(); + } +}; From 2237da2bcd08ccea4aa73225562ebf010fdf765d Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Tue, 28 Feb 2023 20:32:45 -0800 Subject: [PATCH 021/255] Update bundle_v2.zig --- src/bundler/bundle_v2.zig | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 2e86383c2ca7..7da0c22f34e6 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -3473,7 +3473,7 @@ const LinkerContext = struct { chunks: []Chunk, }; fn generateChunkJS(ctx: GenerateChunkCtx, chunk: *Chunk, chunk_index: usize) void { - generateChunkJS_(ctx, chunk, chunk_index) catch |err| std.debug.panic("TODO: handle error: {s}", .{@errorName(err)}); + generateChunkJS_(ctx, chunk, chunk_index) catch |err| Output.panic("TODO: handle error: {s}", .{@errorName(err)}); } // TODO: investigate if we need to parallelize this function @@ -5434,6 +5434,12 @@ const LinkerContext = struct { var ctx = GenerateChunkCtx{ .wg = wait_group, .c = c, .chunks = chunks_ }; try c.parse_graph.pool.pool.doPtr(c.allocator, wait_group, ctx, generateChunkJS, chunks_); } + + // TODO: enforceNoCyclicChunkImports() + + // Compute the final hashes of each chunk. This can technically be done in + // parallel but it probably doesn't matter so much because we're not hashing + // that much data. } // Sort cross-chunk exports by chunk name for determinism From 12ea12abe49f33bf5727f8d1ee6bbf4937b85718 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Tue, 28 Feb 2023 22:29:53 -0800 Subject: [PATCH 022/255] Update bindings.zig --- src/bun.js/bindings/bindings.zig | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig index 5529db27d204..75ab64bfbf47 100644 --- a/src/bun.js/bindings/bindings.zig +++ b/src/bun.js/bindings/bindings.zig @@ -4740,7 +4740,9 @@ pub fn initialize() void { \\Environment variables must be prefixed with "BUN_JSC_". This code runs before .env files are loaded, so those won't work here. \\ \\Warning: options change between releases of Bun and WebKit without notice. This is not a stable API, you should not rely on it beyond debugging something, and it may be removed entirely in a future version of Bun. - , .{name[0..len]}); + , + .{name[0..len]}, + ); bun.Global.exit(1); } }.callback, From 417c30366a60b3762e2699373c718c6cc17dccb9 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Wed, 1 Mar 2023 00:27:48 -0800 Subject: [PATCH 023/255] alright now just gotta try running it --- src/bundler/bundle_v2.zig | 145 ++++++++++++++++++++++++++++++++++---- src/cli/bun_command.zig | 122 +++++++++++++++++++------------- 2 files changed, 206 insertions(+), 61 deletions(-) diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 7da0c22f34e6..df026bb89aa9 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -347,7 +347,7 @@ pub const BundleV2 = struct { package_bundle_map: options.BundlePackage.Map, event_loop: EventLoop, unique_key: u64, - ) !void { + ) !std.ArrayList(options.OutputFile) { _ = try bundler.fs.fs.openTmpDir(); var tmpname_buf: [64]u8 = undefined; bundler.resetStore(); @@ -533,14 +533,16 @@ pub const BundleV2 = struct { this.linker.graph.allocator = this.bundler.allocator; this.linker.graph.ast = try this.graph.ast.clone(this.linker.allocator); - try this.linker.link( + var chunks = try this.linker.link( this, this.graph.entry_points.items, try this.findReachableFiles(), unique_key, ); - // return null; + const output_files = try this.linker.generateChunksInParallel(chunks); + + return output_files; } pub fn onParseTaskComplete(parse_result: *ParseTask.Result, this: *BundleV2) void { @@ -1616,7 +1618,7 @@ const LinkerContext = struct { entry_points: []Index, reachable: []Index, unique_key: u64, - ) !void { + ) ![]Chunk { try this.load(bundle, entry_points, reachable); try this.scanImportsAndExports(); @@ -1633,6 +1635,8 @@ const LinkerContext = struct { try this.computeCrossChunkDependencies(chunks); this.graph.symbols.followAll(); + + return chunks; } pub noinline fn computeChunks( @@ -5422,24 +5426,48 @@ const LinkerContext = struct { }; } - pub fn generateChunksInParallel(c: *LinkerContext, chunks_: []Chunk) ![]options.OutputFile { + pub fn generateChunksInParallel(c: *LinkerContext, chunks: []Chunk) !std.ArrayList(options.OutputFile) { { + debug("START Generating {d} chunks in parallel", .{chunks.len}); + defer debug(" DONE Generating {d} chunks in parallel", .{chunks.len}); var wait_group = try c.allocator.create(sync.WaitGroup); wait_group.* = sync.WaitGroup.init(); defer { wait_group.deinit(); c.allocator.destroy(wait_group); } - wait_group.addN(chunks_.len); - var ctx = GenerateChunkCtx{ .wg = wait_group, .c = c, .chunks = chunks_ }; - try c.parse_graph.pool.pool.doPtr(c.allocator, wait_group, ctx, generateChunkJS, chunks_); + wait_group.addN(chunks.len); + var ctx = GenerateChunkCtx{ .wg = wait_group, .c = c, .chunks = chunks }; + try c.parse_graph.pool.pool.doPtr(c.allocator, wait_group, ctx, generateChunkJS, chunks); } // TODO: enforceNoCyclicChunkImports() + { + + // Compute the final hashes of each chunk. This can technically be done in + // parallel but it probably doesn't matter so much because we're not hashing + // that much data. + for (chunks) |*chunk| { + // TODO: non-isolated-hash + chunk.template.placeholder.hash = chunk.isolated_hash; + + chunk.final_rel_path = std.fmt.allocPrint("{any}", .{chunk.template}) catch unreachable; + } + } + + // Generate the final output files by joining file pieces together + var output_files = std.ArrayList(options.OutputFile).initCapacity(c.allocator, chunks.len) catch unreachable; + output_files.expandToCapacity(); + for (chunks, output_files.items) |*chunk, *output_file| { + output_file.* = options.OutputFile.initBuf( + chunk.intermediate_output.code(c.allocator) catch @panic("Failed to allocate memory for output file"), + chunk.final_rel_path, + // TODO: remove this field + .js, + ); + } - // Compute the final hashes of each chunk. This can technically be done in - // parallel but it probably doesn't matter so much because we're not hashing - // that much data. + return output_files; } // Sort cross-chunk exports by chunk name for determinism @@ -6594,11 +6622,72 @@ pub const PathTemplate = struct { data: string = "", placeholder: Placeholder = .{}, + pub fn format(self: PathTemplate, comptime _: []const u8, opts: std.fmt.FormatOptions, writer: anytype) !void { + var remain = self.data; + while (strings.indexOfChar(remain, '[')) |j| { + try writer.writeAll(remain[0..j]); + remain = remain[j + 1 ..]; + if (remain.len == 0) { + // TODO: throw error + try writer.writeAll("["); + break; + } + + var count: isize = 1; + var end_len: usize = remain.len; + for (remain) |*c| { + count += switch (c.*) { + '[' => 1, + ']' => -1, + else => 0, + }; + + if (count == 0) { + end_len = @ptrToInt(c) - @ptrToInt(remain.ptr); + std.debug.assert(end_len <= remain.len); + break; + } + } + + const placeholder = remain[0..end_len]; + + const field = self.placeholder.map.get(placeholder) orelse { + try writer.writeAll(placeholder); + remain = remain[end_len..]; + continue; + }; + + switch (field) { + .dir => try writer.writeAll(opts.context.dir), + .name => try writer.writeAll(opts.context.name), + .ext => try writer.writeAll(opts.context.ext), + .hash => { + if (opts.context.hash) |hash| { + try writer.print("{any}", .{bun.fmt.hexIntLower(hash)}); + } + }, + } + remain = remain[end_len + 1 ..]; + } + + try writer.writeAll(remain); + } + pub const Placeholder = struct { dir: []const u8 = "", name: []const u8 = "", ext: []const u8 = "", hash: ?u64 = null, + + pub const map = bun.ComptimeStringMap( + std.meta.FieldEnum(Placeholder), + .{ + .{ "dir", .dir }, + .{ "name", .name }, + .{ "ext", .ext }, + .{ "hash", .hash }, + }, + ); }; pub const chunk = PathTemplate{ @@ -6679,11 +6768,43 @@ pub const Chunk = struct { pieces: bun.BabyList(OutputPiece), /// If the chunk doesn't have any references to other chunks, then - /// "joiner" contains the contents of the chunk. This is more efficient + /// `joiner` contains the contents of the chunk. This is more efficient /// because it avoids doing a join operation twice. joiner: bun.Joiner, empty: void, + + pub fn code(this: IntermediateOutput, allocator: std.mem.Allocator) ![]const u8 { + switch (this) { + .pieces => |*pieces| { + var count: usize = 0; + + for (pieces.slice()) |piece| { + count += piece.data_len; + } + + var total_buf = try allocator.alloc(u8, count); + var remain = total_buf; + + for (pieces.slice()) |piece| { + const data = piece.data(); + @memcpy(remain.ptr, data.ptr, data.len); + remain = remain[data.len..]; + } + + std.debug.assert(remain.len == 0); + std.debug.assert(total_buf.len == count); + + return total_buf; + }, + .joiner => |joiner_| { + // TODO: make this safe + var joiny = joiner_; + return joiny.done(allocator); + }, + .empty => return "", + } + } }; pub const OutputPiece = struct { diff --git a/src/cli/bun_command.zig b/src/cli/bun_command.zig index f2673491fa6d..ca3792018cbf 100644 --- a/src/cli/bun_command.zig +++ b/src/cli/bun_command.zig @@ -178,7 +178,7 @@ pub const BunCommand = struct { // Always generate the client-only bundle // we can revisit this decision if people ask - try BundleV2.generate( + const output_files = try BundleV2.generate( &this_bundler, allocator, loaded_framework, @@ -190,55 +190,79 @@ pub const BunCommand = struct { std.crypto.random.int(u64), ); - // const estimated_input_lines_of_code = estimated_input_lines_of_code_; - - // if (node_modules_) |node_modules| { - // if (log.errors > 0) { - // try log.printForLogLevel(Output.errorWriter()); - // } else { - // var elapsed = @divTrunc(std.time.nanoTimestamp() - ctx.start_time, @as(i128, std.time.ns_per_ms)); - // const print_summary = !(ctx.args.no_summary orelse false); - // if (print_summary) { - // var bundle = NodeModuleBundle.init(node_modules, allocator); - // bundle.printSummary(); - // } - // const indent = comptime " "; - - // switch (estimated_input_lines_of_code) { - // 0...99999 => { - // if (generated_server) { - // Output.prettyln(indent ++ "{d:<5} LOC parsed x2", .{estimated_input_lines_of_code}); - // } else { - // Output.prettyln(indent ++ "{d:<5} LOC parsed", .{estimated_input_lines_of_code}); - // } - // }, - // else => { - // const formatted_loc: f32 = @floatCast(f32, @intToFloat(f128, estimated_input_lines_of_code) / 1000); - // if (generated_server) { - // Output.prettyln(indent ++ "{d:<5.2}k LOC parsed x2", .{formatted_loc}); - // } else { - // Output.prettyln(indent ++ "{d:<5.2}k LOC parsed", .{ - // formatted_loc, - // }); - // } - // }, - // } - - // Output.prettyln(indent ++ "{d:6}ms elapsed", .{@intCast(u32, elapsed)}); - - // if (generated_server) { - // Output.prettyln(indent ++ "Saved to ./{s}, ./{s}", .{ filepath, server_bundle_filepath }); - // } else { - // Output.prettyln(indent ++ "Saved to ./{s}", .{filepath}); - // } - - // Output.flush(); - - // try log.printForLogLevel(Output.errorWriter()); - // } - // } else { + { + defer Output.flush(); + var writer = Output.errorWriter(); + + const root_path = ctx.args.output_dir orelse "out"; + const root_dir = try std.fs.cwd().openIterableDir(root_path, .{}); + var all_paths = try ctx.allocator.alloc([]const u8, output_files.items.len); + var max_path_len: usize = 0; + for (output_files.items, all_paths) |*dest, src| { + dest.* = src.input.text; + } + + var from_path = resolve_path.longestCommonPath(all_paths); + + for (output_files.items) |f| { + max_path_len = std.math.max( + std.math.max(from_path.len, f.input.text.len) + 2 - from_path.len, + max_path_len, + ); + } + + // On posix, file handles automatically close on process exit by the OS + // Closing files shows up in profiling. + // So don't do that unless we actually need to. + // const do_we_need_to_close = !FeatureFlags.store_file_descriptors or (@intCast(usize, root_dir.fd) + open_file_limit) < output_files.items.len; + + var filepath_buf: [bun.MAX_PATH_BYTES]u8 = undefined; + filepath_buf[0] = '.'; + filepath_buf[1] = '/'; + + for (output_files.items) |f| { + var rel_path: []const u8 = undefined; + switch (f.value) { + // easy mode: write the buffer + .buffer => |value| { + rel_path = resolve_path.relative(from_path, f.input.text); + if (std.fs.path.dirname(rel_path)) |parent| { + if (parent.len > root_path.len) { + root_dir.makePath(parent); + } + } + try root_dir.writeFile(rel_path, value); + }, + .move => |value| { + // const primary = f.input.text[from_path.len..]; + // bun.copy(u8, filepath_buf[2..], primary); + // rel_path = filepath_buf[0 .. primary.len + 2]; + rel_path = value.pathname; + + // try f.moveTo(result.outbase, constStrToU8(rel_path), root_dir.fd); + }, + .copy => |value| { + rel_path = value.pathname; + + try f.copyTo(root_path, bun.constStrToU8(rel_path), root_dir.fd); + }, + .noop => {}, + .pending => unreachable, + } + + // Print summary + _ = try writer.write("\n"); + const padding_count = 2 + (std.math.max(rel_path.len, max_path_len) - rel_path.len); + try writer.writeByteNTimes(' ', 2); + try writer.writeAll(rel_path); + try writer.writeByteNTimes(' ', padding_count); + const size = @intToFloat(f64, f.size) / 1000.0; + try std.fmt.formatFloatDecimal(size, .{ .precision = 2 }, writer); + try writer.writeAll(" KB\n"); + } + } + try log.printForLogLevel(Output.errorWriter()); - // } } } }; From 49553f43195fb03914c91f8b281967b07ab2ed90 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Wed, 1 Mar 2023 03:37:15 -0800 Subject: [PATCH 024/255] fix a gajillion compiler errors --- src/ast/base.zig | 1 - src/bit_set.zig | 28 ++++---- src/bundler/bundle_v2.zig | 134 +++++++++++++++++++------------------- src/cli/bun_command.zig | 8 +-- src/js_ast.zig | 8 +-- src/js_parser.zig | 23 ++++--- src/js_printer.zig | 27 ++++---- src/renamer.zig | 10 +-- src/thread_pool.zig | 4 +- 9 files changed, 124 insertions(+), 119 deletions(-) diff --git a/src/ast/base.zig b/src/ast/base.zig index 509f38c5b290..d272ccb9cee7 100644 --- a/src/ast/base.zig +++ b/src/ast/base.zig @@ -231,7 +231,6 @@ pub const Ref = packed struct(u64) { // 2 bits of padding for whatever is the parent pub const None = Ref{ .inner_index = 0, .source_index = 0, .tag = .invalid }; - pub const RuntimeRef = Ref{ .inner_index = 0, .source_index = 0, .tag = .runtime }; pub inline fn sourceIndex(this: Ref) Int { return this.source_index; diff --git a/src/bit_set.zig b/src/bit_set.zig index cda34152d060..4032b082d59b 100644 --- a/src/bit_set.zig +++ b/src/bit_set.zig @@ -879,8 +879,8 @@ pub const DynamicBitSetUnmanaged = struct { pub fn toggleSet(self: *Self, toggles: Self) void { if (comptime Environment.allow_assert) std.debug.assert(toggles.bit_length == self.bit_length); const num_masks = numMasks(self.bit_length); - for (&self.masks[0..num_masks], 0..) |*mask, i| { - mask.* ^= toggles.masks[i]; + for (self.masks[0..num_masks], toggles.masks[0..num_masks]) |*mask, other_mask| { + mask.* ^= other_mask; } } @@ -898,7 +898,7 @@ pub const DynamicBitSetUnmanaged = struct { if (bit_length == 0) return; const num_masks = numMasks(self.bit_length); - for (&self.masks[0..num_masks]) |*mask| { + for (self.masks[0..num_masks]) |*mask| { mask.* = ~mask.*; } @@ -913,8 +913,8 @@ pub const DynamicBitSetUnmanaged = struct { if (bit_length == 0) return; const num_masks = numMasks(self.bit_length); - for (&self.masks[0..num_masks], 0..) |*mask, i| { - mask.* = other.masks[i]; + for (self.masks[0..num_masks], other.masks) |*mask, other_mask| { + mask.* = other_mask; } const padding_bits = num_masks * @bitSizeOf(MaskInt) - bit_length; @@ -929,8 +929,8 @@ pub const DynamicBitSetUnmanaged = struct { pub fn setUnion(self: *Self, other: Self) void { if (comptime Environment.allow_assert) std.debug.assert(other.bit_length == self.bit_length); const num_masks = numMasks(self.bit_length); - for (&self.masks[0..num_masks], 0..) |*mask, i| { - mask.* |= other.masks[i]; + for (self.masks[0..num_masks], other.masks) |*mask, other_mask| { + mask.* |= other_mask; } } @@ -941,25 +941,25 @@ pub const DynamicBitSetUnmanaged = struct { pub fn setIntersection(self: *Self, other: Self) void { if (comptime Environment.allow_assert) std.debug.assert(other.bit_length == self.bit_length); const num_masks = numMasks(self.bit_length); - for (&self.masks[0..num_masks], 0..) |*mask, i| { - mask.* &= other.masks[i]; + for (self.masks[0..num_masks], other.masks) |*mask, other_mask| { + mask.* &= other_mask; } } pub fn setExcludeTwo(self: *Self, other: Self, third: Self) void { if (comptime Environment.allow_assert) std.debug.assert(other.bit_length == self.bit_length); const num_masks = numMasks(self.bit_length); - for (&self.masks[0..num_masks], 0..) |*mask, i| { - mask.* &= ~other.masks[i]; - mask.* &= ~third.masks[i]; + for (self.masks[0..num_masks], other.masks[0..num_masks], third.masks[0..num_masks]) |*mask, other_mask, third_mask| { + mask.* &= ~other_mask; + mask.* &= ~third_mask; } } pub fn setExclude(self: *Self, other: Self) void { if (comptime Environment.allow_assert) std.debug.assert(other.bit_length == self.bit_length); const num_masks = numMasks(self.bit_length); - for (&self.masks[0..num_masks], 0..) |*mask, i| { - mask.* &= ~other.masks[i]; + for (self.masks[0..num_masks], other.masks) |*mask, other_mask| { + mask.* &= ~other_mask; } } diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index df026bb89aa9..38ce601bde0d 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -33,7 +33,7 @@ const Fs = @import("../fs.zig"); const schema = @import("../api/schema.zig"); const Api = schema.Api; const _resolver = @import("../resolver/resolver.zig"); -const sync = @import("../sync.zig"); +const sync = bun.ThreadPool; const ImportRecord = bun.ImportRecord; const ImportKind = bun.ImportKind; const allocators = @import("../allocators.zig"); @@ -342,36 +342,24 @@ pub const BundleV2 = struct { allocator: std.mem.Allocator, framework_config: ?Api.LoadedFramework, route_config: ?Api.LoadedRouteConfig, - destination: [*:0]const u8, + _: [*:0]const u8, estimated_input_lines_of_code: *usize, package_bundle_map: options.BundlePackage.Map, event_loop: EventLoop, unique_key: u64, ) !std.ArrayList(options.OutputFile) { _ = try bundler.fs.fs.openTmpDir(); - var tmpname_buf: [64]u8 = undefined; bundler.resetStore(); try bundler.configureDefines(); _ = route_config; _ = estimated_input_lines_of_code; _ = package_bundle_map; - const tmpname = try bundler.fs.tmpname( - ".bun", - std.mem.span(&tmpname_buf), - wyhash(@intCast(usize, std.time.milliTimestamp()) % std.math.maxInt(u32), std.mem.span(destination)), - ); - - var tmpfile = Fs.FileSystem.RealFS.Tmpfile{}; - try tmpfile.create(&bundler.fs.fs, tmpname); - - errdefer tmpfile.closeAndDelete(tmpname); - var generator = try allocator.create(BundleV2); defer allocator.destroy(generator); generator.* = BundleV2{ - .tmpfile = tmpfile.file(), + .tmpfile = undefined, .bundler = bundler, .graph = .{ .pool = undefined, @@ -1608,7 +1596,7 @@ const LinkerContext = struct { const sources: []const Logger.Source = this.parse_graph.input_files.items(.source); try this.graph.load(entry_points, sources); - try this.wait_group.init(); + this.wait_group.init(); this.ambiguous_result_pool = std.ArrayList(MatchImport).init(this.allocator); } @@ -1625,7 +1613,7 @@ const LinkerContext = struct { // Stop now if there were errors if (this.log.hasErrors()) { - return; + return &[_]Chunk{}; } try this.treeShakingAndCodeSplitting(); @@ -3486,7 +3474,7 @@ const LinkerContext = struct { c: *LinkerContext, allocator: std.mem.Allocator, chunk: *Chunk, - files_in_order: []u32, + files_in_order: []const u32, ) !renamer.Renamer { // TODO: minify identifiers @@ -3504,7 +3492,7 @@ const LinkerContext = struct { var reserved_names = try renamer.computeInitialReservedNames(allocator); for (files_in_order) |source_index| { - renamer.computeReservedNamesForScope(all_module_scopes[source_index], &c.graph.symbols, &reserved_names, allocator); + renamer.computeReservedNamesForScope(&all_module_scopes[source_index].?, &c.graph.symbols, &reserved_names, allocator); } break :brk reserved_names; @@ -3621,7 +3609,7 @@ const LinkerContext = struct { } } } - r.assignNamesRecursiveWithNumberScope(child_number_scope, all_module_scopes[source_index], source_index, sorted); + r.assignNamesRecursiveWithNumberScope(child_number_scope, &all_module_scopes[source_index].?, source_index, sorted); continue; }, @@ -3661,7 +3649,7 @@ const LinkerContext = struct { fn generateChunkJS_(ctx: GenerateChunkCtx, chunk: *Chunk, chunk_index: usize) !void { _ = chunk_index; - defer ctx.wg.done(); + defer ctx.wg.finish(); var worker = ThreadPool.Worker.get(); const allocator = worker.allocator; const c = ctx.c; @@ -3669,7 +3657,7 @@ const LinkerContext = struct { var repr = &chunk.content.javascript; - var runtime_scope: *Scope = c.graph.ast.items(.module_scope)[c.graph.files.items(.input_file)[Ref.RuntimeRef.sourceIndex()]]; + var runtime_scope: *Scope = &c.graph.ast.items(.module_scope)[c.graph.files.items(.input_file)[Index.runtime.value].get()].?; var runtime_members = &runtime_scope.members; const toCommonJSRef = c.graph.symbols.follow(runtime_members.get("toCommonJS").?.ref); const toESMRef = c.graph.symbols.follow(runtime_members.get("toESM").?.ref); @@ -3687,12 +3675,12 @@ const LinkerContext = struct { defer arena.deinit(); var compile_results = std.ArrayList(CompileResult).initCapacity(allocator, part_ranges.len) catch unreachable; { - defer arena.reset(.retain_capacity); + defer _ = arena.reset(.retain_capacity); var buffer_writer = js_printer.BufferWriter.init(allocator) catch unreachable; for (part_ranges, 0..) |part_range, i| { - if (i > 0) arena.reset(.retain_capacity); + if (i > 0) _ = arena.reset(.retain_capacity); const result = c.generateCodeForFileInChunkJS( &buffer_writer, r, @@ -3701,7 +3689,6 @@ const LinkerContext = struct { toCommonJSRef, toESMRef, runtimeRequireRef, - &chunk.entry_bits, &stmts, allocator, arena.allocator(), @@ -3710,7 +3697,12 @@ const LinkerContext = struct { if (i < part_ranges.len - 1) { compile_results.appendAssumeCapacity( // we reuse the memory buffer up until the final chunk to minimize reallocations - result.clone(allocator) catch unreachable, + .{ + .javascript = .{ + .result = result.clone(allocator) catch unreachable, + .source_index = part_range.source_index.get(), + }, + }, ); } else { if (comptime Environment.allow_assert) { @@ -3724,7 +3716,12 @@ const LinkerContext = struct { } // the final chunk owns the memory buffer - compile_results.appendAssumeCapacity(result); + compile_results.appendAssumeCapacity(.{ + .javascript = .{ + .result = result, + .source_index = part_range.source_index.get(), + }, + }); } } } @@ -3747,12 +3744,13 @@ const LinkerContext = struct { }; var cross_chunk_import_records = ImportRecord.List.initCapacity(allocator, chunk.cross_chunk_imports.len) catch unreachable; - defer cross_chunk_import_records.deinit(); + defer cross_chunk_import_records.deinitWithAllocator(allocator); for (chunk.cross_chunk_imports.slice()) |import_record| { cross_chunk_import_records.appendAssumeCapacity( .{ .kind = import_record.import_kind, .path = Fs.Path.init(chunk.unique_key), + .range = Logger.Range.None, }, ); } @@ -3783,7 +3781,6 @@ const LinkerContext = struct { const entry_point_tail = brk: { if (chunk.isEntryPoint()) { break :brk c.generateEntryPointTailJS( - c, toCommonJSRef, toESMRef, chunk.entry_point.source_index, @@ -4049,21 +4046,24 @@ const LinkerContext = struct { // "export default require_foo();" S.ExportDefault, .{ - .value = Stmt.alloc( - S.SExpr, - .{ - .value = Expr.init( - E.Call, - E.Call{ - .target = Expr.initIdentifier( - ast.wrapper_ref.?, - Logger.Loc.Empty, - ), - }, - Logger.Loc.Empty, - ), - }, - ), + .value = .{ + .stmt = Stmt.alloc( + S.SExpr, + .{ + .value = Expr.init( + E.Call, + E.Call{ + .target = Expr.initIdentifier( + ast.wrapper_ref.?, + Logger.Loc.Empty, + ), + }, + Logger.Loc.Empty, + ), + }, + Logger.Loc.Empty, + ), + }, }, Logger.Loc.Empty, ), @@ -4417,6 +4417,7 @@ const LinkerContext = struct { .decls = try bun.fromSlice( []G.Decl, allocator, + []const G.Decl, &.{ .{ .binding = Binding.alloc( @@ -4463,6 +4464,7 @@ const LinkerContext = struct { .decls = try bun.fromSlice( []G.Decl, allocator, + []const G.Decl, &.{ .{ .binding = Binding.alloc( @@ -4517,6 +4519,7 @@ const LinkerContext = struct { E.Await{ .value = default, }, + loc, ); } @@ -4534,6 +4537,8 @@ const LinkerContext = struct { ); }, } + + return true; } fn convertStmtsForChunk( @@ -4549,7 +4554,7 @@ const LinkerContext = struct { const shouldExtractESMStmtsForWrap = wrap != .none; const shouldStripExports = c.options.mode != .passthrough or !chunk.isEntryPoint(); - const flags = c.graph.meta.items(.flag); + const flags = c.graph.meta.items(.flags); // If this file is a CommonJS entry point, double-write re-exports to the // external CommonJS "module.exports" object in addition to our internal ESM @@ -4571,6 +4576,7 @@ const LinkerContext = struct { Logger.Loc.Empty, ), .name = "exports", + .name_loc = Logger.Loc.Empty, }, Logger.Loc.Empty, ); @@ -4582,8 +4588,7 @@ const LinkerContext = struct { .s_import => |s| { // "import * as ns from 'path'" // "import {foo} from 'path'" - if (c.shouldRemoveImportExportStmt( - source_index, + if (try c.shouldRemoveImportExportStmt( stmts, stmt.loc, s.namespace_ref, @@ -4603,13 +4608,11 @@ const LinkerContext = struct { .s_export_star => |s| { // "export * as ns from 'path'" if (s.alias) |alias| { - if (c.shouldRemoveImportExportStmt( - source_index, + if (try c.shouldRemoveImportExportStmt( stmts, stmt.loc, s.namespace_ref, s.import_record_index, - chunk, allocator, ast, )) { @@ -4661,7 +4664,7 @@ const LinkerContext = struct { // Prefix this module with "__reExport(exports, ns, module.exports)" const export_star_ref = c.runtimeFunction("__reExport"); - var args = try allocator.alloc(Expr, 2 + @boolToInt(module_exports_for_export != null)); + var args = try allocator.alloc(Expr, 2 + @as(usize, @boolToInt(module_exports_for_export != null))); args[0..2].* = .{ Expr.init( E.Identifier, @@ -4687,7 +4690,7 @@ const LinkerContext = struct { Stmt.alloc( S.SExpr, S.SExpr{ - .expr = Expr.init( + .value = Expr.init( E.Call, E.Call{ .target = Expr.init( @@ -4808,7 +4811,6 @@ const LinkerContext = struct { // "export {foo} from 'path'" if (try c.shouldRemoveImportExportStmt( - source_index, stmts, stmt.loc, s.namespace_ref, @@ -5013,7 +5015,7 @@ const LinkerContext = struct { } fn runtimeFunction(c: *LinkerContext, name: []const u8) Ref { - return c.graph.ast.items(.module_scope)[Ref.RuntimeRef.source_index].members.get(name).?.ref; + return c.graph.ast.items(.module_scope)[Index.runtime.value].?.members.get(name).?.ref; } fn generateCodeForFileInChunkJS( @@ -5030,7 +5032,7 @@ const LinkerContext = struct { temp_allocator: std.mem.Allocator, ) js_printer.PrintResult { // var file = &c.graph.files.items(.input_file)[part.source_index.get()]; - var parts: []js_ast.Part = c.graph.ast.items(.parts)[part_range.source_index.get()][part_range.part_index_begin..part_range.part_index_end]; + var parts: []js_ast.Part = c.graph.ast.items(.parts)[part_range.source_index.get()].slice()[part_range.part_index_begin..part_range.part_index_end]; const resolved_exports: []ResolvedExports = c.graph.meta.items(.resolved_exports); _ = resolved_exports; const all_flags: []const JSMeta.Flags = c.graph.meta.items(.flags); @@ -5055,13 +5057,13 @@ const LinkerContext = struct { parts[namespace_export_part_index].is_live) { c.convertStmtsForChunk( - part_range.source_index, + part_range.source_index.get(), stmts, parts[namespace_export_part_index].stmts, chunk, temp_allocator, flags.wrap, - ast, + &ast, ) catch |err| return .{ .err = err, }; @@ -5431,12 +5433,12 @@ const LinkerContext = struct { debug("START Generating {d} chunks in parallel", .{chunks.len}); defer debug(" DONE Generating {d} chunks in parallel", .{chunks.len}); var wait_group = try c.allocator.create(sync.WaitGroup); - wait_group.* = sync.WaitGroup.init(); + wait_group.init(); defer { wait_group.deinit(); c.allocator.destroy(wait_group); } - wait_group.addN(chunks.len); + wait_group.counter = @truncate(u32, chunks.len); var ctx = GenerateChunkCtx{ .wg = wait_group, .c = c, .chunks = chunks }; try c.parse_graph.pool.pool.doPtr(c.allocator, wait_group, ctx, generateChunkJS, chunks); } @@ -5451,7 +5453,7 @@ const LinkerContext = struct { // TODO: non-isolated-hash chunk.template.placeholder.hash = chunk.isolated_hash; - chunk.final_rel_path = std.fmt.allocPrint("{any}", .{chunk.template}) catch unreachable; + chunk.final_rel_path = std.fmt.allocPrint(c.allocator, "{any}", .{chunk.template}) catch unreachable; } } @@ -6622,7 +6624,7 @@ pub const PathTemplate = struct { data: string = "", placeholder: Placeholder = .{}, - pub fn format(self: PathTemplate, comptime _: []const u8, opts: std.fmt.FormatOptions, writer: anytype) !void { + pub fn format(self: PathTemplate, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { var remain = self.data; while (strings.indexOfChar(remain, '[')) |j| { try writer.writeAll(remain[0..j]); @@ -6651,18 +6653,18 @@ pub const PathTemplate = struct { const placeholder = remain[0..end_len]; - const field = self.placeholder.map.get(placeholder) orelse { + const field = PathTemplate.Placeholder.map.get(placeholder) orelse { try writer.writeAll(placeholder); remain = remain[end_len..]; continue; }; switch (field) { - .dir => try writer.writeAll(opts.context.dir), - .name => try writer.writeAll(opts.context.name), - .ext => try writer.writeAll(opts.context.ext), + .dir => try writer.writeAll(self.placeholder.dir), + .name => try writer.writeAll(self.placeholder.name), + .ext => try writer.writeAll(self.placeholder.ext), .hash => { - if (opts.context.hash) |hash| { + if (self.placeholder.hash) |hash| { try writer.print("{any}", .{bun.fmt.hexIntLower(hash)}); } }, diff --git a/src/cli/bun_command.zig b/src/cli/bun_command.zig index ca3792018cbf..07ef9e1f68d9 100644 --- a/src/cli/bun_command.zig +++ b/src/cli/bun_command.zig @@ -198,7 +198,7 @@ pub const BunCommand = struct { const root_dir = try std.fs.cwd().openIterableDir(root_path, .{}); var all_paths = try ctx.allocator.alloc([]const u8, output_files.items.len); var max_path_len: usize = 0; - for (output_files.items, all_paths) |*dest, src| { + for (all_paths, output_files.items) |*dest, src| { dest.* = src.input.text; } @@ -228,10 +228,10 @@ pub const BunCommand = struct { rel_path = resolve_path.relative(from_path, f.input.text); if (std.fs.path.dirname(rel_path)) |parent| { if (parent.len > root_path.len) { - root_dir.makePath(parent); + try root_dir.dir.makePath(parent); } } - try root_dir.writeFile(rel_path, value); + try root_dir.dir.writeFile(rel_path, value); }, .move => |value| { // const primary = f.input.text[from_path.len..]; @@ -244,7 +244,7 @@ pub const BunCommand = struct { .copy => |value| { rel_path = value.pathname; - try f.copyTo(root_path, bun.constStrToU8(rel_path), root_dir.fd); + try f.copyTo(root_path, bun.constStrToU8(rel_path), root_dir.dir.fd); }, .noop => {}, .pending => unreachable, diff --git a/src/js_ast.zig b/src/js_ast.zig index 33ae4615bca0..16a351668dd0 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -776,7 +776,7 @@ pub const Symbol = struct { } return switch (kind) { - .mangled_prop => .mangled_prop, + // .mangled_prop => .mangled_prop, .label => .label, else => .default, }; @@ -968,7 +968,7 @@ pub const Symbol = struct { return new; } - pub fn get(self: *Map, ref: Ref) ?*Symbol { + pub fn get(self: *const Map, ref: Ref) ?*Symbol { if (Ref.isSourceIndexNull(ref.sourceIndex()) or ref.isSourceContentsSlice()) { return null; } @@ -993,7 +993,7 @@ pub const Symbol = struct { return Map{ .symbols_for_source = list }; } - pub fn getWithLink(symbols: *Map, ref: Ref) ?*Symbol { + pub fn getWithLink(symbols: *const Map, ref: Ref) ?*Symbol { var symbol: *Symbol = symbols.get(ref) orelse return null; if (symbol.hasLink()) { return symbols.get(symbol.link) orelse symbol; @@ -1019,7 +1019,7 @@ pub const Symbol = struct { } } - pub fn follow(symbols: *Map, ref: Ref) Ref { + pub fn follow(symbols: *const Map, ref: Ref) Ref { if (symbols.get(ref)) |symbol| { const link = symbol.link; if (link.isNull()) diff --git a/src/js_parser.zig b/src/js_parser.zig index f95a10a9028c..3b563090709d 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -4216,8 +4216,8 @@ fn NewParser_( var symbol_use_values = part.symbol_uses.values(); var symbols = p.symbols.items; - for (symbol_use_refs, 0..) |ref, i| { - symbols[ref.innerIndex()].use_count_estimate -|= symbol_use_values[i].count_estimate; + for (symbol_use_refs, symbol_use_values) |ref, prev| { + symbols[ref.innerIndex()].use_count_estimate -|= prev.count_estimate; } const declared_refs = part.declared_symbols.refs.items; for (declared_refs) |declared| { @@ -4590,11 +4590,11 @@ fn NewParser_( bun.copy(u8, namespace_identifier[suffix.len..], import_path_identifier); const namespace_ref = try p.newSymbol(.other, namespace_identifier); - try p.module_scope.generated.append(allocator, namespace_ref); - for (imports, 0..) |alias, i| { + try p.module_scope.generated.push(allocator, namespace_ref); + for (imports, clause_items) |alias, *clause_item| { const ref = symbols.get(alias) orelse unreachable; const alias_name = if (@TypeOf(symbols) == RuntimeImports) RuntimeImports.all[alias] else alias; - clause_items[i] = js_ast.ClauseItem{ + clause_item.* = js_ast.ClauseItem{ .alias = alias_name, .original_name = alias_name, .alias_loc = logger.Loc{}, @@ -4610,11 +4610,14 @@ fn NewParser_( }); } - stmts[0] = p.s(S.Import{ - .namespace_ref = namespace_ref, - .items = clause_items, - .import_record_index = import_record_i, - }, logger.Loc{}); + stmts[0] = p.s( + S.Import{ + .namespace_ref = namespace_ref, + .items = clause_items, + .import_record_index = import_record_i, + }, + logger.Loc{}, + ); if (additional_stmt) |add| { stmts[1] = add; } diff --git a/src/js_printer.zig b/src/js_printer.zig index 94963763ca70..137f63371d3c 100644 --- a/src/js_printer.zig +++ b/src/js_printer.zig @@ -1508,7 +1508,7 @@ fn NewPrinter( } } - inline fn symbols(p: *const Printer) js_ast.Symbol.Map { + inline fn symbols(p: *Printer) js_ast.Symbol.Map { return p.renamer.symbols(); } @@ -2373,7 +2373,7 @@ fn NewPrinter( p.printWhitespacer(ws("(0, ")); } p.addSourceMapping(expr.loc); - p.printNamespaceAlias(import_record, namespace); + p.printNamespaceAlias(import_record.*, namespace); if (wrap) { p.print(")"); @@ -3461,7 +3461,7 @@ fn NewPrinter( p.printSpace(); const last = s.items.len - 1; for (s.items, 0..) |item, i| { - const symbol = p.symbols.getWithLink(item.name.ref.?).?; + const symbol = p.symbols().getWithLink(item.name.ref.?).?; const name = symbol.original_name; var did_print = false; @@ -3470,7 +3470,7 @@ fn NewPrinter( if (import_record.is_bundled or (comptime is_inside_bundle) or namespace.was_originally_property_access) { p.printIdentifier(name); p.print(": () => "); - p.printNamespaceAlias(import_record, namespace); + p.printNamespaceAlias(import_record.*, namespace); did_print = true; } } @@ -3542,7 +3542,7 @@ fn NewPrinter( p.print("var "); p.printSymbol(item.name.ref.?); p.@"print = "(); - p.printNamespaceAlias(import_record, namespace); + p.printNamespaceAlias(import_record.*, namespace); p.printSemicolonAfterStatement(); _ = array.swapRemove(i); @@ -3678,7 +3678,7 @@ fn NewPrinter( } p.print("}=import.meta.require("); - p.printImportRecordPath(&import_record); + p.printImportRecordPath(import_record); p.print(")"); p.printSemicolonAfterStatement(); p.printWhitespacer(ws("export {")); @@ -3741,7 +3741,7 @@ fn NewPrinter( } p.printWhitespacer(ws("} from ")); - p.printImportRecordPath(&import_record); + p.printImportRecordPath(import_record); p.printSemicolonAfterStatement(); }, .s_local => |s| { @@ -5419,8 +5419,9 @@ pub fn printJSON( var stmts = &[_]js_ast.Stmt{stmt}; var parts = &[_]js_ast.Part{.{ .stmts = stmts }}; const ast = Ast.initTest(parts); - - var renamer = rename.NoOpRenamer.init(ast.symbols, source); + var list = js_ast.Symbol.List.init(ast.symbols.slice()); + var nested_list = js_ast.Symbol.NestedList.init(&[_]js_ast.Symbol.List{list}); + var renamer = rename.NoOpRenamer.init(js_ast.Symbol.Map.initList(nested_list), source); var printer = PrinterType.init( writer, @@ -5448,11 +5449,11 @@ pub fn print( renamer: bun.renamer.Renamer, ) PrintResult { var buffer_writer = BufferWriter.init(allocator) catch |err| return .{ .err = err }; - var buffer_printer = BufferPrinter.init(&buffer_writer); + var buffer_printer = BufferPrinter.init(buffer_writer); return printWithWriter( *BufferPrinter, - buffer_printer, + &buffer_printer, platform, opts, import_records, @@ -5471,7 +5472,7 @@ pub fn printWithWriter( renamer: bun.renamer.Renamer, ) PrintResult { return switch (platform.isBun()) { - inline else => |is_bun_platform| printWithWriter( + inline else => |is_bun_platform| printWithWriterAndPlatform( Writer, _writer, is_bun_platform, @@ -5555,7 +5556,7 @@ pub fn printCommonJS( tree.import_records.slice(), opts, renamer.toRenamer(), - getSourceMapBuilder(generate_source_map, false, opts, source, tree), + getSourceMapBuilder(generate_source_map, false, opts, source, &tree), ); defer { imported_module_ids_list = printer.imported_module_ids; diff --git a/src/renamer.zig b/src/renamer.zig index 51a8ef7f5323..5a05f1d8536c 100644 --- a/src/renamer.zig +++ b/src/renamer.zig @@ -167,7 +167,7 @@ pub const NumberRenamer = struct { // Symbols in child scopes may also have to be renamed to avoid conflicts for (scope.children.slice()) |child| { child_scope.name_counts.clearRetainingCapacity(); - r.assignNamesRecursiveWithNumberScope(child_scope, child, source_index, s, sorted); + r.assignNamesRecursiveWithNumberScope(child_scope, child, source_index, sorted); } } @@ -243,7 +243,7 @@ pub const NumberRenamer = struct { // avoid rehashing the same string over for each scope const ctx = bun.StringHashMapContext.pre(name); - while (s) |*scope| : (s = scope.parent) { + while (s) |scope| : (s = scope.parent) { if (scope.name_counts.getAdapted(name, ctx)) |count| { if (scope == this) { return .{ .same_scope = count }; @@ -376,12 +376,12 @@ pub fn computeInitialReservedNames( ) !bun.StringHashMapUnmanaged(u32) { var names = bun.StringHashMapUnmanaged(u32){}; - const extras = &.{ + const extras = .{ "Promise", "Require", }; - try names.ensureTotalCapacity(allocator, JSLexer.Keywords.keys().len + JSLexer.StrictModeReservedWords.keys().len + 1 + extras.len); + try names.ensureTotalCapacity(allocator, @truncate(u32, JSLexer.Keywords.keys().len + JSLexer.StrictModeReservedWords.keys().len + 1 + extras.len)); for (JSLexer.Keywords.keys()) |keyword| { names.putAssumeCapacity(keyword, 1); @@ -391,7 +391,7 @@ pub fn computeInitialReservedNames( names.putAssumeCapacity(keyword, 1); } - for (extras) |extra| { + inline for (comptime extras) |extra| { names.putAssumeCapacity(extra, 1); } diff --git a/src/thread_pool.zig b/src/thread_pool.zig index 70068b92a83a..240a57cf73de 100644 --- a/src/thread_pool.zig +++ b/src/thread_pool.zig @@ -130,7 +130,7 @@ pub const WaitGroup = struct { counter: u32 = 0, event: std.Thread.ResetEvent, - pub fn init(self: *WaitGroup) !void { + pub fn init(self: *WaitGroup) void { self.* = .{ .mutex = .{}, .counter = 0, @@ -284,7 +284,7 @@ pub fn Do( var wait_group = wg orelse brk: { allocated_wait_group = try allocator.create(WaitGroup); - try allocated_wait_group.?.init(); + allocated_wait_group.?.init(); break :brk allocated_wait_group.?; }; const WaitContext = struct { From 32c9f11f9785ad19f0ca0938ca5a0d26489748f8 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Wed, 1 Mar 2023 04:09:54 -0800 Subject: [PATCH 025/255] even more code --- src/bundler/bundle_v2.zig | 84 +++++++++++++++++++++++-------------- src/renamer.zig | 2 +- src/sourcemap/sourcemap.zig | 9 ++-- 3 files changed, 58 insertions(+), 37 deletions(-) diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 38ce601bde0d..c6b3c88b4b66 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -3836,7 +3836,7 @@ const LinkerContext = struct { var prev_filename_comment: Index.Int = 0; - const sources: []const Logger.Source = c.graph.files.items(.source); + const sources: []const Logger.Source = c.parse_graph.input_files.items(.source); for (@as([]CompileResult, compile_results.items)) |compile_result| { const source_index = compile_result.sourceIndex(); const is_runtime = source_index == Index.runtime.value; @@ -3862,7 +3862,7 @@ const LinkerContext = struct { const pretty = sources[source_index].path.pretty; // TODO: quote this. This is really janky. - const comment_type = if (strings.indexOfNewlineOrNonASCII(pretty) != null) + const comment_type = if (strings.indexOfNewlineOrNonASCII(pretty, 0) != null) CommentType.multiline else CommentType.single; @@ -3944,7 +3944,7 @@ const LinkerContext = struct { chunk.intermediate_output = c.breakOutputIntoPieces( allocator, &j, - ctx.chunks.len, + @truncate(u32, ctx.chunks.len), ) catch @panic("Unhandled out of memory error in breakOutputIntoPieces()"); // TODO: meta contents @@ -3961,7 +3961,7 @@ const LinkerContext = struct { // that live in separate parts in the same file must not be merged. This only // needs to be done for JavaScript files, not CSS files. if (chunk.content == .javascript) { - const sources = c.graph.files.items(.source); + const sources = c.parse_graph.input_files.items(.source); for (chunk.content.javascript.parts_in_chunk_in_order) |part_range| { const source: Logger.Source = sources[part_range.source_index.get()]; @@ -4011,10 +4011,13 @@ const LinkerContext = struct { // data in the spans between them. if (chunk.intermediate_output == .pieces) { for (chunk.intermediate_output.pieces.slice()) |piece| { - hasher.write(piece.data); + hasher.write(piece.data()); } } else { - hasher.write(chunk.intermediate_output.single_piece.data); + var el = chunk.intermediate_output.joiner.head; + while (el) |e| : (el = e.next) { + hasher.write(e.data.slice); + } } return hasher.digest(); @@ -4046,6 +4049,9 @@ const LinkerContext = struct { // "export default require_foo();" S.ExportDefault, .{ + .default_name = .{ + .loc = Logger.Loc.Empty, + }, .value = .{ .stmt = Stmt.alloc( S.SExpr, @@ -4142,7 +4148,7 @@ const LinkerContext = struct { // already been resolved by this point, so we can't generate a new import // and have that be resolved later. if (imports_to_bind.get(resolved_export.data.import_ref)) |import_data| { - resolved_export.data.import_ref = import_data.import_ref; + resolved_export.data.import_ref = import_data.data.import_ref; resolved_export.data.source_index = import_data.data.source_index; } @@ -4192,6 +4198,7 @@ const LinkerContext = struct { .decls = bun.fromSlice( []js_ast.G.Decl, temp_allocator, + []const js_ast.G.Decl, &.{ .{ .binding = Binding.alloc( @@ -4199,6 +4206,7 @@ const LinkerContext = struct { B.Identifier{ .ref = temp_ref, }, + Logger.Loc.Empty, ), }, }, @@ -4212,8 +4220,10 @@ const LinkerContext = struct { .{ .name = js_ast.LocRef{ .ref = temp_ref, + .loc = Logger.Loc.Empty, }, .alias = alias, + .alias_loc = Logger.Loc.Empty, }, ) catch unreachable; } else { @@ -4243,8 +4253,10 @@ const LinkerContext = struct { items.append(.{ .name = js_ast.LocRef{ .ref = resolved_export.data.import_ref, + .loc = resolved_export.data.name_loc, }, .alias = alias, + .alias_loc = resolved_export.data.name_loc, }) catch unreachable; } } @@ -4272,12 +4284,15 @@ const LinkerContext = struct { // "module.exports = require_foo();" stmts.append( Stmt.assign( - Expr.init(E.Dot, .{ - .target = .{ + Expr.init( + E.Dot, + .{ .target = Expr.initIdentifier(c.unbound_module_ref, Logger.Loc.Empty), .name = "exports", + .name_loc = Logger.Loc.Empty, }, - }, Logger.Loc.Empty), + Logger.Loc.Empty, + ), Expr.init( E.Call, .{ @@ -4293,7 +4308,7 @@ const LinkerContext = struct { // "init_foo();" stmts.append( Stmt.alloc( - S.Expr, + S.SExpr, .{ .value = Expr.init( E.Call, @@ -4322,8 +4337,10 @@ const LinkerContext = struct { if (stmts.items.len == 0) { return .{ .javascript = .{ - .source_index = source_index, - .code = "", + .result = .{ + .source_index = source_index, + .code = "", + }, }, }; } @@ -4700,7 +4717,7 @@ const LinkerContext = struct { }, stmt.loc, ), - .args = args, + .args = bun.BabyList(Expr).init(args), }, stmt.loc, ), @@ -4756,13 +4773,12 @@ const LinkerContext = struct { .import_record_index = s.import_record_index, }, stmt.loc, - .{}, ); }; // Prefix this module with "__reExport(exports, require(path), module.exports)" const export_star_ref = c.runtimeFunction("__reExport"); - var args = try allocator.alloc(Expr, 2 + @boolToInt(module_exports_for_export != null)); + var args = try allocator.alloc(Expr, 2 + @as(usize, @boolToInt(module_exports_for_export != null))); args[0..2].* = .{ Expr.init( E.Identifier, @@ -4782,7 +4798,7 @@ const LinkerContext = struct { Stmt.alloc( S.SExpr, S.SExpr{ - .expr = Expr.init( + .value = Expr.init( E.Call, E.Call{ .target = Expr.init( @@ -4792,7 +4808,7 @@ const LinkerContext = struct { }, stmt.loc, ), - .args = args, + .args = js_ast.ExprNodeList.init(args), }, stmt.loc, ), @@ -4922,9 +4938,10 @@ const LinkerContext = struct { stmt = Stmt.alloc( S.Local, S.Local{ - .decls = try bun.from( - []js_ast.Decl, + .decls = try bun.fromSlice( + []js_ast.G.Decl, allocator, + []const js_ast.G.Decl, &.{ .{ .binding = Binding.alloc( @@ -4932,7 +4949,7 @@ const LinkerContext = struct { B.Identifier{ .ref = s.default_name.ref.?, }, - s2.loc, + s2.value.loc, ), .value = s2.value, }, @@ -4983,9 +5000,10 @@ const LinkerContext = struct { stmt = Stmt.alloc( S.Local, S.Local{ - .decls = try bun.from( - []js_ast.Decl, + .decls = try bun.fromSlice( + []js_ast.G.Decl, allocator, + []const js_ast.G.Decl, &.{ .{ .binding = Binding.alloc( @@ -5075,6 +5093,7 @@ const LinkerContext = struct { .cjs => { stmts.inside_wrapper_prefix.appendSlice(stmts.inside_wrapper_suffix.items) catch unreachable; }, + else => {}, } stmts.inside_wrapper_suffix.clearRetainingCapacity(); } @@ -5082,19 +5101,19 @@ const LinkerContext = struct { // TODO: defaultLazyExport // Add all other parts in this chunk - for (parts) |part| { + for (parts, 0..) |part, index| { if (!part.is_live) { // Skip the part if it's not in this chunk continue; } - if (part.part_index == namespace_export_part_index) { + if (index == namespace_export_part_index) { // Skip the namespace export part because we already handled it above continue; } - if (part.part_index == wrapper_part_index) { + if (index == wrapper_part_index.value) { // Skip the wrapper part because we already handled it above needs_wrapper = true; continue; @@ -5104,7 +5123,7 @@ const LinkerContext = struct { // convert c.convertStmtsForChunk( - part_range.source_index, + part_range.source_index.get(), stmts, part.stmts, chunk, @@ -6412,7 +6431,7 @@ const LinkerContext = struct { while (true) { const invalid_boundary = std.math.maxInt(usize); // Scan for the next piece boundary - const boundary = strings.indexOf(output, prefix) orelse invalid_boundary; + var boundary = strings.indexOf(output, prefix) orelse invalid_boundary; var output_piece_index = Chunk.OutputPieceIndex{}; @@ -6468,7 +6487,8 @@ const LinkerContext = struct { if (boundary == invalid_boundary) { try pieces.append(Chunk.OutputPiece{ .index = output_piece_index, - .data = output, + .data_ptr = output.ptr, + .data_len = @truncate(u32, output.len), }); break; } @@ -6946,17 +6966,17 @@ const CompileResult = union(enum) { pub fn code(this: *const CompileResult) []const u8 { return switch (this.*) { .javascript => |r| switch (r.result) { - .code => |c| c, + .result => |r2| r2.code, else => "", }, - else => "", + // else => "", }; } pub fn sourceIndex(this: *const CompileResult) Index.Int { return switch (this.*) { .javascript => |r| r.source_index, - else => 0, + // else => 0, }; } }; diff --git a/src/renamer.zig b/src/renamer.zig index 5a05f1d8536c..daebf6c349eb 100644 --- a/src/renamer.zig +++ b/src/renamer.zig @@ -75,7 +75,7 @@ pub const NumberRenamer = struct { root: NumberScope = .{}, pub fn deinit(self: *NumberRenamer) void { - self.names.deinit(self.allocator); + self.names.deinitWithAllocator(self.allocator); self.root.deinit(self.temp_allocator); } diff --git a/src/sourcemap/sourcemap.zig b/src/sourcemap/sourcemap.zig index 971341cdc744..721c8caf6987 100644 --- a/src/sourcemap/sourcemap.zig +++ b/src/sourcemap/sourcemap.zig @@ -325,8 +325,8 @@ pub const LineColumnOffset = packed struct { pub fn advance(this: *Optional, input: []const u8) void { switch (this.*) { - .none => {}, - .value => this.offset.advance(input), + .null => {}, + .value => this.value.advance(input), } } }; @@ -343,8 +343,9 @@ pub const LineColumnOffset = packed struct { offset = i; var cp = strings.CodepointIterator.initOffset(input, offset); - _ = cp.next(); - switch (cp.c) { + var cursor = strings.CodepointIterator.Cursor{}; + _ = cp.next(&cursor); + switch (cursor.c) { '\r', '\n', 0x2028, 0x2029 => { // Handle Windows-specific "\r\n" newlines if (cp.c == '\r' and input.len > offset + 1 and input[offset + 1] == '\n') { From 41ad0066527f166428bda0e94093c8d8f58447b0 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Wed, 1 Mar 2023 04:29:19 -0800 Subject: [PATCH 026/255] okay i fixed more errors --- src/bundler/bundle_v2.zig | 49 +++++++++++++++++---------------------- src/js_printer.zig | 2 +- 2 files changed, 22 insertions(+), 29 deletions(-) diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index c6b3c88b4b66..ddfda4d532f7 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -4337,10 +4337,10 @@ const LinkerContext = struct { if (stmts.items.len == 0) { return .{ .javascript = .{ - .result = .{ - .source_index = source_index, + .source_index = source_index, + .result = .{ .result = .{ .code = "", - }, + } }, }, }; } @@ -5129,7 +5129,7 @@ const LinkerContext = struct { chunk, temp_allocator, flags.wrap, - ast, + &ast, ) catch |err| return .{ .err = err, }; @@ -5175,7 +5175,7 @@ const LinkerContext = struct { .binding = js_ast.Binding.alloc( temp_allocator, js_ast.B.Identifier{ - .ref = ast.module_ref, + .ref = ast.module_ref.?, }, Logger.Loc.Empty, ), @@ -5190,13 +5190,8 @@ const LinkerContext = struct { E.Arrow{ .args = args.items, .body = .{ - .block = Stmt.alloc( - S.Block, - .{ - .stmts = stmts.all_stmts.items, - }, - Logger.Loc.Empty, - ), + .stmts = stmts.all_stmts.items, + .loc = Logger.Loc.Empty, }, }, Logger.Loc.Empty, @@ -5212,7 +5207,7 @@ const LinkerContext = struct { }, Logger.Loc.Empty, ), - .args = cjs_args, + .args = bun.BabyList(Expr).init(cjs_args), }, Logger.Loc.Empty, ); @@ -5253,12 +5248,13 @@ const LinkerContext = struct { { const Hoisty = struct { decls: std.ArrayList(G.Decl), + allocator: std.mem.Allocator, pub fn wrapIdentifier(w: *@This(), loc: Logger.Loc, ref: Ref) Expr { w.decls.append( G.Decl{ .binding = Binding.alloc( - w.decls.allocator, + w.allocator, B.Identifier{ .ref = ref, }, @@ -5284,6 +5280,7 @@ const LinkerContext = struct { }; var hoisty = Hoisty{ .decls = std.ArrayList(G.Decl).init(temp_allocator), + .allocator = temp_allocator, }; var end: usize = 0; for (stmts.all_stmts.items) |stmt_| { @@ -5292,7 +5289,7 @@ const LinkerContext = struct { .s_local => |local| { var value: Expr = Expr.init(E.Missing, E.Missing{}, Logger.Loc.Empty); for (local.decls) |*decl| { - const binding = decl.binding.toExpr(hoisty); + const binding = decl.binding.toExpr(&hoisty); if (decl.value) |other| { value = value.joinWithComma( binding.assign( @@ -5308,9 +5305,9 @@ const LinkerContext = struct { continue; } stmt = Stmt.alloc( - S.Expr, - S.Expr{ - .expr = value, + S.SExpr, + S.SExpr{ + .value = value, }, stmt.loc, ); @@ -5319,6 +5316,7 @@ const LinkerContext = struct { stmts.outside_wrapper_prefix.append(stmt) catch unreachable; continue; }, + else => {}, } stmts.all_stmts.items[end] = stmt; end += 1; @@ -5336,13 +5334,8 @@ const LinkerContext = struct { .args = &.{}, .is_async = is_async, .body = .{ - .block = Stmt.alloc( - S.Block, - .{ - .stmts = stmts.all_stmts.items, - }, - Logger.Loc.Empty, - ), + .stmts = stmts.all_stmts.items, + .loc = Logger.Loc.Empty, }, }, Logger.Loc.Empty, @@ -5360,7 +5353,7 @@ const LinkerContext = struct { }, Logger.Loc.Empty, ), - .args = esm_args, + .args = bun.BabyList(Expr).init(esm_args), }, Logger.Loc.Empty, ); @@ -5396,7 +5389,7 @@ const LinkerContext = struct { const parts_to_print = &[_]js_ast.Part{ js_ast.Part{ - .kind = .stmts, + // .tag = .stmts, .stmts = out_stmts, }, }; @@ -5426,7 +5419,7 @@ const LinkerContext = struct { &printer, c.resolver.opts.platform, print_options, - ast.import_records, + ast.import_records.slice(), parts_to_print, r, ); diff --git a/src/js_printer.zig b/src/js_printer.zig index 137f63371d3c..fc517338d9a2 100644 --- a/src/js_printer.zig +++ b/src/js_printer.zig @@ -549,7 +549,7 @@ pub const RequireOrImportMeta = struct { ) Callback { return Callback{ .ctx = bun.cast(*anyopaque, ctx), - .callback = @ptrCast(*const Fn, callback), + .callback = @ptrCast(*const Fn, &callback), }; } }; From 38daf43a5d879f67144f3cfe2c0aaa634c62c05e Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Fri, 3 Mar 2023 02:50:39 -0800 Subject: [PATCH 027/255] wip --- src/bundler/bundle_v2.zig | 97 ++++++++++++++++++++------- test/fixtures/bundle/trivial/fn.js | 3 + test/fixtures/bundle/trivial/index.js | 4 ++ 3 files changed, 78 insertions(+), 26 deletions(-) create mode 100644 test/fixtures/bundle/trivial/fn.js create mode 100644 test/fixtures/bundle/trivial/index.js diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index ddfda4d532f7..cf920c134884 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -598,7 +598,7 @@ pub const BundleV2 = struct { var import_records = result.ast.import_records.slice(); for (import_records) |*record| { if (graph.path_to_source_index_map.get(wyhash(0, record.path.text))) |source_index| { - record.source_index.set(source_index); + record.source_index.value = source_index; } } @@ -1391,7 +1391,8 @@ const LinkerGraph = struct { if (use_count == 0) return; // Mark this symbol as used by this part - var part: *js_ast.Part = &g.ast.items(.parts)[id].slice()[part_index]; + var parts_list = g.ast.items(.parts)[id].slice(); + var part: *js_ast.Part = &parts_list[part_index]; var uses = part.symbol_uses.getOrPut(g.allocator, ref) catch unreachable; if (uses.found_existing) { @@ -1481,9 +1482,13 @@ const LinkerGraph = struct { // Setup files { - var stable_source_indices = try this.allocator.alloc(Index, sources.len); - for (this.reachable_files, 0..) |_, i| { - stable_source_indices[i] = Index.source(i); + var stable_source_indices = try this.allocator.alloc(Index, sources.len + 1); + + // set it to max value so that if we access an invalid one, it crashes + @memset(std.mem.sliceAsBytes(stable_source_indices).ptr, 255, std.mem.sliceAsBytes(stable_source_indices).len); + + for (this.reachable_files, 0..) |source_index, i| { + stable_source_indices[source_index.get()] = Index.source(i); } const file = comptime LinkerGraph.File{}; @@ -1493,9 +1498,34 @@ const LinkerGraph = struct { files.items(.distance_from_entry_point), comptime file.distance_from_entry_point, ); + this.stable_source_indices = @ptrCast([]const u32, stable_source_indices); } this.symbols = js_ast.Symbol.Map.initList(js_ast.Symbol.NestedList.init(this.ast.items(.symbols))); + var import_records_list: []ImportRecord.List = this.ast.items(.import_records); + try this.meta.ensureTotalCapacity(this.allocator, import_records_list.len); + this.meta.len = this.ast.len; + this.meta.zero(); + + var in_resolved_exports: []ResolvedExports = this.meta.items(.resolved_exports); + var src_resolved_exports: []js_ast.Ast.NamedExports = this.ast.items(.named_exports); + for (src_resolved_exports, in_resolved_exports, 0..) |src, *dest, source_index| { + var resolved = ResolvedExports{}; + resolved.ensureTotalCapacity(this.allocator, src.count()) catch unreachable; + for (src.keys(), src.values()) |key, value| { + resolved.putAssumeCapacityNoClobber( + key, + .{ + .data = .{ + .import_ref = value.ref, + .name_loc = value.alias_loc, + .source_index = Index.source(source_index), + }, + }, + ); + } + dest.* = resolved; + } } pub const File = struct { @@ -1728,7 +1758,7 @@ const LinkerContext = struct { if (chunk.entry_point.is_entry_point) { chunk.template = PathTemplate.file; - const pathname = Fs.PathName.init(this.graph.entry_points.items(.output_path)[chunk.entry_point.source_index].slice()); + const pathname = Fs.PathName.init(this.graph.entry_points.items(.output_path)[chunk.entry_point.entry_point_id].slice()); chunk.template.placeholder.name = pathname.base; chunk.template.placeholder.ext = "js"; chunk.template.placeholder.dir = pathname.dir; @@ -1913,9 +1943,6 @@ const LinkerContext = struct { { var import_records_list: []ImportRecord.List = this.graph.ast.items(.import_records); - try this.graph.meta.ensureTotalCapacity(this.graph.allocator, import_records_list.len); - this.graph.meta.len = this.graph.ast.len; - this.graph.meta.zero(); // var parts_list: [][]js_ast.Part = this.graph.ast.items(.parts); var exports_kind: []js_ast.ExportsKind = this.graph.ast.items(.exports_kind); @@ -2332,7 +2359,7 @@ const LinkerContext = struct { ) catch unreachable; } - var imports_to_bind = imports_to_bind_list[id]; + var imports_to_bind = &imports_to_bind_list[id]; var imports_to_bind_iter = imports_to_bind.iterator(); var parts: []js_ast.Part = parts_list[id].slice(); @@ -2762,7 +2789,7 @@ const LinkerContext = struct { var declared_symbols = js_ast.DeclaredSymbol.List{}; var exports_ref = c.graph.ast.items(.exports_ref)[id]; var export_stmts: []js_ast.Stmt = stmts.head; - std.debug.assert(stmts.head.len <= 2); // assert we allocated exactly the right amount + // std.debug.assert(stmts.head.len <= 3); // assert we allocated exactly the right amount stmts.head.len = 0; // Prefix this part with "var exports = {}" if this isn't a CommonJS entry point @@ -2958,8 +2985,8 @@ const LinkerContext = struct { const other_parts = c.topLevelSymbolsToParts(id, ref); for (other_parts) |other_part_index| { - var local = local_dependencies.getOrPutValue(@intCast(u32, other_part_index), @intCast(u32, part_index)) catch unreachable; - if (local.value_ptr.* != @intCast(u32, part_index)) { + var local = local_dependencies.getOrPut(@intCast(u32, other_part_index)) catch unreachable; + if (!local.found_existing) { local.value_ptr.* = @intCast(u32, part_index); // note: if we crash on append, it is due to threadlocal heaps in mimalloc part.dependencies.push( @@ -3659,8 +3686,8 @@ const LinkerContext = struct { var runtime_scope: *Scope = &c.graph.ast.items(.module_scope)[c.graph.files.items(.input_file)[Index.runtime.value].get()].?; var runtime_members = &runtime_scope.members; - const toCommonJSRef = c.graph.symbols.follow(runtime_members.get("toCommonJS").?.ref); - const toESMRef = c.graph.symbols.follow(runtime_members.get("toESM").?.ref); + const toCommonJSRef = c.graph.symbols.follow(runtime_members.get("__toCommonJS").?.ref); + const toESMRef = c.graph.symbols.follow(runtime_members.get("__toESM").?.ref); const runtimeRequireRef = c.graph.symbols.follow(runtime_members.get("__require").?.ref); var r = try c.renameSymbolsInChunk(allocator, chunk, repr.files_in_chunk_order); @@ -5579,12 +5606,20 @@ const LinkerContext = struct { import_records: []bun.BabyList(bun.ImportRecord), entry_point_kinds: []EntryPoint.Kind, ) void { + if (comptime bun.Environment.allow_assert) + debug( + "markFileLiveForTreeShaking({d}, {s}) = {s}", + .{ + source_index, + c.parse_graph.input_files.get(source_index).source.path.text, + if (c.graph.files_live.isSet(source_index)) "seen" else "not seen", + }, + ); + if (c.graph.files_live.isSet(source_index)) return; c.graph.files_live.set(source_index); - if (comptime bun.Environment.allow_assert) - debug("markFileLiveForTreeShaking: {s}", .{c.parse_graph.input_files.get(source_index).source.path.text}); // TODO: CSS source index @@ -5726,7 +5761,7 @@ const LinkerContext = struct { const next_tracker = advanced.tracker.*; const status = advanced.status; const potentially_ambiguous_export_star_refs = advanced.import_data; - const other_id = tracker.source_index.get(); + const other_id = advanced.value.source_index.get(); switch (status) { .cjs, .cjs_without_exports, .disabled, .external => { @@ -5823,8 +5858,9 @@ const LinkerContext = struct { source, r, c.allocator, - "No matching export in \"{s}\" for import \"{s}\"", + "No matching export \"{s}\" in \"{s}\" for import \"{s}\"", .{ + named_import.alias.?, next_source.path.pretty, named_import.alias.?, }, @@ -6186,12 +6222,15 @@ const LinkerContext = struct { }; } - pub fn matchImportsWithExportsForFile(c: *LinkerContext, named_imports: *JSAst.NamedImports, imports_to_bind: *RefImportData, source_index: Index.Int) void { - var iter = named_imports.iterator(); - // TODO: do we need to sort here? I don't think so - // because NamedImports is an ArrayHashMap, it's order should naturally be deterministic + pub fn matchImportsWithExportsForFile( + c: *LinkerContext, + named_imports_ptr: *JSAst.NamedImports, + imports_to_bind: *RefImportData, + source_index: Index.Int, + ) void { + var named_imports = named_imports_ptr.cloneWithAllocator(c.allocator) catch unreachable; + defer named_imports.deinit(); - // Pair imports with their matching exports const Sorter = struct { imports: *JSAst.NamedImports, @@ -6203,16 +6242,22 @@ const LinkerContext = struct { } }; var sorter = Sorter{ - .imports = named_imports, + .imports = &named_imports, }; named_imports.sort(sorter); + var iter = named_imports.iterator(); while (iter.next()) |entry| { // Re-use memory for the cycle detector c.cycle_detector.clearRetainingCapacity(); const ref = entry.key_ptr.*; - const import_ref = Ref.init(ref.innerIndex(), @truncate(Ref.Int, source_index), ref.isSourceContentsSlice()); + const import_ref = Ref{ + .inner_index = ref.innerIndex(), + .source_index = @truncate(Ref.Int, source_index), + .tag = ref.tag, + }; + var import_tracker = ImportData{ .data = .{ .source_index = Index.source(source_index), diff --git a/test/fixtures/bundle/trivial/fn.js b/test/fixtures/bundle/trivial/fn.js new file mode 100644 index 000000000000..467f11c0b0dd --- /dev/null +++ b/test/fixtures/bundle/trivial/fn.js @@ -0,0 +1,3 @@ +export function fn() { + return 42; +} diff --git a/test/fixtures/bundle/trivial/index.js b/test/fixtures/bundle/trivial/index.js new file mode 100644 index 000000000000..dde7300980e5 --- /dev/null +++ b/test/fixtures/bundle/trivial/index.js @@ -0,0 +1,4 @@ +import { fn } from "./fn"; + +console.log(fn(42)); +globalThis.fn = fn; From a3745ec2ed6a6008c148befb5998ccad04e97ccb Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Fri, 3 Mar 2023 02:50:42 -0800 Subject: [PATCH 028/255] Update launch.json --- .vscode/launch.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index d478eff36b94..3f0af19bdd80 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -114,8 +114,8 @@ "request": "launch", "name": "bun build debug", "program": "bun-debug", - "args": ["build", "--platform=bun", "--outdir=/tmp/testout", "${file}"], - "cwd": "${file}/../../", + "args": ["bun", "./test/fixtures/bundle/trivial/index.js"], + "cwd": "${workspaceFolder}", "console": "internalConsole", "env": { "BUN_CONFIG_MINIFY_WHITESPACE": "1" From c892ce93a50704b8302caa9c9d5de42e2cfba685 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Fri, 3 Mar 2023 04:19:47 -0800 Subject: [PATCH 029/255] Update string_builder.zig --- src/string_builder.zig | 1 - 1 file changed, 1 deletion(-) diff --git a/src/string_builder.zig b/src/string_builder.zig index 49637944765b..1dcf7b494859 100644 --- a/src/string_builder.zig +++ b/src/string_builder.zig @@ -76,7 +76,6 @@ pub fn allocatedSlice(this: *StringBuilder) []u8 { var ptr = this.ptr orelse return &[_]u8{}; if (comptime Environment.allow_assert) { assert(this.cap > 0); - assert(this.len > 0); } return ptr[0..this.cap]; } From fe53991df385b177e027cc3f898927528732ba92 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Fri, 3 Mar 2023 16:09:36 -0800 Subject: [PATCH 030/255] `fast_debug_build_mode` makes debug build 2x faster --- src/bun.zig | 10 +++++++++- src/cli.zig | 33 +++++++++++++++++---------------- src/resolver/resolver.zig | 1 + 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/src/bun.zig b/src/bun.zig index 4cf9dabcaed8..a2a064360228 100644 --- a/src/bun.zig +++ b/src/bun.zig @@ -1021,7 +1021,8 @@ pub const ImportRecord = @import("./import_record.zig").ImportRecord; pub const ImportKind = @import("./import_record.zig").ImportKind; pub usingnamespace @import("./util.zig"); -pub const fast_debug_build_mode = false and +pub const fast_debug_build_cmd = .BunCommand; +pub const fast_debug_build_mode = fast_debug_build_cmd != .None and Environment.isDebug; pub const MultiArrayList = @import("./multi_array_list.zig").MultiArrayList; @@ -1044,3 +1045,10 @@ pub fn asByteSlice(buffer: anytype) []const u8 { }, }; } + +comptime { + if (fast_debug_build_cmd != .RunCommand and fast_debug_build_mode) { + _ = @import("./bun.js/node/buffer.zig").BufferVectorized.fill; + _ = @import("./cli/upgrade_command.zig").Version; + } +} diff --git a/src/cli.zig b/src/cli.zig index 62483ceb07f8..3b05b6eb75d0 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -1032,85 +1032,86 @@ pub const Command = struct { switch (tag) { .BunCommand => { + if (comptime bun.fast_debug_build_mode and bun.fast_debug_build_cmd != .BunCommand) unreachable; const ctx = try Command.Context.create(allocator, log, .BunCommand); try BunCommand.exec(ctx); }, .DevCommand => { - if (comptime bun.fast_debug_build_mode) unreachable; + if (comptime bun.fast_debug_build_mode and bun.fast_debug_build_cmd != .DevCommand) unreachable; const ctx = try Command.Context.create(allocator, log, .DevCommand); try DevCommand.exec(ctx); }, .BuildCommand => { - if (comptime bun.fast_debug_build_mode) unreachable; + if (comptime bun.fast_debug_build_mode and bun.fast_debug_build_cmd != .BuildCommand) unreachable; const ctx = try Command.Context.create(allocator, log, .BuildCommand); try BuildCommand.exec(ctx); }, .InstallCompletionsCommand => { - if (comptime bun.fast_debug_build_mode) unreachable; + if (comptime bun.fast_debug_build_mode and bun.fast_debug_build_cmd != .InstallCompletionsCommand) unreachable; try InstallCompletionsCommand.exec(allocator); return; }, .InstallCommand => { - if (comptime bun.fast_debug_build_mode) unreachable; + if (comptime bun.fast_debug_build_mode and bun.fast_debug_build_cmd != .InstallCommand) unreachable; const ctx = try Command.Context.create(allocator, log, .InstallCommand); try InstallCommand.exec(ctx); return; }, .AddCommand => { - if (comptime bun.fast_debug_build_mode) unreachable; + if (comptime bun.fast_debug_build_mode and bun.fast_debug_build_cmd != .AddCommand) unreachable; const ctx = try Command.Context.create(allocator, log, .AddCommand); try AddCommand.exec(ctx); return; }, .BunxCommand => { - if (comptime bun.fast_debug_build_mode) unreachable; + if (comptime bun.fast_debug_build_mode and bun.fast_debug_build_cmd != .BunxCommand) unreachable; const ctx = try Command.Context.create(allocator, log, .BunxCommand); try BunxCommand.exec(ctx); return; }, .RemoveCommand => { - if (comptime bun.fast_debug_build_mode) unreachable; + if (comptime bun.fast_debug_build_mode and bun.fast_debug_build_cmd != .RemoveCommand) unreachable; const ctx = try Command.Context.create(allocator, log, .RemoveCommand); try RemoveCommand.exec(ctx); return; }, .LinkCommand => { - if (comptime bun.fast_debug_build_mode) unreachable; + if (comptime bun.fast_debug_build_mode and bun.fast_debug_build_cmd != .LinkCommand) unreachable; const ctx = try Command.Context.create(allocator, log, .LinkCommand); try LinkCommand.exec(ctx); return; }, .UnlinkCommand => { - if (comptime bun.fast_debug_build_mode) unreachable; + if (comptime bun.fast_debug_build_mode and bun.fast_debug_build_cmd != .UnlinkCommand) unreachable; const ctx = try Command.Context.create(allocator, log, .UnlinkCommand); try UnlinkCommand.exec(ctx); return; }, .PackageManagerCommand => { - if (comptime bun.fast_debug_build_mode) unreachable; + if (comptime bun.fast_debug_build_mode and bun.fast_debug_build_cmd != .PackageManagerCommand) unreachable; const ctx = try Command.Context.create(allocator, log, .PackageManagerCommand); try PackageManagerCommand.exec(ctx); return; }, .TestCommand => { - if (comptime bun.fast_debug_build_mode) unreachable; + if (comptime bun.fast_debug_build_mode and bun.fast_debug_build_cmd != .TestCommand) unreachable; const ctx = try Command.Context.create(allocator, log, .TestCommand); try TestCommand.exec(ctx); return; }, .GetCompletionsCommand => { - if (comptime bun.fast_debug_build_mode) unreachable; + if (comptime bun.fast_debug_build_mode and bun.fast_debug_build_cmd != .GetCompletionsCommand) unreachable; const ctx = try Command.Context.create(allocator, log, .GetCompletionsCommand); var filter = ctx.positionals; @@ -1197,7 +1198,7 @@ pub const Command = struct { return; }, .CreateCommand => { - if (comptime bun.fast_debug_build_mode) unreachable; + if (comptime bun.fast_debug_build_mode and bun.fast_debug_build_cmd != .CreateCommand) unreachable; const ctx = try Command.Context.create(allocator, log, .CreateCommand); var positionals: [2]string = undefined; var positional_i: usize = 0; @@ -1221,7 +1222,7 @@ pub const Command = struct { return; }, .RunCommand => { - if (comptime bun.fast_debug_build_mode) unreachable; + if (comptime bun.fast_debug_build_mode and bun.fast_debug_build_cmd != .RunCommand) unreachable; const ctx = try Command.Context.create(allocator, log, .RunCommand); if (ctx.positionals.len > 0) { if (try RunCommand.exec(ctx, false, true)) { @@ -1232,13 +1233,13 @@ pub const Command = struct { } }, .UpgradeCommand => { - if (comptime bun.fast_debug_build_mode) unreachable; + if (comptime bun.fast_debug_build_mode and bun.fast_debug_build_cmd != .UpgradeCommand) unreachable; const ctx = try Command.Context.create(allocator, log, .UpgradeCommand); try UpgradeCommand.exec(ctx); return; }, .AutoCommand => { - if (comptime bun.fast_debug_build_mode) unreachable; + if (comptime bun.fast_debug_build_mode and bun.fast_debug_build_cmd != .AutoCommand) unreachable; var ctx = Command.Context.create(allocator, log, .AutoCommand) catch |e| { switch (e) { error.MissingEntryPoint => { diff --git a/src/resolver/resolver.zig b/src/resolver/resolver.zig index f2cc22b1d61c..8713c0733edb 100644 --- a/src/resolver/resolver.zig +++ b/src/resolver/resolver.zig @@ -1571,6 +1571,7 @@ pub const Resolver = struct { // this is the magic! if (global_cache.canUse(any_node_modules_folder) and r.usePackageManager() and esm_ != null) { + if (comptime bun.fast_debug_build_mode and bun.fast_debug_build_cmd != .RunCommand) unreachable; const esm = esm_.?.withAutoVersion(); load_module_from_cache: { From 97e19302f13b3e0045289b63f8f1bddf00b518f2 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Fri, 3 Mar 2023 18:48:39 -0800 Subject: [PATCH 031/255] Update bundle_v2.zig --- src/bundler/bundle_v2.zig | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index cf920c134884..d50c7b943adb 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -5736,7 +5736,6 @@ const LinkerContext = struct { defer ambiguous_results.clearAndFree(); var result: MatchImport = MatchImport{}; const named_imports = c.graph.ast.items(.named_imports); - var top_level_symbols_to_parts: []js_ast.Ast.TopLevelSymbolToParts = c.graph.ast.items(.top_level_symbols_to_parts); loop: while (true) { // Make sure we avoid infinite loops trying to resolve cycles: @@ -5915,7 +5914,7 @@ const LinkerContext = struct { // Depend on the statement(s) that declared this import symbol in the // original file { - var deps = top_level_symbols_to_parts[other_id].get(tracker.import_ref).?.slice(); + var deps = c.graph.topLevelSymbolToParts(other_id, tracker.import_ref); re_exports.ensureUnusedCapacity(deps.len) catch unreachable; for (deps) |dep| { re_exports.appendAssumeCapacity( From 3bd3d63bdc30deccb5ecd48ebb667d79a11deca6 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Fri, 3 Mar 2023 23:43:40 -0800 Subject: [PATCH 032/255] more code! --- src/baby_list.zig | 10 +++++++++ src/bit_set.zig | 4 ++++ src/bundler/bundle_v2.zig | 44 ++++++++++++++++++++------------------- src/js_parser.zig | 11 +++++++++- src/renamer.zig | 10 +++++++-- 5 files changed, 55 insertions(+), 24 deletions(-) diff --git a/src/baby_list.zig b/src/baby_list.zig index 3e98742166a5..8a3d9af03d09 100644 --- a/src/baby_list.zig +++ b/src/baby_list.zig @@ -56,6 +56,16 @@ pub fn BabyList(comptime Type: type) type { return this.ptr[this.len]; } + pub fn clone(this: @This(), allocator: std.mem.Allocator) !@This() { + var list_ = this.listManaged(allocator); + var copy = try list_.clone(); + return ListType{ + .ptr = copy.items.ptr, + .len = @truncate(u32, copy.items.len), + .cap = @truncate(u32, copy.capacity), + }; + } + pub inline fn appendAssumeCapacity(this: *@This(), value: Type) void { this.ptr[this.len] = value; this.len += 1; diff --git a/src/bit_set.zig b/src/bit_set.zig index 4032b082d59b..83f47c6239a1 100644 --- a/src/bit_set.zig +++ b/src/bit_set.zig @@ -1112,6 +1112,10 @@ pub const AutoBitSet = union(enum) { }; } + pub fn eql(this: *const AutoBitSet, b: *const AutoBitSet) bool { + return bun.strings.eqlLong(this.bytes(), b.bytes(), true); + } + pub fn deinit(this: *AutoBitSet, allocator: std.mem.Allocator) void { switch (std.meta.activeTag(this.*)) { .static => {}, diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index d50c7b943adb..6d5db71db9c3 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -595,12 +595,13 @@ pub const BundleV2 = struct { } } - var import_records = result.ast.import_records.slice(); - for (import_records) |*record| { + var import_records = result.ast.import_records.clone(this.graph.allocator) catch unreachable; + for (import_records.slice()) |*record| { if (graph.path_to_source_index_map.get(wyhash(0, record.path.text))) |source_index| { record.source_index.value = source_index; } } + result.ast.import_records = import_records; graph.ast.set(result.source.index.get(), result.ast); // schedule as early as possible @@ -1715,14 +1716,14 @@ const LinkerContext = struct { .javascript = .{}, }, }; - } + } else {} var files = try js_chunk_entry.value_ptr.files_with_parts_in_chunk.getOrPut(this.allocator, source_index.get()); _ = files; } } - js_chunks.sort(strings.StringArrayByIndexSorter.init(js_chunks.keys())); + js_chunks.sort(strings.StringArrayByIndexSorter.init(try temp_allocator.dupe(string, js_chunks.keys()))); var chunks: []Chunk = js_chunks.values(); @@ -1837,14 +1838,14 @@ const LinkerContext = struct { }) catch unreachable; } + // Traverse the graph using this stable order and linearize the files with + // dependencies before dependents pub fn visit(v: *@This(), source_index: Index.Int) void { if (source_index == Index.invalid.value) return; const visited_entry = v.visited.getOrPut(source_index) catch unreachable; if (visited_entry.found_existing) return; - var is_file_in_chunk = v.entry_bits.isSet( - source_index, - ); + var is_file_in_chunk = v.entry_bits.eql(&v.c.graph.files.items(.entry_bits)[source_index]); // Wrapped files can't be split because they are all inside the wrapper const can_be_split = v.flags[source_index].wrap == .none; @@ -1856,7 +1857,7 @@ const LinkerContext = struct { const records = v.import_records[source_index].slice(); - for (parts, 0..) |*part, part_index_| { + for (parts, 0..) |part, part_index_| { const part_index = @truncate(u32, part_index_); const is_part_in_this_chunk = is_file_in_chunk and part.is_live; @@ -3525,7 +3526,6 @@ const LinkerContext = struct { break :brk reserved_names; }, ); - { var sorted_imports_from_other_chunks: std.ArrayList(StableRef) = brk: { var list = std.ArrayList(StableRef).init(allocator); @@ -5117,10 +5117,9 @@ const LinkerContext = struct { .esm => { stmts.outside_wrapper_prefix.appendSlice(stmts.inside_wrapper_suffix.items) catch unreachable; }, - .cjs => { + else => { stmts.inside_wrapper_prefix.appendSlice(stmts.inside_wrapper_suffix.items) catch unreachable; }, - else => {}, } stmts.inside_wrapper_suffix.clearRetainingCapacity(); } @@ -5168,8 +5167,9 @@ const LinkerContext = struct { // evaluated (well, except for cyclic import scenarios). We need to preserve // these semantics even when modules imported via ES6 import statements end // up being CommonJS modules. - - stmts.all_stmts.appendSlice(stmts.inside_wrapper_prefix.items) catch unreachable; + stmts.all_stmts.ensureUnusedCapacity(stmts.outside_wrapper_prefix.items.len + stmts.inside_wrapper_suffix.items.len) catch unreachable; + stmts.all_stmts.appendSliceAssumeCapacity(stmts.inside_wrapper_prefix.items); + stmts.all_stmts.appendSliceAssumeCapacity(stmts.inside_wrapper_suffix.items); var out_stmts: []js_ast.Stmt = stmts.all_stmts.items; // Optionally wrap all statements in a closure @@ -5550,11 +5550,13 @@ const LinkerContext = struct { } const out_dist = distance + 1; + var bits = &file_entry_bits[source_index]; + // Don't mark this file more than once - if (file_entry_bits[source_index].isSet(entry_points_count) and !traverse_again) + if (bits.isSet(entry_points_count) and !traverse_again) return; - file_entry_bits[source_index].set(entry_points_count); + bits.set(entry_points_count); if (comptime bun.Environment.allow_assert) debug( @@ -5581,7 +5583,7 @@ const LinkerContext = struct { } } - for (parts[source_index].slice()) |*part| { + for (parts[source_index].slice()) |part| { for (part.dependencies.slice()) |dependency| { if (dependency.source_index.get() != source_index) { c.markFileReachableForCodeSplitting( @@ -5627,7 +5629,7 @@ const LinkerContext = struct { if (@as(usize, id) >= c.graph.ast.len) return; var _parts = parts[id].slice(); - for (_parts, 0..) |*part, part_index| { + for (_parts, 0..) |part, part_index| { var can_be_removed_if_unused = part.can_be_removed_if_unused; // Also include any statement-level imports @@ -5757,6 +5759,7 @@ const LinkerContext = struct { // Resolve the import by one step var advanced = c.advanceImportTracker(tracker); + advanced.tracker.* = advanced.value; const next_tracker = advanced.tracker.*; const status = advanced.status; const potentially_ambiguous_export_star_refs = advanced.import_data; @@ -5878,12 +5881,11 @@ const LinkerContext = struct { for (potentially_ambiguous_export_star_refs) |*ambiguous_tracker| { // If this is a re-export of another import, follow the import if (named_imports[ambiguous_tracker.data.source_index.get()].contains(ambiguous_tracker.data.import_ref)) { + c.cycle_detector.clearRetainingCapacity(); + c.swap_cycle_detector.clearRetainingCapacity(); - // TODO: not fully confident this will work - // test with nested ambiguous re-exports var old_cycle_detector = c.cycle_detector; c.cycle_detector = c.swap_cycle_detector; - c.cycle_detector.clearRetainingCapacity(); var ambig = c.matchImportWithExport(&ambiguous_tracker.data, re_exports); c.cycle_detector.clearRetainingCapacity(); c.swap_cycle_detector = c.cycle_detector; @@ -5920,7 +5922,7 @@ const LinkerContext = struct { re_exports.appendAssumeCapacity( .{ .part_index = dep, - .source_index = tracker.source_index, + .source_index = next_tracker.source_index, }, ); } diff --git a/src/js_parser.zig b/src/js_parser.zig index 3b563090709d..8fdbe001d6a3 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -2446,6 +2446,13 @@ pub const Parser = struct { var after = ListManaged(js_ast.Part).init(p.allocator); var parts = ListManaged(js_ast.Part).init(p.allocator); + if (p.options.bundle) { + // allocate an empty part for the bundle + before.append( + js_ast.Part{}, + ) catch unreachable; + } + if (!p.options.tree_shaking) { try p.appendPart(&parts, stmts); } else { @@ -18359,7 +18366,9 @@ fn NewParser_( } if (part.import_record_indices.len == 0) { - part.import_record_indices.update(p.import_records_for_current_part.clone(p.allocator) catch unreachable); + part.import_record_indices = @TypeOf(part.import_record_indices).init( + (p.import_records_for_current_part.clone(p.allocator) catch unreachable).items, + ); } else { part.import_record_indices.append(p.allocator, p.import_records_for_current_part.items) catch unreachable; } diff --git a/src/renamer.zig b/src/renamer.zig index daebf6c349eb..3bbac102755e 100644 --- a/src/renamer.zig +++ b/src/renamer.zig @@ -102,9 +102,11 @@ pub const NumberRenamer = struct { const name = scope.findUnusedName(r.allocator, symbol.original_name); - if (inner.cap < ref.innerIndex() + 1) { - inner.ensureUnusedCapacity(r.allocator, ref.innerIndex() + 1) catch unreachable; + const new_len = @max(inner.len, ref.innerIndex() + 1); + if (inner.cap < new_len) { + inner.ensureUnusedCapacity(r.allocator, new_len) catch unreachable; } + inner.len = new_len; inner.mut(ref.innerIndex()).* = name; } @@ -122,6 +124,10 @@ pub const NumberRenamer = struct { .names = try bun.BabyList(bun.BabyList(string)).initCapacity(allocator, symbols.symbols_for_source.len), }; renamer.root.name_counts = root_names; + renamer.names.len = symbols.symbols_for_source.len; + for (renamer.names.slice()) |*inner| { + inner.* = .{}; + } return renamer; } From 0194f8ea8009095983401c80cf034ce0219f122f Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Sun, 5 Mar 2023 04:07:18 -0800 Subject: [PATCH 033/255] It bundles! --- src/bundler/bundle_v2.zig | 70 +++++++++++++++------------ src/cli/bun_command.zig | 13 +++-- src/renamer.zig | 18 +++++-- test/fixtures/bundle/trivial/index.js | 1 - 4 files changed, 61 insertions(+), 41 deletions(-) diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 6d5db71db9c3..58e2fd9f632e 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -315,6 +315,13 @@ pub const BundleV2 = struct { } this.graph.parse_pending += 1; const source_index = Index.source(this.graph.input_files.len); + if (path.pretty.ptr == path.text.ptr) { + // TODO: outbase + const rel = bun.path.relative(this.bundler.fs.top_level_dir, path.text); + if (rel.len > 0 and rel[0] != '.') { + path.pretty = rel; + } + } path.* = try path.dupeAlloc(this.graph.allocator); entry.value_ptr.* = source_index.get(); this.graph.ast.append(this.graph.allocator, js_ast.Ast.empty) catch unreachable; @@ -884,6 +891,14 @@ const ParseTask = struct { continue; } + if (path.pretty.ptr == path.text.ptr) { + // TODO: outbase + const rel = bun.path.relative(bundler.fs.top_level_dir, path.text); + if (rel.len > 0 and rel[0] != '.') { + path.pretty = rel; + } + } + path.* = try path.dupeAlloc(allocator); import_record.path = path.*; debug("created ParseTask: {s}", .{path.text}); @@ -2240,7 +2255,7 @@ const LinkerContext = struct { for (reachable) |source_index_| { const source_index = source_index_.get(); const id = source_index; - if (id >= max_id) { + if (id > max_id) { continue; } @@ -2361,23 +2376,22 @@ const LinkerContext = struct { } var imports_to_bind = &imports_to_bind_list[id]; - var imports_to_bind_iter = imports_to_bind.iterator(); var parts: []js_ast.Part = parts_list[id].slice(); - while (imports_to_bind_iter.next()) |import| { - const import_source_index = import.value_ptr.data.source_index.get(); + for (imports_to_bind.keys(), imports_to_bind.values()) |*import_ref, import| { + const import_source_index = import.data.source_index.get(); const import_id = import_source_index; + const ref = import_ref.*; - const import_ref = import.key_ptr.*; - var named_import = named_imports[import_id].getPtr(import_ref) orelse continue; - const parts_declaring_symbol = this.topLevelSymbolsToParts(import_id, import_ref); + const named_import = named_imports[id].get(ref) orelse continue; for (named_import.local_parts_with_uses.slice()) |part_index| { var part: *js_ast.Part = &parts[part_index]; + const parts_declaring_symbol = this.graph.ast.items(.top_level_symbols_to_parts)[import_id].get(import.data.import_ref).?.slice(); part.dependencies.ensureUnusedCapacity( this.allocator, - parts_declaring_symbol.len + @as(usize, import.value_ptr.re_exports.len), + parts_declaring_symbol.len + @as(usize, import.re_exports.len), ) catch unreachable; // Depend on the file containing the imported symbol @@ -2392,11 +2406,11 @@ const LinkerContext = struct { // Also depend on any files that re-exported this symbol in between the // file containing the import and the file containing the imported symbol - part.dependencies.appendSliceAssumeCapacity(import.value_ptr.re_exports.slice()); + part.dependencies.appendSliceAssumeCapacity(import.re_exports.slice()); } // Merge these symbols so they will share the same name - _ = this.graph.symbols.merge(import_ref, import.value_ptr.data.import_ref); + import_ref.* = this.graph.symbols.merge(ref, import.data.import_ref); } // If this is an entry point, depend on all exports so they are included @@ -2987,7 +3001,7 @@ const LinkerContext = struct { for (other_parts) |other_part_index| { var local = local_dependencies.getOrPut(@intCast(u32, other_part_index)) catch unreachable; - if (!local.found_existing) { + if (!local.found_existing or other_part_index != part_index) { local.value_ptr.* = @intCast(u32, part_index); // note: if we crash on append, it is due to threadlocal heaps in mimalloc part.dependencies.push( @@ -3292,14 +3306,13 @@ const LinkerContext = struct { ); // Mark imported symbols as exported in the chunk from which they are declared - for (chunks, 0..) |*chunk, chunk_index| { + for (chunks, chunk_metas, 0..) |*chunk, *chunk_meta, chunk_index| { if (chunk.content != .javascript) { continue; } var js = &chunk.content.javascript; - var chunk_meta = &chunk_metas[chunk_index]; // Find all uses in this chunk of symbols from other chunks for (chunk_meta.imports.keys()) |import_ref| { const symbol = c.graph.symbols.get(import_ref).?; @@ -5127,7 +5140,8 @@ const LinkerContext = struct { // TODO: defaultLazyExport // Add all other parts in this chunk - for (parts, 0..) |part, index| { + for (parts, 0..) |part, index_| { + const index = part_range.part_index_begin + @truncate(u32, index_); if (!part.is_live) { // Skip the part if it's not in this chunk @@ -5916,13 +5930,13 @@ const LinkerContext = struct { // Depend on the statement(s) that declared this import symbol in the // original file { - var deps = c.graph.topLevelSymbolToParts(other_id, tracker.import_ref); + var deps = c.topLevelSymbolsToParts(other_id, tracker.import_ref); re_exports.ensureUnusedCapacity(deps.len) catch unreachable; for (deps) |dep| { re_exports.appendAssumeCapacity( .{ .part_index = dep, - .source_index = next_tracker.source_index, + .source_index = tracker.source_index, }, ); } @@ -5966,10 +5980,7 @@ const LinkerContext = struct { } pub fn topLevelSymbolsToParts(c: *LinkerContext, id: u32, ref: Ref) []u32 { - return if (c.graph.ast.items(.top_level_symbols_to_parts)[id].get(ref)) |list| - list.slice() - else - &[_]u32{}; + return c.graph.topLevelSymbolToParts(id, ref); } pub fn topLevelSymbolsToPartsForRuntime(c: *LinkerContext, ref: Ref) []u32 { @@ -6004,8 +6015,8 @@ const LinkerContext = struct { // generate a dummy part that depends on the "__commonJS" symbol var dependencies = c.allocator.alloc(js_ast.Dependency, common_js_parts.len) catch unreachable; - for (common_js_parts, 0..) |part, i| { - dependencies[i] = .{ + for (common_js_parts, dependencies) |part, *cjs| { + cjs.* = .{ .part_index = part, .source_index = Index.runtime, }; @@ -6057,8 +6068,8 @@ const LinkerContext = struct { // generate a dummy part that depends on the "__esm" symbol var dependencies = c.allocator.alloc(js_ast.Dependency, esm_parts.len) catch unreachable; - for (esm_parts, 0..) |part, i| { - dependencies[i] = .{ + for (esm_parts, dependencies) |part, *esm| { + esm.* = .{ .part_index = part, .source_index = Index.runtime, }; @@ -6246,12 +6257,10 @@ const LinkerContext = struct { .imports = &named_imports, }; named_imports.sort(sorter); - var iter = named_imports.iterator(); - while (iter.next()) |entry| { + for (named_imports.keys(), named_imports.values()) |ref, named_import| { // Re-use memory for the cycle detector c.cycle_detector.clearRetainingCapacity(); - const ref = entry.key_ptr.*; const import_ref = Ref{ .inner_index = ref.innerIndex(), @@ -6311,14 +6320,14 @@ const LinkerContext = struct { }, .cycle => { const source = &c.parse_graph.input_files.items(.source)[source_index]; - const r = lex.rangeOfIdentifier(source, entry.value_ptr.alias_loc orelse Logger.Loc{}); + const r = lex.rangeOfIdentifier(source, named_import.alias_loc orelse Logger.Loc{}); c.log.addRangeErrorFmt( source, r, c.allocator, "Detected cycle while resolving import {s}", .{ - entry.value_ptr.alias.?, + named_import.alias.?, }, ) catch unreachable; }, @@ -6330,10 +6339,9 @@ const LinkerContext = struct { ) catch unreachable; }, .ambiguous => { - var named_import = entry.value_ptr.*; const source = &c.parse_graph.input_files.items(.source)[source_index]; - const r = lex.rangeOfIdentifier(source, entry.value_ptr.alias_loc orelse Logger.Loc{}); + const r = lex.rangeOfIdentifier(source, named_import.alias_loc orelse Logger.Loc{}); // if (result.name_loc.start != 0) // TODO: better error diff --git a/src/cli/bun_command.zig b/src/cli/bun_command.zig index 07ef9e1f68d9..dff3836126b7 100644 --- a/src/cli/bun_command.zig +++ b/src/cli/bun_command.zig @@ -195,7 +195,7 @@ pub const BunCommand = struct { var writer = Output.errorWriter(); const root_path = ctx.args.output_dir orelse "out"; - const root_dir = try std.fs.cwd().openIterableDir(root_path, .{}); + const root_dir = try std.fs.cwd().makeOpenPathIterable(root_path, .{}); var all_paths = try ctx.allocator.alloc([]const u8, output_files.items.len); var max_path_len: usize = 0; for (all_paths, output_files.items) |*dest, src| { @@ -225,10 +225,13 @@ pub const BunCommand = struct { switch (f.value) { // easy mode: write the buffer .buffer => |value| { - rel_path = resolve_path.relative(from_path, f.input.text); - if (std.fs.path.dirname(rel_path)) |parent| { - if (parent.len > root_path.len) { - try root_dir.dir.makePath(parent); + rel_path = f.input.text; + if (f.input.text.len > from_path.len) { + rel_path = resolve_path.relative(from_path, f.input.text); + if (std.fs.path.dirname(rel_path)) |parent| { + if (parent.len > root_path.len) { + try root_dir.dir.makePath(parent); + } } } try root_dir.dir.writeFile(rel_path, value); diff --git a/src/renamer.zig b/src/renamer.zig index 3bbac102755e..c9829f50b34d 100644 --- a/src/renamer.zig +++ b/src/renamer.zig @@ -104,7 +104,10 @@ pub const NumberRenamer = struct { const new_len = @max(inner.len, ref.innerIndex() + 1); if (inner.cap < new_len) { + const prev_cap = inner.cap; inner.ensureUnusedCapacity(r.allocator, new_len) catch unreachable; + const to_write = inner.ptr[prev_cap..inner.cap]; + @memset(std.mem.sliceAsBytes(to_write).ptr, 0, std.mem.sliceAsBytes(to_write).len); } inner.len = new_len; inner.mut(ref.innerIndex()).* = name; @@ -219,10 +222,17 @@ pub const NumberRenamer = struct { const resolved = renamer.symbols.follow(ref); - return renamer - .names - .at(resolved.sourceIndex()) - .at(resolved.innerIndex()).*; + const renamed_list = renamer.names + .at(resolved.sourceIndex()); + + if (renamed_list.len > resolved.innerIndex()) { + const renamed = renamed_list.at(resolved.innerIndex()).*; + if (renamed.len > 0) { + return renamed; + } + } + + return renamer.symbols.getConst(resolved).?.original_name; } pub const NumberScope = struct { diff --git a/test/fixtures/bundle/trivial/index.js b/test/fixtures/bundle/trivial/index.js index dde7300980e5..a6b6eb0ce997 100644 --- a/test/fixtures/bundle/trivial/index.js +++ b/test/fixtures/bundle/trivial/index.js @@ -1,4 +1,3 @@ import { fn } from "./fn"; console.log(fn(42)); -globalThis.fn = fn; From f527cbabeb7c7586fb3d1ff493e5dc6a6078ce62 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Mon, 6 Mar 2023 15:31:29 -0800 Subject: [PATCH 034/255] Rename `Bun.Transpiler` to `Bun.Bundler` --- packages/bun-types/bun.d.ts | 11 +- ...spiler.classes.ts => JSBundler.classes.ts} | 4 +- .../api/{transpiler.zig => JSBundler.zig} | 2 +- src/bun.js/api/bun.zig | 11 +- src/bun.js/api/ffi.zig | 1 - src/bun.js/api/server.zig | 1 - src/bun.js/base.zig | 1 - ...ugin-impl.h => JSBundler+BunPlugin-impl.h} | 0 ...igGeneratedClasses+DOMClientIsoSubspaces.h | 5 +- .../ZigGeneratedClasses+DOMIsoSubspaces.h | 5 +- .../ZigGeneratedClasses+lazyStructureHeader.h | 14 +- .../ZigGeneratedClasses+lazyStructureImpl.h | 14 +- src/bun.js/bindings/ZigGeneratedClasses.cpp | 646 +++++++++--------- src/bun.js/bindings/ZigGeneratedClasses.h | 106 +-- src/bun.js/bindings/generated_classes.zig | 162 ++--- .../bindings/generated_classes_list.zig | 2 +- src/bun.js/event_loop.zig | 2 +- src/bun.js/javascript.zig | 1 - src/bun.js/module_loader.zig | 1 - src/jsc.zig | 2 +- test/bun.js/transpiler.test.js | 28 +- 21 files changed, 511 insertions(+), 508 deletions(-) rename src/bun.js/api/{transpiler.classes.ts => JSBundler.classes.ts} (91%) rename src/bun.js/api/{transpiler.zig => JSBundler.zig} (99%) rename src/bun.js/bindings/{JSTranspiler+BunPlugin-impl.h => JSBundler+BunPlugin-impl.h} (100%) diff --git a/packages/bun-types/bun.d.ts b/packages/bun-types/bun.d.ts index 3dffc21b2ac3..5a054f576672 100644 --- a/packages/bun-types/bun.d.ts +++ b/packages/bun-types/bun.d.ts @@ -836,7 +836,7 @@ declare module "bun" { * */ - export class Transpiler { + export class Bundler { constructor(options?: TranspilerOptions); /** @@ -907,6 +907,13 @@ declare module "bun" { scanImports(code: StringOrBuffer): Import[]; } + /** + * Bun.Transpiler was renamed to Bun.Bundler in Bun v0.6 + * + * See {@link Bun.Bundler} + */ + export const Transpiler: typeof Bundler; + export interface Import { path: string; @@ -2676,7 +2683,7 @@ declare module "bun" { */ builder: PluginBuilder, ): void | Promise; - }): ReturnType; + }): ReturnType<(typeof options)["setup"]>; /** * Deactivate all plugins diff --git a/src/bun.js/api/transpiler.classes.ts b/src/bun.js/api/JSBundler.classes.ts similarity index 91% rename from src/bun.js/api/transpiler.classes.ts rename to src/bun.js/api/JSBundler.classes.ts index f016a184480e..7472304c9e48 100644 --- a/src/bun.js/api/transpiler.classes.ts +++ b/src/bun.js/api/JSBundler.classes.ts @@ -2,7 +2,7 @@ import { define } from "../scripts/class-definitions"; export default [ define({ - name: "Transpiler", + name: "Bundler", construct: true, finalize: true, hasPendingActivity: false, @@ -30,7 +30,7 @@ export default [ custom: { onLoadPlugins: { extraHeaderIncludes: ["BunPlugin.h"], - impl: "JSTranspiler+BunPlugin-impl.h", + impl: "JSBundler+BunPlugin-impl.h", type: `WTF::Vector>`, }, onResolvePlugins: { diff --git a/src/bun.js/api/transpiler.zig b/src/bun.js/api/JSBundler.zig similarity index 99% rename from src/bun.js/api/transpiler.zig rename to src/bun.js/api/JSBundler.zig index 52faf8cae80c..b99d0415eae7 100644 --- a/src/bun.js/api/transpiler.zig +++ b/src/bun.js/api/JSBundler.zig @@ -43,7 +43,7 @@ const Runtime = @import("../../runtime.zig").Runtime; const JSLexer = bun.js_lexer; const Expr = JSAst.Expr; -pub usingnamespace JSC.Codegen.JSTranspiler; +pub usingnamespace JSC.Codegen.JSBundler; bundler: Bundler.Bundler, arena: std.heap.ArenaAllocator, diff --git a/src/bun.js/api/bun.zig b/src/bun.js/api/bun.zig index 5dc3abcf9394..a72be36d43eb 100644 --- a/src/bun.js/api/bun.zig +++ b/src/bun.js/api/bun.zig @@ -71,7 +71,7 @@ const VM = @import("bun").JSC.VM; const JSFunction = @import("bun").JSC.JSFunction; const Config = @import("../config.zig"); const URL = @import("../../url.zig").URL; -const Transpiler = @import("./transpiler.zig"); +const Transpiler = bun.JSC.API.JSBundler; const VirtualMachine = JSC.VirtualMachine; const IOTask = JSC.IOTask; const zlib = @import("../../zlib.zig"); @@ -1317,7 +1317,10 @@ pub const Class = NewClass( .get = enableANSIColors, }, .Transpiler = .{ - .get = getTranspilerConstructor, + .get = getBundlerConstructor, + }, + .Bundler = .{ + .get = getBundlerConstructor, }, .hash = .{ .get = getHashObject, @@ -2339,14 +2342,14 @@ pub fn mmapFile( }.x, @intToPtr(?*anyopaque, map.len), exception); } -pub fn getTranspilerConstructor( +pub fn getBundlerConstructor( _: void, ctx: js.JSContextRef, _: js.JSValueRef, _: js.JSStringRef, _: js.ExceptionRef, ) js.JSValueRef { - return JSC.API.Bun.Transpiler.getConstructor(ctx).asObjectRef(); + return JSC.API.Bundler.getConstructor(ctx).asObjectRef(); } pub fn getFileSystemRouter( diff --git a/src/bun.js/api/ffi.zig b/src/bun.js/api/ffi.zig index 89192dc85d86..6e2ad424244f 100644 --- a/src/bun.js/api/ffi.zig +++ b/src/bun.js/api/ffi.zig @@ -71,7 +71,6 @@ const VM = @import("bun").JSC.VM; const JSFunction = @import("bun").JSC.JSFunction; const Config = @import("../config.zig"); const URL = @import("../../url.zig").URL; -const Transpiler = @import("./transpiler.zig"); const VirtualMachine = JSC.VirtualMachine; const IOTask = JSC.IOTask; const ComptimeStringMap = @import("../../comptime_string_map.zig").ComptimeStringMap; diff --git a/src/bun.js/api/server.zig b/src/bun.js/api/server.zig index bb2f251a2a72..5da1b360f54b 100644 --- a/src/bun.js/api/server.zig +++ b/src/bun.js/api/server.zig @@ -70,7 +70,6 @@ const VM = @import("bun").JSC.VM; const JSFunction = @import("bun").JSC.JSFunction; const Config = @import("../config.zig"); const URL = @import("../../url.zig").URL; -const Transpiler = @import("./transpiler.zig"); const VirtualMachine = JSC.VirtualMachine; const IOTask = JSC.IOTask; const is_bindgen = JSC.is_bindgen; diff --git a/src/bun.js/base.zig b/src/bun.js/base.zig index 044a0430686e..c8c794cc784e 100644 --- a/src/bun.js/base.zig +++ b/src/bun.js/base.zig @@ -2163,7 +2163,6 @@ const Expect = Test.Expect; const DescribeScope = Test.DescribeScope; const TestScope = Test.TestScope; const NodeFS = JSC.Node.NodeFS; -const Transpiler = @import("./api/transpiler.zig"); const TextEncoder = WebCore.TextEncoder; const TextDecoder = WebCore.TextDecoder; const HTMLRewriter = JSC.Cloudflare.HTMLRewriter; diff --git a/src/bun.js/bindings/JSTranspiler+BunPlugin-impl.h b/src/bun.js/bindings/JSBundler+BunPlugin-impl.h similarity index 100% rename from src/bun.js/bindings/JSTranspiler+BunPlugin-impl.h rename to src/bun.js/bindings/JSBundler+BunPlugin-impl.h diff --git a/src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h b/src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h index d642ae40f837..a48a54cdc397 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h +++ b/src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h @@ -1,5 +1,6 @@ std::unique_ptr m_clientSubspaceForBlob; -std::unique_ptr m_clientSubspaceForBlobConstructor;std::unique_ptr m_clientSubspaceForCryptoHasher; +std::unique_ptr m_clientSubspaceForBlobConstructor;std::unique_ptr m_clientSubspaceForBundler; +std::unique_ptr m_clientSubspaceForBundlerConstructor;std::unique_ptr m_clientSubspaceForCryptoHasher; std::unique_ptr m_clientSubspaceForCryptoHasherConstructor;std::unique_ptr m_clientSubspaceForDirent; std::unique_ptr m_clientSubspaceForDirentConstructor;std::unique_ptr m_clientSubspaceForExpect; std::unique_ptr m_clientSubspaceForExpectConstructor;std::unique_ptr m_clientSubspaceForFileSystemRouter; @@ -23,5 +24,3 @@ std::unique_ptr m_clientSubspaceForTCPSocket; std::unique_ptr m_clientSubspaceForTLSSocket; std::unique_ptr m_clientSubspaceForTextDecoder; std::unique_ptr m_clientSubspaceForTextDecoderConstructor;std::unique_ptr m_clientSubspaceForTimeout; -std::unique_ptr m_clientSubspaceForTranspiler; -std::unique_ptr m_clientSubspaceForTranspilerConstructor; \ No newline at end of file diff --git a/src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h b/src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h index 93cb9bf5ed57..022e406b22d4 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h +++ b/src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h @@ -1,5 +1,6 @@ std::unique_ptr m_subspaceForBlob; -std::unique_ptr m_subspaceForBlobConstructor;std::unique_ptr m_subspaceForCryptoHasher; +std::unique_ptr m_subspaceForBlobConstructor;std::unique_ptr m_subspaceForBundler; +std::unique_ptr m_subspaceForBundlerConstructor;std::unique_ptr m_subspaceForCryptoHasher; std::unique_ptr m_subspaceForCryptoHasherConstructor;std::unique_ptr m_subspaceForDirent; std::unique_ptr m_subspaceForDirentConstructor;std::unique_ptr m_subspaceForExpect; std::unique_ptr m_subspaceForExpectConstructor;std::unique_ptr m_subspaceForFileSystemRouter; @@ -23,5 +24,3 @@ std::unique_ptr m_subspaceForTCPSocket; std::unique_ptr m_subspaceForTLSSocket; std::unique_ptr m_subspaceForTextDecoder; std::unique_ptr m_subspaceForTextDecoderConstructor;std::unique_ptr m_subspaceForTimeout; -std::unique_ptr m_subspaceForTranspiler; -std::unique_ptr m_subspaceForTranspilerConstructor; \ No newline at end of file diff --git a/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h b/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h index ab75a99497ce..b76eee83de0f 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h +++ b/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h @@ -4,6 +4,12 @@ JSC::Structure* JSBlobStructure() { return m_JSBlob.getInitializedOnMainThread(t JSC::LazyClassStructure m_JSBlob; bool hasJSBlobSetterValue { false }; mutable JSC::WriteBarrier m_JSBlobSetterValue; +JSC::Structure* JSBundlerStructure() { return m_JSBundler.getInitializedOnMainThread(this); } + JSC::JSObject* JSBundlerConstructor() { return m_JSBundler.constructorInitializedOnMainThread(this); } + JSC::JSValue JSBundlerPrototype() { return m_JSBundler.prototypeInitializedOnMainThread(this); } + JSC::LazyClassStructure m_JSBundler; + bool hasJSBundlerSetterValue { false }; + mutable JSC::WriteBarrier m_JSBundlerSetterValue; JSC::Structure* JSCryptoHasherStructure() { return m_JSCryptoHasher.getInitializedOnMainThread(this); } JSC::JSObject* JSCryptoHasherConstructor() { return m_JSCryptoHasher.constructorInitializedOnMainThread(this); } JSC::JSValue JSCryptoHasherPrototype() { return m_JSCryptoHasher.prototypeInitializedOnMainThread(this); } @@ -147,10 +153,4 @@ JSC::Structure* JSTimeoutStructure() { return m_JSTimeout.getInitializedOnMainTh JSC::JSValue JSTimeoutPrototype() { return m_JSTimeout.prototypeInitializedOnMainThread(this); } JSC::LazyClassStructure m_JSTimeout; bool hasJSTimeoutSetterValue { false }; - mutable JSC::WriteBarrier m_JSTimeoutSetterValue; -JSC::Structure* JSTranspilerStructure() { return m_JSTranspiler.getInitializedOnMainThread(this); } - JSC::JSObject* JSTranspilerConstructor() { return m_JSTranspiler.constructorInitializedOnMainThread(this); } - JSC::JSValue JSTranspilerPrototype() { return m_JSTranspiler.prototypeInitializedOnMainThread(this); } - JSC::LazyClassStructure m_JSTranspiler; - bool hasJSTranspilerSetterValue { false }; - mutable JSC::WriteBarrier m_JSTranspilerSetterValue; \ No newline at end of file + mutable JSC::WriteBarrier m_JSTimeoutSetterValue; \ No newline at end of file diff --git a/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h b/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h index aed941a2e5b9..df8715d30363 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h +++ b/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h @@ -5,6 +5,12 @@ void GlobalObject::initGeneratedLazyClasses() { init.setStructure(WebCore::JSBlob::createStructure(init.vm, init.global, init.prototype)); init.setConstructor(WebCore::JSBlob::createConstructor(init.vm, init.global, init.prototype)); }); + m_JSBundler.initLater( + [](LazyClassStructure::Initializer& init) { + init.setPrototype(WebCore::JSBundler::createPrototype(init.vm, reinterpret_cast(init.global))); + init.setStructure(WebCore::JSBundler::createStructure(init.vm, init.global, init.prototype)); + init.setConstructor(WebCore::JSBundler::createConstructor(init.vm, init.global, init.prototype)); + }); m_JSCryptoHasher.initLater( [](LazyClassStructure::Initializer& init) { init.setPrototype(WebCore::JSCryptoHasher::createPrototype(init.vm, reinterpret_cast(init.global))); @@ -149,17 +155,12 @@ void GlobalObject::initGeneratedLazyClasses() { init.setStructure(WebCore::JSTimeout::createStructure(init.vm, init.global, init.prototype)); }); - m_JSTranspiler.initLater( - [](LazyClassStructure::Initializer& init) { - init.setPrototype(WebCore::JSTranspiler::createPrototype(init.vm, reinterpret_cast(init.global))); - init.setStructure(WebCore::JSTranspiler::createStructure(init.vm, init.global, init.prototype)); - init.setConstructor(WebCore::JSTranspiler::createConstructor(init.vm, init.global, init.prototype)); - }); } template void GlobalObject::visitGeneratedLazyClasses(GlobalObject *thisObject, Visitor& visitor) { thisObject->m_JSBlob.visit(visitor); visitor.append(thisObject->m_JSBlobSetterValue); + thisObject->m_JSBundler.visit(visitor); visitor.append(thisObject->m_JSBundlerSetterValue); thisObject->m_JSCryptoHasher.visit(visitor); visitor.append(thisObject->m_JSCryptoHasherSetterValue); thisObject->m_JSDirent.visit(visitor); visitor.append(thisObject->m_JSDirentSetterValue); thisObject->m_JSExpect.visit(visitor); visitor.append(thisObject->m_JSExpectSetterValue); @@ -184,5 +185,4 @@ void GlobalObject::visitGeneratedLazyClasses(GlobalObject *thisObject, Visitor& thisObject->m_JSTLSSocket.visit(visitor); visitor.append(thisObject->m_JSTLSSocketSetterValue); thisObject->m_JSTextDecoder.visit(visitor); visitor.append(thisObject->m_JSTextDecoderSetterValue); thisObject->m_JSTimeout.visit(visitor); visitor.append(thisObject->m_JSTimeoutSetterValue); - thisObject->m_JSTranspiler.visit(visitor); visitor.append(thisObject->m_JSTranspilerSetterValue); } \ No newline at end of file diff --git a/src/bun.js/bindings/ZigGeneratedClasses.cpp b/src/bun.js/bindings/ZigGeneratedClasses.cpp index 716f683e5ce7..a17be9444b56 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses.cpp +++ b/src/bun.js/bindings/ZigGeneratedClasses.cpp @@ -455,6 +455,329 @@ extern "C" EncodedJSValue Blob__create(Zig::GlobalObject* globalObject, void* pt return JSValue::encode(instance); } +class JSBundlerPrototype final : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + + static JSBundlerPrototype* create(JSC::VM& vm, JSGlobalObject* globalObject, JSC::Structure* structure) + { + JSBundlerPrototype* ptr = new (NotNull, JSC::allocateCell(vm)) JSBundlerPrototype(vm, globalObject, structure); + ptr->finishCreation(vm, globalObject); + return ptr; + } + + DECLARE_INFO; + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + return &vm.plainObjectSpace(); + } + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + } + +private: + JSBundlerPrototype(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(JSC::VM&, JSC::JSGlobalObject*); +}; + +class JSBundlerConstructor final : public JSC::InternalFunction { +public: + using Base = JSC::InternalFunction; + static JSBundlerConstructor* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSBundlerPrototype* prototype); + + static constexpr unsigned StructureFlags = Base::StructureFlags; + static constexpr bool needsDestruction = false; + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, StructureFlags), info()); + } + + template static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return WebCore::subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForBundlerConstructor.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForBundlerConstructor = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForBundlerConstructor.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForBundlerConstructor = std::forward(space); }); + } + + void initializeProperties(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSBundlerPrototype* prototype); + + // Must be defined for each specialization class. + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSC::JSGlobalObject*, JSC::CallFrame*); + + DECLARE_EXPORT_INFO; + +private: + JSBundlerConstructor(JSC::VM& vm, JSC::Structure* structure); + void finishCreation(JSC::VM&, JSC::JSGlobalObject* globalObject, JSBundlerPrototype* prototype); +}; + +extern "C" void* BundlerClass__construct(JSC::JSGlobalObject*, JSC::CallFrame*); +JSC_DECLARE_CUSTOM_GETTER(jsBundlerConstructor); +extern "C" void BundlerClass__finalize(void*); + +extern "C" EncodedJSValue BundlerPrototype__scan(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(BundlerPrototype__scanCallback); + +extern "C" EncodedJSValue BundlerPrototype__scanImports(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(BundlerPrototype__scanImportsCallback); + +extern "C" EncodedJSValue BundlerPrototype__transform(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(BundlerPrototype__transformCallback); + +extern "C" EncodedJSValue BundlerPrototype__transformSync(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(BundlerPrototype__transformSyncCallback); + +STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSBundlerPrototype, JSBundlerPrototype::Base); + +static const HashTableValue JSBundlerPrototypeTableValues[] = { + { "scan"_s, static_cast(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, BundlerPrototype__scanCallback, 2 } }, + { "scanImports"_s, static_cast(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, BundlerPrototype__scanImportsCallback, 2 } }, + { "transform"_s, static_cast(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, BundlerPrototype__transformCallback, 2 } }, + { "transformSync"_s, static_cast(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, BundlerPrototype__transformSyncCallback, 2 } } +}; + +const ClassInfo JSBundlerPrototype::s_info = { "Bundler"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSBundlerPrototype) }; + +JSC_DEFINE_CUSTOM_GETTER(jsBundlerConstructor, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = JSC::getVM(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + auto* globalObject = reinterpret_cast(lexicalGlobalObject); + auto* prototype = jsDynamicCast(JSValue::decode(thisValue)); + + if (UNLIKELY(!prototype)) + return throwVMTypeError(lexicalGlobalObject, throwScope); + return JSValue::encode(globalObject->JSBundlerConstructor()); +} + +JSC_DEFINE_HOST_FUNCTION(BundlerPrototype__scanCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + auto& vm = lexicalGlobalObject->vm(); + + JSBundler* thisObject = jsDynamicCast(callFrame->thisValue()); + + if (UNLIKELY(!thisObject)) { + auto throwScope = DECLARE_THROW_SCOPE(vm); + return throwVMTypeError(lexicalGlobalObject, throwScope); + } + + JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject); + + return BundlerPrototype__scan(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + +JSC_DEFINE_HOST_FUNCTION(BundlerPrototype__scanImportsCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + auto& vm = lexicalGlobalObject->vm(); + + JSBundler* thisObject = jsDynamicCast(callFrame->thisValue()); + + if (UNLIKELY(!thisObject)) { + auto throwScope = DECLARE_THROW_SCOPE(vm); + return throwVMTypeError(lexicalGlobalObject, throwScope); + } + + JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject); + + return BundlerPrototype__scanImports(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + +JSC_DEFINE_HOST_FUNCTION(BundlerPrototype__transformCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + auto& vm = lexicalGlobalObject->vm(); + + JSBundler* thisObject = jsDynamicCast(callFrame->thisValue()); + + if (UNLIKELY(!thisObject)) { + auto throwScope = DECLARE_THROW_SCOPE(vm); + return throwVMTypeError(lexicalGlobalObject, throwScope); + } + + JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject); + + return BundlerPrototype__transform(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + +JSC_DEFINE_HOST_FUNCTION(BundlerPrototype__transformSyncCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + auto& vm = lexicalGlobalObject->vm(); + + JSBundler* thisObject = jsDynamicCast(callFrame->thisValue()); + + if (UNLIKELY(!thisObject)) { + auto throwScope = DECLARE_THROW_SCOPE(vm); + return throwVMTypeError(lexicalGlobalObject, throwScope); + } + + JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject); + + return BundlerPrototype__transformSync(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + +void JSBundlerPrototype::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject) +{ + Base::finishCreation(vm); + reifyStaticProperties(vm, JSBundler::info(), JSBundlerPrototypeTableValues, *this); + JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); +} + +void JSBundlerConstructor::finishCreation(VM& vm, JSC::JSGlobalObject* globalObject, JSBundlerPrototype* prototype) +{ + Base::finishCreation(vm, 0, "Bundler"_s, PropertyAdditionMode::WithoutStructureTransition); + + putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); + ASSERT(inherits(info())); +} + +JSBundlerConstructor::JSBundlerConstructor(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure, construct, construct) +{ +} + +JSBundlerConstructor* JSBundlerConstructor::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSBundlerPrototype* prototype) +{ + JSBundlerConstructor* ptr = new (NotNull, JSC::allocateCell(vm)) JSBundlerConstructor(vm, structure); + ptr->finishCreation(vm, globalObject, prototype); + return ptr; +} + +JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSBundlerConstructor::construct(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame) +{ + Zig::GlobalObject* globalObject = reinterpret_cast(lexicalGlobalObject); + JSC::VM& vm = globalObject->vm(); + JSObject* newTarget = asObject(callFrame->newTarget()); + auto* constructor = globalObject->JSBundlerConstructor(); + Structure* structure = globalObject->JSBundlerStructure(); + if (constructor != newTarget) { + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* functionGlobalObject = reinterpret_cast( + // ShadowRealm functions belong to a different global object. + getFunctionRealm(globalObject, newTarget)); + RETURN_IF_EXCEPTION(scope, {}); + structure = InternalFunction::createSubclassStructure( + globalObject, + newTarget, + functionGlobalObject->JSBundlerStructure()); + } + + void* ptr = BundlerClass__construct(globalObject, callFrame); + + if (UNLIKELY(!ptr)) { + return JSValue::encode(JSC::jsUndefined()); + } + + JSBundler* instance = JSBundler::create(vm, globalObject, structure, ptr); + + return JSValue::encode(instance); +} + +void JSBundlerConstructor::initializeProperties(VM& vm, JSC::JSGlobalObject* globalObject, JSBundlerPrototype* prototype) +{ +} + +const ClassInfo JSBundlerConstructor::s_info = { "Function"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSBundlerConstructor) }; + +extern "C" EncodedJSValue Bundler__getConstructor(Zig::GlobalObject* globalObject) +{ + return JSValue::encode(globalObject->JSBundlerConstructor()); +} + +#include "JSBundler+BunPlugin-impl.h"; + +JSBundler::~JSBundler() +{ + if (m_ctx) { + BundlerClass__finalize(m_ctx); + } +} +void JSBundler::destroy(JSCell* cell) +{ + static_cast(cell)->JSBundler::~JSBundler(); +} + +const ClassInfo JSBundler::s_info = { "Bundler"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSBundler) }; + +void JSBundler::finishCreation(VM& vm) +{ + Base::finishCreation(vm); + ASSERT(inherits(info())); +} + +JSBundler* JSBundler::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, void* ctx) +{ + JSBundler* ptr = new (NotNull, JSC::allocateCell(vm)) JSBundler(vm, structure, ctx); + ptr->finishCreation(vm); + return ptr; +} + +extern "C" void* Bundler__fromJS(JSC::EncodedJSValue value) +{ + JSC::JSValue decodedValue = JSC::JSValue::decode(value); + if (decodedValue.isEmpty() || !decodedValue.isCell()) + return nullptr; + + JSC::JSCell* cell = decodedValue.asCell(); + JSBundler* object = JSC::jsDynamicCast(cell); + + if (!object) + return nullptr; + + return object->wrapped(); +} + +extern "C" bool Bundler__dangerouslySetPtr(JSC::EncodedJSValue value, void* ptr) +{ + JSBundler* object = JSC::jsDynamicCast(JSValue::decode(value)); + if (!object) + return false; + + object->m_ctx = ptr; + return true; +} + +extern "C" const size_t Bundler__ptrOffset = JSBundler::offsetOfWrapped(); + +void JSBundler::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) +{ + auto* thisObject = jsCast(cell); + if (void* wrapped = thisObject->wrapped()) { + // if (thisObject->scriptExecutionContext()) + // analyzer.setLabelForCell(cell, "url " + thisObject->scriptExecutionContext()->url().string()); + } + Base::analyzeHeap(cell, analyzer); +} + +JSObject* JSBundler::createConstructor(VM& vm, JSGlobalObject* globalObject, JSValue prototype) +{ + return WebCore::JSBundlerConstructor::create(vm, globalObject, WebCore::JSBundlerConstructor::createStructure(vm, globalObject, globalObject->functionPrototype()), jsCast(prototype)); +} + +JSObject* JSBundler::createPrototype(VM& vm, JSDOMGlobalObject* globalObject) +{ + return JSBundlerPrototype::create(vm, globalObject, JSBundlerPrototype::createStructure(vm, globalObject, globalObject->objectPrototype())); +} + +extern "C" EncodedJSValue Bundler__create(Zig::GlobalObject* globalObject, void* ptr) +{ + auto& vm = globalObject->vm(); + JSC::Structure* structure = globalObject->JSBundlerStructure(); + JSBundler* instance = JSBundler::create(vm, globalObject, structure, ptr); + + return JSValue::encode(instance); +} class JSCryptoHasherPrototype final : public JSC::JSNonFinalObject { public: using Base = JSC::JSNonFinalObject; @@ -12868,328 +13191,5 @@ extern "C" EncodedJSValue Timeout__create(Zig::GlobalObject* globalObject, void* return JSValue::encode(instance); } -class JSTranspilerPrototype final : public JSC::JSNonFinalObject { -public: - using Base = JSC::JSNonFinalObject; - - static JSTranspilerPrototype* create(JSC::VM& vm, JSGlobalObject* globalObject, JSC::Structure* structure) - { - JSTranspilerPrototype* ptr = new (NotNull, JSC::allocateCell(vm)) JSTranspilerPrototype(vm, globalObject, structure); - ptr->finishCreation(vm, globalObject); - return ptr; - } - - DECLARE_INFO; - template - static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) - { - return &vm.plainObjectSpace(); - } - static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) - { - return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); - } - -private: - JSTranspilerPrototype(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) - : Base(vm, structure) - { - } - - void finishCreation(JSC::VM&, JSC::JSGlobalObject*); -}; - -class JSTranspilerConstructor final : public JSC::InternalFunction { -public: - using Base = JSC::InternalFunction; - static JSTranspilerConstructor* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSTranspilerPrototype* prototype); - - static constexpr unsigned StructureFlags = Base::StructureFlags; - static constexpr bool needsDestruction = false; - - static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) - { - return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, StructureFlags), info()); - } - - template static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) - { - if constexpr (mode == JSC::SubspaceAccess::Concurrently) - return nullptr; - return WebCore::subspaceForImpl( - vm, - [](auto& spaces) { return spaces.m_clientSubspaceForTranspilerConstructor.get(); }, - [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForTranspilerConstructor = std::forward(space); }, - [](auto& spaces) { return spaces.m_subspaceForTranspilerConstructor.get(); }, - [](auto& spaces, auto&& space) { spaces.m_subspaceForTranspilerConstructor = std::forward(space); }); - } - - void initializeProperties(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSTranspilerPrototype* prototype); - - // Must be defined for each specialization class. - static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSC::JSGlobalObject*, JSC::CallFrame*); - - DECLARE_EXPORT_INFO; - -private: - JSTranspilerConstructor(JSC::VM& vm, JSC::Structure* structure); - void finishCreation(JSC::VM&, JSC::JSGlobalObject* globalObject, JSTranspilerPrototype* prototype); -}; - -extern "C" void* TranspilerClass__construct(JSC::JSGlobalObject*, JSC::CallFrame*); -JSC_DECLARE_CUSTOM_GETTER(jsTranspilerConstructor); -extern "C" void TranspilerClass__finalize(void*); - -extern "C" EncodedJSValue TranspilerPrototype__scan(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); -JSC_DECLARE_HOST_FUNCTION(TranspilerPrototype__scanCallback); - -extern "C" EncodedJSValue TranspilerPrototype__scanImports(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); -JSC_DECLARE_HOST_FUNCTION(TranspilerPrototype__scanImportsCallback); - -extern "C" EncodedJSValue TranspilerPrototype__transform(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); -JSC_DECLARE_HOST_FUNCTION(TranspilerPrototype__transformCallback); - -extern "C" EncodedJSValue TranspilerPrototype__transformSync(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); -JSC_DECLARE_HOST_FUNCTION(TranspilerPrototype__transformSyncCallback); - -STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSTranspilerPrototype, JSTranspilerPrototype::Base); - -static const HashTableValue JSTranspilerPrototypeTableValues[] = { - { "scan"_s, static_cast(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, TranspilerPrototype__scanCallback, 2 } }, - { "scanImports"_s, static_cast(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, TranspilerPrototype__scanImportsCallback, 2 } }, - { "transform"_s, static_cast(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, TranspilerPrototype__transformCallback, 2 } }, - { "transformSync"_s, static_cast(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, TranspilerPrototype__transformSyncCallback, 2 } } -}; - -const ClassInfo JSTranspilerPrototype::s_info = { "Transpiler"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSTranspilerPrototype) }; - -JSC_DEFINE_CUSTOM_GETTER(jsTranspilerConstructor, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName)) -{ - VM& vm = JSC::getVM(lexicalGlobalObject); - auto throwScope = DECLARE_THROW_SCOPE(vm); - auto* globalObject = reinterpret_cast(lexicalGlobalObject); - auto* prototype = jsDynamicCast(JSValue::decode(thisValue)); - - if (UNLIKELY(!prototype)) - return throwVMTypeError(lexicalGlobalObject, throwScope); - return JSValue::encode(globalObject->JSTranspilerConstructor()); -} - -JSC_DEFINE_HOST_FUNCTION(TranspilerPrototype__scanCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) -{ - auto& vm = lexicalGlobalObject->vm(); - - JSTranspiler* thisObject = jsDynamicCast(callFrame->thisValue()); - - if (UNLIKELY(!thisObject)) { - auto throwScope = DECLARE_THROW_SCOPE(vm); - return throwVMTypeError(lexicalGlobalObject, throwScope); - } - - JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject); - - return TranspilerPrototype__scan(thisObject->wrapped(), lexicalGlobalObject, callFrame); -} - -JSC_DEFINE_HOST_FUNCTION(TranspilerPrototype__scanImportsCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) -{ - auto& vm = lexicalGlobalObject->vm(); - - JSTranspiler* thisObject = jsDynamicCast(callFrame->thisValue()); - - if (UNLIKELY(!thisObject)) { - auto throwScope = DECLARE_THROW_SCOPE(vm); - return throwVMTypeError(lexicalGlobalObject, throwScope); - } - - JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject); - - return TranspilerPrototype__scanImports(thisObject->wrapped(), lexicalGlobalObject, callFrame); -} - -JSC_DEFINE_HOST_FUNCTION(TranspilerPrototype__transformCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) -{ - auto& vm = lexicalGlobalObject->vm(); - - JSTranspiler* thisObject = jsDynamicCast(callFrame->thisValue()); - - if (UNLIKELY(!thisObject)) { - auto throwScope = DECLARE_THROW_SCOPE(vm); - return throwVMTypeError(lexicalGlobalObject, throwScope); - } - - JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject); - - return TranspilerPrototype__transform(thisObject->wrapped(), lexicalGlobalObject, callFrame); -} - -JSC_DEFINE_HOST_FUNCTION(TranspilerPrototype__transformSyncCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) -{ - auto& vm = lexicalGlobalObject->vm(); - - JSTranspiler* thisObject = jsDynamicCast(callFrame->thisValue()); - - if (UNLIKELY(!thisObject)) { - auto throwScope = DECLARE_THROW_SCOPE(vm); - return throwVMTypeError(lexicalGlobalObject, throwScope); - } - - JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject); - - return TranspilerPrototype__transformSync(thisObject->wrapped(), lexicalGlobalObject, callFrame); -} - -void JSTranspilerPrototype::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject) -{ - Base::finishCreation(vm); - reifyStaticProperties(vm, JSTranspiler::info(), JSTranspilerPrototypeTableValues, *this); - JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); -} - -void JSTranspilerConstructor::finishCreation(VM& vm, JSC::JSGlobalObject* globalObject, JSTranspilerPrototype* prototype) -{ - Base::finishCreation(vm, 0, "Transpiler"_s, PropertyAdditionMode::WithoutStructureTransition); - - putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); - ASSERT(inherits(info())); -} - -JSTranspilerConstructor::JSTranspilerConstructor(JSC::VM& vm, JSC::Structure* structure) - : Base(vm, structure, construct, construct) -{ -} - -JSTranspilerConstructor* JSTranspilerConstructor::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSTranspilerPrototype* prototype) -{ - JSTranspilerConstructor* ptr = new (NotNull, JSC::allocateCell(vm)) JSTranspilerConstructor(vm, structure); - ptr->finishCreation(vm, globalObject, prototype); - return ptr; -} - -JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSTranspilerConstructor::construct(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame) -{ - Zig::GlobalObject* globalObject = reinterpret_cast(lexicalGlobalObject); - JSC::VM& vm = globalObject->vm(); - JSObject* newTarget = asObject(callFrame->newTarget()); - auto* constructor = globalObject->JSTranspilerConstructor(); - Structure* structure = globalObject->JSTranspilerStructure(); - if (constructor != newTarget) { - auto scope = DECLARE_THROW_SCOPE(vm); - - auto* functionGlobalObject = reinterpret_cast( - // ShadowRealm functions belong to a different global object. - getFunctionRealm(globalObject, newTarget)); - RETURN_IF_EXCEPTION(scope, {}); - structure = InternalFunction::createSubclassStructure( - globalObject, - newTarget, - functionGlobalObject->JSTranspilerStructure()); - } - - void* ptr = TranspilerClass__construct(globalObject, callFrame); - - if (UNLIKELY(!ptr)) { - return JSValue::encode(JSC::jsUndefined()); - } - - JSTranspiler* instance = JSTranspiler::create(vm, globalObject, structure, ptr); - - return JSValue::encode(instance); -} - -void JSTranspilerConstructor::initializeProperties(VM& vm, JSC::JSGlobalObject* globalObject, JSTranspilerPrototype* prototype) -{ -} - -const ClassInfo JSTranspilerConstructor::s_info = { "Function"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSTranspilerConstructor) }; - -extern "C" EncodedJSValue Transpiler__getConstructor(Zig::GlobalObject* globalObject) -{ - return JSValue::encode(globalObject->JSTranspilerConstructor()); -} - -#include "JSTranspiler+BunPlugin-impl.h"; - -JSTranspiler::~JSTranspiler() -{ - if (m_ctx) { - TranspilerClass__finalize(m_ctx); - } -} -void JSTranspiler::destroy(JSCell* cell) -{ - static_cast(cell)->JSTranspiler::~JSTranspiler(); -} - -const ClassInfo JSTranspiler::s_info = { "Transpiler"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSTranspiler) }; - -void JSTranspiler::finishCreation(VM& vm) -{ - Base::finishCreation(vm); - ASSERT(inherits(info())); -} - -JSTranspiler* JSTranspiler::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, void* ctx) -{ - JSTranspiler* ptr = new (NotNull, JSC::allocateCell(vm)) JSTranspiler(vm, structure, ctx); - ptr->finishCreation(vm); - return ptr; -} - -extern "C" void* Transpiler__fromJS(JSC::EncodedJSValue value) -{ - JSC::JSValue decodedValue = JSC::JSValue::decode(value); - if (decodedValue.isEmpty() || !decodedValue.isCell()) - return nullptr; - - JSC::JSCell* cell = decodedValue.asCell(); - JSTranspiler* object = JSC::jsDynamicCast(cell); - - if (!object) - return nullptr; - - return object->wrapped(); -} - -extern "C" bool Transpiler__dangerouslySetPtr(JSC::EncodedJSValue value, void* ptr) -{ - JSTranspiler* object = JSC::jsDynamicCast(JSValue::decode(value)); - if (!object) - return false; - - object->m_ctx = ptr; - return true; -} - -extern "C" const size_t Transpiler__ptrOffset = JSTranspiler::offsetOfWrapped(); - -void JSTranspiler::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) -{ - auto* thisObject = jsCast(cell); - if (void* wrapped = thisObject->wrapped()) { - // if (thisObject->scriptExecutionContext()) - // analyzer.setLabelForCell(cell, "url " + thisObject->scriptExecutionContext()->url().string()); - } - Base::analyzeHeap(cell, analyzer); -} - -JSObject* JSTranspiler::createConstructor(VM& vm, JSGlobalObject* globalObject, JSValue prototype) -{ - return WebCore::JSTranspilerConstructor::create(vm, globalObject, WebCore::JSTranspilerConstructor::createStructure(vm, globalObject, globalObject->functionPrototype()), jsCast(prototype)); -} - -JSObject* JSTranspiler::createPrototype(VM& vm, JSDOMGlobalObject* globalObject) -{ - return JSTranspilerPrototype::create(vm, globalObject, JSTranspilerPrototype::createStructure(vm, globalObject, globalObject->objectPrototype())); -} - -extern "C" EncodedJSValue Transpiler__create(Zig::GlobalObject* globalObject, void* ptr) -{ - auto& vm = globalObject->vm(); - JSC::Structure* structure = globalObject->JSTranspilerStructure(); - JSTranspiler* instance = JSTranspiler::create(vm, globalObject, structure, ptr); - - return JSValue::encode(instance); -} } // namespace WebCore diff --git a/src/bun.js/bindings/ZigGeneratedClasses.h b/src/bun.js/bindings/ZigGeneratedClasses.h index acf1dc140708..5ec802544ad7 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses.h +++ b/src/bun.js/bindings/ZigGeneratedClasses.h @@ -66,6 +66,59 @@ class JSBlob final : public JSC::JSDestructibleObject { void finishCreation(JSC::VM&); }; +class JSBundler final : public JSC::JSDestructibleObject { +public: + using Base = JSC::JSDestructibleObject; + static JSBundler* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, void* ctx); + + DECLARE_EXPORT_INFO; + template static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return WebCore::subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForBundler.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForBundler = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForBundler.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForBundler = std::forward(space); }); + } + + static void destroy(JSC::JSCell*); + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(static_cast(0b11101110), StructureFlags), info()); + } + + static JSObject* createPrototype(VM& vm, JSDOMGlobalObject* globalObject); + static JSObject* createConstructor(VM& vm, JSGlobalObject* globalObject, JSValue prototype); + + ~JSBundler(); + + void* wrapped() const { return m_ctx; } + + void detach() + { + m_ctx = nullptr; + } + + static void analyzeHeap(JSCell*, JSC::HeapAnalyzer&); + static ptrdiff_t offsetOfWrapped() { return OBJECT_OFFSETOF(JSBundler, m_ctx); } + + void* m_ctx { nullptr }; + + JSBundler(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr) + : Base(vm, structure) + { + m_ctx = sinkPtr; + } + + void finishCreation(JSC::VM&); + + WTF::Vector> onLoadPlugins; + WTF::Vector> onResolvePlugins; +}; + class JSCryptoHasher final : public JSC::JSDestructibleObject { public: using Base = JSC::JSDestructibleObject; @@ -1455,57 +1508,4 @@ class JSTimeout final : public JSC::JSDestructibleObject { void finishCreation(JSC::VM&); }; -class JSTranspiler final : public JSC::JSDestructibleObject { -public: - using Base = JSC::JSDestructibleObject; - static JSTranspiler* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, void* ctx); - - DECLARE_EXPORT_INFO; - template static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) - { - if constexpr (mode == JSC::SubspaceAccess::Concurrently) - return nullptr; - return WebCore::subspaceForImpl( - vm, - [](auto& spaces) { return spaces.m_clientSubspaceForTranspiler.get(); }, - [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForTranspiler = std::forward(space); }, - [](auto& spaces) { return spaces.m_subspaceForTranspiler.get(); }, - [](auto& spaces, auto&& space) { spaces.m_subspaceForTranspiler = std::forward(space); }); - } - - static void destroy(JSC::JSCell*); - static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) - { - return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(static_cast(0b11101110), StructureFlags), info()); - } - - static JSObject* createPrototype(VM& vm, JSDOMGlobalObject* globalObject); - static JSObject* createConstructor(VM& vm, JSGlobalObject* globalObject, JSValue prototype); - - ~JSTranspiler(); - - void* wrapped() const { return m_ctx; } - - void detach() - { - m_ctx = nullptr; - } - - static void analyzeHeap(JSCell*, JSC::HeapAnalyzer&); - static ptrdiff_t offsetOfWrapped() { return OBJECT_OFFSETOF(JSTranspiler, m_ctx); } - - void* m_ctx { nullptr }; - - JSTranspiler(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr) - : Base(vm, structure) - { - m_ctx = sinkPtr; - } - - void finishCreation(JSC::VM&); - - WTF::Vector> onLoadPlugins; - WTF::Vector> onResolvePlugins; -}; - } diff --git a/src/bun.js/bindings/generated_classes.zig b/src/bun.js/bindings/generated_classes.zig index 015c740ec45e..1294438a7ae4 100644 --- a/src/bun.js/bindings/generated_classes.zig +++ b/src/bun.js/bindings/generated_classes.zig @@ -122,6 +122,86 @@ pub const JSBlob = struct { } } }; +pub const JSBundler = struct { + const Bundler = Classes.Bundler; + const GetterType = fn (*Bundler, *JSC.JSGlobalObject) callconv(.C) JSC.JSValue; + const GetterTypeWithThisValue = fn (*Bundler, JSC.JSValue, *JSC.JSGlobalObject) callconv(.C) JSC.JSValue; + const SetterType = fn (*Bundler, *JSC.JSGlobalObject, JSC.JSValue) callconv(.C) bool; + const SetterTypeWithThisValue = fn (*Bundler, JSC.JSValue, *JSC.JSGlobalObject, JSC.JSValue) callconv(.C) bool; + const CallbackType = fn (*Bundler, *JSC.JSGlobalObject, *JSC.CallFrame) callconv(.C) JSC.JSValue; + + /// Return the pointer to the wrapped object. + /// If the object does not match the type, return null. + pub fn fromJS(value: JSC.JSValue) ?*Bundler { + JSC.markBinding(@src()); + return Bundler__fromJS(value); + } + + /// Get the Bundler constructor value. + /// This loads lazily from the global object. + pub fn getConstructor(globalObject: *JSC.JSGlobalObject) JSC.JSValue { + JSC.markBinding(@src()); + return Bundler__getConstructor(globalObject); + } + + /// Create a new instance of Bundler + pub fn toJS(this: *Bundler, globalObject: *JSC.JSGlobalObject) JSC.JSValue { + JSC.markBinding(@src()); + if (comptime Environment.allow_assert) { + const value__ = Bundler__create(globalObject, this); + std.debug.assert(value__.as(Bundler).? == this); // If this fails, likely a C ABI issue. + return value__; + } else { + return Bundler__create(globalObject, this); + } + } + + /// Modify the internal ptr to point to a new instance of Bundler. + pub fn dangerouslySetPtr(value: JSC.JSValue, ptr: ?*Bundler) bool { + JSC.markBinding(@src()); + return Bundler__dangerouslySetPtr(value, ptr); + } + + /// Detach the ptr from the thisValue + pub fn detachPtr(_: *Bundler, value: JSC.JSValue) void { + JSC.markBinding(@src()); + std.debug.assert(Bundler__dangerouslySetPtr(value, null)); + } + + extern fn Bundler__fromJS(JSC.JSValue) ?*Bundler; + extern fn Bundler__getConstructor(*JSC.JSGlobalObject) JSC.JSValue; + + extern fn Bundler__create(globalObject: *JSC.JSGlobalObject, ptr: ?*Bundler) JSC.JSValue; + + extern fn Bundler__dangerouslySetPtr(JSC.JSValue, ?*Bundler) bool; + + comptime { + if (@TypeOf(Bundler.constructor) != (fn (*JSC.JSGlobalObject, *JSC.CallFrame) callconv(.C) ?*Bundler)) { + @compileLog("Bundler.constructor is not a constructor"); + } + + if (@TypeOf(Bundler.finalize) != (fn (*Bundler) callconv(.C) void)) { + @compileLog("Bundler.finalize is not a finalizer"); + } + + if (@TypeOf(Bundler.scan) != CallbackType) + @compileLog("Expected Bundler.scan to be a callback but received " ++ @typeName(@TypeOf(Bundler.scan))); + if (@TypeOf(Bundler.scanImports) != CallbackType) + @compileLog("Expected Bundler.scanImports to be a callback but received " ++ @typeName(@TypeOf(Bundler.scanImports))); + if (@TypeOf(Bundler.transform) != CallbackType) + @compileLog("Expected Bundler.transform to be a callback but received " ++ @typeName(@TypeOf(Bundler.transform))); + if (@TypeOf(Bundler.transformSync) != CallbackType) + @compileLog("Expected Bundler.transformSync to be a callback but received " ++ @typeName(@TypeOf(Bundler.transformSync))); + if (!JSC.is_bindgen) { + @export(Bundler.constructor, .{ .name = "BundlerClass__construct" }); + @export(Bundler.finalize, .{ .name = "BundlerClass__finalize" }); + @export(Bundler.scan, .{ .name = "BundlerPrototype__scan" }); + @export(Bundler.scanImports, .{ .name = "BundlerPrototype__scanImports" }); + @export(Bundler.transform, .{ .name = "BundlerPrototype__transform" }); + @export(Bundler.transformSync, .{ .name = "BundlerPrototype__transformSync" }); + } + } +}; pub const JSCryptoHasher = struct { const CryptoHasher = Classes.CryptoHasher; const GetterType = fn (*CryptoHasher, *JSC.JSGlobalObject) callconv(.C) JSC.JSValue; @@ -3679,89 +3759,10 @@ pub const JSTimeout = struct { } } }; -pub const JSTranspiler = struct { - const Transpiler = Classes.Transpiler; - const GetterType = fn (*Transpiler, *JSC.JSGlobalObject) callconv(.C) JSC.JSValue; - const GetterTypeWithThisValue = fn (*Transpiler, JSC.JSValue, *JSC.JSGlobalObject) callconv(.C) JSC.JSValue; - const SetterType = fn (*Transpiler, *JSC.JSGlobalObject, JSC.JSValue) callconv(.C) bool; - const SetterTypeWithThisValue = fn (*Transpiler, JSC.JSValue, *JSC.JSGlobalObject, JSC.JSValue) callconv(.C) bool; - const CallbackType = fn (*Transpiler, *JSC.JSGlobalObject, *JSC.CallFrame) callconv(.C) JSC.JSValue; - - /// Return the pointer to the wrapped object. - /// If the object does not match the type, return null. - pub fn fromJS(value: JSC.JSValue) ?*Transpiler { - JSC.markBinding(@src()); - return Transpiler__fromJS(value); - } - - /// Get the Transpiler constructor value. - /// This loads lazily from the global object. - pub fn getConstructor(globalObject: *JSC.JSGlobalObject) JSC.JSValue { - JSC.markBinding(@src()); - return Transpiler__getConstructor(globalObject); - } - - /// Create a new instance of Transpiler - pub fn toJS(this: *Transpiler, globalObject: *JSC.JSGlobalObject) JSC.JSValue { - JSC.markBinding(@src()); - if (comptime Environment.allow_assert) { - const value__ = Transpiler__create(globalObject, this); - std.debug.assert(value__.as(Transpiler).? == this); // If this fails, likely a C ABI issue. - return value__; - } else { - return Transpiler__create(globalObject, this); - } - } - - /// Modify the internal ptr to point to a new instance of Transpiler. - pub fn dangerouslySetPtr(value: JSC.JSValue, ptr: ?*Transpiler) bool { - JSC.markBinding(@src()); - return Transpiler__dangerouslySetPtr(value, ptr); - } - - /// Detach the ptr from the thisValue - pub fn detachPtr(_: *Transpiler, value: JSC.JSValue) void { - JSC.markBinding(@src()); - std.debug.assert(Transpiler__dangerouslySetPtr(value, null)); - } - - extern fn Transpiler__fromJS(JSC.JSValue) ?*Transpiler; - extern fn Transpiler__getConstructor(*JSC.JSGlobalObject) JSC.JSValue; - - extern fn Transpiler__create(globalObject: *JSC.JSGlobalObject, ptr: ?*Transpiler) JSC.JSValue; - - extern fn Transpiler__dangerouslySetPtr(JSC.JSValue, ?*Transpiler) bool; - - comptime { - if (@TypeOf(Transpiler.constructor) != (fn (*JSC.JSGlobalObject, *JSC.CallFrame) callconv(.C) ?*Transpiler)) { - @compileLog("Transpiler.constructor is not a constructor"); - } - - if (@TypeOf(Transpiler.finalize) != (fn (*Transpiler) callconv(.C) void)) { - @compileLog("Transpiler.finalize is not a finalizer"); - } - - if (@TypeOf(Transpiler.scan) != CallbackType) - @compileLog("Expected Transpiler.scan to be a callback but received " ++ @typeName(@TypeOf(Transpiler.scan))); - if (@TypeOf(Transpiler.scanImports) != CallbackType) - @compileLog("Expected Transpiler.scanImports to be a callback but received " ++ @typeName(@TypeOf(Transpiler.scanImports))); - if (@TypeOf(Transpiler.transform) != CallbackType) - @compileLog("Expected Transpiler.transform to be a callback but received " ++ @typeName(@TypeOf(Transpiler.transform))); - if (@TypeOf(Transpiler.transformSync) != CallbackType) - @compileLog("Expected Transpiler.transformSync to be a callback but received " ++ @typeName(@TypeOf(Transpiler.transformSync))); - if (!JSC.is_bindgen) { - @export(Transpiler.constructor, .{ .name = "TranspilerClass__construct" }); - @export(Transpiler.finalize, .{ .name = "TranspilerClass__finalize" }); - @export(Transpiler.scan, .{ .name = "TranspilerPrototype__scan" }); - @export(Transpiler.scanImports, .{ .name = "TranspilerPrototype__scanImports" }); - @export(Transpiler.transform, .{ .name = "TranspilerPrototype__transform" }); - @export(Transpiler.transformSync, .{ .name = "TranspilerPrototype__transformSync" }); - } - } -}; comptime { _ = JSBlob; + _ = JSBundler; _ = JSCryptoHasher; _ = JSDirent; _ = JSExpect; @@ -3786,5 +3787,4 @@ comptime { _ = JSTLSSocket; _ = JSTextDecoder; _ = JSTimeout; - _ = JSTranspiler; } diff --git a/src/bun.js/bindings/generated_classes_list.zig b/src/bun.js/bindings/generated_classes_list.zig index d779a4ff16a7..7ae572583c01 100644 --- a/src/bun.js/bindings/generated_classes_list.zig +++ b/src/bun.js/bindings/generated_classes_list.zig @@ -24,7 +24,7 @@ pub const Classes = struct { pub const MatchedRoute = JSC.API.MatchedRoute; pub const Dirent = JSC.Node.Dirent; pub const NodeJSFS = JSC.Node.NodeJSFS; - pub const Transpiler = JSC.API.Transpiler; + pub const Bundler = JSC.API.Bundler; pub const Stats = JSC.Node.Stats; pub const Timeout = JSC.API.Bun.Timer.TimerObject; }; diff --git a/src/bun.js/event_loop.zig b/src/bun.js/event_loop.zig index 772530a5ad4b..a85b512348d6 100644 --- a/src/bun.js/event_loop.zig +++ b/src/bun.js/event_loop.zig @@ -12,7 +12,7 @@ const Bun = JSC.API.Bun; const TaggedPointerUnion = @import("../tagged_pointer.zig").TaggedPointerUnion; const typeBaseName = @import("../meta.zig").typeBaseName; const CopyFilePromiseTask = WebCore.Blob.Store.CopyFile.CopyFilePromiseTask; -const AsyncTransformTask = @import("./api/transpiler.zig").TransformTask.AsyncTransformTask; +const AsyncTransformTask = JSC.API.Bundler.TransformTask.AsyncTransformTask; const ReadFileTask = WebCore.Blob.Store.ReadFile.ReadFileTask; const WriteFileTask = WebCore.Blob.Store.WriteFile.WriteFileTask; const napi_async_work = JSC.napi.napi_async_work; diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig index 445f30233fe1..46a52289478d 100644 --- a/src/bun.js/javascript.zig +++ b/src/bun.js/javascript.zig @@ -79,7 +79,6 @@ const VM = @import("bun").JSC.VM; const JSFunction = @import("bun").JSC.JSFunction; const Config = @import("./config.zig"); const URL = @import("../url.zig").URL; -const Transpiler = @import("./api/transpiler.zig"); const Bun = JSC.API.Bun; const EventLoop = JSC.EventLoop; const PendingResolution = @import("../resolver/resolver.zig").PendingResolution; diff --git a/src/bun.js/module_loader.zig b/src/bun.js/module_loader.zig index a62f19809c00..2024d40313ce 100644 --- a/src/bun.js/module_loader.zig +++ b/src/bun.js/module_loader.zig @@ -79,7 +79,6 @@ const VM = @import("bun").JSC.VM; const JSFunction = @import("bun").JSC.JSFunction; const Config = @import("./config.zig"); const URL = @import("../url.zig").URL; -const Transpiler = @import("./api/transpiler.zig"); const Bun = JSC.API.Bun; const EventLoop = JSC.EventLoop; const PendingResolution = @import("../resolver/resolver.zig").PendingResolution; diff --git a/src/jsc.zig b/src/jsc.zig index 2ac14cde07f9..4d008bf90eff 100644 --- a/src/jsc.zig +++ b/src/jsc.zig @@ -25,7 +25,7 @@ pub const Cloudflare = struct { }; pub const Jest = @import("./bun.js/test/jest.zig"); pub const API = struct { - pub const Transpiler = @import("./bun.js/api/transpiler.zig"); + pub const Bundler = @import("./bun.js/api/JSBundler.zig"); pub const Server = @import("./bun.js/api/server.zig").Server; pub const ServerConfig = @import("./bun.js/api/server.zig").ServerConfig; pub const ServerWebSocket = @import("./bun.js/api/server.zig").ServerWebSocket; diff --git a/test/bun.js/transpiler.test.js b/test/bun.js/transpiler.test.js index 352e0c2ec3d8..985b42d99bfd 100644 --- a/test/bun.js/transpiler.test.js +++ b/test/bun.js/transpiler.test.js @@ -1,7 +1,7 @@ import { expect, it, describe } from "bun:test"; -describe("Bun.Transpiler", () => { - const transpiler = new Bun.Transpiler({ +describe("Bun.Bundler", () => { + const transpiler = new Bun.Bundler({ loader: "tsx", define: { "process.env.NODE_ENV": JSON.stringify("development"), @@ -344,7 +344,7 @@ export default class { }); describe("exports.replace", () => { - const transpiler = new Bun.Transpiler({ + const transpiler = new Bun.Bundler({ exports: { replace: { // export var foo = function() { } @@ -453,7 +453,7 @@ export default class { }); }); - const bunTranspiler = new Bun.Transpiler({ + const bunTranspiler = new Bun.Bundler({ loader: "tsx", define: { "process.env.NODE_ENV": JSON.stringify("development"), @@ -505,7 +505,7 @@ export default class { `; it("jsxFactory (two level)", () => { - var bun = new Bun.Transpiler({ + var bun = new Bun.Bundler({ loader: "jsx", allowBunRuntime: false, tsconfig: JSON.stringify({ @@ -530,7 +530,7 @@ export default <>hi }); it("jsxFactory (one level)", () => { - var bun = new Bun.Transpiler({ + var bun = new Bun.Bundler({ loader: "jsx", allowBunRuntime: false, tsconfig: JSON.stringify({ @@ -554,7 +554,7 @@ export default <>hi }); it("JSX", () => { - var bun = new Bun.Transpiler({ + var bun = new Bun.Bundler({ loader: "jsx", define: { "process.env.NODE_ENV": JSON.stringify("development"), @@ -658,7 +658,7 @@ export default <>hi }); describe("inline JSX", () => { - const inliner = new Bun.Transpiler({ + const inliner = new Bun.Bundler({ loader: "tsx", define: { "process.env.NODE_ENV": JSON.stringify("production"), @@ -738,12 +738,12 @@ export var ComponentThatHasSpreadCausesDeopt = $jsx(Hello, { }); it("require with a dynamic non-string expression", () => { - var nodeTranspiler = new Bun.Transpiler({ platform: "node" }); + var nodeTranspiler = new Bun.Bundler({ platform: "node" }); expect(nodeTranspiler.transformSync("require('hi' + bar)")).toBe('require("hi" + bar);\n'); }); it("CommonJS", () => { - var nodeTranspiler = new Bun.Transpiler({ platform: "node" }); + var nodeTranspiler = new Bun.Bundler({ platform: "node" }); expect(nodeTranspiler.transformSync("module.require('hi' + 123)")).toBe('require("hi" + 123);\n'); expect(nodeTranspiler.transformSync("module.require(1 ? 'foo' : 'bar')")).toBe('require("foo");\n'); @@ -1146,7 +1146,7 @@ console.log(resolve.length) describe("Browsers", () => { it('require.resolve("my-module") -> "/resolved/my-module"', () => { - // the module resolver & linker doesn't run with Bun.Transpiler + // the module resolver & linker doesn't run with Bun.Bundler // so in this test, it becomes the same path string expectPrinted_(`export const foo = require.resolve('my-module')`, `export const foo = "my-module"`); }); @@ -1505,7 +1505,7 @@ class Foo { }); it("const inlining", () => { - var transpiler = new Bun.Transpiler({ + var transpiler = new Bun.Bundler({ inline: true, platform: "bun", allowBunRuntime: false, @@ -1598,7 +1598,7 @@ console.log(foo, array); }); it("constant folding scopes", () => { - var transpiler = new Bun.Transpiler({ + var transpiler = new Bun.Bundler({ inline: true, platform: "bun", allowBunRuntime: false, @@ -1646,7 +1646,7 @@ console.log(foo, array); }); it("substitution", () => { - var transpiler = new Bun.Transpiler({ + var transpiler = new Bun.Bundler({ inline: true, platform: "bun", allowBunRuntime: false, From 8e004c5f3d9f5bd1d3951785dbff238fdff5873d Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Mon, 6 Mar 2023 22:26:06 -0800 Subject: [PATCH 035/255] `import()` expressions almost work --- src/bundler/bundle_v2.zig | 106 ++++++++++++++------------ src/runtime.js | 5 ++ test/fixtures/bundle/trivial/index.js | 6 +- 3 files changed, 65 insertions(+), 52 deletions(-) diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 58e2fd9f632e..6e6e4455a673 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -837,6 +837,7 @@ const ParseTask = struct { opts.warn_about_unbundled_modules = false; opts.macro_context = &this.data.macro_context; opts.bundle = true; + opts.features.top_level_await = true; opts.features.auto_import_jsx = task.jsx.parse and bundler.options.auto_import_jsx; opts.features.trim_unused_imports = bundler.options.trim_unused_imports orelse loader.isTypeScript(); opts.tree_shaking = task.tree_shaking; @@ -1441,11 +1442,14 @@ const LinkerGraph = struct { var dependencies = &part.dependencies; const part_ids = g.topLevelSymbolToParts(id, ref); try dependencies.ensureUnusedCapacity(g.allocator, part_ids.len); - for (part_ids, 0..) |_, part_id| { - dependencies.appendAssumeCapacity(.{ + const old_len = dependencies.len; + dependencies.len += @truncate(u32, part_ids.len); + var new_dependencies = dependencies.slice()[old_len..]; + for (part_ids, new_dependencies) |part_id, *dependency| { + dependency.* = .{ .source_index = source_index_to_import_from, .part_index = @truncate(u32, part_id), - }); + }; } } @@ -1485,14 +1489,14 @@ const LinkerGraph = struct { @memset(output_was_auto_generated.ptr, 0, output_was_auto_generated.len); } - for (entry_points, 0..) |i, j| { + for (entry_points, path_strings, source_indices) |i, *path_string, *source_index| { const source = sources[i.get()]; if (comptime Environment.allow_assert) { std.debug.assert(source.index.get() == i.get()); } entry_point_kinds[source.index.get()] = EntryPoint.Kind.user_specified; - path_strings[j] = bun.PathString.init(source.path.text); - source_indices[j] = source.index.get(); + path_string.* = bun.PathString.init(source.path.text); + source_index.* = source.index.get(); } } @@ -1620,7 +1624,7 @@ const LinkerContext = struct { }; fn isExternalDynamicImport(this: *LinkerContext, record: *const ImportRecord, source_index: u32) bool { - return record.kind == .dynamic and this.graph.files.items(.entry_point_kind)[source_index].isEntryPoint() and record.source_index.get() != source_index; + return record.kind == .dynamic and this.graph.files.items(.entry_point_kind)[record.source_index.get()].isEntryPoint() and record.source_index.get() != source_index; } inline fn shouldCallRuntimeRequire(format: options.OutputFormat) bool { @@ -1644,6 +1648,11 @@ const LinkerContext = struct { try this.graph.load(entry_points, sources); this.wait_group.init(); this.ambiguous_result_pool = std.ArrayList(MatchImport).init(this.allocator); + + var runtime_named_exports = &this.graph.ast.items(.named_exports)[Index.runtime.get()]; + + this.esm_runtime_ref = runtime_named_exports.get("__esm").?.ref; + this.cjs_runtime_ref = runtime_named_exports.get("__commonJS").?.ref; } pub noinline fn link( @@ -2318,10 +2327,10 @@ const LinkerContext = struct { if (is_entry_point and this.options.output_format == .esm) { var copies = this.allocator.alloc(Ref, aliases.len) catch unreachable; - for (aliases, 0..) |alias, i| { + for (aliases, copies) |alias, *copy| { const original_name = bufPrint(buf, "export_{}", .{strings.fmtIdentifier(alias)}) catch unreachable; buf = buf[original_name.len..]; - copies[i] = this.graph.generateNewSymbol(source_index, .other, original_name); + copy.* = this.graph.generateNewSymbol(source_index, .other, original_name); } this.graph.meta.items(.cjs_export_copies)[id] = copies; } @@ -2387,7 +2396,7 @@ const LinkerContext = struct { for (named_import.local_parts_with_uses.slice()) |part_index| { var part: *js_ast.Part = &parts[part_index]; - const parts_declaring_symbol = this.graph.ast.items(.top_level_symbols_to_parts)[import_id].get(import.data.import_ref).?.slice(); + const parts_declaring_symbol = this.topLevelSymbolsToParts(import_id, import.data.import_ref); part.dependencies.ensureUnusedCapacity( this.allocator, @@ -3110,6 +3119,8 @@ const LinkerContext = struct { var js_chunks_count: usize = 0; for (chunks) |*chunk| { js_chunks_count += @boolToInt(chunk.content == .javascript); + if (js_chunks_count > 1) + break; } // TODO: remove this branch before release. We are keeping it to assert this code is correct @@ -3356,16 +3367,13 @@ const LinkerContext = struct { var dynamic_chunk_indices = chunk_meta.dynamic_imports.keys(); std.sort.sort(Index.Int, dynamic_chunk_indices, void{}, std.sort.asc(Index.Int)); - var list = chunk.cross_chunk_imports.listManaged(c.allocator); - defer chunk.cross_chunk_imports.update(list); - try list.ensureTotalCapacity(dynamic_chunk_indices.len); - for (dynamic_chunk_indices) |dynamic_chunk_index| { - list.appendAssumeCapacity( - .{ - .import_kind = .dynamic, - .chunk_index = dynamic_chunk_index, - }, - ); + chunk.cross_chunk_imports = @TypeOf(chunk.cross_chunk_imports).initCapacity(c.allocator, dynamic_chunk_indices.len) catch unreachable; + chunk.cross_chunk_imports.len = @truncate(u32, dynamic_chunk_indices.len); + for (dynamic_chunk_indices, chunk.cross_chunk_imports.slice()) |dynamic_chunk_index, *item| { + item.* = .{ + .import_kind = .dynamic, + .chunk_index = dynamic_chunk_index, + }; } } } @@ -3374,7 +3382,6 @@ const LinkerContext = struct { // imports because of export alias renaming, which must consider all export // aliases simultaneously to avoid collisions. { - var chunk_metas_ptr = chunk_metas.ptr; std.debug.assert(chunk_metas.len == chunks.len); var r = renamer.ExportRenamer.init(c.allocator); defer r.deinit(); @@ -3382,10 +3389,7 @@ const LinkerContext = struct { var stable_ref_list = std.ArrayList(StableRef).init(c.allocator); defer stable_ref_list.deinit(); - for (chunks) |*chunk| { - var chunk_meta = chunk_metas_ptr[0]; - chunk_metas_ptr += 1; - + for (chunks, chunk_metas) |*chunk, *chunk_meta| { if (chunk.content != .javascript) continue; var repr = &chunk.content.javascript; @@ -3394,26 +3398,26 @@ const LinkerContext = struct { .esm => { stable_ref_list = c.sortedCrossChunkExportItems(chunk_meta.exports, stable_ref_list); var clause_items = BabyList(js_ast.ClauseItem).initCapacity(c.allocator, stable_ref_list.items.len) catch unreachable; - for (stable_ref_list.items) |stable_ref| { + clause_items.len = @truncate(u32, stable_ref_list.items.len); + repr.exports_to_other_chunks.ensureUnusedCapacity(c.allocator, stable_ref_list.items.len) catch unreachable; + for (stable_ref_list.items, clause_items.slice()) |stable_ref, *clause_item| { r.clearRetainingCapacity(); const alias = r.nextRenamedName(c.graph.symbols.get(stable_ref.ref).?.original_name); - clause_items.appendAssumeCapacity( - .{ - .name = .{ - .ref = stable_ref.ref, - .loc = Logger.Loc.Empty, - }, - .alias = alias, - .alias_loc = Logger.Loc.Empty, - .original_name = "", + clause_item.* = .{ + .name = .{ + .ref = stable_ref.ref, + .loc = Logger.Loc.Empty, }, - ); - repr.exports_to_other_chunks.put( - c.allocator, + .alias = alias, + .alias_loc = Logger.Loc.Empty, + .original_name = "", + }; + + repr.exports_to_other_chunks.putAssumeCapacity( stable_ref.ref, alias, - ) catch unreachable; + ); } if (clause_items.len > 0) { @@ -6606,19 +6610,21 @@ const LinkerContext = struct { return; } - var flags = this.flags[source_index]; + this.flags[source_index] = brk: { + var flags = this.flags[source_index]; - if (flags.did_wrap_dependencies) return; - flags.did_wrap_dependencies = true; + if (flags.did_wrap_dependencies) return; + flags.did_wrap_dependencies = true; - // This module must be wrapped - if (flags.wrap == .none) { - flags.wrap = switch (this.exports_kind[source_index]) { - .cjs => .cjs, - else => .esm, - }; - } - this.flags[source_index] = flags; + // This module must be wrapped + if (flags.wrap == .none) { + flags.wrap = switch (this.exports_kind[source_index]) { + .cjs => .cjs, + else => .esm, + }; + } + break :brk flags; + }; const records = this.import_records[source_index].slice(); for (records) |record| { diff --git a/src/runtime.js b/src/runtime.js index 3db6afc34309..aefe9e6c88c2 100644 --- a/src/runtime.js +++ b/src/runtime.js @@ -254,3 +254,8 @@ export var __toCommonJS = /* @__PURE__ */ (cache => { ); }; })(typeof WeakMap !== "undefined" ? new WeakMap() : 0); + +export var __esm = (fn, res) => + function __init() { + return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])((fn = 0))), res; + }; diff --git a/test/fixtures/bundle/trivial/index.js b/test/fixtures/bundle/trivial/index.js index a6b6eb0ce997..6988ba77b51c 100644 --- a/test/fixtures/bundle/trivial/index.js +++ b/test/fixtures/bundle/trivial/index.js @@ -1,3 +1,5 @@ -import { fn } from "./fn"; +const Handler = import("./fn"); -console.log(fn(42)); +Handler.then(({ fn }) => { + console.log(fn(42)); +}); From fc2cfa699dfe2882bbc1d2c273cd740e78011533 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Tue, 7 Mar 2023 02:24:20 -0800 Subject: [PATCH 036/255] wip attempt to get import() expr to work --- src/ast/base.zig | 4 + src/bun.zig | 23 ++ src/bundler/bundle_v2.zig | 165 +++++++----- src/import_record.zig | 1 + src/js_ast.zig | 525 ++++++++++++++++++++++++++++++++++++-- src/js_parser.zig | 33 ++- src/js_printer.zig | 13 +- src/runtime.js | 59 +++-- src/util.zig | 7 +- 9 files changed, 701 insertions(+), 129 deletions(-) diff --git a/src/ast/base.zig b/src/ast/base.zig index d272ccb9cee7..bbc5bdafc4fb 100644 --- a/src/ast/base.zig +++ b/src/ast/base.zig @@ -197,6 +197,10 @@ pub const Ref = packed struct(u64) { source_index: Int = 0, inner_index: Int = 0, + pub inline fn isEmpty(this: Ref) bool { + return @bitCast(u64, this) == 0; + } + pub const ArrayHashCtx = RefHashCtx; pub const Int = std.meta.Int(.unsigned, (64 - 2) / 2); diff --git a/src/bun.zig b/src/bun.zig index a2a064360228..141fa0e572da 100644 --- a/src/bun.zig +++ b/src/bun.zig @@ -1052,3 +1052,26 @@ comptime { _ = @import("./cli/upgrade_command.zig").Version; } } + +pub fn DebugOnlyDisabler(comptime Type: type) type { + return struct { + const T = Type; + threadlocal var disable_create_in_debug: if (Environment.allow_assert) usize else u0 = 0; + pub inline fn disable() void { + if (comptime !Environment.allow_assert) return; + disable_create_in_debug += 1; + } + + pub inline fn enable() void { + if (comptime !Environment.allow_assert) return; + disable_create_in_debug -= 1; + } + + pub inline fn assert() void { + if (comptime !Environment.allow_assert) return; + if (disable_create_in_debug > 0) { + Output.panic(comptime "[" ++ @typeName(T) ++ "] called while disabled (did you forget to call enable?)", .{}); + } + } + }; +} diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 6e6e4455a673..6c6b98e2f493 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -339,7 +339,7 @@ pub const BundleV2 = struct { task.* = ParseTask.init(&result, source_index); task.loader = loader; task.task.node.next = null; - task.tree_shaking = this.bundler.options.tree_shaking; + task.tree_shaking = this.linker.options.tree_shaking; batch.push(ThreadPoolLib.Batch.from(&task.task)); return source_index.get(); } @@ -437,7 +437,7 @@ pub const BundleV2 = struct { try this.graph.input_files.append(allocator, Graph.InputFile{ .source = ParseTask.runtime_source, .loader = .js, - .side_effects = _resolver.SideEffects.no_side_effects__package_json, + .side_effects = _resolver.SideEffects.no_side_effects__pure_data, }); // try this.graph.entry_points.append(allocator, Index.runtime); @@ -446,6 +446,7 @@ pub const BundleV2 = struct { var runtime_parse_task = try this.graph.allocator.create(ParseTask); runtime_parse_task.* = ParseTask.runtime; runtime_parse_task.task.node.next = null; + runtime_parse_task.tree_shaking = true; runtime_parse_task.loader = .js; this.graph.parse_pending += 1; batch.push(ThreadPoolLib.Batch.from(&runtime_parse_task.task)); @@ -527,6 +528,12 @@ pub const BundleV2 = struct { this.linker.allocator = this.bundler.allocator; this.linker.graph.allocator = this.bundler.allocator; this.linker.graph.ast = try this.graph.ast.clone(this.linker.allocator); + var ast = this.linker.graph.ast.slice(); + for (ast.items(.module_scope)) |*new_module_scope| { + for (new_module_scope.children.slice()) |new_child| { + new_child.parent = new_module_scope; + } + } var chunks = try this.linker.link( this, @@ -1326,7 +1333,7 @@ const LinkerGraph = struct { }, ) catch unreachable; - this.ast.items(.module_scope)[source_index].?.generated.push(this.allocator, ref) catch unreachable; + this.ast.items(.module_scope)[source_index].generated.push(this.allocator, ref) catch unreachable; return ref; } @@ -1337,7 +1344,9 @@ const LinkerGraph = struct { name: []const u8, count: u32, ) !void { - const ref = graph.ast.items(.module_scope)[Index.runtime.get()].?.members.get(name).?.ref; + if (count > 0) debug("generateRuntimeSymbolImportAndUse({s}) for {d}", .{ name, source_index }); + + const ref = graph.ast.items(.module_scope)[Index.runtime.get()].members.get(name).?.ref; try graph.generateSymbolImportAndUse( source_index, entry_point_part_index.get(), @@ -1428,6 +1437,9 @@ const LinkerGraph = struct { g.ast.items(.uses_module_ref)[id] = true; } + // null ref shouldn't be there. + std.debug.assert(!ref.isEmpty()); + // Track that this specific symbol was imported if (source_index_to_import_from.get() != id) { try g.meta.items(.imports_to_bind)[id].put(g.allocator, ref, .{ @@ -1440,7 +1452,7 @@ const LinkerGraph = struct { // Pull in all parts that declare this symbol var dependencies = &part.dependencies; - const part_ids = g.topLevelSymbolToParts(id, ref); + const part_ids = g.topLevelSymbolToParts(source_index_to_import_from.get(), ref); try dependencies.ensureUnusedCapacity(g.allocator, part_ids.len); const old_len = dependencies.len; dependencies.len += @truncate(u32, part_ids.len); @@ -2225,11 +2237,14 @@ const LinkerContext = struct { flags[id].needs_exports_variable = true; } + const wrapped_ref = this.graph.ast.items(.wrapper_ref)[id] orelse continue; + if (wrapped_ref.isNull()) continue; + // Create the wrapper part for wrapped files. This is needed by a later step. this.createWrapperForFile( flags[id].wrap, // if this one is null, the AST does not need to be wrapped. - this.graph.ast.items(.wrapper_ref)[id] orelse continue, + wrapped_ref, &wrapper_part_indices[id], source_index, id, @@ -2370,7 +2385,7 @@ const LinkerContext = struct { // parallel and can't safely mutate the "importsToBind" map of another file. if (flag.needs_exports_variable) { if (!runtime_export_symbol_ref.isValid()) { - runtime_export_symbol_ref = this.graph.ast.items(.module_scope)[Index.runtime.get()].?.members.get("__export").?.ref; + runtime_export_symbol_ref = this.graph.ast.items(.module_scope)[Index.runtime.get()].members.get("__export").?.ref; } std.debug.assert(runtime_export_symbol_ref.isValid()); @@ -2500,7 +2515,7 @@ const LinkerContext = struct { } // Encode import-specific constraints in the dependency graph - var import_records = import_records_list[id].slice(); + var import_records: []ImportRecord = import_records_list[id].slice(); debug("Binding {d} imports for file {s} (#{d})", .{ import_records.len, source.path.text, id }); for (parts, 0..) |*part, part_index| { @@ -2553,45 +2568,68 @@ const LinkerContext = struct { const other_source_index = record.source_index.get(); const other_id = other_source_index; std.debug.assert(@intCast(usize, other_id) < this.graph.meta.len); - + const other_flags = flags[other_id]; const other_export_kind = exports_kind[other_id]; - switch (wrap) { - else => { + if (other_flags.wrap != .none) { + // Depend on the automatically-generated require wrapper symbol + const wrapper_ref = wrapper_refs[other_id].?; + this.graph.generateSymbolImportAndUse( + source_index, + @intCast(u32, part_index), + wrapper_ref, + 1, + Index.source(other_source_index), + ) catch unreachable; + + // This is an ES6 import of a CommonJS module, so it needs the + // "__toESM" wrapper as long as it's not a bare "require()" + if (kind != .require and other_export_kind == .cjs) { + record.wrap_with_to_esm = true; + to_esm_uses += 1; + } - // Depend on the automatically-generated require wrapper symbol - const wrapper_ref = wrapper_refs[other_id].?; + // If this is an ESM wrapper, also depend on the exports object + // since the final code will contain an inline reference to it. + // This must be done for "require()" and "import()" expressions + // but does not need to be done for "import" statements since + // those just cause us to reference the exports directly. + if (other_flags.wrap == .esm and record.kind != .stmt) { this.graph.generateSymbolImportAndUse( source_index, @intCast(u32, part_index), - wrapper_ref, + this.graph.ast.items(.exports_ref)[other_id], 1, Index.source(other_source_index), ) catch unreachable; - // This is an ES6 import of a CommonJS module, so it needs the - // "__toESM" wrapper as long as it's not a bare "require()" - if (kind != .require and other_export_kind == .cjs) { - record.wrap_with_to_esm = true; - to_esm_uses += 1; - } - }, - .none => { - if (kind == .stmt and other_export_kind == .esm_with_dynamic_fallback) { - // This is an import of a module that has a dynamic export fallback - // object. In that case we need to depend on that object in case - // something ends up needing to use it later. This could potentially - // be omitted in some cases with more advanced analysis if this - // dynamic export fallback object doesn't end up being needed. - this.graph.generateSymbolImportAndUse( - source_index, - @intCast(u32, part_index), - this.graph.ast.items(.exports_ref)[other_id], - 1, - Index.source(other_source_index), - ) catch unreachable; + // If this is a "require()" call, then we should add the + // "__esModule" marker to behave as if the module was converted + // from ESM to CommonJS. This is done via a wrapper instead of + // by modifying the exports object itself because the same ES + // module may be simultaneously imported and required, and the + // importing code should not see "__esModule" while the requiring + // code should see "__esModule". This is an extremely complex + // and subtle set of bundler interop issues. See for example + // https://github.com/evanw/esbuild/issues/1591. + if (record.kind == .require) { + record.wrap_with_to_commonjs = true; + to_common_js_uses += 1; } - }, + } + } else if (kind == .stmt and other_export_kind == .esm_with_dynamic_fallback) { + // This is an import of a module that has a dynamic export fallback + // object. In that case we need to depend on that object in case + // something ends up needing to use it later. This could potentially + // be omitted in some cases with more advanced analysis if this + // dynamic export fallback object doesn't end up being needed. + this.graph.generateSymbolImportAndUse( + source_index, + @intCast(u32, part_index), + this.graph.ast.items(.exports_ref)[other_id], + 1, + Index.source(other_source_index), + ) catch unreachable; } } @@ -2698,6 +2736,10 @@ const LinkerContext = struct { // WARNING: This method is run in parallel over all files. Do not mutate data // for other files within this method or you will create a data race. //////////////////////////////////////////////////////////////////////////////// + Stmt.Disabler.disable(); + defer Stmt.Disabler.enable(); + Expr.Disabler.disable(); + defer Expr.Disabler.enable(); // 1 property per export var properties = std.ArrayList(js_ast.G.Property) @@ -2710,7 +2752,7 @@ const LinkerContext = struct { const stmts_count = // 3 statements for every export - export_aliases.len * 3 + + export_aliases.len * 2 + // + 1 if there are non-zero exports @as(usize, @boolToInt(export_aliases.len > 0)) + // + 1 if we need to inject the exports variable @@ -2759,9 +2801,10 @@ const LinkerContext = struct { } var block = stmts.eat1( - js_ast.Stmt.alloc(js_ast.S.Block, .{ + js_ast.Stmt.allocate(allocator_, js_ast.S.Block, .{ .stmts = stmts.eat1( - js_ast.Stmt.alloc( + js_ast.Stmt.allocate( + allocator_, js_ast.S.Return, .{ .value = value }, loc, @@ -2775,7 +2818,8 @@ const LinkerContext = struct { }; properties.appendAssumeCapacity( .{ - .key = js_ast.Expr.init( + .key = js_ast.Expr.allocate( + allocator_, js_ast.E.String, .{ // TODO: test emoji work as expected @@ -2784,7 +2828,8 @@ const LinkerContext = struct { }, loc, ), - .value = js_ast.Expr.init( + .value = js_ast.Expr.allocate( + allocator_, js_ast.E.Arrow, .{ .prefer_expr = true, .body = fn_body }, loc, @@ -2812,9 +2857,8 @@ const LinkerContext = struct { var declared_symbols = js_ast.DeclaredSymbol.List{}; var exports_ref = c.graph.ast.items(.exports_ref)[id]; - var export_stmts: []js_ast.Stmt = stmts.head; - // std.debug.assert(stmts.head.len <= 3); // assert we allocated exactly the right amount - stmts.head.len = 0; + var export_stmts: []js_ast.Stmt = stmts.head[0 .. @as(usize, @boolToInt(needs_exports_variable)) + @as(usize, @boolToInt(properties.items.len > 0))]; + stmts.head = stmts.head[export_stmts.len..]; // Prefix this part with "var exports = {}" if this isn't a CommonJS entry point if (needs_exports_variable) { @@ -2827,9 +2871,10 @@ const LinkerContext = struct { }, loc, ), - .value = js_ast.Expr.init(js_ast.E.Object, .{}, loc), + .value = js_ast.Expr.allocate(allocator_, js_ast.E.Object, .{}, loc), }; - export_stmts[0] = js_ast.Stmt.alloc( + export_stmts[0] = js_ast.Stmt.allocate( + allocator_, js_ast.S.Local, .{ .decls = decls, @@ -2842,17 +2887,19 @@ const LinkerContext = struct { // "__export(exports, { foo: () => foo })" var export_ref = Ref.None; if (properties.items.len > 0) { - export_ref = c.graph.ast.items(.module_scope)[Index.runtime.get()].?.members.get("__export").?.ref; + export_ref = c.graph.ast.items(.module_scope)[Index.runtime.get()].members.get("__export").?.ref; var args = allocator_.alloc(js_ast.Expr, 2) catch unreachable; args[0..2].* = [_]js_ast.Expr{ js_ast.Expr.initIdentifier(exports_ref, loc), - js_ast.Expr.init(js_ast.E.Object, .{ .properties = js_ast.G.Property.List.fromList(properties) }, loc), + js_ast.Expr.allocate(allocator_, js_ast.E.Object, .{ .properties = js_ast.G.Property.List.fromList(properties) }, loc), }; // the end incase we somehow get into a state where needs_export_variable is false but properties.len > 0 - export_stmts[export_stmts.len - 1] = js_ast.Stmt.alloc( + export_stmts[export_stmts.len - 1] = js_ast.Stmt.allocate( + allocator_, js_ast.S.SExpr, .{ - .value = js_ast.Expr.init( + .value = js_ast.Expr.allocate( + allocator_, js_ast.E.Call, .{ .target = js_ast.Expr.initIdentifier(export_ref, loc), @@ -3537,7 +3584,7 @@ const LinkerContext = struct { var reserved_names = try renamer.computeInitialReservedNames(allocator); for (files_in_order) |source_index| { - renamer.computeReservedNamesForScope(&all_module_scopes[source_index].?, &c.graph.symbols, &reserved_names, allocator); + renamer.computeReservedNamesForScope(&all_module_scopes[source_index], &c.graph.symbols, &reserved_names, allocator); } break :brk reserved_names; @@ -3653,7 +3700,7 @@ const LinkerContext = struct { } } } - r.assignNamesRecursiveWithNumberScope(child_number_scope, &all_module_scopes[source_index].?, source_index, sorted); + r.assignNamesRecursiveWithNumberScope(child_number_scope, &all_module_scopes[source_index], source_index, sorted); continue; }, @@ -3701,7 +3748,7 @@ const LinkerContext = struct { var repr = &chunk.content.javascript; - var runtime_scope: *Scope = &c.graph.ast.items(.module_scope)[c.graph.files.items(.input_file)[Index.runtime.value].get()].?; + var runtime_scope: *Scope = &c.graph.ast.items(.module_scope)[c.graph.files.items(.input_file)[Index.runtime.value].get()]; var runtime_members = &runtime_scope.members; const toCommonJSRef = c.graph.symbols.follow(runtime_members.get("__toCommonJS").?.ref); const toESMRef = c.graph.symbols.follow(runtime_members.get("__toESM").?.ref); @@ -4974,8 +5021,7 @@ const LinkerContext = struct { if (shouldStripExports) { switch (s.value) { - .stmt => { - var stmt2 = s.value.stmt; + .stmt => |stmt2| { switch (stmt2.data) { .s_expr => |s2| { // "export default foo;" => "var default = foo;" @@ -5035,8 +5081,11 @@ const LinkerContext = struct { }, else => bun.unreachablePanic( - "Unexpected type {any}", - .{stmt2.data}, + "Unexpected type {any} in source file {s}", + .{ + stmt2.data, + c.parse_graph.input_files.get(c.graph.files.get(source_index).input_file.get()).source.path.text, + }, ), } }, @@ -5077,7 +5126,7 @@ const LinkerContext = struct { } fn runtimeFunction(c: *LinkerContext, name: []const u8) Ref { - return c.graph.ast.items(.module_scope)[Index.runtime.value].?.members.get(name).?.ref; + return c.graph.ast.items(.module_scope)[Index.runtime.value].members.get(name).?.ref; } fn generateCodeForFileInChunkJS( diff --git a/src/import_record.zig b/src/import_record.zig index a1eece6c926c..787057f84c66 100644 --- a/src/import_record.zig +++ b/src/import_record.zig @@ -160,6 +160,7 @@ pub const ImportRecord = struct { print_namespace_in_path: bool = false, wrap_with_to_esm: bool = false, + wrap_with_to_commonjs: bool = false, pub const List = bun.BabyList(ImportRecord); diff --git a/src/js_ast.zig b/src/js_ast.zig index 16a351668dd0..23f15b1529a9 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -179,7 +179,8 @@ pub fn NewBaseStore(comptime Union: anytype, comptime count: usize) type { _self = undefined; } - pub fn append(comptime ValueType: type, value: ValueType) *ValueType { + pub fn append(comptime Disabler: type, comptime ValueType: type, value: ValueType) *ValueType { + Disabler.assert(); return _self._append(ValueType, value); } @@ -2077,6 +2078,7 @@ pub const Stmt = struct { pub var icount: usize = 0; pub fn init(comptime StatementType: type, origData: *StatementType, loc: logger.Loc) Stmt { icount += 1; + return switch (comptime StatementType) { S.Empty => Stmt{ .loc = loc, .data = Data{ .s_empty = S.Empty{} } }, S.Block => Stmt.comptime_init("s_block", S.Block, origData, loc), @@ -2119,6 +2121,13 @@ pub const Stmt = struct { return Stmt{ .loc = loc, .data = @unionInit(Data, tag_name, Data.Store.append(typename, origData)) }; } + fn allocateData(allocator: std.mem.Allocator, comptime tag_name: string, comptime typename: type, origData: anytype, loc: logger.Loc) Stmt { + var value = allocator.create(@TypeOf(origData)) catch unreachable; + value.* = origData; + + return comptime_init(tag_name, *typename, value, loc); + } + inline fn comptime_init(comptime tag_name: string, comptime TypeName: type, origData: TypeName, loc: logger.Loc) Stmt { return Stmt{ .loc = loc, .data = @unionInit(Data, tag_name, origData) }; } @@ -2165,6 +2174,53 @@ pub const Stmt = struct { }; } + pub const Disabler = bun.DebugOnlyDisabler(@This()); + + /// When the lifetime of an Stmt.Data's pointer must exist longer than reset() is called, use this function. + /// Be careful to free the memory (or use an allocator that does it for you) + /// Also, prefer Stmt.init or Stmt.alloc when possible. This will be slower. + pub fn allocate(allocator: std.mem.Allocator, comptime StatementData: type, origData: StatementData, loc: logger.Loc) Stmt { + Stmt.Data.Store.assert(); + + icount += 1; + return switch (StatementData) { + S.Block => Stmt.allocateData(allocator, "s_block", S.Block, origData, loc), + S.Break => Stmt.allocateData(allocator, "s_break", S.Break, origData, loc), + S.Class => Stmt.allocateData(allocator, "s_class", S.Class, origData, loc), + S.Comment => Stmt.allocateData(allocator, "s_comment", S.Comment, origData, loc), + S.Continue => Stmt.allocateData(allocator, "s_continue", S.Continue, origData, loc), + S.Debugger => Stmt{ .loc = loc, .data = .{ .s_debugger = origData } }, + S.Directive => Stmt.allocateData(allocator, "s_directive", S.Directive, origData, loc), + S.DoWhile => Stmt.allocateData(allocator, "s_do_while", S.DoWhile, origData, loc), + S.Empty => Stmt{ .loc = loc, .data = Data{ .s_empty = S.Empty{} } }, + S.Enum => Stmt.allocateData(allocator, "s_enum", S.Enum, origData, loc), + S.ExportClause => Stmt.allocateData(allocator, "s_export_clause", S.ExportClause, origData, loc), + S.ExportDefault => Stmt.allocateData(allocator, "s_export_default", S.ExportDefault, origData, loc), + S.ExportEquals => Stmt.allocateData(allocator, "s_export_equals", S.ExportEquals, origData, loc), + S.ExportFrom => Stmt.allocateData(allocator, "s_export_from", S.ExportFrom, origData, loc), + S.ExportStar => Stmt.allocateData(allocator, "s_export_star", S.ExportStar, origData, loc), + S.SExpr => Stmt.allocateData(allocator, "s_expr", S.SExpr, origData, loc), + S.ForIn => Stmt.allocateData(allocator, "s_for_in", S.ForIn, origData, loc), + S.ForOf => Stmt.allocateData(allocator, "s_for_of", S.ForOf, origData, loc), + S.For => Stmt.allocateData(allocator, "s_for", S.For, origData, loc), + S.Function => Stmt.allocateData(allocator, "s_function", S.Function, origData, loc), + S.If => Stmt.allocateData(allocator, "s_if", S.If, origData, loc), + S.Import => Stmt.allocateData(allocator, "s_import", S.Import, origData, loc), + S.Label => Stmt.allocateData(allocator, "s_label", S.Label, origData, loc), + S.LazyExport => Stmt.allocateData(allocator, "s_lazy_export", S.LazyExport, origData, loc), + S.Local => Stmt.allocateData(allocator, "s_local", S.Local, origData, loc), + S.Namespace => Stmt.allocateData(allocator, "s_namespace", S.Namespace, origData, loc), + S.Return => Stmt.allocateData(allocator, "s_return", S.Return, origData, loc), + S.Switch => Stmt.allocateData(allocator, "s_switch", S.Switch, origData, loc), + S.Throw => Stmt.allocateData(allocator, "s_throw", S.Throw, origData, loc), + S.Try => Stmt.allocateData(allocator, "s_try", S.Try, origData, loc), + S.TypeScript => Stmt{ .loc = loc, .data = Data{ .s_type_script = S.TypeScript{} } }, + S.While => Stmt.allocateData(allocator, "s_while", S.While, origData, loc), + S.With => Stmt.allocateData(allocator, "s_with", S.With, origData, loc), + else => @compileError("Invalid type in Stmt.init"), + }; + } + pub const Tag = enum(u6) { s_block, s_break, @@ -2314,7 +2370,7 @@ pub const Stmt = struct { } pub fn append(comptime ValueType: type, value: anytype) *ValueType { - return All.append(ValueType, value); + return All.append(Disabler, ValueType, value); } pub fn toOwnedSlice() []*Store.All.Block { @@ -2703,6 +2759,421 @@ pub const Expr = struct { var false_bool = E.Boolean{ .value = false }; var bool_values = [_]*E.Boolean{ &false_bool, &true_bool }; + /// When the lifetime of an Expr.Data's pointer must exist longer than reset() is called, use this function. + /// Be careful to free the memory (or use an allocator that does it for you) + /// Also, prefer Expr.init or Expr.alloc when possible. This will be slower. + pub fn allocate(allocator: std.mem.Allocator, comptime Type: type, st: Type, loc: logger.Loc) Expr { + icount += 1; + Data.Store.assert(); + + switch (Type) { + E.Array => { + return Expr{ + .loc = loc, + .data = Data{ + .e_array = brk: { + var item = allocator.create(Type) catch unreachable; + item.* = st; + break :brk item; + }, + }, + }; + }, + E.Class => { + return Expr{ + .loc = loc, + .data = Data{ + .e_class = brk: { + var item = allocator.create(Type) catch unreachable; + item.* = st; + break :brk item; + }, + }, + }; + }, + E.Unary => { + return Expr{ + .loc = loc, + .data = Data{ + .e_unary = brk: { + var item = allocator.create(Type) catch unreachable; + item.* = st; + break :brk item; + }, + }, + }; + }, + E.Binary => { + return Expr{ + .loc = loc, + .data = Data{ + .e_binary = brk: { + var item = allocator.create(Type) catch unreachable; + item.* = st; + break :brk item; + }, + }, + }; + }, + E.This => { + return Expr{ + .loc = loc, + .data = Data{ + .e_this = st, + }, + }; + }, + E.Boolean => { + return Expr{ + .loc = loc, + .data = Data{ + .e_boolean = st, + }, + }; + }, + E.Super => { + return Expr{ + .loc = loc, + .data = Data{ + .e_super = st, + }, + }; + }, + E.Null => { + return Expr{ + .loc = loc, + .data = Data{ + .e_null = st, + }, + }; + }, + E.Undefined => { + return Expr{ + .loc = loc, + .data = Data{ + .e_undefined = st, + }, + }; + }, + E.New => { + return Expr{ + .loc = loc, + .data = Data{ + .e_new = brk: { + var item = allocator.create(Type) catch unreachable; + item.* = st; + break :brk item; + }, + }, + }; + }, + E.NewTarget => { + return Expr{ + .loc = loc, + .data = Data{ + .e_new_target = st, + }, + }; + }, + E.Function => { + return Expr{ + .loc = loc, + .data = Data{ + .e_function = brk: { + var item = allocator.create(Type) catch unreachable; + item.* = st; + break :brk item; + }, + }, + }; + }, + E.ImportMeta => { + return Expr{ + .loc = loc, + .data = Data{ + .e_import_meta = st, + }, + }; + }, + E.Call => { + return Expr{ + .loc = loc, + .data = Data{ + .e_call = brk: { + var item = allocator.create(Type) catch unreachable; + item.* = st; + break :brk item; + }, + }, + }; + }, + E.Dot => { + return Expr{ + .loc = loc, + .data = Data{ + .e_dot = brk: { + var item = allocator.create(Type) catch unreachable; + item.* = st; + break :brk item; + }, + }, + }; + }, + E.Index => { + return Expr{ + .loc = loc, + .data = Data{ + .e_index = brk: { + var item = allocator.create(Type) catch unreachable; + item.* = st; + break :brk item; + }, + }, + }; + }, + E.Arrow => { + return Expr{ + .loc = loc, + .data = Data{ + .e_arrow = brk: { + var item = allocator.create(Type) catch unreachable; + item.* = st; + break :brk item; + }, + }, + }; + }, + E.Identifier => { + return Expr{ + .loc = loc, + .data = Data{ + .e_identifier = E.Identifier{ + .ref = st.ref, + .must_keep_due_to_with_stmt = st.must_keep_due_to_with_stmt, + .can_be_removed_if_unused = st.can_be_removed_if_unused, + .call_can_be_unwrapped_if_unused = st.call_can_be_unwrapped_if_unused, + }, + }, + }; + }, + E.ImportIdentifier => { + return Expr{ + .loc = loc, + .data = Data{ + .e_import_identifier = .{ + .ref = st.ref, + .was_originally_identifier = st.was_originally_identifier, + }, + }, + }; + }, + E.PrivateIdentifier => { + return Expr{ + .loc = loc, + .data = Data{ + .e_private_identifier = st, + }, + }; + }, + E.JSXElement => { + return Expr{ + .loc = loc, + .data = Data{ + .e_jsx_element = brk: { + var item = allocator.create(Type) catch unreachable; + item.* = st; + break :brk item; + }, + }, + }; + }, + E.Missing => { + return Expr{ .loc = loc, .data = Data{ .e_missing = E.Missing{} } }; + }, + E.Number => { + return Expr{ + .loc = loc, + .data = Data{ + .e_number = st, + }, + }; + }, + E.BigInt => { + return Expr{ + .loc = loc, + .data = Data{ + .e_big_int = brk: { + var item = allocator.create(Type) catch unreachable; + item.* = st; + break :brk item; + }, + }, + }; + }, + E.Object => { + return Expr{ + .loc = loc, + .data = Data{ + .e_object = brk: { + var item = allocator.create(Type) catch unreachable; + item.* = st; + break :brk item; + }, + }, + }; + }, + E.Spread => { + return Expr{ + .loc = loc, + .data = Data{ + .e_spread = brk: { + var item = allocator.create(Type) catch unreachable; + item.* = st; + break :brk item; + }, + }, + }; + }, + E.String => { + if (comptime Environment.isDebug) { + // Sanity check: assert string is not a null ptr + if (st.data.len > 0 and st.isUTF8()) { + std.debug.assert(@ptrToInt(st.data.ptr) > 0); + std.debug.assert(st.data[0] > 0); + } + } + return Expr{ + .loc = loc, + .data = Data{ + .e_string = brk: { + var item = allocator.create(Type) catch unreachable; + item.* = st; + break :brk item; + }, + }, + }; + }, + E.TemplatePart => { + return Expr{ + .loc = loc, + .data = Data{ + .e_template_part = brk: { + var item = allocator.create(Type) catch unreachable; + item.* = st; + break :brk item; + }, + }, + }; + }, + E.Template => { + return Expr{ + .loc = loc, + .data = Data{ + .e_template = brk: { + var item = allocator.create(Type) catch unreachable; + item.* = st; + break :brk item; + }, + }, + }; + }, + E.RegExp => { + return Expr{ + .loc = loc, + .data = Data{ + .e_reg_exp = brk: { + var item = allocator.create(Type) catch unreachable; + item.* = st; + break :brk item; + }, + }, + }; + }, + E.Await => { + return Expr{ + .loc = loc, + .data = Data{ + .e_await = brk: { + var item = allocator.create(Type) catch unreachable; + item.* = st; + break :brk item; + }, + }, + }; + }, + E.Yield => { + return Expr{ + .loc = loc, + .data = Data{ + .e_yield = brk: { + var item = allocator.create(Type) catch unreachable; + item.* = st; + break :brk item; + }, + }, + }; + }, + E.If => { + return Expr{ + .loc = loc, + .data = Data{ + .e_if = brk: { + var item = allocator.create(Type) catch unreachable; + item.* = st; + break :brk item; + }, + }, + }; + }, + E.RequireOrRequireResolve => { + return Expr{ + .loc = loc, + .data = Data{ + .e_require_or_require_resolve = st, + }, + }; + }, + E.Import => { + return Expr{ + .loc = loc, + .data = Data{ + .e_import = brk: { + var item = allocator.create(Type) catch unreachable; + item.* = st; + break :brk item; + }, + }, + }; + }, + E.Require => { + return Expr{ + .loc = loc, + .data = Data{ + .e_require = st, + }, + }; + }, + *E.String => { + return Expr{ + .loc = loc, + .data = Data{ + .e_string = brk: { + var item = allocator.create(Type) catch unreachable; + item.* = st.*; + break :brk item; + }, + }, + }; + }, + + else => { + @compileError("Invalid type passed to Expr.init: " ++ @typeName(Type)); + }, + } + } + + pub const Disabler = bun.DebugOnlyDisabler(@This()); + pub fn init(comptime Type: type, st: Type, loc: logger.Loc) Expr { icount += 1; Data.Store.assert(); @@ -2712,7 +3183,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_array = Data.Store.All.append(Type, st), + .e_array = Data.Store.All.append(Disabler, Type, st), }, }; }, @@ -2720,7 +3191,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_class = Data.Store.All.append(Type, st), + .e_class = Data.Store.All.append(Disabler, Type, st), }, }; }, @@ -2728,7 +3199,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_unary = Data.Store.All.append(Type, st), + .e_unary = Data.Store.All.append(Disabler, Type, st), }, }; }, @@ -2736,7 +3207,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_binary = Data.Store.All.append(Type, st), + .e_binary = Data.Store.All.append(Disabler, Type, st), }, }; }, @@ -2784,7 +3255,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_new = Data.Store.All.append(Type, st), + .e_new = Data.Store.All.append(Disabler, Type, st), }, }; }, @@ -2800,7 +3271,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_function = Data.Store.All.append(Type, st), + .e_function = Data.Store.All.append(Disabler, Type, st), }, }; }, @@ -2816,7 +3287,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_call = Data.Store.All.append(Type, st), + .e_call = Data.Store.All.append(Disabler, Type, st), }, }; }, @@ -2824,7 +3295,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_dot = Data.Store.All.append(Type, st), + .e_dot = Data.Store.All.append(Disabler, Type, st), }, }; }, @@ -2832,7 +3303,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_index = Data.Store.All.append(Type, st), + .e_index = Data.Store.All.append(Disabler, Type, st), }, }; }, @@ -2840,7 +3311,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_arrow = Data.Store.All.append(Type, st), + .e_arrow = Data.Store.All.append(Disabler, Type, st), }, }; }, @@ -2880,7 +3351,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_jsx_element = Data.Store.All.append(Type, st), + .e_jsx_element = Data.Store.All.append(Disabler, Type, st), }, }; }, @@ -2899,7 +3370,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_big_int = Data.Store.All.append(Type, st), + .e_big_int = Data.Store.All.append(Disabler, Type, st), }, }; }, @@ -2907,7 +3378,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_object = Data.Store.All.append(Type, st), + .e_object = Data.Store.All.append(Disabler, Type, st), }, }; }, @@ -2915,7 +3386,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_spread = Data.Store.All.append(Type, st), + .e_spread = Data.Store.All.append(Disabler, Type, st), }, }; }, @@ -2930,7 +3401,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_string = Data.Store.All.append(Type, st), + .e_string = Data.Store.All.append(Disabler, Type, st), }, }; }, @@ -2938,7 +3409,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_template_part = Data.Store.All.append(Type, st), + .e_template_part = Data.Store.All.append(Disabler, Type, st), }, }; }, @@ -2946,7 +3417,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_template = Data.Store.All.append(Type, st), + .e_template = Data.Store.All.append(Disabler, Type, st), }, }; }, @@ -2954,7 +3425,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_reg_exp = Data.Store.All.append(Type, st), + .e_reg_exp = Data.Store.All.append(Disabler, Type, st), }, }; }, @@ -2962,7 +3433,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_await = Data.Store.All.append(Type, st), + .e_await = Data.Store.All.append(Disabler, Type, st), }, }; }, @@ -2970,7 +3441,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_yield = Data.Store.All.append(Type, st), + .e_yield = Data.Store.All.append(Disabler, Type, st), }, }; }, @@ -2978,7 +3449,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_if = Data.Store.All.append(Type, st), + .e_if = Data.Store.All.append(Disabler, Type, st), }, }; }, @@ -2994,7 +3465,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_import = Data.Store.All.append(Type, st), + .e_import = Data.Store.All.append(Disabler, Type, st), }, }; }, @@ -3010,7 +3481,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_string = Data.Store.All.append(@TypeOf(st.*), st.*), + .e_string = Data.Store.All.append(Disabler, @TypeOf(st.*), st.*), }, }; }, @@ -4677,7 +5148,7 @@ pub const Ast = struct { parts: Part.List = Part.List{}, // This list may be mutated later, so we should store the capacity symbols: Symbol.List = Symbol.List{}, - module_scope: ?Scope = null, + module_scope: Scope = Scope{}, // char_freq: *CharFreq, exports_ref: Ref = Ref.None, module_ref: ?Ref = null, @@ -5002,7 +5473,7 @@ pub const Scope = struct { id: usize = 0, kind: Kind = Kind.block, - parent: ?*Scope, + parent: ?*Scope = null, children: BabyList(*Scope) = .{}, members: MemberHashMap = .{}, generated: BabyList(Ref) = .{}, diff --git a/src/js_parser.zig b/src/js_parser.zig index 8fdbe001d6a3..563170aeeb03 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -1024,7 +1024,6 @@ const StaticSymbolName = struct { pub const __require = NewStaticSymbol("require"); pub const __cJS2eSM = NewStaticSymbol("__cJS2eSM"); pub const __export = NewStaticSymbol("__export"); - pub const __reExport = NewStaticSymbol("__reExport"); pub const __load = NewStaticSymbol("__load"); pub const @"$$lzy" = NewStaticSymbol("$$lzy"); pub const __HMRModule = NewStaticSymbol("HMR"); @@ -3488,7 +3487,6 @@ pub const Prefill = struct { pub var JSXFilename = "__jsxFilename"; pub var MarkAsModule = "__markAsModule"; pub var CommonJS = "__commonJS"; - pub var ReExport = "__reExport"; pub var ToModule = "__toModule"; const JSXShortname = "jsx"; }; @@ -5206,7 +5204,6 @@ fn NewParser_( } if (p.options.enable_bundling) { - p.runtime_imports.__reExport = try p.declareGeneratedSymbol(.other, "__reExport"); p.runtime_imports.@"$$m" = try p.declareGeneratedSymbol(.other, "$$m"); p.runtime_imports.@"$$lzy" = try p.declareGeneratedSymbol(.other, "$$lzy"); @@ -5303,17 +5300,19 @@ fn NewParser_( } pub fn resolveCommonJSSymbols(p: *P) void { + if (!p.options.features.allow_runtime) + return; + if (p.runtime_imports.__require) |*require| { p.resolveGeneratedSymbol(require); } - if (p.options.features.allow_runtime) - p.ensureRequireSymbol(); + + p.ensureRequireSymbol(); } pub fn resolveBundlingSymbols(p: *P) void { p.recordUsage(p.runtime_imports.@"$$m".?.ref); - p.resolveGeneratedSymbol(&p.runtime_imports.__reExport.?); p.resolveGeneratedSymbol(&p.runtime_imports.@"$$m".?); p.resolveGeneratedSymbol(&p.runtime_imports.@"$$lzy".?); p.resolveGeneratedSymbol(&p.runtime_imports.__export.?); @@ -18331,6 +18330,7 @@ fn NewParser_( p.treeShake(&parts, false); } + const bundling = p.options.bundle; var parts_end: usize = 0; // Handle import paths after the whole file has been visited because we need // symbol usage counts to be able to remove unused type-only imports in @@ -18352,7 +18352,7 @@ fn NewParser_( removed_import_equals = removed_import_equals or result.removed_import_equals; part.stmts = result.stmts; - if (part.stmts.len > 0 or (i == js_ast.namespace_export_part_index and p.options.bundle)) { + if (part.stmts.len > 0 or (i == js_ast.namespace_export_part_index and bundling)) { if (p.module_scope.contains_direct_eval and part.declared_symbols.len() > 0) { // If this file contains a direct call to "eval()", all parts that // declare top-level symbols must be kept since the eval'd code may @@ -18895,6 +18895,23 @@ fn NewParser_( part.stmts = _stmts[0 .. imports_list.len + toplevel_stmts.len + exports_from.len]; } else if (p.options.features.hot_module_reloading) {} var top_level_symbols_to_parts = js_ast.Ast.TopLevelSymbolToParts{}; + const wrapper_ref = brk: { + if (p.options.bundle) { + break :brk p.newSymbol( + .other, + std.fmt.allocPrint( + p.allocator, + "require_{any}", + .{ + p.source.fmtIdentifier(), + }, + ) catch unreachable, + ) catch unreachable; + } + + break :brk @as(?Ref, null); + }; + if (p.options.bundle) { // Each part tracks the other parts it depends on within this file @@ -18951,7 +18968,7 @@ fn NewParser_( .module_scope = p.module_scope.*, .symbols = js_ast.Symbol.List.init(p.symbols.items), .exports_ref = p.exports_ref, - .wrapper_ref = Ref.None, + .wrapper_ref = wrapper_ref, .module_ref = p.module_ref, .import_records = ImportRecord.List.init(p.import_records.items), .export_star_import_records = p.export_star_import_records.items, diff --git a/src/js_printer.zig b/src/js_printer.zig index fc517338d9a2..f70517da11d1 100644 --- a/src/js_printer.zig +++ b/src/js_printer.zig @@ -2341,6 +2341,17 @@ fn NewPrinter( p.print("("); } + if (comptime Environment.allow_assert) { + // This is UB + if (name.len > 65432 or e.ref.isNull() or name.len == 0) { + Output.panic("Invalid identifier at {d} in {s}\ndump:\n{s}", .{ + expr.loc.start, + (p.options.source_path orelse bun.fs.Path.initWithPretty("", "")).pretty, + p.writer.ctx.getWritten(), + }); + } + } + p.printSpaceBeforeIdentifier(); p.addSourceMapping(expr.loc); p.printIdentifier(name); @@ -5511,7 +5522,7 @@ pub fn printWithWriterAndPlatform( renamer, undefined, ); - + defer _writer.* = printer.writer.*; defer { imported_module_ids_list = printer.imported_module_ids; } diff --git a/src/runtime.js b/src/runtime.js index aefe9e6c88c2..cd2d2e82d09d 100644 --- a/src/runtime.js +++ b/src/runtime.js @@ -1,4 +1,3 @@ -var $$mod$ = Symbol.for; var __create = Object.create; var __descs = Object.getOwnPropertyDescriptors; var __defProp = Object.defineProperty; @@ -9,12 +8,24 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor; export var __markAsModule = target => __defProp(target, "__esModule", { value: true, configurable: true }); +export var __reExport = (target, mod, copyDefault, desc) => { + if ((mod && typeof mod === "object") || typeof mod === "function") + for (let key of __getOwnPropNames(mod)) + if (!__hasOwnProp.call(target, key) && (copyDefault || key !== "default")) + __defProp(target, key, { + get: () => mod[key], + configurable: true, + enumerable: !(desc = __getOwnPropDesc(mod, key)) || desc.enumerable, + }); + return target; +}; + // lazy require to prevent loading one icon from a design system -export var $$lzy = (target, module, props) => { +export var $$lzy = (target, mod, props) => { for (let key in props) { if (!__hasOwnProp.call(target, key)) __defProp(target, key, { - get: () => module()[props[key]], + get: () => mod()[props[key]], enumerable: true, configurable: true, }); @@ -22,18 +33,18 @@ export var $$lzy = (target, module, props) => { return target; }; -export var __toModule = module => { +export var __toModule = mod => { return __reExport( __markAsModule( __defProp( - module != null ? __create(__getProtoOf(module)) : {}, + mod != null ? __create(__getProtoOf(mod)) : {}, "default", - module && module.__esModule && "default" in module - ? { get: () => module.default, enumerable: true, configurable: true } - : { value: module, enumerable: true, configurable: true }, + mod && mod.__esModule && "default" in mod + ? { get: () => mod.default, enumerable: true, configurable: true } + : { value: mod, enumerable: true, configurable: true }, ), ), - module, + mod, ); }; @@ -178,18 +189,6 @@ export var __exportDefault = (target, value) => { }); }; -export var __reExport = (target, module, copyDefault, desc) => { - if ((module && typeof module === "object") || typeof module === "function") - for (let key of __getOwnPropNames(module)) - if (!__hasOwnProp.call(target, key) && (copyDefault || key !== "default")) - __defProp(target, key, { - get: () => module[key], - configurable: true, - enumerable: !(desc = __getOwnPropDesc(module, key)) || desc.enumerable, - }); - return target; -}; - function hasAnyProps(obj) { for (let key in obj) return true; return false; @@ -224,11 +223,11 @@ export var __decorateClass = (decorators, target, key, kind) => { export var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index); // Converts the module from CommonJS to ESM -export var __toESM = (module, isNodeMode) => { +export var __toESM = (mod, isNodeMode) => { return __reExport( __markAsModule( __defProp( - module != null ? __create(__getProtoOf(module)) : {}, + mod != null ? __create(__getProtoOf(mod)) : {}, "default", // If the importer is not in node compatibility mode and this is an ESM @@ -236,21 +235,21 @@ export var __toESM = (module, isNodeMode) => { // compatible transform (i.e. "__esModule" has been set), then forward // "default" to the export named "default". Otherwise set "default" to // "module.exports" for node compatibility. - !isNodeMode && module && module.__esModule - ? { get: () => module.default, enumerable: true } - : { value: module, enumerable: true }, + !isNodeMode && mod && mod.__esModule + ? { get: () => mod.default, enumerable: true } + : { value: mod, enumerable: true }, ), ), - module, + mod, ); }; // Converts the module from ESM to CommonJS export var __toCommonJS = /* @__PURE__ */ (cache => { - return (module, temp) => { + return (mod, temp) => { return ( - (cache && cache.get(module)) || - ((temp = __reExport(__markAsModule({}), module, /* copyDefault */ 1)), cache && cache.set(module, temp), temp) + (cache && cache.get(mod)) || + ((temp = __reExport(__markAsModule({}), mod, /* copyDefault */ 1)), cache && cache.set(mod, temp), temp) ); }; })(typeof WeakMap !== "undefined" ? new WeakMap() : 0); diff --git a/src/util.zig b/src/util.zig index 5ce61e42f61b..615732106ad1 100644 --- a/src/util.zig +++ b/src/util.zig @@ -256,15 +256,12 @@ pub fn Batcher(comptime Type: type) type { } pub inline fn eat(this: *@This(), value: Type) *Type { - this.head[0] = value; - var prev = &this.head[0]; - this.head = this.head[1..]; - return prev; + return @ptrCast(*Type, &this.head.eat1(value).ptr); } pub inline fn eat1(this: *@This(), value: Type) []Type { - this.head[0] = value; var prev = this.head[0..1]; + prev[0] = value; this.head = this.head[1..]; return prev; } From a96303715faa8ec269ddf070fd949462b4a920d9 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Tue, 7 Mar 2023 04:46:56 -0800 Subject: [PATCH 037/255] Bundle namespace imports --- src/bundler/bundle_v2.zig | 27 +++--- src/js_parser.zig | 182 +++++++++++++++++++++++++++++++------- src/renamer.zig | 6 +- 3 files changed, 169 insertions(+), 46 deletions(-) diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 6c6b98e2f493..710b3a2efb87 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -1062,10 +1062,10 @@ const RefImportData = std.ArrayHashMapUnmanaged(Ref, ImportData, Ref.ArrayHashCt const ResolvedExports = bun.StringArrayHashMapUnmanaged(ExportData); const TopLevelSymbolToParts = js_ast.Ast.TopLevelSymbolToParts; -pub const WrapKind = enum { - none, - cjs, - esm, +pub const WrapKind = enum(u2) { + none = 0, + cjs = 1, + esm = 2, }; pub const ImportData = struct { @@ -1189,8 +1189,6 @@ pub const JSMeta = struct { flags: Flags = .{}, pub const Flags = packed struct { - wrap: WrapKind = WrapKind.none, - /// This is true if this file is affected by top-level await, either by having /// a top-level await inside this file or by having an import/export statement /// that transitively imports such a file. It is forbidden to call "require()" @@ -1218,6 +1216,8 @@ pub const JSMeta = struct { /// flag is used during the traversal that enforces this invariant, and is used /// to detect when the fixed point has been reached. did_wrap_dependencies: bool = false, + + wrap: WrapKind = WrapKind.none, }; }; @@ -2010,7 +2010,6 @@ const LinkerContext = struct { // other file is empty if (other_file >= exports_kind.len) continue; const other_kind = exports_kind[other_file]; - const other_wrap = flags[other_file].wrap; switch (record.kind) { ImportKind.stmt => { @@ -2036,7 +2035,7 @@ const LinkerContext = struct { // the namespace object must be created. if ((record.contains_import_star or record.contains_default_alias) and // TODO: hasLazyExport - (other_wrap == .none)) + exports_kind[other_file] == .none) { exports_kind[other_file] = .cjs; flags[other_file].wrap = .cjs; @@ -2076,6 +2075,7 @@ const LinkerContext = struct { // for entry point files in CommonJS format (or when in pass-through mode). if (kind == .cjs and (!entry_point_kinds[id].isEntryPoint() or output_format == .iife or output_format == .esm)) { flags[id].wrap = .cjs; + std.debug.assert(kind == .cjs); } } @@ -2220,21 +2220,22 @@ const LinkerContext = struct { ); } const export_kind = exports_kind[id]; - + var flag = flags[id]; // If we're exporting as CommonJS and this file was originally CommonJS, // then we'll be using the actual CommonJS "exports" and/or "module" // symbols. In that case make sure to mark them as such so they don't // get minified. if ((output_format == .cjs or output_format == .preserve) and entry_point_kinds[source_index].isEntryPoint() and - export_kind == .cjs and flags[id].wrap == .none) + export_kind == .cjs and flag.wrap == .none) { const exports_ref = symbols.follow(exports_refs[id]); const module_ref = symbols.follow(module_refs[id].?); symbols.get(exports_ref).?.kind = .unbound; symbols.get(module_ref).?.kind = .unbound; - } else if (flags[id].force_include_exports_for_entry_point or export_kind != .cjs) { - flags[id].needs_exports_variable = true; + } else if (flag.force_include_exports_for_entry_point or export_kind != .cjs) { + flag.needs_exports_variable = true; + flags[id] = flag; } const wrapped_ref = this.graph.ast.items(.wrapper_ref)[id] orelse continue; @@ -2242,7 +2243,7 @@ const LinkerContext = struct { // Create the wrapper part for wrapped files. This is needed by a later step. this.createWrapperForFile( - flags[id].wrap, + flag.wrap, // if this one is null, the AST does not need to be wrapped. wrapped_ref, &wrapper_part_indices[id], diff --git a/src/js_parser.zig b/src/js_parser.zig index 563170aeeb03..ba3116ec31e4 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -654,7 +654,7 @@ pub const ImportScanner = struct { } const namespace_ref = st.namespace_ref; - const convert_star_to_clause = !p.options.enable_bundling and !p.options.can_import_from_bundle and p.symbols.items[namespace_ref.innerIndex()].use_count_estimate == 0; + const convert_star_to_clause = !p.options.bundle or (!p.options.enable_bundling and !p.options.can_import_from_bundle and p.symbols.items[namespace_ref.innerIndex()].use_count_estimate == 0); if (convert_star_to_clause and !keep_unused_imports) { st.star_name_loc = null; @@ -665,35 +665,84 @@ pub const ImportScanner = struct { const existing_items: ImportItemForNamespaceMap = p.import_items_for_namespace.get(namespace_ref) orelse ImportItemForNamespaceMap.init(allocator); - // ESM requires live bindings - // CommonJS does not require live bindings - // We load ESM in browsers & in Bun.js - // We have to simulate live bindings for cases where the code is bundled - // We do not know at this stage whether or not the import statement is bundled - // This keeps track of the `namespace_alias` incase, at printing time, we determine that we should print it with the namespace - for (st.items) |item| { - const is_default = strings.eqlComptime(item.alias, "default"); - record.contains_default_alias = record.contains_default_alias or is_default; + if (p.options.bundle) { + if (st.star_name_loc != null and existing_items.count() > 0) { + var sorted = try allocator.alloc(string, existing_items.count()); + defer allocator.free(sorted); + for (sorted, existing_items.keys()) |*result, alias| { + result.* = alias; + } + strings.sortDesc(sorted); - const name: LocRef = item.name; - const name_ref = name.ref.?; + // Create named imports for these property accesses. This will + // cause missing imports to generate useful warnings. + // + // It will also improve bundling efficiency for internal imports + // by still converting property accesses off the namespace into + // bare identifiers even if the namespace is still needed. + for (sorted) |alias| { + const item = existing_items.get(alias).?; + p.named_imports.put( + item.ref.?, + js_ast.NamedImport{ + .alias = alias, + .alias_loc = item.loc, + .namespace_ref = namespace_ref, + .import_record_index = st.import_record_index, + }, + ) catch unreachable; - try p.named_imports.put(name_ref, js_ast.NamedImport{ - .alias = item.alias, - .alias_loc = name.loc, - .namespace_ref = namespace_ref, - .import_record_index = st.import_record_index, - }); + const name: LocRef = item; + const name_ref = name.ref.?; - // Make sure the printer prints this as a property access - var symbol: *Symbol = &p.symbols.items[name_ref.innerIndex()]; + // Make sure the printer prints this as a property access + var symbol: *Symbol = &p.symbols.items[name_ref.innerIndex()]; - symbol.namespace_alias = G.NamespaceAlias{ - .namespace_ref = namespace_ref, - .alias = item.alias, - .import_record_index = st.import_record_index, - .was_originally_property_access = st.star_name_loc != null and existing_items.contains(symbol.original_name), - }; + symbol.namespace_alias = G.NamespaceAlias{ + .namespace_ref = namespace_ref, + .alias = alias, + .import_record_index = st.import_record_index, + .was_originally_property_access = st.star_name_loc != null and existing_items.contains(symbol.original_name), + }; + + // Also record these automatically-generated top-level namespace alias symbols + p.declared_symbols.append(p.allocator, .{ + .ref = name_ref, + .is_top_level = true, + }) catch unreachable; + } + } + } else { + // ESM requires live bindings + // CommonJS does not require live bindings + // We load ESM in browsers & in Bun.js + // We have to simulate live bindings for cases where the code is bundled + // We do not know at this stage whether or not the import statement is bundled + // This keeps track of the `namespace_alias` incase, at printing time, we determine that we should print it with the namespace + for (st.items) |item| { + const is_default = strings.eqlComptime(item.alias, "default"); + record.contains_default_alias = record.contains_default_alias or is_default; + + const name: LocRef = item.name; + const name_ref = name.ref.?; + + try p.named_imports.put(name_ref, js_ast.NamedImport{ + .alias = item.alias, + .alias_loc = name.loc, + .namespace_ref = namespace_ref, + .import_record_index = st.import_record_index, + }); + + // Make sure the printer prints this as a property access + var symbol: *Symbol = &p.symbols.items[name_ref.innerIndex()]; + + symbol.namespace_alias = G.NamespaceAlias{ + .namespace_ref = namespace_ref, + .alias = item.alias, + .import_record_index = st.import_record_index, + .was_originally_property_access = st.star_name_loc != null and existing_items.contains(symbol.original_name), + }; + } } try p.import_records_for_current_part.append(allocator, st.import_record_index); @@ -1779,10 +1828,11 @@ const AsyncPrefixExpression = enum(u2) { } }; -const IdentifierOpts = struct { +const IdentifierOpts = packed struct { assign_target: js_ast.AssignTarget = js_ast.AssignTarget.none, is_delete_target: bool = false, was_originally_identifier: bool = false, + is_call_target: bool = false, }; fn statementCaresAboutScope(stmt: Stmt) bool { @@ -4533,6 +4583,11 @@ fn NewParser_( }) catch unreachable; } + // TODO: TypeScript namespace + // if (opts.assign_target == .none and !opts.is_delete_target and p.options.bundle) { + + // } + // Substitute an EImportIdentifier now if this is an import item if (p.is_import_item.contains(ref)) { return p.newExpr( @@ -12400,7 +12455,12 @@ fn NewParser_( value, part, loc, - false, + .{ + .is_call_target = false, + .assign_target = .none, + // .is_template_tag = false, + .is_delete_target = false, + }, )) |rewrote| { value = rewrote; } else { @@ -14189,7 +14249,12 @@ fn NewParser_( e_.target, literal, e_.index.loc, - is_call_target, + .{ + .is_call_target = is_call_target, + // .is_template_tag = is_template_tag, + .is_delete_target = is_delete_target, + .assign_target = in.assign_target, + }, )) |val| { return val; } @@ -14393,7 +14458,10 @@ fn NewParser_( e_.target, e_.name, e_.name_loc, - is_call_target, + .{ + .is_call_target = is_call_target, + // .is_template_tag = p.template_tag != null, + }, )) |_expr| { return _expr; } @@ -15521,14 +15589,64 @@ fn NewParser_( target: js_ast.Expr, name: string, name_loc: logger.Loc, - is_call_target: bool, + identifier_opts: IdentifierOpts, ) ?Expr { switch (target.data) { .e_identifier => |id| { + + // Rewrite property accesses on explicit namespace imports as an identifier. + // This lets us replace them easily in the printer to rebind them to + // something else without paying the cost of a whole-tree traversal during + // module linking just to rewrite these EDot expressions. + if (p.options.bundle) { + if (p.import_items_for_namespace.getPtr(id.ref)) |import_items| { + const ref = (import_items.get(name) orelse brk: { + // Generate a new import item symbol in the module scope + const new_item = LocRef{ + .loc = name_loc, + .ref = p.newSymbol(.import, name) catch unreachable, + }; + p.module_scope.generated.push(p.allocator, new_item.ref.?) catch unreachable; + + import_items.put(name, new_item) catch unreachable; + p.is_import_item.put(p.allocator, new_item.ref.?, {}) catch unreachable; + + var symbol = p.symbols.items[new_item.ref.?.innerIndex()]; + + // Mark this as generated in case it's missing. We don't want to + // generate errors for missing import items that are automatically + // generated. + symbol.import_item_status = .generated; + + break :brk new_item; + }).ref.?; + + // Undo the usage count for the namespace itself. This is used later + // to detect whether the namespace symbol has ever been "captured" + // or whether it has just been used to read properties off of. + // + // The benefit of doing this is that if both this module and the + // imported module end up in the same module group and the namespace + // symbol has never been captured, then we don't need to generate + // any code for the namespace at all. + p.ignoreUsage(id.ref); + + // Track how many times we've referenced this symbol + p.recordUsage(ref); + + return p.handleIdentifier( + name_loc, + E.Identifier{ .ref = ref }, + name, + identifier_opts, + ); + } + } + // Rewrite "module.require()" to "require()" for Webpack compatibility. // See https://github.com/webpack/webpack/pull/7750 for more info. // This also makes correctness a little easier. - if (is_call_target and id.ref.eql(p.module_ref) and strings.eqlComptime(name, "require")) { + if (identifier_opts.is_call_target and id.ref.eql(p.module_ref) and strings.eqlComptime(name, "require")) { p.ignoreUsage(p.module_ref); p.recordUsage(p.require_ref); return p.newExpr(E.Identifier{ .ref = p.require_ref }, name_loc); diff --git a/src/renamer.zig b/src/renamer.zig index c9829f50b34d..f2db3e59935d 100644 --- a/src/renamer.zig +++ b/src/renamer.zig @@ -100,7 +100,11 @@ pub const NumberRenamer = struct { // TODO: MustStartWithCapitalLetterForJSX - const name = scope.findUnusedName(r.allocator, symbol.original_name); + var name = scope.findUnusedName(r.allocator, symbol.original_name); + + if (strings.eqlLong(name, symbol.original_name, true)) { + return; + } const new_len = @max(inner.len, ref.innerIndex() + 1); if (inner.cap < new_len) { From 5db1e304115f129119f7e52d80981ff1145310db Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Tue, 7 Mar 2023 17:59:11 -0800 Subject: [PATCH 038/255] Attempt to fix the issue with import() unsuccessfully --- src/bundler/bundle_v2.zig | 91 ++++++++++++++++----------- src/defines-table.zig | 3 + src/defines.zig | 4 +- src/runtime.js | 11 +--- test/fixtures/bundle/trivial/index.js | 4 +- 5 files changed, 62 insertions(+), 51 deletions(-) diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 710b3a2efb87..2800b91474e3 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -1624,6 +1624,7 @@ const LinkerContext = struct { ignore_dce_annotations: bool = false, tree_shaking: bool = true, minify_whitespace: bool = false, + minify_syntax: bool = false, mode: Mode = Mode.bundle, @@ -5207,7 +5208,7 @@ const LinkerContext = struct { continue; } - if (index == wrapper_part_index.value) { + if (index == wrapper_part_index.get()) { // Skip the wrapper part because we already handled it above needs_wrapper = true; continue; @@ -5238,6 +5239,8 @@ const LinkerContext = struct { stmts.all_stmts.ensureUnusedCapacity(stmts.outside_wrapper_prefix.items.len + stmts.inside_wrapper_suffix.items.len) catch unreachable; stmts.all_stmts.appendSliceAssumeCapacity(stmts.inside_wrapper_prefix.items); stmts.all_stmts.appendSliceAssumeCapacity(stmts.inside_wrapper_suffix.items); + stmts.inside_wrapper_prefix.items.len = 0; + stmts.inside_wrapper_suffix.items.len = 0; var out_stmts: []js_ast.Stmt = stmts.all_stmts.items; // Optionally wrap all statements in a closure @@ -5338,45 +5341,44 @@ const LinkerContext = struct { // isn't async because then calling "require()" on that module would // swallow any exceptions thrown during module initialization. const is_async = flags.is_async_or_has_async_dependency; + const Hoisty = struct { + decls: std.ArrayList(G.Decl), + allocator: std.mem.Allocator, + pub fn wrapIdentifier(w: *@This(), loc: Logger.Loc, ref: Ref) Expr { + w.decls.append( + G.Decl{ + .binding = Binding.alloc( + w.allocator, + B.Identifier{ + .ref = ref, + }, + loc, + ), + .value = Expr.init( + E.Identifier, + E.Identifier{ + .ref = ref, + }, + loc, + ), + }, + ) catch unreachable; + return Expr.init( + E.Identifier, + E.Identifier{ + .ref = ref, + }, + loc, + ); + } + }; + var hoisty = Hoisty{ + .decls = std.ArrayList(G.Decl).init(temp_allocator), + .allocator = temp_allocator, + }; // Hoist all top-level "var" and "function" declarations out of the closure { - const Hoisty = struct { - decls: std.ArrayList(G.Decl), - allocator: std.mem.Allocator, - - pub fn wrapIdentifier(w: *@This(), loc: Logger.Loc, ref: Ref) Expr { - w.decls.append( - G.Decl{ - .binding = Binding.alloc( - w.allocator, - B.Identifier{ - .ref = ref, - }, - loc, - ), - .value = Expr.init( - E.Identifier, - E.Identifier{ - .ref = ref, - }, - loc, - ), - }, - ) catch unreachable; - return Expr.init( - E.Identifier, - E.Identifier{ - .ref = ref, - }, - loc, - ); - } - }; - var hoisty = Hoisty{ - .decls = std.ArrayList(G.Decl).init(temp_allocator), - .allocator = temp_allocator, - }; var end: usize = 0; for (stmts.all_stmts.items) |stmt_| { var stmt: Stmt = stmt_; @@ -5419,7 +5421,18 @@ const LinkerContext = struct { stmts.all_stmts.items.len = end; } - // TODO: variants of the runtime functions + if (!c.options.minify_syntax and hoisty.decls.items.len > 0) { + stmts.outside_wrapper_prefix.append( + Stmt.alloc( + S.Local, + S.Local{ + .decls = hoisty.decls.items, + }, + Logger.Loc.Empty, + ), + ) catch unreachable; + hoisty.decls.items.len = 0; + } // "__esm(() => { ... })" var esm_args = temp_allocator.alloc(Expr, 1) catch unreachable; @@ -6097,6 +6110,7 @@ const LinkerContext = struct { .dependencies = Dependency.List.init(dependencies), }, ) catch unreachable; + std.debug.assert(part_index != js_ast.namespace_export_part_index); wrapper_part_index.* = Index.part(part_index); c.graph.generateSymbolImportAndUse( source_index, @@ -6145,6 +6159,7 @@ const LinkerContext = struct { .dependencies = Dependency.List.init(dependencies), }, ) catch unreachable; + std.debug.assert(part_index != js_ast.namespace_export_part_index); wrapper_part_index.* = Index.part(part_index); c.graph.generateSymbolImportAndUse( source_index, diff --git a/src/defines-table.zig b/src/defines-table.zig index 86fe6344defb..fe86baf7f6c4 100644 --- a/src/defines-table.zig +++ b/src/defines-table.zig @@ -187,6 +187,9 @@ pub const GlobalDefinesKey = [_][]const string{ &[_]string{"setTimeout"}, &[_]string{"unescape"}, + &[_]string{ "Symbol", "for" }, + &[_]string{ "Symbol", "keyFor" }, + // Reflect: Static methods // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect#static_methods &[_]string{ "Reflect", "apply" }, diff --git a/src/defines.zig b/src/defines.zig index c1026a7d2b16..f3a6c5e2bb94 100644 --- a/src/defines.zig +++ b/src/defines.zig @@ -260,8 +260,8 @@ pub const Define = struct { define.allocator = allocator; define.identifiers = bun.StringHashMap(IdentifierDefine).init(allocator); define.dots = bun.StringHashMap([]DotDefine).init(allocator); - try define.identifiers.ensureTotalCapacity(641 + 2 + 1); - try define.dots.ensureTotalCapacity(64); + try define.identifiers.ensureTotalCapacity(641 + 2 + 1 + 2); + try define.dots.ensureTotalCapacity(124); var val = js_ast.Expr.Data{ .e_undefined = .{}, diff --git a/src/runtime.js b/src/runtime.js index cd2d2e82d09d..b452fd724556 100644 --- a/src/runtime.js +++ b/src/runtime.js @@ -1,3 +1,5 @@ +const tagSymbol = Symbol.for("CommonJSTransformed"); +const cjsRequireSymbol = Symbol.for("CommonJS"); var __create = Object.create; var __descs = Object.getOwnPropertyDescriptors; var __defProp = Object.defineProperty; @@ -48,8 +50,6 @@ export var __toModule = mod => { ); }; -var tagSymbol = Symbol.for("CommonJSTransformed"); -var cjsRequireSymbol = Symbol.for("CommonJS"); export var __commonJS = (cb, name) => { var mod; var origExports; @@ -138,13 +138,6 @@ export var __require = namespace => { return namespace; }; -// require().default -// this currently does nothing -// get rid of this wrapper once we're more confident we do not need special handling for default -__require.d = namespace => { - return namespace; -}; - export var $$m = __commonJS; export var __name = (target, name) => { diff --git a/test/fixtures/bundle/trivial/index.js b/test/fixtures/bundle/trivial/index.js index 6988ba77b51c..15a8c7af44fa 100644 --- a/test/fixtures/bundle/trivial/index.js +++ b/test/fixtures/bundle/trivial/index.js @@ -1,5 +1,5 @@ -const Handler = import("./fn"); +const NS = import("./fn.js"); -Handler.then(({ fn }) => { +NS.then(({ fn }) => { console.log(fn(42)); }); From f4ea29ca4a33e2940692c3387d11c2deac63cdf7 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sun, 5 Mar 2023 18:54:00 +0200 Subject: [PATCH 039/255] consider current working directory when resolving relative paths (#2313) * consider current working directory when resolving relative paths fixes #2298 * comment test --------- Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> --- .github/workflows/bun-linux-build.yml | 2 +- src/resolver/resolve_path.zig | 66 ++++++++++++++------------- test/bun.js/install/bunx.test.ts | 30 +++++++++++- test/bun.js/path.test.js | 1 + 4 files changed, 65 insertions(+), 34 deletions(-) diff --git a/.github/workflows/bun-linux-build.yml b/.github/workflows/bun-linux-build.yml index 748c14c55017..2b3074b8757a 100644 --- a/.github/workflows/bun-linux-build.yml +++ b/.github/workflows/bun-linux-build.yml @@ -190,7 +190,7 @@ jobs: bun install bun install --cwd test/bun.js bun install --cwd test/bun.js/third-party/body-parser-test - bun install --cwd test/bun.js/third-party/napi_create_external + #bun install --cwd test/bun.js/third-party/napi_create_external bun install --cwd packages/bun-internal-test node packages/bun-internal-test/src/runner.node.mjs || true - name: Comment on PR diff --git a/src/resolver/resolve_path.zig b/src/resolver/resolve_path.zig index c44c2597d325..b1cd6e0bffd8 100644 --- a/src/resolver/resolve_path.zig +++ b/src/resolver/resolve_path.zig @@ -4,6 +4,8 @@ const strings = @import("../string_immutable.zig"); const FeatureFlags = @import("../feature_flags.zig"); const default_allocator = @import("../memory_allocator.zig").c_allocator; const bun = @import("bun"); +const Fs = @import("../fs.zig"); + threadlocal var parser_join_input_buffer: [4096]u8 = undefined; threadlocal var parser_buffer: [1024]u8 = undefined; @@ -153,9 +155,9 @@ pub fn longestCommonPathGeneric(input: []const []const u8, comptime separator: u // To detect /app/public is actually a folder, we do one more loop through the strings // and say, "do one of you have a path separator after what we thought was the end?" for (input) |str| { - if (str.len > index + 1) { + if (str.len > index) { if (@call(.always_inline, isPathSeparator, .{str[index]})) { - return str[0 .. index + 2]; + return str[0 .. index + 1]; } } } @@ -235,12 +237,11 @@ pub fn relativeToCommonPath( while (i <= normalized_from.len) : (i += 1) { if (i == normalized_from.len or (normalized_from[i] == separator and i + 1 < normalized_from.len)) { if (out_slice.len == 0) { - out_slice = buf[0 .. out_slice.len + 2]; - out_slice[0..2].* = "..".*; + buf[0..2].* = "..".*; + out_slice.len = 2; } else { - const old_len = out_slice.len; + buf[out_slice.len..][0..3].* = "/..".*; out_slice.len += 3; - out_slice[old_len..][0..3].* = "/..".*; } } } @@ -254,23 +255,20 @@ pub fn relativeToCommonPath( } } - const insert_leading_slash = last_common_separator > 0 and normalized_to[last_common_separator] != separator and tail[0] != separator; - // avoid making non-absolute paths absolute + const insert_leading_slash = tail[0] != separator and out_slice.len > 0 and out_slice[out_slice.len - 1] != separator; if (insert_leading_slash) { buf[out_slice.len] = separator; - out_slice = buf[0 .. out_slice.len + 1]; + out_slice.len += 1; } // Lastly, append the rest of the destination (`to`) path that comes after // the common path parts. - const start = out_slice.len; - out_slice = buf[0 .. out_slice.len + tail.len]; - - bun.copy(u8, out_slice[start..], tail); + bun.copy(u8, buf[out_slice.len..], tail); + out_slice.len += tail.len; } - return buf[0..out_slice.len]; + return out_slice; } pub fn relativeNormalized(from: []const u8, to: []const u8, comptime platform: Platform, comptime always_copy: bool) []const u8 { @@ -314,27 +312,31 @@ pub fn relative(from: []const u8, to: []const u8) []const u8 { } pub fn relativePlatform(from: []const u8, to: []const u8, comptime platform: Platform, comptime always_copy: bool) []const u8 { - const from_allow_above_root = (from.len > 1 and from[0] == platform.separator()); - const to_allow_above_root = (to.len > 1 and to[0] == platform.separator()); - var normalized_from = - if (!from_allow_above_root) - normalizeStringBuf(from, relative_from_buf[1..], false, platform, true) - else - normalizeStringBuf(from, relative_from_buf[1..], true, platform, true); - var normalized_to = - if (!to_allow_above_root) - normalizeStringBuf(to, relative_to_buf[1..], false, platform, true) - else - normalizeStringBuf(to, relative_to_buf[1..], true, platform, true); - - if (from_allow_above_root == to_allow_above_root and from_allow_above_root) { + const normalized_from = if (from.len > 0 and from[0] == platform.separator()) brk: { + var path = normalizeStringBuf(from, relative_from_buf[1..], true, platform, true); relative_from_buf[0] = platform.separator(); - normalized_from = relative_from_buf[0 .. normalized_from.len + 1]; + break :brk relative_from_buf[0 .. path.len + 1]; + } else joinAbsStringBuf( + Fs.FileSystem.instance.top_level_dir, + &relative_from_buf, + &[_][]const u8{ + normalizeStringBuf(from, relative_from_buf[1..], false, platform, true), + }, + platform, + ); + const normalized_to = if (to.len > 0 and to[0] == platform.separator()) brk: { + var path = normalizeStringBuf(to, relative_to_buf[1..], true, platform, true); relative_to_buf[0] = platform.separator(); - normalized_to = relative_to_buf[0 .. normalized_to.len + 1]; - } - // + break :brk relative_to_buf[0 .. path.len + 1]; + } else joinAbsStringBuf( + Fs.FileSystem.instance.top_level_dir, + &relative_to_buf, + &[_][]const u8{ + normalizeStringBuf(to, relative_to_buf[1..], false, platform, true), + }, + platform, + ); return relativeNormalized(normalized_from, normalized_to, platform, always_copy); } diff --git a/test/bun.js/install/bunx.test.ts b/test/bun.js/install/bunx.test.ts index e43dd673d3df..06266fe948de 100644 --- a/test/bun.js/install/bunx.test.ts +++ b/test/bun.js/install/bunx.test.ts @@ -11,7 +11,7 @@ import { readdirSorted } from "./dummy.registry"; let x_dir; beforeEach(async () => { - x_dir = realpathSync(await mkdtemp(join(tmpdir(), "bun-install.test"))); + x_dir = realpathSync(await mkdtemp(join(tmpdir(), "bun-x.test"))); }); afterEach(async () => { await rm(x_dir, { force: true, recursive: true }); @@ -179,3 +179,31 @@ for (const entry of await decompress(Buffer.from(buffer))) { expect(await exited).toBe(0); expect(await readdirSorted(x_dir)).toEqual([".cache", "test.js"]); }); + +it("should execute from current working directory", async () => { + await writeFile( + join(x_dir, "test.js"), + ` +console.log( +6 +* +7 +)`, + ); + const { stdout, stderr, exited } = spawn({ + cmd: [bunExe(), "--bun", "x", "uglify-js", "test.js", "--compress"], + cwd: x_dir, + stdout: null, + stdin: "pipe", + stderr: "pipe", + env, + }); + expect(stderr).toBeDefined(); + const err = await new Response(stderr).text(); + expect(err).not.toContain("error"); + expect(stdout).toBeDefined(); + const out = await new Response(stdout).text(); + expect(out.split(/\r?\n/)).toEqual(["console.log(42);", ""]); + expect(await exited).toBe(0); + expect(await readdirSorted(x_dir)).toEqual(["test.js"]); +}); diff --git a/test/bun.js/path.test.js b/test/bun.js/path.test.js index 176d98c8eaed..ad5688ea7b2d 100644 --- a/test/bun.js/path.test.js +++ b/test/bun.js/path.test.js @@ -274,6 +274,7 @@ it("path.relative", () => { ["/baz-quux", "/baz", "../baz"], ["/baz", "/baz-quux", "../baz-quux"], ["/page1/page2/foo", "/", "../../.."], + [process.cwd(), "foo", "foo"], ], ], ]; From 843ac05365f9ac4bd517d0bec81d2d63a7c136d4 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Mon, 6 Mar 2023 03:33:38 +0200 Subject: [PATCH 040/255] support `expect().toThrow(/pattern/)` (#2314) - fix time-zone-dependent test failure --- packages/bun-types/bun-test.d.ts | 2 +- src/bun.js/test/jest.zig | 77 ++++++++++++++- test/bun.js/console/console-log.expected.txt | 2 +- test/bun.js/console/console-log.js | 2 +- test/bun.js/crypto-scrypt.test.js | 99 +++++++------------- test/bun.js/test-test.test.ts | 13 ++- 6 files changed, 124 insertions(+), 71 deletions(-) diff --git a/packages/bun-types/bun-test.d.ts b/packages/bun-types/bun-test.d.ts index 5e3adf0412d4..1b7ae47a6241 100644 --- a/packages/bun-types/bun-test.d.ts +++ b/packages/bun-types/bun-test.d.ts @@ -76,7 +76,7 @@ declare module "bun:test" { toBeGreaterThanOrEqual(value: number | bigint): void; toBeLessThan(value: number | bigint): void; toBeLessThanOrEqual(value: number | bigint): void; - toThrow(error?: string | Error | ErrorConstructor): void; + toThrow(error?: string | Error | ErrorConstructor | RegExp): void; } } diff --git a/src/bun.js/test/jest.zig b/src/bun.js/test/jest.zig index 20e11e13268b..5571294c7ce8 100644 --- a/src/bun.js/test/jest.zig +++ b/src/bun.js/test/jest.zig @@ -1768,7 +1768,7 @@ pub const Expect = struct { if (expected_value.isString()) { const received_message = result.getIfPropertyExistsImpl(globalObject, "message", 7); - // partial match (regex not supported) + // partial match { var expected_string = ZigString.Empty; var received_string = ZigString.Empty; @@ -1798,6 +1798,29 @@ pub const Expect = struct { return .zero; } + if (expected_value.isRegExp()) { + const received_message = result.getIfPropertyExistsImpl(globalObject, "message", 7); + + if (expected_value.get(globalObject, "test")) |test_fn| { + const matches = test_fn.callWithThis(globalObject, expected_value, &.{received_message}); + if (!matches.toBooleanSlow(globalObject)) return thisValue; + } + + const fmt = signature ++ "\n\nExpected pattern: not {any}\nReceived message: {any}\n"; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{ + expected_value.toFmt(globalObject, &formatter), + received_message.toFmt(globalObject, &formatter), + }); + return .zero; + } + globalObject.throw(Output.prettyFmt(fmt, false), .{ + expected_value.toFmt(globalObject, &formatter), + received_message.toFmt(globalObject, &formatter), + }); + return .zero; + } + if (expected_value.get(globalObject, "message")) |expected_message| { const received_message = result.getIfPropertyExistsImpl(globalObject, "message", 7); // no partial match for this case @@ -1835,7 +1858,7 @@ pub const Expect = struct { if (expected_value.isString()) { if (_received_message) |received_message| { - // partial match (regex not supported) + // partial match var expected_string = ZigString.Empty; var received_string = ZigString.Empty; expected_value.toZigString(&expected_string, globalObject); @@ -1877,6 +1900,42 @@ pub const Expect = struct { return .zero; } + if (expected_value.isRegExp()) { + if (_received_message) |received_message| { + if (expected_value.get(globalObject, "test")) |test_fn| { + const matches = test_fn.callWithThis(globalObject, expected_value, &.{received_message}); + if (matches.toBooleanSlow(globalObject)) return thisValue; + } + } + + // error: message from received error does not match expected pattern + var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject, .quote_strings = true }; + + if (_received_message) |received_message| { + const expected_value_fmt = expected_value.toFmt(globalObject, &formatter); + const received_message_fmt = received_message.toFmt(globalObject, &formatter); + const fmt = signature ++ "\n\n" ++ "Expected pattern: {any}\nReceived message: {any}\n"; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{ expected_value_fmt, received_message_fmt }); + return .zero; + } + + globalObject.throw(Output.prettyFmt(fmt, false), .{ expected_value_fmt, received_message_fmt }); + return .zero; + } + + const expected_fmt = expected_value.toFmt(globalObject, &formatter); + const received_fmt = result.toFmt(globalObject, &formatter); + const fmt = signature ++ "\n\n" ++ "Expected pattern: {any}\nReceived value: {any}"; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{ expected_fmt, received_fmt }); + return .zero; + } + + globalObject.throw(Output.prettyFmt(fmt, false), .{ expected_fmt, received_fmt }); + return .zero; + } + if (expected_value.get(globalObject, "message")) |expected_message| { if (_received_message) |received_message| { if (received_message.isSameValue(expected_message, globalObject)) return thisValue; @@ -1985,6 +2044,18 @@ pub const Expect = struct { return .zero; } + if (expected_value.isRegExp()) { + const expected_fmt = "\n\nExpected pattern: {any}\n\n" ++ received_line; + const fmt = signature ++ expected_fmt; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{expected_value.toFmt(globalObject, &formatter)}); + return .zero; + } + + globalObject.throw(Output.prettyFmt(fmt, false), .{expected_value.toFmt(globalObject, &formatter)}); + return .zero; + } + if (expected_value.get(globalObject, "message")) |expected_message| { const expected_fmt = "\n\nExpected message: {any}\n\n" ++ received_line; const fmt = signature ++ expected_fmt; @@ -2275,7 +2346,7 @@ pub const TestScope = struct { task, ); task.done_callback_state = .pending; - initial_value = JSValue.fromRef(callback.?).call(vm.global, &.{callback_func}); + initial_value = JSValue.fromRef(callback).call(vm.global, &.{callback_func}); } else { initial_value = js.JSObjectCallAsFunctionReturnValue(vm.global, callback, null, 0, null); } diff --git a/test/bun.js/console/console-log.expected.txt b/test/bun.js/console/console-log.expected.txt index 69fb3eeba980..97191c8be3ca 100644 --- a/test/bun.js/console/console-log.expected.txt +++ b/test/bun.js/console/console-log.expected.txt @@ -8,7 +8,7 @@ false null undefined Symbol(Symbol Description) -2022-02-27T05:11:48.999Z +2000-06-27T02:24:34.304Z [ 123, 456, 789 ] { name: "foo" diff --git a/test/bun.js/console/console-log.js b/test/bun.js/console/console-log.js index 5468c1c986c1..e23a3e9cb935 100644 --- a/test/bun.js/console/console-log.js +++ b/test/bun.js/console/console-log.js @@ -8,7 +8,7 @@ console.log(false); console.log(null); console.log(undefined); console.log(Symbol("Symbol Description")); -console.log(new Date(2021, 12, 30, 666, 777, 888, 999)); +console.log(new Date(Math.pow(2, 34) * 56)); console.log([123, 456, 789]); console.log({ name: "foo" }); console.log({ a: 123, b: 456, c: 789 }); diff --git a/test/bun.js/crypto-scrypt.test.js b/test/bun.js/crypto-scrypt.test.js index e188e9fae2fd..4b7412251b7b 100644 --- a/test/bun.js/crypto-scrypt.test.js +++ b/test/bun.js/crypto-scrypt.test.js @@ -3,35 +3,6 @@ import { expect, it } from "bun:test"; const crypto = require("crypto"); -const assert = { - strictEqual: (a, b) => { - expect(a).toEqual(b); - }, - deepStrictEqual: (a, b) => { - expect(a).toEqual(b); - }, - throws: (fn, err) => { - try { - fn(); - throw "Fail"; - } catch (e) { - if (err.name) { - expect(e?.name).toEqual(err.name); - } - - // if (err.message) { - // expect(err.message.test(e?.message)).toBeTruthy(); - // } - if (err.code) { - expect(e?.code).toEqual(err.code); - } - - expect(e).not.toEqual("Fail"); - return; - } - }, -}; - const good = [ // Zero-length key is legal, functions as a parameter validation check. { @@ -157,8 +128,24 @@ const badargs = [ args: ["", "", null], expected: { code: "ERR_INVALID_ARG_TYPE" /*message: /"keylen"/ */ }, }, + { + args: ["", "", 42, null], + expected: { code: "ERR_INVALID_ARG_TYPE" }, + }, // TODO: throw on these // { + // args: ["", "", 42, {}], + // expected: { code: "ERR_INVALID_ARG_TYPE" }, + // }, + // { + // args: ["", "", 42, {}, {}], + // expected: { code: "ERR_INVALID_ARG_TYPE" }, + // }, + // { + // args: ["", "", 42, {}, null], + // expected: { code: "ERR_INVALID_ARG_TYPE" }, + // }, + // { // args: ["", "", 0.42], // expected: { code: "ERR_OUT_OF_RANGE" /*message: /"keylen"/ */ }, // }, @@ -170,41 +157,37 @@ const badargs = [ // args: ["", "", 2147485780], // expected: { code: "ERR_OUT_OF_RANGE" /*message: /"keylen"/ */ }, // }, + // { + // args: ["", "", 0, { maxmem: 2 ** 53 }], + // expected: { code: "ERR_OUT_OF_RANGE" /*message: /"keylen"/ */ }, + // }, ]; it("scrypt good", () => { for (const options of good) { const { pass, salt, keylen, expected } = options; const actual = crypto.scryptSync(pass, salt, keylen, options); - assert.strictEqual(actual.toString("hex"), expected); + expect(actual.toString("hex")).toBe(expected); } }); it("scrypt bad", () => { for (const options of bad) { - const expected = { - message: /Invalid scrypt param/, - }; - assert.throws(() => crypto.scryptSync("pass", "salt", 1, options), expected); + expect(() => crypto.scryptSync("pass", "salt", 1, options)).toThrow(/Invalid scrypt param/); } }); it("scrypt toobig", () => { for (const options of toobig) { - const expected = { - message: /Invalid scrypt param/, - }; - assert.throws(() => crypto.scryptSync("pass", "salt", 1, options), expected); + expect(() => crypto.scryptSync("pass", "salt", 1, options)).toThrow(/Invalid scrypt param/); } }); it("scrypt defaults eql", () => { - { - const defaults = { N: 16384, p: 1, r: 8 }; - const expected = crypto.scryptSync("pass", "salt", 1, defaults); - const actual = crypto.scryptSync("pass", "salt", 1); - assert.deepStrictEqual(actual.toString("hex"), expected.toString("hex")); - } + const defaults = { N: 16384, p: 1, r: 8 }; + const expected = crypto.scryptSync("pass", "salt", 1, defaults); + const actual = crypto.scryptSync("pass", "salt", 1); + expect(actual.toString("hex")).toBe(expected.toString("hex")); }); // TODO: DEFAULT_ENCODING is read-only @@ -217,27 +200,23 @@ it("scrypt defaults eql", () => { // const testEncoding = "latin1"; // crypto.DEFAULT_ENCODING = testEncoding; // const actual = crypto.scryptSync("pass", "salt", 1); -// assert.deepStrictEqual(actual, expected.toString(testEncoding)); +// expect(actual).toBe(expected.toString(testEncoding)); // crypto.DEFAULT_ENCODING = defaultEncoding; // } // }); it("scrypt badargs", () => { - { - for (const { args, expected } of badargs) { - assert.throws(() => crypto.scryptSync(...args), expected); + for (const { args, expected } of badargs) { + try { + crypto.scryptSync(...args); + expect(() => {}).toThrow(); + } catch (e) { + if (!("code" in e)) throw e; + expect(e.code).toBe(expected.code); } } - { - const expected = { code: "ERR_INVALID_ARG_TYPE" }; - assert.throws(() => crypto.scryptSync("", "", 42, null), expected); - // assert.throws(() => crypto.scryptSync("", "", 42, {}, null), expected); - // assert.throws(() => crypto.scryptSync("", "", 42, {}), expected); - // assert.throws(() => crypto.scryptSync("", "", 42, {}, {}), expected); - } - // { // // Values for maxmem that do not fit in 32 bits but that are still safe // // integers should be allowed. @@ -247,13 +226,7 @@ it("scrypt badargs", () => { // 4, // { maxmem: 2 ** 52 }, // common.mustSucceed((actual) => { - // assert.strictEqual(actual.toString("hex"), "d72c87d0"); + // expect(actual.toString("hex")).toBe("d72c87d0"); // }), // ); - - // // Values that exceed Number.isSafeInteger should not be allowed. - // assert.throws(() => crypto.scryptSync("", "", 0, { maxmem: 2 ** 53 }), { - // code: "ERR_OUT_OF_RANGE", - // }); - // } }); diff --git a/test/bun.js/test-test.test.ts b/test/bun.js/test-test.test.ts index cc6d967c2ee2..c83ee07a53d4 100644 --- a/test/bun.js/test-test.test.ts +++ b/test/bun.js/test-test.test.ts @@ -156,11 +156,20 @@ test("toThrow", () => { throw err; }).toThrow(err); - var err = new Error("good"); expect(() => { - throw err; + throw new Error("good"); }).toThrow(); + expect(() => { + throw new Error("foo"); + }).toThrow(/oo/); + + expect(() => + expect(() => { + throw new Error("bar"); + }).toThrow(/baz/), + ).toThrow("/baz/"); + expect(() => { return true; }).not.toThrow(); From 80d5fdd367e94fbbb65cf3e647b32aee007c3774 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Mon, 6 Mar 2023 08:45:27 +0200 Subject: [PATCH 041/255] fix missing `Blob` error messages on Linux (#2315) --- src/bun.js/webcore/blob.zig | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/bun.js/webcore/blob.zig b/src/bun.js/webcore/blob.zig index ae60ef01f9b5..c610b199dc8f 100644 --- a/src/bun.js/webcore/blob.zig +++ b/src/bun.js/webcore/blob.zig @@ -1188,11 +1188,11 @@ pub const Blob = struct { else this.file_blob.store.?.data.file.pathlike.path; - this.system_error = .{ - .syscall = ZigString.init("open"), - .code = ZigString.init(bun.asByteSlice(@errorName(this.errno.?))), - .path = ZigString.init(path_string.slice()), - }; + this.system_error = (JSC.Node.Syscall.Error{ + .errno = @intCast(JSC.Node.Syscall.Error.Int, -completion.result), + .path = path_string.slice(), + .syscall = .open, + }).toSystemError(); // assert we never end up reusing the memory std.debug.assert(@ptrToInt(this.system_error.?.path.slice().ptr) != @ptrToInt(path_buffer)); From deeebf4289961225d93e0004c1e9aad10765bc93 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Mon, 6 Mar 2023 22:09:44 +0200 Subject: [PATCH 042/255] fix & clean up tests (#2318) - skip flaky tests when running as `root` - use `expect().toThrow()` - clean up temporary files after tests --- packages/bun-types/globals.d.ts | 5 +- test/bun.js/{fetch.test.js => fetch.test.ts} | 276 +++++++++---------- 2 files changed, 133 insertions(+), 148 deletions(-) rename test/bun.js/{fetch.test.js => fetch.test.ts} (84%) diff --git a/packages/bun-types/globals.d.ts b/packages/bun-types/globals.d.ts index e44bcf43dabe..2dd86f91129e 100644 --- a/packages/bun-types/globals.d.ts +++ b/packages/bun-types/globals.d.ts @@ -447,6 +447,7 @@ interface Headers { entries(): IterableIterator<[string, string]>; keys(): IterableIterator; values(): IterableIterator; + [Symbol.iterator](): IterableIterator<[string, string]>; forEach( callbackfn: (value: string, key: string, parent: Headers) => void, thisArg?: any, @@ -533,7 +534,7 @@ interface FormData { has(name: string): boolean; set(name: string, value: string | Blob, fileName?: string): void; keys(): IterableIterator; - values(): IterableIterator; + values(): IterableIterator; entries(): IterableIterator<[string, FormDataEntryValue]>; [Symbol.iterator](): IterableIterator<[string, FormDataEntryValue]>; forEach( @@ -1494,7 +1495,7 @@ interface AbortController { /** * Invoking this method will set this object's AbortSignal's aborted flag and signal to any observers that the associated activity is to be aborted. */ - abort(): void; + abort(reason?: any): void; } /** EventTarget is a DOM interface implemented by objects that can receive events and may have listeners for them. */ diff --git a/test/bun.js/fetch.test.js b/test/bun.js/fetch.test.ts similarity index 84% rename from test/bun.js/fetch.test.js rename to test/bun.js/fetch.test.ts index be64a01093cf..f5f264dd7519 100644 --- a/test/bun.js/fetch.test.js +++ b/test/bun.js/fetch.test.ts @@ -1,36 +1,38 @@ -import { afterAll, afterEach, beforeAll, describe, expect, it, test, beforeEach } from "bun:test"; -import fs, { chmodSync, unlinkSync } from "fs"; +import { serve, sleep } from "bun"; +import { afterAll, afterEach, beforeAll, describe, expect, it, beforeEach } from "bun:test"; +import { chmodSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from "fs"; import { mkfifo } from "mkfifo"; +import { tmpdir } from "os"; +import { join } from "path"; import { gc, withoutAggressiveGC } from "./gc"; -const sleep = countdown => { - return Bun.sleep(countdown); -}; +const tmp_dir = mkdtempSync(join(realpathSync(tmpdir()), "fetch.test")); -const exampleFixture = fs.readFileSync( - import.meta.path.substring(0, import.meta.path.lastIndexOf("/")) + "/fetch.js.txt", - "utf8", -); +const fixture = readFileSync(join(import.meta.dir, "fetch.js.txt"), "utf8"); -var cachedServer; -function getServer({ port, ...rest }) { - return (cachedServer = Bun.serve({ - ...rest, +let server; +function startServer({ fetch, ...options }) { + server = serve({ + ...options, + fetch, port: 0, - })); + }); } afterEach(() => { - cachedServer?.stop?.(true); + server?.stop?.(true); +}); + +afterAll(() => { + rmSync(tmp_dir, { force: true, recursive: true }); }); const payload = new Uint8Array(1024 * 1024 * 2); crypto.getRandomValues(payload); describe("AbortSignal", () => { - var server; beforeEach(() => { - server = getServer({ + startServer({ async fetch(request) { if (request.url.endsWith("/nodelay")) { return new Response("Hello"); @@ -61,12 +63,15 @@ describe("AbortSignal", () => { }, }); }); + afterEach(() => { + server?.stop?.(true); + }); + it("AbortError", async () => { - let name; - try { - var controller = new AbortController(); - const signal = controller.signal; + const controller = new AbortController(); + const signal = controller.signal; + expect(async () => { async function manualAbort() { await sleep(1); controller.abort(); @@ -75,56 +80,47 @@ describe("AbortSignal", () => { fetch(`http://127.0.0.1:${server.port}`, { signal: signal }).then(res => res.text()), manualAbort(), ]); - } catch (error) { - name = error.name; - } - expect(name).toBe("AbortError"); + }).toThrow(new DOMException("The operation was aborted.")); }); it("AbortAfterFinish", async () => { - let error = undefined; - try { - var controller = new AbortController(); - const signal = controller.signal; + const controller = new AbortController(); + const signal = controller.signal; - await fetch(`http://127.0.0.1:${server.port}/nodelay`, { signal: signal }).then(res => res.text()); - controller.abort(); - } catch (ex) { - error = ex; - } - expect(error).toBeUndefined(); + await fetch(`http://127.0.0.1:${server.port}/nodelay`, { signal: signal }).then(async res => + expect(await res.text()).toBe("Hello"), + ); + controller.abort(); }); it("AbortErrorWithReason", async () => { - let reason; - try { - var controller = new AbortController(); - const signal = controller.signal; + const controller = new AbortController(); + const signal = controller.signal; + expect(async () => { async function manualAbort() { await sleep(10); - controller.abort("My Reason"); + controller.abort(new Error("My Reason")); } await Promise.all([ fetch(`http://127.0.0.1:${server.port}`, { signal: signal }).then(res => res.text()), manualAbort(), ]); - } catch (error) { - reason = error; - } - expect(reason).toBe("My Reason"); + }).toThrow("My Reason"); }); it("AbortErrorEventListener", async () => { - let name; - try { - var controller = new AbortController(); - const signal = controller.signal; - var eventSignal = undefined; - signal.addEventListener("abort", ev => { - eventSignal = ev.currentTarget; - }); + const controller = new AbortController(); + const signal = controller.signal; + signal.addEventListener("abort", ev => { + const target = ev.currentTarget; + expect(target).toBeDefined(); + expect(target.aborted).toBe(true); + expect(target.reason).toBeDefined(); + expect(target.reason.name).toBe("AbortError"); + }); + expect(async () => { async function manualAbort() { await sleep(10); controller.abort(); @@ -133,69 +129,59 @@ describe("AbortSignal", () => { fetch(`http://127.0.0.1:${server.port}`, { signal: signal }).then(res => res.text()), manualAbort(), ]); - } catch (error) { - name = error.name; - } - expect(eventSignal).toBeDefined(); - expect(eventSignal.reason.name).toBe(name); - expect(eventSignal.aborted).toBe(true); + }).toThrow(new DOMException("The operation was aborted.")); }); it("AbortErrorWhileUploading", async () => { - const abortController = new AbortController(); - let error; - try { + const controller = new AbortController(); + + expect(async () => { await fetch(`http://localhost:${server.port}`, { method: "POST", body: new ReadableStream({ - pull(controller) { - controller.enqueue(new Uint8Array([1, 2, 3, 4])); + pull(event_controller) { + event_controller.enqueue(new Uint8Array([1, 2, 3, 4])); //this will abort immediately should abort before connected - abortController.abort(); + controller.abort(); }, }), - signal: abortController.signal, + signal: controller.signal, }); - } catch (ex) { - error = ex; - } - - expect(error.name).toBe("AbortError"); - expect(error.message).toBe("The operation was aborted."); - expect(error instanceof DOMException).toBeTruthy(); + }).toThrow(new DOMException("The operation was aborted.")); }); it("TimeoutError", async () => { - let name; + const signal = AbortSignal.timeout(10); + try { - const signal = AbortSignal.timeout(10); await fetch(`http://127.0.0.1:${server.port}`, { signal: signal }).then(res => res.text()); - } catch (error) { - name = error.name; + expect(() => {}).toThrow(); + } catch (ex: any) { + expect(ex.name).toBe("TimeoutError"); } - expect(name).toBe("TimeoutError"); }); + it("Request", async () => { - let name; + const controller = new AbortController(); + const signal = controller.signal; + async function manualAbort() { + await sleep(10); + controller.abort(); + } + try { - var controller = new AbortController(); - const signal = controller.signal; const request = new Request(`http://127.0.0.1:${server.port}`, { signal }); - async function manualAbort() { - await sleep(10); - controller.abort(); - } await Promise.all([fetch(request).then(res => res.text()), manualAbort()]); - } catch (error) { - name = error.name; + expect(() => {}).toThrow(); + } catch (ex: any) { + expect(ex.name).toBe("AbortError"); } - expect(name).toBe("AbortError"); }); }); describe("Headers", () => { it(".toJSON", () => { - var headers = new Headers({ + const headers = new Headers({ "content-length": "123", "content-type": "text/plain", "x-another-custom-header": "Hello World", @@ -207,7 +193,7 @@ describe("Headers", () => { }); it(".getSetCookie() with object", () => { - var headers = new Headers({ + const headers = new Headers({ "content-length": "123", "content-type": "text/plain", "x-another-custom-header": "Hello World", @@ -219,7 +205,7 @@ describe("Headers", () => { }); it(".getSetCookie() with array", () => { - var headers = new Headers([ + const headers = new Headers([ ["content-length", "123"], ["content-type", "text/plain"], ["x-another-custom-header", "Hello World"], @@ -298,13 +284,15 @@ describe("fetch", () => { ]; for (let url of urls) { gc(); - let name = url; - if (name instanceof URL) { - name = "URL: " + name; - } else if (name instanceof Request) { - name = "Request: " + name.url; - } else if (name.hasOwnProperty("toString")) { - name = "Object: " + name.toString(); + let name; + if (url instanceof URL) { + name = "URL: " + url; + } else if (url instanceof Request) { + name = "Request: " + url.url; + } else if (url.hasOwnProperty("toString")) { + name = "Object: " + url.toString(); + } else { + name = url; } it(name, async () => { gc(); @@ -312,12 +300,12 @@ describe("fetch", () => { gc(); const text = await response.text(); gc(); - expect(exampleFixture).toBe(text); + expect(fixture).toBe(text); }); } - it(`"redirect: "manual"`, async () => { - const server = getServer({ + it('redirect: "manual"', async () => { + startServer({ fetch(req) { return new Response(null, { status: 302, @@ -335,8 +323,8 @@ describe("fetch", () => { expect(response.redirected).toBe(true); }); - it(`"redirect: "follow"`, async () => { - const server = getServer({ + it('redirect: "follow"', async () => { + startServer({ fetch(req) { return new Response(null, { status: 302, @@ -355,10 +343,11 @@ describe("fetch", () => { }); it("provide body", async () => { - const server = getServer({ + startServer({ fetch(req) { return new Response(req.body); }, + host: "localhost", }); // POST with body @@ -366,15 +355,16 @@ describe("fetch", () => { const response = await fetch(url, { method: "POST", body: "buntastic" }); expect(response.status).toBe(200); expect(await response.text()).toBe("buntastic"); - - // GET cannot have body - try { - await fetch(url, { body: "buntastic" }); - expect(false).toBe(true); - } catch (exception) { - expect(exception.name).toBe("TypeError"); - } }); + + ["GET", "HEAD", "OPTIONS"].forEach(method => + it(`fail on ${method} with body`, async () => { + const url = `http://${server.hostname}:${server.port}`; + expect(async () => { + await fetch(url, { body: "buntastic" }); + }).toThrow("fetch() request with GET/HEAD/OPTIONS method cannot have body."); + }), + ); }); it("simultaneous HTTPS fetch", async () => { @@ -388,7 +378,7 @@ it("simultaneous HTTPS fetch", async () => { expect(result.length).toBe(20); for (let i = 0; i < 20; i++) { expect(result[i].status).toBe(200); - expect(await result[i].text()).toBe(exampleFixture); + expect(await result[i].text()).toBe(fixture); } } }); @@ -398,7 +388,7 @@ it("website with tlsextname", async () => { await fetch("https://bun.sh", { method: "HEAD" }); }); -function testBlobInterface(blobbyConstructor, hasBlobFn) { +function testBlobInterface(blobbyConstructor, hasBlobFn?) { for (let withGC of [false, true]) { for (let jsonObject of [ { hello: true }, @@ -548,65 +538,59 @@ function testBlobInterface(blobbyConstructor, hasBlobFn) { } describe("Bun.file", () => { - const tempdir = require("os").tmpdir(); - const { join } = require("path"); - var callCount = 0; + let count = 0; testBlobInterface(data => { const blob = new Blob([data]); const buffer = Bun.peek(blob.arrayBuffer()); - const path = join(tempdir, "tmp-" + callCount++ + ".bytes"); - require("fs").writeFileSync(path, buffer); + const path = join(tmp_dir, `tmp-${count++}.bytes`); + writeFileSync(path, buffer); const file = Bun.file(path); expect(blob.size).toBe(file.size); return file; }); it("size is Infinity on a fifo", () => { - try { - unlinkSync("/tmp/test-fifo"); - } catch (e) {} - mkfifo("/tmp/test-fifo"); - - const { size } = Bun.file("/tmp/test-fifo"); + const path = join(tmp_dir, "test-fifo"); + mkfifo(path); + const { size } = Bun.file(path); expect(size).toBe(Infinity); }); - function forEachMethod(fn) { + function forEachMethod(fn, skip?) { const method = ["arrayBuffer", "text", "json"]; for (const m of method) { - test(m, fn(m)); + (skip ? it.skip : it)(m, fn(m)); } } describe("bad permissions throws", () => { + const path = join(tmp_dir, "my-new-file"); beforeAll(async () => { - try { - unlinkSync("/tmp/my-new-file"); - } catch {} - await Bun.write("/tmp/my-new-file", "hey"); - chmodSync("/tmp/my-new-file", 0o000); - }); - afterAll(() => { - try { - unlinkSync("/tmp/my-new-file"); - } catch {} + await Bun.write(path, "hey"); + chmodSync(path, 0o000); }); - forEachMethod(m => () => { - const file = Bun.file("/tmp/my-new-file"); - expect(async () => await file[m]()).toThrow("Permission denied"); - }); + forEachMethod( + m => () => { + const file = Bun.file(path); + expect(async () => await file[m]()).toThrow("Permission denied"); + }, + () => { + try { + readFileSync(path); + } catch { + return false; + } + return true; + }, + ); }); describe("non-existent file throws", () => { - beforeAll(() => { - try { - unlinkSync("/tmp/does-not-exist"); - } catch {} - }); + const path = join(tmp_dir, "does-not-exist"); forEachMethod(m => async () => { - const file = Bun.file("/tmp/does-not-exist"); + const file = Bun.file(path); expect(async () => await file[m]()).toThrow("No such file or directory"); }); }); From 2e0118e6a5c0f8f4c79fae1f46bbd056a9155c5b Mon Sep 17 00:00:00 2001 From: Derrick Farris Date: Mon, 6 Mar 2023 13:09:52 -0800 Subject: [PATCH 043/255] feat(tty): add some `tty.WriteStream` methods to `process.{stdout, stderr}` (#2320) * feat(stdio): add some `tty.WriteStream` methods * chore(builtins): add process builtin gen'd code --- .../cpp/ProcessObjectInternalsBuiltins.cpp | 25 ++++++++++++++++++- .../builtins/js/ProcessObjectInternals.js | 23 +++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/bun.js/builtins/cpp/ProcessObjectInternalsBuiltins.cpp b/src/bun.js/builtins/cpp/ProcessObjectInternalsBuiltins.cpp index 574d18f20d9a..d44656f75374 100644 --- a/src/bun.js/builtins/cpp/ProcessObjectInternalsBuiltins.cpp +++ b/src/bun.js/builtins/cpp/ProcessObjectInternalsBuiltins.cpp @@ -51,7 +51,7 @@ namespace WebCore { const JSC::ConstructAbility s_processObjectInternalsGetStdioWriteStreamCodeConstructAbility = JSC::ConstructAbility::CannotConstruct; const JSC::ConstructorKind s_processObjectInternalsGetStdioWriteStreamCodeConstructorKind = JSC::ConstructorKind::None; const JSC::ImplementationVisibility s_processObjectInternalsGetStdioWriteStreamCodeImplementationVisibility = JSC::ImplementationVisibility::Public; -const int s_processObjectInternalsGetStdioWriteStreamCodeLength = 9767; +const int s_processObjectInternalsGetStdioWriteStreamCodeLength = 10295; static const JSC::Intrinsic s_processObjectInternalsGetStdioWriteStreamCodeIntrinsic = JSC::NoIntrinsic; const char* const s_processObjectInternalsGetStdioWriteStreamCode = "(function (fd_, rawRequire) {\n" \ @@ -224,6 +224,8 @@ const char* const s_processObjectInternalsGetStdioWriteStreamCode = " return normalied === \"utf8\" || normalied === \"utf-8\" || normalied === \"buffer\" || normalied === \"binary\";\n" \ " }\n" \ "\n" \ + " var readline;\n" \ + "\n" \ " var FastStdioWriteStream = class StdioWriteStream extends EventEmitter {\n" \ " #fd;\n" \ " #innerStream;\n" \ @@ -275,6 +277,27 @@ const char* const s_processObjectInternalsGetStdioWriteStreamCode = " return (this.#isTTY ??= require(\"node:tty\").isatty(this.#fd));\n" \ " }\n" \ "\n" \ + " cursorTo(x, y, callback) {\n" \ + " return (readline ??= require(\"readline\")).cursorTo(this, x, y, callback);\n" \ + " }\n" \ + "\n" \ + " moveCursor(dx, dy, callback) {\n" \ + " return (readline ??= require(\"readline\")).moveCursor(this, dx, dy, callback);\n" \ + " }\n" \ + "\n" \ + " clearLine(dir, callback) {\n" \ + " return (readline ??= require(\"readline\")).clearLine(this, dir, callback);\n" \ + " }\n" \ + "\n" \ + " clearScreenDown(callback) {\n" \ + " return (readline ??= require(\"readline\")).clearScreenDown(this, callback);\n" \ + " }\n" \ + "\n" \ + " //\n" \ + " //\n" \ + " //\n" \ + " //\n" \ + "\n" \ " ref() {\n" \ " this.#getWriter().ref();\n" \ " }\n" \ diff --git a/src/bun.js/builtins/js/ProcessObjectInternals.js b/src/bun.js/builtins/js/ProcessObjectInternals.js index 723528fecfff..c1898d686743 100644 --- a/src/bun.js/builtins/js/ProcessObjectInternals.js +++ b/src/bun.js/builtins/js/ProcessObjectInternals.js @@ -191,6 +191,8 @@ function getStdioWriteStream(fd_, rawRequire) { return normalied === "utf8" || normalied === "utf-8" || normalied === "buffer" || normalied === "binary"; } + var readline; + var FastStdioWriteStream = class StdioWriteStream extends EventEmitter { #fd; #innerStream; @@ -242,6 +244,27 @@ function getStdioWriteStream(fd_, rawRequire) { return (this.#isTTY ??= require("node:tty").isatty(this.#fd)); } + cursorTo(x, y, callback) { + return (readline ??= require("readline")).cursorTo(this, x, y, callback); + } + + moveCursor(dx, dy, callback) { + return (readline ??= require("readline")).moveCursor(this, dx, dy, callback); + } + + clearLine(dir, callback) { + return (readline ??= require("readline")).clearLine(this, dir, callback); + } + + clearScreenDown(callback) { + return (readline ??= require("readline")).clearScreenDown(this, callback); + } + + // TODO: once implemented this.columns and this.rows should be uncommented + // getWindowSize() { + // return [this.columns, this.rows]; + // } + ref() { this.#getWriter().ref(); } From 8294166068792fe33e5825c9c337f6689e92946b Mon Sep 17 00:00:00 2001 From: Ashcon Partovi Date: Mon, 6 Mar 2023 14:05:43 -0800 Subject: [PATCH 044/255] Fix docker install command --- docs/installation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/installation.md b/docs/installation.md index b0cc07456bd7..e826ace408b9 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -22,8 +22,8 @@ $ brew install bun ``` ```bash#Docker -$ docker pull oven/bun:edge -$ docker run --rm --init --ulimit memlock=-1:-1 oven/bun:edge +$ docker pull oven/bun +$ docker run --rm --init --ulimit memlock=-1:-1 oven/bun ``` {% /codetabs %} From fae236c27b697f9b843adf69968120847dd82cab Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Tue, 7 Mar 2023 01:22:42 +0200 Subject: [PATCH 045/255] `bun test` on macOS in GitHub Actions (#2322) --- .github/workflows/bun-linux-build.yml | 2 +- .github/workflows/bun-mac-x64-baseline.yml | 73 ++++++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/.github/workflows/bun-linux-build.yml b/.github/workflows/bun-linux-build.yml index 2b3074b8757a..75c84c7f625e 100644 --- a/.github/workflows/bun-linux-build.yml +++ b/.github/workflows/bun-linux-build.yml @@ -170,7 +170,7 @@ jobs: submodules: false - id: download name: Download - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: name: bun-${{matrix.tag}} path: ${{runner.temp}}/release diff --git a/.github/workflows/bun-mac-x64-baseline.yml b/.github/workflows/bun-mac-x64-baseline.yml index d8ddd18c43a4..c4309a66c121 100644 --- a/.github/workflows/bun-mac-x64-baseline.yml +++ b/.github/workflows/bun-mac-x64-baseline.yml @@ -388,3 +388,76 @@ jobs: name: "Canary (${{github.sha}})" tag: "canary" artifacts: "${{runner.temp}}/release/${{matrix.tag}}.zip,${{runner.temp}}/release/${{matrix.tag}}-profile.zip" + macOS-test: + name: Tests ${{matrix.tag}} + runs-on: ${{ matrix.runner }} + needs: [macOS] + if: github.event_name == 'pull_request' + timeout-minutes: 10 + outputs: + failing_tests: ${{ steps.test.outputs.failing_tests }} + failing_tests_count: ${{ steps.test.outputs.failing_tests_count }} + strategy: + fail-fast: false + matrix: + include: + - tag: bun-darwin-x64-baseline + steps: + - id: checkout + name: Checkout + uses: actions/checkout@v3 + with: + submodules: false + - id: download + name: Download + uses: actions/download-artifact@v3 + with: + name: ${{matrix.tag}} + path: ${{runner.temp}}/release + - id: install + name: Install + run: | + cd ${{runner.temp}}/release + unzip ${{matrix.tag}}.zip + cd ${{matrix.tag}} + chmod +x bun + sudo mv bun /usr/local/bin/bun + bun --version + - id: test + name: Test (node runner) + # if: ${{github.event.inputs.use_bun == 'false'}} + run: | + bun install + bun install --cwd test/bun.js + bun install --cwd test/bun.js/third-party/body-parser-test + #bun install --cwd test/bun.js/third-party/napi_create_external + bun install --cwd packages/bun-internal-test + node packages/bun-internal-test/src/runner.node.mjs || true + - name: Comment on PR + if: steps.test.outputs.failing_tests != '' && github.event_name == 'pull_request' + uses: thollander/actions-comment-pull-request@v2 + with: + comment_tag: test-failures-${{matrix.tag}} + message: | + ❌ @${{ github.actor }} ${{ steps.test.outputs.failing_tests_count }} files with test failures on ${{ matrix.tag }}: + + ${{ steps.test.outputs.failing_tests }} + + **[View test output](https://github.com/oven-sh/bun/actions/runs/${{github.run_id}})** + + [#${{github.sha}}](https://github.com/oven-sh/bun/commits/${{github.sha}}) + - name: Uncomment on PR + if: steps.test.outputs.failing_tests == '' && github.event_name == 'pull_request' + uses: thollander/actions-comment-pull-request@v2 + with: + comment_tag: test-failures-${{matrix.tag}} + mode: upsert + create_if_not_exists: false + message: | + ✅ test failures on ${{ matrix.tag }} have been resolved. + + [#${{github.sha}}](https://github.com/oven-sh/bun/commits/${{github.sha}}) + - id: fail + name: Fail the build + if: steps.test.outputs.failing_tests != '' + run: exit 1 From d3a93c480baa2bca86eba1efb8af00b5e5a98b95 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Mon, 6 Mar 2023 19:22:52 -0800 Subject: [PATCH 046/255] Fixes #2323 --- src/deps/lol-html.zig | 64 +++++++++++++++++++------------ test/bun.js/html-rewriter.test.js | 28 ++++++++++++++ 2 files changed, 68 insertions(+), 24 deletions(-) diff --git a/src/deps/lol-html.zig b/src/deps/lol-html.zig index bb4f4c371eed..ff7b02ed18fa 100644 --- a/src/deps/lol-html.zig +++ b/src/deps/lol-html.zig @@ -11,6 +11,21 @@ inline fn auto_disable() void { unreachable; } +/// rust panics if the pointer itself is zero, even if the passed length is zero +/// to work around that, we use a static null-terminated pointer +/// https://github.com/oven-sh/bun/issues/2323 +fn ptrWithoutPanic(buf: []const u8) [*]const u8 { + const null_terminated_ptr = struct { + // we must use a static pointer so the lifetime of this pointer is long enough + const null_terminated_ptr: []const u8 = &[_]u8{0}; + }.null_terminated_ptr; + + if (buf.len == 0) + return null_terminated_ptr.ptr; + + return buf.ptr; +} + pub const HTMLRewriter = opaque { extern fn lol_html_rewriter_write(rewriter: *HTMLRewriter, chunk: [*]const u8, chunk_len: usize) c_int; extern fn lol_html_rewriter_end(rewriter: *HTMLRewriter) c_int; @@ -18,7 +33,7 @@ pub const HTMLRewriter = opaque { pub fn write(rewriter: *HTMLRewriter, chunk: []const u8) Error!void { auto_disable(); - if (rewriter.lol_html_rewriter_write(chunk.ptr, chunk.len) < 0) + if (rewriter.lol_html_rewriter_write(ptrWithoutPanic(chunk), chunk.len) < 0) return error.Fail; } @@ -275,7 +290,7 @@ pub const HTMLSelector = opaque { pub fn parse(selector: []const u8) Error!*HTMLSelector { auto_disable(); - if (lol_html_selector_parse(selector.ptr, selector.len)) |ptr| + if (lol_html_selector_parse(ptrWithoutPanic(selector), selector.len)) |ptr| return ptr else return error.Fail; @@ -318,7 +333,7 @@ pub const TextChunk = opaque { /// can be obtained using `lol_html_take_last_error` function. pub fn before(this: *TextChunk, content: []const u8, is_html: bool) Error!void { auto_disable(); - if (this.lol_html_text_chunk_before(content.ptr, content.len, is_html) < 0) + if (this.lol_html_text_chunk_before(ptrWithoutPanic(content), content.len, is_html) < 0) return error.Fail; } /// Inserts the content string after the text chunk either as raw text or as HTML. @@ -329,7 +344,7 @@ pub const TextChunk = opaque { /// can be obtained using `lol_html_take_last_error` function. pub fn after(this: *TextChunk, content: []const u8, is_html: bool) Error!void { auto_disable(); - if (this.lol_html_text_chunk_after(content.ptr, content.len, is_html) < 0) + if (this.lol_html_text_chunk_after(ptrWithoutPanic(content), content.len, is_html) < 0) return error.Fail; } // Replace the text chunk with the content of the string which is interpreted @@ -341,7 +356,7 @@ pub const TextChunk = opaque { // can be obtained using `lol_html_take_last_error` function. pub fn replace(this: *TextChunk, content: []const u8, is_html: bool) Error!void { auto_disable(); - if (this.lol_html_text_chunk_replace(content.ptr, content.len, is_html) < 0) + if (this.lol_html_text_chunk_replace(ptrWithoutPanic(content), content.len, is_html) < 0) return error.Fail; } /// Removes the text chunk. @@ -382,11 +397,11 @@ pub const Element = opaque { pub fn getAttribute(element: *const Element, name: []const u8) HTMLString { auto_disable(); - return lol_html_element_get_attribute(element, name.ptr, name.len); + return lol_html_element_get_attribute(element, ptrWithoutPanic(name), name.len); } pub fn hasAttribute(element: *const Element, name: []const u8) Error!bool { auto_disable(); - return switch (lol_html_element_has_attribute(element, name.ptr, name.len)) { + return switch (lol_html_element_has_attribute(element, ptrWithoutPanic(name), name.len)) { 0 => false, 1 => true, -1 => error.Fail, @@ -395,7 +410,7 @@ pub const Element = opaque { } pub fn setAttribute(element: *Element, name: []const u8, value: []const u8) Error!void { auto_disable(); - return switch (lol_html_element_set_attribute(element, name.ptr, name.len, value.ptr, value.len)) { + return switch (lol_html_element_set_attribute(element, ptrWithoutPanic(name), name.len, ptrWithoutPanic(value), value.len)) { 0 => void{}, -1 => error.Fail, else => unreachable, @@ -403,7 +418,7 @@ pub const Element = opaque { } pub fn removeAttribute(element: *Element, name: []const u8) Error!void { auto_disable(); - return switch (lol_html_element_remove_attribute(element, name.ptr, name.len)) { + return switch (lol_html_element_remove_attribute(element, ptrWithoutPanic(name), name.len)) { 0 => void{}, -1 => error.Fail, else => unreachable, @@ -411,7 +426,7 @@ pub const Element = opaque { } pub fn before(element: *Element, content: []const u8, is_html: bool) Error!void { auto_disable(); - return switch (lol_html_element_before(element, content.ptr, content.len, is_html)) { + return switch (lol_html_element_before(element, ptrWithoutPanic(content), content.len, is_html)) { 0 => void{}, -1 => error.Fail, else => unreachable, @@ -419,7 +434,7 @@ pub const Element = opaque { } pub fn prepend(element: *Element, content: []const u8, is_html: bool) Error!void { auto_disable(); - return switch (lol_html_element_prepend(element, content.ptr, content.len, is_html)) { + return switch (lol_html_element_prepend(element, ptrWithoutPanic(content), content.len, is_html)) { 0 => void{}, -1 => error.Fail, else => unreachable, @@ -427,7 +442,7 @@ pub const Element = opaque { } pub fn append(element: *Element, content: []const u8, is_html: bool) Error!void { auto_disable(); - return switch (lol_html_element_append(element, content.ptr, content.len, is_html)) { + return switch (lol_html_element_append(element, ptrWithoutPanic(content), content.len, is_html)) { 0 => void{}, -1 => error.Fail, else => unreachable, @@ -435,7 +450,7 @@ pub const Element = opaque { } pub fn after(element: *Element, content: []const u8, is_html: bool) Error!void { auto_disable(); - return switch (lol_html_element_after(element, content.ptr, content.len, is_html)) { + return switch (lol_html_element_after(element, ptrWithoutPanic(content), content.len, is_html)) { 0 => void{}, -1 => error.Fail, else => unreachable, @@ -443,7 +458,8 @@ pub const Element = opaque { } pub fn setInnerContent(element: *Element, content: []const u8, is_html: bool) Error!void { auto_disable(); - return switch (lol_html_element_set_inner_content(element, content.ptr, content.len, is_html)) { + + return switch (lol_html_element_set_inner_content(element, ptrWithoutPanic(content), content.len, is_html)) { 0 => void{}, -1 => error.Fail, else => unreachable, @@ -457,7 +473,7 @@ pub const Element = opaque { /// can be obtained using `lol_html_take_last_error` function. pub fn replace(element: *Element, content: []const u8, is_html: bool) Error!void { auto_disable(); - return switch (lol_html_element_replace(element, content.ptr, content.len, is_html)) { + return switch (lol_html_element_replace(element, ptrWithoutPanic(content), content.len, is_html)) { 0 => void{}, -1 => error.Fail, else => unreachable, @@ -503,7 +519,7 @@ pub const Element = opaque { } pub fn setTagName(element: *Element, name: []const u8) Error!void { - return switch (lol_html_element_tag_name_set(element, name.ptr, name.len)) { + return switch (lol_html_element_tag_name_set(element, ptrWithoutPanic(name), name.len)) { 0 => void{}, -1 => error.Fail, else => unreachable, @@ -554,7 +570,7 @@ pub const EndTag = opaque { pub fn before(end_tag: *EndTag, content: []const u8, is_html: bool) Error!void { auto_disable(); - return switch (lol_html_end_tag_before(end_tag, content.ptr, content.len, is_html)) { + return switch (lol_html_end_tag_before(end_tag, ptrWithoutPanic(content), content.len, is_html)) { 0 => void{}, -1 => error.Fail, else => unreachable, @@ -563,7 +579,7 @@ pub const EndTag = opaque { pub fn after(end_tag: *EndTag, content: []const u8, is_html: bool) Error!void { auto_disable(); - return switch (lol_html_end_tag_after(end_tag, content.ptr, content.len, is_html)) { + return switch (lol_html_end_tag_after(end_tag, ptrWithoutPanic(content), content.len, is_html)) { 0 => void{}, -1 => error.Fail, else => unreachable, @@ -581,7 +597,7 @@ pub const EndTag = opaque { pub fn setName(end_tag: *EndTag, name: []const u8) Error!void { auto_disable(); - return switch (lol_html_end_tag_name_set(end_tag, name.ptr, name.len)) { + return switch (lol_html_end_tag_name_set(end_tag, ptrWithoutPanic(name), name.len)) { 0 => void{}, -1 => error.Fail, else => unreachable, @@ -635,7 +651,7 @@ pub const Comment = opaque { pub fn setText(comment: *Comment, text: []const u8) Error!void { auto_disable(); - return switch (lol_html_comment_text_set(comment, text.ptr, text.len)) { + return switch (lol_html_comment_text_set(comment, ptrWithoutPanic(text), text.len)) { 0 => void{}, -1 => error.Fail, else => unreachable, @@ -644,7 +660,7 @@ pub const Comment = opaque { pub fn before(comment: *Comment, content: []const u8, is_html: bool) Error!void { auto_disable(); - return switch (lol_html_comment_before(comment, content.ptr, content.len, is_html)) { + return switch (lol_html_comment_before(comment, ptrWithoutPanic(content), content.len, is_html)) { 0 => void{}, -1 => error.Fail, else => unreachable, @@ -653,7 +669,7 @@ pub const Comment = opaque { pub fn replace(comment: *Comment, content: []const u8, is_html: bool) Error!void { auto_disable(); - return switch (lol_html_comment_before(comment, content.ptr, content.len, is_html)) { + return switch (lol_html_comment_before(comment, ptrWithoutPanic(content), content.len, is_html)) { 0 => void{}, -1 => error.Fail, else => unreachable, @@ -662,7 +678,7 @@ pub const Comment = opaque { pub fn after(comment: *Comment, content: []const u8, is_html: bool) Error!void { auto_disable(); - return switch (lol_html_comment_after(comment, content.ptr, content.len, is_html)) { + return switch (lol_html_comment_after(comment, ptrWithoutPanic(content), content.len, is_html)) { 0 => void{}, -1 => error.Fail, else => unreachable, @@ -687,7 +703,7 @@ pub const DocEnd = opaque { pub fn append(this: *DocEnd, content: []const u8, is_html: bool) Error!void { auto_disable(); - return switch (lol_html_doc_end_append(this, content.ptr, content.len, is_html)) { + return switch (lol_html_doc_end_append(this, ptrWithoutPanic(content), content.len, is_html)) { 0 => void{}, -1 => error.Fail, else => unreachable, diff --git a/test/bun.js/html-rewriter.test.js b/test/bun.js/html-rewriter.test.js index 660c6e4f6f7e..e33710f28e71 100644 --- a/test/bun.js/html-rewriter.test.js +++ b/test/bun.js/html-rewriter.test.js @@ -272,4 +272,32 @@ describe("HTMLRewriter", () => { "

1

new3
", ); }); + + it("supports deleting innerContent", async () => { + expect( + await new HTMLRewriter() + .on("div", { + element(elem) { + // https://github.com/oven-sh/bun/issues/2323 + elem.setInnerContent(""); + }, + }) + .transform(new Response("
content
")) + .text(), + ).toEqual("
"); + }); + + it("supports deleting innerHTML", async () => { + expect( + await new HTMLRewriter() + .on("div", { + element(elem) { + // https://github.com/oven-sh/bun/issues/2323 + elem.setInnerContent("", { html: true }); + }, + }) + .transform(new Response("
content
")) + .text(), + ).toEqual("
"); + }); }); From a218e1863d999ba1cde96b927b86f1425ddd295e Mon Sep 17 00:00:00 2001 From: Dylan Conway <35280289+dylan-conway@users.noreply.github.com> Date: Mon, 6 Mar 2023 20:00:50 -0800 Subject: [PATCH 047/255] throw invalid parameter errors in `crypto.scryptSync` (#2331) * throw invalid parameter errors * remove comptime, add empty buffer function * remove error_name comptime --- src/bun.js/base.zig | 8 ++ src/bun.js/bindings/bindings.zig | 13 ++ src/bun.js/webcore.zig | 209 +++++++++++++++---------------- 3 files changed, 125 insertions(+), 105 deletions(-) diff --git a/src/bun.js/base.zig b/src/bun.js/base.zig index c8c794cc784e..f9a52b7145a9 100644 --- a/src/bun.js/base.zig +++ b/src/bun.js/base.zig @@ -1772,6 +1772,14 @@ pub const ArrayBuffer = extern struct { }; } + pub fn createEmpty(globalThis: *JSC.JSGlobalObject, comptime kind: JSC.JSValue.JSType) JSValue { + return switch (comptime kind) { + .Uint8Array => Bun__createUint8ArrayForCopy(globalThis, null, 0, false), + .ArrayBuffer => Bun__createArrayBufferForCopy(globalThis, null, 0), + else => @compileError("Not implemented yet"), + }; + } + pub fn createBuffer(globalThis: *JSC.JSGlobalObject, bytes: []const u8) JSValue { JSC.markBinding(@src()); return Bun__createUint8ArrayForCopy(globalThis, bytes.ptr, bytes.len, true); diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig index 71295e157bd2..49ff393a205e 100644 --- a/src/bun.js/bindings/bindings.zig +++ b/src/bun.js/bindings/bindings.zig @@ -2414,6 +2414,19 @@ pub const JSGlobalObject = extern struct { return err; } + pub fn createError( + this: *JSGlobalObject, + code: JSC.Node.ErrorCode, + error_name: string, + comptime message: string, + args: anytype, + ) JSValue { + const err = createErrorInstance(this, message, args); + err.put(this, ZigString.static("code"), ZigString.init(@tagName(code)).toValue(this)); + err.put(this, ZigString.static("name"), ZigString.init(error_name).toValue(this)); + return err; + } + pub fn throw( this: *JSGlobalObject, comptime fmt: string, diff --git a/src/bun.js/webcore.zig b/src/bun.js/webcore.zig index c65c02ffd3eb..c6481438b7a4 100644 --- a/src/bun.js/webcore.zig +++ b/src/bun.js/webcore.zig @@ -8,6 +8,7 @@ pub usingnamespace @import("./webcore/body.zig"); const JSC = @import("bun").JSC; const std = @import("std"); const bun = @import("bun"); +const string = bun.string; pub const AbortSignal = @import("./bindings/bindings.zig").AbortSignal; pub const Lifetime = enum { @@ -388,88 +389,6 @@ pub const Crypto = struct { .{}, ); - pub fn scryptSyncValidate( - globalThis: *JSC.JSGlobalObject, - options: ?JSC.JSValue, - ) JSC.JSValue { - var blockSize: usize = 8; - var cost: usize = 16384; - var parallelization: usize = 1; - var maxmem: usize = 32 * 1024 * 1024; - - if (options) |options_value| outer: { - if (options_value.isUndefined() or options_value == .zero) - break :outer; - - if (!options_value.isObject()) { - return globalThis.createInvalidArgs("options must be an object", .{}); - } - - if (options_value.getTruthy(globalThis, "cost") orelse options_value.get(globalThis, "N")) |N_value| { - const N_int = N_value.to(i64); - if (N_int < 0 or !N_value.isNumber()) { - return globalThis.createRangeError("N must be a positive integer", .{}); - } else if (N_int != 0) { - cost = @intCast(usize, N_int); - } - } - - if (options_value.getTruthy(globalThis, "blockSize") orelse options_value.get(globalThis, "r")) |r_value| { - const r_int = r_value.to(i64); - if (r_int < 0 or !r_value.isNumber()) { - return globalThis.createRangeError("r must be a positive integer", .{}); - } else if (r_int != 0) { - blockSize = @intCast(usize, r_int); - } - } - - if (options_value.getTruthy(globalThis, "parallelization") orelse options_value.get(globalThis, "p")) |p_value| { - const p_int = p_value.to(i64); - if (p_int < 0 or !p_value.isNumber()) { - return globalThis.createRangeError("p must be a positive integer", .{}); - } else if (p_int != 0) { - parallelization = @intCast(usize, p_int); - } - } - - if (options_value.getTruthy(globalThis, "maxmem")) |value| { - const p_int = value.to(i64); - if (p_int < 0 or !value.isNumber()) { - return globalThis.createInvalidArgs("maxmem must be a positive integer", .{}); - } else if (p_int != 0) { - maxmem = @intCast(usize, p_int); - } - } - } - - if (cost < 2 or cost > 0x3fffffff) { - return globalThis.createRangeError("N must be greater than 1 and less than 2^30", .{}); - } - - if (cost == 0 or (cost & (cost - 1)) != 0) { - return globalThis.createRangeError("N must be a power of 2 greater than 1", .{}); - } - - if ((BoringSSL.EVP_PBE_scrypt( - null, - 0, - null, - 0, - cost, - blockSize, - parallelization, - maxmem, - null, - 0, - ) != 1)) { - BoringSSL.ERR_clear_error(); - return globalThis.createErrorInstance("scrypt parameters are invalid", .{}); - } - var slice: []u8 = undefined; - slice.len = 0; - return JSC.ArrayBuffer.create(globalThis, slice, .ArrayBuffer); - } - pub fn scryptSync( globalThis: *JSC.JSGlobalObject, password: JSC.Node.StringOrBuffer, @@ -480,22 +399,24 @@ pub const Crypto = struct { const password_string = password.slice(); const salt_string = salt.slice(); - if (keylen_value.isEmptyOrUndefinedOrNull()) { - return globalThis.createInvalidArgs("keylen must be a number", .{}); + if (keylen_value.isEmptyOrUndefinedOrNull() or !keylen_value.isAnyInt()) { + const err = globalThis.createInvalidArgs("keylen must be an integer", .{}); + globalThis.throwValue(err); + return .zero; } const keylen_int = keylen_value.to(i64); if (keylen_int < 0) { - return globalThis.createRangeError("keylen must be a positive integer", .{}); - } else if (keylen_int == 0) { - return scryptSyncValidate(globalThis, options); + globalThis.throwValue(globalThis.createRangeError("keylen must be a positive integer", .{})); + return .zero; } else if (keylen_int > 0x7fffffff) { - return globalThis.createRangeError("keylen must be less than 2^31", .{}); + globalThis.throwValue(globalThis.createRangeError("keylen must be less than 2^31", .{})); + return .zero; } - var blockSize: usize = 8; - var cost: usize = 16384; - var parallelization: usize = 1; + var blockSize: ?usize = null; + var cost: ?usize = null; + var parallelization: ?usize = null; var maxmem: usize = 32 * 1024 * 1024; const keylen = @intCast(u32, @truncate(i33, keylen_int)); @@ -504,31 +425,50 @@ pub const Crypto = struct { break :outer; if (!options_value.isObject()) { - return globalThis.createInvalidArgs("options must be an object", .{}); + globalThis.throwValue(globalThis.createInvalidArgs("options must be an object", .{})); + return .zero; } if (options_value.getTruthy(globalThis, "cost") orelse options_value.get(globalThis, "N")) |N_value| { + if (cost != null) return throwInvalidParameter(globalThis); const N_int = N_value.to(i64); if (N_int < 0 or !N_value.isNumber()) { - return globalThis.createRangeError("N must be a positive integer", .{}); + return throwInvalidParams( + globalThis, + "RangeError", + "Invalid scrypt params\n\n N must be a positive integer\n", + .{}, + ); } else if (N_int != 0) { cost = @intCast(usize, N_int); } } if (options_value.getTruthy(globalThis, "blockSize") orelse options_value.get(globalThis, "r")) |r_value| { + if (blockSize != null) return throwInvalidParameter(globalThis); const r_int = r_value.to(i64); if (r_int < 0 or !r_value.isNumber()) { - return globalThis.createRangeError("r must be a positive integer", .{}); + return throwInvalidParams( + globalThis, + "RangeError", + "Invalid scrypt params\n\n r must be a positive integer\n", + .{}, + ); } else if (r_int != 0) { blockSize = @intCast(usize, r_int); } } if (options_value.getTruthy(globalThis, "parallelization") orelse options_value.get(globalThis, "p")) |p_value| { + if (parallelization != null) return throwInvalidParameter(globalThis); const p_int = p_value.to(i64); if (p_int < 0 or !p_value.isNumber()) { - return globalThis.createRangeError("p must be a positive integer", .{}); + return throwInvalidParams( + globalThis, + "RangeError", + "Invalid scrypt params\n\n p must be a positive integer\n", + .{}, + ); } else if (p_int != 0) { parallelization = @intCast(usize, p_int); } @@ -537,19 +477,57 @@ pub const Crypto = struct { if (options_value.getTruthy(globalThis, "maxmem")) |value| { const p_int = value.to(i64); if (p_int < 0 or !value.isNumber()) { - return globalThis.createInvalidArgs("maxmem must be a positive integer", .{}); + return throwInvalidParams( + globalThis, + "RangeError", + "Invalid scrypt params\n\n N must be a positive integer\n", + .{}, + ); } else if (p_int != 0) { maxmem = @intCast(usize, p_int); } } } - if (cost < 2 or cost > 0x3fffffff) { - return globalThis.createRangeError("N must be greater than 1 and less than 2^30", .{}); + if (blockSize == null) blockSize = 8; + if (cost == null) cost = 16384; + if (parallelization == null) parallelization = 1; + + if (cost.? < 2 or cost.? > 0x3fffffff) { + return throwInvalidParams( + globalThis, + "RangeError", + "Invalid scrypt params\n\n N must be greater than 1 and less than 2^30\n", + .{}, + ); } - if (cost == 0 or (cost & (cost - 1)) != 0) { - return globalThis.createRangeError("N must be a power of 2 greater than 1", .{}); + if (cost.? == 0 or (cost.? & (cost.? - 1)) != 0) { + return throwInvalidParams( + globalThis, + "RangeError", + "Invalid scrypt params\n\n N must be a power of 2 greater than 1\n", + .{}, + ); + } + + if (keylen == 0) { + if ((BoringSSL.EVP_PBE_scrypt( + null, + 0, + null, + 0, + cost.?, + blockSize.?, + parallelization.?, + maxmem, + null, + 0, + ) != 1)) { + return throwInvalidParams(globalThis, "RangeError", "Invalid scrypt params\n", .{}); + } + + return JSC.ArrayBuffer.createEmpty(globalThis, .ArrayBuffer); } var stackbuf: [1024]u8 = undefined; @@ -572,20 +550,41 @@ pub const Crypto = struct { password_string.len, salt_string.ptr, salt_string.len, - cost, - blockSize, - parallelization, + cost.?, + blockSize.?, + parallelization.?, maxmem, buf.ptr, keylen, ) != 1) { - BoringSSL.ERR_clear_error(); - return globalThis.createErrorInstance("Failed to derive key", .{}); + return throwInvalidParams(globalThis, "RangeError", "Invalid scrypt params\n", .{}); } return JSC.ArrayBuffer.create(globalThis, buf, .ArrayBuffer); } + fn throwInvalidParameter(globalThis: *JSC.JSGlobalObject) JSC.JSValue { + const err = globalThis.createError( + .ERR_CRYPTO_SCRYPT_INVALID_PARAMETER, + "Error", + "Invalid scrypt parameters", + .{}, + ); + globalThis.throwValue(err); + return .zero; + } + + fn throwInvalidParams(globalThis: *JSC.JSGlobalObject, comptime error_name: string, comptime message: string, fmt: anytype) JSC.JSValue { + const err = globalThis.createError( + .ERR_CRYPTO_INVALID_SCRYPT_PARAMS, + error_name, + message, + fmt, + ); + globalThis.throwValue(err); + return .zero; + } + pub fn timingSafeEqual( globalThis: *JSC.JSGlobalObject, _: JSC.JSValue, From d7f5898179b430e004814c46f262293053fa332a Mon Sep 17 00:00:00 2001 From: Ashcon Partovi Date: Mon, 6 Mar 2023 22:50:11 -0800 Subject: [PATCH 048/255] Add reference documentation for bun:test (#2327) --- packages/bun-types/bun-test.d.ts | 443 +++++++++++++++++++++--- packages/bun-types/tests/test.test-d.ts | 66 +++- 2 files changed, 452 insertions(+), 57 deletions(-) diff --git a/packages/bun-types/bun-test.d.ts b/packages/bun-types/bun-test.d.ts index 1b7ae47a6241..8707ac877e21 100644 --- a/packages/bun-types/bun-test.d.ts +++ b/packages/bun-types/bun-test.d.ts @@ -1,82 +1,433 @@ /** * - * This isn't really designed for third-party usage yet. - * You can try it if you want though! - * - * To run the tests, run `bun wiptest` + * To run tests, run `bun test` * * @example * * ```bash - * $ bun wiptest + * $ bun test * ``` * * @example * ```bash - * $ bun wiptest file-name + * $ bun test * ``` */ declare module "bun:test" { - export function describe(label: string, body: () => void): any; - export interface Test { + /** + * Describes a group of related tests. + * + * @example + * function sum(a, b) { + * return a + b; + * } + * describe("sum()", () => { + * test("can sum two values", () => { + * expect(sum(1, 1)).toBe(2); + * }); + * }); + * + * @param label the label for the tests + * @param fn the function that defines the tests + */ + export type Describe = { + ( + label: string, + fn: () => void + ): void; + }; + /** + * Describes a group of related tests. + * + * @example + * function sum(a, b) { + * return a + b; + * } + * describe("sum()", () => { + * test("can sum two values", () => { + * expect(sum(1, 1)).toBe(2); + * }); + * }); + * + * @param label the label for the tests + * @param fn the function that defines the tests + */ + export const describe: Describe; + /** + * Runs a function, once, before all the tests. + * + * This is useful for running set up tasks, like initializing + * a global variable or connecting to a database. + * + * If this function throws, tests will not run in this file. + * + * @example + * let database; + * beforeAll(async () => { + * database = await connect("localhost"); + * }); + * + * @param fn the function to run + */ + export function beforeAll( + fn: (() => void | Promise) + | ((done: (err?: unknown) => void) => void) + ): void; + /** + * Runs a function before each test. + * + * This is useful for running set up tasks, like initializing + * a global variable or connecting to a database. + * + * If this function throws, the test will not run. + * + * @param fn the function to run + */ + export function beforeEach( + fn: (() => void | Promise) + | ((done: (err?: unknown) => void) => void) + ): void; + /** + * Runs a function, once, after all the tests. + * + * This is useful for running clean up tasks, like closing + * a socket or deleting temporary files. + * + * @example + * let database; + * afterAll(async () => { + * if (database) { + * await database.close(); + * } + * }); + * + * @param fn the function to run + */ + export function afterAll( + fn: (() => void | Promise) + | ((done: (err?: unknown) => void) => void) + ): void; + /** + * Runs a function after each test. + * + * This is useful for running clean up tasks, like closing + * a socket or deleting temporary files. + * + * @param fn the function to run + */ + export function afterEach( + fn: (() => void | Promise) + | ((done: (err?: unknown) => void) => void) + ): void; + /** + * Runs a test. + * + * @example + * test("can check if using Bun", () => { + * expect(Bun).toBeDefined(); + * }); + * + * test("can make a fetch() request", async () => { + * const response = await fetch("https://example.com/"); + * expect(response.ok).toBe(true); + * }); + * + * @param label the label for the test + * @param fn the test function + */ + export type Test = { ( label: string, - test: (done: (err?: any) => void) => void | Promise, - ): any; + fn: (() => void | Promise) + | ((done: (err?: unknown) => void) => void) + ): void; /** - * Note: does not fully work yet. + * Skips all other tests, except this test. + * @deprecated Not yet implemented. + * + * @param label the label for the test + * @param fn the test function */ only( label: string, - test: (done: (err?: any) => void) => void | Promise, - ): any; - + fn: (() => void | Promise) + | ((done: (err?: unknown) => void) => void) + ): void; /** - * Skip a test + * Skips this test. + * + * @param label the label for the test + * @param fn the test function */ skip( label: string, - test: (done: (err?: any) => void) => void | Promise, - ): any; + fn: (() => void | Promise) + | ((done: (err?: unknown) => void) => void) + ): void; } + /** + * Runs a test. + * + * @example + * test("can check if using Bun", () => { + * expect(Bun).toBeDefined(); + * }); + * + * test("can make a fetch() request", async () => { + * const response = await fetch("https://example.com/"); + * expect(response.ok).toBe(true); + * }); + * + * @param label the label for the test + * @param fn the test function + */ export const test: Test; export { test as it }; - - export function expect(value: any): Expect; - export function afterAll( - fn: (done: (err?: any) => void) => void | Promise, - ): void; - export function beforeAll( - fn: (done: (err?: any) => void) => void | Promise, - ): void; - - export function afterEach( - fn: (done: (err?: any) => void) => void | Promise, - ): void; - export function beforeEach( - fn: (done: (err?: any) => void) => void | Promise, - ): void; - - interface Expect { + /** + * Asserts that a value matches some criteria. + * + * @link https://jestjs.io/docs/expect#reference + * @example + * expect(1 + 1).toBe(2); + * expect([1,2,3]).toContain(2); + * expect(null).toBeNull(); + * + * @param actual the actual value + */ + export function expect(actual: unknown): Expect; + /** + * Asserts that a value matches some criteria. + * + * @link https://jestjs.io/docs/expect#reference + * @example + * expect(1 + 1).toBe(2); + * expect([1,2,3]).toContain(2); + * expect(null).toBeNull(); + * + * @param actual the actual value + */ + export type Expect = { + /** + * Negates the result of a subsequent assertion. + * + * @example + * expect(1).not.toBe(0); + * expect(null).not.toBeNull(); + */ not: Expect; - toBe(value: any): void; - toContain(value: any): void; - toEqual(value: any): void; - toStrictEqual(value: any): void; - toHaveLength(value: number): void; - toHaveProperty(key: string, value?: any): void; + /** + * Asserts that a value equals what is expected. + * + * - For non-primitive values, like objects and arrays, + * use `toEqual()` instead. + * - For floating-point numbers, use `toBeCloseTo()` instead. + * + * @example + * expect(100 + 23).toBe(123); + * expect("d" + "og").toBe("dog"); + * expect([123]).toBe([123]); // fail, use toEqual() + * expect(3 + 0.14).toBe(3.14); // fail, use toBeCloseTo() + * + * @param expected the expected value + */ + toBe(expected: unknown): void; + /** + * Asserts that a value is deeply equal to what is expected. + * + * @example + * expect(100 + 23).toBe(123); + * expect("d" + "og").toBe("dog"); + * expect([456]).toEqual([456]); + * expect({ value: 1 }).toEqual({ value: 1 }); + * + * @param expected the expected value + */ + toEqual(expected: unknown): void; + /** + * Asserts that a value is deeply and strictly equal to + * what is expected. + * + * There are two key differences from `toEqual()`: + * 1. It checks that the class is the same. + * 2. It checks that `undefined` values match as well. + * + * @example + * class Dog { + * type = "dog"; + * } + * const actual = new Dog(); + * expect(actual).toStrictEqual(new Dog()); + * expect(actual).toStrictEqual({ type: "dog" }); // fail + * + * @example + * const actual = { value: 1, name: undefined }; + * expect(actual).toEqual({ value: 1 }); + * expect(actual).toStrictEqual({ value: 1 }); // fail + * + * @param expected the expected value + */ + toStrictEqual(expected: unknown): void; + /** + * Asserts that a value contains what is expected. + * + * The value must be an array or iterable, which + * includes strings. + * + * @example + * expect([1, 2, 3]).toContain(1); + * expect(new Set([true])).toContain(true); + * expect("hello").toContain("o"); + * + * @param expected the expected value + */ + toContain(expected: unknown): void; + /** + * Asserts that a value has a `.length` property + * that is equal to the expected length. + * + * @example + * expect([]).toHaveLength(0); + * expect("hello").toHaveLength(4); + * + * @param length the expected length + */ + toHaveLength(length: number): void; + /** + * Asserts that a value has a property with the + * expected name, and value, if provided. + * + * @example + * expect(new Set()).toHaveProperty("size"); + * expect(new Uint8Array()).toHaveProperty("byteLength", 0); + * + * @param name the expected property name + * @param value the expected property value, if provided + */ + toHaveProperty(name: string, value?: unknown): void; + /** + * Asserts that a value is "truthy". + * + * To assert that a value equals `true`, use `toBe(true)` instead. + * + * @link https://developer.mozilla.org/en-US/docs/Glossary/Truthy + * @example + * expect(true).toBeTruthy(); + * expect(1).toBeTruthy(); + * expect({}).toBeTruthy(); + */ toBeTruthy(): void; + /** + * Asserts that a value is "falsy". + * + * To assert that a value equals `false`, use `toBe(false)` instead. + * + * @link https://developer.mozilla.org/en-US/docs/Glossary/Falsy + * @example + * expect(true).toBeTruthy(); + * expect(1).toBeTruthy(); + * expect({}).toBeTruthy(); + */ toBeFalsy(): void; + /** + * Asserts that a value is defined. (e.g. is not `undefined`) + * + * @example + * expect(true).toBeDefined(); + * expect(undefined).toBeDefined(); // fail + */ toBeDefined(): void; + /** + * Asserts that a value is `undefined`. + * + * @example + * expect(undefined).toBeUndefined(); + * expect(null).toBeUndefined(); // fail + */ toBeUndefined(): void; - toBeNaN(): void; + /** + * Asserts that a value is `null`. + * + * @example + * expect(null).toBeNull(); + * expect(undefined).toBeNull(); // fail + */ toBeNull(): void; - toBeGreaterThan(value: number | bigint): void; - toBeGreaterThanOrEqual(value: number | bigint): void; - toBeLessThan(value: number | bigint): void; - toBeLessThanOrEqual(value: number | bigint): void; - toThrow(error?: string | Error | ErrorConstructor | RegExp): void; + /** + * Asserts that a value can be coerced to `NaN`. + * + * Same as using `Number.isNaN()`. + * + * @example + * expect(NaN).toBeNaN(); + * expect(Infinity).toBeNaN(); + * expect("notanumber").toBeNaN(); + */ + toBeNaN(): void; + /** + * Asserts that a value is a `number` and is greater than the expected value. + * + * @example + * expect(1).toBeGreaterThan(0); + * expect(3.14).toBeGreaterThan(3); + * expect(9).toBeGreaterThan(9); // fail + * + * @param expected the expected number + */ + toBeGreaterThan(expected: number | bigint): void; + /** + * Asserts that a value is a `number` and is greater than or equal to the expected value. + * + * @example + * expect(1).toBeGreaterThanOrEqual(0); + * expect(3.14).toBeGreaterThanOrEqual(3); + * expect(9).toBeGreaterThanOrEqual(9); + * + * @param expected the expected number + */ + toBeGreaterThanOrEqual(expected: number | bigint): void; + /** + * Asserts that a value is a `number` and is less than the expected value. + * + * @example + * expect(-1).toBeLessThan(0); + * expect(3).toBeLessThan(3.14); + * expect(9).toBeLessThan(9); // fail + * + * @param expected the expected number + */ + toBeLessThan(expected: number | bigint): void; + /** + * Asserts that a value is a `number` and is less than or equal to the expected value. + * + * @example + * expect(-1).toBeLessThanOrEqual(0); + * expect(3).toBeLessThanOrEqual(3.14); + * expect(9).toBeLessThanOrEqual(9); + * + * @param expected the expected number + */ + toBeLessThanOrEqual(expected: number | bigint): void; + /** + * Asserts that a function throws an error. + * + * - If expected is a `string` or `RegExp`, it will check the `message` property. + * - If expected is an `Error` object, it will check the `name` and `message` properties. + * - If expected is an `Error` constructor, it will check the class of the `Error`. + * - If expected is not provided, it will check if anything as thrown. + * + * @example + * function fail() { + * throw new Error("Oops!"); + * } + * expect(fail).toThrow("Oops!"); + * expect(fail).toThrow(/oops/i); + * expect(fail).toThrow(Error); + * expect(fail).toThrow(); + * + * @param expected the expected error, error message, or error pattern + */ + toThrow(expected?: string | Error | ErrorConstructor | RegExp): void; } } diff --git a/packages/bun-types/tests/test.test-d.ts b/packages/bun-types/tests/test.test-d.ts index 653a41c4df81..ce2d0a66a029 100644 --- a/packages/bun-types/tests/test.test-d.ts +++ b/packages/bun-types/tests/test.test-d.ts @@ -1,13 +1,57 @@ -import { test, expect } from "bun:test"; +import { test, expect, describe, beforeAll, afterAll, beforeEach, afterEach } from "bun:test"; -test("new expect() matchers", () => { - expect(1).not.toBe(2); - expect({ a: 1 }).toEqual({ a: 1, b: undefined }); - expect({ a: 1 }).toStrictEqual({ a: 1 }); - expect(new Set()).toHaveProperty("size"); - expect([]).toHaveLength(0); - expect(["bun"]).toContain("bun"); - expect(true).toBeTruthy(); - expect(Math.PI).toBeGreaterThan(3.14); - expect(null).toBeNull(); +const hooks = [ + beforeAll, + beforeEach, + afterAll, + afterEach, +]; + +for (const hook of hooks) { + hook(() => { + // ... + }); + hook(async () => { + // ... + }); + hook((done: (err?: unknown) => void) => { + done(); + done(new Error()); + done("Error"); + }); +} + +describe("bun:test", () => { + describe("expect()", () => { + test("toThrow()", () => { + function fail() { + throw new Error("Bad"); + } + expect(fail).toThrow(); + expect(fail).toThrow("Bad"); + expect(fail).toThrow(/bad/i); + expect(fail).toThrow(Error); + expect(fail).toThrow(new Error("Bad")); + }); + }); + test("expect()", () => { + expect(1).toBe(1); + expect(1).not.toBe(2); + expect({ a: 1 }).toEqual({ a: 1, b: undefined }); + expect({ a: 1 }).toStrictEqual({ a: 1 }); + expect(new Set()).toHaveProperty("size"); + expect(new Uint8Array()).toHaveProperty("byteLength", 0); + expect([]).toHaveLength(0); + expect(["bun"]).toContain("bun"); + expect(true).toBeTruthy(); + expect(false).toBeFalsy(); + expect(Math.PI).toBeGreaterThan(3.14); + expect(Math.PI).toBeGreaterThan(3n); + expect(Math.PI).toBeGreaterThanOrEqual(3.14); + expect(Math.PI).toBeGreaterThanOrEqual(3n); + expect(NaN).toBeNaN(); + expect(null).toBeNull(); + expect(undefined).toBeUndefined(); + expect(undefined).not.toBeDefined(); + }); }); From c8c2b622bf41dba35d1f17e556b4f7d1e7bd79b5 Mon Sep 17 00:00:00 2001 From: Ashcon Partovi Date: Tue, 7 Mar 2023 12:22:34 -0800 Subject: [PATCH 049/255] Reorganize tests (#2332) --- .github/workflows/bun-linux-build.yml | 4 +- .github/workflows/bun-mac-x64-baseline.yml | 4 +- .gitignore | 2 + .prettierignore | 9 +- Makefile | 113 +--- bunfig.toml | 4 +- packages/bun-internal-test/package.json | 3 +- .../bun-internal-test/src/runner.node.mjs | 2 +- packages/bun-internal-test/src/runner.ts | 94 --- packages/bun-internal-test/tsconfig.json | 1 + .../.gitignore => .prettierignore} | 1 - test/README.md | 166 ++--- test/apps/bun-create-next.sh | 57 -- test/apps/bun-create-react.sh | 20 - test/apps/bun-dev-index-html.sh | 69 -- test/apps/bun-dev.sh | 55 -- test/apps/bun-init-check.sh | 26 - test/apps/bun-install-lockfile-status.sh | 65 -- test/apps/bun-install-utf8.sh | 14 - test/apps/bun-install.sh | 89 --- test/apps/bun-run-check-nameless-package.json | 6 - test/apps/bun-run-check-package.json | 7 - test/apps/bun-run-check.sh | 67 -- test/bun.js/.prettierignore | 2 - test/bun.js/bun.lockb | Bin 9454 -> 0 bytes test/bun.js/bunEnv.ts | 6 - test/bun.js/bunExe.ts | 3 - .../always-bundled-module | 1 - .../bundled/always-bundled-module/cjs.js | 10 - .../bundled/always-bundled-module/esm.js | 5 - .../always-bundled-module/package.json | 4 - test/bun.js/bundled/entrypoint.ts | 13 - test/bun.js/bundled/package.json | 12 - .../i-am-bundled/cjs.js | 10 - .../i-am-bundled/esm.js | 5 - .../i-am-bundled/package.json | 4 - test/bun.js/bundled/tsconfig.json | 6 - .../bun.js/file-importing-nonexistent-file.js | 1 - test/bun.js/google-fixture.html | 632 ------------------ test/bun.js/import.live.rexport.js | 2 - test/bun.js/module-require.snapshot.js | 1 - test/bun.js/package.json | 10 - test/bun.js/readdir.js | 9 - test/bun.js/snapshots.debug.js | 7 - test/bun.js/snapshots.js | 7 - test/bun.js/some-fs.js | 45 -- test/bun.js/test-programs/bigpipe-r.c | 26 - test/bun.js/test-programs/bigpipe-w.c | 26 - test/bun.js/test_scope_debug.ts | 172 ----- test/bun.js/tiny-typed-emitter.snapshot.js | 7 - test/bun.js/tsconfig.json | 12 - test/bun.lockb | Bin 0 -> 31909 bytes test/{bun.js => bundler}/decorators.test.ts | 0 test/{bun.js => bundler}/inline.macro.js | 0 test/{bun.js => bundler}/macro-check.js | 0 test/{bun.js => bundler}/transpiler.test.js | 0 .../hot}/hot-runner-imported.js | 0 test/{bun.js => cli/hot}/hot-runner.js | 0 test/{bun.js => cli/hot}/hot.test.ts | 3 +- .../install/bad-workspace.test.ts | 3 +- test/{bun.js => cli}/install/bar-0.0.2.tgz | Bin test/{bun.js => cli}/install/baz-0.0.3.tgz | Bin test/{bun.js => cli}/install/baz-0.0.5.tgz | Bin test/{bun.js => cli}/install/bun-add.test.ts | 3 +- .../install/bun-install.test.ts | 3 +- test/{bun.js => cli}/install/bun-link.test.ts | 3 +- test/{bun.js => cli}/install/bun-pm.test.ts | 3 +- test/{bun.js => cli}/install/bunx.test.ts | 3 +- .../{bun.js => cli}/install/dummy.registry.ts | 0 test/{bun.js => cli}/install/moo-0.1.0.tgz | Bin test/{bun.js => cli/run}/log-test.test.ts | 3 +- test/{bun.js => cli/run}/preload-test.test.js | 3 +- .../bun-link-pkg-fixture.js | 12 - test/fixtures/bun-link-pkg-fixture/bun.lockb | Bin 1191 -> 0 bytes .../bun-link-pkg-fixture/package-lock.json | 30 - .../bun-link-pkg-fixture/package.json | 11 - .../bun-link-to-pkg-fixture/.gitignore | 169 ----- .../bun-link-to-pkg-fixture/README.md | 15 - .../bun-link-to-pkg-fixture/bun.lockb | Bin 1527 -> 0 bytes .../fixtures/bun-link-to-pkg-fixture/index.js | 1 - .../bun-link-to-pkg-fixture/package.json | 12 - .../bun-link-to-pkg-fixture/tsconfig.json | 14 - test/{bun.js/gc.ts => harness.ts} | 13 +- .../bun}/console/console-iterator-run.js | 0 .../bun}/console/console-iterator.test.js | 2 +- .../bun/dns}/resolve-dns.test.ts | 4 +- test/{bun.js => js/bun/ffi}/ffi-test.c | 0 .../bun/ffi}/ffi.test.fixture.callback.c | 3 - .../bun/ffi}/ffi.test.fixture.receiver.c | 0 test/{bun.js => js/bun/ffi}/ffi.test.js | 15 +- test/{bun.js => js/bun}/globals.test.js | 0 .../bun/http}/bun-server.test.ts | 0 test/{bun.js => js/bun/http}/fetch.js.txt | 0 test/{bun.js => js/bun/http}/fixture.html.gz | Bin test/{bun.js => js/bun/http}/proxy.test.js | 2 +- .../bun/http/serve.leak.ts} | 0 test/{bun.js => js/bun/http}/serve.test.ts | 0 .../bun/io}/bun-streams-test-fifo.sh | 0 test/{bun.js => js/bun/io}/bun-write.test.js | 2 +- test/{bun.js => js/bun/io}/emptyFile | 0 test/js/bun/io/fetch.js.txt | 46 ++ test/{bun.js => js/bun/jsc}/bun-jsc.test.js | 0 test/{bun.js => js/bun/jsc}/shadow.test.js | 0 test/{bun.js/socket => js/bun/net}/echo.js | 0 .../socket => js/bun/net}/socket.test.ts | 2 +- .../{bun.js => js/bun/net}/tcp-server.test.ts | 2 +- test/{bun.js => js/bun/plugin}/hello.svelte | 0 test/{bun.js => js/bun/plugin}/hello2.svelte | 0 test/{bun.js => js/bun/plugin}/plugins.d.ts | 0 .../{bun.js => js/bun/plugin}/plugins.test.ts | 2 +- test/{bun.js => js/bun/resolve}/baz.js | 0 .../file-importing-nonexistent-file.js | 1 + test/{bun.js/esm => js/bun/resolve}/first.mjs | 0 .../bun/resolve}/import-meta.test.js | 12 +- .../bun/resolve}/import-require-tla.js | 0 .../bun/resolve}/import.live.decl.js | 0 .../resolve}/import.live.rexport-require.js | 0 test/js/bun/resolve/import.live.rexport.js | 2 + .../bun/resolve/png}/test-png-import.test.js | 0 .../bun/resolve/png}/test-png.png | 0 .../resolve}/require-js-top-level-await.js | 0 test/{bun.js => js/bun/resolve}/require-js.js | 0 .../{bun.js => js/bun/resolve}/require-js2.js | 0 .../bun/resolve}/require-json.json | 0 .../require-referenceerror.snapshot.js | 0 .../bun/resolve}/resolve-error.test.ts | 0 .../bun/resolve}/resolve-typescript-file.tsx | 0 .../bun/resolve}/resolve.test.js | 0 .../esm => js/bun/resolve}/second-child.mjs | 0 .../{bun.js/esm => js/bun/resolve}/second.mjs | 0 .../esm => js/bun/resolve}/startEnd.mjs | 0 test/{bun.js/esm => js/bun/resolve}/third.mjs | 0 .../bun/resolve/toml}/toml-fixture.toml | 0 .../bun/resolve/toml}/toml.test.js | 2 +- test/{bun.js => js/bun/spawn}/bash-echo.sh | 0 .../bun/spawn}/bun-spawn-test.js | 0 test/{bun.js => js/bun/spawn}/exit-code-0.js | 0 test/{bun.js => js/bun/spawn}/exit-code-1.js | 0 .../bun/spawn}/exit-code-await-throw-1.js | 0 .../bun/spawn}/exit-code-unhandled-throw.js | 0 .../bun/spawn}/exit-code.test.ts | 2 +- test/{bun.js => js/bun/spawn}/exit.js | 0 .../bun/spawn}/spawn-streaming-stdin.test.ts | 4 +- .../spawn}/spawn-streaming-stdout-repro.js | 0 .../bun/spawn}/spawn-streaming-stdout.test.ts | 4 +- test/{bun.js => js/bun/spawn}/spawn.test.ts | 3 +- test/{bun.js => js/bun/spawn}/stdin-repro.js | 0 .../bun/spawn}/stdio-test-instance-a-lot.js | 0 .../bun/spawn}/stdio-test-instance.js | 0 .../bun/sqlite}/northwind.testdb | Bin .../{bun.js => js/bun/sqlite}/sql-raw.test.js | 0 .../bun/sqlite}/sqlite-cross-process.js | 0 test/{bun.js => js/bun/sqlite}/sqlite.test.js | 2 +- test/{bun.js => js/bun/test}/bigint.test.js | 0 test/{bun.js => js/bun/test}/bun-test.test.ts | 0 .../bun/test}/jest-doesnt-auto-import.js | 0 .../bun/test}/jest-hooks.test.ts | 0 .../bun/test}/nested-describes.test.ts | 0 .../test-auto-import-jest-globals.test.js | 0 .../{bun.js => js/bun/test}/test-test.test.ts | 3 +- .../bun/util}/arraybuffersink.test.ts | 2 +- test/{bun.js => js/bun/util}/concat.test.js | 1 - test/{bun.js => js/bun/util}/empty.js | 0 .../bun/util}/escapeHTML.test.js | 1 - test/{bun.js => js/bun/util}/fileUrl.test.js | 0 test/{bun.js => js/bun/util}/filesink.test.ts | 0 .../bun/util}/filesystem_router.test.ts | 0 test/{bun.js => js/bun/util}/hash.test.js | 2 +- .../bun/util}/index-of-line.test.ts | 0 test/{bun.js => js/bun/util}/inspect.test.js | 2 +- test/{bun.js => js/bun/util}/mmap.test.js | 2 +- test/{bun.js => js/bun/util}/peek.test.ts | 0 .../bun/util}/reportError.test.js | 0 test/{bun.js => js/bun/util}/sleep.js | 0 .../{bun.js => js/bun/util}/sleepSync.test.ts | 0 test/{bun.js => js/bun/util}/unsafe.test.js | 2 +- test/{bun.js => js/bun/util}/which.test.ts | 0 test/{bun.js => js/bun/wasm}/hello-wasi.wasm | Bin test/{bun.js => js/bun/wasm}/wasi.test.js | 3 +- .../bun/wasm}/wasm-return-1-test.zig | 0 test/{bun.js => js/bun/wasm}/wasm.js | 0 .../bun/websocket}/websocket-server.test.ts | 51 +- .../first_party/undici}/undici.test.ts | 0 .../node/assert}/assert-test.test.ts | 0 test/{bun.js => js/node}/buffer.test.js | 2 +- .../child-process-stdio.test.js | 3 +- .../child_process}/child_process-node.test.js | 3 +- .../node/child_process}/child_process.test.ts | 2 +- .../node/child_process}/readFileSync.txt | 0 .../node/child_process}/spawned-child.js | 0 .../node/crypto}/crypto-scrypt.test.js | 0 .../{bun.js => js/node/crypto}/crypto.test.js | 0 .../node/crypto}/node-crypto.test.js | 0 test/{bun.js => js/node}/dirname.test.js | 0 .../node}/disabled-module.test.js | 0 test/{bun.js => js/node/dns}/dns.node.mjs | 0 test/{bun.js => js/node/dns}/node-dns.test.js | 0 .../node/events}/event-emitter.test.ts | 0 .../node/events}/node-builtins.test.js | 0 .../node/fs}/export-*-from.ts | 0 .../node/fs}/export-from.ts | 0 test/{bun.js => js/node/fs}/fs-stream.js | 0 test/{bun.js => js/node/fs}/fs-stream.link.js | 0 test/{bun.js => js/node/fs}/fs.test.ts | 9 +- test/js/node/fs/readFileSync.txt | 1 + test/js/node/fs/test.txt | 0 test/{bun.js => js/node/fs}/writeFileSync.txt | 0 .../node/harness.test.js} | 0 .../node/harness.ts} | 3 +- .../node/http/node-http.fixme.ts} | 49 +- .../node/module}/node-module-module.test.js | 0 .../socket => js/node/net}/node-net.test.ts | 0 test/{bun.js => js/node/os}/os.test.js | 0 test/{bun.js => js/node/path}/path.test.js | 0 .../node/process}/print-process-args.js | 0 .../node/process}/process-args.test.js | 2 +- .../node/process}/process-nexttick.js | 0 .../node/process}/process-nexttick.test.js | 0 .../node/process}/process-stdin-echo.js | 0 .../node/process}/process-stdio.test.ts | 2 +- .../node/process}/process.test.js | 5 +- .../node/readline}/readline.node.test.ts | 2 +- .../readline}/readline_promises.node.test.ts | 2 +- .../node/stream}/bufferlist.test.ts | 0 .../stream}/node-stream-uint8array.test.ts | 0 .../node/stream}/node-stream.test.js | 0 .../string_decoder}/string-decoder.test.js | 2 +- .../node/timers}/node-timers.test.ts | 0 .../node/util}/test-util-types.test.js | 0 .../node}/util/util-promisify.test.js | 0 test/{bun.js => js/node}/util/util.test.js | 0 .../node/v8}/capture-stack-trace.test.js | 0 test/js/node/zlib/fixture.html.gz | Bin 0 -> 16139 bytes test/{bun.js => js/node/zlib}/zlib.test.js | 0 .../third_party/body-parser}/bun.lockb | Bin .../express-body-parser-test.test.ts | 0 .../third_party/body-parser}/package.json | 0 test/js/third_party/esbuild/bun.lockb | Bin 0 -> 8425 bytes .../esbuild}/esbuild-child_process.test.ts | 2 +- .../third_party/esbuild}/esbuild-test.js | 0 test/js/third_party/esbuild/package.json | 6 + .../napi_create_external/bun.lockb | Bin .../napi-create-external.test.ts | 2 +- .../napi_create_external/package.json | 1 + .../react-dom}/react-dom-server.bun.cjs | 0 .../third_party/react-dom}/react-dom.test.tsx | 2 +- .../third_party/svelte}/bun-loader-svelte.ts | 0 test/js/third_party/svelte/hello.svelte | 5 + .../third_party/svelte}/package.json | 0 test/js/third_party/svelte/svelte.test.ts | 21 + .../web/abort}/abort-signal-timeout.test.js | 0 .../web}/console/console-log.expected.txt | 0 .../{bun.js => js/web}/console/console-log.js | 0 .../web}/console/console-log.test.ts | 2 +- .../web/crypto}/web-crypto.test.ts | 0 .../web/encoding}/text-decoder.test.js | 2 +- .../web/encoding}/text-encoder.test.js | 2 +- .../web/encoding}/utf8-encoding-fixture.bin | Bin .../web/fetch}/body-mixin-errors.test.ts | 0 .../web/fetch}/body-stream.test.ts | 0 .../web/fetch}/fetch-gzip.test.ts | 2 +- test/js/web/fetch/fetch.js.txt | 46 ++ test/{bun.js => js/web/fetch}/fetch.test.ts | 2 +- .../web/fetch}/fetch_headers.test.js | 0 test/{bun.js => js/web/fetch}/fixture.html | 0 test/js/web/fetch/fixture.html.gz | Bin 0 -> 16139 bytes test/{bun.js => js/web/html}/FormData.test.ts | 2 +- .../web/html}/form-data-fixture.txt | 0 test/js/web/streams/bun-streams-test-fifo.sh | 8 + test/js/web/streams/fetch.js.txt | 46 ++ .../web/streams}/streams.test.js | 2 +- .../web/timers}/microtask.test.js | 0 .../web/timers}/performance.test.js | 0 .../web/timers}/setImmediate.test.js | 0 .../web/timers}/setInterval.test.js | 0 .../web/timers}/setTimeout.test.js | 0 test/{bun.js => js/web/url}/url.test.ts | 0 test/{bun.js => js/web/util}/atob.test.js | 0 test/{bun.js => js/web}/web-globals.test.js | 2 +- .../web/websocket}/websocket-subprocess.ts | 0 .../web/websocket}/websocket.test.js | 5 +- .../workerd}/html-rewriter.test.js | 2 +- test/macro/assert.tsx | 4 - test/macro/fetchSync.tsx | 9 - test/macro/hello-fetch-macro.tsx | 5 - test/macro/loadMocks.tsx | 29 - test/macro/macro.d.ts | 1 - test/{bun.js => }/mkfifo.ts | 0 test/package.json | 19 + .../issue/00631.test.ts} | 10 +- .../issue/02005.test.ts} | 2 +- test/tsconfig.json | 23 + tsconfig.json | 2 - 293 files changed, 433 insertions(+), 2321 deletions(-) delete mode 100644 packages/bun-internal-test/src/runner.ts rename test/{fixtures/bun-link-pkg-fixture/.gitignore => .prettierignore} (54%) delete mode 100644 test/apps/bun-create-next.sh delete mode 100644 test/apps/bun-create-react.sh delete mode 100644 test/apps/bun-dev-index-html.sh delete mode 100644 test/apps/bun-dev.sh delete mode 100644 test/apps/bun-init-check.sh delete mode 100644 test/apps/bun-install-lockfile-status.sh delete mode 100644 test/apps/bun-install-utf8.sh delete mode 100644 test/apps/bun-install.sh delete mode 100644 test/apps/bun-run-check-nameless-package.json delete mode 100644 test/apps/bun-run-check-package.json delete mode 100644 test/apps/bun-run-check.sh delete mode 100644 test/bun.js/.prettierignore delete mode 100755 test/bun.js/bun.lockb delete mode 100644 test/bun.js/bunEnv.ts delete mode 100644 test/bun.js/bunExe.ts delete mode 120000 test/bun.js/bundled/always-bundled-module/always-bundled-module delete mode 100644 test/bun.js/bundled/always-bundled-module/cjs.js delete mode 100644 test/bun.js/bundled/always-bundled-module/esm.js delete mode 100644 test/bun.js/bundled/always-bundled-module/package.json delete mode 100644 test/bun.js/bundled/entrypoint.ts delete mode 100644 test/bun.js/bundled/package.json delete mode 100644 test/bun.js/bundled/to_bundle_node_modules/i-am-bundled/cjs.js delete mode 100644 test/bun.js/bundled/to_bundle_node_modules/i-am-bundled/esm.js delete mode 100644 test/bun.js/bundled/to_bundle_node_modules/i-am-bundled/package.json delete mode 100644 test/bun.js/bundled/tsconfig.json delete mode 100644 test/bun.js/file-importing-nonexistent-file.js delete mode 100644 test/bun.js/google-fixture.html delete mode 100644 test/bun.js/import.live.rexport.js delete mode 100644 test/bun.js/module-require.snapshot.js delete mode 100644 test/bun.js/package.json delete mode 100644 test/bun.js/readdir.js delete mode 100644 test/bun.js/snapshots.debug.js delete mode 100644 test/bun.js/snapshots.js delete mode 100644 test/bun.js/some-fs.js delete mode 100644 test/bun.js/test-programs/bigpipe-r.c delete mode 100644 test/bun.js/test-programs/bigpipe-w.c delete mode 100644 test/bun.js/test_scope_debug.ts delete mode 100644 test/bun.js/tiny-typed-emitter.snapshot.js delete mode 100644 test/bun.js/tsconfig.json create mode 100755 test/bun.lockb rename test/{bun.js => bundler}/decorators.test.ts (100%) rename test/{bun.js => bundler}/inline.macro.js (100%) rename test/{bun.js => bundler}/macro-check.js (100%) rename test/{bun.js => bundler}/transpiler.test.js (100%) rename test/{bun.js => cli/hot}/hot-runner-imported.js (100%) rename test/{bun.js => cli/hot}/hot-runner.js (100%) rename test/{bun.js => cli/hot}/hot.test.ts (98%) rename test/{bun.js => cli}/install/bad-workspace.test.ts (93%) rename test/{bun.js => cli}/install/bar-0.0.2.tgz (100%) rename test/{bun.js => cli}/install/baz-0.0.3.tgz (100%) rename test/{bun.js => cli}/install/baz-0.0.5.tgz (100%) rename test/{bun.js => cli}/install/bun-add.test.ts (99%) rename test/{bun.js => cli}/install/bun-install.test.ts (99%) rename test/{bun.js => cli}/install/bun-link.test.ts (99%) rename test/{bun.js => cli}/install/bun-pm.test.ts (98%) rename test/{bun.js => cli}/install/bunx.test.ts (98%) rename test/{bun.js => cli}/install/dummy.registry.ts (100%) rename test/{bun.js => cli}/install/moo-0.1.0.tgz (100%) rename test/{bun.js => cli/run}/log-test.test.ts (95%) rename test/{bun.js => cli/run}/preload-test.test.js (98%) delete mode 100755 test/fixtures/bun-link-pkg-fixture/bun-link-pkg-fixture.js delete mode 100755 test/fixtures/bun-link-pkg-fixture/bun.lockb delete mode 100644 test/fixtures/bun-link-pkg-fixture/package-lock.json delete mode 100644 test/fixtures/bun-link-pkg-fixture/package.json delete mode 100644 test/fixtures/bun-link-to-pkg-fixture/.gitignore delete mode 100644 test/fixtures/bun-link-to-pkg-fixture/README.md delete mode 100755 test/fixtures/bun-link-to-pkg-fixture/bun.lockb delete mode 100644 test/fixtures/bun-link-to-pkg-fixture/index.js delete mode 100644 test/fixtures/bun-link-to-pkg-fixture/package.json delete mode 100644 test/fixtures/bun-link-to-pkg-fixture/tsconfig.json rename test/{bun.js/gc.ts => harness.ts} (70%) rename test/{bun.js => js/bun}/console/console-iterator-run.js (100%) rename test/{bun.js => js/bun}/console/console-iterator.test.js (98%) rename test/{bun.js => js/bun/dns}/resolve-dns.test.ts (93%) rename test/{bun.js => js/bun/ffi}/ffi-test.c (100%) rename test/{bun.js => js/bun/ffi}/ffi.test.fixture.callback.c (99%) rename test/{bun.js => js/bun/ffi}/ffi.test.fixture.receiver.c (100%) rename test/{bun.js => js/bun/ffi}/ffi.test.js (98%) rename test/{bun.js => js/bun}/globals.test.js (100%) rename test/{bun.js => js/bun/http}/bun-server.test.ts (100%) rename test/{bun.js => js/bun/http}/fetch.js.txt (100%) rename test/{bun.js => js/bun/http}/fixture.html.gz (100%) rename test/{bun.js => js/bun/http}/proxy.test.js (98%) rename test/{leaks/http-static-leak.ts => js/bun/http/serve.leak.ts} (100%) rename test/{bun.js => js/bun/http}/serve.test.ts (100%) rename test/{bun.js => js/bun/io}/bun-streams-test-fifo.sh (100%) rename test/{bun.js => js/bun/io}/bun-write.test.js (99%) rename test/{bun.js => js/bun/io}/emptyFile (100%) create mode 100644 test/js/bun/io/fetch.js.txt rename test/{bun.js => js/bun/jsc}/bun-jsc.test.js (100%) rename test/{bun.js => js/bun/jsc}/shadow.test.js (100%) rename test/{bun.js/socket => js/bun/net}/echo.js (100%) rename test/{bun.js/socket => js/bun/net}/socket.test.ts (98%) rename test/{bun.js => js/bun/net}/tcp-server.test.ts (98%) rename test/{bun.js => js/bun/plugin}/hello.svelte (100%) rename test/{bun.js => js/bun/plugin}/hello2.svelte (100%) rename test/{bun.js => js/bun/plugin}/plugins.d.ts (100%) rename test/{bun.js => js/bun/plugin}/plugins.test.ts (99%) rename test/{bun.js => js/bun/resolve}/baz.js (100%) create mode 100644 test/js/bun/resolve/file-importing-nonexistent-file.js rename test/{bun.js/esm => js/bun/resolve}/first.mjs (100%) rename test/{bun.js => js/bun/resolve}/import-meta.test.js (92%) rename test/{bun.js => js/bun/resolve}/import-require-tla.js (100%) rename test/{bun.js => js/bun/resolve}/import.live.decl.js (100%) rename test/{bun.js => js/bun/resolve}/import.live.rexport-require.js (100%) create mode 100644 test/js/bun/resolve/import.live.rexport.js rename test/{bun.js => js/bun/resolve/png}/test-png-import.test.js (100%) rename test/{bun.js => js/bun/resolve/png}/test-png.png (100%) rename test/{bun.js => js/bun/resolve}/require-js-top-level-await.js (100%) rename test/{bun.js => js/bun/resolve}/require-js.js (100%) rename test/{bun.js => js/bun/resolve}/require-js2.js (100%) rename test/{bun.js => js/bun/resolve}/require-json.json (100%) rename test/{bun.js => js/bun/resolve}/require-referenceerror.snapshot.js (100%) rename test/{bun.js => js/bun/resolve}/resolve-error.test.ts (100%) rename test/{bun.js => js/bun/resolve}/resolve-typescript-file.tsx (100%) rename test/{bun.js => js/bun/resolve}/resolve.test.js (100%) rename test/{bun.js/esm => js/bun/resolve}/second-child.mjs (100%) rename test/{bun.js/esm => js/bun/resolve}/second.mjs (100%) rename test/{bun.js/esm => js/bun/resolve}/startEnd.mjs (100%) rename test/{bun.js/esm => js/bun/resolve}/third.mjs (100%) rename test/{bun.js => js/bun/resolve/toml}/toml-fixture.toml (100%) rename test/{bun.js => js/bun/resolve/toml}/toml.test.js (97%) rename test/{bun.js => js/bun/spawn}/bash-echo.sh (100%) rename test/{bun.js => js/bun/spawn}/bun-spawn-test.js (100%) rename test/{bun.js => js/bun/spawn}/exit-code-0.js (100%) rename test/{bun.js => js/bun/spawn}/exit-code-1.js (100%) rename test/{bun.js => js/bun/spawn}/exit-code-await-throw-1.js (100%) rename test/{bun.js => js/bun/spawn}/exit-code-unhandled-throw.js (100%) rename test/{bun.js => js/bun/spawn}/exit-code.test.ts (95%) rename test/{bun.js => js/bun/spawn}/exit.js (100%) rename test/{bun.js => js/bun/spawn}/spawn-streaming-stdin.test.ts (93%) rename test/{bun.js => js/bun/spawn}/spawn-streaming-stdout-repro.js (100%) rename test/{bun.js => js/bun/spawn}/spawn-streaming-stdout.test.ts (92%) rename test/{bun.js => js/bun/spawn}/spawn.test.ts (99%) rename test/{bun.js => js/bun/spawn}/stdin-repro.js (100%) rename test/{bun.js => js/bun/spawn}/stdio-test-instance-a-lot.js (100%) rename test/{bun.js => js/bun/spawn}/stdio-test-instance.js (100%) rename test/{bun.js => js/bun/sqlite}/northwind.testdb (100%) rename test/{bun.js => js/bun/sqlite}/sql-raw.test.js (100%) rename test/{bun.js => js/bun/sqlite}/sqlite-cross-process.js (100%) rename test/{bun.js => js/bun/sqlite}/sqlite.test.js (99%) rename test/{bun.js => js/bun/test}/bigint.test.js (100%) rename test/{bun.js => js/bun/test}/bun-test.test.ts (100%) rename test/{bun.js => js/bun/test}/jest-doesnt-auto-import.js (100%) rename test/{bun.js/bun-test => js/bun/test}/jest-hooks.test.ts (100%) rename test/{bun.js/bun-test => js/bun/test}/nested-describes.test.ts (100%) rename test/{bun.js => js/bun/test}/test-auto-import-jest-globals.test.js (100%) rename test/{bun.js => js/bun/test}/test-test.test.ts (99%) rename test/{bun.js => js/bun/util}/arraybuffersink.test.ts (98%) rename test/{bun.js => js/bun/util}/concat.test.js (97%) rename test/{bun.js => js/bun/util}/empty.js (100%) rename test/{bun.js => js/bun/util}/escapeHTML.test.js (99%) rename test/{bun.js => js/bun/util}/fileUrl.test.js (100%) rename test/{bun.js => js/bun/util}/filesink.test.ts (100%) rename test/{bun.js => js/bun/util}/filesystem_router.test.ts (100%) rename test/{bun.js => js/bun/util}/hash.test.js (97%) rename test/{bun.js => js/bun/util}/index-of-line.test.ts (100%) rename test/{bun.js => js/bun/util}/inspect.test.js (99%) rename test/{bun.js => js/bun/util}/mmap.test.js (97%) rename test/{bun.js => js/bun/util}/peek.test.ts (100%) rename test/{bun.js => js/bun/util}/reportError.test.js (100%) rename test/{bun.js => js/bun/util}/sleep.js (100%) rename test/{bun.js => js/bun/util}/sleepSync.test.ts (100%) rename test/{bun.js => js/bun/util}/unsafe.test.js (97%) rename test/{bun.js => js/bun/util}/which.test.ts (100%) rename test/{bun.js => js/bun/wasm}/hello-wasi.wasm (100%) rename test/{bun.js => js/bun/wasm}/wasi.test.js (84%) rename test/{bun.js => js/bun/wasm}/wasm-return-1-test.zig (100%) rename test/{bun.js => js/bun/wasm}/wasm.js (100%) rename test/{bun.js => js/bun/websocket}/websocket-server.test.ts (97%) rename test/{bun.js => js/first_party/undici}/undici.test.ts (100%) rename test/{bun.js => js/node/assert}/assert-test.test.ts (100%) rename test/{bun.js => js/node}/buffer.test.js (99%) rename test/{bun.js => js/node/child_process}/child-process-stdio.test.js (97%) rename test/{bun.js => js/node/child_process}/child_process-node.test.js (99%) rename test/{bun.js => js/node/child_process}/child_process.test.ts (99%) rename test/{bun.js => js/node/child_process}/readFileSync.txt (100%) rename test/{bun.js => js/node/child_process}/spawned-child.js (100%) rename test/{bun.js => js/node/crypto}/crypto-scrypt.test.js (100%) rename test/{bun.js => js/node/crypto}/crypto.test.js (100%) rename test/{bun.js => js/node/crypto}/node-crypto.test.js (100%) rename test/{bun.js => js/node}/dirname.test.js (100%) rename test/{bun.js => js/node}/disabled-module.test.js (100%) rename test/{bun.js => js/node/dns}/dns.node.mjs (100%) rename test/{bun.js => js/node/dns}/node-dns.test.js (100%) rename test/{bun.js => js/node/events}/event-emitter.test.ts (100%) rename test/{bun.js => js/node/events}/node-builtins.test.js (100%) rename test/{fixtures/export-lazy-fs-streams => js/node/fs}/export-*-from.ts (100%) rename test/{fixtures/export-lazy-fs-streams => js/node/fs}/export-from.ts (100%) rename test/{bun.js => js/node/fs}/fs-stream.js (100%) rename test/{bun.js => js/node/fs}/fs-stream.link.js (100%) rename test/{bun.js => js/node/fs}/fs.test.ts (99%) create mode 100644 test/js/node/fs/readFileSync.txt create mode 100644 test/js/node/fs/test.txt rename test/{bun.js => js/node/fs}/writeFileSync.txt (100%) rename test/{bun.js/node-test-helpers.test.js => js/node/harness.test.js} (100%) rename test/{bun.js/node-test-helpers.ts => js/node/harness.ts} (99%) rename test/{bun.js/node-http.test.ts => js/node/http/node-http.fixme.ts} (94%) rename test/{bun.js => js/node/module}/node-module-module.test.js (100%) rename test/{bun.js/socket => js/node/net}/node-net.test.ts (100%) rename test/{bun.js => js/node/os}/os.test.js (100%) rename test/{bun.js => js/node/path}/path.test.js (100%) rename test/{bun.js => js/node/process}/print-process-args.js (100%) rename test/{bun.js => js/node/process}/process-args.test.js (97%) rename test/{bun.js => js/node/process}/process-nexttick.js (100%) rename test/{bun.js => js/node/process}/process-nexttick.test.js (100%) rename test/{bun.js => js/node/process}/process-stdin-echo.js (100%) rename test/{bun.js => js/node/process}/process-stdio.test.ts (99%) rename test/{bun.js => js/node/process}/process.test.js (96%) rename test/{bun.js => js/node/readline}/readline.node.test.ts (99%) rename test/{bun.js => js/node/readline}/readline_promises.node.test.ts (94%) rename test/{bun.js => js/node/stream}/bufferlist.test.ts (100%) rename test/{bun.js => js/node/stream}/node-stream-uint8array.test.ts (100%) rename test/{bun.js => js/node/stream}/node-stream.test.js (100%) rename test/{bun.js => js/node/string_decoder}/string-decoder.test.js (99%) rename test/{bun.js => js/node/timers}/node-timers.test.ts (100%) rename test/{bun.js => js/node/util}/test-util-types.test.js (100%) rename test/{bun.js => js/node}/util/util-promisify.test.js (100%) rename test/{bun.js => js/node}/util/util.test.js (100%) rename test/{bun.js => js/node/v8}/capture-stack-trace.test.js (100%) create mode 100644 test/js/node/zlib/fixture.html.gz rename test/{bun.js => js/node/zlib}/zlib.test.js (100%) rename test/{bun.js/third-party/body-parser-test => js/third_party/body-parser}/bun.lockb (100%) rename test/{bun.js/third-party/body-parser-test => js/third_party/body-parser}/express-body-parser-test.test.ts (100%) rename test/{bun.js/third-party/body-parser-test => js/third_party/body-parser}/package.json (100%) create mode 100755 test/js/third_party/esbuild/bun.lockb rename test/{bun.js => js/third_party/esbuild}/esbuild-child_process.test.ts (92%) rename test/{bun.js => js/third_party/esbuild}/esbuild-test.js (100%) create mode 100644 test/js/third_party/esbuild/package.json rename test/{bun.js/third-party => js/third_party}/napi_create_external/bun.lockb (100%) rename test/{bun.js/third-party => js/third_party}/napi_create_external/napi-create-external.test.ts (99%) rename test/{bun.js/third-party => js/third_party}/napi_create_external/package.json (93%) rename test/{bun.js => js/third_party/react-dom}/react-dom-server.bun.cjs (100%) rename test/{bun.js => js/third_party/react-dom}/react-dom.test.tsx (99%) rename test/{bun.js/bun-loader-svelte => js/third_party/svelte}/bun-loader-svelte.ts (100%) create mode 100644 test/js/third_party/svelte/hello.svelte rename test/{bun.js/bun-loader-svelte => js/third_party/svelte}/package.json (100%) create mode 100644 test/js/third_party/svelte/svelte.test.ts rename test/{bun.js => js/web/abort}/abort-signal-timeout.test.js (100%) rename test/{bun.js => js/web}/console/console-log.expected.txt (100%) rename test/{bun.js => js/web}/console/console-log.js (100%) rename test/{bun.js => js/web}/console/console-log.test.ts (94%) rename test/{bun.js => js/web/crypto}/web-crypto.test.ts (100%) rename test/{bun.js => js/web/encoding}/text-decoder.test.js (99%) rename test/{bun.js => js/web/encoding}/text-encoder.test.js (99%) rename test/{bun.js => js/web/encoding}/utf8-encoding-fixture.bin (100%) rename test/{bun.js => js/web/fetch}/body-mixin-errors.test.ts (100%) rename test/{bun.js => js/web/fetch}/body-stream.test.ts (100%) rename test/{bun.js => js/web/fetch}/fetch-gzip.test.ts (99%) create mode 100644 test/js/web/fetch/fetch.js.txt rename test/{bun.js => js/web/fetch}/fetch.test.ts (99%) rename test/{bun.js => js/web/fetch}/fetch_headers.test.js (100%) rename test/{bun.js => js/web/fetch}/fixture.html (100%) create mode 100644 test/js/web/fetch/fixture.html.gz rename test/{bun.js => js/web/html}/FormData.test.ts (99%) rename test/{bun.js => js/web/html}/form-data-fixture.txt (100%) create mode 100644 test/js/web/streams/bun-streams-test-fifo.sh create mode 100644 test/js/web/streams/fetch.js.txt rename test/{bun.js => js/web/streams}/streams.test.js (99%) rename test/{bun.js => js/web/timers}/microtask.test.js (100%) rename test/{bun.js => js/web/timers}/performance.test.js (100%) rename test/{bun.js => js/web/timers}/setImmediate.test.js (100%) rename test/{bun.js => js/web/timers}/setInterval.test.js (100%) rename test/{bun.js => js/web/timers}/setTimeout.test.js (100%) rename test/{bun.js => js/web/url}/url.test.ts (100%) rename test/{bun.js => js/web/util}/atob.test.js (100%) rename test/{bun.js => js/web}/web-globals.test.js (99%) rename test/{bun.js => js/web/websocket}/websocket-subprocess.ts (100%) rename test/{bun.js => js/web/websocket}/websocket.test.js (98%) rename test/{bun.js => js/workerd}/html-rewriter.test.js (99%) delete mode 100644 test/macro/assert.tsx delete mode 100644 test/macro/fetchSync.tsx delete mode 100644 test/macro/hello-fetch-macro.tsx delete mode 100644 test/macro/loadMocks.tsx delete mode 100644 test/macro/macro.d.ts rename test/{bun.js => }/mkfifo.ts (100%) create mode 100644 test/package.json rename test/{bun.js/repro_631.test.js => regression/issue/00631.test.ts} (78%) rename test/{bun.js/repro_2005.test.js => regression/issue/02005.test.ts} (86%) create mode 100644 test/tsconfig.json diff --git a/.github/workflows/bun-linux-build.yml b/.github/workflows/bun-linux-build.yml index 75c84c7f625e..07d7703545d9 100644 --- a/.github/workflows/bun-linux-build.yml +++ b/.github/workflows/bun-linux-build.yml @@ -188,9 +188,7 @@ jobs: # if: ${{github.event.inputs.use_bun == 'false'}} run: | bun install - bun install --cwd test/bun.js - bun install --cwd test/bun.js/third-party/body-parser-test - #bun install --cwd test/bun.js/third-party/napi_create_external + bun install --cwd test bun install --cwd packages/bun-internal-test node packages/bun-internal-test/src/runner.node.mjs || true - name: Comment on PR diff --git a/.github/workflows/bun-mac-x64-baseline.yml b/.github/workflows/bun-mac-x64-baseline.yml index c4309a66c121..b2430e93b77c 100644 --- a/.github/workflows/bun-mac-x64-baseline.yml +++ b/.github/workflows/bun-mac-x64-baseline.yml @@ -428,9 +428,7 @@ jobs: # if: ${{github.event.inputs.use_bun == 'false'}} run: | bun install - bun install --cwd test/bun.js - bun install --cwd test/bun.js/third-party/body-parser-test - #bun install --cwd test/bun.js/third-party/napi_create_external + bun install --cwd test bun install --cwd packages/bun-internal-test node packages/bun-internal-test/src/runner.node.mjs || true - name: Comment on PR diff --git a/.gitignore b/.gitignore index 79930b44e735..49fb9c41d5e6 100644 --- a/.gitignore +++ b/.gitignore @@ -112,3 +112,5 @@ src/deps/c-ares/build src/bun.js/debug-bindings-obj failing-tests.txt +test.txt +myscript.sh \ No newline at end of file diff --git a/.prettierignore b/.prettierignore index b9249b91c49f..464309eabd33 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,11 +1,4 @@ src/fallback.html -# src/test -test/bun.js/solid-dom-fixtures -test/bun.js/bundled -#src/bun.js/builtins -# src/api/demo -test/snapshots -test/snapshots-no-hmr src/bun.js/WebKit src/bun.js/builtins/js src/*.out.js @@ -13,3 +6,5 @@ src/*out.*.js src/deps src/test/fixtures src/react-refresh.js +test/snapshots +test/snapshots-no-hmr diff --git a/Makefile b/Makefile index c76eddd21d7e..4df8567ddb6f 100644 --- a/Makefile +++ b/Makefile @@ -590,8 +590,7 @@ release-types: .PHONY: format format: ## to format the code - -$(PRETTIER) --write 'test/bun.js/*.{js,jsx,ts,tsx}' - -$(PRETTIER) --write 'test/bun.js/solid-dom-fixtures/**/*.{js,jsx,ts,tsx}' + -$(PRETTIER) --write 'test/**/*.{js,jsx,ts,tsx}' .PHONY: lolhtml @@ -619,7 +618,7 @@ boringssl-debug: boringssl-build-debug boringssl-copy .PHONY: compile-ffi-test compile-ffi-test: - clang $(OPTIMIZATION_LEVEL) -shared -undefined dynamic_lookup -o /tmp/bun-ffi-test.dylib -fPIC ./test/bun.js/ffi-test.c + clang $(OPTIMIZATION_LEVEL) -shared -undefined dynamic_lookup -o /tmp/bun-ffi-test.dylib -fPIC ./test/js/bun/ffi/ffi-test.c sqlite: @@ -1095,45 +1094,8 @@ dev: mkdir-dev dev-obj bun-link-lld-debug mkdir-dev: mkdir -p $(DEBUG_PACKAGE_DIR)/bin -test-install: - cd test/scripts && $(NPM_CLIENT) install - -.PHONY: test-bun-dev -test-bun-dev: - BUN_BIN=$(RELEASE_BUN) bash test/apps/bun-dev.sh - BUN_BIN=$(RELEASE_BUN) bash test/apps/bun-dev-index-html.sh - -.PHONY: test-dev-bun-dev -test-dev-bun-dev: - BUN_BIN=$(DEBUG_BUN) bash test/apps/bun-dev.sh - BUN_BIN=$(DEBUG_BUN) bash test/apps/bun-dev-index-html.sh - -.PHONY: test-bun-snapshot -test-bun-snapshot: - rm -rf test/bun.js/snapshots.js - touch test/bun.js/snapshots.js - $(foreach i,$(wildcard test/bun.js/*.snapshot.*),echo "" >> test/bun.js/snapshots.js; echo "// $i" >> test/bun.js/snapshots.js; $(RELEASE_BUN) build $i --platform=bun >> test/bun.js/snapshots.js;) - -.PHONY: test-dev-bun-snapshot -test-dev-bun-snapshot: - rm -rf test/bun.js/snapshots.debug.js - touch test/bun.js/snapshots.debug.js - $(foreach i,$(wildcard test/bun.js/*.snapshot.*),echo "" >> test/bun.js/snapshots.debug.js; echo "// $i" >> test/bun.js/snapshots.debug.js; $(DEBUG_BUN) build $i --platform=bun >> test/bun.js/snapshots.debug.js;) - -.PHONY: test-bun-init -test-bun-init: - BUN_BIN=$(RELEASE_BUN) bash test/apps/bun-init-check.sh - -.PHONY: test-dev-bun-init -test-dev-bun-init: - BUN_BIN=$(DEBUG_BUN) bash test/apps/bun-init-check.sh - -.PHONY: test-bun-wiptest -test-bun-wiptest: test/wiptest/run - cd test/wiptest && BUN_BIN=$(DEBUG_BUN) ./run ./fixtures - -.PHONY: test-all -test-all: test-install test-bun-snapshot test-with-hmr test-no-hmr test-create-next test-create-react test-bun-run test-bun-install test-bun-dev test-bun-init +test-all: + $(RELEASE_BUN) test .PHONY: copy-test-node-modules copy-test-node-modules: @@ -1144,73 +1106,6 @@ copy-test-node-modules: kill-bun: -killall -9 bun bun-debug -.PHONY: test-dev-create-next -test-dev-create-next: - BUN_BIN=$(DEBUG_BUN) bash test/apps/bun-create-next.sh - -.PHONY: test-dev-create-react -test-dev-create-react: - BUN_BIN=$(DEBUG_BUN) bash test/apps/bun-create-react.sh - -.PHONY: test-create-next -test-create-next: - BUN_BIN=$(RELEASE_BUN) bash test/apps/bun-create-next.sh - -.PHONY: test-bun-run -test-bun-run: - cd test/apps && BUN_BIN=$(RELEASE_BUN) bash ./bun-run-check.sh - -.PHONY: test-bun-install -test-bun-install: test-bun-install-git-status - cd test/apps && JS_RUNTIME=$(RELEASE_BUN) NPM_CLIENT=$(RELEASE_BUN) bash ./bun-install.sh - cd test/apps && BUN_BIN=$(RELEASE_BUN) bash ./bun-install-utf8.sh - -.PHONY: test-bun-install-git-status -test-bun-install-git-status: - cd test/apps && JS_RUNTIME=$(RELEASE_BUN) BUN_BIN=$(RELEASE_BUN) bash ./bun-install-lockfile-status.sh - -.PHONY: test-dev-bun-install -test-dev-bun-install: test-dev-bun-install-git-status - cd test/apps && JS_RUNTIME=$(DEBUG_BUN) NPM_CLIENT=$(DEBUG_BUN) bash ./bun-install.sh - cd test/apps && BUN_BIN=$(DEBUG_BUN) bash ./bun-install-utf8.sh - -.PHONY: test-dev-bun-install-git-status -test-dev-bun-install-git-status: - cd test/apps && BUN_BIN=$(DEBUG_BUN) bash ./bun-install-lockfile-status.sh - -.PHONY: test-create-react -test-create-react: - BUN_BIN=$(RELEASE_BUN) bash test/apps/bun-create-react.sh - -.PHONY: test-with-hmr -test-with-hmr: kill-bun copy-test-node-modules - BUN_BIN=$(RELEASE_BUN) node test/scripts/browser.js - -.PHONY: test-no-hmr -test-no-hmr: kill-bun copy-test-node-modules - -killall bun -9; - DISABLE_HMR="DISABLE_HMR" BUN_BIN=$(RELEASE_BUN) node test/scripts/browser.js - -.PHONY: test-dev-with-hmr -test-dev-with-hmr: copy-test-node-modules - -killall bun-debug -9; - BUN_BIN=$(DEBUG_BUN) node test/scripts/browser.js - -.PHONY: test-dev-no-hmr -test-dev-no-hmr: copy-test-node-modules - -killall bun-debug -9; - DISABLE_HMR="DISABLE_HMR" BUN_BIN=$(DEBUG_BUN) node test/scripts/browser.js - -.PHONY: test-dev-bun-hmr -test-dev-bun-run: - cd test/apps && BUN_BIN=$(DEBUG_BUN) bash bun-run-check.sh - -.PHONY: test-dev-all -test-dev-all: test-install test-dev-bun-snapshot test-dev-with-hmr test-dev-no-hmr test-dev-create-next test-dev-create-react test-dev-bun-run test-dev-bun-install test-dev-bun-dev test-dev-bun-init -test-dev-bunjs: - -test-dev: test-dev-with-hmr - jsc-copy-headers: cp $(WEBKIT_DIR)/Source/JavaScriptCore/heap/WeakHandleOwner.h $(WEBKIT_RELEASE_DIR)/JavaScriptCore/PrivateHeaders/JavaScriptCore/WeakHandleOwner.h cp $(WEBKIT_DIR)/Source/JavaScriptCore/runtime/LazyClassStructureInlines.h $(WEBKIT_RELEASE_DIR)/JavaScriptCore/PrivateHeaders/JavaScriptCore/LazyClassStructureInlines.h diff --git a/bunfig.toml b/bunfig.toml index 8c6c75190ac1..4e205af4f0ce 100644 --- a/bunfig.toml +++ b/bunfig.toml @@ -1,8 +1,8 @@ [test] # Large monorepos (like Bun) may want to specify the test directory more specifically -# By default, `bun wiptest` scans every single folder recurisvely which, if you +# By default, `bun test` scans every single folder recurisvely which, if you # have a gigantic submodule (like WebKit), it has to do lots of directory # traversals # # Instead, we can just make it scan only the test directory for Bun's runtime tests -root = "test/bun.js" +root = "test" diff --git a/packages/bun-internal-test/package.json b/packages/bun-internal-test/package.json index 6504f57b4866..accc76f77da0 100644 --- a/packages/bun-internal-test/package.json +++ b/packages/bun-internal-test/package.json @@ -1,5 +1,6 @@ { "private": true, + "type": "module", "dependencies": { "@actions/core": "^1.10.0" }, @@ -8,6 +9,6 @@ "prettier": "^2.8.2" }, "scripts": { - "test": "bun run src/runner.ts" + "test": "node src/runner.node.mjs" } } diff --git a/packages/bun-internal-test/src/runner.node.mjs b/packages/bun-internal-test/src/runner.node.mjs index 135ee246e8d7..eca8a9bb142a 100644 --- a/packages/bun-internal-test/src/runner.node.mjs +++ b/packages/bun-internal-test/src/runner.node.mjs @@ -129,7 +129,7 @@ function findErrors(data) { } var tests = []; var testFileNames = []; -for (const path of findTests(resolve(cwd, "test/bun.js"))) { +for (const path of findTests(resolve(cwd, "test"))) { testFileNames.push(path); tests.push(runTest(path).catch(console.error)); } diff --git a/packages/bun-internal-test/src/runner.ts b/packages/bun-internal-test/src/runner.ts deleted file mode 100644 index 4b660fe23ec5..000000000000 --- a/packages/bun-internal-test/src/runner.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { ArrayBufferSink, spawn } from "bun"; -import { readdirSync, openSync, writeSync, truncateSync } from "node:fs"; -import { resolve } from "node:path"; -import * as action from "@actions/core"; -import { StringDecoder } from "node:string_decoder"; - -const cwd = resolve("../.."); -process.chdir(cwd); - -const isAction = !!process.env["GITHUB_ACTION"]; -const errorPattern = /error: ([\S\s]*?)(?=\n.*?at (\/.*):(\d+):(\d+))/gim; - -function* findTests(dir: string, query?: string): Generator { - for (const entry of readdirSync(resolve(dir), { encoding: "utf-8", withFileTypes: true })) { - const path = resolve(dir, entry.name); - if (path.includes("node_modules")) continue; - - if (entry.isDirectory()) { - yield* findTests(path, query); - } else if (entry.isFile() && entry.name.includes(".test.")) { - yield path; - } - } -} - -async function runTest(path: string): Promise { - const name = path.replace(cwd, "").slice(1); - const runner = spawn({ - cmd: ["bun", "test", path], - stdout: "pipe", - stderr: "pipe", - stdin: "ignore", - env: { - ...process.env, - FORCE_COLOR: "1", - }, - }); - const exitCode = await Promise.race([ - new Promise(resolve => { - setTimeout(() => { - runner.kill(); - resolve(124); // Timed Out - }, 60_000); - }), - runner.exited, - ]); - if (isAction) { - const prefix = exitCode === 0 ? "PASS" : `FAIL (exit code ${exitCode})`; - action.startGroup(`${prefix} - ${name}`); - } - for (const stdout of [runner.stdout, runner.stderr]) { - if (!stdout) { - continue; - } - const reader = stdout.getReader(); - while (true) { - const { value, done } = await reader.read(); - if (value) { - console.write(value); - if (isAction) { - findErrors(value); - } - } - if (done) { - break; - } - } - } - if (isAction) { - action.endGroup(); - } -} - -let failed = false; - -function findErrors(data: Uint8Array): void { - const text = new StringDecoder().write(new Buffer(data.buffer)); - for (const [message, _, path, line, col] of text.matchAll(errorPattern)) { - failed = true; - action.error(message, { - file: path.replace(cwd, "").slice(1), - startLine: parseInt(line), - startColumn: parseInt(col), - }); - } - Bun.gc(true); -} - -const tests = []; -for (const path of findTests(resolve(cwd, "test/bun.js"))) { - tests.push(runTest(path).catch(console.error)); -} -await Promise.allSettled(tests); -process.exit(failed ? 1 : 0); diff --git a/packages/bun-internal-test/tsconfig.json b/packages/bun-internal-test/tsconfig.json index 1b2f41220199..860a9d36510a 100644 --- a/packages/bun-internal-test/tsconfig.json +++ b/packages/bun-internal-test/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "noEmit": true, "lib": ["ESNext"], "module": "ESNext", "target": "ESNext", diff --git a/test/fixtures/bun-link-pkg-fixture/.gitignore b/test/.prettierignore similarity index 54% rename from test/fixtures/bun-link-pkg-fixture/.gitignore rename to test/.prettierignore index 6455d29a687e..3c3629e647f5 100644 --- a/test/fixtures/bun-link-pkg-fixture/.gitignore +++ b/test/.prettierignore @@ -1,2 +1 @@ node_modules -*.bunlockb diff --git a/test/README.md b/test/README.md index 274b19fbfbfb..36fe3b005717 100644 --- a/test/README.md +++ b/test/README.md @@ -1,145 +1,73 @@ -# Tests in Bun +# Tests -Bun currently has four different kinds of tests +## Finding tests -To run all the tests: +Tests are located in the [`test/`](test/) directory and are organized using the following structure: -```bash -make test-all -bun --cwd=test/bun.js wiptest -``` - -### Runtime tests - -To run the runtime tests: +* `test/` + * `js/` - tests for JavaScript APIs. + * `cli/` - tests for commands, configs, and stdout. + * `bundler/` - tests for the transpiler/bundler. + * `regression/` - tests that reproduce a specific issue. + * `harness.ts` - utility functions that can be imported from any test. -```bash -cd test/bun.js -bun wiptest -``` +The tests in [`test/js/`](test/js/) directory are further categorized by the type of API. -These tests are in [./bun.js](./bun.js) and are files with `.test.js` or `.test.ts` in the filename. +* `test/js/` + * `bun/` - tests for `Bun`-specific APIs. + * `node/` - tests for Node.js APIs. + * `web/` - tests for Web APIs, like `fetch()`. + * `first_party/` - tests for npm packages that are built-in, like `undici`. + * `third_party/` - tests for npm packages that are not built-in, but are popular, like `esbuild`. -These test that the runtime behaves as expected. These also test the transpiler, both because test files are transpiled and directly by running the transpiler via `Bun.Transpiler`. +## Running tests -#### Adding a new test +To run a test, use Bun's built-in test command: `bun test`. -1. Create a new file in [./bun.js](./bun.js/) with `.test` in the name. +```sh +bun test # Run all tests +bun test js/bun # Only run tests in a directory +bun test sqlite.test.ts # Only run a specific test +``` -These test use `bun:test` as the import (though you can also import from `vitest` or jest and it will work). +If you encounter lots of errors, try running `bun install`, then trying again. -This will eventually be a public test runner for bun, but the reporter isn't very good yet and it doesn't run in parallel. +## Writing tests -The syntax intends for Jest compatibility. +Tests are written in TypeScript (preferred) or JavaScript using Jest's `describe()`, `test()`, and `expect()` APIs. ```ts -import { describe, expect, it } from "bun:test"; - -describe("Example", () => { - it("should work", () => { - expect(1).toBe(1); +import { describe, test, expect } from "bun:test"; +import { gcTick } from "harness"; + +describe("TextEncoder", () => { + test("can encode a string", async () => { + const encoder = new TextEncoder(); + const actual = encoder.encode("bun"); + await gcTick(); + expect(actual).toBe(new Uint8Array([0x62, 0x75, 0x6E])); }); }); ``` -### Browser tests - -Browser tests run end-to-end inside of Puppeteer and execute code transpiled by `bun dev`. These tests are in [./snippets](./snippets). - -The interface is: - -```js -// this function is called after import() -// if testDone() is never called, the test fails -export function test() { - return testDone(import.meta.url); -} -``` - -On success, it saves a snapshot to [./snapshots](./snapshots) which is checked into git. - -#### Adding a new test - -1. Create a new file in the `snippets` directory. -2. Append the filename to [./scripts/snippets.json](./scripts/snippets.json) -3. Run `bun dev` inside this folder in one terminal window -4. Run `make integration-test-dev` - -These tests are run twice. Once with HMR enabled and once with HMR disabled. HMR changes the output enough to warrant it's own special treatment. - -#### Running the tests - -To run the browser tests with HMR on a production build: - -```bash -make test-with-hmr -``` - -To run the browser tests without HMR on a production build: - -```bash -make test-with-no-hmr -``` - -To run the browser tests with HMR on a debug build: - -```bash -make test-dev-with-hmr -``` - -To run the browser tests without HMR on a debug build: +If you are fixing a bug that was reported from a GitHub issue, remember to add a test in the `test/regression/` directory. -```bash -make test-dev-no-hmr -``` +```ts +// test/regression/issue/02005.test.ts -To run the browser tests on whatever version of bun is running on port 3000: +import { it, expect } from "bun:test"; -```bash -make integration-test-dev +it("regex literal should work with non-latin1", () => { + const text = "这是一段要替换的文字"; + expect(text.replace(new RegExp("要替换"), "")).toBe("这是一段的文字"); + expect(text.replace(/要替换/, "")).toBe("这是一段的文字"); +}); ``` -These were the first tests bun started with - -#### Running the tests - -Run `bun wiptest ${part-of-file-name}` - -If you run the test in the top-level bun repo directory, it will take an extra couple seconds because `bun wiptest` will scan through all of WebKit recursively. Consider running it in the `bun.js` directory instead. - -### CLI tests +In the future, a bot will automatically close or re-open issues when a regression is detected or resolved. -These run the bash files in the `apps` directory. - -They check end-to-end that the CLI works as expected. - -```bash -# Install dependencies for running tests -# Does not run tests -make test-install - -# Check a Create React App created via `bun create react ./foo` returns HTML -make test-create-react - -# Check a Next.js app created via `bun create next ./foo` SSRs successfully -make test-create-next - -# Check that bun run works for the same CLI args passed to npm run -make test-bun-run - -# Check that "react" installed via bun install loads successfully -# and that deleting/adding updates the lockfile as expected -make test-bun-install - -# Check that serving public paths works correctly -# and that files which should be transpiled are transpiled and files which shouldn't be aren't -make test-bun-dev -``` - -### Zig tests +## Zig tests These tests live in various `.zig` files throughout Bun's codebase, leveraging Zig's builtin `test` keyword. -Currently, they're not run automatically nor is there a simple way to run all of them. - -This is an area bun needs to improve in. +Currently, they're not run automatically nor is there a simple way to run all of them. We will make this better soon. diff --git a/test/apps/bun-create-next.sh b/test/apps/bun-create-next.sh deleted file mode 100644 index d8e148282c97..000000000000 --- a/test/apps/bun-create-next.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -# The important part of this test: make sure that bun.js successfully loads -# The most likely reason for this test to fail is that something broke in the JavaScriptCore <> bun integration -killall -9 $(basename $BUN_BIN) || echo "" - -rm -rf /tmp/next-app -mkdir -p /tmp/next-app -$BUN_BIN create next /tmp/next-app - -if (($?)); then - echo "bun create failed" - exit 1 -fi - -echo "hi!" >/tmp/next-app/public/file.txt -echo "export default 'string';" >/tmp/next-app/file.js - -cd /tmp/next-app -BUN_CRASH_WITHOUT_JIT=1 $BUN_BIN dev --port 8087 & -sleep 0.1 -curl --fail -Ss http://localhost:8087/ - -if [[ "$(curl --fail -sS http://localhost:8087/file.txt)" != "hi!" ]]; then - echo "" - echo "" - echo "" - echo "ERR: Expected 'hi!', got '$(curl --fail -sS http://localhost:8087/file.txt)'" - killall -9 $(basename $BUN_BIN) || echo "" - exit 1 -fi - -if [[ "$(curl --fail -sS http://localhost:8087/file.js)" != *"string"* ]]; then - echo "" - echo "" - echo "" - echo "ERR: Expected file to contain string got '$(curl --fail -sS http://localhost:8087/file.js)'" - killall -9 $(basename $BUN_BIN) || echo "" - exit 1 -fi - -# very simple HMR test -echo "export default 'string';" >/tmp/next-app/file2.js -sleep 0.1 - -if [[ "$(curl --fail -sS http://localhost:8087/file2.js)" != *"string"* ]]; then - echo "" - echo "" - echo "" - echo "ERR: Expected file to contain string got '$(curl --fail -sS http://localhost:8087/file2.js)'" - killall -9 $(basename $BUN_BIN) || echo "" - exit 1 -fi - -killall -9 $(basename $BUN_BIN) || echo "" diff --git a/test/apps/bun-create-react.sh b/test/apps/bun-create-react.sh deleted file mode 100644 index 564a59d35e42..000000000000 --- a/test/apps/bun-create-react.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bash - -killall -9 $(basename $BUN_BIN) || echo "" - -rm -rf /tmp/react-app -mkdir -p /tmp/react-app -$BUN_BIN create react /tmp/react-app - - -if (($?)); then - echo "bun create failed" - exit 1 -fi - -cd /tmp/react-app -BUN_CRASH_WITHOUT_JIT=1 $BUN_BIN dev --port 8087 & -sleep 0.005 - -curl --fail http://localhost:8087/ && curl --fail http://localhost:8087/src/index.jsx && killall -9 $(basename $BUN_BIN) && echo "✅ bun create react passed." -exit $? diff --git a/test/apps/bun-dev-index-html.sh b/test/apps/bun-dev-index-html.sh deleted file mode 100644 index f22f8df514c3..000000000000 --- a/test/apps/bun-dev-index-html.sh +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -killall -9 $(basename $BUN_BIN) || echo "" - -dir=$(mktemp -d) - -index_content="index.html" -bacon_content="bacon.html" -js_content="if(0) { var foo = 'TEST FAILED'; }" -static_content="PASS" -css_not_transpiled_content="@import url(/index.js); @import url(/i-dont-exist.css); @import url('/i-dont-exist.css'); @import url(\"/i-dont-exist.css\");" -css_is_transpiled_import="*{background-color:red;}" -css_is_transpiled="@import url(./css_is_transpiled_import.css);" - -echo $index_content >"$dir/index.html" -echo $js_content >"$dir/index.js" -echo $bacon_content >"$dir/bacon.html" -echo $static_content >"$dir/static.txt" -echo $css_not_transpiled_content >"$dir/css_not_transpiled_content.css" - -cd $dir -$BUN_BIN dev --port 8087 & -sleep 0.005 - -if [[ "$(curl --fail -sS http://localhost:8087/)" != "$index_content" ]]; then - echo "ERR: Expected '$index_content', got '$(curl --fail -sS http://localhost:8087/)'" - exit 1 -fi - -if [[ "$(curl --fail -sS http://localhost:8087/index)" != "$index_content" ]]; then - echo "ERR: Expected '$index_content', got '$(curl --fail -sS http://localhost:8087/index)'" - exit 1 -fi - -if [[ "$(curl --fail -sS http://localhost:8087/static.txt)" != "PASS" ]]; then - echo "ERR: Expected static file, got '$(curl --fail -sS http://localhost:8087/static.txt)'" - exit 1 -fi - -# Check that the file is actually transpiled -if [[ "$(curl --fail -sS http://localhost:8087/index.js)" == *"TEST FAILED"* ]]; then - echo "ERR: Expected file to be transpiled, got '$(curl --fail -sS http://localhost:8087/index.js)'" - exit 1 -fi - -if [[ "$(curl --fail -sS http://localhost:8087/index.html)" != "$index_content" ]]; then - echo "ERR: Expected '$index_content', got '$(curl --fail -sS http://localhost:8087/index.html)'" - exit 1 -fi - -if [[ "$(curl --fail -sS http://localhost:8087/foo/foo)" != "$index_content" ]]; then - echo "ERR: Expected '$index_content', got '$(curl --fail -sS http://localhost:8087/index.html)'" - exit 1 -fi - -if [[ "$(curl --fail -sS http://localhost:8087/bacon)" != "$bacon_content" ]]; then - echo "ERR: Expected '$bacon_content', got '$(curl --fail -sS http://localhost:8087/bacon)'" - exit 1 -fi - -if [[ "$(curl --fail -sS http://localhost:8087/bacon.html)" != "$bacon_content" ]]; then - echo "ERR: Expected '$bacon_content', got '$(curl --fail -sS http://localhost:8087/bacon.html)'" - exit 1 -fi - -killall -9 $(basename $BUN_BIN) || echo "" -echo "✅ bun dev index html check passed." diff --git a/test/apps/bun-dev.sh b/test/apps/bun-dev.sh deleted file mode 100644 index ea15c1d21d89..000000000000 --- a/test/apps/bun-dev.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -killall -9 $(basename $BUN_BIN) || echo "" - -dir=$(mktemp -d) - -index_content="index.html" -bacon_content="bacon.html" -js_content="console.log('hi')" - -mkdir -p $dir/public - -echo $index_content >"$dir/public/index.html" -echo $js_content >"$dir/index.js" -echo $bacon_content >"$dir/public/bacon.html" - -cd $dir - -$BUN_BIN dev --port 8087 & -sleep 0.005 - -if [ "$(curl --fail -sS http://localhost:8087/)" != "$index_content" ]; then - echo "ERR: Expected '$index_content', got '$(curl --fail -sS http://localhost:8087/)'" - exit 1 -fi - -if [ "$(curl --fail -sS http://localhost:8087/index)" != "$index_content" ]; then - echo "ERR: Expected '$index_content', got '$(curl --fail -sS http://localhost:8087/index)'" - exit 1 -fi - -if [ "$(curl --fail -sS http://localhost:8087/index.html)" != "$index_content" ]; then - echo "ERR: Expected '$index_content', got '$(curl --fail -sS http://localhost:8087/index.html)'" - exit 1 -fi - -if [ "$(curl --fail -sS http://localhost:8087/foo/foo)" != "$index_content" ]; then - echo "ERR: Expected '$index_content', got '$(curl --fail -sS http://localhost:8087/index.html)'" - exit 1 -fi - -if [ "$(curl --fail -sS http://localhost:8087/bacon)" != "$bacon_content" ]; then - echo "ERR: Expected '$index_content', got '$(curl --fail -sS http://localhost:8087/bacon)'" - exit 1 -fi - -if [ "$(curl --fail -sS http://localhost:8087/bacon.html)" != "$bacon_content" ]; then - echo "ERR: Expected '$index_content', got '$(curl --fail -sS http://localhost:8087/bacon.html)'" - exit 1 -fi - -killall -9 $(basename $BUN_BIN) || echo "" -echo "✅ bun dev index html check passed." diff --git a/test/apps/bun-init-check.sh b/test/apps/bun-init-check.sh deleted file mode 100644 index d0077cb60df7..000000000000 --- a/test/apps/bun-init-check.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bash - -# TODO: move this test to bun once we have a child_process equivalent. -(killall -9 $(basename $BUN_BIN) || echo "") >/dev/null 2>&1 - -rm -rf /tmp/bun-init-check -mkdir -p /tmp/bun-init-check - -cd /tmp/bun-init-check - -$BUN_BIN init -y - -if (($?)); then - echo "Bun init failed" - exit 1 -fi - -SHASUM_RESULT=$(cat index.ts .gitignore tsconfig.json package.json | shasum) - -# This test will fail when the minor version of Bun changes. -if [[ "${SHASUM_RESULT}" != "10eabf5101a3ef999bd67232a7af33542c525ec6 -" ]]; then - echo -e "Bun init shasum mismatch\n expected: b1548bb4e806f0506fd1b27ae8901d2e84926774\n actual: ${SHASUM_RESULT}" - exit 1 -fi - -exit 0 diff --git a/test/apps/bun-install-lockfile-status.sh b/test/apps/bun-install-lockfile-status.sh deleted file mode 100644 index fc39a6dd6e81..000000000000 --- a/test/apps/bun-install-lockfile-status.sh +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -killall -9 $(basename $BUN_BIN) || echo "" - -dir=$(mktemp -d) - -cd $dir - -$BUN_BIN add react - -echo "node_modules" >.gitignore - -git init && git add . && git commit -am "Initial commit" - -$BUN_BIN install - -ORIG_LOCKFILE="$($BUN_BIN pm hash-string)" - -[[ -z $(git status --untracked-files=no --porcelain) ]] || { - echo "ERR: Expected empty git status, got '$(git status --untracked-files=no --porcelain)'" - exit 1 -} - -$BUN_BIN add react - -NEW_LOCKFILE="$($BUN_BIN pm hash-string)" - -diff <(echo "$ORIG_LOCKFILE") <(echo "$NEW_LOCKFILE") || { - echo "ERR: Expected lockfile to be unchanged, got '$NEW_LOCKFILE'" - exit 1 -} - -ORIG_HASH=$($BUN_BIN bun.lockb --hash) - -$BUN_BIN remove react -$BUN_BIN add react - -NEW_HASH=$($BUN_BIN bun.lockb --hash) - -diff <(echo "$ORIG_HASH") <(echo "$NEW_HASH") || { - echo "ERR: Expected hash to be unchanged, got '$NEW_HASH'" - exit 1 -} - -echo '{ "dependencies": { "react": "17.0.2", "react-dom": "17.0.2" } }' >package.json - -$BUN_BIN install - -echo "var {version} = JSON.parse(require(\"fs\").readFileSync('./node_modules/react-dom/package.json', 'utf8')); if (version !== '17.0.2') {throw new Error('Unexpected react-dom version');}; " >index.js -$BUN_BIN run ./index.js - -echo "var {version} = JSON.parse(require(\"fs\").readFileSync('./node_modules/react/package.json', 'utf8')); if (version !== '17.0.2') {throw new Error('Unexpected react version');}; " >index.js -$BUN_BIN run ./index.js - -# This is just making sure that the JS was executed -realpath -e node_modules/react-dom >/dev/null || { - echo "ERR: Expected react-dom to be installed" - exit 1 -} -realpath -e node_modules/react >/dev/null || { - echo "ERR: Expected react to be installed" - exit 1 -} diff --git a/test/apps/bun-install-utf8.sh b/test/apps/bun-install-utf8.sh deleted file mode 100644 index ac6b684ed4d2..000000000000 --- a/test/apps/bun-install-utf8.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -killall -9 $(basename $BUN_BIN) || echo "" - -dir=$(mktemp -d) - -cd $dir - -# https://github.com/oven-sh/bun/issues/115 -echo '{ "author": "Arnaud Barré (https://github.com/ArnaudBarre)" }' >package.json - -$BUN_BIN add react diff --git a/test/apps/bun-install.sh b/test/apps/bun-install.sh deleted file mode 100644 index 18813b2d27e3..000000000000 --- a/test/apps/bun-install.sh +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -dir=$(mktemp -d) - -cd $dir -${NPM_CLIENT:-$(which bun)} add react react-dom @types/react @babel/parser esbuild vite@3.0.0 - -echo "console.log(typeof require(\"react\").createElement);" >index.js -chmod +x index.js - -JS_RUNTIME=${JS_RUNTIME:-"$(which bun)"} - -if [ "$JS_RUNTIME" == "node" ]; then - result="$(node ./index.js)" -fi - -if [ "$JS_RUNTIME" != "node" ]; then - result="$($JS_RUNTIME run ./index.js)" -fi - -echo "console.log(typeof require(\"react-dom\").render);" >index.js -chmod +x index.js - -JS_RUNTIME=${JS_RUNTIME:-"$(which bun)"} - -# If this fails to run, it means we didn't link @babel/parser correctly -$(which grealpath || which realpath) -e ./node_modules/.bin/parser >/dev/null - -# If this fails to run, it means we didn't link esbuild correctly or esbuild's install script broke -# - https://github.com/evanw/esbuild/issues/2558 -./node_modules/.bin/esbuild --version >/dev/null - -VITE_ESBUILD="$(echo node_modules/vite/node_modules/esbuild-*)" -$VITE_ESBUILD/bin/esbuild --version >/dev/null - -if [ "$JS_RUNTIME" == "node" ]; then - result="$(node ./index.js)" -fi - -if [ "$JS_RUNTIME" != "node" ]; then - result="$($JS_RUNTIME run ./index.js)" -fi - -if [ "$result" != "function" ]; then - echo "ERR: Expected 'function', got '$result'" - exit 1 -fi - -${NPM_CLIENT:-$(which bun)} remove react-dom - -if [ -d "node_modules/react-dom" ]; then - echo "ERR: react-dom module still exists in $dir" - exit 1 -fi - -yarn_dot_lock=$(${NPM_CLIENT:-$(which bun)} bun.lockb) - -if echo "$yarn_dot_lock" | grep -q "react-dom"; then - echo "ERR: react-dom module still exists in lockfile" - exit 1 -fi - -${NPM_CLIENT:-$(which bun)} remove @types/react - -yarn_dot_lock=$(${NPM_CLIENT:-$(which bun)} bun.lockb) - -if echo "$yarn_dot_lock" | grep -q "@types/react"; then - echo "ERR: @types/react module still exists in lockfile" - exit 1 -fi - -if echo "$yarn_dot_lock" | grep -q "@types/react"; then - echo "ERR: @types/react module still exists in $dir" - exit 1 -fi - -${NPM_CLIENT:-$(which bun)} remove react - -if [ -d "node_modules/react" ]; then - echo "ERR: react module still exists in $dir" - exit 1 -fi - -if [ -d "bun.lockb" ]; then - echo "ERR: empty bun.lockb should be deleted" - exit 1 -fi diff --git a/test/apps/bun-run-check-nameless-package.json b/test/apps/bun-run-check-nameless-package.json deleted file mode 100644 index 8ace0361377b..000000000000 --- a/test/apps/bun-run-check-nameless-package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "scripts": { - "this-should-work": "echo \"✅ bun run test passed!\"", - "argv": "node -e 'console.log(process.argv)'" - } -} diff --git a/test/apps/bun-run-check-package.json b/test/apps/bun-run-check-package.json deleted file mode 100644 index b6eeda0462dd..000000000000 --- a/test/apps/bun-run-check-package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "check", - "scripts": { - "this-should-work": "echo \"✅ bun run test passed!\"", - "argv": "node -e 'console.log(process.argv)'" - } -} diff --git a/test/apps/bun-run-check.sh b/test/apps/bun-run-check.sh deleted file mode 100644 index 25432c4227fa..000000000000 --- a/test/apps/bun-run-check.sh +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env bash - -# TODO: move this test to bun once we have a child_process equivalent. -(killall -9 $(basename $BUN_BIN) || echo "") >/dev/null 2>&1 - -# https://github.com/oven-sh/bun/issues/40 -# Define a function (details aren't important) -fn() { :; } -# The important bit: export the function -export -f fn - -rm -rf /tmp/bun-run-check -mkdir -p /tmp/bun-run-check - -cp ./bun-run-check-package.json /tmp/bun-run-check/package.json -cd /tmp/bun-run-check - -$BUN_BIN run bash -- -c "" - -if (($?)); then - echo "Bash exported functions are broken" - exit 1 -fi - -# We need to run these tests for two variations: -# bun run foo "bar" -# bun run foo -- "bar" -# the "--" should be ignored -# in earlier versions of bun, it was required to be present - -$BUN_BIN run bash -c "" -if (($?)); then - echo "Bash exported functions are broken" - exit 1 -fi - -# https://github.com/oven-sh/bun/issues/53 -rm -f /tmp/bun-run-out.expected.txt /tmp/bun-run-out.txt >/dev/null 2>&1 - -$BUN_BIN run --silent argv -- foo bar baz >/tmp/bun-run-out.txt -npm run --silent argv -- foo bar baz >/tmp/bun-run-out.expected.txt - -cmp -s /tmp/bun-run-out.expected.txt /tmp/bun-run-out.txt -if (($?)); then - echo "argv failed" - exit 1 -fi - -rm -f /tmp/bun-run-out.expected.txt /tmp/bun-run-out.txt >/dev/null 2>&1 - -$BUN_BIN run --silent argv foo bar baz >/tmp/bun-run-out.txt -npm run --silent argv -- foo bar baz >/tmp/bun-run-out.expected.txt - -cmp -s /tmp/bun-run-out.expected.txt /tmp/bun-run-out.txt -if (($?)); then - echo "argv failed" - exit 1 -fi - -$BUN_BIN run --silent this-should-work - -if (($?)); then - echo "this-should work failed" - exit 1 -fi - -exit 0 diff --git a/test/bun.js/.prettierignore b/test/bun.js/.prettierignore deleted file mode 100644 index 91b589eb2a1e..000000000000 --- a/test/bun.js/.prettierignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules -third-party diff --git a/test/bun.js/bun.lockb b/test/bun.js/bun.lockb deleted file mode 100755 index 0c6a33d3d134119c1a670365239ac729a4e6f9eb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9454 zcmeHMc|6ox8z1~c_NZJ*$(EKGV+q+MQI=Fno36oNY7EVcwQ?0%l9Kkci58KvM3!3$ zA?3DRMSC}LNlB7>3-38Ib7o0zZ_GdMeLwH{d}f~C?|IJiJSc^?ST9Jd-QThAg}+(yzMm8{E$zZ2J`~G-WN5pN2|7j%8G~yH zEqswLQ@CXiGfhb^dgdY(B*oCQoUb8mWqf8weY}gw{^z;vULs+lNe#YBKPE&p_lxZ; zcw{zC^0BF+Ljyfd?n6oxgEG}UsRwsYtiqAvxYo~2C1g~_C^J=qli6CE7c(q7Z`*cH zauVM-F3xM_S=I7MFO6pI`)sL~^@-^`(UG)Ux?s+zrl>rL4N3*alAmUNsqf03NAaXv zPd!{K;kJKXTi`X&J)sJi23FAp|3olOaK49Akp6O@1P7HSwLS5P?KR` zApKWB#}w!Tf|Cb7S@`e+1?iWA2X`U#6@cNcLg=p%Qa=aiFA$>rULo6`i?Qb)4*5vN zKLzN!31RHi6!FHHZtfWC_m_FIC3WBv&A;dlYpW&P0e*A!k( zCO{@A{}c*s{om}@fc(I44tO{aKTt$)Y+Qb5Og~J~rTv*@E+Kp7-HU|*uF?^!)1q`b0<81$GBT&T9q_Te zoAa|;Q^|bXgcpsoyPNrN!Li9LnA1D+Fa6^-y6U+aN*iP1(WK$E8U=5Z}B4M z{KML(8e>)$C8w1X`+w+2ubQpvut-^RfnBP|;xE#DlNVW^lsW9{9_iKP7jm1}lXLZs zRfaeP0EvLr7!4>mX1E1=QuethtJ8I?=tu3Oc}d&l{QUQHWR6nW;=01nIOn?5+E#$~_>K~uWa>02A?|PEafP(FxTQJ3^>4gs0>uM`= zo70-Z&5l^7Q94vCVinP~NcCVDF3@o;uk@Gpt zC@_`3(F zOXi&EZuv?4g4~z69@hC+qf(Qi>K=-o>Jx8ov?oDK*4s$Em*Qg?nD3gWckbO z?w&r$*PQ0I4qP5zf*Dypno(VD70_N|^ZxgHFK?NayrVuTH@vXejZ$|wLL!aSb~8X$ zb!vhGK!UjHfP!t1Td<=}F?$lK2Wswb{j$;VY~f!Ex5i9b(6e7Yd&QpgruUl8;VH^< zuI$J*C8u3FdNjTN^fQS7$>ND-TWo0ZtJd}2djyamE(Da~qJnWy2=BMuy*qNv#{0Yf z=(H=Yj%z7&e|UJ~Wyfua%X0I>=$TSw681$|3B5gfYE`Bkbm|za$N4PHzwv(4V!5h< zU)i{#;QIEf3aE#!i-fB^020B8vT)%Tqk@SvB%S;#(Y!icvP#FC7FN9}VTo5*Mc1SKTl5?p{j(}N zLb?tuI+ro9_@;_!tKHTTookNF^=hj;kEqt!JK3pL10cB0ASU>h<`(Q+#A(rsF57nG zsKnSvV_FZm?V9gm`dL zu-f_$*V+IQ^kaw%zS+42TUz9~{DoT1fdirki&C8foZDs|*Swf|Va3zz_>e@CnDFx} zGKfZ77fyMP-RW1hl_j*M29BzG^=P2MN`0&Phogf43GQdmF+j|KTd?+&)RoV4@bi8v z*468*Kjz`2f`4-|;HKiM)vtGEQfuxM;ZmuhMwW?PR(A_A4Y|sSgC}}tM6I;AO#4B#$!_UMKRmQ{jZJi1KH2!P8$q&TT4}K3Lhn~RYuEf+~wWBty>9sPM` zPoMePaVz&8oHqNMhSJ}f_U;yMuiuaLoI?XZaD3rlC?ilnG3T7_8{U;|eS0_POnA`{ z%=}{UMmaih)~&+aLkGzdJrDbO9Pu)9rM2d?%AQiZrhPP`Dp@USYhJn!A)&Yli(t6F+XE^bmhZV`Ex(8j#8!Oj%pHG7 zHmrv2hi4kJ2igVggLXoDq217a*bmqr*e}>W*w0HGdscHCU&>;Y zfw5OON6gfwcW>JwfhJ;xL1R|?lU|%$fMFWXG zfd~XVkLN_Ifn+vYg5udI{>y6zMZ8gjm?uFoaTG)5Nl?@rMVWaL6jw)aXr2T`-ch8Q zCqc1!6wBsGQ1l)}yLl26-$(Iqo&;q8P{f=kK{)~xQ|C!g76C=wc@mUoKyi7V1Z5&n z6-BCppDg%ZLi@D-3dAD49;M4`-6;G!k_QlTM-eTdC{wmb)o;x!>j>Ez<(70t)b2 z;2HYga15BdVv-q529rXIz%wI)$-a1UAcYAU@ue`BtV9<)-vXG-V1}8V9-ZtD*z^bj zEjVy3gAhdb*Mnj`Jd5C2UO-^_M}DXN(3XJqP$piV0KM(|SZwD7`s@ch@Lb^3P>GeH zV<*GDV+&_n005jPJZD>g5H?6kqWRK;D86_SJ&?dX1@a!!3y8N2&4ovjE$&d!HU7b zK0}#w3d1M#>oypMvx(tInMP&`8Xpu)rU`NwA+#U{U-!U}_y&bB@Vvet4zDG|VVVD* z=M^tdpKq20`22G|3`}FAZvYw_jS$Vy;2UU$2E#-{W6FnxSi?l(%@-dQk2y>m9yFp-xI0Y!olp|bpG2qcR;QoL`W zjR;G?&(QWZA|wGHVAzCH@@hiM(s73e9CIZQOp>GJiI^0P;VC}1A<3>zjE zIwHR|Pk=u{OaTKC;1BP}-#*&G6th __esModule; - -export { __esModule, foo as default }; diff --git a/test/bun.js/bundled/always-bundled-module/package.json b/test/bun.js/bundled/always-bundled-module/package.json deleted file mode 100644 index 5029c169598f..000000000000 --- a/test/bun.js/bundled/always-bundled-module/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "always-bundled-module", - "version": "1.0.0" -} diff --git a/test/bun.js/bundled/entrypoint.ts b/test/bun.js/bundled/entrypoint.ts deleted file mode 100644 index b9a17b538a04..000000000000 --- a/test/bun.js/bundled/entrypoint.ts +++ /dev/null @@ -1,13 +0,0 @@ -import "i-am-bundled/cjs"; -import "i-am-bundled/esm"; -import "always-bundled-module/esm"; -import "always-bundled-module/cjs"; -import { foo } from "i-am-bundled/esm"; -import { foo as foo2 } from "always-bundled-module/esm"; -import cJS from "always-bundled-module/cjs"; - -foo(); -foo2(); -cJS(); - -export default cJS(); diff --git a/test/bun.js/bundled/package.json b/test/bun.js/bundled/package.json deleted file mode 100644 index cce72af9c3da..000000000000 --- a/test/bun.js/bundled/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "to-bundle", - "scripts": { - "prebundle": "rm -rf node_modules; cp -r to_bundle_node_modules node_modules; ln -s always-bundled-module node_modules/always-bundled-module", - "bundle": "${BUN_BIN:-$(which bun)} bun ./entrypoint.ts" - }, - "bun": { - "alwaysBundle": [ - "always-bundled-module" - ] - } -} diff --git a/test/bun.js/bundled/to_bundle_node_modules/i-am-bundled/cjs.js b/test/bun.js/bundled/to_bundle_node_modules/i-am-bundled/cjs.js deleted file mode 100644 index 087697589b8f..000000000000 --- a/test/bun.js/bundled/to_bundle_node_modules/i-am-bundled/cjs.js +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = { - default: 0xdeadbeef, - default() { - return "ok"; - }, - default: true, - ok() { - return true; - }, -}; diff --git a/test/bun.js/bundled/to_bundle_node_modules/i-am-bundled/esm.js b/test/bun.js/bundled/to_bundle_node_modules/i-am-bundled/esm.js deleted file mode 100644 index 28e702881b5c..000000000000 --- a/test/bun.js/bundled/to_bundle_node_modules/i-am-bundled/esm.js +++ /dev/null @@ -1,5 +0,0 @@ -const __esModule = true; - -export const foo = () => __esModule; - -export { __esModule, foo as default }; diff --git a/test/bun.js/bundled/to_bundle_node_modules/i-am-bundled/package.json b/test/bun.js/bundled/to_bundle_node_modules/i-am-bundled/package.json deleted file mode 100644 index 661a80b2d16d..000000000000 --- a/test/bun.js/bundled/to_bundle_node_modules/i-am-bundled/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "i-am-bundled", - "version": "1.0.0" -} diff --git a/test/bun.js/bundled/tsconfig.json b/test/bun.js/bundled/tsconfig.json deleted file mode 100644 index 3ccb11ae46d2..000000000000 --- a/test/bun.js/bundled/tsconfig.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "compilerOptions": { - "paths": {}, - "baseUrl": ".", - } -} \ No newline at end of file diff --git a/test/bun.js/file-importing-nonexistent-file.js b/test/bun.js/file-importing-nonexistent-file.js deleted file mode 100644 index 1136a7d8c278..000000000000 --- a/test/bun.js/file-importing-nonexistent-file.js +++ /dev/null @@ -1 +0,0 @@ -import "./does-not-exist.js"; diff --git a/test/bun.js/google-fixture.html b/test/bun.js/google-fixture.html deleted file mode 100644 index a5042789e45b..000000000000 --- a/test/bun.js/google-fixture.html +++ /dev/null @@ -1,632 +0,0 @@ - - - - - - - - Google - - - - - - - -
-
- Search Images - Maps - Play - YouTube - News - Gmail - Drive - More » -
- -
-
-
-
-
-
-

-
-
- - - - - - -
  - -
- -
-
- - -
- Advanced search -
- - -
-
-

- -

- © 2022 - Privacy - Terms -

-
- - - - - diff --git a/test/bun.js/import.live.rexport.js b/test/bun.js/import.live.rexport.js deleted file mode 100644 index e4accd2ba4e7..000000000000 --- a/test/bun.js/import.live.rexport.js +++ /dev/null @@ -1,2 +0,0 @@ -export { foo, setFoo } from "./import.live.decl"; -import { foo as bar } from "./import.live.decl"; diff --git a/test/bun.js/module-require.snapshot.js b/test/bun.js/module-require.snapshot.js deleted file mode 100644 index d0270bb4c8f9..000000000000 --- a/test/bun.js/module-require.snapshot.js +++ /dev/null @@ -1 +0,0 @@ -console.log(module.require("react")); diff --git a/test/bun.js/package.json b/test/bun.js/package.json deleted file mode 100644 index 709dc6602c83..000000000000 --- a/test/bun.js/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "bun-tests", - "dependencies": { - "esbuild": "0.15.13", - "uuid": "^9.0.0", - "tiny-typed-emitter": "^1.0.0", - "svelte": "^3.52.0" - }, - "prettier": "../../.prettierrc.cjs" -} diff --git a/test/bun.js/readdir.js b/test/bun.js/readdir.js deleted file mode 100644 index 18c111d0a0b6..000000000000 --- a/test/bun.js/readdir.js +++ /dev/null @@ -1,9 +0,0 @@ -const { readdirSync } = require("fs"); - -const count = parseInt(process.env.ITERATIONS || "1", 10) || 1; - -for (let i = 0; i < count; i++) { - readdirSync("."); -} - -console.log(readdirSync(".")); diff --git a/test/bun.js/snapshots.debug.js b/test/bun.js/snapshots.debug.js deleted file mode 100644 index cb774b38285e..000000000000 --- a/test/bun.js/snapshots.debug.js +++ /dev/null @@ -1,7 +0,0 @@ -// test/bun.js/module-require.snapshot.js -import { __require as require } from "bun:wrap"; -console.log(import.meta.require("/Users/jarred/Code/bun/node_modules/react/index.js")); - -// test/bun.js/require-referenceerror.snapshot.js - -// test/bun.js/tiny-typed-emitter.snapshot.js diff --git a/test/bun.js/snapshots.js b/test/bun.js/snapshots.js deleted file mode 100644 index cb774b38285e..000000000000 --- a/test/bun.js/snapshots.js +++ /dev/null @@ -1,7 +0,0 @@ -// test/bun.js/module-require.snapshot.js -import { __require as require } from "bun:wrap"; -console.log(import.meta.require("/Users/jarred/Code/bun/node_modules/react/index.js")); - -// test/bun.js/require-referenceerror.snapshot.js - -// test/bun.js/tiny-typed-emitter.snapshot.js diff --git a/test/bun.js/some-fs.js b/test/bun.js/some-fs.js deleted file mode 100644 index c162ed45a5b1..000000000000 --- a/test/bun.js/some-fs.js +++ /dev/null @@ -1,45 +0,0 @@ -const { mkdirSync, existsSync } = require("fs"); - -var performance = globalThis.performance; -if (!performance) { - try { - performance = require("perf_hooks").performance; - } catch (e) {} -} - -const count = parseInt(process.env.ITERATIONS || "1", 10) || 1; -var tempdir = `/tmp/some-fs-test/dir/${Date.now()}/hi`; - -for (let i = 0; i < count; i++) { - tempdir += `/${i.toString(36)}`; -} - -if (existsSync(tempdir)) { - throw new Error(`existsSync reports ${tempdir} exists, but it probably does not`); -} - -var origTempDir = tempdir; -var iterations = new Array(count * count).fill(""); -var total = 0; -for (let i = 0; i < count; i++) { - for (let j = 0; j < count; j++) { - iterations[total++] = `${origTempDir}/${j.toString(36)}-${i.toString(36)}`; - } -} -tempdir = origTempDir; -mkdirSync(origTempDir, { recursive: true }); -const recurse = { recursive: false }; -const start = performance.now(); -for (let i = 0; i < total; i++) { - mkdirSync(iterations[i], recurse); -} - -console.log("MKDIR " + total + " depth took:", performance.now() - start, "ms"); - -if (!existsSync(tempdir)) { - throw new Error("Expected directory to exist after mkdirSync, but it doesn't"); -} - -if (mkdirSync(tempdir, { recursive: true })) { - throw new Error("mkdirSync shouldn't return directory name on existing directories"); -} diff --git a/test/bun.js/test-programs/bigpipe-r.c b/test/bun.js/test-programs/bigpipe-r.c deleted file mode 100644 index 1acd94f7bc0d..000000000000 --- a/test/bun.js/test-programs/bigpipe-r.c +++ /dev/null @@ -1,26 +0,0 @@ -#include - -char one_mb_pipe_buf_1[1024 * 1024]; - - int main(int argc, char* argv[]) - { - for (int i = 0; i < sizeof(one_mb_pipe_buf_1); i++) { - one_mb_pipe_buf_1[i] = i % 256; - } - - while (1) { - - - size_t amt = 0; - size_t cnt = 0; - - cnt = 0; - while (cnt < sizeof(one_mb_pipe_buf_1)) { - amt = read(0, one_mb_pipe_buf_1 + cnt, sizeof(one_mb_pipe_buf_1) - cnt); - if (amt == 0) { - break; - } - cnt += amt; - } - } - } \ No newline at end of file diff --git a/test/bun.js/test-programs/bigpipe-w.c b/test/bun.js/test-programs/bigpipe-w.c deleted file mode 100644 index 1acd94f7bc0d..000000000000 --- a/test/bun.js/test-programs/bigpipe-w.c +++ /dev/null @@ -1,26 +0,0 @@ -#include - -char one_mb_pipe_buf_1[1024 * 1024]; - - int main(int argc, char* argv[]) - { - for (int i = 0; i < sizeof(one_mb_pipe_buf_1); i++) { - one_mb_pipe_buf_1[i] = i % 256; - } - - while (1) { - - - size_t amt = 0; - size_t cnt = 0; - - cnt = 0; - while (cnt < sizeof(one_mb_pipe_buf_1)) { - amt = read(0, one_mb_pipe_buf_1 + cnt, sizeof(one_mb_pipe_buf_1) - cnt); - if (amt == 0) { - break; - } - cnt += amt; - } - } - } \ No newline at end of file diff --git a/test/bun.js/test_scope_debug.ts b/test/bun.js/test_scope_debug.ts deleted file mode 100644 index fbc79acd84b2..000000000000 --- a/test/bun.js/test_scope_debug.ts +++ /dev/null @@ -1,172 +0,0 @@ -type AnyFunction = (...args: any[]) => any; -export function wrap({ - test: test_, - it: it_, - describe: describe_, - beforeEach: beforeEach_ = undefined, - beforeAll: beforeAll_ = undefined, - afterEach: afterEach_ = undefined, - afterAll: afterAll_ = undefined, -}: any) { - if (it_ === undefined) { - it_ = test_; - } - - var describe = (label, cb: AnyFunction) => { - return describe_( - label, - cb instanceof async function () {}.constructor - ? async () => { - console.log(`DESCRIBE [Enter] ${label}`); - try { - return await cb(); - } catch (e) { - throw e; - } finally { - console.log(`DESCRIBE [Exit] ${label}`); - } - } - : () => { - console.log(`DESCRIBE [Enter] ${label}`); - try { - return cb(); - } catch (e) { - throw e; - } finally { - console.log(`DESCRIBE [Exit] ${label}`); - } - }, - ); - }; - - var it = (label, cb: AnyFunction) => { - console.log("Before", label); - return it_( - label, - cb instanceof async function () {}.constructor - ? async () => { - console.log(`TEST [Enter] ${label}`); - try { - return await cb(); - } catch (e) { - throw e; - } finally { - console.log(`TEST [Exit] ${label}`); - } - } - : () => { - console.log(`TEST [Enter] ${label}`); - try { - return cb(); - } catch (e) { - throw e; - } finally { - console.log(`TEST [Exit] ${label}`); - } - }, - ); - }; - - var beforeEach = (cb: AnyFunction) => { - return beforeEach_( - cb instanceof async function () {}.constructor - ? async () => { - console.log(`BEFORE EACH [Enter]`); - try { - return await cb(); - } catch (e) { - throw e; - } finally { - console.log(`BEFORE EACH [Exit]`); - } - } - : () => { - console.log(`BEFORE EACH [Enter]`); - try { - return cb(); - } catch (e) { - throw e; - } finally { - console.log(`BEFORE EACH [Exit]`); - } - }, - ); - }; - var beforeAll = (cb: AnyFunction) => { - return beforeAll_( - cb instanceof async function () {}.constructor - ? async () => { - console.log(`BEFORE ALL [Enter]`); - try { - return await cb(); - } catch (e) { - throw e; - } finally { - console.log(`BEFORE ALL [Exit]`); - } - } - : () => { - console.log(`BEFORE ALL [Enter]`); - try { - return cb(); - } catch (e) { - throw e; - } finally { - console.log(`BEFORE ALL [Exit]`); - } - }, - ); - }; - var afterEach = (cb: AnyFunction) => { - return afterEach_( - cb instanceof async function () {}.constructor - ? async () => { - console.log(`AFTER EACH [Enter]`); - try { - return await cb(); - } catch (e) { - throw e; - } finally { - console.log(`AFTER EACH [Exit]`); - } - } - : () => { - console.log(`AFTER EACH [Enter]`); - try { - return cb(); - } catch (e) { - throw e; - } finally { - console.log(`AFTER EACH [Exit]`); - } - }, - ); - }; - var afterAll = (cb: AnyFunction) => { - return afterAll_( - cb instanceof async function () {}.constructor - ? async () => { - console.log(`AFTER ALL [Enter]`); - try { - return await cb(); - } catch (e) { - throw e; - } finally { - console.log(`AFTER ALL [Exit]`); - } - } - : () => { - console.log(`AFTER ALL [Enter]`); - try { - return cb(); - } catch (e) { - throw e; - } finally { - console.log(`AFTER ALL [Exit]`); - } - }, - ); - }; - - return { describe, it, beforeEach, beforeAll, afterEach, afterAll }; -} diff --git a/test/bun.js/tiny-typed-emitter.snapshot.js b/test/bun.js/tiny-typed-emitter.snapshot.js deleted file mode 100644 index 37299f9864c5..000000000000 --- a/test/bun.js/tiny-typed-emitter.snapshot.js +++ /dev/null @@ -1,7 +0,0 @@ -/** - * https://github.com/oven-sh/bun/issues/453 - */ -import { createRequire as topLevelCreateRequire } from "module"; -import { TypedEmitter as TypedEmitter7 } from "tiny-typed-emitter"; - -const require = topLevelCreateRequire(import.meta.url); diff --git a/test/bun.js/tsconfig.json b/test/bun.js/tsconfig.json deleted file mode 100644 index 0874dfd7ed8f..000000000000 --- a/test/bun.js/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "allowSyntheticDefaultImports": true, - "paths": { - "foo/bar": ["baz.js"], - "@faasjs/*": ["*.js"] - }, - "baseUrl": "." - }, - "exclude": ["solid-dom-fixtures", "bundled"] -} diff --git a/test/bun.lockb b/test/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..f724750a9510e851a521267c387dfe19766f99ad GIT binary patch literal 31909 zcmeHw30#a@8240@77|$sl}Lq5`>vfVp-U(wuG{^P|B7qS+gWU z30aCt$-aNjGtH^jy|)G5?_2M=_i^6yp7Z~o=R9Y5&wFNiMt+!TFo&(`!tzsP`i023 z1pA4CFns)7z1>+pOoqEZ+t)dW5uzr_PovQ^h9@wqENpU@mhRG@qFW*T`Qrr3qUt#j zn@cktCa&}SF(4cYL73kf(S&)i5HF78#adH&7qlQICkU^D{oGitEE>%vnB(FfN~5L1 zye`7#-9F0|vkd`^&aP%wwd`6fW4^@jY{5Oeim z`ne&`eO|f%V&r3jsaR}IkS_;wX*A@+IF*-w8ccwAmY*k+%?jeUF$1_f?w}p0O=AW* zdtmukUirlkV|(4eK5Q_E8`C8i3lRPQXEui!%=UTCD<8@=a62KYx2x7c{m6tvWG3ICR?yuwBUjp$!xX+B5{R)}F2n;M-pb2g&dcZU(tmF+*p%CT zh`9E(5bIU$dn8OY;;Oreu!gX)>{=^U~JpxYGgcxnrw<^DsZG6nl!BFm#*zo)TraPJ$ zmL(Q}D*m#49#y)PUrLO3eEdr5-~^Rq&it=3Yd*%Csq(E$ak}FC_T2ehufLV-_H&t) z5tZ|Nwcg_)XWzzp^SxeZ+{8TXvqmK~e2n0;a~~e-l-3z!WYvA^Q8(=BG|^tUdKS!` zE&`Hs`m7FJo;a`3b5~yR?fi<>licN(RWyo6@lV@Sr29lPG3!BMNm9>VLX5tfUrI_n zEceh_m1BNF&nMu^X^G|M7x-^I?lH*q3g6MumrU+oEs9Ug%gd|@6W7gpyUH)=T5;$T z?Nu=}t;d5emk@q-pHl@bpXRO^R;lWw+OaBi)8)Z|zJ)zaoLWLYkK2C0w>W*NN%dXZ z+WpV${SIyFzxeGP{fPYmGkb=3MBIM(UHR$FzH72{<~Lg15BcmDP&C?Wj_T}_x9B5Q z&8pwDXe1*w^@MVPacHi=x?8)Rs@pdT4q5OfDR$04n+M~}svg*W-aF>9**5-u?_cc~7D65QdpP;_s{u$O$!%VFnzR%CnUQJYd!EY)iIsV1AtdI+8=9}ji^bqIYHbm{% ztrxDdr9OXFx4W#vv@^b;EG^SFhvoXG zphlx@+zfMnc(Y8gLX#lvwAT`EEHl%)HO}FscbU+(^F_Lr<5LKO28cGyKS79bu5~vM zJg%i$fXBLebQcWe2tEU@O?mjgx2ZEag0F=_s6RBFrVG+t4uY2kz*xY;8r7x~Ty_}< z-iL=r0IY-E4g{Y8cvD{c;gQ+xK=7!v0pNcHi<7RUl=*1Kx~>7w-y0$tQRd=rna+`@7qBNJH=efH$S+ z|EulK0X*6t`Cusgvi)8G9_^3y5`oJu1JPemh(;U3s~_>*9Rmb!2Y537K$UI>f{z9~ zX@9rDP>|rS0^XF@e#HN({d>Wy8Fl<(A9N>>`o{wv+fUNn!D1OwJ_zs{fX90`wIROn1y9<*WdY&o9K2dtG6v_*lTB z{Sb@e50c#qq^=_XAmhJVKwcq&uK_%ne`vd3wclWPxkLMtz9)9oOh$p<=t-lcPmX!Yg)%Nqlp^zyL-`%=VH&WId@Z|lgE1jwN1RnzU zNfiCN`wS-dB3}K-jcZVM1HrdY+K<@oauB=*Y}%}N{g2pRU4J40Z^COozW@Ge|D6In zdH!Slzl#3=c-+4Ol&iZGUUVan_M7%;eSYD(^E>004R~{2`w{=E`sV?j%pcSN_YmC) zME|#d|C9H}vGCH0_QyK${k6N#FjBuV970uz$`U*8d6c)_+j{q=A2?e=^|zr2nb_|0niWfJ3uC z8NXhD|C9O;1O89?p9Tx|pV)6A;HUgS|E~r7pR~Uc@PE?&k%RuM{y@M}pTA^X#`V0j zK+5F+UJvYtX?*^Y`<-DikCdweJnsKVc_DJqMNII9gK0Ed9vtPUY+cvzP3sEY5jT4LTBD@|;7p>92>`_=&%b6A!!+ zEH+g}N@B5?VE*>F?>CQxzfjG4+GE5Si_bUS&Yip6__pFDC&ev$U*=CaxhPA2aQga1 zNB2LuTS(!>wV4FE`UU;d3)3?0q+EB*eR0I$(yG}@#r^Id|HR%UT8J#pSd0}+q(_x*fF+-V$CBPY+F_HJ3w+BqSHx>Fj(!yVr#-rPPdP%90H z2roX%NTAon^vhCEEV_8XPSdx~*saVjF=mzn=gqa09W;K>D8oBTr3R?(|GIa{2f>=N z(`V#0`zpMsyY_AMXX$Z=#hnE5>s|s8;iaCp&_yfPE%Ox8>HTI{a-=qy*`r`hX1iG7! zvCpTYrCR4dC72%&G}>~oe`MrFBcnd~M$?$i{x#Q&rrwi|a$Hx|yXdh@mVAu7xnWCA zK=Ab;8&tOrE6qsx3y27BUqT8oJ?EX?QKttka~fxE2{_mH+>I|gclF{UWI%3NP8uBRxGNdtaD?k(b$v zfEN)_7Pi&{CK;HDRL~ci_N;t7WWY+*k&aQWz1*tmPX#EfSigC4xnxqa+Tk7k>x#VD zxl7#l!Z~A`zWtDp7w8iOOy6z)Vtgp?<&mE9>Qj@`mZqGnIlHA{uY_RK;hw&_J;gJ6 zd#%=)wtHLnyBjBDw(F_TRy~)HSZLSq@cC55r@_EO?9iW(LQLtDamSl#K-tpjRDwp+#a zIo*Gi#QGvhK1P*Ta&=3VuR)ge;^z#lQ3C}eGfpcEaP~8qo#45_D(~}jCmQI~)tPeM zXHUJq(qCV2M#LcfP%nWT>Ut*C#tQaLo5fixR;(p#zRu^tsY4=X9^SAq*1RWedv=rb zpw**t6y}O7o3=kuYU}jKF&tyR<9>>|t7j_;ejVjrXz*23cIjH+BKpE>a9g0yysG%N z(AIAC2HRcJKEL(rYq0E1nVkE^>67ls3=~RzS$wKr%>G_6iwjos^EF7wEHvnO$tm1K zbkyfhCs*bB=voDnpGv1(SnOE z&rlv%7`%MbFwt#iD7<)fKmtAH=!LrPTQBNmD#p`jUfzS(9CTQ*V%fT<-;eQ^-uVz7 zA6D_jWXqSK6Za`yygEXd9${cpaIsI``tjLzv2^|`!r4GX#+e);g_wSI>mIWh?Ur0Y zgIcEA^FM z?({RMNPkV?9Y*E#jQ%7k``lY__QPp|Qx7q>Tbl$}&w&n2(M1U&^JGR~9;Da3TqG26!Wwg0+-t-C4j`mKla zdrJsQgiRT#S#H3*C7QmWPqEYj>vC3ut!>Y;Z3WAs@|Rrv5FVc4q&jWEE2Wt=->Lhk zkyPHSt!3#}mjmzZ8I_wIl^$p|+`BP1dGaj%DK|%#&)>T{&$G#TW9W|8>w1q{c+Oh) zamN9UiE8TVvQRm}6ZkBhyI-dlW1eJ5vv`H069-qBRv{mIua-;e7P zy?l*_i(10nipklGA+_eRIzJv~_ONw)aPqIYxjGjlSZ*`iKP1ZD7??RDC_ri0zL+f% zjV}YOz70A>;Z>&cMm-MBFG*wJOo$O2DoCc_oa&dr#b<@Q$JKs#+Oc*?ZpLrD84LA&b%b?-8SfrhX(ZeW!ePahJ!Abd=X^2iN$7ueYOaqcTL zOdWl9_1KHpXMtJy-wQ#7bO>Z1DaD-R&GMzVS!sj!#-yZonG~d8+ zn3ITfh-F1{ppS!Z#Fya$ZX2dG8U^ln?s!M4)LGc4f8lisuPT-IVuGrGrsR`K!PhH7 zU%!o>xy|R~@K;`n*Utxu^gkMwZlK8-4iQ<*CX~ur#HSDo{Cc7yt|2XUNxbV@N5+japJpHa< z;`3b|yCTlbACw%lTrjT2dEB*X3NOCPkwEu*I^pJLv;4kgQ{^Kkze*GL3(uR9Ixu#M z|FUTU>4ECDrRO|*bNK2$^xEt<_v+P0`q!&W-=4J8(KpelPPD$Wk-DyH5mJchPu1Oy zCG0*mXQSx>cFT%5!5V9`3!eR~E}!xq8kTeRfN1{Ce(OIL#~h|z^b23OK)|4EXYZ}c zjy|M0&-11GTvj>=9K;UVRNfVrOl6*U>G++vI>*CfVo~|YLpIC(uihVAxLa>#A6>`S zKTf=MS>*I`$)Et+WaD1q*6$8i3{wp{HrLZGa(U(A0hJV99V&0Y$YU`P_p)d1Ggmn@ z`M9j+^roH5PBuSKdpPrrZ9;|7lHAPa&-*)HDl6ANUL;h_bZ+9WJ33a@Ls58t#Ddr7 zSQQjrT`KSMXH~W{8j}SCw=Oujuc@KPBD>1%!QBt#vDqICk4k9?9UpLPg~o$V6-oDt zt+tIX`Z%akIbn>-D#e`O9l<@4gsJ;qJu2@W!y~@&c86-4XWQi%UElrS++x@RuFuN4 zK0#*0xZS2Y&t4jsxY*a!xPSE$9O0nKCz&6v(@=f!jn{-Jv})-_Tyu$i^{Kq3v^U9V zRV_*bnpZXX?wp|{D)TmYW5$)}z25@8W47xw+BYNznkJM-K07FLYh2#PLuZ}mtYCyV z8Zd^j>qnN>QTxq+%Imzf#D3I|^ov7BZt_T8`lxAeVT}ASedRQ%md&2tW29vEMQTfw z2|UUlTX$*gt%4gvUfd{*`(PC8G&^fb)z?RRsQW1Rn}xPO->Ua=$!QCT(U!ph*^eV{ z%v5?FaKtjTlo>Q{6n}#KsnO>LIaWXiXgjF|>&itCRnpL>0G@zVJfMi0e$8CO>=cqQOF2Z)HixK|^A zzHg^l-qhqM#-NR_w+h?eQ3~`9OUND)Gr)Y-k2NC&8(w&QSUbdEtBIgb;+X>9?Q!-+ z;|yJo4igbeiMDQJ=cFV65#co4>F&7AwH&tG@;pH#}w7R*@eP&HB5T-COD%*uQp%dLA%OsMOmDV6tY#TV-_w=V1| zxFlia^JqX*by)TV<;o}N&9tK-57j1pJ7X#;JAb>_j__#vb6>Br#O5ixP8)eFLi3oU zrCD&v5Ih?o`kGOB4HZnyuMQ0JK4x3x)5~_okGcV&2?_km7m5cDNPXZsv{y#-lN|o{ z;>(9B>DU{8yq^@16@6i_1MB1S*yWb-i&FPdc+IK2Mv;s-og=AC%aZys@qp_+UmyBp zeLzoa3jfounb{G?4rdTj`vp%PwvHzI+)OF%ev^a$Stja$TDg@1K57aQubGNyOK=?w>z zdcUv8WWR`O+Vf^&+?SV52cwfy&sJO2y)T^-H&-<`N+IxaRCx#x@xe#Bkr3nOog~n8 zD=RV&e+ZTM+GE$DH={nfK9qJ_x!2=DOJR7}`plXk%*Y<=-=|EopI7zrX~_|$DJNXE z@j}WBM=9CY5q>vS-ZFuR4?Zc5gm?lWg_tg7UpB^UO?INt0KIsd^~zh;Zi?IZWPj}5 zxF35oi!#h)HzbLCss40)c*&NH{jJyVKiu_b`Jn{66NRILtB2eA?r{YUKKRrv65@#< zB+!e>w_lNETikci_h)D9P`_ok(%{F$#7Anc-8aQ7w0L&6SbFE42b1e|N;W#Sh~!HJ z^}TlC(8|8U%3r#_sZh$DcM^!u4yF)KBBT)0D?X}QDV^(iC&hPOu6}c>{)M!sB9m6! zRyIC2D&eDM`nj*AQ|4Zqoxk-AYte&Qm-v??&(VpEZYh6#bmZry=ZeK!-?M1Ka=?Xa z1qpN;sdo?81wNYXQFPO|XYrlE+r!f)@v&XqgX0yR8HjQwM-3e?eXs4<0ppK;HNQIC zb#JAWS1;j%c}20;1|HxztNs8Y)ED{i*+ByRv3umQhEEX>oF9JFdtfsmEFr+l1+b$Dhoo6|Xb=%VJvSaJ}{KTK-b8(l&^mPqz>4-LJ_Dh>#c05XtA+ z$W1?Rz3KI2=JCPoJcm8g@0?vc_s}Szh{ccH<&t!EwD|iBHWs(IJ)6P)TCTT3Gw{pG z&$Aa?FqOKka-^1z|Hcce=9E@me&n5sguFn1R6Sm8eD>MW-q%j9_z>r5ViGOA z*>>NhZ;9H=Y}w>`e=jS2@zh0ixpz0zX6_%CkfF4*fm2shtG;3T=nZ>;2lU0iC+2=m z$fKpv)ij#Tj$3>wts601TXc*^{TasBGX_yF+?QPu56vmODLI){%lH^$oN~-W^l_fM zO~cDO_I(o6ZC@=M8n{OzVkGV%`CybGFMRj1Ezkw;apKpD&v{?E*vv5WFCn3oiVI%& zO{ps%)102{x%{f0@~1VnbH6N5pXaeAw@1jv`m-Vjlah+1#@!!Pe(Lr6zj^@^(bu|- z73>?}u}r&MeedegN+L6d+E~9Vyf}QA^np_Sp+|M|W$zT%JQsHi9DgC~8-MSaC0{oh z9k>^5p*>{fxCVI*DY~i`Kc00GUK=W}r^4?2C;K#17|qnL%VISSV$9CoV{y{bEBm5k zUej`mx|<8W%F-Of>Yc3FJ=Q8taE%QMt9T-&BHiM^Unc2)qm0Wd%(a6pl{ZMnW6*BL z{K<2^=Z%x~jxm>f>8%}mTt5%3gdTYN< zd!koJbf)!;@0Scy`d)A9-MqlkQ8XjiuqC+nnR|gT{zK-Rn58=W+QOqjbv||epQJbM z8!#OA=r}KM!r}KSNTA1ml--tU-l94x z${e!A$*0$HS@+@|UYmQrxR>+l)IGX+N`qX?mc*~ARk7^3^{=rK7-}EM*b!kC#8>3Ic))wBH+@zOpEN6AsAiN+ zO3t@icZSYmo%!Ip=6Za#V64uBxn_!oMQ~4s`XcWPD(|lCp*Ig(#^||8-74@XuO6x) zm^U^0?t&Yt;p%HnkLopPn5(Q!*m1+h<4ZY?r_a0JTw7xm?V9GD95icUTC@MgAzWTC z0~mQGmA9(pgYdIWIt~SncPUm8S>7i@`_ZY zyeyu++@8~4dFb=6x=H5E>xJiEsTxpFylF$BO=HdbOsNu|nYMzg5dvd3rIrczd6b~k zTVQVQq&FLl_g?QS&egZ|^G)0Xl0e@)pJnkVtLj?*8kSd@{*hyDDOL{lg>J8J7TjH3 zA8)gB!&|>zt(=&VlU0r=O&pb**7UCth{nu-v@}u{$Va5g_!kOwR@L4ESH~2+2O8EEo}KDs<8K`qd5AQF(9Qvl41sso zdww1tCVbE9MUSfbzZ5nz6-(>K=qd^hnU!Ro9WFPmA9EHo2Xdxy#=*NCqfv@cfV zeJFDM?eE9A^t{hXiEj};Cv6ql|AKq`PzUi(;_Aav@#26s|K0oFW&`XG;*W*eAOGF^ z&n`GG{~HJYv;oc;;(JG}FVA<)fA=DBy!`)emqomny=Go4YRF-`s<8aPXO@qT3UM8# z;=pDyjpa1d-YWsdWHI4gH46Wy9 zt>2Gc+j@(J`yRw$8T<|hp26du1!Mf~+h`CB^Fgqj+%!zlXghc>fTD)Mni|HST1QGk zS)!K!Tw^`Ri)T0ZT{Ya}i-4>ENdUp`)+BYF9pOMgau*-a)Z}T zdRP!bSN(|g9}?04*M6%f+* zn8rNpFYH^apVUj*hjr+IV7p8~&{kAEQ7;n^ERQxY2Eq0s#uS9)V;XIbZ661MYrrg! znIJPja7;}HaR9Lg!EuAb!Un_|1cwTaF&v9iKyYkM2AKph5o7|01&Ap~S31sxG9BrN zW66^@9(y@M0)nXF91Wd$!FsUZ>9nmS>OU`dfQux4u!Q+to&C5z36Tw29Z5W8@$)&U zsi>*IfWp4Q9O6?8a@18c)KoNRFgQ=ny;z7 zTRmt^@%lb(T6fGw{74eNdi;E~eB6=P=1G!x*5l_pz~7ofd=(PkZTx&o)m1cAU>Fl` zhQzxYKcBh^{0BPyvroBQ3R%QcBJmu@&!?uMt)c}O;!BbEmgCl=hFQehBJn=giUG5= z`C}yh=}-eT6z_BB;EmA+k@*4@uNxn3byM;JW>)5eXT7; zTN9s^#D^bO1E`8dBVId+7ei!&X#yDHSCjY|r}Sj}l*uTn+S)jd&*| z-Ws{%O&fEF|5D=5vEA0h!zuCj*lJO<0r8DWd_{6~1I6{)yr&XxlI^x8{#A*;NiK$K zYvNIrc$h@3ahB5Be6JE;lw7+3MvLn`6}_<%@03`NHg|~p&PeRIPdm2g@5~b7tCje! zY`69AXx;Jr`JGup-f@Y4PSlON^tO4FB_1|$9KbRL{nPO*>3H;ZJgSI?T;eelHE3NE zJ6?B)w_M^qv~`T)n)o~8n)u))K1opnn8UzEyy{YI(7N&wf4aoKDEIlpU3-b=T;fTT zmxFT~9<#8IizGfsc{#A|5U;$%ODQ);A14Fx$4mT^a&x%mAs%~)2U8v!bP)0BOMINR z=XA{0aZblt6JNl@H!9BH*7f=fY_D;DK)k1-MfJHB?RftD&RpntBz7{lc^;hqxxNvf z#l(jzcdg=fcgJfl@l#Cvw(|37!sx{(Q^!x|j&0EKi0^m}>G%ow&z&6%P+UeknZX@D znTUsG;;|Oz9PaBndoKLbqByf~jqliQztf(M>mmNW+jnVNeYkIbk;KFI-<~p>T5Ue2 ziO*de*W3*;jd*b;UTx|4tjFy&jre&ces9}zh$m>`8Mi%$_=+aJbK7%>H)-Nsw>^jW znXgs5HH`v>v4My@dHl$BDd!}lHk5s zM-tD;?K#Ak_(h&?<@OxnZJc;tZqFh9$ccaE_MDDKd9p0JUB) zxcfrH06Ejym&0^syLxtlrxpViY7)5cIna6ittSbH;fSHi+7|2P~H``2T!3uC1*C< zIh5h<;~W&k^nHpiFA_JE%Ufr4ySKoH#U_k*AN@MCdcWCb_}c|vD0 zU_OF=0c`(8p$um?H&DzEFs;)V+src*hsh3MGVnEt<;r0OLHQtm)Xv{MIEdxLVQ{PJ z#G3e`4W__|JH5DO=%hS_6=0MUpz#2s^U29u$N|Z_0u-JEAk3HP#&YH^UTl23Y+c4W z(WJc)(6uMHV?a~83q`o)-CPLL%>@*}6M?Q^?K&c~Jq4p0$8hJZCoi)AC6}OW*H-(v zUlILNM}z8JYfc>}T!!{x19){f-04brI4$A={oJ6j&}Cf#r~?*&)FfBquEq*wP5_*e z0-M2%?Lu90JOEH~iS6tX=gR?vFQ;`Bbx{WnA}o!gwe2z2$%w@)-U@;Ct~C51b_E_)ylXG|c6(Rvh( zRdHYpGhE@Z=g0Kv#Q4-=ST6;|?E>y#bcOE#FyQCJ+&q{;43;16k~y%MaPeZo_7$$- zIULkU9X}U@T7JF(^{8ufX`i2}29BT7?fvc^?B|M`1g^Sp%*Nn^`nvf0bham>lmcE# zife$b`VGqSP&ntiD*cmsfcq(phUl`V+xh~~ZP#2)yP7TS+ZcehC%9{fE)E&^UATqL zY&UqI2H<7~zT?|-*?G? z{`)O1k4_go*9vM!V-rJ>bbuQ`*++GMM+7r-t-a9Dwhbmit z6R;B+-uEMbgLj3~9nWu35mS;2uX$|PzA>EHzW;VlN9!MV7g6rHIkOkC{1E)lo78f! zm14SZxV2CUlDpKBJ}kfBML%iNc87v&y~lFa)M!($^%k5w_<8)Jpf4+cqoc+2`CH+D z0M~yMWwSW0A^*6`(bQL4^fxqBYd>ZXHjjGCKY;1?_j{aRKYz|orNDZc8h^82>%E^E z*Ln-+JL&H>_1X$U*SB3$JHPuRs6-nJCVr2GJ3iWYxT#+yYBL?sv|axy38hbfhLZYK zqW1mn+SV|j`BiNwt1{3~QolcD)QuPRf&@NT_R#9LiJmAqJW`&lvFphqHOU1Pf4|I_`2FX zkzF2O$t8Eg*A { diff --git a/test/bun.js/install/bad-workspace.test.ts b/test/cli/install/bad-workspace.test.ts similarity index 93% rename from test/bun.js/install/bad-workspace.test.ts rename to test/cli/install/bad-workspace.test.ts index bb3f03a86e6d..1feafc2b0d4f 100644 --- a/test/bun.js/install/bad-workspace.test.ts +++ b/test/cli/install/bad-workspace.test.ts @@ -1,7 +1,6 @@ import { spawnSync } from "bun"; import { test, describe, expect } from "bun:test"; -import { bunEnv } from "bunEnv"; -import { bunExe } from "bunExe"; +import { bunExe, bunEnv } from "harness"; import { mkdirSync, rmSync, writeFileSync } from "fs"; test("bad workspace path", () => { diff --git a/test/bun.js/install/bar-0.0.2.tgz b/test/cli/install/bar-0.0.2.tgz similarity index 100% rename from test/bun.js/install/bar-0.0.2.tgz rename to test/cli/install/bar-0.0.2.tgz diff --git a/test/bun.js/install/baz-0.0.3.tgz b/test/cli/install/baz-0.0.3.tgz similarity index 100% rename from test/bun.js/install/baz-0.0.3.tgz rename to test/cli/install/baz-0.0.3.tgz diff --git a/test/bun.js/install/baz-0.0.5.tgz b/test/cli/install/baz-0.0.5.tgz similarity index 100% rename from test/bun.js/install/baz-0.0.5.tgz rename to test/cli/install/baz-0.0.5.tgz diff --git a/test/bun.js/install/bun-add.test.ts b/test/cli/install/bun-add.test.ts similarity index 99% rename from test/bun.js/install/bun-add.test.ts rename to test/cli/install/bun-add.test.ts index 9ac54bbc55fa..d1222993b5a8 100644 --- a/test/bun.js/install/bun-add.test.ts +++ b/test/cli/install/bun-add.test.ts @@ -1,7 +1,6 @@ import { file, spawn } from "bun"; import { afterAll, afterEach, beforeAll, beforeEach, expect, it } from "bun:test"; -import { bunExe } from "bunExe"; -import { bunEnv as env } from "bunEnv"; +import { bunExe, bunEnv as env } from "harness"; import { access, mkdir, mkdtemp, readlink, realpath, rm, writeFile } from "fs/promises"; import { join, relative } from "path"; import { tmpdir } from "os"; diff --git a/test/bun.js/install/bun-install.test.ts b/test/cli/install/bun-install.test.ts similarity index 99% rename from test/bun.js/install/bun-install.test.ts rename to test/cli/install/bun-install.test.ts index 3ea14f27cac9..ee515280e495 100644 --- a/test/bun.js/install/bun-install.test.ts +++ b/test/cli/install/bun-install.test.ts @@ -1,7 +1,6 @@ import { file, listen, spawn } from "bun"; import { afterAll, afterEach, beforeAll, beforeEach, expect, it } from "bun:test"; -import { bunExe } from "bunExe"; -import { bunEnv as env } from "bunEnv"; +import { bunExe, bunEnv as env } from "harness"; import { access, mkdir, readlink, writeFile } from "fs/promises"; import { join } from "path"; import { diff --git a/test/bun.js/install/bun-link.test.ts b/test/cli/install/bun-link.test.ts similarity index 99% rename from test/bun.js/install/bun-link.test.ts rename to test/cli/install/bun-link.test.ts index c1e1d1890848..5b0abe1390b9 100644 --- a/test/bun.js/install/bun-link.test.ts +++ b/test/cli/install/bun-link.test.ts @@ -1,7 +1,6 @@ import { spawn } from "bun"; import { afterAll, afterEach, beforeAll, beforeEach, expect, it } from "bun:test"; -import { bunExe } from "bunExe"; -import { bunEnv as env } from "bunEnv"; +import { bunExe, bunEnv as env } from "harness"; import { access, mkdtemp, readlink, realpath, rm, writeFile } from "fs/promises"; import { basename, join } from "path"; import { tmpdir } from "os"; diff --git a/test/bun.js/install/bun-pm.test.ts b/test/cli/install/bun-pm.test.ts similarity index 98% rename from test/bun.js/install/bun-pm.test.ts rename to test/cli/install/bun-pm.test.ts index 3db2a4ad0b79..23b73dfaebd2 100644 --- a/test/bun.js/install/bun-pm.test.ts +++ b/test/cli/install/bun-pm.test.ts @@ -1,7 +1,6 @@ import { spawn } from "bun"; import { afterAll, afterEach, beforeAll, beforeEach, expect, it } from "bun:test"; -import { bunExe } from "bunExe"; -import { bunEnv as env } from "bunEnv"; +import { bunExe, bunEnv as env } from "harness"; import { mkdir, writeFile } from "fs/promises"; import { join } from "path"; import { diff --git a/test/bun.js/install/bunx.test.ts b/test/cli/install/bunx.test.ts similarity index 98% rename from test/bun.js/install/bunx.test.ts rename to test/cli/install/bunx.test.ts index 06266fe948de..49d6e1c2d5d9 100644 --- a/test/bun.js/install/bunx.test.ts +++ b/test/cli/install/bunx.test.ts @@ -1,7 +1,6 @@ import { spawn } from "bun"; import { afterEach, beforeEach, expect, it } from "bun:test"; -import { bunExe } from "bunExe"; -import { bunEnv as env } from "bunEnv"; +import { bunExe, bunEnv as env } from "harness"; import { realpathSync } from "fs"; import { mkdtemp, realpath, rm, writeFile } from "fs/promises"; import { tmpdir } from "os"; diff --git a/test/bun.js/install/dummy.registry.ts b/test/cli/install/dummy.registry.ts similarity index 100% rename from test/bun.js/install/dummy.registry.ts rename to test/cli/install/dummy.registry.ts diff --git a/test/bun.js/install/moo-0.1.0.tgz b/test/cli/install/moo-0.1.0.tgz similarity index 100% rename from test/bun.js/install/moo-0.1.0.tgz rename to test/cli/install/moo-0.1.0.tgz diff --git a/test/bun.js/log-test.test.ts b/test/cli/run/log-test.test.ts similarity index 95% rename from test/bun.js/log-test.test.ts rename to test/cli/run/log-test.test.ts index 29f541e322cc..2e980cb7b066 100644 --- a/test/bun.js/log-test.test.ts +++ b/test/cli/run/log-test.test.ts @@ -2,8 +2,7 @@ import { it, expect } from "bun:test"; import { basename, dirname, join } from "path"; import * as fs from "fs"; import { readableStreamToText, spawnSync } from "bun"; -import { bunExe } from "bunExe"; -import { bunEnv } from "bunEnv"; +import { bunExe, bunEnv } from "harness"; it("should not log .env when quiet", async () => { writeDirectoryTree("/tmp/log-test-silent", { diff --git a/test/bun.js/preload-test.test.js b/test/cli/run/preload-test.test.js similarity index 98% rename from test/bun.js/preload-test.test.js rename to test/cli/run/preload-test.test.js index 76603b3a0ae3..e0e068f9a05c 100644 --- a/test/bun.js/preload-test.test.js +++ b/test/cli/run/preload-test.test.js @@ -3,8 +3,7 @@ import { describe, expect, test } from "bun:test"; import { mkdirSync, realpathSync } from "fs"; import { tmpdir } from "os"; import { join } from "path"; -import { bunEnv } from "./bunEnv"; -import { bunExe } from "./bunExe"; +import { bunEnv, bunExe } from "harness"; const preloadModule = ` import {plugin} from 'bun'; diff --git a/test/fixtures/bun-link-pkg-fixture/bun-link-pkg-fixture.js b/test/fixtures/bun-link-pkg-fixture/bun-link-pkg-fixture.js deleted file mode 100755 index ce5ae872bf6b..000000000000 --- a/test/fixtures/bun-link-pkg-fixture/bun-link-pkg-fixture.js +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env node - -// package.json name is bun-link-pkg-fixture-1 to ensure we don't rely on dir name -const _ = require("lodash"); - -if (_.isBoolean(true)) { - console.log("Success"); - process.exit(0); -} - -console.error("Fail"); -process.exit(1); diff --git a/test/fixtures/bun-link-pkg-fixture/bun.lockb b/test/fixtures/bun-link-pkg-fixture/bun.lockb deleted file mode 100755 index ff3a1483f214e893239333c998484af1c20d68d5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1191 zcmY#Z)GsYA(of3F(@)JSQ%EY!;{sycoc!eMw9K4T-L(9o+{6;yG6OCq1_p+m+A-hQ zt%|PMGe2Q6p1h{uw#I~TIgxK79h_6$_)HdT7H0t}0s@d)5a57N4D3+40ZL226oC0+ z3=9o9`6-FT84L_|PxQ8iUfA>cz@swTxt4xc7~fS-eJ;E!tXb)Cu)XfZ(;eIr^JZRo z`(8LwyRpo7-4Q#TkYlnjp}j}XZ;m_Nx-C@lxr5q&@$~5Y{)zcsK+|CsIRe>cKzl$A z6a->XASMBtpsSml|X4Oo8qEmz0ADglEj=GJy`P43n@xXwNo%MP$_{9L_q#o05x+7-0V_gBRim09EMzgs=GpjdXV`X%pjW> z7?hwimrb;hS!}5V(4l5mbjQSKqGxEXXJp8b12qLkqlQFrYHnF-k)CTs zNorAEVoq>LQD$Dcoq{34A`7@hGoU8%Lvsn24aiM~Se*pUgqeBSx&_(kx@nmeC8b5F zx`x<9v5Dzr6=#%`6ck(O>ldY_XBL+fRqEvx9a`FqQr>Q~1YKXO;~^Oaofl+FrN1O&_wih%=4vqR|yDE$Ov7#xT* zFf{lBd162s$Y$UK(w~bhuU=g7WvSg0y{(}a_PnlslYX{C=(Q3fqjbm$pzZ}wS^#Pa zIv-}ZK;Qj4i$h^N1_lR4S+IVXeJ&9-S3&YDP$e*zgWLfIH_E{BFn2IP_16F;t%2?Y zsbK_SZXjL-1Ry>k0I~yS4~z!s!7l!f{~yQ#Dg#Cah`H1@8fGqX1hZsM+^aj=_1S_P zo)gFB>bcfhpQz%E_ICcPTwKPamqJ`l!$PQo}Fo*!-Ar(l2(h-ot5YvDLvJI5xvMDY~*2~N* zE=kPE(Ss#*y^x~RR67MD1BK$us?_{69R(AG#GK6Zy!7+f|We;DHO1_QWI%FN5wEy>p{$WGTy z%d99VElLFmmsA#{7Uuxvtl)}}ROlLF6U8QmU8P=DaYji=L9vy-eo< unknown) { if (!Bun.unsafe.gcAggressionLevel) return block(); const origGC = Bun.unsafe.gcAggressionLevel(); diff --git a/test/bun.js/console/console-iterator-run.js b/test/js/bun/console/console-iterator-run.js similarity index 100% rename from test/bun.js/console/console-iterator-run.js rename to test/js/bun/console/console-iterator-run.js diff --git a/test/bun.js/console/console-iterator.test.js b/test/js/bun/console/console-iterator.test.js similarity index 98% rename from test/bun.js/console/console-iterator.test.js rename to test/js/bun/console/console-iterator.test.js index 053e4382d975..89894fd7267b 100644 --- a/test/bun.js/console/console-iterator.test.js +++ b/test/js/bun/console/console-iterator.test.js @@ -1,6 +1,6 @@ import { spawnSync, spawn } from "bun"; import { describe, expect, it } from "bun:test"; -import { bunExe } from "bunExe"; +import { bunExe } from "harness"; describe("should work for static input", () => { const inputs = [ diff --git a/test/bun.js/resolve-dns.test.ts b/test/js/bun/dns/resolve-dns.test.ts similarity index 93% rename from test/bun.js/resolve-dns.test.ts rename to test/js/bun/dns/resolve-dns.test.ts index d83e23b56f75..52534da13d2f 100644 --- a/test/bun.js/resolve-dns.test.ts +++ b/test/js/bun/dns/resolve-dns.test.ts @@ -1,6 +1,6 @@ import { dns } from "bun"; import { describe, expect, it, test } from "bun:test"; -import { withoutAggressiveGC } from "gc"; +import { withoutAggressiveGC } from "harness"; describe("dns.lookup", () => { const backends = [process.platform === "darwin" ? "system" : undefined, "libc", "c-ares"].filter(Boolean); @@ -28,7 +28,7 @@ describe("dns.lookup", () => { const [first, second] = await dns.lookup("google.com", { backend }); console.log(first, second); }); - it(backend + " local", async () => { + it.skip(backend + " local", async () => { const [first, second] = await dns.lookup("localhost", { backend }); console.log(first, second); }); diff --git a/test/bun.js/ffi-test.c b/test/js/bun/ffi/ffi-test.c similarity index 100% rename from test/bun.js/ffi-test.c rename to test/js/bun/ffi/ffi-test.c diff --git a/test/bun.js/ffi.test.fixture.callback.c b/test/js/bun/ffi/ffi.test.fixture.callback.c similarity index 99% rename from test/bun.js/ffi.test.fixture.callback.c rename to test/js/bun/ffi/ffi.test.fixture.callback.c index 3b9a46577770..624f6cdd018b 100644 --- a/test/bun.js/ffi.test.fixture.callback.c +++ b/test/js/bun/ffi/ffi.test.fixture.callback.c @@ -286,9 +286,6 @@ ZIG_REPR_TYPE JSFunctionCall(void* jsGlobalObject, void* callFrame); bool my_callback_function(void* arg0); bool my_callback_function(void* arg0) { -#ifdef INJECT_BEFORE -INJECT_BEFORE; -#endif ZIG_REPR_TYPE arguments[1]; arguments[0] = PTR_TO_JSVALUE(arg0).asZigRepr; return (bool)JSVALUE_TO_BOOL(_FFI_Callback_call((void*)0x0000000000000000ULL, 1, arguments)); diff --git a/test/bun.js/ffi.test.fixture.receiver.c b/test/js/bun/ffi/ffi.test.fixture.receiver.c similarity index 100% rename from test/bun.js/ffi.test.fixture.receiver.c rename to test/js/bun/ffi/ffi.test.fixture.receiver.c diff --git a/test/bun.js/ffi.test.js b/test/js/bun/ffi/ffi.test.js similarity index 98% rename from test/bun.js/ffi.test.js rename to test/js/bun/ffi/ffi.test.js index 1027377829ee..8b31280c9268 100644 --- a/test/bun.js/ffi.test.js +++ b/test/js/bun/ffi/ffi.test.js @@ -1,5 +1,5 @@ import { afterAll, describe, expect, it } from "bun:test"; -// +import { existsSync } from "fs"; import { CFunction, CString, @@ -20,6 +20,7 @@ const dlopen = (...args) => { throw err; } }; +const ok = existsSync("/tmp/bun-ffi-test.dylib"); it("ffi print", async () => { await Bun.write( @@ -607,7 +608,11 @@ it("read", () => { } }); -describe("run ffi", () => { - ffiRunner(false); - ffiRunner(true); -}); +if (ok) { + describe("run ffi", () => { + ffiRunner(false); + ffiRunner(true); + }); +} else { + it.skip("run ffi", () => {}); +} diff --git a/test/bun.js/globals.test.js b/test/js/bun/globals.test.js similarity index 100% rename from test/bun.js/globals.test.js rename to test/js/bun/globals.test.js diff --git a/test/bun.js/bun-server.test.ts b/test/js/bun/http/bun-server.test.ts similarity index 100% rename from test/bun.js/bun-server.test.ts rename to test/js/bun/http/bun-server.test.ts diff --git a/test/bun.js/fetch.js.txt b/test/js/bun/http/fetch.js.txt similarity index 100% rename from test/bun.js/fetch.js.txt rename to test/js/bun/http/fetch.js.txt diff --git a/test/bun.js/fixture.html.gz b/test/js/bun/http/fixture.html.gz similarity index 100% rename from test/bun.js/fixture.html.gz rename to test/js/bun/http/fixture.html.gz diff --git a/test/bun.js/proxy.test.js b/test/js/bun/http/proxy.test.js similarity index 98% rename from test/bun.js/proxy.test.js rename to test/js/bun/http/proxy.test.js index c903efab31fa..abe05133d58f 100644 --- a/test/bun.js/proxy.test.js +++ b/test/js/bun/http/proxy.test.js @@ -1,5 +1,5 @@ import { afterAll, beforeAll, describe, expect, it } from "bun:test"; -import { gc } from "./gc"; +import { gc } from "harness"; let proxy, server; diff --git a/test/leaks/http-static-leak.ts b/test/js/bun/http/serve.leak.ts similarity index 100% rename from test/leaks/http-static-leak.ts rename to test/js/bun/http/serve.leak.ts diff --git a/test/bun.js/serve.test.ts b/test/js/bun/http/serve.test.ts similarity index 100% rename from test/bun.js/serve.test.ts rename to test/js/bun/http/serve.test.ts diff --git a/test/bun.js/bun-streams-test-fifo.sh b/test/js/bun/io/bun-streams-test-fifo.sh similarity index 100% rename from test/bun.js/bun-streams-test-fifo.sh rename to test/js/bun/io/bun-streams-test-fifo.sh diff --git a/test/bun.js/bun-write.test.js b/test/js/bun/io/bun-write.test.js similarity index 99% rename from test/bun.js/bun-write.test.js rename to test/js/bun/io/bun-write.test.js index c324d36a0415..c3b9b187bbd4 100644 --- a/test/bun.js/bun-write.test.js +++ b/test/js/bun/io/bun-write.test.js @@ -1,7 +1,7 @@ import fs from "fs"; import { it, expect, describe } from "bun:test"; import path from "path"; -import { gcTick, withoutAggressiveGC } from "./gc"; +import { gcTick, withoutAggressiveGC } from "harness"; it("Bun.write blob", async () => { await Bun.write(Bun.file("/tmp/response-file.test.txt"), Bun.file(path.join(import.meta.dir, "fetch.js.txt"))); diff --git a/test/bun.js/emptyFile b/test/js/bun/io/emptyFile similarity index 100% rename from test/bun.js/emptyFile rename to test/js/bun/io/emptyFile diff --git a/test/js/bun/io/fetch.js.txt b/test/js/bun/io/fetch.js.txt new file mode 100644 index 000000000000..5a9b52fcf1f9 --- /dev/null +++ b/test/js/bun/io/fetch.js.txt @@ -0,0 +1,46 @@ + + + + Example Domain + + + + + + + + +
+

Example Domain

+

This domain is for use in illustrative examples in documents. You may use this + domain in literature without prior coordination or asking for permission.

+

More information...

+
+ + diff --git a/test/bun.js/bun-jsc.test.js b/test/js/bun/jsc/bun-jsc.test.js similarity index 100% rename from test/bun.js/bun-jsc.test.js rename to test/js/bun/jsc/bun-jsc.test.js diff --git a/test/bun.js/shadow.test.js b/test/js/bun/jsc/shadow.test.js similarity index 100% rename from test/bun.js/shadow.test.js rename to test/js/bun/jsc/shadow.test.js diff --git a/test/bun.js/socket/echo.js b/test/js/bun/net/echo.js similarity index 100% rename from test/bun.js/socket/echo.js rename to test/js/bun/net/echo.js diff --git a/test/bun.js/socket/socket.test.ts b/test/js/bun/net/socket.test.ts similarity index 98% rename from test/bun.js/socket/socket.test.ts rename to test/js/bun/net/socket.test.ts index 4c58243ef3fc..881175b242fc 100644 --- a/test/bun.js/socket/socket.test.ts +++ b/test/js/bun/net/socket.test.ts @@ -1,5 +1,5 @@ import { expect, it } from "bun:test"; -import { bunExe } from "../bunExe"; +import { bunExe } from "harness"; import { connect, spawn } from "bun"; it("should keep process alive only when active", async () => { diff --git a/test/bun.js/tcp-server.test.ts b/test/js/bun/net/tcp-server.test.ts similarity index 98% rename from test/bun.js/tcp-server.test.ts rename to test/js/bun/net/tcp-server.test.ts index 6f3f869eced2..ba6927fe3322 100644 --- a/test/bun.js/tcp-server.test.ts +++ b/test/js/bun/net/tcp-server.test.ts @@ -1,4 +1,4 @@ -import { listen, connect, TCPSocketListener, TCPSocketOptions, SocketHandler } from "bun"; +import { listen, connect, TCPSocketListener, SocketHandler } from "bun"; import { describe, expect, it } from "bun:test"; import * as JSC from "bun:jsc"; diff --git a/test/bun.js/hello.svelte b/test/js/bun/plugin/hello.svelte similarity index 100% rename from test/bun.js/hello.svelte rename to test/js/bun/plugin/hello.svelte diff --git a/test/bun.js/hello2.svelte b/test/js/bun/plugin/hello2.svelte similarity index 100% rename from test/bun.js/hello2.svelte rename to test/js/bun/plugin/hello2.svelte diff --git a/test/bun.js/plugins.d.ts b/test/js/bun/plugin/plugins.d.ts similarity index 100% rename from test/bun.js/plugins.d.ts rename to test/js/bun/plugin/plugins.d.ts diff --git a/test/bun.js/plugins.test.ts b/test/js/bun/plugin/plugins.test.ts similarity index 99% rename from test/bun.js/plugins.test.ts rename to test/js/bun/plugin/plugins.test.ts index e533cef06492..2bac4b4d47cd 100644 --- a/test/bun.js/plugins.test.ts +++ b/test/js/bun/plugin/plugins.test.ts @@ -131,7 +131,7 @@ plugin({ }); // This is to test that it works when imported from a separate file -import "bun-loader-svelte"; +import "../../third_party/svelte"; describe("require", () => { it("SSRs `

Hello world!

` with Svelte", () => { diff --git a/test/bun.js/baz.js b/test/js/bun/resolve/baz.js similarity index 100% rename from test/bun.js/baz.js rename to test/js/bun/resolve/baz.js diff --git a/test/js/bun/resolve/file-importing-nonexistent-file.js b/test/js/bun/resolve/file-importing-nonexistent-file.js new file mode 100644 index 000000000000..f805e78bcdc0 --- /dev/null +++ b/test/js/bun/resolve/file-importing-nonexistent-file.js @@ -0,0 +1 @@ +import "./does-not-exist.js.js.js.js"; diff --git a/test/bun.js/esm/first.mjs b/test/js/bun/resolve/first.mjs similarity index 100% rename from test/bun.js/esm/first.mjs rename to test/js/bun/resolve/first.mjs diff --git a/test/bun.js/import-meta.test.js b/test/js/bun/resolve/import-meta.test.js similarity index 92% rename from test/bun.js/import-meta.test.js rename to test/js/bun/resolve/import-meta.test.js index 3f89c09f0ecd..f87c8f89f579 100644 --- a/test/bun.js/import-meta.test.js +++ b/test/js/bun/resolve/import-meta.test.js @@ -1,7 +1,7 @@ import { it, expect } from "bun:test"; import { mkdirSync, rmSync, writeFileSync } from "node:fs"; import * as Module from "node:module"; -import sync from "./require-json.json"; +import sync from "./require-json.json.js"; const { path, dir } = import.meta; @@ -27,7 +27,7 @@ it("require with a query string works on dynamically created content", () => { try { const require = Module.createRequire("/tmp/bun-test-import-meta-dynamic-dir/foo.js"); try { - require("./bar.js?query=123"); + require("./bar.js?query=123.js"); } catch (e) { expect(e.name).toBe("ResolveError"); } @@ -36,7 +36,7 @@ it("require with a query string works on dynamically created content", () => { writeFileSync("/tmp/bun-test-import-meta-dynamic-dir/bar.js", "export default 'hello';", "utf8"); - expect(require("./bar.js?query=123").default).toBe("hello"); + expect(require("./bar.js?query=123.js").default).toBe("hello"); } catch (e) { throw e; } finally { @@ -50,7 +50,7 @@ it("require with a query string works on dynamically created content", () => { it("import.meta.require (json)", () => { expect(import.meta.require("./require-json.json").hello).toBe(sync.hello); const require = Module.createRequire(import.meta.path); - expect(require("./require-json.json").hello).toBe(sync.hello); + expect(require("./require-json.json.js").hello).toBe(sync.hello); }); it("const f = require;require(json)", () => { @@ -148,11 +148,11 @@ it("import.meta.require (javascript, live bindings)", () => { }); it("import.meta.dir", () => { - expect(dir.endsWith("/bun/test/bun.js")).toBe(true); + expect(dir.endsWith("/bun/test/js/bun/resolve")).toBe(true); }); it("import.meta.path", () => { - expect(path.endsWith("/bun/test/bun.js/import-meta.test.js")).toBe(true); + expect(path.endsWith("/bun/test/js/bun/resolve/import-meta.test.js")).toBe(true); }); it('require("bun") works', () => { diff --git a/test/bun.js/import-require-tla.js b/test/js/bun/resolve/import-require-tla.js similarity index 100% rename from test/bun.js/import-require-tla.js rename to test/js/bun/resolve/import-require-tla.js diff --git a/test/bun.js/import.live.decl.js b/test/js/bun/resolve/import.live.decl.js similarity index 100% rename from test/bun.js/import.live.decl.js rename to test/js/bun/resolve/import.live.decl.js diff --git a/test/bun.js/import.live.rexport-require.js b/test/js/bun/resolve/import.live.rexport-require.js similarity index 100% rename from test/bun.js/import.live.rexport-require.js rename to test/js/bun/resolve/import.live.rexport-require.js diff --git a/test/js/bun/resolve/import.live.rexport.js b/test/js/bun/resolve/import.live.rexport.js new file mode 100644 index 000000000000..6709c3466030 --- /dev/null +++ b/test/js/bun/resolve/import.live.rexport.js @@ -0,0 +1,2 @@ +export { foo, setFoo } from "./import.live.decl.js"; +import { foo as bar } from "./import.live.decl.js"; diff --git a/test/bun.js/test-png-import.test.js b/test/js/bun/resolve/png/test-png-import.test.js similarity index 100% rename from test/bun.js/test-png-import.test.js rename to test/js/bun/resolve/png/test-png-import.test.js diff --git a/test/bun.js/test-png.png b/test/js/bun/resolve/png/test-png.png similarity index 100% rename from test/bun.js/test-png.png rename to test/js/bun/resolve/png/test-png.png diff --git a/test/bun.js/require-js-top-level-await.js b/test/js/bun/resolve/require-js-top-level-await.js similarity index 100% rename from test/bun.js/require-js-top-level-await.js rename to test/js/bun/resolve/require-js-top-level-await.js diff --git a/test/bun.js/require-js.js b/test/js/bun/resolve/require-js.js similarity index 100% rename from test/bun.js/require-js.js rename to test/js/bun/resolve/require-js.js diff --git a/test/bun.js/require-js2.js b/test/js/bun/resolve/require-js2.js similarity index 100% rename from test/bun.js/require-js2.js rename to test/js/bun/resolve/require-js2.js diff --git a/test/bun.js/require-json.json b/test/js/bun/resolve/require-json.json similarity index 100% rename from test/bun.js/require-json.json rename to test/js/bun/resolve/require-json.json diff --git a/test/bun.js/require-referenceerror.snapshot.js b/test/js/bun/resolve/require-referenceerror.snapshot.js similarity index 100% rename from test/bun.js/require-referenceerror.snapshot.js rename to test/js/bun/resolve/require-referenceerror.snapshot.js diff --git a/test/bun.js/resolve-error.test.ts b/test/js/bun/resolve/resolve-error.test.ts similarity index 100% rename from test/bun.js/resolve-error.test.ts rename to test/js/bun/resolve/resolve-error.test.ts diff --git a/test/bun.js/resolve-typescript-file.tsx b/test/js/bun/resolve/resolve-typescript-file.tsx similarity index 100% rename from test/bun.js/resolve-typescript-file.tsx rename to test/js/bun/resolve/resolve-typescript-file.tsx diff --git a/test/bun.js/resolve.test.js b/test/js/bun/resolve/resolve.test.js similarity index 100% rename from test/bun.js/resolve.test.js rename to test/js/bun/resolve/resolve.test.js diff --git a/test/bun.js/esm/second-child.mjs b/test/js/bun/resolve/second-child.mjs similarity index 100% rename from test/bun.js/esm/second-child.mjs rename to test/js/bun/resolve/second-child.mjs diff --git a/test/bun.js/esm/second.mjs b/test/js/bun/resolve/second.mjs similarity index 100% rename from test/bun.js/esm/second.mjs rename to test/js/bun/resolve/second.mjs diff --git a/test/bun.js/esm/startEnd.mjs b/test/js/bun/resolve/startEnd.mjs similarity index 100% rename from test/bun.js/esm/startEnd.mjs rename to test/js/bun/resolve/startEnd.mjs diff --git a/test/bun.js/esm/third.mjs b/test/js/bun/resolve/third.mjs similarity index 100% rename from test/bun.js/esm/third.mjs rename to test/js/bun/resolve/third.mjs diff --git a/test/bun.js/toml-fixture.toml b/test/js/bun/resolve/toml/toml-fixture.toml similarity index 100% rename from test/bun.js/toml-fixture.toml rename to test/js/bun/resolve/toml/toml-fixture.toml diff --git a/test/bun.js/toml.test.js b/test/js/bun/resolve/toml/toml.test.js similarity index 97% rename from test/bun.js/toml.test.js rename to test/js/bun/resolve/toml/toml.test.js index a1aa09a4c679..17d167476103 100644 --- a/test/bun.js/toml.test.js +++ b/test/js/bun/resolve/toml/toml.test.js @@ -1,5 +1,5 @@ import { describe, it, expect } from "bun:test"; -import { gc } from "./gc"; +import { gc } from "harness"; it("syntax", async () => { gc(); diff --git a/test/bun.js/bash-echo.sh b/test/js/bun/spawn/bash-echo.sh similarity index 100% rename from test/bun.js/bash-echo.sh rename to test/js/bun/spawn/bash-echo.sh diff --git a/test/bun.js/bun-spawn-test.js b/test/js/bun/spawn/bun-spawn-test.js similarity index 100% rename from test/bun.js/bun-spawn-test.js rename to test/js/bun/spawn/bun-spawn-test.js diff --git a/test/bun.js/exit-code-0.js b/test/js/bun/spawn/exit-code-0.js similarity index 100% rename from test/bun.js/exit-code-0.js rename to test/js/bun/spawn/exit-code-0.js diff --git a/test/bun.js/exit-code-1.js b/test/js/bun/spawn/exit-code-1.js similarity index 100% rename from test/bun.js/exit-code-1.js rename to test/js/bun/spawn/exit-code-1.js diff --git a/test/bun.js/exit-code-await-throw-1.js b/test/js/bun/spawn/exit-code-await-throw-1.js similarity index 100% rename from test/bun.js/exit-code-await-throw-1.js rename to test/js/bun/spawn/exit-code-await-throw-1.js diff --git a/test/bun.js/exit-code-unhandled-throw.js b/test/js/bun/spawn/exit-code-unhandled-throw.js similarity index 100% rename from test/bun.js/exit-code-unhandled-throw.js rename to test/js/bun/spawn/exit-code-unhandled-throw.js diff --git a/test/bun.js/exit-code.test.ts b/test/js/bun/spawn/exit-code.test.ts similarity index 95% rename from test/bun.js/exit-code.test.ts rename to test/js/bun/spawn/exit-code.test.ts index 5f1aab9043e9..cda76a395fc1 100644 --- a/test/bun.js/exit-code.test.ts +++ b/test/js/bun/spawn/exit-code.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it, test } from "bun:test"; -import { bunExe } from "bunExe"; +import { bunExe } from "harness"; import { spawnSync } from "bun"; it("process.exit(1) works", () => { diff --git a/test/bun.js/exit.js b/test/js/bun/spawn/exit.js similarity index 100% rename from test/bun.js/exit.js rename to test/js/bun/spawn/exit.js diff --git a/test/bun.js/spawn-streaming-stdin.test.ts b/test/js/bun/spawn/spawn-streaming-stdin.test.ts similarity index 93% rename from test/bun.js/spawn-streaming-stdin.test.ts rename to test/js/bun/spawn/spawn-streaming-stdin.test.ts index 02067b71932f..e2d346ec84c7 100644 --- a/test/bun.js/spawn-streaming-stdin.test.ts +++ b/test/js/bun/spawn/spawn-streaming-stdin.test.ts @@ -1,9 +1,7 @@ import { it, test, expect } from "bun:test"; import { spawn } from "bun"; -import { bunExe } from "./bunExe"; -import { gcTick } from "gc"; +import { bunExe, bunEnv, gcTick } from "harness"; import { closeSync, openSync } from "fs"; -import { bunEnv } from "./bunEnv"; const N = 100; test("spawn can write to stdin multiple chunks", async () => { diff --git a/test/bun.js/spawn-streaming-stdout-repro.js b/test/js/bun/spawn/spawn-streaming-stdout-repro.js similarity index 100% rename from test/bun.js/spawn-streaming-stdout-repro.js rename to test/js/bun/spawn/spawn-streaming-stdout-repro.js diff --git a/test/bun.js/spawn-streaming-stdout.test.ts b/test/js/bun/spawn/spawn-streaming-stdout.test.ts similarity index 92% rename from test/bun.js/spawn-streaming-stdout.test.ts rename to test/js/bun/spawn/spawn-streaming-stdout.test.ts index bda1031add16..75e36ca2c330 100644 --- a/test/bun.js/spawn-streaming-stdout.test.ts +++ b/test/js/bun/spawn/spawn-streaming-stdout.test.ts @@ -1,9 +1,7 @@ import { it, test, expect } from "bun:test"; import { spawn } from "bun"; -import { bunExe } from "./bunExe"; -import { gcTick } from "gc"; +import { bunExe, bunEnv, gcTick } from "harness"; import { closeSync, openSync } from "fs"; -import { bunEnv } from "./bunEnv"; test("spawn can read from stdout multiple chunks", async () => { gcTick(true); diff --git a/test/bun.js/spawn.test.ts b/test/js/bun/spawn/spawn.test.ts similarity index 99% rename from test/bun.js/spawn.test.ts rename to test/js/bun/spawn/spawn.test.ts index def0330ee23c..876985e6654a 100644 --- a/test/bun.js/spawn.test.ts +++ b/test/js/bun/spawn/spawn.test.ts @@ -1,8 +1,7 @@ import { ArrayBufferSink, readableStreamToText, spawn, spawnSync, write } from "bun"; import { describe, expect, it } from "bun:test"; -import { gcTick as _gcTick } from "./gc"; +import { gcTick as _gcTick, bunEnv } from "harness"; import { rmdirSync, unlinkSync, rmSync, writeFileSync } from "node:fs"; -import { bunEnv } from "./bunEnv"; for (let [gcTick, label] of [ [_gcTick, "gcTick"], diff --git a/test/bun.js/stdin-repro.js b/test/js/bun/spawn/stdin-repro.js similarity index 100% rename from test/bun.js/stdin-repro.js rename to test/js/bun/spawn/stdin-repro.js diff --git a/test/bun.js/stdio-test-instance-a-lot.js b/test/js/bun/spawn/stdio-test-instance-a-lot.js similarity index 100% rename from test/bun.js/stdio-test-instance-a-lot.js rename to test/js/bun/spawn/stdio-test-instance-a-lot.js diff --git a/test/bun.js/stdio-test-instance.js b/test/js/bun/spawn/stdio-test-instance.js similarity index 100% rename from test/bun.js/stdio-test-instance.js rename to test/js/bun/spawn/stdio-test-instance.js diff --git a/test/bun.js/northwind.testdb b/test/js/bun/sqlite/northwind.testdb similarity index 100% rename from test/bun.js/northwind.testdb rename to test/js/bun/sqlite/northwind.testdb diff --git a/test/bun.js/sql-raw.test.js b/test/js/bun/sqlite/sql-raw.test.js similarity index 100% rename from test/bun.js/sql-raw.test.js rename to test/js/bun/sqlite/sql-raw.test.js diff --git a/test/bun.js/sqlite-cross-process.js b/test/js/bun/sqlite/sqlite-cross-process.js similarity index 100% rename from test/bun.js/sqlite-cross-process.js rename to test/js/bun/sqlite/sqlite-cross-process.js diff --git a/test/bun.js/sqlite.test.js b/test/js/bun/sqlite/sqlite.test.js similarity index 99% rename from test/bun.js/sqlite.test.js rename to test/js/bun/sqlite/sqlite.test.js index 408fa5a26b7c..a23c2f037836 100644 --- a/test/bun.js/sqlite.test.js +++ b/test/js/bun/sqlite/sqlite.test.js @@ -2,7 +2,7 @@ import { expect, it, describe } from "bun:test"; import { Database, constants } from "bun:sqlite"; import { existsSync, fstat, realpathSync, rmSync, writeFileSync } from "fs"; import { spawnSync } from "bun"; -import { bunExe } from "bunExe"; +import { bunExe } from "harness"; import { tmpdir } from "os"; var encode = text => new TextEncoder().encode(text); diff --git a/test/bun.js/bigint.test.js b/test/js/bun/test/bigint.test.js similarity index 100% rename from test/bun.js/bigint.test.js rename to test/js/bun/test/bigint.test.js diff --git a/test/bun.js/bun-test.test.ts b/test/js/bun/test/bun-test.test.ts similarity index 100% rename from test/bun.js/bun-test.test.ts rename to test/js/bun/test/bun-test.test.ts diff --git a/test/bun.js/jest-doesnt-auto-import.js b/test/js/bun/test/jest-doesnt-auto-import.js similarity index 100% rename from test/bun.js/jest-doesnt-auto-import.js rename to test/js/bun/test/jest-doesnt-auto-import.js diff --git a/test/bun.js/bun-test/jest-hooks.test.ts b/test/js/bun/test/jest-hooks.test.ts similarity index 100% rename from test/bun.js/bun-test/jest-hooks.test.ts rename to test/js/bun/test/jest-hooks.test.ts diff --git a/test/bun.js/bun-test/nested-describes.test.ts b/test/js/bun/test/nested-describes.test.ts similarity index 100% rename from test/bun.js/bun-test/nested-describes.test.ts rename to test/js/bun/test/nested-describes.test.ts diff --git a/test/bun.js/test-auto-import-jest-globals.test.js b/test/js/bun/test/test-auto-import-jest-globals.test.js similarity index 100% rename from test/bun.js/test-auto-import-jest-globals.test.js rename to test/js/bun/test/test-auto-import-jest-globals.test.js diff --git a/test/bun.js/test-test.test.ts b/test/js/bun/test/test-test.test.ts similarity index 99% rename from test/bun.js/test-test.test.ts rename to test/js/bun/test/test-test.test.ts index c83ee07a53d4..b834d2152b43 100644 --- a/test/bun.js/test-test.test.ts +++ b/test/js/bun/test/test-test.test.ts @@ -1,7 +1,6 @@ import { spawn, spawnSync } from "bun"; import { describe, expect, it, test } from "bun:test"; -import { bunEnv } from "bunEnv"; -import { bunExe } from "bunExe"; +import { bunExe, bunEnv } from "harness"; import { mkdirSync, realpathSync, rmSync, writeFileSync } from "fs"; import { mkdtemp, rm, writeFile } from "fs/promises"; import { tmpdir } from "os"; diff --git a/test/bun.js/arraybuffersink.test.ts b/test/js/bun/util/arraybuffersink.test.ts similarity index 98% rename from test/bun.js/arraybuffersink.test.ts rename to test/js/bun/util/arraybuffersink.test.ts index f0df03d90017..373ac111658b 100644 --- a/test/bun.js/arraybuffersink.test.ts +++ b/test/js/bun/util/arraybuffersink.test.ts @@ -1,6 +1,6 @@ import { ArrayBufferSink } from "bun"; import { describe, expect, it } from "bun:test"; -import { withoutAggressiveGC } from "gc"; +import { withoutAggressiveGC } from "harness"; describe("ArrayBufferSink", () => { const fixtures = [ diff --git a/test/bun.js/concat.test.js b/test/js/bun/util/concat.test.js similarity index 97% rename from test/bun.js/concat.test.js rename to test/js/bun/util/concat.test.js index 36421cab62d7..0cea303fe93a 100644 --- a/test/bun.js/concat.test.js +++ b/test/js/bun/util/concat.test.js @@ -1,5 +1,4 @@ import { describe, it, expect } from "bun:test"; -import { gcTick } from "./gc"; import { concatArrayBuffers } from "bun"; describe("concat", () => { diff --git a/test/bun.js/empty.js b/test/js/bun/util/empty.js similarity index 100% rename from test/bun.js/empty.js rename to test/js/bun/util/empty.js diff --git a/test/bun.js/escapeHTML.test.js b/test/js/bun/util/escapeHTML.test.js similarity index 99% rename from test/bun.js/escapeHTML.test.js rename to test/js/bun/util/escapeHTML.test.js index f96849a84326..bdddf4a45db5 100644 --- a/test/bun.js/escapeHTML.test.js +++ b/test/js/bun/util/escapeHTML.test.js @@ -1,5 +1,4 @@ import { describe, it, expect } from "bun:test"; -import { gcTick } from "./gc"; import { escapeHTML } from "bun"; describe("escapeHTML", () => { diff --git a/test/bun.js/fileUrl.test.js b/test/js/bun/util/fileUrl.test.js similarity index 100% rename from test/bun.js/fileUrl.test.js rename to test/js/bun/util/fileUrl.test.js diff --git a/test/bun.js/filesink.test.ts b/test/js/bun/util/filesink.test.ts similarity index 100% rename from test/bun.js/filesink.test.ts rename to test/js/bun/util/filesink.test.ts diff --git a/test/bun.js/filesystem_router.test.ts b/test/js/bun/util/filesystem_router.test.ts similarity index 100% rename from test/bun.js/filesystem_router.test.ts rename to test/js/bun/util/filesystem_router.test.ts diff --git a/test/bun.js/hash.test.js b/test/js/bun/util/hash.test.js similarity index 97% rename from test/bun.js/hash.test.js rename to test/js/bun/util/hash.test.js index 0c1baaf14a7d..87a5a9ce327a 100644 --- a/test/bun.js/hash.test.js +++ b/test/js/bun/util/hash.test.js @@ -1,7 +1,7 @@ import fs from "fs"; import { it, expect } from "bun:test"; import path from "path"; -import { gcTick } from "gc"; +import { gcTick } from "harness"; it(`Bun.hash()`, () => { gcTick(); diff --git a/test/bun.js/index-of-line.test.ts b/test/js/bun/util/index-of-line.test.ts similarity index 100% rename from test/bun.js/index-of-line.test.ts rename to test/js/bun/util/index-of-line.test.ts diff --git a/test/bun.js/inspect.test.js b/test/js/bun/util/inspect.test.js similarity index 99% rename from test/bun.js/inspect.test.js rename to test/js/bun/util/inspect.test.js index 024a976a3d98..ec2fb0b45ced 100644 --- a/test/bun.js/inspect.test.js +++ b/test/js/bun/util/inspect.test.js @@ -278,7 +278,7 @@ const fixture = [ return new (import.meta.require("events").EventEmitter)(); }, async () => await import("node:assert"), - async () => await import("./empty.js"), + async () => await import("../../empty.js.js"), () => import.meta.require("./empty.js"), () => new Proxy({ yolo: 1 }, {}), () => diff --git a/test/bun.js/mmap.test.js b/test/js/bun/util/mmap.test.js similarity index 97% rename from test/bun.js/mmap.test.js rename to test/js/bun/util/mmap.test.js index 2b15a4000fbd..4972359da5ec 100644 --- a/test/bun.js/mmap.test.js +++ b/test/js/bun/util/mmap.test.js @@ -1,5 +1,5 @@ import { describe, it, expect } from "bun:test"; -import { gcTick } from "./gc"; +import { gcTick } from "harness"; describe("Bun.mmap", async () => { await gcTick(); diff --git a/test/bun.js/peek.test.ts b/test/js/bun/util/peek.test.ts similarity index 100% rename from test/bun.js/peek.test.ts rename to test/js/bun/util/peek.test.ts diff --git a/test/bun.js/reportError.test.js b/test/js/bun/util/reportError.test.js similarity index 100% rename from test/bun.js/reportError.test.js rename to test/js/bun/util/reportError.test.js diff --git a/test/bun.js/sleep.js b/test/js/bun/util/sleep.js similarity index 100% rename from test/bun.js/sleep.js rename to test/js/bun/util/sleep.js diff --git a/test/bun.js/sleepSync.test.ts b/test/js/bun/util/sleepSync.test.ts similarity index 100% rename from test/bun.js/sleepSync.test.ts rename to test/js/bun/util/sleepSync.test.ts diff --git a/test/bun.js/unsafe.test.js b/test/js/bun/util/unsafe.test.js similarity index 97% rename from test/bun.js/unsafe.test.js rename to test/js/bun/util/unsafe.test.js index 8ce7598db8b0..38830fa36df0 100644 --- a/test/bun.js/unsafe.test.js +++ b/test/js/bun/util/unsafe.test.js @@ -1,5 +1,5 @@ import { test, expect, it, describe } from "bun:test"; -import { gc } from "./gc"; +import { gc } from "harness"; it("arrayBufferToString u8", async () => { var encoder = new TextEncoder(); diff --git a/test/bun.js/which.test.ts b/test/js/bun/util/which.test.ts similarity index 100% rename from test/bun.js/which.test.ts rename to test/js/bun/util/which.test.ts diff --git a/test/bun.js/hello-wasi.wasm b/test/js/bun/wasm/hello-wasi.wasm similarity index 100% rename from test/bun.js/hello-wasi.wasm rename to test/js/bun/wasm/hello-wasi.wasm diff --git a/test/bun.js/wasi.test.js b/test/js/bun/wasm/wasi.test.js similarity index 84% rename from test/bun.js/wasi.test.js rename to test/js/bun/wasm/wasi.test.js index f6d8e76f5f5e..ac1d986d46e1 100644 --- a/test/bun.js/wasi.test.js +++ b/test/js/bun/wasm/wasi.test.js @@ -1,7 +1,6 @@ import { spawnSync } from "bun"; import { expect, it } from "bun:test"; -import { bunEnv } from "bunEnv"; -import { bunExe } from "bunExe"; +import { bunExe, bunEnv } from "harness"; it("Should support printing 'hello world'", () => { const { stdout, exitCode } = spawnSync({ diff --git a/test/bun.js/wasm-return-1-test.zig b/test/js/bun/wasm/wasm-return-1-test.zig similarity index 100% rename from test/bun.js/wasm-return-1-test.zig rename to test/js/bun/wasm/wasm-return-1-test.zig diff --git a/test/bun.js/wasm.js b/test/js/bun/wasm/wasm.js similarity index 100% rename from test/bun.js/wasm.js rename to test/js/bun/wasm/wasm.js diff --git a/test/bun.js/websocket-server.test.ts b/test/js/bun/websocket/websocket-server.test.ts similarity index 97% rename from test/bun.js/websocket-server.test.ts rename to test/js/bun/websocket/websocket-server.test.ts index 3205ff9490a6..b3b2e57b5005 100644 --- a/test/bun.js/websocket-server.test.ts +++ b/test/js/bun/websocket/websocket-server.test.ts @@ -1,20 +1,11 @@ import { describe, expect, it } from "bun:test"; -import { gcTick } from "./gc"; +import { gcTick } from "harness"; import { serve } from "bun"; -var port = 4301; -function getPort() { - if (port > 4444) { - port = 4321; - } - - return port++; -} - describe("websocket server", () => { it("can do publish()", async done => { var server = serve({ - port: getPort(), + port: 0, websocket: { open(ws) { ws.subscribe("all"); @@ -51,7 +42,7 @@ describe("websocket server", () => { it("can do publish() with publishToSelf: false", async done => { var server = serve({ - port: getPort(), + port: 0, websocket: { open(ws) { ws.subscribe("all"); @@ -92,7 +83,7 @@ describe("websocket server", () => { it("in close() should work", async () => { var count = 0; var server = serve({ - port: getPort(), + port: 0, websocket: { open(ws) { ws.subscribe("all"); @@ -152,7 +143,7 @@ describe("websocket server", () => { var resolve; console.trace("here"); var server = serve({ - port: getPort(), + port: 0, websocket: { open(ws) {}, message(ws, msg) {}, @@ -196,7 +187,7 @@ describe("websocket server", () => { it("headers error doesn't crash", async () => { await new Promise((resolve, reject) => { const server = serve({ - port: getPort(), + port: 0, websocket: { open(ws) { ws.close(); @@ -236,7 +227,7 @@ describe("websocket server", () => { }); it("can do hello world", async () => { const server = serve({ - port: getPort(), + port: 0, websocket: { open(ws) { server.stop(); @@ -290,7 +281,7 @@ describe("websocket server", () => { it("fetch() allows a Response object to be returned for an upgraded ServerWebSocket", () => { const server = serve({ - port: getPort(), + port: 0, websocket: { open(ws) { server.stop(); @@ -349,7 +340,7 @@ describe("websocket server", () => { it("fetch() allows a Promise object to be returned for an upgraded ServerWebSocket", () => { const server = serve({ - port: getPort(), + port: 0, websocket: { async open(ws) { server.stop(); @@ -409,7 +400,7 @@ describe("websocket server", () => { it("binaryType works", async () => { var done = false; const server = serve({ - port: getPort(), + port: 0, websocket: { open(ws) { server.stop(); @@ -471,7 +462,7 @@ describe("websocket server", () => { it("does not upgrade for non-websocket connections", async () => { await new Promise(async (resolve, reject) => { var server = serve({ - port: getPort(), + port: 0, websocket: { open(ws) { ws.send("hello world"); @@ -497,7 +488,7 @@ describe("websocket server", () => { it("does not upgrade for non-websocket servers", async () => { await new Promise(async (resolve, reject) => { const server = serve({ - port: getPort(), + port: 0, fetch(req, server) { server.stop(); expect(() => { @@ -515,7 +506,7 @@ describe("websocket server", () => { it("async can do hello world", async () => { const server = serve({ - port: getPort(), + port: 0, websocket: { async open(ws) { server.stop(true); @@ -555,7 +546,7 @@ describe("websocket server", () => { await new Promise((resolve, reject) => { var websocket; var server = serve({ - port: getPort(), + port: 0, websocket: { async open(ws) { // we don't care about the data @@ -585,7 +576,7 @@ describe("websocket server", () => { await new Promise((resolve, reject) => { var websocket; var server = serve({ - port: getPort(), + port: 0, websocket: { async open(ws) { // we don't care about the data @@ -613,7 +604,7 @@ describe("websocket server", () => { await new Promise((resolve, reject) => { var websocket; var server = serve({ - port: getPort(), + port: 0, websocket: { async open(ws) { // we don't care about the data @@ -641,7 +632,7 @@ describe("websocket server", () => { await new Promise((resolve, reject) => { var websocket; var server = serve({ - port: getPort(), + port: 0, websocket: { async open(ws) { // we don't care about the data @@ -667,7 +658,7 @@ describe("websocket server", () => { it("can do hello world corked", async () => { const server = serve({ - port: getPort(), + port: 0, websocket: { open(ws) { server.stop(); @@ -709,7 +700,7 @@ describe("websocket server", () => { it("can do some back and forth", async () => { var dataCount = 0; const server = serve({ - port: getPort(), + port: 0, websocket: { open(ws) { server.stop(); @@ -785,7 +776,7 @@ describe("websocket server", () => { var clientCounter = 0; const server = serve({ - port: getPort(), + port: 0, websocket: { open(ws) { server.stop(); @@ -846,7 +837,7 @@ describe("websocket server", () => { var serverCounter = 0; var clientCount = 0; const server = serve({ - port: getPort(), + port: 0, websocket: { open(ws) { server.stop(); diff --git a/test/bun.js/undici.test.ts b/test/js/first_party/undici/undici.test.ts similarity index 100% rename from test/bun.js/undici.test.ts rename to test/js/first_party/undici/undici.test.ts diff --git a/test/bun.js/assert-test.test.ts b/test/js/node/assert/assert-test.test.ts similarity index 100% rename from test/bun.js/assert-test.test.ts rename to test/js/node/assert/assert-test.test.ts diff --git a/test/bun.js/buffer.test.js b/test/js/node/buffer.test.js similarity index 99% rename from test/bun.js/buffer.test.js rename to test/js/node/buffer.test.js index b8fade4d2333..568bf8d44584 100644 --- a/test/bun.js/buffer.test.js +++ b/test/js/node/buffer.test.js @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach, afterEach, test } from "bun:test"; -import { gc } from "./gc"; +import { gc } from "harness"; const BufferModule = await import("buffer"); diff --git a/test/bun.js/child-process-stdio.test.js b/test/js/node/child_process/child-process-stdio.test.js similarity index 97% rename from test/bun.js/child-process-stdio.test.js rename to test/js/node/child_process/child-process-stdio.test.js index 43bcc771ac85..d0c6d9bc7561 100644 --- a/test/bun.js/child-process-stdio.test.js +++ b/test/js/node/child_process/child-process-stdio.test.js @@ -1,7 +1,6 @@ import { describe, it, expect, beforeAll } from "bun:test"; import { spawn, execSync } from "node:child_process"; -import { bunExe } from "bunExe"; -import { bunEnv } from "bunEnv"; +import { bunExe, bunEnv } from "harness"; const CHILD_PROCESS_FILE = import.meta.dir + "/spawned-child.js"; const OUT_FILE = import.meta.dir + "/stdio-test-out.txt"; diff --git a/test/bun.js/child_process-node.test.js b/test/js/node/child_process/child_process-node.test.js similarity index 99% rename from test/bun.js/child_process-node.test.js rename to test/js/node/child_process/child_process-node.test.js index f2c18c67ce64..deb3bfb8681a 100644 --- a/test/bun.js/child_process-node.test.js +++ b/test/js/node/child_process/child_process-node.test.js @@ -1,8 +1,7 @@ import { beforeAll, describe, expect, it } from "bun:test"; import { ChildProcess, spawn, exec } from "node:child_process"; -import { throws, assert, createCallCheckCtx, createDoneDotAll } from "node-test-helpers"; +import { throws, assert, createCallCheckCtx, createDoneDotAll } from "node-harness"; import { tmpdir } from "node:os"; -import { gcTick } from "gc"; const strictEqual = (a, b) => expect(a).toStrictEqual(b); const debug = process.env.DEBUG ? console.log : () => {}; diff --git a/test/bun.js/child_process.test.ts b/test/js/node/child_process/child_process.test.ts similarity index 99% rename from test/bun.js/child_process.test.ts rename to test/js/node/child_process/child_process.test.ts index 7f2c6b383f1c..167cbd8b0641 100644 --- a/test/bun.js/child_process.test.ts +++ b/test/js/node/child_process/child_process.test.ts @@ -1,5 +1,5 @@ import { describe, it as it_, expect as expect_ } from "bun:test"; -import { gcTick } from "gc"; +import { gcTick } from "harness"; import { ChildProcess, spawn, execFile, exec, fork, spawnSync, execFileSync, execSync } from "node:child_process"; import { tmpdir } from "node:os"; import { promisify } from "node:util"; diff --git a/test/bun.js/readFileSync.txt b/test/js/node/child_process/readFileSync.txt similarity index 100% rename from test/bun.js/readFileSync.txt rename to test/js/node/child_process/readFileSync.txt diff --git a/test/bun.js/spawned-child.js b/test/js/node/child_process/spawned-child.js similarity index 100% rename from test/bun.js/spawned-child.js rename to test/js/node/child_process/spawned-child.js diff --git a/test/bun.js/crypto-scrypt.test.js b/test/js/node/crypto/crypto-scrypt.test.js similarity index 100% rename from test/bun.js/crypto-scrypt.test.js rename to test/js/node/crypto/crypto-scrypt.test.js diff --git a/test/bun.js/crypto.test.js b/test/js/node/crypto/crypto.test.js similarity index 100% rename from test/bun.js/crypto.test.js rename to test/js/node/crypto/crypto.test.js diff --git a/test/bun.js/node-crypto.test.js b/test/js/node/crypto/node-crypto.test.js similarity index 100% rename from test/bun.js/node-crypto.test.js rename to test/js/node/crypto/node-crypto.test.js diff --git a/test/bun.js/dirname.test.js b/test/js/node/dirname.test.js similarity index 100% rename from test/bun.js/dirname.test.js rename to test/js/node/dirname.test.js diff --git a/test/bun.js/disabled-module.test.js b/test/js/node/disabled-module.test.js similarity index 100% rename from test/bun.js/disabled-module.test.js rename to test/js/node/disabled-module.test.js diff --git a/test/bun.js/dns.node.mjs b/test/js/node/dns/dns.node.mjs similarity index 100% rename from test/bun.js/dns.node.mjs rename to test/js/node/dns/dns.node.mjs diff --git a/test/bun.js/node-dns.test.js b/test/js/node/dns/node-dns.test.js similarity index 100% rename from test/bun.js/node-dns.test.js rename to test/js/node/dns/node-dns.test.js diff --git a/test/bun.js/event-emitter.test.ts b/test/js/node/events/event-emitter.test.ts similarity index 100% rename from test/bun.js/event-emitter.test.ts rename to test/js/node/events/event-emitter.test.ts diff --git a/test/bun.js/node-builtins.test.js b/test/js/node/events/node-builtins.test.js similarity index 100% rename from test/bun.js/node-builtins.test.js rename to test/js/node/events/node-builtins.test.js diff --git a/test/fixtures/export-lazy-fs-streams/export-*-from.ts b/test/js/node/fs/export-*-from.ts similarity index 100% rename from test/fixtures/export-lazy-fs-streams/export-*-from.ts rename to test/js/node/fs/export-*-from.ts diff --git a/test/fixtures/export-lazy-fs-streams/export-from.ts b/test/js/node/fs/export-from.ts similarity index 100% rename from test/fixtures/export-lazy-fs-streams/export-from.ts rename to test/js/node/fs/export-from.ts diff --git a/test/bun.js/fs-stream.js b/test/js/node/fs/fs-stream.js similarity index 100% rename from test/bun.js/fs-stream.js rename to test/js/node/fs/fs-stream.js diff --git a/test/bun.js/fs-stream.link.js b/test/js/node/fs/fs-stream.link.js similarity index 100% rename from test/bun.js/fs-stream.link.js rename to test/js/node/fs/fs-stream.link.js diff --git a/test/bun.js/fs.test.ts b/test/js/node/fs/fs.test.ts similarity index 99% rename from test/bun.js/fs.test.ts rename to test/js/node/fs/fs.test.ts index 4c847d25a46d..48abef6cb124 100644 --- a/test/bun.js/fs.test.ts +++ b/test/js/node/fs/fs.test.ts @@ -1,5 +1,5 @@ import { beforeEach, describe, expect, it } from "bun:test"; -import { gc, gcTick } from "./gc"; +import { gc, gcTick } from "harness"; import fs, { closeSync, existsSync, @@ -32,11 +32,8 @@ import _promises from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { ReadStream as ReadStream_, WriteStream as WriteStream_ } from "../fixtures/export-lazy-fs-streams/export-from"; -import { - ReadStream as ReadStreamStar_, - WriteStream as WriteStreamStar_, -} from "../fixtures/export-lazy-fs-streams/export-*-from"; +import { ReadStream as ReadStream_, WriteStream as WriteStream_ } from "./export-from.js"; +import { ReadStream as ReadStreamStar_, WriteStream as WriteStreamStar_ } from "./export-*-from.js"; const Buffer = globalThis.Buffer || Uint8Array; diff --git a/test/js/node/fs/readFileSync.txt b/test/js/node/fs/readFileSync.txt new file mode 100644 index 000000000000..ddc94b988e30 --- /dev/null +++ b/test/js/node/fs/readFileSync.txt @@ -0,0 +1 @@ +File read successfully \ No newline at end of file diff --git a/test/js/node/fs/test.txt b/test/js/node/fs/test.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/test/bun.js/writeFileSync.txt b/test/js/node/fs/writeFileSync.txt similarity index 100% rename from test/bun.js/writeFileSync.txt rename to test/js/node/fs/writeFileSync.txt diff --git a/test/bun.js/node-test-helpers.test.js b/test/js/node/harness.test.js similarity index 100% rename from test/bun.js/node-test-helpers.test.js rename to test/js/node/harness.test.js diff --git a/test/bun.js/node-test-helpers.ts b/test/js/node/harness.ts similarity index 99% rename from test/bun.js/node-test-helpers.ts rename to test/js/node/harness.ts index 0eaa7d07b473..227009a64298 100644 --- a/test/bun.js/node-test-helpers.ts +++ b/test/js/node/harness.ts @@ -1,6 +1,5 @@ import { expect as expect_ } from "bun:test"; -// @ts-ignore -import { gcTick } from "gc"; +import { gcTick } from "harness"; import assertNode from "node:assert"; type DoneCb = (err?: Error) => any; diff --git a/test/bun.js/node-http.test.ts b/test/js/node/http/node-http.fixme.ts similarity index 94% rename from test/bun.js/node-http.test.ts rename to test/js/node/http/node-http.fixme.ts index 619d8cb35266..d47002a6ffce 100644 --- a/test/bun.js/node-http.test.ts +++ b/test/js/node/http/node-http.fixme.ts @@ -1,6 +1,19 @@ import { describe, expect, it, beforeAll, afterAll } from "bun:test"; import { createServer, request, get, Agent, globalAgent, Server } from "node:http"; -import { createDoneDotAll } from "node-test-helpers"; +import { createDoneDotAll } from "node-harness"; + +function listen(server: any): Promise { + return new Promise((resolve, reject) => { + server.listen({ port: 0 }, (err, hostname, port) => { + if (err) { + reject(err); + } else { + resolve(new URL(`http://${hostname}:${port}`)); + } + }); + setTimeout(() => reject("Timed out"), 5000); + }); +} describe("node:http", () => { describe("createServer", async () => { @@ -10,9 +23,8 @@ describe("node:http", () => { res.writeHead(200, { "Content-Type": "text/plain" }); res.end("Hello World"); }); - server.listen(8123); - - const res = await fetch("http://localhost:8123/hello?world"); + const url = await listen(server); + const res = await fetch(new URL("/hello?world", url)); expect(await res.text()).toBe("Hello World"); server.close(); }); @@ -32,9 +44,8 @@ describe("node:http", () => { res.end(); }); }); - server.listen(8124); - - const res = await fetch("http://localhost:8124", { + const url = await listen(server); + const res = await fetch(url, { method: "POST", body: bodyBlob, }); @@ -59,9 +70,8 @@ describe("node:http", () => { res.end(); }); }); - server.listen(8125); - - const res = await fetch("http://localhost:8125", { + const url = await listen(server); + const res = await fetch(url, { method: "POST", body: bodyBlob, }); @@ -73,7 +83,7 @@ describe("node:http", () => { it("listen should return server", async () => { const server = createServer(); - const listenResponse = server.listen(8129); + const listenResponse = server.listen(0); expect(listenResponse instanceof Server).toBe(true); expect(listenResponse).toBe(server); listenResponse.close(); @@ -464,24 +474,25 @@ describe("node:http", () => { done(); }); - server.listen({ signal, port: 8130 }); + server.listen({ signal, port: 0 }); }); }); describe("get", () => { let server; - beforeAll(() => { + let url; + beforeAll(async () => { server = createServer((req, res) => { res.writeHead(200, { "Content-Type": "text/plain" }); res.end("Hello World"); }); - server.listen(8127); + url = await listen(server); }); afterAll(() => { server.close(); }); it("should make a standard GET request, like request", done => { - get("http://127.0.0.1:8127", res => { + get(url, res => { let data = ""; res.setEncoding("utf8"); res.on("data", chunk => { @@ -506,12 +517,12 @@ describe("node:http", () => { res.writeHead(200, { "Content-Type": "text/plain" }); res.end("Hello World"); }); - server.listen(8128, () => { + server.listen({ port: 0 }, (_, host, port) => { // Setup request after server is listening dummyReq = request( { - host: "localhost", - port: 8128, + host, + port, agent: dummyAgent, }, res => {}, @@ -570,7 +581,7 @@ describe("node:http", () => { server.close(); }); it("should attempt to make a standard GET request and abort", done => { - get(`http://127.0.0.1:${server_port}`, { signal: AbortSignal.timeout(5) }, res => { + get(`http://${server_host}:${server_port}`, { signal: AbortSignal.timeout(5) }, res => { let data = ""; res.setEncoding("utf8"); res.on("data", chunk => { diff --git a/test/bun.js/node-module-module.test.js b/test/js/node/module/node-module-module.test.js similarity index 100% rename from test/bun.js/node-module-module.test.js rename to test/js/node/module/node-module-module.test.js diff --git a/test/bun.js/socket/node-net.test.ts b/test/js/node/net/node-net.test.ts similarity index 100% rename from test/bun.js/socket/node-net.test.ts rename to test/js/node/net/node-net.test.ts diff --git a/test/bun.js/os.test.js b/test/js/node/os/os.test.js similarity index 100% rename from test/bun.js/os.test.js rename to test/js/node/os/os.test.js diff --git a/test/bun.js/path.test.js b/test/js/node/path/path.test.js similarity index 100% rename from test/bun.js/path.test.js rename to test/js/node/path/path.test.js diff --git a/test/bun.js/print-process-args.js b/test/js/node/process/print-process-args.js similarity index 100% rename from test/bun.js/print-process-args.js rename to test/js/node/process/print-process-args.js diff --git a/test/bun.js/process-args.test.js b/test/js/node/process/process-args.test.js similarity index 97% rename from test/bun.js/process-args.test.js rename to test/js/node/process/process-args.test.js index a2c79616f54d..4da3a5381f1b 100644 --- a/test/bun.js/process-args.test.js +++ b/test/js/node/process/process-args.test.js @@ -1,6 +1,6 @@ import { spawn } from "bun"; import { test, expect } from "bun:test"; -import { bunExe } from "./bunExe"; +import { bunExe } from "harness"; test("args exclude run", async () => { const arg0 = process.argv[0]; diff --git a/test/bun.js/process-nexttick.js b/test/js/node/process/process-nexttick.js similarity index 100% rename from test/bun.js/process-nexttick.js rename to test/js/node/process/process-nexttick.js diff --git a/test/bun.js/process-nexttick.test.js b/test/js/node/process/process-nexttick.test.js similarity index 100% rename from test/bun.js/process-nexttick.test.js rename to test/js/node/process/process-nexttick.test.js diff --git a/test/bun.js/process-stdin-echo.js b/test/js/node/process/process-stdin-echo.js similarity index 100% rename from test/bun.js/process-stdin-echo.js rename to test/js/node/process/process-stdin-echo.js diff --git a/test/bun.js/process-stdio.test.ts b/test/js/node/process/process-stdio.test.ts similarity index 99% rename from test/bun.js/process-stdio.test.ts rename to test/js/node/process/process-stdio.test.ts index 6054eeeebbf2..463ab5fda385 100644 --- a/test/bun.js/process-stdio.test.ts +++ b/test/js/node/process/process-stdio.test.ts @@ -1,6 +1,6 @@ import { spawn, spawnSync } from "bun"; import { describe, expect, it, test } from "bun:test"; -import { bunExe } from "bunExe"; +import { bunExe } from "harness"; import { isatty } from "tty"; test("process.stdin", () => { diff --git a/test/bun.js/process.test.js b/test/js/node/process/process.test.js similarity index 96% rename from test/bun.js/process.test.js rename to test/js/node/process/process.test.js index 8cc3e6b87eb4..59f54c53f941 100644 --- a/test/bun.js/process.test.js +++ b/test/js/node/process/process.test.js @@ -1,6 +1,6 @@ import { resolveSync, which } from "bun"; import { describe, expect, it } from "bun:test"; -import { readFileSync, realpathSync } from "fs"; +import { existsSync, readFileSync, realpathSync } from "fs"; import { basename } from "path"; it("process", () => { @@ -120,7 +120,8 @@ it("process.umask()", () => { expect(process.umask(orig)).toBe(777); }); -it("process.versions", () => { +const versions = existsSync(import.meta.dir + "/../../src/generated_versions_list.zig"); +(versions ? it : it.skip)("process.versions", () => { // Generate a list of all the versions in the versions object // example: // pub const boringssl = "b275c5ce1c88bc06f5a967026d3c0ce1df2be815"; diff --git a/test/bun.js/readline.node.test.ts b/test/js/node/readline/readline.node.test.ts similarity index 99% rename from test/bun.js/readline.node.test.ts rename to test/js/node/readline/readline.node.test.ts index 4a6c4be8c482..7dfa51ac8d80 100644 --- a/test/bun.js/readline.node.test.ts +++ b/test/js/node/readline/readline.node.test.ts @@ -2,7 +2,7 @@ import { beforeEach, describe, it } from "bun:test"; import readline from "node:readline"; import { Writable, PassThrough } from "node:stream"; import { EventEmitter } from "node:events"; -import { createDoneDotAll, createCallCheckCtx, assert } from "./node-test-helpers"; +import { createDoneDotAll, createCallCheckCtx, assert } from "node-harness"; var { CSI, diff --git a/test/bun.js/readline_promises.node.test.ts b/test/js/node/readline/readline_promises.node.test.ts similarity index 94% rename from test/bun.js/readline_promises.node.test.ts rename to test/js/node/readline/readline_promises.node.test.ts index 93093aecd012..c75d254ca055 100644 --- a/test/bun.js/readline_promises.node.test.ts +++ b/test/js/node/readline/readline_promises.node.test.ts @@ -1,7 +1,7 @@ import { describe, it } from "bun:test"; import readlinePromises from "node:readline/promises"; import { EventEmitter } from "node:events"; -import { createDoneDotAll, createCallCheckCtx, assert } from "./node-test-helpers"; +import { createDoneDotAll, createCallCheckCtx, assert } from "node-harness"; // ---------------------------------------------------------------------------- // Helpers diff --git a/test/bun.js/bufferlist.test.ts b/test/js/node/stream/bufferlist.test.ts similarity index 100% rename from test/bun.js/bufferlist.test.ts rename to test/js/node/stream/bufferlist.test.ts diff --git a/test/bun.js/node-stream-uint8array.test.ts b/test/js/node/stream/node-stream-uint8array.test.ts similarity index 100% rename from test/bun.js/node-stream-uint8array.test.ts rename to test/js/node/stream/node-stream-uint8array.test.ts diff --git a/test/bun.js/node-stream.test.js b/test/js/node/stream/node-stream.test.js similarity index 100% rename from test/bun.js/node-stream.test.js rename to test/js/node/stream/node-stream.test.js diff --git a/test/bun.js/string-decoder.test.js b/test/js/node/string_decoder/string-decoder.test.js similarity index 99% rename from test/bun.js/string-decoder.test.js rename to test/js/node/string_decoder/string-decoder.test.js index 4bc9d7e8bbc0..f3732667823e 100644 --- a/test/bun.js/string-decoder.test.js +++ b/test/js/node/string_decoder/string-decoder.test.js @@ -1,5 +1,5 @@ import { describe, expect, it } from "bun:test"; -import { withoutAggressiveGC } from "gc"; +import { withoutAggressiveGC } from "harness"; const RealStringDecoder = require("string_decoder").StringDecoder; diff --git a/test/bun.js/node-timers.test.ts b/test/js/node/timers/node-timers.test.ts similarity index 100% rename from test/bun.js/node-timers.test.ts rename to test/js/node/timers/node-timers.test.ts diff --git a/test/bun.js/test-util-types.test.js b/test/js/node/util/test-util-types.test.js similarity index 100% rename from test/bun.js/test-util-types.test.js rename to test/js/node/util/test-util-types.test.js diff --git a/test/bun.js/util/util-promisify.test.js b/test/js/node/util/util-promisify.test.js similarity index 100% rename from test/bun.js/util/util-promisify.test.js rename to test/js/node/util/util-promisify.test.js diff --git a/test/bun.js/util/util.test.js b/test/js/node/util/util.test.js similarity index 100% rename from test/bun.js/util/util.test.js rename to test/js/node/util/util.test.js diff --git a/test/bun.js/capture-stack-trace.test.js b/test/js/node/v8/capture-stack-trace.test.js similarity index 100% rename from test/bun.js/capture-stack-trace.test.js rename to test/js/node/v8/capture-stack-trace.test.js diff --git a/test/js/node/zlib/fixture.html.gz b/test/js/node/zlib/fixture.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..0bb85d4cbd6c73a189d1bf1ccb2093866940ef69 GIT binary patch literal 16139 zcmbW8(^n-98-;U|ZQHgzO_OcgwrjF$vTN#O+qP}n?u1j{?{D~S-nI7ayVkzg&sq;j z%#RUsM%q2Sq_ z#x1>HZjeyo1IeuEc{`4~#;^DhxneXqT3#Rn~ ze)OuUzJ=Rg`<0Jk%7%bw(nB0zhmu`za%qS)-Ak)ht!|X1soFxIDh~EU>sn<+ReHGf zDDC*1y;Q`At$GV!*X&q5^)K(6vqX7^O=SpnE2Wrf4;z`1?4lNZX*UR)gWAw;c?FZC zZQwUREe5@;-e~=Afwi-G0zrrP@*mAYip-0J(y3I3N`zyuG_ZF6(PG1t84B2{F&_HJ z>?*Azy5%|38-J0!hSDvi7P_+)mEqb7hxIuIsx$VN!`qkqu;B-uicWp{nJ@`Q4ygvF zi|?=6@3W#sYaF%vxTQKNWFb#8~BvRk9wHR~++@$6VCpfarAe7HdIaYb877c{H{j1Pq zqlq5a{irY_hwmb|6Un!j4piz1J~0=9E0X+;mxcFaIS6g^6;A75Cx)mJVYmUOs58C*SDa~jc0#||0s%Gp zaG>deaYkDAb1b5iKO53u>DR?qQJI(C5|s5<*t=v*lc;4N!j?9jS19%Z{9&&#SS^j0 zZO(KQ-91o4PnAZM0Dp`wGetWvG^~(dMFosQ2xcds&;)B4v#f}Wp`L)=1Ep=xa#Slb zS)5r_u~dv%y$nV$#;}qfb(Ed6AA9eVxTu#voAuOrp4D8*dLk!sz%s4Wk@l42E4&Ef zN*#>J_z)P!f;o8Chx09OYr8%%AhianS8*(BtJAZI_WcN_Cw;{aSt9FYn8Tmfpbi&* zt_ZT)L`B+cDg`eb(g?`-iBGR-k9!DjE)v!#>%}2QbFsW-KaFFOcv!z z#Zs9FA#j#uTdpUWFE_3bIK%1MQAspc&-W|jRn-P=c7@bKi1uKYDfCdtK|wDeXOzKkOs#}k(EO~md&!%((|*vP4}QE${`t5)q>Cz zSk;DyzQiNR*h!bqj72!$`2De*NtSe|VztZIGkkwTWLFUug5cH>A?mopN=Ek;WtY=R zE#YqO;xot$cjxsAX%%Ix-xZ}n7y`fK&I|{yM=OXtbH~Bcs&r)K!S3;FqZRjQ1|ATG zOFy|q!9q(%gQ*ZMC!^+BpzeyLK`_U_zkVN82@-Lju*x5HG^O+(bzz3iqKG%^LyqFH zrhG&ZK4Nfqwj=z=tfhUwq!RE$NBJT$eTC@>&cMXA5yW8=0SJFzio}RGH|^c*2IAG; zFh;C2l%_O-(v9d=rpJ?jtUI0;Zg?$`nzy21k*ze^;6+!k`K9N_Nm6oa15l~=k@3!( zS}<37bNCTgB;k-o2aA&wF^P2=9G&0uaELoK~Ht zh`Gv6Kz!yfiydanjvNcRzutpP)k!iks6QGz3-OvqBrh7Rq?Dq6=$drfug>M%^i5zP z3PlU&>R_zYz-ITAe~i+LRGwCj&(zVMoK5&)MVV31C2nD&3AJxQDKu#IV-R8Z$i(p$ zpe~uwO{3(|UYOBjNVp|BiD~9;j%Ik)dzlm=jQ;hbdCfMDhtSk@fD)REFNlATgH9(( zROn1n!V1gwVor;H|6@lP$d)^!g!cv1&`EPG5@i$2|J8`r%3-RbBs$m-RR(Az=LR!ACU-Qg zg_m`9!&KmF8&xJu|NYNpJ^^Avk3KuH;}S7dmcnSax(wA@5IIVll?lFd3mgn}tH4lu zp!da5Sq0`de$W26(}`C2tc->law`gvA_PCoo>p;zgLi8Y=Pjp$>WO&&EZPZqVSniY zCWz*fd5;BCHE(#Av+T)B?jgC-wDagSItw0H+$!wwGr6U{!aDEP&0-nq@n1|F*v}v> zRWa@rQEew;y4TyJpZy9%-n}LJ?V76d zV~j^KI4RE}(r4ljF^7o6ZQK;T64R-Eb&qcI91R}Gd zqq{Yb*E1t_dem|I8|U9P70$_R(Z)AY&lS-jZm_bt_WMIsE@4cakFj7Z!!S-8DV`46 zN&AGx)pP$TiA|6UC}OwaCt}d(rI$L!ea4iDS^*@7=L&PoX4}>RW0Ym+u>~%hYZWl4 za2LH)$zavkYCJK9AAyJHI$Pu^Rr_!$lZTN7X)K^cTVGDUMNXRLy=0?rM1FvTLzYN< zJBt0`&crkhASHXS5sj`t0DemB4tG{nCTzD}GTU{v3o7&Sv0wTz4!l~3jRMzV#u*!P zYDVMt($Ma@J2!JEP-UF7w3RJ6rDZ%zr{N*JSKt2RW(4X_JmY+)Vs-YSH!Qs=EqmOgFmG0$k+q% zlNW!upGam4DI${v+#M7vyoDfX-D5R1R!|G@!C@5!->R{=g)HlPY7(9sX8WcoAFgxh9`oKVIAd(yk{s{h1ZD?q5)=_o5ceMsnUtrcFZc zj`yrW=t0hDuP+u*?-QA8g<)%iQA6b%Ckm+}H4Osu>m_u@Dg#$?ze7tq5FWZBKv&XC zNB&rH(K4c&Dw$fY&uI&;ku9ifBp;mgo2e~4WlVw1kBu9~Grj0FS@ zW3e#2s2c$5J&9fZ-RdU*A)^rT2KvSb7zlE3zY{5<>%fn|rP6275${yp4x!d(piD+z ze0znxbqfpHQL7MnYv=3dPy(%2gg>L0rYDEQ{Ry!m7Wh63F@(l{^~lH?kRb*~u+_UH= zg^+qaC@(Wbx+y!~v%m(j!TyTriDn~_R5^YL)5+YhjE%|FnfUk2-@e1xv@6-KvQVB4 z_@EZpz)mYhuWotzs;IzLIWTHW_GqHtYPBvm>ssN)T!6;&l`A7$x5njP9Km8cht4Eko4lmZ@awRvoqRY9S+O0)J5`GlxZZBE7cI?UCqS4?+EwYZW>qD1P45WW(S(}yO*?9EzLU&@E@Kthn8fkt6%-wDhIla zD-0m?;1q%;xuqbPJ5R9p+zBpM6hc~r+6?u%EM0LA1Fke86~)!34MetOFpTFW)K8mN z=Rtb@S@tV~r!C6ei;1nn`vunVZ5x$>d#A7gJYd3mGu(;stjGltI3f{x-D<}Lfdw+5 z_dTx#{utH^OuBK^61Ug`U;&&K$Sao6unRbt_Y)Y&VVXrm4 zb#n_G+&Vvm0dd-RUZmO|isYW&iQ`dmoL*g!+RCbg3s55+kbE1#jF8)+d6ur7>$#zX zp@cI!qMaUvaDJn`%!GUWLpC$`qJtiAM;_2lT0xD__+)Z0M_kZN$AVcXOBa#{8(m(j zlj=CdC0?xxn(Jf&9hb8|b1!ToUy+PS30$D93##|N%%Hf9){Hd~l*iIfJu&;MtPO^@L z)7aLqJ`hfoq?Z_0yAoOjkWb-bLv~{!3gkq4o}sXKQ2yaxYIV{?f96HIz|ye?f?vFm z_q3+TCl3Fd$wKaX5YPfPOS4SGu!RCSvDn2@foxmE_XRXA-ibngW$?Y*!2RYNu5`B9$GmJIowgOlS%6SJvpS-W zE;f-KPCw7gFbFNtCvfJ>;@u##gWYWVIMDA(qS@P}@F4Ve3Xvd)!g?s%b}8O>&NP^C zBEXnKp%<{9tCd7I;T~+RMf7zy*U0R-4hVusfS<@`5!?y=x6|IC;k$$zUot-JvyA5L2cAtbl)`HtAH{B^;nVA5G6=)RFXNOteY-a6ap znfa-zL4)wu4;207upR8u*^oVDa=@Q=*Iae8xzdn{sd1GUL5qJ}15mtATZev5+PHG- zJprb3yHQ~XD!&l#2lQEle2A?N$azSx63l=y;ke?scUh+*gq_e|rmHMma}L3At^pW< zQ$c=qQ6ig{M8Nig4Fa0mgDLcG@)rx_Lug(2Cn#-qKi)O5J*GL6fucZ%nToPn-B{VI zIgux^;%_Ki(%k|K`ib9}u$j9>abA=!6_$KGZV>tYf3=%ntpXXJq=0(Gn|Wd)Yk=b| z27y&s^p|vq^^K1AUuFZ(_hsp66ae))p|f3?Ue;2Wcc$>C7)~M|#GegjUdR7x@*r_A62U9~q=eq?>W1 zzEro%ddk~wgzpx4>&4)yI|2>wV1<;hgsPL;@+rl<1s(G=vc7=!hj z*Wp#WqyO!SErx%G_i4D#@rh#QmAoDYaSyJ31OE)ZLmQ$q)(66)hA$M@Bzr)bJ;cDv z4BR0Nh@furVhk2xGX8{WWZ%JT7akf_4;17gYG`3*a*1uT z0@qLYv}A3`x;C|=8G+ynU&HwHca3gPtUn>g@RH!OQ)R$j{SQ;&a0uXxCyYfC{^}n)dnpE=}^|Vf##8lvp(F z#44}?_=;*sGJ=X1h}7Q-mfMQm@|FECN-v$tuw8944J!vmGQjyNa1 zfeN}r-?#=NzI?hI=`tFP(jW14eS*Z*49>rN_X1<19wHWHSg!>nebN37K<3<-L_YhO z=V_;jcO*^Tf_ZiuLx%c_z47a*aj$po<_WoDK}tk<1ZZSaf92&VX zPibPIK+5c{VPZ|Qn|EOJozsl1?z8BeMa>VbPx8zknl6c+Z)14;z%A@t)yy7R7@|2Q zBTm4l8979qbo=E{?|sE`%|GJ3=U7h^#nVYka#uc}CG#28q9H*L?kqHi{s_(zQJ$Ou zJ<=2kjHPM_;oo1Si=1S$w1+@UjJl?Xf~+WqM10Z}YTC^)rC@{keI4R2Y8T_5m=QDt zoi_!Z$9aHbzIfZWVr7*sU7$JOYRq%d_DF&tk8`tmxMDK+>+0xNc+N-l_(NR31)}M&3;dH)CD;`82t284a{Md)*s9ew@zRnqouZpStmbP?Z;)R#?ar&E z_&u*70n$&mX1&HEvBjVO%QZ*S@Bk?Ho^i-{%Afv>JhVk~iRnOI$PkDtz{00gm)<(7SL?IAm%zqLv5o@FGZYJ+U_{z!~JZX||=8-&^ zfaskmK-Le}x=#bKjb`DMJvXak8Z!+H`6CTecH~OFC${bxF(ctunNgs!^0Ps;n|b&@ z_Ke%=ksufd?jpHeCVt-Shk^Dp9#*nu7)vz1gj?7Z*$`R#QCa8U)Q-s1HdWR4LieV! z1Gb1VOyrGT_Re({?Go}AkYx3GUin2=Bzc=!U{fUi@}1cFJ2Oe7m&tqv-p7h#h`pRC zs8Ib_^$a#`@U_yjv^w_>lquPQS3mvQxTPVqeTk%CHridU)yjM@{@=v{b@OWnzXax@ z0*Piuz&o|AjWFGEY_o~UwXfCoOl598pn|yLI5pgiMfCH!do;<{zNk>|XR0S=d79`~ zDMY+iwKdrY-IQOy0cd{yOvyQ1rwfs7^`?5dW1`B@>TAW1+-I;OIQqG zLry0lbvh+c2`Vs?WJ`^?sj?pXUG1u{FU&!RG?4~wZhCeFmxDsXoC}>g1C9#wJ^nUZ z#D^1$dS|av?Pu4Oo3*>Epd5YJ#&K74G>yV7`;AQ&Wf;1tU9<;pDqyqOuU*%A+J_C1 zDQT7XJF67yi`^t(O_&gGvP|5aHR>GERq`QFBAZkxzg}*-a{WN*(?0|Fst$%+gue!H zwODWDe7`HMf4!Zu%R{74vPYqaTR;&L^{VwjKh4V@`8*#5Q5bTkB=o&o6v4o-LMXTF zJL2mXKOh_?YhK)mm@j}S3{J09=y73GvgcKlpaJiYmB6b3600gLcxjry)PmKFb5Qo&M(~zhV9OWI7pLd8tPzamd#0($1nM7m~J}1z&e@BHz>&paQKMU?= z{O(`In2cEZU-GfBgB-uMZ15Ko@qGn{DM^Y^TpvM)>1A`h<a_h4w}^`!#YRdjo+^37!GK`%Ar4 z+hyc+@aZeIf%%c5h@`OR(f7c%X;G%?Lcvk!0TMZBVLS`aDd1^ zL^EmSoOMTRK|`BzU(TicX#<=MG@~WVuB1t#OW1gc`D*9Gti22i9*bo5%{;> zUPMpMkNoc$pY>~zKy{w$-i{rx1+xMq7vofBH@x&m1Q4jR$IsiBUQ7UexuA-_b08M6we^I z&T7Pp=T;>eqeW2vl7_v@6&RlNzB^-`S&V7JsX7WohxX$M#`h-F8B@ul3@FPVJ2cF- zWZdM0cmW~xVBN`mJN*mjXMSpoplL{wksG3H>9{Mli3B=?^8Ogcx%KL#@H14mg$^(x z@I)4SQB+7dT&hdk5n&YLw?>e4NIjGc3I1%eUEg~U9fTDfuz0!Oq6{^{_7OoffQA zEcw`Y#=SnwPAg>BoX9g!mFRjER=4`df#+?2MKug+pwyJh0alrxZfU}%B7IYj&6~Gq z&%yxZpPc92WzRB>TIE>$Z>RFT`crZiDPJdKTuJwX857kz{}y&|>DoljExc+38+l`D zwV9fi8=^Jjy%c&3T5AO!m5td zJPt0u2Q=7o8tB?|cp2VclZ=K5W&rf48X4rHpT$cxEpMU|CZ{?oBL{$Q!tRDyvM7z- z0bQFOqpNYo`z64QE?XW$Pj1!nS^@vZ4&Dfj*XNZS$5ft@bBl&CwMNg9PVeR}z1j&A z_VudnSQ>F_3OkbQ)TF+a(%hU(TS^3(60ZN4Bqu{{vl-p0&sKFl!u6@lLr1g!mQ{1d z(93&VfPL5D(-CMB1~Zsfb>zbbra%E@osZW z$r>w%bS$5`y>@Szj+)vSDrzoMo`eH8;qKYLwyP|&B>*#3lZL^KV!v%&O6krc4^xBM z{E_oyEylmA6FJk{)h-_Fy^T7ZuzVj=3Kd~e#_w%j$2FD@M;*1%jp?HR9g*h^HHO90 zCb!-x=|b(iLs9}n&J(GuMHJp@nJ4I39%BihQ-%@y0Hc&`5WzgLsF zJe11hY4oJMbZXDkPR*CPR^L0rKIhK4@gD_FZaNkiS4zh-SvzWlaJbTHu~kEi%ory8 zfBtGjf&cTA@aF4N&0YFz-&}FEwC|C;StF^s0Q?wn_u&3zAbVv5S_rtnB6)w+xDH6H zrbirN?)HztI^AJbt zzj=51F;#l#^fcaDhK<-!rW&k1NTxRrY+I?GBhjyqzPPOgsN`>KQ`_5b{02NQnsrom zXeWk^JQn9b$<~+MT^u6cT?pd|fTPkzPg^S}Ez2469!y-?O)q7;Hw$J8Z&@0&R!e2o ztQc7MB$!(2dizuhH`TO;6r`R9_@vz2-ekYsobadQT7^(imzePBlTeYYHV;cw{5PG{ zyQ}e1dsqz<+5wpQnHrWUG7~UiLD_EFw!$ow{(^R%Hg@G}*%Pm|{>STW*->%v>uN9~ z?0h<@(p1zO)4G1eZ%UZW4>6^sZA%5kuYUyYnB0$zr_7h%ZAYqFr-*43V$=N`jdY58 z?n!6VlmjP3F0SGMu-zeA#G3008f)bf4Q(@mzr3%>tXHf8^fyy22TxrpM)i2CxZBS! zx-*wqe^)yD*6qbUQKz%>vxLTt37cJ{FpDrFo~vu_*kpFDUU2fAaaMrT99gYOZrLyG zI+aloJja=MtPx#mr^gp#Et+rW*ZuceHpL9*@JE2gZF;B8OTU+;e_HHV7&5HNY1gVR zuyld6YTy)oXsF^f?Wzq(WU6PjCx7WQ)w=3qAtLnp5gy+R=WQ@Fj{CHTXXm;UFUp@r z3|Of7W-V{NK3LV+noZOuNO(8c0JzL^7&;HrDvZV0W9fFnWVQIoU^_}e7 z{_9n|$31I?>66XNzx=#?YJ^yDHm~ap!oeO(GJTX%Tq~*O27Bh~Jc^Aq7!_OyS<=qE zY2F^_-Trp2nD)$FG5_4G2K^KKo!~;V+s(12#<1#hfb*_pc+AD|_t2+>S2Oev9RnZ=;)p8nGd_t(Y%K&rh3Uz5j)qEQ1 zX`kJdo1g3=+44S<(`VWQY<7wZZ&mt$T1&ULeVwv$rWg{7*32EdZK9`#xYLi=Ep2NQ zZR>g^jmHrcV(uy*a}AhY(qngL`pse)hFr%}$(=c%3xhQaqK|6LHWoEDjeO>I1Bc-b z9DzZXE;hes!ttq!ZujBCGPve#N#@a6iIxw3R`+qJm1nwL{RYwYCIbW2)JE8~QGvtl z=%;L8E`!I-CM1_(4h#Krgb&YPGfiz*GaGMR<@lu!i>^;z0z((|cH``%O@0km>6?Bl z@9LXxCr81$y6V{c)3a=icBLjUJP1}oXP3*enlszfzEEjG{`3)WP@|as@#3$pk)@cG zHhyjQ=rB`F9Ya?gX>D4$WRQ0RcowbBkX0X@I`XctY81Q9I$y2jib_LjR^hG*TL`UI zt6eVb%DLy<#Jhs(&bD4EJ(R2Auc%k`Uu6=4tMsARwE6qn!)5i@bDZ&rnWf94w@PC% zY=Uvp(Xyq=Zt3!#vlzL?wsFNDVEkgsN8xM8)DXI3m01F?$3}0qDH*zcn5t4KPq5W) z@v7Yx;v^8Vo!?~!_BY(|H!p;}C(8<14_^uKgpZ!j)2nB1bXkd<+*o$zty{-6yS0ey z8)_s-6f!x#KiAnf!1z%FGDU&ndi3tr?KaIkCo5L$JX-y4WU_EqE0yb2#&0e26+{PZMic>eLovRjx?X2n1;3 z@!7YTa%G-ZNWkGY8U8%cSx0I%YpKf-vUzD5F11JRI~C~uGW`fRZ zJR4x33Jz`R8AfIai{E~4NUUYJ=1%z%S^d!VTC-2md;V>MoNG(po?) z4}a?nmq(0DosBXC7d9io?kkSwGw;o98Qfk@4run|ck6N(#1D3GCs~v%&2T~lWMZSq ztb?8;`|<Xat&B$i`t6!=-4% zD6gvR9!?A8EqH-sNYc3kTra{3;pSzu%pHW(C_7sfJ61g{GHvpYIw!mWh=uKv&{}Rj z69KzO{&^IBF70}XcEQE9$|qr;$cH*oOoo+8>DS&bHe$fRy)J)0HSX=Ga^-B134*3^ zEHZ=-Sr*MdsBcE7^Lh#U1v*W5{)*BNs9hy@bwm5R5hN}p_jN5@HA^ZTU4&f=c5XR9 z|7Mw#e)P!lW`r{x!W~s|z3v10bfVOQ8Ayayxvq5n2|af^S=oxQwV%G5+(FCD7+pX? zTXLz;we6AXJjlm)OF;zaE>Ra&8YgE?nn@3RUICPDuT;K7 z;X53Dt{rO8V$U{8>2fft^NAo8O!OKVu3s#=L-RKryW}p?e4q7G{!cKa9TsGGGF@jo zvG}V|;R&Z_bhP`r?cPh~f%#ngVU@Z5O?~<$zCO zBgk8u4tq#W_J;){^bbna{8xo1ddyDLGKM&Eu2WLp8RY;&T4wB<@&G%P@xd&72bmoX z4>vortN*|NOe>2Tf4cmAFQ27{Utt4YyaJ7ua8XJ@HOew*)Im81d<{u720!56?lxpM zX5}62{^IitGJcJF{Ke^q?}LfBWg-9;)Blk7wmrv{A4&qP%5=ew`vX!dVh?QtDd+>d zOCWefac}!r$h*rG=FRs3c~=i-Sb%%9G!X0-RZzSW+({(FbKVMsxTC+dnCEGh_+RXQ z5`mvg!3K)0|HLyQgD8}>U`#Huh?GgfWDDKk*V7&Dt+@vvvbsSl;@R`Lrc)LtcrIAu8Yxyb8^vCdnTGsxo|Ou@mPf~OR+oj&1y~=B0%xRG+!hZ^y8`%IBSM6 zB@zwx!!+xVGAk8WPy3@`U?G+?9xL8-RF67$`sgSpdB)<+^9Ptu*sd%8X#eoAcHiNJHs2h)vI)sSW0Bh}k8t^{Vma3!I?-SR^AcNjcRRj3+P4 zPu#c-#UCUrH)D;IH=^If3*7uE2OPW8!aTOc96Vsx()TR*t@&tQh8y$3@18L4o{1gi z+WfDeD_8VZMqkLj0L)i%why?e&L)xs&l%e9z^N@4m7i!FU5Oa~?4r|MQHLf?`Iuz< zsHtSv+0rq{3MQQ$t~{K^<6nOly}I<$IB{88_*andxqd!27PWvvhENbU{8)PhqD;To zOn@YGu#~>KpEt>MEoHOvG|r#SMSHOCLJgt~vdPQy-?Y}-37Bxnv!E~Xdz8e5`tK5z zj@EQWipTb|<2fgvqufgDq-{{FT#dsIb}FUCWyyYL+X>i)HbGk0A^D}r3BLJB5o zv8`K(DT?a$dP1qq4{DdPTiN-6wa?hAs}SLrD7+Ssz>mvzw-7~tob;T;yuP;pZlc0R zj5RkI65{M%o4YCUMIon8?LVVpjBqEZ)~;U5)e8`C4qh8?e$?mpb;!QoX-RKm68RMmnh8@$|1jVNb<+Fx)qe7Zp#*`* z&Yx}p?>d#F)9;y$xe7*BJj&C>dWf&_021nkt4w=0ZWgA@1?%XdAn zNT0+Dv4J<@Vw@V>znxhbzVXK@UIl`%`y5mgHh{(Du3-asqVQ(Xav&x3Y#oHO#_|aK z>=B4j<~`k3J4i`I9c@g+A($+>83Clb)Cr8sm;_Q`Msa~tCk}1cF-HM`a^XsDuCSS_ za&fu`$^L0sgv8g%q4_jc;5wAVPXj?@+*r1+*PVO`e+}-Iq7+RoLKtl*@onCM=co#X zc~Jy-VJamIRW!SXM!2p`6;)F7CL{?+|AY;sX3T$lGj^h{cgEpj5!`iB&QcnT?nMmj zRHPARi+!3;N;h;@2zr*{T)QD%$_3uk2E46Ea$3wxM}-uC8HYMif8pSNct9Ix!o7(R z2?(PzCOlAc_-NC`}qc;KEYL!E9c*u9P2xeKhqCZJPCS{cnw@O=CMgQ&7 zqbFm&e?9_`vfrj3IFOgp)EXo>&~|moGJ;|wsjh#~_sT)4^cWBoZk4@Y|MQMUB1{uq z{nL1u+Rd&UI6_UE)}DNKiUhxke;pnK0ei*CYHN@s3cnQ|G$)!Aw`}&CZ$2hJ6nno* zW(h1&K#H(VBn*@ZLSDtY~ExRr~W% zGeaVqS7j!`&8P|!D4&uD8QOH8>Qc?b-*$!x;-MU6E}cHzd>>Psw{VvAVgZh?3b>ko zD=~n?(;oQ9L6QyBDpl7@e{Njp#KQ=_6FIk_PvDb&`wcD?V5sZHeRg}2nwDATaw?Lns8h_z<7xCTy=zawOlFvZP0g0X%(E7M?vnq>Zvl- zLYjhGiIK=6kwA{mKKXuM5d_ zR`X()15Az3Z3fquzR#5>KP2PVqi1mEey`=8ZHnAGQNsEnwA04FX{4}BBq)jtm3||{ zHbn>}qy%g)9$zK$dv$As;I2$OOdSrWA1jz3buOgbp}(~mPW`ym3BPgi%pgSb2uQ?) zz*pjaxHdtP)*84J9%c)hwzdiV1aEeU!6?QLy?a( zQyQR7lOj?`f!H&sgEj%A@gIEh*6rPi##NB(El+D!siGXTR1_%1*nbGN3@fm7F+jRT#ZpI;9TKBQt@kAUo5YWq^4{gt;f-Sie0w^s4e9hFBh#2>!u59^-E; zgjO9YgWy()cgfpB2zUCAwOp0-wz%ta=)Tu<>@C1S>A{>3FToEC^_?eyIat#97KtsB zdnIFb4-b-5Dww%@NLgIu^j>xDj!W-PG{!V4^fcTT2oB$jDcK~qC9*Jw;32`+RDK*r zRGL#+>+89C9UUD!IHHYG^O=s~PdQ4(wZEuRMWS`Vzr+2Memb}xLJ+-*lyx&X!$>`( z2mpgV9<6yT@2Qw3T<%27qZlJT3^}m zU&*^%{$MhN$V@n_7Qbc#nk9?E^Rz`mo!ZsQp)86x?X1%KRNZ=zSmVrQ$7_hN%096I z1~%fBg5tbdbSWLRyB^PHs@&Gje*`CGX4NSaj^E*0_ezy2MUkzfOnq7A$n9%kt_6ej zhb*KV1ZnE;-8#l$3r7{Y=<#i*AVkU?g<@AQatCFy0z%J0XJ)H3_wVU@%+bAGM*qRR zyLE!_aiyjYmdT&hF-m(nVN`j72M$x;*pU4#Y36tj$!B4IWk>2pSNq4LsGz+{W>xlW z*+W`z7P4vn$u#olWgW2dl>N9JTn6uBjeP}oqY9#J8?3Ky{O4n7h?w$o;E4SXE8o#t z=e_%X*37*%ibnLau*+wHR3^aDD%$hEP88_RaV=Z`od3%3RsEXhS;PCy|4s2Xa-R*-B(9Q*s0$J8W5+5y)=E!=^&3V% z7C&YgC7eZ{5apfs5*9K^CKUeU%-no9^LrtLqy7eVi#)Vy4H5-oTAo4qrpP!sV>8z| zGpB$l8<+4#!VsNiLSa~*ORl`7O;Gbj#W&6o9&z;_LZ5_Nj{hh9oacYZn5Z}*KncN$ zzCi^Jnq%;>M(gB&R}RSDBZ|sMSjyoWv4ce`9_?Ywjfr{^9t_kuMfpL}c0u7I**oGQ ze@s#JozC6xERdm`Edb@XX9nD?t2#;77} zVZ$Mmu&7hjPH1s|KnP-naBY2X|Lt{s%|SKJdv(tHA%zFWL49Mm;e^6=M91!jI;gM| z;pQ1vc9($j5zn;!>HRwi%W$K>TtdPl(AsS6ho)6d=AYJ;WKwWExzcdWvZnoLjxAY-g8Yhad}fR?}|f8jifT_Me1|^OlTp%+`kY!KBr^l`vftyzL2^6 zG+53hXVqmRYeeT%8KNVw-PQ)9j)Gy<;v2oq$8V|ywpxw-77InB(JE`^42{_DsID&O z*OXwfcl98QBE`qRkReV-oM!D+%3ot-aIy}()CKSlXh*w<@sHjJr0Z!B2mQwWGM!ze z=)6>&pVeeI8Kt{=#rY&m=$0ai^nIaP_wJ83Ok+3=DMPdUAiG@I0`1zS zA&JSCsH`fi4R*@l3KQ43TwjqvQp0R308klm*k%D}`UeP8OD17S4!>d3H+{kWUEyqd z6#N4mr%YNNnu7nmNWzTmt7(^t#N1#&*_mz?TooIZ>VenQ8-VGK0`^RO!K`7}PUwl7Iek>4H(MOuEo1ZA;B9|})~hNgPga$b z_zS!it_c8>m_^w%1(dHbsD6vFU&i~&i|?@fwX$-#($HMlMcOf4=m+}ITvXV$mpw24DwbZZHQNoa$&QXcb^{_HxGK6U&X)4d?!Q1_etmnnwo#9t6iT(Q&sF z=T~%#1YBGL8mncHTe4cJm&okj(qF33k4dSZ`!CS#(Ey^j$L=Jw^K*upv0|8J&d*(y zp|xat>O{LL=Ylh*PgnSrTVNJi*ix8m-cim?ZmZ*3DrwO11znNL*e-T_)}Oi}Yv{YGQupXsR8F^46Pb5k-lUC)i(i-7mButH3Rw ze;v~Kk2Dq5BkK{b1388rmhF}V0WQW8>eR*pr=-|Y3u#57WQt*>_lOzXkFpcKSDe+<$rV9buf8P-~7q5Z+s2l%COHmG;; zCFp#-p?3!23QY8CvutcfCK1OJzr^ z*c-ibdm*_wY=}I!rM&ZQ>p#(GS3RG(GLP~O*GHHlrrIEJjWpizTG_VoMLHR3Hq;xM z{skzUyi{Izr5tj*is(P2`U1LVGI${8R|jKQuOb8qu5SS;Q0@oMF3ooC%gt<~?|lN# zzHfxZpCD8NpKg#Zc~TImvlP6>T~LL6L|5Vr>tognUs~U)zdnes9yKi=EAHJx7UV8s zB_;c#K`$I_-K&L2L(?x_Ms(Ku>ZDF~uhHe$H(b2qu-tEn=eLYjshUTNJ*8;KV~c&o z!GwnJXxD1}a81CY&^u9^kB~qu*WgTZioVKjkG}+FAc7zgidHae7X_Aa9rIFPiPZ%H zmSxS37yI2#FX%8B@byv?>>E+mW%*Qb6_?UcY7(465;)ZtDiQgU#L3eD3a=C1U3?1r z$?3p9iA@y$?#?}UI~QlVyN8m!{a_#u1=^uKnwu>b@iMz7lu2FPl#c2jRgDnTFXzwoRl_|QcSQK~pd|E@kq2tZHv1fhXyxyF5;dR# z+_*CqIwlqPn}ABqkz8E>a?~T5Nxp<;4c$rfdrD|+=^2JEK2#9Tmk@9&A7jYjPiM59 z4D!Q|md{NB%rb^UD~P8w5mR7KlIndgjmZ`P!tpjFHE7{9v1m6&8V{lsr4X&?itk^1 zR-V+oQI($ul*G`i5hmH0@>n6>>6JJzsGzb^R=>o^-u--y8qQX%nQq&=y3_bTDZPj+ zqoGk2$It+obw_Z67P}$oD@np?4*>>Z)WvwNGr?@U7KB-f0{|xExb(LtO|Jg48r$3y W5lu8>Y1$O|6p`Ic^4Ep|2m2p#g6O3H literal 0 HcmV?d00001 diff --git a/test/bun.js/zlib.test.js b/test/js/node/zlib/zlib.test.js similarity index 100% rename from test/bun.js/zlib.test.js rename to test/js/node/zlib/zlib.test.js diff --git a/test/bun.js/third-party/body-parser-test/bun.lockb b/test/js/third_party/body-parser/bun.lockb similarity index 100% rename from test/bun.js/third-party/body-parser-test/bun.lockb rename to test/js/third_party/body-parser/bun.lockb diff --git a/test/bun.js/third-party/body-parser-test/express-body-parser-test.test.ts b/test/js/third_party/body-parser/express-body-parser-test.test.ts similarity index 100% rename from test/bun.js/third-party/body-parser-test/express-body-parser-test.test.ts rename to test/js/third_party/body-parser/express-body-parser-test.test.ts diff --git a/test/bun.js/third-party/body-parser-test/package.json b/test/js/third_party/body-parser/package.json similarity index 100% rename from test/bun.js/third-party/body-parser-test/package.json rename to test/js/third_party/body-parser/package.json diff --git a/test/js/third_party/esbuild/bun.lockb b/test/js/third_party/esbuild/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..f46c6aa5dae1649e0176cf623fe7138146213cb9 GIT binary patch literal 8425 zcmeI1dsvLy8o;M~U8M`s?A=u^NxDqwDpARGOXRMorkPCUl9}nsIi?$l_9;>>9fWSD zwxTGzt*zt|LQ07wY*Z=HXGv*ZW)VTHm^TZ{x;V zhVr99%iK~Pa=^PJ`&E?-ye8r@WLw1 z^V#Q011iIk<~9a)W*y$#Mh*5gTxA3*fkYMyk}@upa4Ep$;ZgVlA!xjSPzE!Q)FDSA zjU&r{d)cfgA(sJK}j`Aay`q4KIHOT7=vuKtgUk zkg&a9@q8YT&^`@FST7n#Z6H~=eg&>~$MZmllIFPS?%_(WO6`8Qb~dm z>b1-L0>jP6mhG;5l9D@0XU^N*)4Ik5C+$y2zzWu{({hS38H)W6BxV$s;G($nAKXS(mA)Up=W~QT}X(FL#DX#*w_g2lpsXxOFduD9KXU@b9`I#ehW_27q9(KvfZU4GbMGg6bx>i5ecTiW82L~xjuGrC*xZE_; z)7Q$mtmKtGyPZ`fSNt||q)_qL$ycTW)CYQjNqIq+`pPy(rRF};r3n;M`_mVC`?hrc zcJVZACU3Jr_oeg|%gF{Wx3KFhUeiF&;jDmZIQTh$ae~iU;0N=6CL#Ox;Qe^WU_TRp z=Ez{b4cO0SEjDJ3` zm!w>@B<4z^v17xO4@@4<^6Spf z=-Di!x{WXlNQzbOI+s>pqTb(>%k7ANk^O2;{QJ(eKgH}hP}S<$-e0>Q-pBGp^n{I9 zq8q}1kASBv01}Lq<&Y`DjhJJotevhUApKcdsUyies6 znXh&I(#a&Pu??N{S4}1-e9wxwB;}bvf^CyXSknN-)8zc{#&nkN3CG?8j+ZijQ<=N{ zwwcR$)5HOrqvwUS3w*AxKK0K^M#TM9HS&Mkv&Jqirmx}UnMs3xKVPlk0!VOtjerE( z8IdqA@|y>{H@2+~s;F}rUfuZpq0O0d<+xOOXu^aiPO7|l(T3W~@)l3io^?j(cFmZY zch56eP5Gyiin!}z3V8mOp8yGRArNd6M8Y1@1zWq`ZMpCNV8G$N*X*#nw(d{fFP*(E z>%!KOhbs;$V`b%!S3J{bci!Q#Fw)o|^-bRn3r}08n01(Ur242A!GHw0nn0qPK7?Zh zH(tD)M=SrHTe38JS!2~EpOdCaTQ)tU8>QGC?&C1OchT^;z1o~BY;f3av+;e>;A+21 zGs(9tPCt{AzuDp0n=axe$w$3< zRd4-_ebl>ojH8==h-g1?C+%5ozI$Sh$&oHzd&M(K+M&s5d4L1_B|)w#&V&N9vhH;) z_jq61KHk_?bxP3lN^@bQQ*;M?>s5`&qOv-jd5mY~1FTLAZOr$LC z2{+uBZM|iZ6=2ALSq8b%_g+k)nU|2Nv8KOvldE&&4ke|eiGCgI1?>$}dXMf|zwMfX z*}LS$KJWb~zCp<+6vOsDuTuFbC8e6|e%G|&*O&b})I?mdzwj9)EF@^FZ389mACpZ~ zRvLOO>MXl#JXWuu*3s~c{V9XS>PJsCmT#PODe8l~`pS#Kz0(VtV?1nilia(;S(7o# zV0rLeLyW@+1O*aXKOrmstY%l!^p%e7#~Izw*Zi`|()fD#8Gbvno%2p_|G%`2tpNzG3%DlWw-8EL z!hpfS18#kmreW`t+}_ma*xN>WYjv}-u)+FKnFrzxB8o~C9y5+Tu=3n_^m=Af@%1(v z;j!brA00u`fYXOT-}VdOtozW^lHW6{puaf zMLoYZV{YkPMzI+?g$G*VxIWK&{F&ixeZiHlvh8aQ`nlc)BcU`QfZu-tu!7=ykgudB#vPH|*iBnv>bf*{cgk&|mlme47&qYw3Hdm(Ns-zvx;W zGx>Jxhn9-{N)9=JFOCK$3NjzR+4eCiqG-&fTzB`>txFG)=FcbjTzGx|l)bV^@WJA% z+_w}F7kCS}@GgWB=Ef}x7d~7zZO`Mu#Wv9mNmq5#lWsW3H{6?6c_lLccsA>Gam$nu zFK@qYb|%|-kS!EcYPg|OpS2_wKJ5#e*Nx^G7CWfwt4eLn;AY8+NZen zrYf(y+M-=ry)UiItNT%ZF8LyJB~#UvdoE@dYWwrK&TJOSNFId3=8+(*lL*ES7RbEu8A3o!j12$)-<8f!b> zPzNxucEp%aJ|9@L&QSbMp12sh10wKFfFf;}oRtN|-hx6QtpFO}zW~MJBpMcOgQ9g3 zO*GH|_XH@OC()z<4Hy9diug%1@V*1vK`}vz21Pqj)KH?S2R2|V1t_j4(L4bf*dG*m zlxR>)6~!hc8WeR!(MyR2#br@^Q=&nUTNDA6Xi#hy#YiO@6#Yd}REY+~hf$nWqCpX4 z6p58+P>dPHY9$&Jg+|d_i3Y`~QM^~8L6K|}5te9BtQ*CQC7L#T1|s?Ss^`&IVKlCV zM+Be7W&1O|`CJA&$i;{vnWPTHB-O-2@r#YX>jMd_Eij)czc-ypqJIR4WAW?x{1Bd# zr6rdZ#NhF{krwO_RxrZ3Y|Tc{_H?5hY@J*&t?6mGP|i? zl}1RF1^RQt8Ek0$b)j@UI@tOGc%oLMDxy;9noI^eG~zRFh!QDFaT&wk#+qtBUzPG~9IYb0u$K0kP2cz$Rwe33}@q@iKZA`x?jv`7e3qGJH_t=>rO$^avk8!8BO z455b9HT { const { exitCode, stderr, stdout } = spawnSync([bunExe(), import.meta.dir + "/esbuild-test.js"], { diff --git a/test/bun.js/esbuild-test.js b/test/js/third_party/esbuild/esbuild-test.js similarity index 100% rename from test/bun.js/esbuild-test.js rename to test/js/third_party/esbuild/esbuild-test.js diff --git a/test/js/third_party/esbuild/package.json b/test/js/third_party/esbuild/package.json new file mode 100644 index 000000000000..ad6bc55f6e86 --- /dev/null +++ b/test/js/third_party/esbuild/package.json @@ -0,0 +1,6 @@ +{ + "type": "module", + "dependencies": { + "esbuild": "^0.17.11" + } +} \ No newline at end of file diff --git a/test/bun.js/third-party/napi_create_external/bun.lockb b/test/js/third_party/napi_create_external/bun.lockb similarity index 100% rename from test/bun.js/third-party/napi_create_external/bun.lockb rename to test/js/third_party/napi_create_external/bun.lockb diff --git a/test/bun.js/third-party/napi_create_external/napi-create-external.test.ts b/test/js/third_party/napi_create_external/napi-create-external.test.ts similarity index 99% rename from test/bun.js/third-party/napi_create_external/napi-create-external.test.ts rename to test/js/third_party/napi_create_external/napi-create-external.test.ts index ca7e50b991fb..c3fe5ad65bde 100644 --- a/test/bun.js/third-party/napi_create_external/napi-create-external.test.ts +++ b/test/js/third_party/napi_create_external/napi-create-external.test.ts @@ -1,5 +1,5 @@ import { test, it, describe, expect } from "bun:test"; -import { withoutAggressiveGC } from "gc"; +import { withoutAggressiveGC } from "harness"; import * as _ from "lodash"; function rebase(str, inBase, outBase) { diff --git a/test/bun.js/third-party/napi_create_external/package.json b/test/js/third_party/napi_create_external/package.json similarity index 93% rename from test/bun.js/third-party/napi_create_external/package.json rename to test/js/third_party/napi_create_external/package.json index 82b0ef58d62a..659b279b2125 100644 --- a/test/bun.js/third-party/napi_create_external/package.json +++ b/test/js/third_party/napi_create_external/package.json @@ -1,5 +1,6 @@ { "name": "napi-create-external-test", + "type": "module", "version": "1.0.0", "description": "Test for napi_create_external", "dependencies": { diff --git a/test/bun.js/react-dom-server.bun.cjs b/test/js/third_party/react-dom/react-dom-server.bun.cjs similarity index 100% rename from test/bun.js/react-dom-server.bun.cjs rename to test/js/third_party/react-dom/react-dom-server.bun.cjs diff --git a/test/bun.js/react-dom.test.tsx b/test/js/third_party/react-dom/react-dom.test.tsx similarity index 99% rename from test/bun.js/react-dom.test.tsx rename to test/js/third_party/react-dom/react-dom.test.tsx index b484982e061c..2f1309fb8a47 100644 --- a/test/bun.js/react-dom.test.tsx +++ b/test/js/third_party/react-dom/react-dom.test.tsx @@ -9,7 +9,7 @@ import { import { heapStats } from "bun:jsc"; import { describe, expect, it } from "bun:test"; import { renderToReadableStream as renderToReadableStreamBrowser } from "react-dom/server.browser"; -import { gc } from "./gc"; +import { gc } from "harness"; import { renderToReadableStream as renderToReadableStreamBun } from "./react-dom-server.bun.cjs"; import React from "react"; diff --git a/test/bun.js/bun-loader-svelte/bun-loader-svelte.ts b/test/js/third_party/svelte/bun-loader-svelte.ts similarity index 100% rename from test/bun.js/bun-loader-svelte/bun-loader-svelte.ts rename to test/js/third_party/svelte/bun-loader-svelte.ts diff --git a/test/js/third_party/svelte/hello.svelte b/test/js/third_party/svelte/hello.svelte new file mode 100644 index 000000000000..05dac4294083 --- /dev/null +++ b/test/js/third_party/svelte/hello.svelte @@ -0,0 +1,5 @@ + + +

Hello {name}!

diff --git a/test/bun.js/bun-loader-svelte/package.json b/test/js/third_party/svelte/package.json similarity index 100% rename from test/bun.js/bun-loader-svelte/package.json rename to test/js/third_party/svelte/package.json diff --git a/test/js/third_party/svelte/svelte.test.ts b/test/js/third_party/svelte/svelte.test.ts new file mode 100644 index 000000000000..44e36cce2729 --- /dev/null +++ b/test/js/third_party/svelte/svelte.test.ts @@ -0,0 +1,21 @@ +import { describe, expect, it } from "bun:test"; +import "./bun-loader-svelte"; + +describe("require", () => { + it("SSRs `

Hello world!

` with Svelte", () => { + const { default: App } = require("./hello.svelte"); + const { html } = App.render(); + + expect(html).toBe("

Hello world!

"); + }); +}); + +describe("dynamic import", () => { + it("SSRs `

Hello world!

` with Svelte", async () => { + const { default: App }: any = await import("./hello.svelte"); + + const { html } = App.render(); + + expect(html).toBe("

Hello world!

"); + }); +}); diff --git a/test/bun.js/abort-signal-timeout.test.js b/test/js/web/abort/abort-signal-timeout.test.js similarity index 100% rename from test/bun.js/abort-signal-timeout.test.js rename to test/js/web/abort/abort-signal-timeout.test.js diff --git a/test/bun.js/console/console-log.expected.txt b/test/js/web/console/console-log.expected.txt similarity index 100% rename from test/bun.js/console/console-log.expected.txt rename to test/js/web/console/console-log.expected.txt diff --git a/test/bun.js/console/console-log.js b/test/js/web/console/console-log.js similarity index 100% rename from test/bun.js/console/console-log.js rename to test/js/web/console/console-log.js diff --git a/test/bun.js/console/console-log.test.ts b/test/js/web/console/console-log.test.ts similarity index 94% rename from test/bun.js/console/console-log.test.ts rename to test/js/web/console/console-log.test.ts index 7fd9c79e5727..98c8370def5b 100644 --- a/test/bun.js/console/console-log.test.ts +++ b/test/js/web/console/console-log.test.ts @@ -1,6 +1,6 @@ import { file, spawn } from "bun"; import { expect, it } from "bun:test"; -import { bunExe } from "bunExe"; +import { bunExe } from "harness"; it("should log to console correctly", async () => { const { stdout, stderr, exited } = spawn({ diff --git a/test/bun.js/web-crypto.test.ts b/test/js/web/crypto/web-crypto.test.ts similarity index 100% rename from test/bun.js/web-crypto.test.ts rename to test/js/web/crypto/web-crypto.test.ts diff --git a/test/bun.js/text-decoder.test.js b/test/js/web/encoding/text-decoder.test.js similarity index 99% rename from test/bun.js/text-decoder.test.js rename to test/js/web/encoding/text-decoder.test.js index da04974641c0..abd4c2a72f0c 100644 --- a/test/bun.js/text-decoder.test.js +++ b/test/js/web/encoding/text-decoder.test.js @@ -1,5 +1,5 @@ import { expect, it, describe } from "bun:test"; -import { gc as gcTrace, withoutAggressiveGC } from "./gc"; +import { gc as gcTrace, withoutAggressiveGC } from "harness"; const getByteLength = str => { // returns the byte length of an utf8 string diff --git a/test/bun.js/text-encoder.test.js b/test/js/web/encoding/text-encoder.test.js similarity index 99% rename from test/bun.js/text-encoder.test.js rename to test/js/web/encoding/text-encoder.test.js index a18c22618f51..3d271026daac 100644 --- a/test/bun.js/text-encoder.test.js +++ b/test/js/web/encoding/text-encoder.test.js @@ -1,5 +1,5 @@ import { expect, it, describe } from "bun:test"; -import { gc as gcTrace, withoutAggressiveGC } from "./gc"; +import { gc as gcTrace, withoutAggressiveGC } from "harness"; const getByteLength = str => { // returns the byte length of an utf8 string diff --git a/test/bun.js/utf8-encoding-fixture.bin b/test/js/web/encoding/utf8-encoding-fixture.bin similarity index 100% rename from test/bun.js/utf8-encoding-fixture.bin rename to test/js/web/encoding/utf8-encoding-fixture.bin diff --git a/test/bun.js/body-mixin-errors.test.ts b/test/js/web/fetch/body-mixin-errors.test.ts similarity index 100% rename from test/bun.js/body-mixin-errors.test.ts rename to test/js/web/fetch/body-mixin-errors.test.ts diff --git a/test/bun.js/body-stream.test.ts b/test/js/web/fetch/body-stream.test.ts similarity index 100% rename from test/bun.js/body-stream.test.ts rename to test/js/web/fetch/body-stream.test.ts diff --git a/test/bun.js/fetch-gzip.test.ts b/test/js/web/fetch/fetch-gzip.test.ts similarity index 99% rename from test/bun.js/fetch-gzip.test.ts rename to test/js/web/fetch/fetch-gzip.test.ts index eee841375887..01eedc54ac91 100644 --- a/test/bun.js/fetch-gzip.test.ts +++ b/test/js/web/fetch/fetch-gzip.test.ts @@ -1,7 +1,7 @@ import { concatArrayBuffers } from "bun"; import { it, describe, expect } from "bun:test"; import fs from "fs"; -import { gc, gcTick } from "./gc"; +import { gc, gcTick } from "harness"; it("fetch() with a buffered gzip response works (one chunk)", async () => { var server = Bun.serve({ diff --git a/test/js/web/fetch/fetch.js.txt b/test/js/web/fetch/fetch.js.txt new file mode 100644 index 000000000000..5a9b52fcf1f9 --- /dev/null +++ b/test/js/web/fetch/fetch.js.txt @@ -0,0 +1,46 @@ + + + + Example Domain + + + + + + + + +
+

Example Domain

+

This domain is for use in illustrative examples in documents. You may use this + domain in literature without prior coordination or asking for permission.

+

More information...

+
+ + diff --git a/test/bun.js/fetch.test.ts b/test/js/web/fetch/fetch.test.ts similarity index 99% rename from test/bun.js/fetch.test.ts rename to test/js/web/fetch/fetch.test.ts index f5f264dd7519..1185dbd55fab 100644 --- a/test/bun.js/fetch.test.ts +++ b/test/js/web/fetch/fetch.test.ts @@ -4,7 +4,7 @@ import { chmodSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSy import { mkfifo } from "mkfifo"; import { tmpdir } from "os"; import { join } from "path"; -import { gc, withoutAggressiveGC } from "./gc"; +import { gc, withoutAggressiveGC } from "harness"; const tmp_dir = mkdtempSync(join(realpathSync(tmpdir()), "fetch.test")); diff --git a/test/bun.js/fetch_headers.test.js b/test/js/web/fetch/fetch_headers.test.js similarity index 100% rename from test/bun.js/fetch_headers.test.js rename to test/js/web/fetch/fetch_headers.test.js diff --git a/test/bun.js/fixture.html b/test/js/web/fetch/fixture.html similarity index 100% rename from test/bun.js/fixture.html rename to test/js/web/fetch/fixture.html diff --git a/test/js/web/fetch/fixture.html.gz b/test/js/web/fetch/fixture.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..0bb85d4cbd6c73a189d1bf1ccb2093866940ef69 GIT binary patch literal 16139 zcmbW8(^n-98-;U|ZQHgzO_OcgwrjF$vTN#O+qP}n?u1j{?{D~S-nI7ayVkzg&sq;j z%#RUsM%q2Sq_ z#x1>HZjeyo1IeuEc{`4~#;^DhxneXqT3#Rn~ ze)OuUzJ=Rg`<0Jk%7%bw(nB0zhmu`za%qS)-Ak)ht!|X1soFxIDh~EU>sn<+ReHGf zDDC*1y;Q`At$GV!*X&q5^)K(6vqX7^O=SpnE2Wrf4;z`1?4lNZX*UR)gWAw;c?FZC zZQwUREe5@;-e~=Afwi-G0zrrP@*mAYip-0J(y3I3N`zyuG_ZF6(PG1t84B2{F&_HJ z>?*Azy5%|38-J0!hSDvi7P_+)mEqb7hxIuIsx$VN!`qkqu;B-uicWp{nJ@`Q4ygvF zi|?=6@3W#sYaF%vxTQKNWFb#8~BvRk9wHR~++@$6VCpfarAe7HdIaYb877c{H{j1Pq zqlq5a{irY_hwmb|6Un!j4piz1J~0=9E0X+;mxcFaIS6g^6;A75Cx)mJVYmUOs58C*SDa~jc0#||0s%Gp zaG>deaYkDAb1b5iKO53u>DR?qQJI(C5|s5<*t=v*lc;4N!j?9jS19%Z{9&&#SS^j0 zZO(KQ-91o4PnAZM0Dp`wGetWvG^~(dMFosQ2xcds&;)B4v#f}Wp`L)=1Ep=xa#Slb zS)5r_u~dv%y$nV$#;}qfb(Ed6AA9eVxTu#voAuOrp4D8*dLk!sz%s4Wk@l42E4&Ef zN*#>J_z)P!f;o8Chx09OYr8%%AhianS8*(BtJAZI_WcN_Cw;{aSt9FYn8Tmfpbi&* zt_ZT)L`B+cDg`eb(g?`-iBGR-k9!DjE)v!#>%}2QbFsW-KaFFOcv!z z#Zs9FA#j#uTdpUWFE_3bIK%1MQAspc&-W|jRn-P=c7@bKi1uKYDfCdtK|wDeXOzKkOs#}k(EO~md&!%((|*vP4}QE${`t5)q>Cz zSk;DyzQiNR*h!bqj72!$`2De*NtSe|VztZIGkkwTWLFUug5cH>A?mopN=Ek;WtY=R zE#YqO;xot$cjxsAX%%Ix-xZ}n7y`fK&I|{yM=OXtbH~Bcs&r)K!S3;FqZRjQ1|ATG zOFy|q!9q(%gQ*ZMC!^+BpzeyLK`_U_zkVN82@-Lju*x5HG^O+(bzz3iqKG%^LyqFH zrhG&ZK4Nfqwj=z=tfhUwq!RE$NBJT$eTC@>&cMXA5yW8=0SJFzio}RGH|^c*2IAG; zFh;C2l%_O-(v9d=rpJ?jtUI0;Zg?$`nzy21k*ze^;6+!k`K9N_Nm6oa15l~=k@3!( zS}<37bNCTgB;k-o2aA&wF^P2=9G&0uaELoK~Ht zh`Gv6Kz!yfiydanjvNcRzutpP)k!iks6QGz3-OvqBrh7Rq?Dq6=$drfug>M%^i5zP z3PlU&>R_zYz-ITAe~i+LRGwCj&(zVMoK5&)MVV31C2nD&3AJxQDKu#IV-R8Z$i(p$ zpe~uwO{3(|UYOBjNVp|BiD~9;j%Ik)dzlm=jQ;hbdCfMDhtSk@fD)REFNlATgH9(( zROn1n!V1gwVor;H|6@lP$d)^!g!cv1&`EPG5@i$2|J8`r%3-RbBs$m-RR(Az=LR!ACU-Qg zg_m`9!&KmF8&xJu|NYNpJ^^Avk3KuH;}S7dmcnSax(wA@5IIVll?lFd3mgn}tH4lu zp!da5Sq0`de$W26(}`C2tc->law`gvA_PCoo>p;zgLi8Y=Pjp$>WO&&EZPZqVSniY zCWz*fd5;BCHE(#Av+T)B?jgC-wDagSItw0H+$!wwGr6U{!aDEP&0-nq@n1|F*v}v> zRWa@rQEew;y4TyJpZy9%-n}LJ?V76d zV~j^KI4RE}(r4ljF^7o6ZQK;T64R-Eb&qcI91R}Gd zqq{Yb*E1t_dem|I8|U9P70$_R(Z)AY&lS-jZm_bt_WMIsE@4cakFj7Z!!S-8DV`46 zN&AGx)pP$TiA|6UC}OwaCt}d(rI$L!ea4iDS^*@7=L&PoX4}>RW0Ym+u>~%hYZWl4 za2LH)$zavkYCJK9AAyJHI$Pu^Rr_!$lZTN7X)K^cTVGDUMNXRLy=0?rM1FvTLzYN< zJBt0`&crkhASHXS5sj`t0DemB4tG{nCTzD}GTU{v3o7&Sv0wTz4!l~3jRMzV#u*!P zYDVMt($Ma@J2!JEP-UF7w3RJ6rDZ%zr{N*JSKt2RW(4X_JmY+)Vs-YSH!Qs=EqmOgFmG0$k+q% zlNW!upGam4DI${v+#M7vyoDfX-D5R1R!|G@!C@5!->R{=g)HlPY7(9sX8WcoAFgxh9`oKVIAd(yk{s{h1ZD?q5)=_o5ceMsnUtrcFZc zj`yrW=t0hDuP+u*?-QA8g<)%iQA6b%Ckm+}H4Osu>m_u@Dg#$?ze7tq5FWZBKv&XC zNB&rH(K4c&Dw$fY&uI&;ku9ifBp;mgo2e~4WlVw1kBu9~Grj0FS@ zW3e#2s2c$5J&9fZ-RdU*A)^rT2KvSb7zlE3zY{5<>%fn|rP6275${yp4x!d(piD+z ze0znxbqfpHQL7MnYv=3dPy(%2gg>L0rYDEQ{Ry!m7Wh63F@(l{^~lH?kRb*~u+_UH= zg^+qaC@(Wbx+y!~v%m(j!TyTriDn~_R5^YL)5+YhjE%|FnfUk2-@e1xv@6-KvQVB4 z_@EZpz)mYhuWotzs;IzLIWTHW_GqHtYPBvm>ssN)T!6;&l`A7$x5njP9Km8cht4Eko4lmZ@awRvoqRY9S+O0)J5`GlxZZBE7cI?UCqS4?+EwYZW>qD1P45WW(S(}yO*?9EzLU&@E@Kthn8fkt6%-wDhIla zD-0m?;1q%;xuqbPJ5R9p+zBpM6hc~r+6?u%EM0LA1Fke86~)!34MetOFpTFW)K8mN z=Rtb@S@tV~r!C6ei;1nn`vunVZ5x$>d#A7gJYd3mGu(;stjGltI3f{x-D<}Lfdw+5 z_dTx#{utH^OuBK^61Ug`U;&&K$Sao6unRbt_Y)Y&VVXrm4 zb#n_G+&Vvm0dd-RUZmO|isYW&iQ`dmoL*g!+RCbg3s55+kbE1#jF8)+d6ur7>$#zX zp@cI!qMaUvaDJn`%!GUWLpC$`qJtiAM;_2lT0xD__+)Z0M_kZN$AVcXOBa#{8(m(j zlj=CdC0?xxn(Jf&9hb8|b1!ToUy+PS30$D93##|N%%Hf9){Hd~l*iIfJu&;MtPO^@L z)7aLqJ`hfoq?Z_0yAoOjkWb-bLv~{!3gkq4o}sXKQ2yaxYIV{?f96HIz|ye?f?vFm z_q3+TCl3Fd$wKaX5YPfPOS4SGu!RCSvDn2@foxmE_XRXA-ibngW$?Y*!2RYNu5`B9$GmJIowgOlS%6SJvpS-W zE;f-KPCw7gFbFNtCvfJ>;@u##gWYWVIMDA(qS@P}@F4Ve3Xvd)!g?s%b}8O>&NP^C zBEXnKp%<{9tCd7I;T~+RMf7zy*U0R-4hVusfS<@`5!?y=x6|IC;k$$zUot-JvyA5L2cAtbl)`HtAH{B^;nVA5G6=)RFXNOteY-a6ap znfa-zL4)wu4;207upR8u*^oVDa=@Q=*Iae8xzdn{sd1GUL5qJ}15mtATZev5+PHG- zJprb3yHQ~XD!&l#2lQEle2A?N$azSx63l=y;ke?scUh+*gq_e|rmHMma}L3At^pW< zQ$c=qQ6ig{M8Nig4Fa0mgDLcG@)rx_Lug(2Cn#-qKi)O5J*GL6fucZ%nToPn-B{VI zIgux^;%_Ki(%k|K`ib9}u$j9>abA=!6_$KGZV>tYf3=%ntpXXJq=0(Gn|Wd)Yk=b| z27y&s^p|vq^^K1AUuFZ(_hsp66ae))p|f3?Ue;2Wcc$>C7)~M|#GegjUdR7x@*r_A62U9~q=eq?>W1 zzEro%ddk~wgzpx4>&4)yI|2>wV1<;hgsPL;@+rl<1s(G=vc7=!hj z*Wp#WqyO!SErx%G_i4D#@rh#QmAoDYaSyJ31OE)ZLmQ$q)(66)hA$M@Bzr)bJ;cDv z4BR0Nh@furVhk2xGX8{WWZ%JT7akf_4;17gYG`3*a*1uT z0@qLYv}A3`x;C|=8G+ynU&HwHca3gPtUn>g@RH!OQ)R$j{SQ;&a0uXxCyYfC{^}n)dnpE=}^|Vf##8lvp(F z#44}?_=;*sGJ=X1h}7Q-mfMQm@|FECN-v$tuw8944J!vmGQjyNa1 zfeN}r-?#=NzI?hI=`tFP(jW14eS*Z*49>rN_X1<19wHWHSg!>nebN37K<3<-L_YhO z=V_;jcO*^Tf_ZiuLx%c_z47a*aj$po<_WoDK}tk<1ZZSaf92&VX zPibPIK+5c{VPZ|Qn|EOJozsl1?z8BeMa>VbPx8zknl6c+Z)14;z%A@t)yy7R7@|2Q zBTm4l8979qbo=E{?|sE`%|GJ3=U7h^#nVYka#uc}CG#28q9H*L?kqHi{s_(zQJ$Ou zJ<=2kjHPM_;oo1Si=1S$w1+@UjJl?Xf~+WqM10Z}YTC^)rC@{keI4R2Y8T_5m=QDt zoi_!Z$9aHbzIfZWVr7*sU7$JOYRq%d_DF&tk8`tmxMDK+>+0xNc+N-l_(NR31)}M&3;dH)CD;`82t284a{Md)*s9ew@zRnqouZpStmbP?Z;)R#?ar&E z_&u*70n$&mX1&HEvBjVO%QZ*S@Bk?Ho^i-{%Afv>JhVk~iRnOI$PkDtz{00gm)<(7SL?IAm%zqLv5o@FGZYJ+U_{z!~JZX||=8-&^ zfaskmK-Le}x=#bKjb`DMJvXak8Z!+H`6CTecH~OFC${bxF(ctunNgs!^0Ps;n|b&@ z_Ke%=ksufd?jpHeCVt-Shk^Dp9#*nu7)vz1gj?7Z*$`R#QCa8U)Q-s1HdWR4LieV! z1Gb1VOyrGT_Re({?Go}AkYx3GUin2=Bzc=!U{fUi@}1cFJ2Oe7m&tqv-p7h#h`pRC zs8Ib_^$a#`@U_yjv^w_>lquPQS3mvQxTPVqeTk%CHridU)yjM@{@=v{b@OWnzXax@ z0*Piuz&o|AjWFGEY_o~UwXfCoOl598pn|yLI5pgiMfCH!do;<{zNk>|XR0S=d79`~ zDMY+iwKdrY-IQOy0cd{yOvyQ1rwfs7^`?5dW1`B@>TAW1+-I;OIQqG zLry0lbvh+c2`Vs?WJ`^?sj?pXUG1u{FU&!RG?4~wZhCeFmxDsXoC}>g1C9#wJ^nUZ z#D^1$dS|av?Pu4Oo3*>Epd5YJ#&K74G>yV7`;AQ&Wf;1tU9<;pDqyqOuU*%A+J_C1 zDQT7XJF67yi`^t(O_&gGvP|5aHR>GERq`QFBAZkxzg}*-a{WN*(?0|Fst$%+gue!H zwODWDe7`HMf4!Zu%R{74vPYqaTR;&L^{VwjKh4V@`8*#5Q5bTkB=o&o6v4o-LMXTF zJL2mXKOh_?YhK)mm@j}S3{J09=y73GvgcKlpaJiYmB6b3600gLcxjry)PmKFb5Qo&M(~zhV9OWI7pLd8tPzamd#0($1nM7m~J}1z&e@BHz>&paQKMU?= z{O(`In2cEZU-GfBgB-uMZ15Ko@qGn{DM^Y^TpvM)>1A`h<a_h4w}^`!#YRdjo+^37!GK`%Ar4 z+hyc+@aZeIf%%c5h@`OR(f7c%X;G%?Lcvk!0TMZBVLS`aDd1^ zL^EmSoOMTRK|`BzU(TicX#<=MG@~WVuB1t#OW1gc`D*9Gti22i9*bo5%{;> zUPMpMkNoc$pY>~zKy{w$-i{rx1+xMq7vofBH@x&m1Q4jR$IsiBUQ7UexuA-_b08M6we^I z&T7Pp=T;>eqeW2vl7_v@6&RlNzB^-`S&V7JsX7WohxX$M#`h-F8B@ul3@FPVJ2cF- zWZdM0cmW~xVBN`mJN*mjXMSpoplL{wksG3H>9{Mli3B=?^8Ogcx%KL#@H14mg$^(x z@I)4SQB+7dT&hdk5n&YLw?>e4NIjGc3I1%eUEg~U9fTDfuz0!Oq6{^{_7OoffQA zEcw`Y#=SnwPAg>BoX9g!mFRjER=4`df#+?2MKug+pwyJh0alrxZfU}%B7IYj&6~Gq z&%yxZpPc92WzRB>TIE>$Z>RFT`crZiDPJdKTuJwX857kz{}y&|>DoljExc+38+l`D zwV9fi8=^Jjy%c&3T5AO!m5td zJPt0u2Q=7o8tB?|cp2VclZ=K5W&rf48X4rHpT$cxEpMU|CZ{?oBL{$Q!tRDyvM7z- z0bQFOqpNYo`z64QE?XW$Pj1!nS^@vZ4&Dfj*XNZS$5ft@bBl&CwMNg9PVeR}z1j&A z_VudnSQ>F_3OkbQ)TF+a(%hU(TS^3(60ZN4Bqu{{vl-p0&sKFl!u6@lLr1g!mQ{1d z(93&VfPL5D(-CMB1~Zsfb>zbbra%E@osZW z$r>w%bS$5`y>@Szj+)vSDrzoMo`eH8;qKYLwyP|&B>*#3lZL^KV!v%&O6krc4^xBM z{E_oyEylmA6FJk{)h-_Fy^T7ZuzVj=3Kd~e#_w%j$2FD@M;*1%jp?HR9g*h^HHO90 zCb!-x=|b(iLs9}n&J(GuMHJp@nJ4I39%BihQ-%@y0Hc&`5WzgLsF zJe11hY4oJMbZXDkPR*CPR^L0rKIhK4@gD_FZaNkiS4zh-SvzWlaJbTHu~kEi%ory8 zfBtGjf&cTA@aF4N&0YFz-&}FEwC|C;StF^s0Q?wn_u&3zAbVv5S_rtnB6)w+xDH6H zrbirN?)HztI^AJbt zzj=51F;#l#^fcaDhK<-!rW&k1NTxRrY+I?GBhjyqzPPOgsN`>KQ`_5b{02NQnsrom zXeWk^JQn9b$<~+MT^u6cT?pd|fTPkzPg^S}Ez2469!y-?O)q7;Hw$J8Z&@0&R!e2o ztQc7MB$!(2dizuhH`TO;6r`R9_@vz2-ekYsobadQT7^(imzePBlTeYYHV;cw{5PG{ zyQ}e1dsqz<+5wpQnHrWUG7~UiLD_EFw!$ow{(^R%Hg@G}*%Pm|{>STW*->%v>uN9~ z?0h<@(p1zO)4G1eZ%UZW4>6^sZA%5kuYUyYnB0$zr_7h%ZAYqFr-*43V$=N`jdY58 z?n!6VlmjP3F0SGMu-zeA#G3008f)bf4Q(@mzr3%>tXHf8^fyy22TxrpM)i2CxZBS! zx-*wqe^)yD*6qbUQKz%>vxLTt37cJ{FpDrFo~vu_*kpFDUU2fAaaMrT99gYOZrLyG zI+aloJja=MtPx#mr^gp#Et+rW*ZuceHpL9*@JE2gZF;B8OTU+;e_HHV7&5HNY1gVR zuyld6YTy)oXsF^f?Wzq(WU6PjCx7WQ)w=3qAtLnp5gy+R=WQ@Fj{CHTXXm;UFUp@r z3|Of7W-V{NK3LV+noZOuNO(8c0JzL^7&;HrDvZV0W9fFnWVQIoU^_}e7 z{_9n|$31I?>66XNzx=#?YJ^yDHm~ap!oeO(GJTX%Tq~*O27Bh~Jc^Aq7!_OyS<=qE zY2F^_-Trp2nD)$FG5_4G2K^KKo!~;V+s(12#<1#hfb*_pc+AD|_t2+>S2Oev9RnZ=;)p8nGd_t(Y%K&rh3Uz5j)qEQ1 zX`kJdo1g3=+44S<(`VWQY<7wZZ&mt$T1&ULeVwv$rWg{7*32EdZK9`#xYLi=Ep2NQ zZR>g^jmHrcV(uy*a}AhY(qngL`pse)hFr%}$(=c%3xhQaqK|6LHWoEDjeO>I1Bc-b z9DzZXE;hes!ttq!ZujBCGPve#N#@a6iIxw3R`+qJm1nwL{RYwYCIbW2)JE8~QGvtl z=%;L8E`!I-CM1_(4h#Krgb&YPGfiz*GaGMR<@lu!i>^;z0z((|cH``%O@0km>6?Bl z@9LXxCr81$y6V{c)3a=icBLjUJP1}oXP3*enlszfzEEjG{`3)WP@|as@#3$pk)@cG zHhyjQ=rB`F9Ya?gX>D4$WRQ0RcowbBkX0X@I`XctY81Q9I$y2jib_LjR^hG*TL`UI zt6eVb%DLy<#Jhs(&bD4EJ(R2Auc%k`Uu6=4tMsARwE6qn!)5i@bDZ&rnWf94w@PC% zY=Uvp(Xyq=Zt3!#vlzL?wsFNDVEkgsN8xM8)DXI3m01F?$3}0qDH*zcn5t4KPq5W) z@v7Yx;v^8Vo!?~!_BY(|H!p;}C(8<14_^uKgpZ!j)2nB1bXkd<+*o$zty{-6yS0ey z8)_s-6f!x#KiAnf!1z%FGDU&ndi3tr?KaIkCo5L$JX-y4WU_EqE0yb2#&0e26+{PZMic>eLovRjx?X2n1;3 z@!7YTa%G-ZNWkGY8U8%cSx0I%YpKf-vUzD5F11JRI~C~uGW`fRZ zJR4x33Jz`R8AfIai{E~4NUUYJ=1%z%S^d!VTC-2md;V>MoNG(po?) z4}a?nmq(0DosBXC7d9io?kkSwGw;o98Qfk@4run|ck6N(#1D3GCs~v%&2T~lWMZSq ztb?8;`|<Xat&B$i`t6!=-4% zD6gvR9!?A8EqH-sNYc3kTra{3;pSzu%pHW(C_7sfJ61g{GHvpYIw!mWh=uKv&{}Rj z69KzO{&^IBF70}XcEQE9$|qr;$cH*oOoo+8>DS&bHe$fRy)J)0HSX=Ga^-B134*3^ zEHZ=-Sr*MdsBcE7^Lh#U1v*W5{)*BNs9hy@bwm5R5hN}p_jN5@HA^ZTU4&f=c5XR9 z|7Mw#e)P!lW`r{x!W~s|z3v10bfVOQ8Ayayxvq5n2|af^S=oxQwV%G5+(FCD7+pX? zTXLz;we6AXJjlm)OF;zaE>Ra&8YgE?nn@3RUICPDuT;K7 z;X53Dt{rO8V$U{8>2fft^NAo8O!OKVu3s#=L-RKryW}p?e4q7G{!cKa9TsGGGF@jo zvG}V|;R&Z_bhP`r?cPh~f%#ngVU@Z5O?~<$zCO zBgk8u4tq#W_J;){^bbna{8xo1ddyDLGKM&Eu2WLp8RY;&T4wB<@&G%P@xd&72bmoX z4>vortN*|NOe>2Tf4cmAFQ27{Utt4YyaJ7ua8XJ@HOew*)Im81d<{u720!56?lxpM zX5}62{^IitGJcJF{Ke^q?}LfBWg-9;)Blk7wmrv{A4&qP%5=ew`vX!dVh?QtDd+>d zOCWefac}!r$h*rG=FRs3c~=i-Sb%%9G!X0-RZzSW+({(FbKVMsxTC+dnCEGh_+RXQ z5`mvg!3K)0|HLyQgD8}>U`#Huh?GgfWDDKk*V7&Dt+@vvvbsSl;@R`Lrc)LtcrIAu8Yxyb8^vCdnTGsxo|Ou@mPf~OR+oj&1y~=B0%xRG+!hZ^y8`%IBSM6 zB@zwx!!+xVGAk8WPy3@`U?G+?9xL8-RF67$`sgSpdB)<+^9Ptu*sd%8X#eoAcHiNJHs2h)vI)sSW0Bh}k8t^{Vma3!I?-SR^AcNjcRRj3+P4 zPu#c-#UCUrH)D;IH=^If3*7uE2OPW8!aTOc96Vsx()TR*t@&tQh8y$3@18L4o{1gi z+WfDeD_8VZMqkLj0L)i%why?e&L)xs&l%e9z^N@4m7i!FU5Oa~?4r|MQHLf?`Iuz< zsHtSv+0rq{3MQQ$t~{K^<6nOly}I<$IB{88_*andxqd!27PWvvhENbU{8)PhqD;To zOn@YGu#~>KpEt>MEoHOvG|r#SMSHOCLJgt~vdPQy-?Y}-37Bxnv!E~Xdz8e5`tK5z zj@EQWipTb|<2fgvqufgDq-{{FT#dsIb}FUCWyyYL+X>i)HbGk0A^D}r3BLJB5o zv8`K(DT?a$dP1qq4{DdPTiN-6wa?hAs}SLrD7+Ssz>mvzw-7~tob;T;yuP;pZlc0R zj5RkI65{M%o4YCUMIon8?LVVpjBqEZ)~;U5)e8`C4qh8?e$?mpb;!QoX-RKm68RMmnh8@$|1jVNb<+Fx)qe7Zp#*`* z&Yx}p?>d#F)9;y$xe7*BJj&C>dWf&_021nkt4w=0ZWgA@1?%XdAn zNT0+Dv4J<@Vw@V>znxhbzVXK@UIl`%`y5mgHh{(Du3-asqVQ(Xav&x3Y#oHO#_|aK z>=B4j<~`k3J4i`I9c@g+A($+>83Clb)Cr8sm;_Q`Msa~tCk}1cF-HM`a^XsDuCSS_ za&fu`$^L0sgv8g%q4_jc;5wAVPXj?@+*r1+*PVO`e+}-Iq7+RoLKtl*@onCM=co#X zc~Jy-VJamIRW!SXM!2p`6;)F7CL{?+|AY;sX3T$lGj^h{cgEpj5!`iB&QcnT?nMmj zRHPARi+!3;N;h;@2zr*{T)QD%$_3uk2E46Ea$3wxM}-uC8HYMif8pSNct9Ix!o7(R z2?(PzCOlAc_-NC`}qc;KEYL!E9c*u9P2xeKhqCZJPCS{cnw@O=CMgQ&7 zqbFm&e?9_`vfrj3IFOgp)EXo>&~|moGJ;|wsjh#~_sT)4^cWBoZk4@Y|MQMUB1{uq z{nL1u+Rd&UI6_UE)}DNKiUhxke;pnK0ei*CYHN@s3cnQ|G$)!Aw`}&CZ$2hJ6nno* zW(h1&K#H(VBn*@ZLSDtY~ExRr~W% zGeaVqS7j!`&8P|!D4&uD8QOH8>Qc?b-*$!x;-MU6E}cHzd>>Psw{VvAVgZh?3b>ko zD=~n?(;oQ9L6QyBDpl7@e{Njp#KQ=_6FIk_PvDb&`wcD?V5sZHeRg}2nwDATaw?Lns8h_z<7xCTy=zawOlFvZP0g0X%(E7M?vnq>Zvl- zLYjhGiIK=6kwA{mKKXuM5d_ zR`X()15Az3Z3fquzR#5>KP2PVqi1mEey`=8ZHnAGQNsEnwA04FX{4}BBq)jtm3||{ zHbn>}qy%g)9$zK$dv$As;I2$OOdSrWA1jz3buOgbp}(~mPW`ym3BPgi%pgSb2uQ?) zz*pjaxHdtP)*84J9%c)hwzdiV1aEeU!6?QLy?a( zQyQR7lOj?`f!H&sgEj%A@gIEh*6rPi##NB(El+D!siGXTR1_%1*nbGN3@fm7F+jRT#ZpI;9TKBQt@kAUo5YWq^4{gt;f-Sie0w^s4e9hFBh#2>!u59^-E; zgjO9YgWy()cgfpB2zUCAwOp0-wz%ta=)Tu<>@C1S>A{>3FToEC^_?eyIat#97KtsB zdnIFb4-b-5Dww%@NLgIu^j>xDj!W-PG{!V4^fcTT2oB$jDcK~qC9*Jw;32`+RDK*r zRGL#+>+89C9UUD!IHHYG^O=s~PdQ4(wZEuRMWS`Vzr+2Memb}xLJ+-*lyx&X!$>`( z2mpgV9<6yT@2Qw3T<%27qZlJT3^}m zU&*^%{$MhN$V@n_7Qbc#nk9?E^Rz`mo!ZsQp)86x?X1%KRNZ=zSmVrQ$7_hN%096I z1~%fBg5tbdbSWLRyB^PHs@&Gje*`CGX4NSaj^E*0_ezy2MUkzfOnq7A$n9%kt_6ej zhb*KV1ZnE;-8#l$3r7{Y=<#i*AVkU?g<@AQatCFy0z%J0XJ)H3_wVU@%+bAGM*qRR zyLE!_aiyjYmdT&hF-m(nVN`j72M$x;*pU4#Y36tj$!B4IWk>2pSNq4LsGz+{W>xlW z*+W`z7P4vn$u#olWgW2dl>N9JTn6uBjeP}oqY9#J8?3Ky{O4n7h?w$o;E4SXE8o#t z=e_%X*37*%ibnLau*+wHR3^aDD%$hEP88_RaV=Z`od3%3RsEXhS;PCy|4s2Xa-R*-B(9Q*s0$J8W5+5y)=E!=^&3V% z7C&YgC7eZ{5apfs5*9K^CKUeU%-no9^LrtLqy7eVi#)Vy4H5-oTAo4qrpP!sV>8z| zGpB$l8<+4#!VsNiLSa~*ORl`7O;Gbj#W&6o9&z;_LZ5_Nj{hh9oacYZn5Z}*KncN$ zzCi^Jnq%;>M(gB&R}RSDBZ|sMSjyoWv4ce`9_?Ywjfr{^9t_kuMfpL}c0u7I**oGQ ze@s#JozC6xERdm`Edb@XX9nD?t2#;77} zVZ$Mmu&7hjPH1s|KnP-naBY2X|Lt{s%|SKJdv(tHA%zFWL49Mm;e^6=M91!jI;gM| z;pQ1vc9($j5zn;!>HRwi%W$K>TtdPl(AsS6ho)6d=AYJ;WKwWExzcdWvZnoLjxAY-g8Yhad}fR?}|f8jifT_Me1|^OlTp%+`kY!KBr^l`vftyzL2^6 zG+53hXVqmRYeeT%8KNVw-PQ)9j)Gy<;v2oq$8V|ywpxw-77InB(JE`^42{_DsID&O z*OXwfcl98QBE`qRkReV-oM!D+%3ot-aIy}()CKSlXh*w<@sHjJr0Z!B2mQwWGM!ze z=)6>&pVeeI8Kt{=#rY&m=$0ai^nIaP_wJ83Ok+3=DMPdUAiG@I0`1zS zA&JSCsH`fi4R*@l3KQ43TwjqvQp0R308klm*k%D}`UeP8OD17S4!>d3H+{kWUEyqd z6#N4mr%YNNnu7nmNWzTmt7(^t#N1#&*_mz?TooIZ>VenQ8-VGK0`^RO!K`7}PUwl7Iek>4H(MOuEo1ZA;B9|})~hNgPga$b z_zS!it_c8>m_^w%1(dHbsD6vFU&i~&i|?@fwX$-#($HMlMcOf4=m+}ITvXV$mpw24DwbZZHQNoa$&QXcb^{_HxGK6U&X)4d?!Q1_etmnnwo#9t6iT(Q&sF z=T~%#1YBGL8mncHTe4cJm&okj(qF33k4dSZ`!CS#(Ey^j$L=Jw^K*upv0|8J&d*(y zp|xat>O{LL=Ylh*PgnSrTVNJi*ix8m-cim?ZmZ*3DrwO11znNL*e-T_)}Oi}Yv{YGQupXsR8F^46Pb5k-lUC)i(i-7mButH3Rw ze;v~Kk2Dq5BkK{b1388rmhF}V0WQW8>eR*pr=-|Y3u#57WQt*>_lOzXkFpcKSDe+<$rV9buf8P-~7q5Z+s2l%COHmG;; zCFp#-p?3!23QY8CvutcfCK1OJzr^ z*c-ibdm*_wY=}I!rM&ZQ>p#(GS3RG(GLP~O*GHHlrrIEJjWpizTG_VoMLHR3Hq;xM z{skzUyi{Izr5tj*is(P2`U1LVGI${8R|jKQuOb8qu5SS;Q0@oMF3ooC%gt<~?|lN# zzHfxZpCD8NpKg#Zc~TImvlP6>T~LL6L|5Vr>tognUs~U)zdnes9yKi=EAHJx7UV8s zB_;c#K`$I_-K&L2L(?x_Ms(Ku>ZDF~uhHe$H(b2qu-tEn=eLYjshUTNJ*8;KV~c&o z!GwnJXxD1}a81CY&^u9^kB~qu*WgTZioVKjkG}+FAc7zgidHae7X_Aa9rIFPiPZ%H zmSxS37yI2#FX%8B@byv?>>E+mW%*Qb6_?UcY7(465;)ZtDiQgU#L3eD3a=C1U3?1r z$?3p9iA@y$?#?}UI~QlVyN8m!{a_#u1=^uKnwu>b@iMz7lu2FPl#c2jRgDnTFXzwoRl_|QcSQK~pd|E@kq2tZHv1fhXyxyF5;dR# z+_*CqIwlqPn}ABqkz8E>a?~T5Nxp<;4c$rfdrD|+=^2JEK2#9Tmk@9&A7jYjPiM59 z4D!Q|md{NB%rb^UD~P8w5mR7KlIndgjmZ`P!tpjFHE7{9v1m6&8V{lsr4X&?itk^1 zR-V+oQI($ul*G`i5hmH0@>n6>>6JJzsGzb^R=>o^-u--y8qQX%nQq&=y3_bTDZPj+ zqoGk2$It+obw_Z67P}$oD@np?4*>>Z)WvwNGr?@U7KB-f0{|xExb(LtO|Jg48r$3y W5lu8>Y1$O|6p`Ic^4Ep|2m2p#g6O3H literal 0 HcmV?d00001 diff --git a/test/bun.js/FormData.test.ts b/test/js/web/html/FormData.test.ts similarity index 99% rename from test/bun.js/FormData.test.ts rename to test/js/web/html/FormData.test.ts index 25c0d5d549f1..9d0db4361117 100644 --- a/test/bun.js/FormData.test.ts +++ b/test/js/web/html/FormData.test.ts @@ -1,7 +1,7 @@ import { afterAll, beforeAll, describe, expect, it, test } from "bun:test"; import fs, { chmodSync, unlinkSync } from "fs"; import { mkfifo } from "mkfifo"; -import { gc, withoutAggressiveGC } from "./gc"; +import { gc, withoutAggressiveGC } from "../../gc"; describe("FormData", () => { it("should be able to append a string", () => { diff --git a/test/bun.js/form-data-fixture.txt b/test/js/web/html/form-data-fixture.txt similarity index 100% rename from test/bun.js/form-data-fixture.txt rename to test/js/web/html/form-data-fixture.txt diff --git a/test/js/web/streams/bun-streams-test-fifo.sh b/test/js/web/streams/bun-streams-test-fifo.sh new file mode 100644 index 000000000000..57650ba1d7d7 --- /dev/null +++ b/test/js/web/streams/bun-streams-test-fifo.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +echoerr() { echo "$@" 1>&2; } + +echoerr "bun-streams-test-fifo.sh: starting" +echo -e "$FIFO_TEST" >>${@: -1} +echoerr "bun-streams-test-fifo.sh: ending" +exit 0 diff --git a/test/js/web/streams/fetch.js.txt b/test/js/web/streams/fetch.js.txt new file mode 100644 index 000000000000..5a9b52fcf1f9 --- /dev/null +++ b/test/js/web/streams/fetch.js.txt @@ -0,0 +1,46 @@ + + + + Example Domain + + + + + + + + +
+

Example Domain

+

This domain is for use in illustrative examples in documents. You may use this + domain in literature without prior coordination or asking for permission.

+

More information...

+
+ + diff --git a/test/bun.js/streams.test.js b/test/js/web/streams/streams.test.js similarity index 99% rename from test/bun.js/streams.test.js rename to test/js/web/streams/streams.test.js index 7ae49db40d22..c4af85e4f5a9 100644 --- a/test/bun.js/streams.test.js +++ b/test/js/web/streams/streams.test.js @@ -4,7 +4,7 @@ import { mkfifo } from "mkfifo"; import { realpathSync, unlinkSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { tmpdir } from "os"; -import { gc } from "./gc"; +import { gc } from "harness"; beforeEach(() => gc()); afterEach(() => gc()); diff --git a/test/bun.js/microtask.test.js b/test/js/web/timers/microtask.test.js similarity index 100% rename from test/bun.js/microtask.test.js rename to test/js/web/timers/microtask.test.js diff --git a/test/bun.js/performance.test.js b/test/js/web/timers/performance.test.js similarity index 100% rename from test/bun.js/performance.test.js rename to test/js/web/timers/performance.test.js diff --git a/test/bun.js/setImmediate.test.js b/test/js/web/timers/setImmediate.test.js similarity index 100% rename from test/bun.js/setImmediate.test.js rename to test/js/web/timers/setImmediate.test.js diff --git a/test/bun.js/setInterval.test.js b/test/js/web/timers/setInterval.test.js similarity index 100% rename from test/bun.js/setInterval.test.js rename to test/js/web/timers/setInterval.test.js diff --git a/test/bun.js/setTimeout.test.js b/test/js/web/timers/setTimeout.test.js similarity index 100% rename from test/bun.js/setTimeout.test.js rename to test/js/web/timers/setTimeout.test.js diff --git a/test/bun.js/url.test.ts b/test/js/web/url/url.test.ts similarity index 100% rename from test/bun.js/url.test.ts rename to test/js/web/url/url.test.ts diff --git a/test/bun.js/atob.test.js b/test/js/web/util/atob.test.js similarity index 100% rename from test/bun.js/atob.test.js rename to test/js/web/util/atob.test.js diff --git a/test/bun.js/web-globals.test.js b/test/js/web/web-globals.test.js similarity index 99% rename from test/bun.js/web-globals.test.js rename to test/js/web/web-globals.test.js index aa5756c520a3..b7a243190ba7 100644 --- a/test/bun.js/web-globals.test.js +++ b/test/js/web/web-globals.test.js @@ -1,6 +1,6 @@ import { unsafe } from "bun"; import { expect, it, test } from "bun:test"; -import { withoutAggressiveGC } from "gc"; +import { withoutAggressiveGC } from "harness"; test("exists", () => { expect(typeof URL !== "undefined").toBe(true); diff --git a/test/bun.js/websocket-subprocess.ts b/test/js/web/websocket/websocket-subprocess.ts similarity index 100% rename from test/bun.js/websocket-subprocess.ts rename to test/js/web/websocket/websocket-subprocess.ts diff --git a/test/bun.js/websocket.test.js b/test/js/web/websocket/websocket.test.js similarity index 98% rename from test/bun.js/websocket.test.js rename to test/js/web/websocket/websocket.test.js index 61c9ec054d8f..f0f29c1c3955 100644 --- a/test/bun.js/websocket.test.js +++ b/test/js/web/websocket/websocket.test.js @@ -1,9 +1,6 @@ import { describe, it, expect } from "bun:test"; import { unsafe, spawn, readableStreamToText } from "bun"; -import { bunExe } from "bunExe"; - -import { gc } from "./gc"; -import { bunEnv } from "bunEnv"; +import { bunExe, bunEnv, gc } from "harness"; const TEST_WEBSOCKET_HOST = process.env.TEST_WEBSOCKET_HOST || "wss://ws.postman-echo.com/raw"; diff --git a/test/bun.js/html-rewriter.test.js b/test/js/workerd/html-rewriter.test.js similarity index 99% rename from test/bun.js/html-rewriter.test.js rename to test/js/workerd/html-rewriter.test.js index e33710f28e71..b5db3fde2e6f 100644 --- a/test/bun.js/html-rewriter.test.js +++ b/test/js/workerd/html-rewriter.test.js @@ -1,5 +1,5 @@ import { describe, it, expect } from "bun:test"; -import { gcTick } from "./gc"; +import { gcTick } from "../gc"; var setTimeoutAsync = (fn, delay) => { return new Promise((resolve, reject) => { diff --git a/test/macro/assert.tsx b/test/macro/assert.tsx deleted file mode 100644 index 8a6e4d8224f2..000000000000 --- a/test/macro/assert.tsx +++ /dev/null @@ -1,4 +0,0 @@ -// This logs the result at build time -export function unreachable(call) { - throw new Error(call.arguments[0].toString() || "unreachable"); -} diff --git a/test/macro/fetchSync.tsx b/test/macro/fetchSync.tsx deleted file mode 100644 index 51d1ca4f537e..000000000000 --- a/test/macro/fetchSync.tsx +++ /dev/null @@ -1,9 +0,0 @@ -// @ts-nocheck -export async function fetchSync(ctx) { - const str = ctx.arguments[0].toString(); - - const response = await fetch(str); - const text = await response.text(); - - return ; -} diff --git a/test/macro/hello-fetch-macro.tsx b/test/macro/hello-fetch-macro.tsx deleted file mode 100644 index fa0de4a9d7f4..000000000000 --- a/test/macro/hello-fetch-macro.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { fetchSync } from "macro:./fetchSync.tsx"; - -const synchronousFetch = fetchSync(`https://example.com`); - -console.log(synchronousFetch); diff --git a/test/macro/loadMocks.tsx b/test/macro/loadMocks.tsx deleted file mode 100644 index 08e90dece608..000000000000 --- a/test/macro/loadMocks.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { unreachable } from "macro:./assert"; - -if (process.env.NODE_ENV !== "test") unreachable("This module should only be imported in tests"); - -export const mockData = { - Copilot: { - id: "Copilot", - name: "Copilot", - description: "Copilot", - icon: "https://s3.amazonaws.com/copilot-public/images/icons/Copilot.png", - color: "#00AEEF", - type: "service", - tags: ["copilot"], - categories: ["copilot"], - links: [ - { - id: "Copilot", - name: "Copilot", - url: "https://copilot.io", - description: "Copilot", - icon: "https://s3.amazonaws.com/copilot-public/images/icons/Copilot.png", - color: "#00AEEF", - type: "service", - tags: ["copilot"], - categories: ["copilot"], - }, - ], - }, -}; diff --git a/test/macro/macro.d.ts b/test/macro/macro.d.ts deleted file mode 100644 index ba02c03edebc..000000000000 --- a/test/macro/macro.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module "macro:*"; diff --git a/test/bun.js/mkfifo.ts b/test/mkfifo.ts similarity index 100% rename from test/bun.js/mkfifo.ts rename to test/mkfifo.ts diff --git a/test/package.json b/test/package.json new file mode 100644 index 000000000000..dfeabcd42ae6 --- /dev/null +++ b/test/package.json @@ -0,0 +1,19 @@ +{ + "private": true, + "name": "test", + "type": "module", + "devDependencies": { + "bun-types": "canary" + }, + "dependencies": { + "bktree-fast": "^0.0.7", + "body-parser": "^1.20.2", + "esbuild": "^0.17.11", + "express": "^4.18.2", + "iconv-lite": "^0.6.3", + "lodash": "^4.17.21", + "react-dom": "^18.2.0", + "svelte": "^3.55.1", + "undici": "^5.20.0" + } +} \ No newline at end of file diff --git a/test/bun.js/repro_631.test.js b/test/regression/issue/00631.test.ts similarity index 78% rename from test/bun.js/repro_631.test.js rename to test/regression/issue/00631.test.ts index 11eba44742f9..691c3227cfc5 100644 --- a/test/bun.js/repro_631.test.js +++ b/test/regression/issue/00631.test.ts @@ -1,10 +1,11 @@ import { expect, it } from "bun:test"; -import { bunExe } from "./bunExe.ts"; -import { bunEnv } from "./bunEnv.ts"; -import { mkdirSync, rmSync, writeFileSync, readFileSync } from "fs"; +import { bunExe, bunEnv } from "../../harness.js"; +import { mkdirSync, rmSync, writeFileSync, readFileSync, mkdtempSync } from "fs"; +import { tmpdir } from "os"; +import { join } from "path"; it("JSON strings escaped properly", async () => { - const testDir = import.meta.dir + "/repro_631/"; + const testDir = mkdtempSync(join(tmpdir(), "issue631-")); // Clean up from prior runs if necessary rmSync(testDir, { recursive: true, force: true }); @@ -23,6 +24,7 @@ it("JSON strings escaped properly", async () => { }); expect(exitCode).toBe(0); + console.log(testDir); const packageContents = readFileSync(testDir + "package.json", { encoding: "utf8" }); expect(packageContents).toBe(String.raw`{ "testRegex": "\\a\n\\b\\", diff --git a/test/bun.js/repro_2005.test.js b/test/regression/issue/02005.test.ts similarity index 86% rename from test/bun.js/repro_2005.test.js rename to test/regression/issue/02005.test.ts index dc0cd9a975cb..230be5301beb 100644 --- a/test/bun.js/repro_2005.test.js +++ b/test/regression/issue/02005.test.ts @@ -1,6 +1,6 @@ import { it, expect } from "bun:test"; -it("regex literal with non-Latin1 should work", () => { +it("regex literal with non-latin1 should work", () => { const text = "这是一段要替换的文字"; //Correct results: 这是一段的文字 diff --git a/test/tsconfig.json b/test/tsconfig.json new file mode 100644 index 000000000000..e879cd5f5441 --- /dev/null +++ b/test/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "noEmit": true, + "lib": ["ESNext"], + "module": "ESNext", + "target": "ESNext", + "moduleResolution": "nodenext", + "strict": true, + "downlevelIteration": true, + "skipLibCheck": true, + "jsx": "preserve", + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "allowJs": true, + "types": ["bun-types"], + "baseUrl": ".", + "paths": { + "harness": ["harness.ts"], + "mkfifo": ["mkfifo.ts"], + "node-harness": ["js/node/harness.ts"] + } + } +} diff --git a/tsconfig.json b/tsconfig.json index 518dca2c165f..461fdb4724a3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,8 +16,6 @@ "bench", "examples/react-fast-refresh-test", "examples/macros", - "test/bun.js/solid-dom-fixtures", - "test/bun.js/bundled", "src/deps", "bun-webkit", // JavaScriptCore builtins use a non-standard "@" symbol to indicate a private identifier which no other tool supports From 0b5e19d08e90db378ac380045abd0ea9eae5c7da Mon Sep 17 00:00:00 2001 From: Ashcon Partovi Date: Tue, 7 Mar 2023 12:28:16 -0800 Subject: [PATCH 050/255] Fix html-rewriter.test.js --- test/js/workerd/html-rewriter.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/js/workerd/html-rewriter.test.js b/test/js/workerd/html-rewriter.test.js index b5db3fde2e6f..1ca92a567a37 100644 --- a/test/js/workerd/html-rewriter.test.js +++ b/test/js/workerd/html-rewriter.test.js @@ -1,5 +1,5 @@ import { describe, it, expect } from "bun:test"; -import { gcTick } from "../gc"; +import { gcTick } from "harness"; var setTimeoutAsync = (fn, delay) => { return new Promise((resolve, reject) => { From 176ac69abccc5aba9af2111aedb8f988299b2845 Mon Sep 17 00:00:00 2001 From: pfg Date: Tue, 7 Mar 2023 16:11:54 -0500 Subject: [PATCH 051/255] fix the wrong thing being incremented in hmr example (#2334) --- docs/runtime/hot.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/runtime/hot.md b/docs/runtime/hot.md index 36288de3354e..547728f4d3b4 100644 --- a/docs/runtime/hot.md +++ b/docs/runtime/hot.md @@ -56,7 +56,7 @@ For more fine-grained control, you can use the `Bun.serve` API directly and hand import type {Serve} from "bun"; globalThis.count = globalThis.count ?? 0; -globalThis.reloadCount++; +globalThis.count++; // define server parameters const serverOptions: Serve = { From 9f8cf19f71ce1450b75f55eb477622189e85fc75 Mon Sep 17 00:00:00 2001 From: Ashcon Partovi Date: Tue, 7 Mar 2023 17:02:34 -0800 Subject: [PATCH 052/255] Add more test harness --- test/js/deno/abort/abort-controller.test.ts | 66 ++++++ test/js/deno/harness.ts | 226 ++++++++++++++++++++ test/js/deno/html/blob.test.ts | 117 ++++++++++ test/js/deno/resources/imports.json | 4 + test/js/deno/resources/tests.json | 10 + test/js/deno/scripts/postinstall.ts | 31 +++ test/tsconfig.json | 4 +- 7 files changed, 457 insertions(+), 1 deletion(-) create mode 100644 test/js/deno/abort/abort-controller.test.ts create mode 100644 test/js/deno/harness.ts create mode 100644 test/js/deno/html/blob.test.ts create mode 100644 test/js/deno/resources/imports.json create mode 100644 test/js/deno/resources/tests.json create mode 100644 test/js/deno/scripts/postinstall.ts diff --git a/test/js/deno/abort/abort-controller.test.ts b/test/js/deno/abort/abort-controller.test.ts new file mode 100644 index 000000000000..ba386d021c62 --- /dev/null +++ b/test/js/deno/abort/abort-controller.test.ts @@ -0,0 +1,66 @@ +// Updated: Wed, 08 Mar 2023 00:55:15 GMT +// URL: https://raw.githubusercontent.com/denoland/deno/main/cli/tests/unit/abort_controller_test.ts +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { assert, assertEquals } from "deno:harness"; + +Deno.test(function basicAbortController() { + const controller = new AbortController(); + assert(controller); + const { signal } = controller; + assert(signal); + assertEquals(signal.aborted, false); + controller.abort(); + assertEquals(signal.aborted, true); +}); + +Deno.test(function signalCallsOnabort() { + const controller = new AbortController(); + const { signal } = controller; + let called = false; + signal.onabort = (evt) => { + assert(evt); + assertEquals(evt.type, "abort"); + called = true; + }; + controller.abort(); + assert(called); +}); + +Deno.test(function signalEventListener() { + const controller = new AbortController(); + const { signal } = controller; + let called = false; + signal.addEventListener("abort", function (ev) { + assert(this === signal); + assertEquals(ev.type, "abort"); + called = true; + }); + controller.abort(); + assert(called); +}); + +Deno.test(function onlyAbortsOnce() { + const controller = new AbortController(); + const { signal } = controller; + let called = 0; + signal.addEventListener("abort", () => called++); + signal.onabort = () => { + called++; + }; + controller.abort(); + assertEquals(called, 2); + controller.abort(); + assertEquals(called, 2); +}); + +Deno.test(function controllerHasProperToString() { + const actual = Object.prototype.toString.call(new AbortController()); + assertEquals(actual, "[object AbortController]"); +}); + +Deno.test(function abortReason() { + const signal = AbortSignal.abort("hey!"); + assertEquals(signal.aborted, true); + assertEquals(signal.reason, "hey!"); +}); diff --git a/test/js/deno/harness.ts b/test/js/deno/harness.ts new file mode 100644 index 000000000000..167860d1f35b --- /dev/null +++ b/test/js/deno/harness.ts @@ -0,0 +1,226 @@ +// Deno's test utilities implemented using expect(). +// https://github.com/denoland/deno/blob/main/cli/tests/unit/test_util.ts + +import { concatArrayBuffers } from "bun"; +import { it, expect } from "bun:test"; + +export function test(fn: () => void): void { + it(fn.name, fn); +} + +export function assert(condition: unknown, message?: string): asserts condition is true { + if (message) { + it(message, () => assert(condition)); + } else { + expect(condition).toBeTruthy(); + } +} + +export function assertFalse(condition: unknown, message?: string): asserts condition is false { + if (message) { + it(message, () => assertFalse(condition)); + } else { + expect(condition).toBeFalsy(); + } +} + +export function assertEquals(actual: unknown, expected: unknown, message?: string): void { + if (message) { + it(message, () => assertEquals(actual, expected)); + } else { + expect(actual).toEqual(expected); + } +} + +export function assertExists(value: unknown, message?: string): void { + if (message) { + it(message, () => assertExists(value)); + } else { + expect(value).toBeDefined(); + } +} + +export function assertNotEquals(actual: unknown, expected: unknown, message?: string): void { + if (message) { + it(message, () => assertNotEquals(actual, expected)); + } else { + expect(actual).not.toEqual(expected); + } +} + +export function assertStrictEquals(actual: unknown, expected: unknown, message?: string): void { + if (message) { + it(message, () => assertStrictEquals(actual, expected)); + } else { + expect(actual).toStrictEqual(expected); + } +} + +export function assertNotStrictEquals(actual: unknown, expected: unknown, message?: string): void { + if (message) { + it(message, () => assertNotStrictEquals(actual, expected)); + } else { + expect(actual).not.toStrictEqual(expected); + } +} + +export function assertAlmostEquals(actual: unknown, expected: number, epsilon: number = 1e-7, message?: string): void { + if (message) { + it(message, () => assertAlmostEquals(actual, expected)); + } else if (typeof actual === "number") { + // TODO: toBeCloseTo() + expect(Math.abs(actual - expected)).toBeLessThanOrEqual(epsilon); + } else { + expect(typeof actual).toBe("number"); + } +} + +export function assertInstanceOf(actual: unknown, expected: unknown, message?: string): void { + if (message) { + it(message, () => assertInstanceOf(actual, expected)); + } else if (typeof actual === "object") { + if (actual !== null) { + expect(actual).toHaveProperty("constructor", expected); + } else { + expect(actual).not.toBeNull(); + } + } else { + expect(typeof actual).toBe("object"); + } +} + +export function assertNotInstanceOf(actual: unknown, expected: unknown, message?: string): void { + if (message) { + it(message, () => assertNotInstanceOf(actual, expected)); + } else if (typeof actual === "object") { + if (actual !== null) { + expect(actual).not.toHaveProperty("constructor", expected); + } else { + expect(actual).not.toBeNull(); + } + } else { + expect(typeof actual).toBe("object"); + } +} + +export function assertStringIncludes(actual: unknown, expected: string, message?: string): void { + if (message) { + it(message, () => assertStringIncludes(actual, expected)); + } else if (typeof actual === "string") { + expect(actual).toContain(expected); + } else { + expect(typeof actual).toBe("string"); + } +} + +export function assertArrayIncludes(actual: unknown, expected: unknown[], message?: string): void { + if (message) { + it(message, () => assertArrayIncludes(actual, expected)); + } else if (Array.isArray(actual)) { + for (const value of expected) { + expect(actual).toContain(value); + } + } else { + expect(Array.isArray(actual)).toBe(true); + } +} + +export function assertMatch(actual: unknown, expected: RegExp, message?: string): void { + if (message) { + it(message, () => assertMatch(actual, expected)); + } else if (typeof actual === "string") { + expect(expected.test(actual)).toBe(true); + } else { + expect(typeof actual).toBe("string"); + } +} + +export function assertNotMatch(actual: unknown, expected: RegExp, message?: string): void { + if (message) { + it(message, () => assertNotMatch(actual, expected)); + } else if (typeof actual === "string") { + expect(expected.test(actual)).toBe(false); + } else { + expect(typeof actual).toBe("string"); + } +} + +export function assertObjectMatch(actual: unknown, expected: Record, message?: string): void { + if (message) { + it(message, () => assertObjectMatch(actual, expected)); + } else if (typeof actual === "object") { + // TODO: toMatchObject() + if (actual !== null) { + const expectedKeys = Object.keys(expected); + for (const key of Object.keys(actual)) { + if (!expectedKeys.includes(key)) { + // @ts-ignore + delete actual[key]; + } + } + expect(actual).toEqual(expected); + } else { + expect(actual).not.toBeNull(); + } + } else { + expect(typeof actual).toBe("object"); + } +} + +export function assertThrows(fn: () => void, message?: string): void { + if (message) { + it(message, () => assertThrows(fn)); + } else { + try { + fn(); + } catch (error) { + expect(error).toBeDefined(); + return; + } + throw new Error("Expected an error to be thrown"); + } +} + +export async function assertRejects(fn: () => Promise, message?: string): Promise { + if (message) { + it(message, () => assertRejects(fn)); + } else { + try { + await fn(); + } catch (error) { + expect(error).toBeDefined(); + return; + } + throw new Error("Expected an error to be thrown"); + } +} + +export function equal(a: unknown, b: unknown): boolean { + return Bun.deepEquals(a, b); +} + +export function fail(message: string): never { + throw new Error(message); +} + +export function unimplemented(message: string): never { + throw new Error(`Unimplemented: ${message}`); +} + +export function unreachable(): never { + throw new Error("Unreachable"); +} + +export function concat(...buffers: Uint8Array[]): Uint8Array { + return new Uint8Array(concatArrayBuffers(buffers)); +} + +export function inspect(...args: unknown[]): string { + return Bun.inspect(...args); +} + +// @ts-expect-error +globalThis["Deno"] = { + test, + inspect, +}; diff --git a/test/js/deno/html/blob.test.ts b/test/js/deno/html/blob.test.ts new file mode 100644 index 000000000000..0b50c8452eb4 --- /dev/null +++ b/test/js/deno/html/blob.test.ts @@ -0,0 +1,117 @@ +// Updated: Wed, 08 Mar 2023 00:55:15 GMT +// URL: https://raw.githubusercontent.com/denoland/deno/main/cli/tests/unit/blob_test.ts +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { assert, assertEquals, assertStringIncludes } from "deno:harness"; +import { concat } from "deno:harness"; + +Deno.test(function blobString() { + const b1 = new Blob(["Hello World"]); + const str = "Test"; + const b2 = new Blob([b1, str]); + assertEquals(b2.size, b1.size + str.length); +}); + +Deno.test(function blobBuffer() { + const buffer = new ArrayBuffer(12); + const u8 = new Uint8Array(buffer); + const f1 = new Float32Array(buffer); + const b1 = new Blob([buffer, u8]); + assertEquals(b1.size, 2 * u8.length); + const b2 = new Blob([b1, f1]); + assertEquals(b2.size, 3 * u8.length); +}); + +Deno.test(function blobSlice() { + const blob = new Blob(["Deno", "Foo"]); + const b1 = blob.slice(0, 3, "Text/HTML"); + assert(b1 instanceof Blob); + assertEquals(b1.size, 3); + assertEquals(b1.type, "text/html"); + const b2 = blob.slice(-1, 3); + assertEquals(b2.size, 0); + const b3 = blob.slice(100, 3); + assertEquals(b3.size, 0); + const b4 = blob.slice(0, 10); + assertEquals(b4.size, blob.size); +}); + +Deno.test(function blobInvalidType() { + const blob = new Blob(["foo"], { + type: "\u0521", + }); + + assertEquals(blob.type, ""); +}); + +Deno.test(function blobShouldNotThrowError() { + let hasThrown = false; + + try { + // deno-lint-ignore no-explicit-any + const options1: any = { + ending: "utf8", + hasOwnProperty: "hasOwnProperty", + }; + const options2 = Object.create(null); + new Blob(["Hello World"], options1); + new Blob(["Hello World"], options2); + } catch { + hasThrown = true; + } + + assertEquals(hasThrown, false); +}); + +/* TODO https://github.com/denoland/deno/issues/7540 +Deno.test(function nativeEndLine() { + const options = { + ending: "native", + } as const; + const blob = new Blob(["Hello\nWorld"], options); + + assertEquals(blob.size, Deno.build.os === "windows" ? 12 : 11); +}); +*/ + +Deno.test(async function blobText() { + const blob = new Blob(["Hello World"]); + assertEquals(await blob.text(), "Hello World"); +}); + +Deno.test(async function blobStream() { + const blob = new Blob(["Hello World"]); + const stream = blob.stream(); + assert(stream instanceof ReadableStream); + const reader = stream.getReader(); + let bytes = new Uint8Array(); + const read = async (): Promise => { + const { done, value } = await reader.read(); + if (!done && value) { + bytes = concat(bytes, value); + return read(); + } + }; + await read(); + const decoder = new TextDecoder(); + assertEquals(decoder.decode(bytes), "Hello World"); +}); + +Deno.test(async function blobArrayBuffer() { + const uint = new Uint8Array([102, 111, 111]); + const blob = new Blob([uint]); + assertEquals(await blob.arrayBuffer(), uint.buffer); +}); + +Deno.test(function blobConstructorNameIsBlob() { + const blob = new Blob(); + assertEquals(blob.constructor.name, "Blob"); +}); + +Deno.test(function blobCustomInspectFunction() { + const blob = new Blob(); + assertEquals( + Deno.inspect(blob), + `Blob { size: 0, type: "" }`, + ); + assertStringIncludes(Deno.inspect(Blob.prototype), "Blob"); +}); diff --git a/test/js/deno/resources/imports.json b/test/js/deno/resources/imports.json new file mode 100644 index 000000000000..0c75b5739a1e --- /dev/null +++ b/test/js/deno/resources/imports.json @@ -0,0 +1,4 @@ +[ + "test_util.ts", + "test_util/std/bytes/concat.ts" +] diff --git a/test/js/deno/resources/tests.json b/test/js/deno/resources/tests.json new file mode 100644 index 000000000000..1d7e0f52acf7 --- /dev/null +++ b/test/js/deno/resources/tests.json @@ -0,0 +1,10 @@ +[ + { + "path": "abort/abort-controller.test.ts", + "remotePath": "unit/abort_controller_test.ts" + }, + { + "path": "html/blob.test.ts", + "remotePath": "unit/blob_test.ts" + } +] \ No newline at end of file diff --git a/test/js/deno/scripts/postinstall.ts b/test/js/deno/scripts/postinstall.ts new file mode 100644 index 000000000000..4031b29df9a6 --- /dev/null +++ b/test/js/deno/scripts/postinstall.ts @@ -0,0 +1,31 @@ +import { mkdirSync } from "node:fs"; +import { join, dirname } from "node:path"; +import imports from "../resources/imports.json"; +import tests from "../resources/tests.json"; + +for (const test of tests) { + const path = join(import.meta.dir, "..", test.path); + const url = new URL( + test.remotePath, + "https://raw.githubusercontent.com/denoland/deno/main/cli/tests/" + ); + const response = await fetch(url); + console.log(response.status, url.toString(), "->", test.path); + if (!response.ok) { + throw new Error( + `Failed to download from GitHub: ${url} [status: ${response.status}]` + ); + } + let body = await response.text(); + for (const query of imports) { + const pattern = new RegExp(`"(.*${query})"`, "gmi"); + body = body.replace(pattern, "\"deno:harness\""); + } + const src = `// Updated: ${response.headers.get("Date")} +// URL: ${url} +${body}`; + try { + mkdirSync(dirname(path)); + } catch {} + await Bun.write(path, src); +} diff --git a/test/tsconfig.json b/test/tsconfig.json index e879cd5f5441..12ad19dc76e2 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -12,12 +12,14 @@ "allowSyntheticDefaultImports": true, "forceConsistentCasingInFileNames": true, "allowJs": true, + "resolveJsonModule": true, "types": ["bun-types"], "baseUrl": ".", "paths": { "harness": ["harness.ts"], "mkfifo": ["mkfifo.ts"], - "node-harness": ["js/node/harness.ts"] + "node-harness": ["js/node/harness.ts"], + "deno:harness": ["js/deno/harness.ts"] } } } From 7a9702016445491ad30dab3b19727fb54e1e428f Mon Sep 17 00:00:00 2001 From: Colin McDonnell Date: Tue, 7 Mar 2023 17:44:05 -0800 Subject: [PATCH 053/255] Improve Benchmarking page, small fixes (#2339) * Improve benchmarking page * WIP * Add typescript instructions to hot * Document preload in Plugins. Fix loader in plugin types. --- bench/react-hello-world/bun.lockb | Bin 0 -> 2630 bytes bench/react-hello-world/package.json | 16 ++++++ docs/index.md | 2 +- docs/nav.ts | 4 +- .../project/{profiling.md => benchmarking.md} | 49 +++++++++++------- docs/runtime/hot.md | 16 +++++- docs/runtime/plugins.md | 14 ++++- packages/bun-types/bun.d.ts | 2 +- packages/bun-types/tests/array.test-d.ts | 5 +- 9 files changed, 78 insertions(+), 30 deletions(-) create mode 100755 bench/react-hello-world/bun.lockb create mode 100644 bench/react-hello-world/package.json rename docs/project/{profiling.md => benchmarking.md} (73%) diff --git a/bench/react-hello-world/bun.lockb b/bench/react-hello-world/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..6fd04cd03b6ce1bba231a012263d937dbc222209 GIT binary patch literal 2630 zcmY#Z)GsYA(of3F(@)JSQ%EY!;{sycoc!eMw9K4T-L(9o+{6;yG6OCq1_p-uyHD=b zC9*wG|gGlzKs*82ng696axp8W{1)ZP}+kP$^cV> z3=9oG%mCEfP?VaOTmodb05K1cU%&_vUk8=nY9eGS@WwK_TEhBQW_w85gy%QzNlrL2 zh3R@>!*oX@?H4P$_^(}KD+qeLEPG1Q`Y@n&iU7#hAYcf@!9e#20cmEC02D|8X>p*q z0Mln32i7bDm^h4v>AwmT@TNe&0?;E4K>Z;9u)qu_f`-{&3Djr>)K7|Dknu46n}Pa4 z{s)OkfCwlcF&DQO|Yk42OgKpH1SyzQ3&;y7#;)&Px4z?%z=>Eku)t;L)rfi8oY3kz3}V_Bvzi9T4i*Y^49_FDl$>$dUOtURhBpY`oi zZm&Uj^oR7d*Dw4F&0HwAaf0!c#V_YZU+=XJ$apNUq51t>9<|J<&Av$HBBxE3>Dn7@ zvi?W@$e#Lg8^``DMWr&=&9yX`)L3lZP3*hzB>t0=a_pqOeYHaO_QjaWF&laF-G3b$ zqS$Bh!Mo5^i|a0uxyb2;#r0B1T{z3#qsCgwT29HYolI^2v1fbUifTrQrQw8-mj7E*;;?&%-)FM6Cijvf#yu_T~ zlA_GKbUOt@M9^5kgJuQPq;=3b6BsMNP&C9Ah(Mo!{imCenv;{STb^H(lL8S)$FXDzre_wH6jkcw735|W>*W`v1BHNftS(Ti zzAl2TYiOZotY@H$!#KT?^s1cv{NhwyU^5{ztBR%4UbXC&IX!@%+)p21Ns7_KMv@3Jp+ZB8ihEJh(T7dZb^Q2YF;rOgW+m`ro!2} ZVC@8)1P>pePB>c^f0%-d1SW(*2>@03Gi3k( literal 0 HcmV?d00001 diff --git a/bench/react-hello-world/package.json b/bench/react-hello-world/package.json new file mode 100644 index 000000000000..b114852054b0 --- /dev/null +++ b/bench/react-hello-world/package.json @@ -0,0 +1,16 @@ +{ + "name": "react-hello-world", + "version": "1.0.0", + "description": "", + "main": "react-hello-world.node.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "Colin McDonnell", + "license": "ISC", + "dependencies": { + "react": "next", + "react-dom": "next" + } +} diff --git a/docs/index.md b/docs/index.md index be9051a9ffbc..0426981c0ccf 100644 --- a/docs/index.md +++ b/docs/index.md @@ -21,7 +21,7 @@ $ bunx cowsay "Hello, world!" # execute a package ### Get started -{% block className="gap-[15px] grid grid-flow-row grid-cols-1 md:grid-cols-2" %} +{% block className="gap-[10px] grid grid-flow-row grid-cols-1 md:grid-cols-2" %} {% arrowbutton href="/docs/installation" text="Install Bun" /%} {% arrowbutton href="/docs/quickstart" text="Do the quickstart" /%} {% arrowbutton href="/docs/cli/install" text="Install a package" /%} diff --git a/docs/nav.ts b/docs/nav.ts index c5ff4b426b37..dfc9ee0d21f9 100644 --- a/docs/nav.ts +++ b/docs/nav.ts @@ -180,8 +180,8 @@ export default { page("project/configuration", "Configuration", { description: `Bun's runtime is configurable with environment variables and the bunfig.toml config file.`, }), - page("project/profiling", "Profiling", { - description: `Bun is designed for performance. Learn how to benchmark and profile Bun yourself.`, + page("project/benchmarking", "Benchmarking", { + description: `Bun is designed for performance. Learn how to benchmark Bun yourself.`, }), page("project/developing", "Development", { description: "Learn how to contribute to Bun and get your local development environment up and running.", diff --git a/docs/project/profiling.md b/docs/project/benchmarking.md similarity index 73% rename from docs/project/profiling.md rename to docs/project/benchmarking.md index d734588b2366..0e3578237710 100644 --- a/docs/project/profiling.md +++ b/docs/project/benchmarking.md @@ -1,19 +1,25 @@ +Bun is designed for speed. Hot paths are extensively profiled and benchmarked. The source code for all of Bun's public benchmarks can be found in the [`/bench`](https://github.com/oven-sh/bun/tree/main/bench) directory of the Bun repo. + +## Measuring time + To precisely measure time, Bun offers two runtime APIs functions: -1. The web-standard [`performance.now()`](https://developer.mozilla.org/en-US/docs/Web/API/Performance/now) function +1. The Web-standard [`performance.now()`](https://developer.mozilla.org/en-US/docs/Web/API/Performance/now) function 2. `Bun.nanoseconds()` which is similar to `performance.now()` except it returns the current time since the application started in nanoseconds. You can use `performance.timeOrigin` to convert this to a Unix timestamp. -## Benchmarking `Bun.serve` +## Benchmarking tools -You will need an HTTP client which is at least as fast as `Bun.serve()`. +When writing your own benchmarks, it's important to choose the right tool. -That means popular Node.js-based benchmarking tools like **autocannon is not fast enough**. +- For microbenchmarks, a great general-purpose tool is [`mitata`](https://github.com/evanwashere/mitata). +- For load testing, you *must use* an HTTP benchmarking tool that is at least as fast as `Bun.serve()`, or your results will be skewed. Some popular Node.js-based benchmarking tools like [`autocannon`](https://github.com/mcollina/autocannon) are not fast enough. We recommend one of the following: + - [`bombardier`](https://github.com/codesenberg/bombardier) + - [`oha`](https://github.com/hatoo/oha) + - [`http_load_test`](https://github.com/uNetworking/uSockets/blob/master/examples/http_load_test.c) +- For benchmarking scripts or CLI commands, we recommend [`hyperfine`](https://github.com/sharkdp/hyperfine). It's an easy way to compare Recommended HTTP clients: -- [`bombardier`](https://github.com/codesenberg/bombardier) -- [`oha`](https://github.com/hatoo/oha) -- [`http_load_test`](https://github.com/uNetworking/uSockets/blob/master/examples/http_load_test.c) ## Measuring memory usage @@ -26,8 +32,9 @@ The `bun:jsc` module exposes a few functions for measuring memory usage: ```ts import { heapStats } from "bun:jsc"; console.log(heapStats()); - -// will show something like this: +``` +{% details summary="View example statistics" %} +```ts { heapSize: 1657575, heapCapacity: 2872775, @@ -129,23 +136,18 @@ console.log(heapStats()); } } ``` +{% /details %} JavaScript is a garbage-collected language, not reference counted. It's normal and correct for objects to not be freed immediately in all cases, though it's not normal for objects to never be freed. -You can force garbage collection to run manually by calling: +Tp force garbage collection to run manually: ```js -const synchronously = true; -Bun.gc(synchronously); +Bun.gc(true); // synchronous +Bun.gc(false); // asynchronous ``` -### JavaScript heap snapshot - -Heap snapshots let you inspect what objects are not being freed. You can use the `bun:jsc` module to take a heap snapshot and then view it with Safari or WebKit GTK developer tools. - -{% image alt="image" src="https://user-images.githubusercontent.com/709451/204429337-b0d8935f-3509-4071-b991-217794d1fb27.png" /%} - -To generate a heap snapshot: +Heap snapshots let you inspect what objects are not being freed. You can use the `bun:jsc` module to take a heap snapshot and then view it with Safari or WebKit GTK developer tools. To generate a heap snapshot: ```ts import { generateHeapSnapshot } from "bun"; @@ -161,7 +163,14 @@ To view the snapshot, open the `heap.json` file in Safari's Developer Tools (or 3. Click "JavaScript Allocations" in the menu on the left. It might not be visible until you click the pencil icon to show all the timelines 4. Click "Import" and select your heap snapshot JSON -![image](https://user-images.githubusercontent.com/709451/204428943-ba999e8f-8984-4f23-97cb-b4e3e280363e.png) + +{% image alt="Import heap json" src="https://user-images.githubusercontent.com/709451/204428943-ba999e8f-8984-4f23-97cb-b4e3e280363e.png" caption="Importing a heap snapshot" /%} + +Once imported, you should see something like this: + + +{% image alt="Viewing heap snapshot in Safari" src="https://user-images.githubusercontent.com/709451/204429337-b0d8935f-3509-4071-b991-217794d1fb27.png" caption="Viewing heap snapshot in Safari Dev Tools" /%} + ### Native heap stats diff --git a/docs/runtime/hot.md b/docs/runtime/hot.md index 547728f4d3b4..96571453cb3f 100644 --- a/docs/runtime/hot.md +++ b/docs/runtime/hot.md @@ -10,14 +10,20 @@ $ bun --hot server.ts Starting from the entrypoint (`server.ts` in the example above), Bun builds a registry of all imported source files (excluding those in `node_modules`) and watches them for changes. When a change is detected, Bun performs a "soft reload". All files are re-evaluated, but all global state (notably, the `globalThis` object) is persisted. ```ts#server.ts +// make TypeScript happy +declare global { + var count: number; +} + globalThis.count = globalThis.count ?? 0; console.log(`Reloaded ${globalThis.count} times`); globalThis.count++; +// prevent `bun run` from exiting setInterval(function () {}, 1000000); ``` -If you run this file with `bun --hot server.ts`, you'll see the reload count increment every time you save the file. The call to `setInterval` is there to prevent the process from exiting. +If you run this file with `bun --hot server.ts`, you'll see the reload count increment every time you save the file. ```bash $ bun --hot index.ts @@ -53,7 +59,13 @@ Unlike an explicit call to `Bun.serve`, the object-based syntax works out of the For more fine-grained control, you can use the `Bun.serve` API directly and handle the server reloading manually. ```ts#server.ts -import type {Serve} from "bun"; +import type {Serve, Server} from "bun"; + +// make TypeScript happy +declare global { + var count: number; + var server: Server; +} globalThis.count = globalThis.count ?? 0; globalThis.count++; diff --git a/docs/runtime/plugins.md b/docs/runtime/plugins.md index de8885507e85..911d0ba1df8b 100644 --- a/docs/runtime/plugins.md +++ b/docs/runtime/plugins.md @@ -19,7 +19,15 @@ plugin({ }); ``` -To consume this plugin, import it at the top of your project's entrypoint, before any application code is imported. +To consume this plugin, add this file to the `preload` option in your [`bunfig.toml`](/docs/project/configuration). Bun automatically loads the files/modules specified in `preload` before running a file. + +```toml +preload = ["./yamlPlugin.ts"] +``` + +{% details summary="Usage eithout preload" %} + +Alternatively, you can import this file manually at the top of your project's entrypoint, before any application code is imported. ```ts#app.ts import "./yamlPlugin.ts"; @@ -28,6 +36,10 @@ import { config } from "./config.yml"; console.log(config); ``` +{% /details %} + +## Third party plugins + By convention, third-party plugins intended for consumption should export a factory function that accepts some configuration and returns a plugin object. ```ts diff --git a/packages/bun-types/bun.d.ts b/packages/bun-types/bun.d.ts index 5a054f576672..19b46d60176b 100644 --- a/packages/bun-types/bun.d.ts +++ b/packages/bun-types/bun.d.ts @@ -2491,7 +2491,7 @@ declare module "bun" { * * "css" will be added in a future version of Bun. */ - loader: "js" | "jsx" | "ts" | "tsx"; + loader: "js" | "jsx" | "ts" | "tsx" | "json" | "toml"; } interface OnLoadResultObject { diff --git a/packages/bun-types/tests/array.test-d.ts b/packages/bun-types/tests/array.test-d.ts index ac04c6b3410e..65295b3b154a 100644 --- a/packages/bun-types/tests/array.test-d.ts +++ b/packages/bun-types/tests/array.test-d.ts @@ -1,5 +1,3 @@ -import { expectType } from "tsd"; - async function* listReleases() { for (let page = 1; ; page++) { const response = await fetch( @@ -16,4 +14,5 @@ async function* listReleases() { } const releases = await Array.fromAsync(listReleases()); -expectType<{ data: string }[]>(releases); + +export {}; From f4a2f04bf21c022ea6daf068bee5bf24f59a472f Mon Sep 17 00:00:00 2001 From: Colin McDonnell Date: Tue, 7 Mar 2023 18:07:53 -0800 Subject: [PATCH 054/255] Fix typo --- docs/runtime/plugins.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/runtime/plugins.md b/docs/runtime/plugins.md index 911d0ba1df8b..a7edb0c7b328 100644 --- a/docs/runtime/plugins.md +++ b/docs/runtime/plugins.md @@ -25,7 +25,7 @@ To consume this plugin, add this file to the `preload` option in your [`bunfig.t preload = ["./yamlPlugin.ts"] ``` -{% details summary="Usage eithout preload" %} +{% details summary="Usage without preload" %} Alternatively, you can import this file manually at the top of your project's entrypoint, before any application code is imported. From 05a0d4a5438a89e9e6950d5374ed739902ecaf91 Mon Sep 17 00:00:00 2001 From: Colin McDonnell Date: Tue, 7 Mar 2023 18:43:15 -0800 Subject: [PATCH 055/255] Fix links --- docs/project/developing.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/project/developing.md b/docs/project/developing.md index 49c763680962..50da312a4413 100644 --- a/docs/project/developing.md +++ b/docs/project/developing.md @@ -13,7 +13,7 @@ To develop on Linux/Windows, [Docker](https://www.docker.com) is required. If us If you're using VSCode, you'll need to have the [Dev Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) extension installed. -To get started, open VS Code in the `bun` repository. The first time you try to open the dev container, the extension will automatically build it for you, based on [`Dockerfile.devcontainer`](Dockerfile.devcontainer). +To get started, open VS Code in the `bun` repository. The first time you try to open the dev container, the extension will automatically build it for you, based on [`Dockerfile.devcontainer`](https://github.com/oven-sh/bun/blob/main/Dockerfile.devcontainer). To open the dev container, open the command palette (`Ctrl` + `Shift` + `P`) and run: `Dev Containers: Reopen in Container`. To later rebuild it (only needed when the devcontainer itself changes, not the Bun code), run: `Dev Containers: Rebuild and Reopen in Container`. @@ -180,9 +180,9 @@ This ensures that the types for Zig and the types for C++ match up correctly, by TypeScript files that end with `*.classes.ts` are another code generation script. They generate C++ boilerplate for classes implemented in Zig. The generated code lives in: -- [src/bun.js/bindings/ZigGeneratedClasses.cpp](src/bun.js/bindings/ZigGeneratedClasses.cpp) -- [src/bun.js/bindings/ZigGeneratedClasses.h](src/bun.js/bindings/ZigGeneratedClasses.h) -- [src/bun.js/bindings/generated_classes.zig](src/bun.js/bindings/generated_classes.zig) +- [src/bun.js/bindings/ZigGeneratedClasses.cpp](https://github.com/oven-sh/bun/tree/main/src/bun.js/bindings/ZigGeneratedClasses.cpp) +- [src/bun.js/bindings/ZigGeneratedClasses.h](https://github.com/oven-sh/bun/tree/main/src/bun.js/bindings/ZigGeneratedClasses.h) +- [src/bun.js/bindings/generated_classes.zig](https://github.com/oven-sh/bun/tree/main/src/bun.js/bindings/generated_classes.zig) To generate the code, run: ```bash From f780ac3f26b4264a0489a9bf13132470d2312b4c Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Tue, 7 Mar 2023 18:44:01 -0800 Subject: [PATCH 056/255] run prettier --- test/js/deno/abort/abort-controller.test.ts | 2 +- test/js/deno/html/blob.test.ts | 5 +---- test/js/deno/resources/imports.json | 5 +---- test/js/deno/resources/tests.json | 4 ++-- test/js/deno/scripts/postinstall.ts | 11 +++-------- 5 files changed, 8 insertions(+), 19 deletions(-) diff --git a/test/js/deno/abort/abort-controller.test.ts b/test/js/deno/abort/abort-controller.test.ts index ba386d021c62..3aae2b623c99 100644 --- a/test/js/deno/abort/abort-controller.test.ts +++ b/test/js/deno/abort/abort-controller.test.ts @@ -18,7 +18,7 @@ Deno.test(function signalCallsOnabort() { const controller = new AbortController(); const { signal } = controller; let called = false; - signal.onabort = (evt) => { + signal.onabort = evt => { assert(evt); assertEquals(evt.type, "abort"); called = true; diff --git a/test/js/deno/html/blob.test.ts b/test/js/deno/html/blob.test.ts index 0b50c8452eb4..af1e7f6e8d2d 100644 --- a/test/js/deno/html/blob.test.ts +++ b/test/js/deno/html/blob.test.ts @@ -109,9 +109,6 @@ Deno.test(function blobConstructorNameIsBlob() { Deno.test(function blobCustomInspectFunction() { const blob = new Blob(); - assertEquals( - Deno.inspect(blob), - `Blob { size: 0, type: "" }`, - ); + assertEquals(Deno.inspect(blob), `Blob { size: 0, type: "" }`); assertStringIncludes(Deno.inspect(Blob.prototype), "Blob"); }); diff --git a/test/js/deno/resources/imports.json b/test/js/deno/resources/imports.json index 0c75b5739a1e..6b044a9a55d2 100644 --- a/test/js/deno/resources/imports.json +++ b/test/js/deno/resources/imports.json @@ -1,4 +1 @@ -[ - "test_util.ts", - "test_util/std/bytes/concat.ts" -] +["test_util.ts", "test_util/std/bytes/concat.ts"] diff --git a/test/js/deno/resources/tests.json b/test/js/deno/resources/tests.json index 1d7e0f52acf7..067cb7205b96 100644 --- a/test/js/deno/resources/tests.json +++ b/test/js/deno/resources/tests.json @@ -5,6 +5,6 @@ }, { "path": "html/blob.test.ts", - "remotePath": "unit/blob_test.ts" + "remotePath": "unit/blob_test.ts" } -] \ No newline at end of file +] diff --git a/test/js/deno/scripts/postinstall.ts b/test/js/deno/scripts/postinstall.ts index 4031b29df9a6..bca1e6582263 100644 --- a/test/js/deno/scripts/postinstall.ts +++ b/test/js/deno/scripts/postinstall.ts @@ -5,21 +5,16 @@ import tests from "../resources/tests.json"; for (const test of tests) { const path = join(import.meta.dir, "..", test.path); - const url = new URL( - test.remotePath, - "https://raw.githubusercontent.com/denoland/deno/main/cli/tests/" - ); + const url = new URL(test.remotePath, "https://raw.githubusercontent.com/denoland/deno/main/cli/tests/"); const response = await fetch(url); console.log(response.status, url.toString(), "->", test.path); if (!response.ok) { - throw new Error( - `Failed to download from GitHub: ${url} [status: ${response.status}]` - ); + throw new Error(`Failed to download from GitHub: ${url} [status: ${response.status}]`); } let body = await response.text(); for (const query of imports) { const pattern = new RegExp(`"(.*${query})"`, "gmi"); - body = body.replace(pattern, "\"deno:harness\""); + body = body.replace(pattern, '"deno:harness"'); } const src = `// Updated: ${response.headers.get("Date")} // URL: ${url} From ac5874b16753e73e2952807c836dc2cb0e517f7c Mon Sep 17 00:00:00 2001 From: Colin McDonnell Date: Tue, 7 Mar 2023 21:05:37 -0800 Subject: [PATCH 057/255] Document openInEditor --- docs/api/http.md | 4 +++- docs/api/utils.md | 26 ++++++++++++++++++++++++++ docs/runtime/hot.md | 4 +++- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/docs/api/http.md b/docs/api/http.md index 4cb55349a058..3e11299dd3b1 100644 --- a/docs/api/http.md +++ b/docs/api/http.md @@ -134,11 +134,13 @@ Bun.serve({ Thus far, the examples on this page have used the explicit `Bun.serve` API. Bun also supports an alternate syntax. ```ts#server.ts +import {type Serve} from "bun"; + export default { fetch(req) { return new Response(`Bun!`); }, -}; +} satisfies Serve; ``` Instead of passing the server options into `Bun.serve`, export it. This file can be executed as-is; when Bun runs a file with a `default` export containing a `fetch` handler, it passes it into `Bun.serve` under the hood. diff --git a/docs/api/utils.md b/docs/api/utils.md index dcd9c01284f7..ce3d0d6f977c 100644 --- a/docs/api/utils.md +++ b/docs/api/utils.md @@ -118,3 +118,29 @@ test("peek.status", () => { expect(peek.status(rejected)).toBe("rejected"); }); ``` + +## `Bun.openInEditor` + +Open a file in your default editor. Bun auto-detects your editor via the `$VISUAL` or `$EDITOR` environment variables. + +```ts +const currentFile = import.meta.url; +Bun.openInEditor(currentFile); +``` + +You can override this via the `debug.editor` setting in your [`bunfig.toml`](/docs/project/configuration) + +```toml-diff#bunfig.toml ++ [debug] ++ editor = "code" +``` + +Or specify an editor with the `editor` param. You can also specify a line and column number. + +```ts +Bun.openInEditor(import.meta.url, { + editor: "vscode", // or "subl" + line: 10, + column: 5, +}) +``` diff --git a/docs/runtime/hot.md b/docs/runtime/hot.md index 96571453cb3f..a66534949066 100644 --- a/docs/runtime/hot.md +++ b/docs/runtime/hot.md @@ -39,6 +39,8 @@ Traditional file watchers like `nodemon` restart the entire process, so HTTP ser Bun provides the following simplified API for implementing HTTP servers. Refer to [API > HTTP](/docs/api/http) for full details. ```ts#server.ts +import {type Serve} from "bun"; + globalThis.count = globalThis.count ?? 0; globalThis.count++; @@ -47,7 +49,7 @@ export default { return new Response(`Reloaded ${globalThis.count} times`); }, port: 3000, -}; +} satisfies Serve; ``` The file above is simply exporting an object with a `fetch` handler defined. When this file is executed, Bun interprets this as an HTTP server and passes the exported object into `Bun.serve`. From bd1e76c096416e143632d1aed17c43fe39225e53 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Wed, 8 Mar 2023 08:29:29 +0200 Subject: [PATCH 058/255] improve `Buffer` compatibility with Node.js (#2341) * improve `Buffer` compatibility with Node.js * use `memmove()` allow `encoding` to be `undefined` --- src/baby_list.zig | 2 +- src/base64/base64.zig | 92 +- src/bun.js/bindings/Buffer.h | 2 +- src/bun.js/bindings/JSBuffer.cpp | 574 ++--- src/bun.js/bindings/JSBufferEncodingType.cpp | 5 +- src/bun.js/bindings/headers-handwritten.h | 4 +- .../cpp/JSBufferPrototypeBuiltins.cpp | 345 ++- .../builtins/cpp/JSBufferPrototypeBuiltins.h | 81 +- src/bun.js/builtins/js/JSBufferPrototype.js | 272 ++- src/bun.js/node/buffer.zig | 114 +- src/bun.js/webcore/base64.zig | 445 ---- src/bun.js/webcore/body.zig | 1 + src/bun.js/webcore/encoding.zig | 154 +- src/bun.js/webcore/streams.zig | 2 +- src/http/websocket_http_client.zig | 2 +- src/napi/napi.zig | 14 +- src/string_immutable.zig | 44 +- src/string_mutable.zig | 2 + test/js/node/buffer.test.js | 2132 ++++++++--------- 19 files changed, 2115 insertions(+), 2172 deletions(-) delete mode 100644 src/bun.js/webcore/base64.zig diff --git a/src/baby_list.zig b/src/baby_list.zig index 8a3d9af03d09..38f0caf1ee55 100644 --- a/src/baby_list.zig +++ b/src/baby_list.zig @@ -243,7 +243,7 @@ pub fn BabyList(comptime Type: type) type { const orig_len = list_.items.len; var slice_ = list_.items.ptr[orig_len..list_.capacity]; - const result = strings.copyUTF16IntoUTF8WithBuffer(slice_, []const u16, remain, trimmed, out_len); + const result = strings.copyUTF16IntoUTF8WithBuffer(slice_, []const u16, remain, trimmed, out_len, true); remain = remain[result.read..]; list_.items.len += @as(usize, result.written); if (result.read == 0 or result.written == 0) break; diff --git a/src/base64/base64.zig b/src/base64/base64.zig index daf5b228ecaa..0cd16fb8ca16 100644 --- a/src/base64/base64.zig +++ b/src/base64/base64.zig @@ -5,34 +5,28 @@ pub const DecodeResult = struct { fail: bool = false, }; -pub fn decode(destination: []u8, source: []const u8) DecodeResult { - var wrote: usize = 0; - const result = zig_base64.standard.decoderWithIgnore(&[_]u8{ - ' ', - '\n', - '\r', - '\t', +const mixed_decoder = brk: { + var decoder = zig_base64.standard.decoderWithIgnore("\xff \t\r\n" ++ [_]u8{ std.ascii.control_code.vt, - }).decode(destination, source, &wrote) catch { - return .{ - .written = wrote, - .fail = true, - }; - }; + std.ascii.control_code.ff, + }); - return .{ .written = result, .fail = false }; -} + for (zig_base64.url_safe_alphabet_chars[62..], 62..) |c, i| { + decoder.decoder.char_to_index[c] = @intCast(u8, i); + } -pub fn decodeURLSafe(destination: []u8, source: []const u8) DecodeResult { + break :brk decoder; +}; + +pub fn decode(destination: []u8, source: []const u8) DecodeResult { var wrote: usize = 0; - const result = urlsafe.decode(destination, source, &wrote) catch { + mixed_decoder.decode(destination, source, &wrote) catch { return .{ .written = wrote, .fail = true, }; }; - - return .{ .written = result, .fail = false }; + return .{ .written = wrote, .fail = false }; } pub fn encode(destination: []u8, source: []const u8) usize { @@ -58,14 +52,6 @@ pub fn encodeLen(source: anytype) usize { return zig_base64.standard.Encoder.calcSize(source.len); } -pub const urlsafe = zig_base64.Base64DecoderWithIgnore.init( - zig_base64.url_safe_alphabet_chars, - null, - "= \t\r\n" ++ [_]u8{ std.ascii.control_code.vt, std.ascii.control_code.ff }, -); - -pub const urlsafeEncoder = zig_base64.url_safe_no_pad.Encoder; - // This is just std.base64 copy-pasted // with support for returning how many bytes were decoded const zig_base64 = struct { @@ -118,7 +104,7 @@ const zig_base64 = struct { pub const url_safe_alphabet_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".*; fn urlSafeBase64DecoderWithIgnore(ignore: []const u8) Base64DecoderWithIgnore { - return Base64DecoderWithIgnore.init(url_safe_alphabet_chars, null, ignore); + return Base64DecoderWithIgnore.init(url_safe_alphabet_chars, '=', ignore); } /// URL-safe Base64 codecs, with padding @@ -316,7 +302,7 @@ const zig_base64 = struct { /// Return the maximum possible decoded size for a given input length - The actual length may be less if the input includes padding /// `InvalidPadding` is returned if the input length is not valid. - pub fn calcSizeUpperBound(decoder_with_ignore: *const Base64DecoderWithIgnore, source_len: usize) Error!usize { + pub fn calcSizeUpperBound(decoder_with_ignore: *const Base64DecoderWithIgnore, source_len: usize) usize { var result = source_len / 4 * 3; if (decoder_with_ignore.decoder.pad_char == null) { const leftover = source_len % 4; @@ -329,7 +315,7 @@ const zig_base64 = struct { /// Invalid padding results in error.InvalidPadding. /// Decoding more data than can fit in dest results in error.NoSpaceLeft. See also ::calcSizeUpperBound. /// Returns the number of bytes written to dest. - pub fn decode(decoder_with_ignore: *const Base64DecoderWithIgnore, dest: []u8, source: []const u8, wrote: *usize) Error!usize { + pub fn decode(decoder_with_ignore: *const Base64DecoderWithIgnore, dest: []u8, source: []const u8, wrote: *usize) Error!void { const decoder = &decoder_with_ignore.decoder; var acc: u12 = 0; var acc_len: u4 = 0; @@ -337,16 +323,21 @@ const zig_base64 = struct { var leftover_idx: ?usize = null; defer { - wrote.* = leftover_idx orelse dest_idx; + wrote.* = dest_idx; } for (source, 0..) |c, src_idx| { if (decoder_with_ignore.char_is_ignored[c]) continue; const d = decoder.char_to_index[c]; if (d == Base64Decoder.invalid_char) { - if (decoder.pad_char == null or c != decoder.pad_char.?) return error.InvalidCharacter; - leftover_idx = src_idx; - break; + if (decoder.pad_char) |pad_char| { + if (c == pad_char) { + leftover_idx = src_idx; + break; + } + } + if (decoder_with_ignore.char_is_ignored[Base64Decoder.invalid_char]) continue; + return error.InvalidCharacter; } acc = (acc << 6) + d; acc_len += 6; @@ -357,27 +348,26 @@ const zig_base64 = struct { dest_idx += 1; } } - if (acc_len > 4 or (acc & (@as(u12, 1) << acc_len) - 1) != 0) { - return error.InvalidPadding; - } - const padding_len = acc_len / 2; - if (leftover_idx == null) { - if (decoder.pad_char != null and padding_len != 0) return error.InvalidPadding; - return dest_idx; - } - var leftover = source[leftover_idx.?..]; + if (acc_len > 4 or (acc & (@as(u12, 1) << acc_len) - 1) != 0) return error.InvalidPadding; + if (decoder.pad_char) |pad_char| { - var padding_chars: usize = 0; - for (leftover) |c| { - if (decoder_with_ignore.char_is_ignored[c]) continue; - if (c != pad_char) { - return if (c == Base64Decoder.invalid_char) error.InvalidCharacter else error.InvalidPadding; + const padding_len = acc_len / 2; + + if (leftover_idx) |idx| { + var leftover = source[idx..]; + var padding_chars: usize = 0; + for (leftover) |c| { + if (decoder_with_ignore.char_is_ignored[c]) continue; + if (c != pad_char) { + return if (c == Base64Decoder.invalid_char) error.InvalidCharacter else error.InvalidPadding; + } + padding_chars += 1; } - padding_chars += 1; + if (padding_chars != padding_len) return error.InvalidPadding; + } else if (padding_len > 0) { + return error.InvalidPadding; } - if (padding_chars != padding_len) return error.InvalidPadding; } - return dest_idx; } }; diff --git a/src/bun.js/bindings/Buffer.h b/src/bun.js/bindings/Buffer.h index fecc627a1c3b..4f15835135ee 100644 --- a/src/bun.js/bindings/Buffer.h +++ b/src/bun.js/bindings/Buffer.h @@ -16,7 +16,7 @@ extern "C" JSC::EncodedJSValue JSBuffer__bufferFromLength(JSC::JSGlobalObject* l extern "C" JSC::EncodedJSValue JSBuffer__bufferFromPointerAndLengthAndDeinit(JSC::JSGlobalObject* lexicalGlobalObject, char* ptr, size_t length, void* ctx, JSTypedArrayBytesDeallocator bytesDeallocator); extern "C" JSC::EncodedJSValue Bun__encoding__toString(const uint8_t* input, size_t len, JSC::JSGlobalObject* globalObject, Encoding encoding); extern "C" JSC::EncodedJSValue Bun__encoding__toStringUTF8(const uint8_t* input, size_t len, JSC::JSGlobalObject* globalObject); -extern "C" void Bun__Buffer_fill(ZigString*, void*, size_t, WebCore::BufferEncodingType); +extern "C" bool Bun__Buffer_fill(ZigString*, void*, size_t, WebCore::BufferEncodingType); namespace WebCore { diff --git a/src/bun.js/bindings/JSBuffer.cpp b/src/bun.js/bindings/JSBuffer.cpp index 2ce07617aecd..9b3854b95410 100644 --- a/src/bun.js/bindings/JSBuffer.cpp +++ b/src/bun.js/bindings/JSBuffer.cpp @@ -133,6 +133,35 @@ static int normalizeCompareVal(int val, size_t a_length, size_t b_length) return val; } +static inline uint32_t parseIndex(JSC::JSGlobalObject* lexicalGlobalObject, JSC::ThrowScope& scope, JSValue arg) +{ + if (auto num = arg.tryGetAsUint32Index()) + return num.value(); + + if (arg.isNumber()) + throwRangeError(lexicalGlobalObject, scope, "Invalid array length"_s); + else + throwTypeError(lexicalGlobalObject, scope, "Expected number"_s); + + return 0; +} + +static inline WebCore::BufferEncodingType parseEncoding(JSC::JSGlobalObject* lexicalGlobalObject, JSC::ThrowScope& scope, JSValue arg) +{ + if (UNLIKELY(!arg.isString())) { + throwTypeError(lexicalGlobalObject, scope, "Expected string"_s); + return WebCore::BufferEncodingType::utf8; + } + + std::optional encoded = parseEnumeration(*lexicalGlobalObject, arg); + if (UNLIKELY(!encoded)) { + throwTypeError(lexicalGlobalObject, scope, "Invalid encoding"_s); + return WebCore::BufferEncodingType::utf8; + } + + return encoded.value(); +} + namespace WebCore { using namespace JSC; @@ -190,8 +219,11 @@ using namespace JSC; static inline EncodedJSValue writeToBuffer(JSC::JSGlobalObject* lexicalGlobalObject, JSArrayBufferView* castedThis, JSString* str, uint32_t offset, uint32_t length, BufferEncodingType encoding) { + if (UNLIKELY(str->length() == 0)) + return JSC::JSValue::encode(JSC::jsNumber(0)); + auto view = str->tryGetValue(lexicalGlobalObject); - int64_t written = 0; + size_t written = 0; switch (encoding) { case WebCore::BufferEncodingType::utf8: @@ -358,9 +390,6 @@ static inline JSC::EncodedJSValue constructBufferFromStringAndEncoding(JSC::JSGl RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(jsUndefined())); - if (str->length() == 0) - return constructBufferEmpty(lexicalGlobalObject); - if (arg1 && arg1.isString()) { std::optional encoded = parseEnumeration(*lexicalGlobalObject, arg1); if (!encoded) { @@ -371,6 +400,9 @@ static inline JSC::EncodedJSValue constructBufferFromStringAndEncoding(JSC::JSGl encoding = encoded.value(); } + if (str->length() == 0) + return constructBufferEmpty(lexicalGlobalObject); + JSC::EncodedJSValue result = constructFromEncoding(lexicalGlobalObject, str, encoding); RELEASE_AND_RETURN(scope, result); @@ -380,7 +412,12 @@ static inline JSC::EncodedJSValue jsBufferConstructorFunction_allocBody(JSC::JSG { VM& vm = lexicalGlobalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); - auto length = callFrame->uncheckedArgument(0).toInt32(lexicalGlobalObject); + auto lengthArg = callFrame->uncheckedArgument(0); + if (UNLIKELY(!lengthArg.isNumber())) { + throwTypeError(lexicalGlobalObject, scope, "Expected number"_s); + return JSValue::encode(jsUndefined()); + } + auto length = lengthArg.toInt32(lexicalGlobalObject); if (length < 0) { throwRangeError(lexicalGlobalObject, scope, "Invalid array length"_s); return JSValue::encode(jsUndefined()); @@ -392,10 +429,56 @@ static inline JSC::EncodedJSValue jsBufferConstructorFunction_allocBody(JSC::JSG // fill argument if (UNLIKELY(callFrame->argumentCount() > 1)) { auto* uint8Array = JSC::JSUint8Array::createUninitialized(lexicalGlobalObject, subclassStructure, length); - auto value = callFrame->argument(1); - if (!value.isString()) { + if (value.isString()) { + size_t length = uint8Array->byteLength(); + size_t start = 0; + size_t end = length; + WebCore::BufferEncodingType encoding = WebCore::BufferEncodingType::utf8; + if (callFrame->argumentCount() > 2) { + EnsureStillAliveScope arg2 = callFrame->uncheckedArgument(2); + if (!arg2.value().isUndefined()) { + encoding = parseEncoding(lexicalGlobalObject, scope, arg2.value()); + RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(jsUndefined())); + } + } + auto startPtr = uint8Array->typedVector() + start; + auto str_ = value.toWTFString(lexicalGlobalObject); + ZigString str = Zig::toZigString(str_); + + if (UNLIKELY(!Bun__Buffer_fill(&str, startPtr, end - start, encoding))) { + throwTypeError(lexicalGlobalObject, scope, "Failed to decode value"_s); + return JSC::JSValue::encode(jsUndefined()); + } + } else if (auto* view = JSC::jsDynamicCast(value)) { + if (UNLIKELY(view->isDetached())) { + throwVMTypeError(lexicalGlobalObject, scope, "Uint8Array is detached"_s); + return JSValue::encode(jsUndefined()); + } + + size_t length = view->byteLength(); + if (UNLIKELY(length == 0)) { + throwTypeError(lexicalGlobalObject, scope, "Buffer cannot be empty"_s); + return JSC::JSValue::encode(jsUndefined()); + } + + auto* start = uint8Array->typedVector(); + auto* head = start; + size_t remain = uint8Array->byteLength(); + memmove(head, view->vector(), length); + remain -= length; + head += length; + while (remain >= length) { + memcpy(head, start, length); + remain -= length; + head += length; + length <<= 1; + } + if (remain > 0) { + memcpy(head, start, remain); + } + } else { auto value_ = value.toInt32(lexicalGlobalObject) & 0xFF; auto value_uint8 = static_cast(value_); @@ -408,33 +491,9 @@ static inline JSC::EncodedJSValue jsBufferConstructorFunction_allocBody(JSC::JSG auto startPtr = uint8Array->typedVector() + start; auto endPtr = uint8Array->typedVector() + end; memset(startPtr, value_uint8, endPtr - startPtr); - RELEASE_AND_RETURN(scope, JSC::JSValue::encode(uint8Array)); } - { - size_t length = uint8Array->byteLength(); - size_t start = 0; - size_t end = length; - WebCore::BufferEncodingType encoding = WebCore::BufferEncodingType::utf8; - if (callFrame->argumentCount() > 2) { - EnsureStillAliveScope arg2 = callFrame->uncheckedArgument(2); - if (arg2.value().isString()) { - std::optional encoded = parseEnumeration(*lexicalGlobalObject, arg2.value()); - if (!encoded) { - throwTypeError(lexicalGlobalObject, scope, "Invalid encoding"_s); - return JSC::JSValue::encode(jsUndefined()); - } - - encoding = encoded.value(); - } - } - auto startPtr = uint8Array->typedVector() + start; - auto str_ = value.toWTFString(lexicalGlobalObject); - ZigString str = Zig::toZigString(str_); - - Bun__Buffer_fill(&str, startPtr, end - start, encoding); - RELEASE_AND_RETURN(scope, JSC::JSValue::encode(uint8Array)); - } + RELEASE_AND_RETURN(scope, JSC::JSValue::encode(uint8Array)); } else { auto* uint8Array = JSC::JSUint8Array::create(lexicalGlobalObject, subclassStructure, length); if (UNLIKELY(!uint8Array)) { @@ -585,6 +644,10 @@ static inline JSC::EncodedJSValue jsBufferConstructorFunction_compareBody(JSC::J throwVMTypeError(lexicalGlobalObject, throwScope, "Expected Buffer (first argument)"_s); return JSValue::encode(jsUndefined()); } + if (UNLIKELY(castedThis->isDetached())) { + throwVMTypeError(lexicalGlobalObject, throwScope, "Uint8Array (first argument) is detached"_s); + return JSValue::encode(jsUndefined()); + } auto buffer = callFrame->uncheckedArgument(1); JSC::JSArrayBufferView* view = JSC::jsDynamicCast(buffer); @@ -592,13 +655,7 @@ static inline JSC::EncodedJSValue jsBufferConstructorFunction_compareBody(JSC::J throwVMTypeError(lexicalGlobalObject, throwScope, "Expected Buffer (2nd argument)"_s); return JSValue::encode(jsUndefined()); } - if (UNLIKELY(view->isDetached())) { - throwVMTypeError(lexicalGlobalObject, throwScope, "Uint8Array (first argument) is detached"_s); - return JSValue::encode(jsUndefined()); - } - - if (UNLIKELY(castedThis->isDetached())) { throwVMTypeError(lexicalGlobalObject, throwScope, "Uint8Array (second argument) is detached"_s); return JSValue::encode(jsUndefined()); } @@ -611,43 +668,27 @@ static inline JSC::EncodedJSValue jsBufferConstructorFunction_compareBody(JSC::J size_t sourceEndInit = castedThis->byteLength(); size_t sourceEnd = sourceEndInit; - if (callFrame->argumentCount() > 2) { - if (auto targetEnd_ = callFrame->uncheckedArgument(2).tryGetAsUint32Index()) { - targetStart = targetEnd_.value(); - } else { - throwVMTypeError(lexicalGlobalObject, throwScope, "Expected number"_s); - return JSValue::encode(jsUndefined()); - } - - if (callFrame->argumentCount() > 3) { - auto targetEndArgument = callFrame->uncheckedArgument(3); - if (auto targetEnd_ = targetEndArgument.tryGetAsUint32Index()) { - targetEnd = targetEnd_.value(); - } else { - throwVMTypeError(lexicalGlobalObject, throwScope, "Expected number"_s); - return JSValue::encode(jsUndefined()); - } - } - - if (callFrame->argumentCount() > 4) { - auto targetEndArgument = callFrame->uncheckedArgument(4); - if (auto targetEnd_ = targetEndArgument.tryGetAsUint32Index()) { - sourceStart = targetEnd_.value(); - } else { - throwVMTypeError(lexicalGlobalObject, throwScope, "Expected number"_s); - return JSValue::encode(jsUndefined()); - } - } - - if (callFrame->argumentCount() > 5) { - auto targetEndArgument = callFrame->uncheckedArgument(5); - if (auto targetEnd_ = targetEndArgument.tryGetAsUint32Index()) { - sourceEnd = targetEnd_.value(); - } else { - throwVMTypeError(lexicalGlobalObject, throwScope, "Expected number"_s); - return JSValue::encode(jsUndefined()); - } - } + switch (callFrame->argumentCount()) { + default: + sourceEnd = parseIndex(lexicalGlobalObject, throwScope, callFrame->uncheckedArgument(5)); + RETURN_IF_EXCEPTION(throwScope, JSValue::encode(jsUndefined())); + FALLTHROUGH; + case 5: + sourceStart = parseIndex(lexicalGlobalObject, throwScope, callFrame->uncheckedArgument(4)); + RETURN_IF_EXCEPTION(throwScope, JSValue::encode(jsUndefined())); + FALLTHROUGH; + case 4: + targetEnd = parseIndex(lexicalGlobalObject, throwScope, callFrame->uncheckedArgument(3)); + RETURN_IF_EXCEPTION(throwScope, JSValue::encode(jsUndefined())); + FALLTHROUGH; + case 3: + targetStart = parseIndex(lexicalGlobalObject, throwScope, callFrame->uncheckedArgument(2)); + RETURN_IF_EXCEPTION(throwScope, JSValue::encode(jsUndefined())); + break; + case 2: + case 1: + case 0: + break; } targetStart = std::min(targetStart, std::min(targetEnd, targetEndInit)); @@ -822,43 +863,26 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_compareBody(JSC::JSG size_t sourceEndInit = castedThis->byteLength(); size_t sourceEnd = sourceEndInit; - if (callFrame->argumentCount() > 1) { - if (auto targetEnd_ = callFrame->uncheckedArgument(1).tryGetAsUint32Index()) { - targetStart = targetEnd_.value(); - } else { - throwVMTypeError(lexicalGlobalObject, throwScope, "Expected number"_s); - return JSValue::encode(jsUndefined()); - } - - if (callFrame->argumentCount() > 2) { - auto targetEndArgument = callFrame->uncheckedArgument(2); - if (auto targetEnd_ = targetEndArgument.tryGetAsUint32Index()) { - targetEnd = targetEnd_.value(); - } else { - throwVMTypeError(lexicalGlobalObject, throwScope, "Expected number"_s); - return JSValue::encode(jsUndefined()); - } - } - - if (callFrame->argumentCount() > 3) { - auto targetEndArgument = callFrame->uncheckedArgument(3); - if (auto targetEnd_ = targetEndArgument.tryGetAsUint32Index()) { - sourceStart = targetEnd_.value(); - } else { - throwVMTypeError(lexicalGlobalObject, throwScope, "Expected number"_s); - return JSValue::encode(jsUndefined()); - } - } - - if (callFrame->argumentCount() > 4) { - auto targetEndArgument = callFrame->uncheckedArgument(4); - if (auto targetEnd_ = targetEndArgument.tryGetAsUint32Index()) { - sourceEnd = targetEnd_.value(); - } else { - throwVMTypeError(lexicalGlobalObject, throwScope, "Expected number"_s); - return JSValue::encode(jsUndefined()); - } - } + switch (callFrame->argumentCount()) { + default: + sourceEnd = parseIndex(lexicalGlobalObject, throwScope, callFrame->uncheckedArgument(4)); + RETURN_IF_EXCEPTION(throwScope, JSValue::encode(jsUndefined())); + FALLTHROUGH; + case 4: + sourceStart = parseIndex(lexicalGlobalObject, throwScope, callFrame->uncheckedArgument(3)); + RETURN_IF_EXCEPTION(throwScope, JSValue::encode(jsUndefined())); + FALLTHROUGH; + case 3: + targetEnd = parseIndex(lexicalGlobalObject, throwScope, callFrame->uncheckedArgument(2)); + RETURN_IF_EXCEPTION(throwScope, JSValue::encode(jsUndefined())); + FALLTHROUGH; + case 2: + targetStart = parseIndex(lexicalGlobalObject, throwScope, callFrame->uncheckedArgument(1)); + RETURN_IF_EXCEPTION(throwScope, JSValue::encode(jsUndefined())); + break; + case 1: + case 0: + break; } targetStart = std::min(targetStart, std::min(targetEnd, targetEndInit)); @@ -905,31 +929,22 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_copyBody(JSC::JSGlob size_t sourceEndInit = castedThis->byteLength(); size_t sourceEnd = sourceEndInit; - if (callFrame->argumentCount() > 1) { - if (auto targetStart_ = callFrame->uncheckedArgument(1).tryGetAsUint32Index()) { - targetStart = targetStart_.value(); - } else { - throwVMTypeError(lexicalGlobalObject, throwScope, "Expected number"_s); - return JSValue::encode(jsUndefined()); - } - - if (callFrame->argumentCount() > 2) { - if (auto sourceStart_ = callFrame->uncheckedArgument(2).tryGetAsUint32Index()) { - sourceStart = sourceStart_.value(); - } else { - throwVMTypeError(lexicalGlobalObject, throwScope, "Expected number"_s); - return JSValue::encode(jsUndefined()); - } - } - - if (callFrame->argumentCount() > 3) { - if (auto sourceEnd_ = callFrame->uncheckedArgument(3).tryGetAsUint32Index()) { - sourceEnd = sourceEnd_.value(); - } else { - throwVMTypeError(lexicalGlobalObject, throwScope, "Expected number"_s); - return JSValue::encode(jsUndefined()); - } - } + switch (callFrame->argumentCount()) { + default: + sourceEnd = parseIndex(lexicalGlobalObject, throwScope, callFrame->uncheckedArgument(3)); + RETURN_IF_EXCEPTION(throwScope, JSValue::encode(jsUndefined())); + FALLTHROUGH; + case 3: + sourceStart = parseIndex(lexicalGlobalObject, throwScope, callFrame->uncheckedArgument(2)); + RETURN_IF_EXCEPTION(throwScope, JSValue::encode(jsUndefined())); + FALLTHROUGH; + case 2: + targetStart = parseIndex(lexicalGlobalObject, throwScope, callFrame->uncheckedArgument(1)); + RETURN_IF_EXCEPTION(throwScope, JSValue::encode(jsUndefined())); + break; + case 1: + case 0: + break; } targetStart = std::min(targetStart, targetEnd); @@ -993,120 +1008,111 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_fillBody(JSC::JSGlob } auto value = callFrame->uncheckedArgument(0); + const size_t limit = castedThis->byteLength(); + size_t start = 0; + size_t end = limit; + WebCore::BufferEncodingType encoding = WebCore::BufferEncodingType::utf8; + JSValue encodingValue = jsUndefined(); + JSValue offsetValue = jsUndefined(); + JSValue lengthValue = jsUndefined(); - if (!value.isString()) { - auto value_ = value.toInt32(lexicalGlobalObject) & 0xFF; + switch (callFrame->argumentCount()) { + case 4: + encodingValue = callFrame->uncheckedArgument(3); + FALLTHROUGH; + case 3: + lengthValue = callFrame->uncheckedArgument(2); + FALLTHROUGH; + case 2: + offsetValue = callFrame->uncheckedArgument(1); + FALLTHROUGH; + default: + break; + } - auto value_uint8 = static_cast(value_); + if (offsetValue.isUndefined() || offsetValue.isString()) { + encodingValue = offsetValue; + offsetValue = jsUndefined(); + } else if (lengthValue.isString()) { + encodingValue = lengthValue; + lengthValue = jsUndefined(); + } + + if (!encodingValue.isUndefined()) { + encoding = parseEncoding(lexicalGlobalObject, scope, encodingValue); RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(jsUndefined())); + } - auto length = castedThis->byteLength(); - auto start = 0; - auto end = length; - if (callFrame->argumentCount() > 1) { - if (auto start_ = callFrame->uncheckedArgument(1).tryGetAsUint32Index()) { - start = start_.value(); - } else { - return throwVMError(lexicalGlobalObject, scope, createRangeError(lexicalGlobalObject, "start out of range"_s)); - } - if (callFrame->argumentCount() > 2) { - if (auto end_ = callFrame->uncheckedArgument(2).tryGetAsUint32Index()) { - end = end_.value(); - } else { - return throwVMError(lexicalGlobalObject, scope, createRangeError(lexicalGlobalObject, "end out of range"_s)); - } - } - } - if (start > end) { - return throwVMError(lexicalGlobalObject, scope, createRangeError(lexicalGlobalObject, "start out of range"_s)); - } - if (end > length) { - return throwVMError(lexicalGlobalObject, scope, createRangeError(lexicalGlobalObject, "end out of range"_s)); - } - auto startPtr = castedThis->typedVector() + start; - auto endPtr = castedThis->typedVector() + end; - memset(startPtr, value_uint8, endPtr - startPtr); - return JSValue::encode(castedThis); + if (!offsetValue.isUndefined()) { + start = parseIndex(lexicalGlobalObject, scope, offsetValue); + RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(jsUndefined())); } - { - EnsureStillAliveScope value_ = callFrame->argument(0); - - size_t length = castedThis->byteLength(); - size_t start = 0; - size_t end = length; - WebCore::BufferEncodingType encoding = WebCore::BufferEncodingType::utf8; - - JSValue encodingValue = jsUndefined(); - JSValue offsetValue = jsUndefined(); - JSValue lengthValue = jsUndefined(); - - switch (callFrame->argumentCount()) { - case 4: - encodingValue = callFrame->uncheckedArgument(3); - FALLTHROUGH; - case 3: - lengthValue = callFrame->uncheckedArgument(2); - FALLTHROUGH; - case 2: - offsetValue = callFrame->uncheckedArgument(1); - FALLTHROUGH; - default: - break; - } + if (!lengthValue.isUndefined()) { + end = parseIndex(lexicalGlobalObject, scope, lengthValue); + RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(jsUndefined())); + } - if (offsetValue.isUndefined() || offsetValue.isString()) { - encodingValue = offsetValue; - offsetValue = jsUndefined(); - } else if (lengthValue.isString()) { - encodingValue = lengthValue; - lengthValue = jsUndefined(); - } + if (start >= end) { + RELEASE_AND_RETURN(scope, JSValue::encode(castedThis)); + } - if (encodingValue.isString()) { - std::optional encoded = parseEnumeration(*lexicalGlobalObject, encodingValue); - if (!encoded) { - throwTypeError(lexicalGlobalObject, scope, "Invalid encoding"_s); - return JSC::JSValue::encode(jsUndefined()); - } + if (UNLIKELY(end > limit)) { + throwRangeError(lexicalGlobalObject, scope, "end out of range"_s); + return JSC::JSValue::encode(jsUndefined()); + } - encoding = encoded.value(); - } + if (value.isString()) { + auto startPtr = castedThis->typedVector() + start; + auto str_ = value.toWTFString(lexicalGlobalObject); + ZigString str = Zig::toZigString(str_); - if (!offsetValue.isUndefined()) { - if (auto offset = offsetValue.tryGetAsUint32Index()) { - start = offset.value(); - } else { - throwVMError(lexicalGlobalObject, scope, createRangeError(lexicalGlobalObject, "start out of range"_s)); - return JSC::JSValue::encode(jsUndefined()); - } + if (str.len == 0) { + memset(startPtr, 0, end - start); + } else if (UNLIKELY(!Bun__Buffer_fill(&str, startPtr, end - start, encoding))) { + throwTypeError(lexicalGlobalObject, scope, "Failed to decode value"_s); + return JSC::JSValue::encode(jsUndefined()); } + } else if (auto* view = JSC::jsDynamicCast(value)) { + auto* startPtr = castedThis->typedVector() + start; + auto* head = startPtr; + size_t remain = end - start; - if (!lengthValue.isUndefined()) { - if (auto length = lengthValue.tryGetAsUint32Index()) { - end = std::min(static_cast(length.value()), end); - } else { - throwVMError(lexicalGlobalObject, scope, createRangeError(lexicalGlobalObject, "end out of range"_s)); - return JSC::JSValue::encode(jsUndefined()); - } + if (UNLIKELY(view->isDetached())) { + throwVMTypeError(lexicalGlobalObject, scope, "Uint8Array is detached"_s); + return JSValue::encode(jsUndefined()); } - if (start >= end) { - return JSValue::encode(castedThis); + size_t length = view->byteLength(); + if (UNLIKELY(length == 0)) { + throwTypeError(lexicalGlobalObject, scope, "Buffer cannot be empty"_s); + return JSC::JSValue::encode(jsUndefined()); } - auto startPtr = castedThis->typedVector() + start; - auto str_ = value.toWTFString(lexicalGlobalObject); - ZigString str = Zig::toZigString(str_); - - if (str.len > 0) { - Bun__Buffer_fill(&str, startPtr, end - start, encoding); - } else { - memset(startPtr, 0, end - start); + memmove(head, view->vector(), length); + remain -= length; + head += length; + while (remain >= length) { + memcpy(head, startPtr, length); + remain -= length; + head += length; + length <<= 1; + } + if (remain > 0) { + memcpy(head, startPtr, remain); } + } else { + auto value_ = value.toInt32(lexicalGlobalObject) & 0xFF; - RELEASE_AND_RETURN(scope, JSValue::encode(castedThis)); + auto value_uint8 = static_cast(value_); + RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(jsUndefined())); + + auto startPtr = castedThis->typedVector() + start; + auto endPtr = castedThis->typedVector() + end; + memset(startPtr, value_uint8, endPtr - startPtr); } + + RELEASE_AND_RETURN(scope, JSValue::encode(castedThis)); } static int64_t indexOf(const uint8_t* thisPtr, int64_t thisLength, const uint8_t* valuePtr, int64_t valueLength, int64_t byteOffset) @@ -1150,42 +1156,42 @@ static int64_t indexOf(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* int64_t byteOffset = last ? length - 1 : 0; if (callFrame->argumentCount() > 1) { - auto byteOffset_ = callFrame->uncheckedArgument(1).toNumber(lexicalGlobalObject); - RETURN_IF_EXCEPTION(scope, -1); - - if (std::isnan(byteOffset_) || std::isinf(byteOffset_)) { - byteOffset = last ? length - 1 : 0; - } else if (byteOffset_ < 0) { - byteOffset = length + static_cast(byteOffset_); + EnsureStillAliveScope arg1 = callFrame->uncheckedArgument(1); + if (arg1.value().isString()) { + encoding = parseEncoding(lexicalGlobalObject, scope, arg1.value()); + RETURN_IF_EXCEPTION(scope, -1); } else { - byteOffset = static_cast(byteOffset_); - } + auto byteOffset_ = arg1.value().toNumber(lexicalGlobalObject); + RETURN_IF_EXCEPTION(scope, -1); - if (last) { - if (byteOffset < 0) { - return -1; - } else if (byteOffset > length - 1) { - byteOffset = length - 1; - } - } else { - if (byteOffset <= 0) { - byteOffset = 0; - } else if (byteOffset > length - 1) { - return -1; + if (std::isnan(byteOffset_) || std::isinf(byteOffset_)) { + byteOffset = last ? length - 1 : 0; + } else if (byteOffset_ < 0) { + byteOffset = length + static_cast(byteOffset_); + } else { + byteOffset = static_cast(byteOffset_); } - } - if (callFrame->argumentCount() > 2) { - EnsureStillAliveScope encodingValue = callFrame->uncheckedArgument(2); - - if (encodingValue.value().isString()) { - std::optional encoded = parseEnumeration(*lexicalGlobalObject, encodingValue.value()); - if (!encoded) { - throwTypeError(lexicalGlobalObject, scope, "Invalid encoding"_s); - return JSC::JSValue::encode(jsUndefined()); + if (last) { + if (byteOffset < 0) { + return -1; + } else if (byteOffset > length - 1) { + byteOffset = length - 1; + } + } else { + if (byteOffset <= 0) { + byteOffset = 0; + } else if (byteOffset > length - 1) { + return -1; } + } - encoding = encoded.value(); + if (callFrame->argumentCount() > 2) { + EnsureStillAliveScope encodingValue = callFrame->uncheckedArgument(2); + if (!encodingValue.value().isUndefined()) { + encoding = parseEncoding(lexicalGlobalObject, scope, encodingValue.value()); + RETURN_IF_EXCEPTION(scope, -1); + } } } } @@ -1420,6 +1426,7 @@ static inline JSC::EncodedJSValue jsBufferToString(JSC::VM& vm, JSC::JSGlobalObj static inline JSC::EncodedJSValue jsBufferPrototypeFunction_toStringBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation::ClassParameter castedThis) { auto& vm = JSC::getVM(lexicalGlobalObject); + auto scope = DECLARE_THROW_SCOPE(vm); uint32_t offset = 0; uint32_t length = castedThis->length(); uint32_t byteLength = length; @@ -1436,18 +1443,10 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_toStringBody(JSC::JS case 3: case 1: { EnsureStillAliveScope arg1 = callFrame->uncheckedArgument(0); - if (arg1.value().isString()) { - std::optional encoded = parseEnumeration(*lexicalGlobalObject, arg1.value()); - if (!encoded) { - auto scope = DECLARE_THROW_SCOPE(vm); - - throwTypeError(lexicalGlobalObject, scope, "Invalid encoding"_s); - return JSC::JSValue::encode(jsUndefined()); - } - - encoding = encoded.value(); + if (!arg1.value().isUndefined()) { + encoding = parseEncoding(lexicalGlobalObject, scope, arg1.value()); + RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(jsUndefined())); } - if (callFrame->argumentCount() == 1) break; } @@ -1456,8 +1455,6 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_toStringBody(JSC::JS JSC::JSValue arg2 = callFrame->uncheckedArgument(1); int32_t ioffset = arg2.toInt32(lexicalGlobalObject); if (ioffset < 0) { - auto scope = DECLARE_THROW_SCOPE(vm); - throwTypeError(lexicalGlobalObject, scope, "Offset must be a positive integer"_s); return JSC::JSValue::encode(jsUndefined()); } @@ -1524,9 +1521,6 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_writeBody(JSC::JSGlo return JSC::JSValue::encode(jsUndefined()); } - if (str->length() == 0) - return JSC::JSValue::encode(JSC::jsNumber(0)); - JSValue offsetValue = jsUndefined(); JSValue lengthValue = jsUndefined(); JSValue encodingValue = jsUndefined(); @@ -1546,14 +1540,8 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_writeBody(JSC::JSGlo } auto setEncoding = [&]() { - if (encodingValue.isString()) { - std::optional encoded = parseEnumeration(*lexicalGlobalObject, encodingValue); - if (!encoded) { - throwTypeError(lexicalGlobalObject, scope, "Invalid encoding"_s); - return; - } - - encoding = encoded.value(); + if (!encodingValue.isUndefined()) { + encoding = parseEncoding(lexicalGlobalObject, scope, encodingValue); } }; @@ -1570,6 +1558,11 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_writeBody(JSC::JSGlo RELEASE_AND_RETURN(scope, writeToBuffer(lexicalGlobalObject, castedThis, str, offset, length, encoding)); } + if (UNLIKELY(!offsetValue.isNumber())) { + throwTypeError(lexicalGlobalObject, scope, "Invalid offset"_s); + return JSC::JSValue::encode(jsUndefined()); + } + int32_t userOffset = offsetValue.toInt32(lexicalGlobalObject); RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(jsUndefined())); if (userOffset < 0 || userOffset > max) { @@ -1845,11 +1838,15 @@ static const HashTableValue JSBufferPrototypeTableValues[] { "readInt32BE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeReadInt32BECodeGenerator, 1 } }, { "readInt32LE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeReadInt32LECodeGenerator, 1 } }, { "readInt8"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeReadInt8CodeGenerator, 2 } }, + { "readIntBE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeReadIntBECodeGenerator, 1 } }, + { "readIntLE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeReadIntLECodeGenerator, 1 } }, { "readUInt16BE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeReadUInt16BECodeGenerator, 1 } }, { "readUInt16LE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeReadUInt16LECodeGenerator, 1 } }, { "readUInt32BE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeReadUInt32BECodeGenerator, 1 } }, { "readUInt32LE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeReadUInt32LECodeGenerator, 1 } }, { "readUInt8"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeReadUInt8CodeGenerator, 1 } }, + { "readUIntBE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeReadUIntBECodeGenerator, 1 } }, + { "readUIntLE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeReadUIntLECodeGenerator, 1 } }, { "readUint16BE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeReadUInt16BECodeGenerator, 1 } }, { "readUint16LE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeReadUInt16LECodeGenerator, 1 } }, { "readUint32BE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeReadUInt32BECodeGenerator, 1 } }, @@ -1861,6 +1858,7 @@ static const HashTableValue JSBufferPrototypeTableValues[] { "swap32"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBufferPrototypeFunction_swap32, 0 } }, { "swap64"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBufferPrototypeFunction_swap64, 0 } }, { "toJSON"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeToJSONCodeGenerator, 1 } }, + { "toLocaleString"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBufferPrototypeFunction_toString, 4 } }, { "toString"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBufferPrototypeFunction_toString, 4 } }, { "ucs2Slice"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeUcs2SliceCodeGenerator, 2 } }, { "ucs2Write"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeUcs2WriteCodeGenerator, 1 } }, @@ -1886,6 +1884,8 @@ static const HashTableValue JSBufferPrototypeTableValues[] { "writeInt32BE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeWriteInt32BECodeGenerator, 1 } }, { "writeInt32LE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeWriteInt32LECodeGenerator, 1 } }, { "writeInt8"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeWriteInt8CodeGenerator, 1 } }, + { "writeIntBE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeWriteIntBECodeGenerator, 1 } }, + { "writeIntLE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeWriteIntLECodeGenerator, 1 } }, { "writeUInt16"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeWriteUInt16LECodeGenerator, 1 } }, { "writeUInt16BE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeWriteUInt16BECodeGenerator, 1 } }, { "writeUInt16LE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeWriteUInt16LECodeGenerator, 1 } }, @@ -1893,6 +1893,8 @@ static const HashTableValue JSBufferPrototypeTableValues[] { "writeUInt32BE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeWriteUInt32BECodeGenerator, 1 } }, { "writeUInt32LE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeWriteUInt32LECodeGenerator, 1 } }, { "writeUInt8"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeWriteUInt8CodeGenerator, 1 } }, + { "writeUIntBE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeWriteUIntBECodeGenerator, 1 } }, + { "writeUIntLE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeWriteUIntLECodeGenerator, 1 } }, { "writeUint16"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeWriteUInt16LECodeGenerator, 1 } }, { "writeUint16BE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeWriteUInt16BECodeGenerator, 1 } }, { "writeUint16LE"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeWriteUInt16LECodeGenerator, 1 } }, diff --git a/src/bun.js/bindings/JSBufferEncodingType.cpp b/src/bun.js/bindings/JSBufferEncodingType.cpp index 8d99dd4db0ec..dde9e1be2725 100644 --- a/src/bun.js/bindings/JSBufferEncodingType.cpp +++ b/src/bun.js/bindings/JSBufferEncodingType.cpp @@ -39,7 +39,6 @@ static const NeverDestroyed values[] = { MAKE_STATIC_STRING_IMPL("base64"), MAKE_STATIC_STRING_IMPL("base64url"), MAKE_STATIC_STRING_IMPL("hex"), - MAKE_STATIC_STRING_IMPL("buffer"), }; String convertEnumerationToString(BufferEncodingType enumerationValue) @@ -104,8 +103,6 @@ template<> std::optional parseEnumeration std::optional parseEnumeration const char* expectedEnumerationValues() { - return "\"utf8\", \"ucs2\", \"utf16le\", \"latin1\", \"ascii\", \"base64\", \"base64url\", \"hex\", \"buffer\""; + return "\"utf8\", \"ucs2\", \"utf16le\", \"latin1\", \"ascii\", \"base64\", \"base64url\", \"hex\""; } } // namespace WebCore diff --git a/src/bun.js/bindings/headers-handwritten.h b/src/bun.js/bindings/headers-handwritten.h index 8f925e84f26e..d4e1e72af273 100644 --- a/src/bun.js/bindings/headers-handwritten.h +++ b/src/bun.js/bindings/headers-handwritten.h @@ -268,8 +268,8 @@ extern "C" const char* Bun__version_sha; extern "C" void ZigString__free_global(const unsigned char* ptr, size_t len); -extern "C" int64_t Bun__encoding__writeLatin1(const unsigned char* ptr, size_t len, unsigned char* to, size_t other_len, Encoding encoding); -extern "C" int64_t Bun__encoding__writeUTF16(const UChar* ptr, size_t len, unsigned char* to, size_t other_len, Encoding encoding); +extern "C" size_t Bun__encoding__writeLatin1(const unsigned char* ptr, size_t len, unsigned char* to, size_t other_len, Encoding encoding); +extern "C" size_t Bun__encoding__writeUTF16(const UChar* ptr, size_t len, unsigned char* to, size_t other_len, Encoding encoding); extern "C" size_t Bun__encoding__byteLengthLatin1(const unsigned char* ptr, size_t len, Encoding encoding); extern "C" size_t Bun__encoding__byteLengthUTF16(const UChar* ptr, size_t len, Encoding encoding); diff --git a/src/bun.js/builtins/cpp/JSBufferPrototypeBuiltins.cpp b/src/bun.js/builtins/cpp/JSBufferPrototypeBuiltins.cpp index 8aff61064885..1ce441bb6026 100644 --- a/src/bun.js/builtins/cpp/JSBufferPrototypeBuiltins.cpp +++ b/src/bun.js/builtins/cpp/JSBufferPrototypeBuiltins.cpp @@ -180,6 +180,146 @@ const char* const s_jsBufferPrototypeReadUInt32BECode = "})\n" \ ; +const JSC::ConstructAbility s_jsBufferPrototypeReadIntLECodeConstructAbility = JSC::ConstructAbility::CannotConstruct; +const JSC::ConstructorKind s_jsBufferPrototypeReadIntLECodeConstructorKind = JSC::ConstructorKind::None; +const JSC::ImplementationVisibility s_jsBufferPrototypeReadIntLECodeImplementationVisibility = JSC::ImplementationVisibility::Public; +const int s_jsBufferPrototypeReadIntLECodeLength = 882; +static const JSC::Intrinsic s_jsBufferPrototypeReadIntLECodeIntrinsic = JSC::NoIntrinsic; +const char* const s_jsBufferPrototypeReadIntLECode = + "(function (offset, byteLength) {\n" \ + " \"use strict\";\n" \ + " const view = this.@dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength);\n" \ + " switch (byteLength) {\n" \ + " case 1: {\n" \ + " return view.getInt8(offset);\n" \ + " }\n" \ + " case 2: {\n" \ + " return view.getInt16(offset, true);\n" \ + " }\n" \ + " case 3: {\n" \ + " const val = view.getUint16(offset, true) + view.getUint8(offset + 2) * 2 ** 16;\n" \ + " return val | (val & 2 ** 23) * 0x1fe;\n" \ + " }\n" \ + " case 4: {\n" \ + " return view.getInt32(offset, true);\n" \ + " }\n" \ + " case 5: {\n" \ + " const last = view.getUint8(offset + 4);\n" \ + " return (last | (last & 2 ** 7) * 0x1fffffe) * 2 ** 32 + view.getUint32(offset, true);\n" \ + " }\n" \ + " case 6: {\n" \ + " const last = view.getUint16(offset + 4, true);\n" \ + " return (last | (last & 2 ** 15) * 0x1fffe) * 2 ** 32 + view.getUint32(offset, true);\n" \ + " }\n" \ + " }\n" \ + " @throwRangeError(\"byteLength must be >= 1 and <= 6\");\n" \ + "})\n" \ +; + +const JSC::ConstructAbility s_jsBufferPrototypeReadIntBECodeConstructAbility = JSC::ConstructAbility::CannotConstruct; +const JSC::ConstructorKind s_jsBufferPrototypeReadIntBECodeConstructorKind = JSC::ConstructorKind::None; +const JSC::ImplementationVisibility s_jsBufferPrototypeReadIntBECodeImplementationVisibility = JSC::ImplementationVisibility::Public; +const int s_jsBufferPrototypeReadIntBECodeLength = 888; +static const JSC::Intrinsic s_jsBufferPrototypeReadIntBECodeIntrinsic = JSC::NoIntrinsic; +const char* const s_jsBufferPrototypeReadIntBECode = + "(function (offset, byteLength) {\n" \ + " \"use strict\";\n" \ + " const view = this.@dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength);\n" \ + " switch (byteLength) {\n" \ + " case 1: {\n" \ + " return view.getInt8(offset);\n" \ + " }\n" \ + " case 2: {\n" \ + " return view.getInt16(offset, false);\n" \ + " }\n" \ + " case 3: {\n" \ + " const val = view.getUint16(offset + 1, false) + view.getUint8(offset) * 2 ** 16;\n" \ + " return val | (val & 2 ** 23) * 0x1fe;\n" \ + " }\n" \ + " case 4: {\n" \ + " return view.getInt32(offset, false);\n" \ + " }\n" \ + " case 5: {\n" \ + " const last = view.getUint8(offset);\n" \ + " return (last | (last & 2 ** 7) * 0x1fffffe) * 2 ** 32 + view.getUint32(offset + 1, false);\n" \ + " }\n" \ + " case 6: {\n" \ + " const last = view.getUint16(offset, false);\n" \ + " return (last | (last & 2 ** 15) * 0x1fffe) * 2 ** 32 + view.getUint32(offset + 2, false);\n" \ + " }\n" \ + " }\n" \ + " @throwRangeError(\"byteLength must be >= 1 and <= 6\");\n" \ + "})\n" \ +; + +const JSC::ConstructAbility s_jsBufferPrototypeReadUIntLECodeConstructAbility = JSC::ConstructAbility::CannotConstruct; +const JSC::ConstructorKind s_jsBufferPrototypeReadUIntLECodeConstructorKind = JSC::ConstructorKind::None; +const JSC::ImplementationVisibility s_jsBufferPrototypeReadUIntLECodeImplementationVisibility = JSC::ImplementationVisibility::Public; +const int s_jsBufferPrototypeReadUIntLECodeLength = 723; +static const JSC::Intrinsic s_jsBufferPrototypeReadUIntLECodeIntrinsic = JSC::NoIntrinsic; +const char* const s_jsBufferPrototypeReadUIntLECode = + "(function (offset, byteLength) {\n" \ + " \"use strict\";\n" \ + " const view = this.@dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength);\n" \ + " switch (byteLength) {\n" \ + " case 1: {\n" \ + " return view.getUint8(offset);\n" \ + " }\n" \ + " case 2: {\n" \ + " return view.getUint16(offset, true);\n" \ + " }\n" \ + " case 3: {\n" \ + " return view.getUint16(offset, true) + view.getUint8(offset + 2) * 2 ** 16;\n" \ + " }\n" \ + " case 4: {\n" \ + " return view.getUint32(offset, true);\n" \ + " }\n" \ + " case 5: {\n" \ + " return view.getUint8(offset + 4) * 2 ** 32 + view.getUint32(offset, true);\n" \ + " }\n" \ + " case 6: {\n" \ + " return view.getUint16(offset + 4, true) * 2 ** 32 + view.getUint32(offset, true);\n" \ + " }\n" \ + " }\n" \ + " @throwRangeError(\"byteLength must be >= 1 and <= 6\");\n" \ + "})\n" \ +; + +const JSC::ConstructAbility s_jsBufferPrototypeReadUIntBECodeConstructAbility = JSC::ConstructAbility::CannotConstruct; +const JSC::ConstructorKind s_jsBufferPrototypeReadUIntBECodeConstructorKind = JSC::ConstructorKind::None; +const JSC::ImplementationVisibility s_jsBufferPrototypeReadUIntBECodeImplementationVisibility = JSC::ImplementationVisibility::Public; +const int s_jsBufferPrototypeReadUIntBECodeLength = 842; +static const JSC::Intrinsic s_jsBufferPrototypeReadUIntBECodeIntrinsic = JSC::NoIntrinsic; +const char* const s_jsBufferPrototypeReadUIntBECode = + "(function (offset, byteLength) {\n" \ + " \"use strict\";\n" \ + " const view = this.@dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength);\n" \ + " switch (byteLength) {\n" \ + " case 1: {\n" \ + " return view.getUint8(offset);\n" \ + " }\n" \ + " case 2: {\n" \ + " return view.getUint16(offset, false);\n" \ + " }\n" \ + " case 3: {\n" \ + " return view.getUint16(offset + 1, false) + view.getUint8(offset) * 2 ** 16;\n" \ + " }\n" \ + " case 4: {\n" \ + " return view.getUint32(offset, false);\n" \ + " }\n" \ + " case 5: {\n" \ + " const last = view.getUint8(offset);\n" \ + " return (last | (last & 2 ** 7) * 0x1fffffe) * 2 ** 32 + view.getUint32(offset + 1, false);\n" \ + " }\n" \ + " case 6: {\n" \ + " const last = view.getUint16(offset, false);\n" \ + " return (last | (last & 2 ** 15) * 0x1fffe) * 2 ** 32 + view.getUint32(offset + 2, false);\n" \ + " }\n" \ + " }\n" \ + " @throwRangeError(\"byteLength must be >= 1 and <= 6\");\n" \ + "})\n" \ +; + const JSC::ConstructAbility s_jsBufferPrototypeReadFloatLECodeConstructAbility = JSC::ConstructAbility::CannotConstruct; const JSC::ConstructorKind s_jsBufferPrototypeReadFloatLECodeConstructorKind = JSC::ConstructorKind::None; const JSC::ImplementationVisibility s_jsBufferPrototypeReadFloatLECodeImplementationVisibility = JSC::ImplementationVisibility::Public; @@ -406,6 +546,186 @@ const char* const s_jsBufferPrototypeWriteUInt32BECode = "})\n" \ ; +const JSC::ConstructAbility s_jsBufferPrototypeWriteIntLECodeConstructAbility = JSC::ConstructAbility::CannotConstruct; +const JSC::ConstructorKind s_jsBufferPrototypeWriteIntLECodeConstructorKind = JSC::ConstructorKind::None; +const JSC::ImplementationVisibility s_jsBufferPrototypeWriteIntLECodeImplementationVisibility = JSC::ImplementationVisibility::Public; +const int s_jsBufferPrototypeWriteIntLECodeLength = 949; +static const JSC::Intrinsic s_jsBufferPrototypeWriteIntLECodeIntrinsic = JSC::NoIntrinsic; +const char* const s_jsBufferPrototypeWriteIntLECode = + "(function (value, offset, byteLength) {\n" \ + " \"use strict\";\n" \ + " const view = this.@dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength);\n" \ + " switch (byteLength) {\n" \ + " case 1: {\n" \ + " view.setInt8(offset, value);\n" \ + " break;\n" \ + " }\n" \ + " case 2: {\n" \ + " view.setInt16(offset, value, true);\n" \ + " break;\n" \ + " }\n" \ + " case 3: {\n" \ + " view.setUint16(offset, value & 0xFFFF, true);\n" \ + " view.setInt8(offset + 2, Math.floor(value * 2 ** -16));\n" \ + " break;\n" \ + " }\n" \ + " case 4: {\n" \ + " view.setInt32(offset, value, true);\n" \ + " break;\n" \ + " }\n" \ + " case 5: {\n" \ + " view.setUint32(offset, value | 0, true);\n" \ + " view.setInt8(offset + 4, Math.floor(value * 2 ** -32));\n" \ + " break;\n" \ + " }\n" \ + " case 6: {\n" \ + " view.setUint32(offset, value | 0, true);\n" \ + " view.setInt16(offset + 4, Math.floor(value * 2 ** -32), true);\n" \ + " break;\n" \ + " }\n" \ + " default: {\n" \ + " @throwRangeError(\"byteLength must be >= 1 and <= 6\");\n" \ + " }\n" \ + " }\n" \ + " return offset + byteLength;\n" \ + "})\n" \ +; + +const JSC::ConstructAbility s_jsBufferPrototypeWriteIntBECodeConstructAbility = JSC::ConstructAbility::CannotConstruct; +const JSC::ConstructorKind s_jsBufferPrototypeWriteIntBECodeConstructorKind = JSC::ConstructorKind::None; +const JSC::ImplementationVisibility s_jsBufferPrototypeWriteIntBECodeImplementationVisibility = JSC::ImplementationVisibility::Public; +const int s_jsBufferPrototypeWriteIntBECodeLength = 955; +static const JSC::Intrinsic s_jsBufferPrototypeWriteIntBECodeIntrinsic = JSC::NoIntrinsic; +const char* const s_jsBufferPrototypeWriteIntBECode = + "(function (value, offset, byteLength) {\n" \ + " \"use strict\";\n" \ + " const view = this.@dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength);\n" \ + " switch (byteLength) {\n" \ + " case 1: {\n" \ + " view.setInt8(offset, value);\n" \ + " break;\n" \ + " }\n" \ + " case 2: {\n" \ + " view.setInt16(offset, value, false);\n" \ + " break;\n" \ + " }\n" \ + " case 3: {\n" \ + " view.setUint16(offset + 1, value & 0xFFFF, false);\n" \ + " view.setInt8(offset, Math.floor(value * 2 ** -16));\n" \ + " break;\n" \ + " }\n" \ + " case 4: {\n" \ + " view.setInt32(offset, value, false);\n" \ + " break;\n" \ + " }\n" \ + " case 5: {\n" \ + " view.setUint32(offset + 1, value | 0, false);\n" \ + " view.setInt8(offset, Math.floor(value * 2 ** -32));\n" \ + " break;\n" \ + " }\n" \ + " case 6: {\n" \ + " view.setUint32(offset + 2, value | 0, false);\n" \ + " view.setInt16(offset, Math.floor(value * 2 ** -32), false);\n" \ + " break;\n" \ + " }\n" \ + " default: {\n" \ + " @throwRangeError(\"byteLength must be >= 1 and <= 6\");\n" \ + " }\n" \ + " }\n" \ + " return offset + byteLength;\n" \ + "})\n" \ +; + +const JSC::ConstructAbility s_jsBufferPrototypeWriteUIntLECodeConstructAbility = JSC::ConstructAbility::CannotConstruct; +const JSC::ConstructorKind s_jsBufferPrototypeWriteUIntLECodeConstructorKind = JSC::ConstructorKind::None; +const JSC::ImplementationVisibility s_jsBufferPrototypeWriteUIntLECodeImplementationVisibility = JSC::ImplementationVisibility::Public; +const int s_jsBufferPrototypeWriteUIntLECodeLength = 955; +static const JSC::Intrinsic s_jsBufferPrototypeWriteUIntLECodeIntrinsic = JSC::NoIntrinsic; +const char* const s_jsBufferPrototypeWriteUIntLECode = + "(function (value, offset, byteLength) {\n" \ + " \"use strict\";\n" \ + " const view = this.@dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength);\n" \ + " switch (byteLength) {\n" \ + " case 1: {\n" \ + " view.setUint8(offset, value);\n" \ + " break;\n" \ + " }\n" \ + " case 2: {\n" \ + " view.setUint16(offset, value, true);\n" \ + " break;\n" \ + " }\n" \ + " case 3: {\n" \ + " view.setUint16(offset, value & 0xFFFF, true);\n" \ + " view.setUint8(offset + 2, Math.floor(value * 2 ** -16));\n" \ + " break;\n" \ + " }\n" \ + " case 4: {\n" \ + " view.setUint32(offset, value, true);\n" \ + " break;\n" \ + " }\n" \ + " case 5: {\n" \ + " view.setUint32(offset, value | 0, true);\n" \ + " view.setUint8(offset + 4, Math.floor(value * 2 ** -32));\n" \ + " break;\n" \ + " }\n" \ + " case 6: {\n" \ + " view.setUint32(offset, value | 0, true);\n" \ + " view.setUint16(offset + 4, Math.floor(value * 2 ** -32), true);\n" \ + " break;\n" \ + " }\n" \ + " default: {\n" \ + " @throwRangeError(\"byteLength must be >= 1 and <= 6\");\n" \ + " }\n" \ + " }\n" \ + " return offset + byteLength;\n" \ + "})\n" \ +; + +const JSC::ConstructAbility s_jsBufferPrototypeWriteUIntBECodeConstructAbility = JSC::ConstructAbility::CannotConstruct; +const JSC::ConstructorKind s_jsBufferPrototypeWriteUIntBECodeConstructorKind = JSC::ConstructorKind::None; +const JSC::ImplementationVisibility s_jsBufferPrototypeWriteUIntBECodeImplementationVisibility = JSC::ImplementationVisibility::Public; +const int s_jsBufferPrototypeWriteUIntBECodeLength = 961; +static const JSC::Intrinsic s_jsBufferPrototypeWriteUIntBECodeIntrinsic = JSC::NoIntrinsic; +const char* const s_jsBufferPrototypeWriteUIntBECode = + "(function (value, offset, byteLength) {\n" \ + " \"use strict\";\n" \ + " const view = this.@dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength);\n" \ + " switch (byteLength) {\n" \ + " case 1: {\n" \ + " view.setUint8(offset, value);\n" \ + " break;\n" \ + " }\n" \ + " case 2: {\n" \ + " view.setUint16(offset, value, false);\n" \ + " break;\n" \ + " }\n" \ + " case 3: {\n" \ + " view.setUint16(offset + 1, value & 0xFFFF, false);\n" \ + " view.setUint8(offset, Math.floor(value * 2 ** -16));\n" \ + " break;\n" \ + " }\n" \ + " case 4: {\n" \ + " view.setUint32(offset, value, false);\n" \ + " break;\n" \ + " }\n" \ + " case 5: {\n" \ + " view.setUint32(offset + 1, value | 0, false);\n" \ + " view.setUint8(offset, Math.floor(value * 2 ** -32));\n" \ + " break;\n" \ + " }\n" \ + " case 6: {\n" \ + " view.setUint32(offset + 2, value | 0, false);\n" \ + " view.setUint16(offset, Math.floor(value * 2 ** -32), false);\n" \ + " break;\n" \ + " }\n" \ + " default: {\n" \ + " @throwRangeError(\"byteLength must be >= 1 and <= 6\");\n" \ + " }\n" \ + " }\n" \ + " return offset + byteLength;\n" \ + "})\n" \ +; + const JSC::ConstructAbility s_jsBufferPrototypeWriteFloatLECodeConstructAbility = JSC::ConstructAbility::CannotConstruct; const JSC::ConstructorKind s_jsBufferPrototypeWriteFloatLECodeConstructorKind = JSC::ConstructorKind::None; const JSC::ImplementationVisibility s_jsBufferPrototypeWriteFloatLECodeImplementationVisibility = JSC::ImplementationVisibility::Public; @@ -719,7 +1039,7 @@ const char* const s_jsBufferPrototypeToJSONCode = const JSC::ConstructAbility s_jsBufferPrototypeSliceCodeConstructAbility = JSC::ConstructAbility::CannotConstruct; const JSC::ConstructorKind s_jsBufferPrototypeSliceCodeConstructorKind = JSC::ConstructorKind::None; const JSC::ImplementationVisibility s_jsBufferPrototypeSliceCodeImplementationVisibility = JSC::ImplementationVisibility::Public; -const int s_jsBufferPrototypeSliceCodeLength = 612; +const int s_jsBufferPrototypeSliceCodeLength = 613; static const JSC::Intrinsic s_jsBufferPrototypeSliceCodeIntrinsic = JSC::NoIntrinsic; const char* const s_jsBufferPrototypeSliceCode = "(function (start, end) {\n" \ @@ -741,7 +1061,7 @@ const char* const s_jsBufferPrototypeSliceCode = " }\n" \ "\n" \ " var start_ = adjustOffset(start, byteLength);\n" \ - " var end_ = end !== undefined ? adjustOffset(end, byteLength) : byteLength;\n" \ + " var end_ = end !== @undefined ? adjustOffset(end, byteLength) : byteLength;\n" \ " return new Buffer(buffer, byteOffset + start_, end_ > start_ ? (end_ - start_) : 0);\n" \ "})\n" \ ; @@ -749,37 +1069,24 @@ const char* const s_jsBufferPrototypeSliceCode = const JSC::ConstructAbility s_jsBufferPrototypeParentCodeConstructAbility = JSC::ConstructAbility::CannotConstruct; const JSC::ConstructorKind s_jsBufferPrototypeParentCodeConstructorKind = JSC::ConstructorKind::None; const JSC::ImplementationVisibility s_jsBufferPrototypeParentCodeImplementationVisibility = JSC::ImplementationVisibility::Public; -const int s_jsBufferPrototypeParentCodeLength = 57; +const int s_jsBufferPrototypeParentCodeLength = 114; static const JSC::Intrinsic s_jsBufferPrototypeParentCodeIntrinsic = JSC::NoIntrinsic; const char* const s_jsBufferPrototypeParentCode = "(function () {\n" \ " \"use strict\";\n" \ - " return this?.buffer;\n" \ + " return @isObject(this) && this instanceof @Buffer ? this.buffer : @undefined;\n" \ "})\n" \ ; const JSC::ConstructAbility s_jsBufferPrototypeOffsetCodeConstructAbility = JSC::ConstructAbility::CannotConstruct; const JSC::ConstructorKind s_jsBufferPrototypeOffsetCodeConstructorKind = JSC::ConstructorKind::None; const JSC::ImplementationVisibility s_jsBufferPrototypeOffsetCodeImplementationVisibility = JSC::ImplementationVisibility::Public; -const int s_jsBufferPrototypeOffsetCodeLength = 61; +const int s_jsBufferPrototypeOffsetCodeLength = 118; static const JSC::Intrinsic s_jsBufferPrototypeOffsetCodeIntrinsic = JSC::NoIntrinsic; const char* const s_jsBufferPrototypeOffsetCode = "(function () {\n" \ " \"use strict\";\n" \ - " return this?.byteOffset;\n" \ - "})\n" \ -; - -const JSC::ConstructAbility s_jsBufferPrototypeInitializeBunBufferCodeConstructAbility = JSC::ConstructAbility::CannotConstruct; -const JSC::ConstructorKind s_jsBufferPrototypeInitializeBunBufferCodeConstructorKind = JSC::ConstructorKind::None; -const JSC::ImplementationVisibility s_jsBufferPrototypeInitializeBunBufferCodeImplementationVisibility = JSC::ImplementationVisibility::Public; -const int s_jsBufferPrototypeInitializeBunBufferCodeLength = 45; -static const JSC::Intrinsic s_jsBufferPrototypeInitializeBunBufferCodeIntrinsic = JSC::NoIntrinsic; -const char* const s_jsBufferPrototypeInitializeBunBufferCode = - "(function (parameters)\n" \ - "{\n" \ - " \"use strict\";\n" \ - "\n" \ + " return @isObject(this) && this instanceof @Buffer ? this.byteOffset : @undefined;\n" \ "})\n" \ ; diff --git a/src/bun.js/builtins/cpp/JSBufferPrototypeBuiltins.h b/src/bun.js/builtins/cpp/JSBufferPrototypeBuiltins.h index f7a3364deaab..60c00bedf60c 100644 --- a/src/bun.js/builtins/cpp/JSBufferPrototypeBuiltins.h +++ b/src/bun.js/builtins/cpp/JSBufferPrototypeBuiltins.h @@ -102,6 +102,26 @@ extern const int s_jsBufferPrototypeReadUInt32BECodeLength; extern const JSC::ConstructAbility s_jsBufferPrototypeReadUInt32BECodeConstructAbility; extern const JSC::ConstructorKind s_jsBufferPrototypeReadUInt32BECodeConstructorKind; extern const JSC::ImplementationVisibility s_jsBufferPrototypeReadUInt32BECodeImplementationVisibility; +extern const char* const s_jsBufferPrototypeReadIntLECode; +extern const int s_jsBufferPrototypeReadIntLECodeLength; +extern const JSC::ConstructAbility s_jsBufferPrototypeReadIntLECodeConstructAbility; +extern const JSC::ConstructorKind s_jsBufferPrototypeReadIntLECodeConstructorKind; +extern const JSC::ImplementationVisibility s_jsBufferPrototypeReadIntLECodeImplementationVisibility; +extern const char* const s_jsBufferPrototypeReadIntBECode; +extern const int s_jsBufferPrototypeReadIntBECodeLength; +extern const JSC::ConstructAbility s_jsBufferPrototypeReadIntBECodeConstructAbility; +extern const JSC::ConstructorKind s_jsBufferPrototypeReadIntBECodeConstructorKind; +extern const JSC::ImplementationVisibility s_jsBufferPrototypeReadIntBECodeImplementationVisibility; +extern const char* const s_jsBufferPrototypeReadUIntLECode; +extern const int s_jsBufferPrototypeReadUIntLECodeLength; +extern const JSC::ConstructAbility s_jsBufferPrototypeReadUIntLECodeConstructAbility; +extern const JSC::ConstructorKind s_jsBufferPrototypeReadUIntLECodeConstructorKind; +extern const JSC::ImplementationVisibility s_jsBufferPrototypeReadUIntLECodeImplementationVisibility; +extern const char* const s_jsBufferPrototypeReadUIntBECode; +extern const int s_jsBufferPrototypeReadUIntBECodeLength; +extern const JSC::ConstructAbility s_jsBufferPrototypeReadUIntBECodeConstructAbility; +extern const JSC::ConstructorKind s_jsBufferPrototypeReadUIntBECodeConstructorKind; +extern const JSC::ImplementationVisibility s_jsBufferPrototypeReadUIntBECodeImplementationVisibility; extern const char* const s_jsBufferPrototypeReadFloatLECode; extern const int s_jsBufferPrototypeReadFloatLECodeLength; extern const JSC::ConstructAbility s_jsBufferPrototypeReadFloatLECodeConstructAbility; @@ -192,6 +212,26 @@ extern const int s_jsBufferPrototypeWriteUInt32BECodeLength; extern const JSC::ConstructAbility s_jsBufferPrototypeWriteUInt32BECodeConstructAbility; extern const JSC::ConstructorKind s_jsBufferPrototypeWriteUInt32BECodeConstructorKind; extern const JSC::ImplementationVisibility s_jsBufferPrototypeWriteUInt32BECodeImplementationVisibility; +extern const char* const s_jsBufferPrototypeWriteIntLECode; +extern const int s_jsBufferPrototypeWriteIntLECodeLength; +extern const JSC::ConstructAbility s_jsBufferPrototypeWriteIntLECodeConstructAbility; +extern const JSC::ConstructorKind s_jsBufferPrototypeWriteIntLECodeConstructorKind; +extern const JSC::ImplementationVisibility s_jsBufferPrototypeWriteIntLECodeImplementationVisibility; +extern const char* const s_jsBufferPrototypeWriteIntBECode; +extern const int s_jsBufferPrototypeWriteIntBECodeLength; +extern const JSC::ConstructAbility s_jsBufferPrototypeWriteIntBECodeConstructAbility; +extern const JSC::ConstructorKind s_jsBufferPrototypeWriteIntBECodeConstructorKind; +extern const JSC::ImplementationVisibility s_jsBufferPrototypeWriteIntBECodeImplementationVisibility; +extern const char* const s_jsBufferPrototypeWriteUIntLECode; +extern const int s_jsBufferPrototypeWriteUIntLECodeLength; +extern const JSC::ConstructAbility s_jsBufferPrototypeWriteUIntLECodeConstructAbility; +extern const JSC::ConstructorKind s_jsBufferPrototypeWriteUIntLECodeConstructorKind; +extern const JSC::ImplementationVisibility s_jsBufferPrototypeWriteUIntLECodeImplementationVisibility; +extern const char* const s_jsBufferPrototypeWriteUIntBECode; +extern const int s_jsBufferPrototypeWriteUIntBECodeLength; +extern const JSC::ConstructAbility s_jsBufferPrototypeWriteUIntBECodeConstructAbility; +extern const JSC::ConstructorKind s_jsBufferPrototypeWriteUIntBECodeConstructorKind; +extern const JSC::ImplementationVisibility s_jsBufferPrototypeWriteUIntBECodeImplementationVisibility; extern const char* const s_jsBufferPrototypeWriteFloatLECode; extern const int s_jsBufferPrototypeWriteFloatLECodeLength; extern const JSC::ConstructAbility s_jsBufferPrototypeWriteFloatLECodeConstructAbility; @@ -332,11 +372,6 @@ extern const int s_jsBufferPrototypeOffsetCodeLength; extern const JSC::ConstructAbility s_jsBufferPrototypeOffsetCodeConstructAbility; extern const JSC::ConstructorKind s_jsBufferPrototypeOffsetCodeConstructorKind; extern const JSC::ImplementationVisibility s_jsBufferPrototypeOffsetCodeImplementationVisibility; -extern const char* const s_jsBufferPrototypeInitializeBunBufferCode; -extern const int s_jsBufferPrototypeInitializeBunBufferCodeLength; -extern const JSC::ConstructAbility s_jsBufferPrototypeInitializeBunBufferCodeConstructAbility; -extern const JSC::ConstructorKind s_jsBufferPrototypeInitializeBunBufferCodeConstructorKind; -extern const JSC::ImplementationVisibility s_jsBufferPrototypeInitializeBunBufferCodeImplementationVisibility; #define WEBCORE_FOREACH_JSBUFFERPROTOTYPE_BUILTIN_DATA(macro) \ macro(setBigUint64, jsBufferPrototypeSetBigUint64, 3) \ @@ -350,6 +385,10 @@ extern const JSC::ImplementationVisibility s_jsBufferPrototypeInitializeBunBuffe macro(readInt32BE, jsBufferPrototypeReadInt32BE, 1) \ macro(readUInt32LE, jsBufferPrototypeReadUInt32LE, 1) \ macro(readUInt32BE, jsBufferPrototypeReadUInt32BE, 1) \ + macro(readIntLE, jsBufferPrototypeReadIntLE, 2) \ + macro(readIntBE, jsBufferPrototypeReadIntBE, 2) \ + macro(readUIntLE, jsBufferPrototypeReadUIntLE, 2) \ + macro(readUIntBE, jsBufferPrototypeReadUIntBE, 2) \ macro(readFloatLE, jsBufferPrototypeReadFloatLE, 1) \ macro(readFloatBE, jsBufferPrototypeReadFloatBE, 1) \ macro(readDoubleLE, jsBufferPrototypeReadDoubleLE, 1) \ @@ -368,6 +407,10 @@ extern const JSC::ImplementationVisibility s_jsBufferPrototypeInitializeBunBuffe macro(writeInt32BE, jsBufferPrototypeWriteInt32BE, 2) \ macro(writeUInt32LE, jsBufferPrototypeWriteUInt32LE, 2) \ macro(writeUInt32BE, jsBufferPrototypeWriteUInt32BE, 2) \ + macro(writeIntLE, jsBufferPrototypeWriteIntLE, 3) \ + macro(writeIntBE, jsBufferPrototypeWriteIntBE, 3) \ + macro(writeUIntLE, jsBufferPrototypeWriteUIntLE, 3) \ + macro(writeUIntBE, jsBufferPrototypeWriteUIntBE, 3) \ macro(writeFloatLE, jsBufferPrototypeWriteFloatLE, 2) \ macro(writeFloatBE, jsBufferPrototypeWriteFloatBE, 2) \ macro(writeDoubleLE, jsBufferPrototypeWriteDoubleLE, 2) \ @@ -396,7 +439,6 @@ extern const JSC::ImplementationVisibility s_jsBufferPrototypeInitializeBunBuffe macro(slice, jsBufferPrototypeSlice, 2) \ macro(parent, jsBufferPrototypeParent, 0) \ macro(offset, jsBufferPrototypeOffset, 0) \ - macro(initializeBunBuffer, jsBufferPrototypeInitializeBunBuffer, 1) \ #define WEBCORE_BUILTIN_JSBUFFERPROTOTYPE_SETBIGUINT64 1 #define WEBCORE_BUILTIN_JSBUFFERPROTOTYPE_READINT8 1 @@ -409,6 +451,10 @@ extern const JSC::ImplementationVisibility s_jsBufferPrototypeInitializeBunBuffe #define WEBCORE_BUILTIN_JSBUFFERPROTOTYPE_READINT32BE 1 #define WEBCORE_BUILTIN_JSBUFFERPROTOTYPE_READUINT32LE 1 #define WEBCORE_BUILTIN_JSBUFFERPROTOTYPE_READUINT32BE 1 +#define WEBCORE_BUILTIN_JSBUFFERPROTOTYPE_READINTLE 1 +#define WEBCORE_BUILTIN_JSBUFFERPROTOTYPE_READINTBE 1 +#define WEBCORE_BUILTIN_JSBUFFERPROTOTYPE_READUINTLE 1 +#define WEBCORE_BUILTIN_JSBUFFERPROTOTYPE_READUINTBE 1 #define WEBCORE_BUILTIN_JSBUFFERPROTOTYPE_READFLOATLE 1 #define WEBCORE_BUILTIN_JSBUFFERPROTOTYPE_READFLOATBE 1 #define WEBCORE_BUILTIN_JSBUFFERPROTOTYPE_READDOUBLELE 1 @@ -427,6 +473,10 @@ extern const JSC::ImplementationVisibility s_jsBufferPrototypeInitializeBunBuffe #define WEBCORE_BUILTIN_JSBUFFERPROTOTYPE_WRITEINT32BE 1 #define WEBCORE_BUILTIN_JSBUFFERPROTOTYPE_WRITEUINT32LE 1 #define WEBCORE_BUILTIN_JSBUFFERPROTOTYPE_WRITEUINT32BE 1 +#define WEBCORE_BUILTIN_JSBUFFERPROTOTYPE_WRITEINTLE 1 +#define WEBCORE_BUILTIN_JSBUFFERPROTOTYPE_WRITEINTBE 1 +#define WEBCORE_BUILTIN_JSBUFFERPROTOTYPE_WRITEUINTLE 1 +#define WEBCORE_BUILTIN_JSBUFFERPROTOTYPE_WRITEUINTBE 1 #define WEBCORE_BUILTIN_JSBUFFERPROTOTYPE_WRITEFLOATLE 1 #define WEBCORE_BUILTIN_JSBUFFERPROTOTYPE_WRITEFLOATBE 1 #define WEBCORE_BUILTIN_JSBUFFERPROTOTYPE_WRITEDOUBLELE 1 @@ -455,7 +505,6 @@ extern const JSC::ImplementationVisibility s_jsBufferPrototypeInitializeBunBuffe #define WEBCORE_BUILTIN_JSBUFFERPROTOTYPE_SLICE 1 #define WEBCORE_BUILTIN_JSBUFFERPROTOTYPE_PARENT 1 #define WEBCORE_BUILTIN_JSBUFFERPROTOTYPE_OFFSET 1 -#define WEBCORE_BUILTIN_JSBUFFERPROTOTYPE_INITIALIZEBUNBUFFER 1 #define WEBCORE_FOREACH_JSBUFFERPROTOTYPE_BUILTIN_CODE(macro) \ macro(jsBufferPrototypeSetBigUint64Code, setBigUint64, ASCIILiteral(), s_jsBufferPrototypeSetBigUint64CodeLength) \ @@ -469,6 +518,10 @@ extern const JSC::ImplementationVisibility s_jsBufferPrototypeInitializeBunBuffe macro(jsBufferPrototypeReadInt32BECode, readInt32BE, ASCIILiteral(), s_jsBufferPrototypeReadInt32BECodeLength) \ macro(jsBufferPrototypeReadUInt32LECode, readUInt32LE, ASCIILiteral(), s_jsBufferPrototypeReadUInt32LECodeLength) \ macro(jsBufferPrototypeReadUInt32BECode, readUInt32BE, ASCIILiteral(), s_jsBufferPrototypeReadUInt32BECodeLength) \ + macro(jsBufferPrototypeReadIntLECode, readIntLE, ASCIILiteral(), s_jsBufferPrototypeReadIntLECodeLength) \ + macro(jsBufferPrototypeReadIntBECode, readIntBE, ASCIILiteral(), s_jsBufferPrototypeReadIntBECodeLength) \ + macro(jsBufferPrototypeReadUIntLECode, readUIntLE, ASCIILiteral(), s_jsBufferPrototypeReadUIntLECodeLength) \ + macro(jsBufferPrototypeReadUIntBECode, readUIntBE, ASCIILiteral(), s_jsBufferPrototypeReadUIntBECodeLength) \ macro(jsBufferPrototypeReadFloatLECode, readFloatLE, ASCIILiteral(), s_jsBufferPrototypeReadFloatLECodeLength) \ macro(jsBufferPrototypeReadFloatBECode, readFloatBE, ASCIILiteral(), s_jsBufferPrototypeReadFloatBECodeLength) \ macro(jsBufferPrototypeReadDoubleLECode, readDoubleLE, ASCIILiteral(), s_jsBufferPrototypeReadDoubleLECodeLength) \ @@ -487,6 +540,10 @@ extern const JSC::ImplementationVisibility s_jsBufferPrototypeInitializeBunBuffe macro(jsBufferPrototypeWriteInt32BECode, writeInt32BE, ASCIILiteral(), s_jsBufferPrototypeWriteInt32BECodeLength) \ macro(jsBufferPrototypeWriteUInt32LECode, writeUInt32LE, ASCIILiteral(), s_jsBufferPrototypeWriteUInt32LECodeLength) \ macro(jsBufferPrototypeWriteUInt32BECode, writeUInt32BE, ASCIILiteral(), s_jsBufferPrototypeWriteUInt32BECodeLength) \ + macro(jsBufferPrototypeWriteIntLECode, writeIntLE, ASCIILiteral(), s_jsBufferPrototypeWriteIntLECodeLength) \ + macro(jsBufferPrototypeWriteIntBECode, writeIntBE, ASCIILiteral(), s_jsBufferPrototypeWriteIntBECodeLength) \ + macro(jsBufferPrototypeWriteUIntLECode, writeUIntLE, ASCIILiteral(), s_jsBufferPrototypeWriteUIntLECodeLength) \ + macro(jsBufferPrototypeWriteUIntBECode, writeUIntBE, ASCIILiteral(), s_jsBufferPrototypeWriteUIntBECodeLength) \ macro(jsBufferPrototypeWriteFloatLECode, writeFloatLE, ASCIILiteral(), s_jsBufferPrototypeWriteFloatLECodeLength) \ macro(jsBufferPrototypeWriteFloatBECode, writeFloatBE, ASCIILiteral(), s_jsBufferPrototypeWriteFloatBECodeLength) \ macro(jsBufferPrototypeWriteDoubleLECode, writeDoubleLE, ASCIILiteral(), s_jsBufferPrototypeWriteDoubleLECodeLength) \ @@ -515,7 +572,6 @@ extern const JSC::ImplementationVisibility s_jsBufferPrototypeInitializeBunBuffe macro(jsBufferPrototypeSliceCode, slice, ASCIILiteral(), s_jsBufferPrototypeSliceCodeLength) \ macro(jsBufferPrototypeParentCode, parent, "get parent"_s, s_jsBufferPrototypeParentCodeLength) \ macro(jsBufferPrototypeOffsetCode, offset, "get offset"_s, s_jsBufferPrototypeOffsetCodeLength) \ - macro(jsBufferPrototypeInitializeBunBufferCode, initializeBunBuffer, ASCIILiteral(), s_jsBufferPrototypeInitializeBunBufferCodeLength) \ #define WEBCORE_FOREACH_JSBUFFERPROTOTYPE_BUILTIN_FUNCTION_NAME(macro) \ macro(asciiSlice) \ @@ -526,7 +582,6 @@ extern const JSC::ImplementationVisibility s_jsBufferPrototypeInitializeBunBuffe macro(base64urlWrite) \ macro(hexSlice) \ macro(hexWrite) \ - macro(initializeBunBuffer) \ macro(latin1Slice) \ macro(latin1Write) \ macro(offset) \ @@ -544,11 +599,15 @@ extern const JSC::ImplementationVisibility s_jsBufferPrototypeInitializeBunBuffe macro(readInt32BE) \ macro(readInt32LE) \ macro(readInt8) \ + macro(readIntBE) \ + macro(readIntLE) \ macro(readUInt16BE) \ macro(readUInt16LE) \ macro(readUInt32BE) \ macro(readUInt32LE) \ macro(readUInt8) \ + macro(readUIntBE) \ + macro(readUIntLE) \ macro(setBigUint64) \ macro(slice) \ macro(toJSON) \ @@ -571,11 +630,15 @@ extern const JSC::ImplementationVisibility s_jsBufferPrototypeInitializeBunBuffe macro(writeInt32BE) \ macro(writeInt32LE) \ macro(writeInt8) \ + macro(writeIntBE) \ + macro(writeIntLE) \ macro(writeUInt16BE) \ macro(writeUInt16LE) \ macro(writeUInt32BE) \ macro(writeUInt32LE) \ macro(writeUInt8) \ + macro(writeUIntBE) \ + macro(writeUIntLE) \ #define DECLARE_BUILTIN_GENERATOR(codeName, functionName, overriddenName, argumentCount) \ JSC::FunctionExecutable* codeName##Generator(JSC::VM&); diff --git a/src/bun.js/builtins/js/JSBufferPrototype.js b/src/bun.js/builtins/js/JSBufferPrototype.js index 827e613d84c0..32aebb3f2ce9 100644 --- a/src/bun.js/builtins/js/JSBufferPrototype.js +++ b/src/bun.js/builtins/js/JSBufferPrototype.js @@ -73,6 +73,116 @@ function readUInt32BE(offset) { "use strict"; return (this.@dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getUint32(offset, false); } + +function readIntLE(offset, byteLength) { + "use strict"; + const view = this.@dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength); + switch (byteLength) { + case 1: { + return view.getInt8(offset); + } + case 2: { + return view.getInt16(offset, true); + } + case 3: { + const val = view.getUint16(offset, true) + view.getUint8(offset + 2) * 2 ** 16; + return val | (val & 2 ** 23) * 0x1fe; + } + case 4: { + return view.getInt32(offset, true); + } + case 5: { + const last = view.getUint8(offset + 4); + return (last | (last & 2 ** 7) * 0x1fffffe) * 2 ** 32 + view.getUint32(offset, true); + } + case 6: { + const last = view.getUint16(offset + 4, true); + return (last | (last & 2 ** 15) * 0x1fffe) * 2 ** 32 + view.getUint32(offset, true); + } + } + @throwRangeError("byteLength must be >= 1 and <= 6"); +} +function readIntBE(offset, byteLength) { + "use strict"; + const view = this.@dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength); + switch (byteLength) { + case 1: { + return view.getInt8(offset); + } + case 2: { + return view.getInt16(offset, false); + } + case 3: { + const val = view.getUint16(offset + 1, false) + view.getUint8(offset) * 2 ** 16; + return val | (val & 2 ** 23) * 0x1fe; + } + case 4: { + return view.getInt32(offset, false); + } + case 5: { + const last = view.getUint8(offset); + return (last | (last & 2 ** 7) * 0x1fffffe) * 2 ** 32 + view.getUint32(offset + 1, false); + } + case 6: { + const last = view.getUint16(offset, false); + return (last | (last & 2 ** 15) * 0x1fffe) * 2 ** 32 + view.getUint32(offset + 2, false); + } + } + @throwRangeError("byteLength must be >= 1 and <= 6"); +} +function readUIntLE(offset, byteLength) { + "use strict"; + const view = this.@dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength); + switch (byteLength) { + case 1: { + return view.getUint8(offset); + } + case 2: { + return view.getUint16(offset, true); + } + case 3: { + return view.getUint16(offset, true) + view.getUint8(offset + 2) * 2 ** 16; + } + case 4: { + return view.getUint32(offset, true); + } + case 5: { + return view.getUint8(offset + 4) * 2 ** 32 + view.getUint32(offset, true); + } + case 6: { + return view.getUint16(offset + 4, true) * 2 ** 32 + view.getUint32(offset, true); + } + } + @throwRangeError("byteLength must be >= 1 and <= 6"); +} +function readUIntBE(offset, byteLength) { + "use strict"; + const view = this.@dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength); + switch (byteLength) { + case 1: { + return view.getUint8(offset); + } + case 2: { + return view.getUint16(offset, false); + } + case 3: { + return view.getUint16(offset + 1, false) + view.getUint8(offset) * 2 ** 16; + } + case 4: { + return view.getUint32(offset, false); + } + case 5: { + const last = view.getUint8(offset); + return (last | (last & 2 ** 7) * 0x1fffffe) * 2 ** 32 + view.getUint32(offset + 1, false); + } + case 6: { + const last = view.getUint16(offset, false); + return (last | (last & 2 ** 15) * 0x1fffe) * 2 ** 32 + view.getUint32(offset + 2, false); + } + } + @throwRangeError("byteLength must be >= 1 and <= 6"); +} + function readFloatLE(offset) { "use strict"; return (this.@dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getFloat32(offset, true); @@ -105,6 +215,7 @@ function readBigUInt64BE(offset) { "use strict"; return (this.@dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getBigUint64(offset, false); } + function writeInt8(value, offset) { "use strict"; (this.@dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setInt8(offset, value); @@ -156,6 +267,155 @@ function writeUInt32BE(value, offset) { return offset + 4; } +function writeIntLE(value, offset, byteLength) { + "use strict"; + const view = this.@dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength); + switch (byteLength) { + case 1: { + view.setInt8(offset, value); + break; + } + case 2: { + view.setInt16(offset, value, true); + break; + } + case 3: { + view.setUint16(offset, value & 0xFFFF, true); + view.setInt8(offset + 2, Math.floor(value * 2 ** -16)); + break; + } + case 4: { + view.setInt32(offset, value, true); + break; + } + case 5: { + view.setUint32(offset, value | 0, true); + view.setInt8(offset + 4, Math.floor(value * 2 ** -32)); + break; + } + case 6: { + view.setUint32(offset, value | 0, true); + view.setInt16(offset + 4, Math.floor(value * 2 ** -32), true); + break; + } + default: { + @throwRangeError("byteLength must be >= 1 and <= 6"); + } + } + return offset + byteLength; +} +function writeIntBE(value, offset, byteLength) { + "use strict"; + const view = this.@dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength); + switch (byteLength) { + case 1: { + view.setInt8(offset, value); + break; + } + case 2: { + view.setInt16(offset, value, false); + break; + } + case 3: { + view.setUint16(offset + 1, value & 0xFFFF, false); + view.setInt8(offset, Math.floor(value * 2 ** -16)); + break; + } + case 4: { + view.setInt32(offset, value, false); + break; + } + case 5: { + view.setUint32(offset + 1, value | 0, false); + view.setInt8(offset, Math.floor(value * 2 ** -32)); + break; + } + case 6: { + view.setUint32(offset + 2, value | 0, false); + view.setInt16(offset, Math.floor(value * 2 ** -32), false); + break; + } + default: { + @throwRangeError("byteLength must be >= 1 and <= 6"); + } + } + return offset + byteLength; +} +function writeUIntLE(value, offset, byteLength) { + "use strict"; + const view = this.@dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength); + switch (byteLength) { + case 1: { + view.setUint8(offset, value); + break; + } + case 2: { + view.setUint16(offset, value, true); + break; + } + case 3: { + view.setUint16(offset, value & 0xFFFF, true); + view.setUint8(offset + 2, Math.floor(value * 2 ** -16)); + break; + } + case 4: { + view.setUint32(offset, value, true); + break; + } + case 5: { + view.setUint32(offset, value | 0, true); + view.setUint8(offset + 4, Math.floor(value * 2 ** -32)); + break; + } + case 6: { + view.setUint32(offset, value | 0, true); + view.setUint16(offset + 4, Math.floor(value * 2 ** -32), true); + break; + } + default: { + @throwRangeError("byteLength must be >= 1 and <= 6"); + } + } + return offset + byteLength; +} +function writeUIntBE(value, offset, byteLength) { + "use strict"; + const view = this.@dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength); + switch (byteLength) { + case 1: { + view.setUint8(offset, value); + break; + } + case 2: { + view.setUint16(offset, value, false); + break; + } + case 3: { + view.setUint16(offset + 1, value & 0xFFFF, false); + view.setUint8(offset, Math.floor(value * 2 ** -16)); + break; + } + case 4: { + view.setUint32(offset, value, false); + break; + } + case 5: { + view.setUint32(offset + 1, value | 0, false); + view.setUint8(offset, Math.floor(value * 2 ** -32)); + break; + } + case 6: { + view.setUint32(offset + 2, value | 0, false); + view.setUint16(offset, Math.floor(value * 2 ** -32), false); + break; + } + default: { + @throwRangeError("byteLength must be >= 1 and <= 6"); + } + } + return offset + byteLength; +} + function writeFloatLE(value, offset) { "use strict"; (this.@dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setFloat32(offset, value, true); @@ -296,24 +556,18 @@ function slice(start, end) { } var start_ = adjustOffset(start, byteLength); - var end_ = end !== undefined ? adjustOffset(end, byteLength) : byteLength; + var end_ = end !== @undefined ? adjustOffset(end, byteLength) : byteLength; return new Buffer(buffer, byteOffset + start_, end_ > start_ ? (end_ - start_) : 0); } @getter function parent() { "use strict"; - return this?.buffer; + return @isObject(this) && this instanceof @Buffer ? this.buffer : @undefined; } @getter function offset() { "use strict"; - return this?.byteOffset; -} - -function initializeBunBuffer(parameters) -{ - "use strict"; - + return @isObject(this) && this instanceof @Buffer ? this.byteOffset : @undefined; } diff --git a/src/bun.js/node/buffer.zig b/src/bun.js/node/buffer.zig index 5a7d64955087..f3cc3c2c2909 100644 --- a/src/bun.js/node/buffer.zig +++ b/src/bun.js/node/buffer.zig @@ -1,96 +1,72 @@ -const std = @import("std"); const bun = @import("bun"); -const strings = bun.strings; -const string = bun.string; -const AsyncIO = @import("bun").AsyncIO; -const JSC = @import("bun").JSC; -const PathString = JSC.PathString; -const Environment = bun.Environment; -const C = bun.C; -const Syscall = @import("./syscall.zig"); -const os = std.os; - -const JSGlobalObject = JSC.JSGlobalObject; -const ArgumentsSlice = JSC.Node.ArgumentsSlice; +const JSC = bun.JSC; +const Encoder = JSC.WebCore.Encoder; pub const BufferVectorized = struct { - extern fn memset_pattern16(b: *anyopaque, pattern16: *const anyopaque, len: usize) void; - pub fn fill( str: *JSC.ZigString, buf_ptr: [*]u8, fill_length: usize, encoding: JSC.Node.Encoding, - ) callconv(.C) void { - if (str.len == 0) return; + ) callconv(.C) bool { + if (str.len == 0) return true; var buf = buf_ptr[0..fill_length]; const written = switch (encoding) { - JSC.Node.Encoding.utf8 => if (str.is16Bit()) - JSC.WebCore.Encoder.writeU16(str.utf16SliceAligned().ptr, str.utf16SliceAligned().len, buf.ptr, buf.len, JSC.Node.Encoding.utf8, true) + .utf8 => if (str.is16Bit()) + Encoder.writeU16(str.utf16SliceAligned().ptr, str.utf16SliceAligned().len, buf.ptr, buf.len, .utf8, true) else - JSC.WebCore.Encoder.writeU8(str.slice().ptr, str.slice().len, buf.ptr, buf.len, JSC.Node.Encoding.utf8), - JSC.Node.Encoding.ascii => if (str.is16Bit()) - JSC.WebCore.Encoder.writeU16(str.utf16SliceAligned().ptr, str.utf16SliceAligned().len, buf.ptr, buf.len, JSC.Node.Encoding.ascii, true) + Encoder.writeU8(str.slice().ptr, str.slice().len, buf.ptr, buf.len, .utf8), + .ascii => if (str.is16Bit()) + Encoder.writeU16(str.utf16SliceAligned().ptr, str.utf16SliceAligned().len, buf.ptr, buf.len, .ascii, true) else - JSC.WebCore.Encoder.writeU8(str.slice().ptr, str.slice().len, buf.ptr, buf.len, JSC.Node.Encoding.ascii), - JSC.Node.Encoding.latin1 => if (str.is16Bit()) - JSC.WebCore.Encoder.writeU16(str.utf16SliceAligned().ptr, str.utf16SliceAligned().len, buf.ptr, buf.len, JSC.Node.Encoding.latin1, true) + Encoder.writeU8(str.slice().ptr, str.slice().len, buf.ptr, buf.len, .ascii), + .latin1 => if (str.is16Bit()) + Encoder.writeU16(str.utf16SliceAligned().ptr, str.utf16SliceAligned().len, buf.ptr, buf.len, .latin1, true) else - JSC.WebCore.Encoder.writeU8(str.slice().ptr, str.slice().len, buf.ptr, buf.len, JSC.Node.Encoding.latin1), - JSC.Node.Encoding.buffer => if (str.is16Bit()) - JSC.WebCore.Encoder.writeU16(str.utf16SliceAligned().ptr, str.utf16SliceAligned().len, buf.ptr, buf.len, JSC.Node.Encoding.buffer, true) + Encoder.writeU8(str.slice().ptr, str.slice().len, buf.ptr, buf.len, .latin1), + .buffer => if (str.is16Bit()) + Encoder.writeU16(str.utf16SliceAligned().ptr, str.utf16SliceAligned().len, buf.ptr, buf.len, .buffer, true) else - JSC.WebCore.Encoder.writeU8(str.slice().ptr, str.slice().len, buf.ptr, buf.len, JSC.Node.Encoding.buffer), - JSC.Node.Encoding.utf16le, - JSC.Node.Encoding.ucs2, - => if (str.is16Bit()) - JSC.WebCore.Encoder.writeU16(str.utf16SliceAligned().ptr, str.utf16SliceAligned().len, buf.ptr, buf.len, JSC.Node.Encoding.utf16le, true) + Encoder.writeU8(str.slice().ptr, str.slice().len, buf.ptr, buf.len, .buffer), + .utf16le, .ucs2 => if (str.is16Bit()) + Encoder.writeU16(str.utf16SliceAligned().ptr, str.utf16SliceAligned().len, buf.ptr, buf.len, .utf16le, true) else - JSC.WebCore.Encoder.writeU8(str.slice().ptr, str.slice().len, buf.ptr, buf.len, JSC.Node.Encoding.utf16le), - JSC.Node.Encoding.base64 => if (str.is16Bit()) - JSC.WebCore.Encoder.writeU16(str.utf16SliceAligned().ptr, str.utf16SliceAligned().len, buf.ptr, buf.len, JSC.Node.Encoding.base64, true) + Encoder.writeU8(str.slice().ptr, str.slice().len, buf.ptr, buf.len, .utf16le), + .base64 => if (str.is16Bit()) + Encoder.writeU16(str.utf16SliceAligned().ptr, str.utf16SliceAligned().len, buf.ptr, buf.len, .base64, true) else - JSC.WebCore.Encoder.writeU8(str.slice().ptr, str.slice().len, buf.ptr, buf.len, JSC.Node.Encoding.base64), - JSC.Node.Encoding.base64url => if (str.is16Bit()) - JSC.WebCore.Encoder.writeU16(str.utf16SliceAligned().ptr, str.utf16SliceAligned().len, buf.ptr, buf.len, JSC.Node.Encoding.base64url, true) + Encoder.writeU8(str.slice().ptr, str.slice().len, buf.ptr, buf.len, .base64), + .base64url => if (str.is16Bit()) + Encoder.writeU16(str.utf16SliceAligned().ptr, str.utf16SliceAligned().len, buf.ptr, buf.len, .base64url, true) else - JSC.WebCore.Encoder.writeU8(str.slice().ptr, str.slice().len, buf.ptr, buf.len, JSC.Node.Encoding.base64url), - JSC.Node.Encoding.hex => if (str.is16Bit()) - JSC.WebCore.Encoder.writeU16(str.utf16SliceAligned().ptr, str.utf16SliceAligned().len, buf.ptr, buf.len, JSC.Node.Encoding.hex, true) + Encoder.writeU8(str.slice().ptr, str.slice().len, buf.ptr, buf.len, .base64url), + .hex => if (str.is16Bit()) + Encoder.writeU16(str.utf16SliceAligned().ptr, str.utf16SliceAligned().len, buf.ptr, buf.len, .hex, true) else - JSC.WebCore.Encoder.writeU8(str.slice().ptr, str.slice().len, buf.ptr, buf.len, JSC.Node.Encoding.hex), - }; - - if (written <= 0) { - return; - } + Encoder.writeU8(str.slice().ptr, str.slice().len, buf.ptr, buf.len, .hex), + } catch return false; - var contents = buf[0..@intCast(usize, written)]; - buf = buf[@intCast(usize, written)..]; + switch (written) { + 0 => {}, + 1 => @memset(buf.ptr, buf[0], buf.len), + else => { + var contents = buf[0..written]; + buf = buf[written..]; - if (contents.len == 1) { - @memset(buf.ptr, contents[0], buf.len); - return; - } - - const minimum_contents = contents; - while (buf.len >= contents.len) { - const min_len = @min(contents.len, buf.len); - bun.copy(u8, buf, contents[0..min_len]); - if (buf.len <= contents.len) { - break; - } - buf = buf[min_len..]; - contents.len *= 2; - } + while (buf.len >= contents.len) { + bun.copy(u8, buf, contents); + buf = buf[contents.len..]; + contents.len *= 2; + } - while (buf.len > 0) { - const to_fill = @min(minimum_contents.len, buf.len); - bun.copy(u8, buf, minimum_contents[0..to_fill]); - buf = buf[to_fill..]; + if (buf.len > 0) { + bun.copy(u8, buf, contents[0..buf.len]); + } + }, } + return true; } }; diff --git a/src/bun.js/webcore/base64.zig b/src/bun.js/webcore/base64.zig deleted file mode 100644 index aad1c471b7fe..000000000000 --- a/src/bun.js/webcore/base64.zig +++ /dev/null @@ -1,445 +0,0 @@ -// this is ripped from zig's stdlib -const std = @import("std"); -const assert = std.debug.assert; -const testing = std.testing; -const mem = std.mem; - -pub const Error = error{ - InvalidCharacter, - InvalidPadding, - NoSpaceLeft, -}; - -/// Base64 codecs -pub const Codecs = struct { - alphabet_chars: [64]u8, - pad_char: ?u8, - decoderWithIgnore: fn (ignore: []const u8) Base64DecoderWithIgnore, - Encoder: Base64Encoder, - Decoder: Base64Decoder, -}; - -pub const standard_alphabet_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".*; -fn standardBase64DecoderWithIgnore(ignore: []const u8) Base64DecoderWithIgnore { - return Base64DecoderWithIgnore.init(standard_alphabet_chars, '=', ignore); -} - -/// Standard Base64 codecs, with padding -pub const standard = Codecs{ - .alphabet_chars = standard_alphabet_chars, - .pad_char = '=', - .decoderWithIgnore = standardBase64DecoderWithIgnore, - .Encoder = Base64Encoder.init(standard_alphabet_chars, '='), - .Decoder = Base64Decoder.init(standard_alphabet_chars, '='), -}; - -/// Standard Base64 codecs, without padding -pub const standard_no_pad = Codecs{ - .alphabet_chars = standard_alphabet_chars, - .pad_char = null, - .decoderWithIgnore = standardBase64DecoderWithIgnore, - .Encoder = Base64Encoder.init(standard_alphabet_chars, null), - .Decoder = Base64Decoder.init(standard_alphabet_chars, null), -}; - -pub const url_safe_alphabet_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".*; -fn urlSafeBase64DecoderWithIgnore(ignore: []const u8) Base64DecoderWithIgnore { - return Base64DecoderWithIgnore.init(url_safe_alphabet_chars, null, ignore); -} - -/// URL-safe Base64 codecs, with padding -pub const url_safe = Codecs{ - .alphabet_chars = url_safe_alphabet_chars, - .pad_char = '=', - .decoderWithIgnore = urlSafeBase64DecoderWithIgnore, - .Encoder = Base64Encoder.init(url_safe_alphabet_chars, '='), - .Decoder = Base64Decoder.init(url_safe_alphabet_chars, '='), -}; - -/// URL-safe Base64 codecs, without padding -pub const url_safe_no_pad = Codecs{ - .alphabet_chars = url_safe_alphabet_chars, - .pad_char = null, - .decoderWithIgnore = urlSafeBase64DecoderWithIgnore, - .Encoder = Base64Encoder.init(url_safe_alphabet_chars, null), - .Decoder = Base64Decoder.init(url_safe_alphabet_chars, null), -}; - -pub const standard_pad_char = @compileError("deprecated; use standard.pad_char"); -pub const standard_encoder = @compileError("deprecated; use standard.Encoder"); -pub const standard_decoder = @compileError("deprecated; use standard.Decoder"); - -pub const Base64Encoder = struct { - alphabet_chars: [64]u8, - pad_char: ?u8, - - /// A bunch of assertions, then simply pass the data right through. - pub fn init(alphabet_chars: [64]u8, pad_char: ?u8) Base64Encoder { - assert(alphabet_chars.len == 64); - var char_in_alphabet = [_]bool{false} ** 256; - for (alphabet_chars) |c| { - assert(!char_in_alphabet[c]); - assert(pad_char == null or c != pad_char.?); - char_in_alphabet[c] = true; - } - return Base64Encoder{ - .alphabet_chars = alphabet_chars, - .pad_char = pad_char, - }; - } - - /// Compute the encoded length - pub fn calcSize(encoder: *const Base64Encoder, source_len: usize) usize { - if (encoder.pad_char != null) { - return @divTrunc(source_len + 2, 3) * 4; - } else { - const leftover = source_len % 3; - return @divTrunc(source_len, 3) * 4 + @divTrunc(leftover * 4 + 2, 3); - } - } - - /// dest.len must at least be what you get from ::calcSize. - pub fn encode(encoder: *const Base64Encoder, dest: []u8, source: []const u8) []const u8 { - const out_len = encoder.calcSize(source.len); - assert(dest.len >= out_len); - - var acc: u12 = 0; - var acc_len: u4 = 0; - var out_idx: usize = 0; - for (source) |v| { - acc = (acc << 8) + v; - acc_len += 8; - while (acc_len >= 6) { - acc_len -= 6; - dest[out_idx] = encoder.alphabet_chars[@truncate(u6, (acc >> acc_len))]; - out_idx += 1; - } - } - if (acc_len > 0) { - dest[out_idx] = encoder.alphabet_chars[@truncate(u6, (acc << 6 - acc_len))]; - out_idx += 1; - } - if (encoder.pad_char) |pad_char| { - for (&dest[out_idx..]) |*pad| { - pad.* = pad_char; - } - } - return dest[0..out_len]; - } -}; - -pub const Base64Decoder = struct { - const invalid_char: u8 = 0xff; - - /// e.g. 'A' => 0. - /// `invalid_char` for any value not in the 64 alphabet chars. - char_to_index: [256]u8, - pad_char: ?u8, - - pub fn init(alphabet_chars: [64]u8, pad_char: ?u8) Base64Decoder { - var result = Base64Decoder{ - .char_to_index = [_]u8{invalid_char} ** 256, - .pad_char = pad_char, - }; - - var char_in_alphabet = [_]bool{false} ** 256; - for (alphabet_chars, 0..) |c, i| { - assert(!char_in_alphabet[c]); - assert(pad_char == null or c != pad_char.?); - - result.char_to_index[c] = @intCast(u8, i); - char_in_alphabet[c] = true; - } - return result; - } - - /// Return the maximum possible decoded size for a given input length - The actual length may be less if the input includes padding. - /// `InvalidPadding` is returned if the input length is not valid. - pub fn calcSizeUpperBound(decoder: *const Base64Decoder, source_len: usize) Error!usize { - var result = source_len / 4 * 3; - const leftover = source_len % 4; - if (decoder.pad_char != null) { - if (leftover % 4 != 0) return error.InvalidPadding; - } else { - if (leftover % 4 == 1) return error.InvalidPadding; - result += leftover * 3 / 4; - } - return result; - } - - /// Return the exact decoded size for a slice. - /// `InvalidPadding` is returned if the input length is not valid. - pub fn calcSizeForSlice(decoder: *const Base64Decoder, source: []const u8) Error!usize { - const source_len = source.len; - var result = try decoder.calcSizeUpperBound(source_len); - if (decoder.pad_char) |pad_char| { - if (source_len >= 1 and source[source_len - 1] == pad_char) result -= 1; - if (source_len >= 2 and source[source_len - 2] == pad_char) result -= 1; - } - return result; - } - - /// dest.len must be what you get from ::calcSize. - /// invalid characters result in error.InvalidCharacter. - /// invalid padding results in error.InvalidPadding. - pub fn decode(decoder: *const Base64Decoder, dest: []u8, source: []const u8) Error!void { - if (decoder.pad_char != null and source.len % 4 != 0) return error.InvalidPadding; - var acc: u12 = 0; - var acc_len: u4 = 0; - var dest_idx: usize = 0; - var leftover_idx: ?usize = null; - for (source, 0..) |c, src_idx| { - const d = decoder.char_to_index[c]; - if (d == invalid_char) { - if (decoder.pad_char == null or c != decoder.pad_char.?) return error.InvalidCharacter; - leftover_idx = src_idx; - break; - } - acc = (acc << 6) + d; - acc_len += 6; - if (acc_len >= 8) { - acc_len -= 8; - dest[dest_idx] = @truncate(u8, acc >> acc_len); - dest_idx += 1; - } - } - if (acc_len > 4 or (acc & (@as(u12, 1) << acc_len) - 1) != 0) { - return error.InvalidPadding; - } - if (leftover_idx == null) return; - var leftover = source[leftover_idx.?..]; - if (decoder.pad_char) |pad_char| { - const padding_len = acc_len / 2; - var padding_chars: usize = 0; - for (leftover) |c| { - if (c != pad_char) { - return if (c == Base64Decoder.invalid_char) error.InvalidCharacter else error.InvalidPadding; - } - padding_chars += 1; - } - if (padding_chars != padding_len) return error.InvalidPadding; - } - } -}; - -pub const Base64DecoderWithIgnore = struct { - decoder: Base64Decoder, - char_is_ignored: [256]bool, - - pub fn init(alphabet_chars: [64]u8, pad_char: ?u8, ignore_chars: []const u8) Base64DecoderWithIgnore { - var result = Base64DecoderWithIgnore{ - .decoder = Base64Decoder.init(alphabet_chars, pad_char), - .char_is_ignored = [_]bool{false} ** 256, - }; - for (ignore_chars) |c| { - assert(result.decoder.char_to_index[c] == Base64Decoder.invalid_char); - assert(!result.char_is_ignored[c]); - assert(result.decoder.pad_char != c); - result.char_is_ignored[c] = true; - } - return result; - } - - /// Return the maximum possible decoded size for a given input length - The actual length may be less if the input includes padding - /// `InvalidPadding` is returned if the input length is not valid. - pub fn calcSizeUpperBound(decoder_with_ignore: *const Base64DecoderWithIgnore, source_len: usize) Error!usize { - var result = source_len / 4 * 3; - if (decoder_with_ignore.decoder.pad_char == null) { - const leftover = source_len % 4; - result += leftover * 3 / 4; - } - return result; - } - - /// Invalid characters that are not ignored result in error.InvalidCharacter. - /// Invalid padding results in error.InvalidPadding. - /// Decoding more data than can fit in dest results in error.NoSpaceLeft. See also ::calcSizeUpperBound. - /// Returns the number of bytes written to dest. - pub fn decode(decoder_with_ignore: *const Base64DecoderWithIgnore, dest: []u8, source: []const u8) Error!usize { - const decoder = &decoder_with_ignore.decoder; - var acc: u12 = 0; - var acc_len: u4 = 0; - var dest_idx: usize = 0; - var leftover_idx: ?usize = null; - for (source, 0..) |c, src_idx| { - if (decoder_with_ignore.char_is_ignored[c]) continue; - const d = decoder.char_to_index[c]; - if (d == Base64Decoder.invalid_char) { - if (decoder.pad_char == null or c != decoder.pad_char.?) return error.InvalidCharacter; - leftover_idx = src_idx; - break; - } - acc = (acc << 6) + d; - acc_len += 6; - if (acc_len >= 8) { - if (dest_idx == dest.len) return error.NoSpaceLeft; - acc_len -= 8; - dest[dest_idx] = @truncate(u8, acc >> acc_len); - dest_idx += 1; - } - } - if (acc_len > 4 or (acc & (@as(u12, 1) << acc_len) - 1) != 0) { - return error.InvalidPadding; - } - const padding_len = acc_len / 2; - if (leftover_idx == null) { - if (decoder.pad_char != null and padding_len != 0) return error.InvalidPadding; - return dest_idx; - } - var leftover = source[leftover_idx.?..]; - if (decoder.pad_char) |pad_char| { - var padding_chars: usize = 0; - for (leftover) |c| { - if (decoder_with_ignore.char_is_ignored[c]) continue; - if (c != pad_char) { - return if (c == Base64Decoder.invalid_char) error.InvalidCharacter else error.InvalidPadding; - } - padding_chars += 1; - } - if (padding_chars != padding_len) return error.InvalidPadding; - } - return dest_idx; - } -}; - -test "base64" { - @setEvalBranchQuota(8000); - try testBase64(); - comptime try testAllApis(standard, "comptime", "Y29tcHRpbWU="); -} - -test "base64 url_safe_no_pad" { - @setEvalBranchQuota(8000); - try testBase64UrlSafeNoPad(); - comptime try testAllApis(url_safe_no_pad, "comptime", "Y29tcHRpbWU"); -} - -fn testBase64() !void { - const codecs = standard; - - try testAllApis(codecs, "", ""); - try testAllApis(codecs, "f", "Zg=="); - try testAllApis(codecs, "fo", "Zm8="); - try testAllApis(codecs, "foo", "Zm9v"); - try testAllApis(codecs, "foob", "Zm9vYg=="); - try testAllApis(codecs, "fooba", "Zm9vYmE="); - try testAllApis(codecs, "foobar", "Zm9vYmFy"); - - try testDecodeIgnoreSpace(codecs, "", " "); - try testDecodeIgnoreSpace(codecs, "f", "Z g= ="); - try testDecodeIgnoreSpace(codecs, "fo", " Zm8="); - try testDecodeIgnoreSpace(codecs, "foo", "Zm9v "); - try testDecodeIgnoreSpace(codecs, "foob", "Zm9vYg = = "); - try testDecodeIgnoreSpace(codecs, "fooba", "Zm9v YmE="); - try testDecodeIgnoreSpace(codecs, "foobar", " Z m 9 v Y m F y "); - - // test getting some api errors - try testError(codecs, "A", error.InvalidPadding); - try testError(codecs, "AA", error.InvalidPadding); - try testError(codecs, "AAA", error.InvalidPadding); - try testError(codecs, "A..A", error.InvalidCharacter); - try testError(codecs, "AA=A", error.InvalidPadding); - try testError(codecs, "AA/=", error.InvalidPadding); - try testError(codecs, "A/==", error.InvalidPadding); - try testError(codecs, "A===", error.InvalidPadding); - try testError(codecs, "====", error.InvalidPadding); - - try testNoSpaceLeftError(codecs, "AA=="); - try testNoSpaceLeftError(codecs, "AAA="); - try testNoSpaceLeftError(codecs, "AAAA"); - try testNoSpaceLeftError(codecs, "AAAAAA=="); -} - -fn testBase64UrlSafeNoPad() !void { - const codecs = url_safe_no_pad; - - try testAllApis(codecs, "", ""); - try testAllApis(codecs, "f", "Zg"); - try testAllApis(codecs, "fo", "Zm8"); - try testAllApis(codecs, "foo", "Zm9v"); - try testAllApis(codecs, "foob", "Zm9vYg"); - try testAllApis(codecs, "fooba", "Zm9vYmE"); - try testAllApis(codecs, "foobar", "Zm9vYmFy"); - - try testDecodeIgnoreSpace(codecs, "", " "); - try testDecodeIgnoreSpace(codecs, "f", "Z g "); - try testDecodeIgnoreSpace(codecs, "fo", " Zm8"); - try testDecodeIgnoreSpace(codecs, "foo", "Zm9v "); - try testDecodeIgnoreSpace(codecs, "foob", "Zm9vYg "); - try testDecodeIgnoreSpace(codecs, "fooba", "Zm9v YmE"); - try testDecodeIgnoreSpace(codecs, "foobar", " Z m 9 v Y m F y "); - - // test getting some api errors - try testError(codecs, "A", error.InvalidPadding); - try testError(codecs, "AAA=", error.InvalidCharacter); - try testError(codecs, "A..A", error.InvalidCharacter); - try testError(codecs, "AA=A", error.InvalidCharacter); - try testError(codecs, "AA/=", error.InvalidCharacter); - try testError(codecs, "A/==", error.InvalidCharacter); - try testError(codecs, "A===", error.InvalidCharacter); - try testError(codecs, "====", error.InvalidCharacter); - - try testNoSpaceLeftError(codecs, "AA"); - try testNoSpaceLeftError(codecs, "AAA"); - try testNoSpaceLeftError(codecs, "AAAA"); - try testNoSpaceLeftError(codecs, "AAAAAA"); -} - -fn testAllApis(codecs: Codecs, expected_decoded: []const u8, expected_encoded: []const u8) !void { - // Base64Encoder - { - var buffer: [0x100]u8 = undefined; - const encoded = codecs.Encoder.encode(&buffer, expected_decoded); - try testing.expectEqualSlices(u8, expected_encoded, encoded); - } - - // Base64Decoder - { - var buffer: [0x100]u8 = undefined; - var decoded = buffer[0..try codecs.Decoder.calcSizeForSlice(expected_encoded)]; - try codecs.Decoder.decode(decoded, expected_encoded); - try testing.expectEqualSlices(u8, expected_decoded, decoded); - } - - // Base64DecoderWithIgnore - { - const decoder_ignore_nothing = codecs.decoderWithIgnore(""); - var buffer: [0x100]u8 = undefined; - var decoded = buffer[0..try decoder_ignore_nothing.calcSizeUpperBound(expected_encoded.len)]; - var written = try decoder_ignore_nothing.decode(decoded, expected_encoded); - try testing.expect(written <= decoded.len); - try testing.expectEqualSlices(u8, expected_decoded, decoded[0..written]); - } -} - -fn testDecodeIgnoreSpace(codecs: Codecs, expected_decoded: []const u8, encoded: []const u8) !void { - const decoder_ignore_space = codecs.decoderWithIgnore(" "); - var buffer: [0x100]u8 = undefined; - var decoded = buffer[0..try decoder_ignore_space.calcSizeUpperBound(encoded.len)]; - var written = try decoder_ignore_space.decode(decoded, encoded); - try testing.expectEqualSlices(u8, expected_decoded, decoded[0..written]); -} - -fn testError(codecs: Codecs, encoded: []const u8, expected_err: anyerror) !void { - const decoder_ignore_space = codecs.decoderWithIgnore(" "); - var buffer: [0x100]u8 = undefined; - if (codecs.Decoder.calcSizeForSlice(encoded)) |decoded_size| { - var decoded = buffer[0..decoded_size]; - if (codecs.Decoder.decode(decoded, encoded)) |_| { - return error.ExpectedError; - } else |err| if (err != expected_err) return err; - } else |err| if (err != expected_err) return err; - - if (decoder_ignore_space.decode(buffer[0..], encoded)) |_| { - return error.ExpectedError; - } else |err| if (err != expected_err) return err; -} - -fn testNoSpaceLeftError(codecs: Codecs, encoded: []const u8) !void { - const decoder_ignore_space = codecs.decoderWithIgnore(" "); - var buffer: [0x100]u8 = undefined; - var decoded = buffer[0 .. (try codecs.Decoder.calcSizeForSlice(encoded)) - 1]; - if (decoder_ignore_space.decode(decoded, encoded)) |_| { - return error.ExpectedError; - } else |err| if (err != error.NoSpaceLeft) return err; -} diff --git a/src/bun.js/webcore/body.zig b/src/bun.js/webcore/body.zig index 97c9f78748da..d4bd490c174b 100644 --- a/src/bun.js/webcore/body.zig +++ b/src/bun.js/webcore/body.zig @@ -477,6 +477,7 @@ pub const Body = struct { // blob.bytes[0..blob.bytes.len], // []const u16, // str.utf16SliceAligned(), + // true, // ); // blob.len = @intCast(InlineBlob.IntSize, result.written); // std.debug.assert(@as(usize, result.read) == str.len); diff --git a/src/bun.js/webcore/encoding.zig b/src/bun.js/webcore/encoding.zig index deab624d0bdf..d58513c1e811 100644 --- a/src/bun.js/webcore/encoding.zig +++ b/src/bun.js/webcore/encoding.zig @@ -98,7 +98,7 @@ pub const TextEncoder = struct { // max utf16 -> utf8 length if (slice.len <= buf.len / 4) { - const result = strings.copyUTF16IntoUTF8(&buf, @TypeOf(slice), slice); + const result = strings.copyUTF16IntoUTF8(&buf, @TypeOf(slice), slice, true); if (result.read == 0 or result.written == 0) { const uint8array = JSC.JSValue.createUninitializedUint8Array(globalThis, 3); const array_buffer = uint8array.asArrayBuffer(globalThis).?; @@ -221,7 +221,7 @@ pub const TextEncoder = struct { ) u64 { var output = buf_ptr[0..buf_len]; const input = input_ptr[0..input_len]; - const result: strings.EncodeIntoResult = strings.copyUTF16IntoUTF8(output, []const u16, input); + const result: strings.EncodeIntoResult = strings.copyUTF16IntoUTF8(output, []const u16, input, true); if (result.read == 0 or result.written == 0) { const replacement_char = [_]u8{ 239, 191, 189 }; @memcpy(buf_ptr, &replacement_char, replacement_char.len); @@ -683,7 +683,7 @@ pub const TextDecoder = struct { }; pub const Encoder = struct { - export fn Bun__encoding__writeLatin1(input: [*]const u8, len: usize, to: [*]u8, to_len: usize, encoding: u8) i64 { + export fn Bun__encoding__writeLatin1(input: [*]const u8, len: usize, to: [*]u8, to_len: usize, encoding: u8) usize { return switch (@intToEnum(JSC.Node.Encoding, encoding)) { .utf8 => writeU8(input, len, to, to_len, .utf8), .latin1 => writeU8(input, len, to, to_len, .ascii), @@ -694,9 +694,9 @@ pub const Encoder = struct { .base64url => writeU8(input, len, to, to_len, .base64url), .hex => writeU8(input, len, to, to_len, .hex), else => unreachable, - }; + } catch 0; } - export fn Bun__encoding__writeUTF16(input: [*]const u16, len: usize, to: [*]u8, to_len: usize, encoding: u8) i64 { + export fn Bun__encoding__writeUTF16(input: [*]const u16, len: usize, to: [*]u8, to_len: usize, encoding: u8) usize { return switch (@intToEnum(JSC.Node.Encoding, encoding)) { .utf8 => writeU16(input, len, to, to_len, .utf8, false), .latin1 => writeU16(input, len, to, to_len, .ascii, false), @@ -707,7 +707,7 @@ pub const Encoder = struct { .base64url => writeU16(input, len, to, to_len, .base64url, false), .hex => writeU16(input, len, to, to_len, .hex, false), else => unreachable, - }; + } catch 0; } export fn Bun__encoding__byteLengthLatin1(input: [*]const u8, len: usize, encoding: u8) usize { return switch (@intToEnum(JSC.Node.Encoding, encoding)) { @@ -783,7 +783,7 @@ pub const Encoder = struct { } // pub fn writeUTF16AsUTF8(utf16: [*]const u16, len: usize, to: [*]u8, to_len: usize) callconv(.C) i32 { - // return @intCast(i32, strings.copyUTF16IntoUTF8(to[0..to_len], []const u16, utf16[0..len]).written); + // return @intCast(i32, strings.copyUTF16IntoUTF8(to[0..to_len], []const u16, utf16[0..len], true).written); // } pub fn toString(input_ptr: [*]const u8, len: usize, global: *JSGlobalObject, comptime encoding: JSC.Node.Encoding) JSValue { @@ -839,7 +839,7 @@ pub const Encoder = struct { return ZigString.toExternalU16(output.ptr, output.len, global); }, - JSC.Node.Encoding.hex => { + .hex => { var output = allocator.alloc(u8, input.len * 2) catch return ZigString.init("Out of memory").toErrorInstance(global); const wrote = strings.encodeBytesToHex(output, input); std.debug.assert(wrote == output.len); @@ -848,11 +848,11 @@ pub const Encoder = struct { return val.toExternalValue(global); }, - JSC.Node.Encoding.base64url => { + .base64url => { return JSC.WTF.toBase64URLStringValue(input, global); }, - JSC.Node.Encoding.base64 => { + .base64 => { const to_len = bun.base64.encodeLen(input); var to = allocator.alloc(u8, to_len) catch return ZigString.init("Out of memory").toErrorInstance(global); const wrote = bun.base64.encode(to, input); @@ -861,7 +861,7 @@ pub const Encoder = struct { } } - pub fn writeU8(input: [*]const u8, len: usize, to_ptr: [*]u8, to_len: usize, comptime encoding: JSC.Node.Encoding) i64 { + pub fn writeU8(input: [*]const u8, len: usize, to_ptr: [*]u8, to_len: usize, comptime encoding: JSC.Node.Encoding) !usize { if (len == 0 or to_len == 0) return 0; @@ -873,11 +873,11 @@ pub const Encoder = struct { // if (comptime encoding.isBinaryToText()) {} switch (comptime encoding) { - JSC.Node.Encoding.buffer => { + .buffer => { const written = @min(len, to_len); @memcpy(to_ptr, input, written); - return @intCast(i64, written); + return written; }, .latin1, .ascii => { const written = @min(len, to_len); @@ -891,14 +891,14 @@ pub const Encoder = struct { strings.copyLatin1IntoASCII(to, remain); } - return @intCast(i64, written); + return written; }, .utf8 => { // need to encode - return @intCast(i64, strings.copyLatin1IntoUTF8(to_ptr[0..to_len], []const u8, input[0..len]).written); + return strings.copyLatin1IntoUTF8(to_ptr[0..to_len], []const u8, input[0..len]).written; }, // encode latin1 into UTF16 - JSC.Node.Encoding.ucs2, JSC.Node.Encoding.utf16le => { + .ucs2, .utf16le => { if (to_len < 2) return 0; @@ -917,29 +917,13 @@ pub const Encoder = struct { } }, - JSC.Node.Encoding.hex => { - return @intCast(i64, strings.decodeHexToBytes(to_ptr[0..to_len], u8, input[0..len])); - }, - - JSC.Node.Encoding.base64url => { - var slice = strings.trim(input[0..len], "\r\n\t " ++ [_]u8{std.ascii.control_code.vt}); - if (slice.len == 0) - return 0; - - if (strings.endsWithComptime(slice, "==")) { - slice = slice[0 .. slice.len - 2]; - } else if (slice[slice.len - 1] == '=') { - slice = slice[0 .. slice.len - 1]; - } - - const wrote = bun.base64.decodeURLSafe(to_ptr[0..to_len], slice).written; - return @intCast(i64, wrote); + .hex => { + return strings.decodeHexToBytes(to_ptr[0..to_len], u8, input[0..len]); }, - JSC.Node.Encoding.base64 => { - return @intCast(i64, bun.base64.decode(to_ptr[0..to_len], input[0..len]).written); + .base64, .base64url => { + return bun.base64.decode(to_ptr[0..to_len], input[0..len]).written; }, - // else => return 0, } } @@ -952,46 +936,46 @@ pub const Encoder = struct { return strings.elementLengthLatin1IntoUTF8([]const u8, input[0..len]); }, - .latin1, JSC.Node.Encoding.ascii, JSC.Node.Encoding.buffer => { + .latin1, .ascii, .buffer => { return len; }, - JSC.Node.Encoding.ucs2, JSC.Node.Encoding.utf16le => { + .ucs2, .utf16le => { return strings.elementLengthUTF8IntoUTF16([]const u8, input[0..len]) * 2; }, - JSC.Node.Encoding.hex => { + .hex => { return len / 2; }, - JSC.Node.Encoding.base64, JSC.Node.Encoding.base64url => { + .base64, .base64url => { return bun.base64.decodeLen(input[0..len]); }, // else => return &[_]u8{}; } } - pub fn writeU16(input: [*]const u16, len: usize, to: [*]u8, to_len: usize, comptime encoding: JSC.Node.Encoding, comptime allow_partial_write: bool) i64 { + pub fn writeU16(input: [*]const u16, len: usize, to: [*]u8, to_len: usize, comptime encoding: JSC.Node.Encoding, comptime allow_partial_write: bool) !usize { if (len == 0) return 0; switch (comptime encoding) { .utf8 => { - return @intCast(i32, strings.copyUTF16IntoUTF8(to[0..to_len], []const u16, input[0..len]).written); + return strings.copyUTF16IntoUTF8(to[0..to_len], []const u16, input[0..len], allow_partial_write).written; }, - .latin1, JSC.Node.Encoding.ascii, JSC.Node.Encoding.buffer => { + .latin1, .ascii, .buffer => { const out = @min(len, to_len); strings.copyU16IntoU8(to[0..to_len], []const u16, input[0..out]); - return @intCast(i64, out); + return out; }, // string is already encoded, just need to copy the data - JSC.Node.Encoding.ucs2, JSC.Node.Encoding.utf16le => { + .ucs2, .utf16le => { if (allow_partial_write) { const bytes_input_len = len * 2; const written = @min(bytes_input_len, to_len); const input_u8 = @ptrCast([*]const u8, input); strings.copyU16IntoU8(to[0..written], []const u8, input_u8[0..written]); - return @intCast(i64, written); + return written; } else { const bytes_input_len = len * 2; const written = @min(bytes_input_len, to_len); @@ -1000,15 +984,15 @@ pub const Encoder = struct { const fixed_len = (written / 2) * 2; const input_u8 = @ptrCast([*]const u8, input); strings.copyU16IntoU8(to[0..written], []const u8, input_u8[0..fixed_len]); - return @intCast(i64, fixed_len); + return fixed_len; } }, - JSC.Node.Encoding.hex => { - return @intCast(i64, strings.decodeHexToBytes(to[0..to_len], u16, input[0..len])); + .hex => { + return strings.decodeHexToBytes(to[0..to_len], u16, input[0..len]); }, - JSC.Node.Encoding.base64, JSC.Node.Encoding.base64url => { + .base64, .base64url => { if (to_len < 2 or len == 0) return 0; @@ -1033,15 +1017,15 @@ pub const Encoder = struct { .ascii, .latin1, .utf8 => { return strings.elementLengthUTF16IntoUTF8([]const u16, input[0..len]); }, - JSC.Node.Encoding.ucs2, JSC.Node.Encoding.buffer, JSC.Node.Encoding.utf16le => { + .ucs2, .buffer, .utf16le => { return len * 2; }, - JSC.Node.Encoding.hex => { + .hex => { return len / 2; }, - JSC.Node.Encoding.base64, JSC.Node.Encoding.base64url => { + .base64, .base64url => { return bun.base64.decodeLenUpperBound(len); }, // else => return &[_]u8{}; @@ -1057,13 +1041,12 @@ pub const Encoder = struct { } pub fn constructFromU8(input: [*]const u8, len: usize, comptime encoding: JSC.Node.Encoding) []u8 { - if (len == 0) - return &[_]u8{}; + if (len == 0) return &[_]u8{}; const allocator = VirtualMachine.get().allocator; switch (comptime encoding) { - JSC.Node.Encoding.buffer => { + .buffer => { var to = allocator.alloc(u8, len) catch return &[_]u8{}; @memcpy(to.ptr, input, len); @@ -1082,52 +1065,35 @@ pub const Encoder = struct { }, // encode latin1 into UTF16 // return as bytes - JSC.Node.Encoding.ucs2, JSC.Node.Encoding.utf16le => { + .ucs2, .utf16le => { var to = allocator.alloc(u16, len) catch return &[_]u8{}; _ = strings.copyLatin1IntoUTF16([]u16, to, []const u8, input[0..len]); return std.mem.sliceAsBytes(to[0..len]); }, - JSC.Node.Encoding.hex => { + .hex => { if (len < 2) return &[_]u8{}; var to = allocator.alloc(u8, len / 2) catch return &[_]u8{}; - return to[0..strings.decodeHexToBytes(to, u8, input[0..len])]; + return to[0..strings.decodeHexToBytesTruncate(to, u8, input[0..len])]; }, - JSC.Node.Encoding.base64url => { - var slice = strings.trim(input[0..len], "\r\n\t " ++ [_]u8{std.ascii.control_code.vt}); - if (slice.len == 0) - return &[_]u8{}; + .base64, .base64url => { + const slice = strings.trim(input[0..len], "\r\n\t " ++ [_]u8{std.ascii.control_code.vt}); + if (slice.len == 0) return &[_]u8{}; - if (strings.endsWithComptime(slice, "==")) { - slice = slice[0 .. slice.len - 2]; - } else if (slice[slice.len - 1] == '=') { - slice = slice[0 .. slice.len - 1]; - } + const outlen = bun.base64.decodeLen(slice); + const to = allocator.alloc(u8, outlen) catch return &[_]u8{}; - const to_len = bun.base64.urlsafe.decoder.calcSizeForSlice(slice) catch unreachable; - var to = allocator.alloc(u8, to_len) catch return &[_]u8{}; - const wrote = bun.base64.decodeURLSafe(to[0..to_len], slice).written; + const wrote = bun.base64.decode(to[0..outlen], slice).written; return to[0..wrote]; }, - - JSC.Node.Encoding.base64 => { - var slice = strings.trim(input[0..len], "\r\n\t " ++ [_]u8{std.ascii.control_code.vt}); - var outlen = bun.base64.decodeLen(slice); - - var to = allocator.alloc(u8, outlen) catch return &[_]u8{}; - const written = bun.base64.decode(to[0..outlen], slice).written; - return to[0..@min(written, outlen)]; - }, - // else => return 0, } } pub fn constructFromU16(input: [*]const u16, len: usize, comptime encoding: JSC.Node.Encoding) []u8 { - if (len == 0) - return &[_]u8{}; + if (len == 0) return &[_]u8{}; const allocator = VirtualMachine.get().allocator; @@ -1135,7 +1101,7 @@ pub const Encoder = struct { .utf8 => { return strings.toUTF8AllocWithType(allocator, []const u16, input[0..len]) catch return &[_]u8{}; }, - JSC.Node.Encoding.latin1, JSC.Node.Encoding.buffer, JSC.Node.Encoding.ascii => { + .latin1, .buffer, .ascii => { var to = allocator.alloc(u8, len) catch return &[_]u8{}; var input_bytes = std.mem.sliceAsBytes(input[0..len]); @memcpy(to.ptr, input_bytes.ptr, input_bytes.len); @@ -1146,34 +1112,24 @@ pub const Encoder = struct { return to; }, // string is already encoded, just need to copy the data - JSC.Node.Encoding.ucs2, JSC.Node.Encoding.utf16le => { + .ucs2, .utf16le => { var to = std.mem.sliceAsBytes(allocator.alloc(u16, len * 2) catch return &[_]u8{}); @memcpy(to.ptr, std.mem.sliceAsBytes(input[0..len]).ptr, std.mem.sliceAsBytes(input[0..len]).len); return to; }, - JSC.Node.Encoding.hex => { + .hex => { var to = allocator.alloc(u8, len * 2) catch return &[_]u8{}; - return to[0..strings.decodeHexToBytes(to, u16, input[0..len])]; + return to[0..strings.decodeHexToBytesTruncate(to, u16, input[0..len])]; }, - JSC.Node.Encoding.base64 => { - // very very slow case! - // shouldn't really happen though - var transcoded = strings.toUTF8Alloc(allocator, input[0..len]) catch return &[_]u8{}; - defer allocator.free(transcoded); - return constructFromU8(transcoded.ptr, transcoded.len, .base64); - }, - - JSC.Node.Encoding.base64url => { - + .base64, .base64url => { // very very slow case! // shouldn't really happen though var transcoded = strings.toUTF8Alloc(allocator, input[0..len]) catch return &[_]u8{}; defer allocator.free(transcoded); - return constructFromU8(transcoded.ptr, transcoded.len, .base64url); + return constructFromU8(transcoded.ptr, transcoded.len, encoding); }, - // else => return 0, } } diff --git a/src/bun.js/webcore/streams.zig b/src/bun.js/webcore/streams.zig index 1de209b693d9..748b4583c69e 100644 --- a/src/bun.js/webcore/streams.zig +++ b/src/bun.js/webcore/streams.zig @@ -983,7 +983,7 @@ pub const Sink = struct { if (stack_size >= str.len * 2) { var buf: [stack_size]u8 = undefined; - const copied = strings.copyUTF16IntoUTF8(&buf, []const u16, str); + const copied = strings.copyUTF16IntoUTF8(&buf, []const u16, str, true); std.debug.assert(copied.written <= stack_size); std.debug.assert(copied.read <= stack_size); if (input.isDone()) { diff --git a/src/http/websocket_http_client.zig b/src/http/websocket_http_client.zig index a4f9284d3894..a5be7faab5d1 100644 --- a/src/http/websocket_http_client.zig +++ b/src/http/websocket_http_client.zig @@ -776,7 +776,7 @@ const Copy = union(enum) { switch (this) { .utf16 => |utf16| { header.len = WebsocketHeader.packLength(content_byte_len); - const encode_into_result = strings.copyUTF16IntoUTF8(to_mask, []const u16, utf16); + const encode_into_result = strings.copyUTF16IntoUTF8(to_mask, []const u16, utf16, true); std.debug.assert(@as(usize, encode_into_result.written) == content_byte_len); std.debug.assert(@as(usize, encode_into_result.read) == utf16.len); header.len = WebsocketHeader.packLength(encode_into_result.written); diff --git a/src/napi/napi.zig b/src/napi/napi.zig index b06e25d7e548..99d7c82fbf5d 100644 --- a/src/napi/napi.zig +++ b/src/napi/napi.zig @@ -384,11 +384,8 @@ pub export fn napi_get_value_string_latin1(env: napi_env, value: napi_value, buf if (zig_str.is16Bit()) { const utf16 = zig_str.utf16SliceAligned(); - const wrote = JSC.WebCore.Encoder.writeU16(utf16.ptr, utf16.len, buf, buf_.len, .latin1, false); - if (wrote < 0) { - return genericFailure(); - } - maybeAppendNull(&buf[@intCast(usize, wrote)], bufsize == 0); + const wrote = JSC.WebCore.Encoder.writeU16(utf16.ptr, utf16.len, buf, buf_.len, .latin1, false) catch return genericFailure(); + maybeAppendNull(&buf[wrote], bufsize == 0); // if zero terminated, report the length of the string without the null result.* = @intCast(@TypeOf(result.*), wrote); return .ok; @@ -448,11 +445,8 @@ pub export fn napi_get_value_string_utf8(env: napi_env, value: napi_value, buf_p if (zig_str.is16Bit()) { const utf16 = zig_str.utf16SliceAligned(); - const wrote = JSC.WebCore.Encoder.writeU16(utf16.ptr, utf16.len, buf, buf_.len, .utf8, false); - if (wrote < 0) { - return genericFailure(); - } - buf[@intCast(usize, wrote)] = 0; + const wrote = JSC.WebCore.Encoder.writeU16(utf16.ptr, utf16.len, buf, buf_.len, .utf8, false) catch return genericFailure(); + buf[wrote] = 0; if (result_ptr) |result| { result.* = @intCast(@TypeOf(result.*), wrote); } diff --git a/src/string_immutable.zig b/src/string_immutable.zig index 9079cbc7e01a..4a8753ab7415 100644 --- a/src/string_immutable.zig +++ b/src/string_immutable.zig @@ -2621,7 +2621,7 @@ pub fn latin1ToCodepointBytesAssumeNotASCII16(char: u32) u16 { return latin1_to_utf16_conversion_table[@truncate(u8, char)]; } -pub fn copyUTF16IntoUTF8(buf: []u8, comptime Type: type, utf16: Type) EncodeIntoResult { +pub fn copyUTF16IntoUTF8(buf: []u8, comptime Type: type, utf16: Type, comptime allow_partial_write: bool) EncodeIntoResult { if (comptime Type == []const u16) { if (bun.FeatureFlags.use_simdutf) { if (utf16.len == 0) @@ -2635,14 +2635,14 @@ pub fn copyUTF16IntoUTF8(buf: []u8, comptime Type: type, utf16: Type) EncodeInto else buf.len; - return copyUTF16IntoUTF8WithBuffer(buf, Type, utf16, trimmed, out_len); + return copyUTF16IntoUTF8WithBuffer(buf, Type, utf16, trimmed, out_len, allow_partial_write); } } - return copyUTF16IntoUTF8WithBuffer(buf, Type, utf16, utf16, utf16.len); + return copyUTF16IntoUTF8WithBuffer(buf, Type, utf16, utf16, utf16.len, allow_partial_write); } -pub fn copyUTF16IntoUTF8WithBuffer(buf: []u8, comptime Type: type, utf16: Type, trimmed: Type, out_len: usize) EncodeIntoResult { +pub fn copyUTF16IntoUTF8WithBuffer(buf: []u8, comptime Type: type, utf16: Type, trimmed: Type, out_len: usize, comptime allow_partial_write: bool) EncodeIntoResult { var remaining = buf; var utf16_remaining = utf16; var ended_on_non_ascii = false; @@ -2675,7 +2675,7 @@ pub fn copyUTF16IntoUTF8WithBuffer(buf: []u8, comptime Type: type, utf16: Type, const width: usize = replacement.utf8Width(); if (width > remaining.len) { ended_on_non_ascii = width > 1; - switch (width) { + if (comptime allow_partial_write) switch (width) { 2 => { if (remaining.len > 0) { //only first will be written @@ -2721,7 +2721,7 @@ pub fn copyUTF16IntoUTF8WithBuffer(buf: []u8, comptime Type: type, utf16: Type, }, else => {}, - } + }; break; } @@ -3309,8 +3309,9 @@ pub fn indexOfNotChar(slice: []const u8, char: u8) ?u32 { return null; } +const invalid_char: u8 = 0xff; const hex_table: [255]u8 = brk: { - var values: [255]u8 = [_]u8{0} ** 255; + var values: [255]u8 = [_]u8{invalid_char} ** 255; values['0'] = 0; values['1'] = 1; values['2'] = 2; @@ -3337,22 +3338,41 @@ const hex_table: [255]u8 = brk: { break :brk values; }; -pub fn decodeHexToBytes(destination: []u8, comptime Char: type, source: []const Char) usize { +pub fn decodeHexToBytes(destination: []u8, comptime Char: type, source: []const Char) !usize { + return _decodeHexToBytes(destination, Char, source, false); +} + +pub fn decodeHexToBytesTruncate(destination: []u8, comptime Char: type, source: []const Char) usize { + return _decodeHexToBytes(destination, Char, source, true) catch 0; +} + +inline fn _decodeHexToBytes(destination: []u8, comptime Char: type, source: []const Char, comptime truncate: bool) !usize { var remain = destination; var input = source; - while (input.len > 1 and remain.len > 0) { + while (remain.len > 0 and input.len > 1) { const int = input[0..2].*; + if (comptime @sizeOf(Char) > 1) { + if (int[0] > std.math.maxInt(u8) or int[1] > std.math.maxInt(u8)) { + if (comptime truncate) break; + return error.InvalidByteSequence; + } + } const a = hex_table[@truncate(u8, int[0])]; const b = hex_table[@truncate(u8, int[1])]; - if (a == 255 or b == 255) { - break; + if (a == invalid_char or b == invalid_char) { + if (comptime truncate) break; + return error.InvalidByteSequence; } remain[0] = a << 4 | b; remain = remain[1..]; input = input[2..]; } + if (comptime !truncate) { + if (remain.len > 0 and input.len > 0) return error.InvalidByteSequence; + } + return destination.len - remain.len; } @@ -3667,7 +3687,7 @@ pub fn formatUTF16Type(comptime Slice: type, slice_: Slice, writer: anytype) !vo var slice = slice_; while (slice.len > 0) { - const result = strings.copyUTF16IntoUTF8(chunk, Slice, slice); + const result = strings.copyUTF16IntoUTF8(chunk, Slice, slice, true); if (result.read == 0 or result.written == 0) break; try writer.writeAll(chunk[0..result.written]); diff --git a/src/string_mutable.zig b/src/string_mutable.zig index dcaee8398d06..8457e7f12ba2 100644 --- a/src/string_mutable.zig +++ b/src/string_mutable.zig @@ -342,6 +342,7 @@ pub const MutableString = struct { this.remain()[0 .. bytes.len * 2], []const u16, bytes, + true, ); this.context.list.items.len += @as(usize, decoded.written); return pending.len; @@ -355,6 +356,7 @@ pub const MutableString = struct { this.remain()[0 .. bytes.len * 2], []const u16, bytes, + true, ); this.pos += @as(usize, decoded.written); } diff --git a/test/js/node/buffer.test.js b/test/js/node/buffer.test.js index 568bf8d44584..f0295b08face 100644 --- a/test/js/node/buffer.test.js +++ b/test/js/node/buffer.test.js @@ -1,4 +1,5 @@ -import { describe, it, expect, beforeEach, afterEach, test } from "bun:test"; +import { Buffer, SlowBuffer } from "buffer"; +import { describe, it, expect, beforeEach, afterEach } from "bun:test"; import { gc } from "harness"; const BufferModule = await import("buffer"); @@ -6,25 +7,6 @@ const BufferModule = await import("buffer"); beforeEach(() => gc()); afterEach(() => gc()); -function assert(a) { - expect(a).toBeTruthy(); -} - -Object.assign(assert, { - ok(a) { - expect(a).toBeTruthy(); - }, - deepStrictEqual(a, b) { - expect(b).toStrictEqual(a); - }, - strictEqual(a, b) { - expect(a).toBe(b); - }, - throws(a, b) { - expect(a).toThrow(); - }, -}); - // https://github.com/oven-sh/bun/issues/2052 it("Buffer global is settable", () => { var prevBuffer = globalThis.Buffer; @@ -35,68 +17,72 @@ it("Buffer global is settable", () => { expect(globalThis.Buffer).toBe(prevBuffer); }); -it("Buffer.alloc", () => { +it("length overflow", () => { // Verify the maximum Uint8Array size. There is no concrete limit by spec. The // internal limits should be updated if this fails. - assert.throws(() => new Uint8Array(2 ** 32 + 1), { - message: "Invalid typed array length: 4294967297", - }); + expect(() => new Uint8Array(2 ** 32 + 1)).toThrow(/length/); +}); +it("truncate input values", () => { const b = Buffer.allocUnsafe(1024); - assert.strictEqual(b.length, 1024); + expect(b.length).toBe(1024); b[0] = -1; - assert.strictEqual(b[0], 255); + expect(b[0]).toBe(255); for (let i = 0; i < 1024; i++) { - b[i] = i % 256; + b[i] = i; } for (let i = 0; i < 1024; i++) { - assert.strictEqual(i % 256, b[i]); + expect(i % 256).toBe(b[i]); } +}); +it("Buffer.allocUnsafe()", () => { const c = Buffer.allocUnsafe(512); - assert.strictEqual(c.length, 512); + expect(c.length).toBe(512); +}); +it("Buffer.from()", () => { const d = Buffer.from([]); - assert.strictEqual(d.length, 0); + expect(d.length).toBe(0); +}); - // Test offset properties - { - const b = Buffer.alloc(128); - assert.strictEqual(b.length, 128); - assert.strictEqual(b.byteOffset, 0); - assert.strictEqual(b.offset, 0); - } +it("offset properties", () => { + const b = Buffer.alloc(128); + expect(b.length).toBe(128); + expect(b.byteOffset).toBe(0); + expect(b.offset).toBe(0); +}); - // Test creating a Buffer from a Uint32Array - { - const ui32 = new Uint32Array(4).fill(42); - const e = Buffer.from(ui32); - for (const [index, value] of e.entries()) { - assert.strictEqual(value, ui32[index]); - } +it("creating a Buffer from a Uint32Array", () => { + const ui32 = new Uint32Array(4).fill(42); + const e = Buffer.from(ui32); + for (const [index, value] of e.entries()) { + expect(value).toBe(ui32[index]); } - // Test creating a Buffer from a Uint32Array (old constructor) - { - const ui32 = new Uint32Array(4).fill(42); - const e = Buffer(ui32); - for (const [key, value] of e.entries()) { - assert.deepStrictEqual(value, ui32[key]); - } +}); + +it("creating a Buffer from a Uint32Array (old constructor)", () => { + const ui32 = new Uint32Array(4).fill(42); + const e = Buffer(ui32); + for (const [key, value] of e.entries()) { + expect(value).toBe(ui32[key]); } +}); +it("invalid encoding", () => { + const b = Buffer.allocUnsafe(64); // Test invalid encoding for Buffer.toString - assert.throws(() => b.toString("invalid"), /Unknown encoding: invalid/); + expect(() => b.toString("invalid")).toThrow(/encoding/); // Invalid encoding for Buffer.write - assert.throws(() => b.write("test string", 0, 5, "invalid"), /Unknown encoding: invalid/); + expect(() => b.write("test string", 0, 5, "invalid")).toThrow(/encoding/); // Unsupported arguments for Buffer.write - // assert.throws(() => b.write("test", "utf8", 0), { - // code: "ERR_INVALID_ARG_TYPE", - // }); + expect(() => b.write("test", "utf8", 0)).toThrow(/invalid/i); +}); - // Try to create 0-length buffers. Should not throw. +it("create 0-length buffers", () => { Buffer.from(""); Buffer.from("", "ascii"); Buffer.from("", "latin1"); @@ -107,490 +93,445 @@ it("Buffer.alloc", () => { new Buffer("", "latin1"); new Buffer("", "binary"); Buffer(0); +}); - const outOfRangeError = { - code: "ERR_OUT_OF_RANGE", - name: "RangeError", - }; - +it("write() beyond end of buffer", () => { + const b = Buffer.allocUnsafe(64); // Try to write a 0-length string beyond the end of b - // assert.throws(() => b.write("", 2048), outOfRangeError); - - // // Throw when writing to negative offset - // assert.throws(() => b.write("a", -1), outOfRangeError); - - // // Throw when writing past bounds from the pool - // assert.throws(() => b.write("a", 2048), outOfRangeError); - - // // Throw when writing to negative offset - // assert.throws(() => b.write("a", -1), outOfRangeError); + expect(() => b.write("", 2048)).toThrow(RangeError); + // Throw when writing to negative offset + expect(() => b.write("a", -1)).toThrow(RangeError); + // Throw when writing past bounds from the pool + expect(() => b.write("a", 2048)).toThrow(RangeError); + // Throw when writing to negative offset + expect(() => b.write("a", -1)).toThrow(RangeError); + // Offset points to the end of the buffer and does not throw. + // (see https://github.com/nodejs/node/issues/8127). + Buffer.alloc(1).write("", 1, 0); +}); +it("copy() beyond end of buffer", () => { + const b = Buffer.allocUnsafe(64); // Try to copy 0 bytes worth of data into an empty buffer b.copy(Buffer.alloc(0), 0, 0, 0); - // Try to copy 0 bytes past the end of the target buffer b.copy(Buffer.alloc(0), 1, 1, 1); b.copy(Buffer.alloc(1), 1, 1, 1); - // Try to copy 0 bytes from past the end of the source buffer b.copy(Buffer.alloc(1), 0, 2048, 2048); +}); - // Testing for smart defaults and ability to pass string values as offset - { - const writeTest = Buffer.from("abcdes"); - writeTest.write("n", "ascii"); - assert.throws(() => writeTest.write("o", "1", "ascii"), { - code: "ERR_INVALID_ARG_TYPE", - }); - writeTest.write("o", 1, "ascii"); - writeTest.write("d", 2, "ascii"); - writeTest.write("e", 3, "ascii"); - writeTest.write("j", 4, "ascii"); - assert.strictEqual(writeTest.toString(), "nodejs"); - } - - // Offset points to the end of the buffer and does not throw. - // (see https://github.com/nodejs/node/issues/8127). - Buffer.alloc(1).write("", 1, 0); - - // ASCII slice test - { - const asciiString = "hello world"; - - for (let i = 0; i < asciiString.length; i++) { - b[i] = asciiString.charCodeAt(i); - } - const asciiSlice = b.toString("ascii", 0, asciiString.length); - assert.strictEqual(asciiString, asciiSlice); - } - - { - const asciiString = "hello world"; - const offset = 100; +it("smart defaults and ability to pass string values as offset", () => { + const writeTest = Buffer.from("abcdes"); + writeTest.write("n", "ascii"); + expect(() => writeTest.write("o", "1", "ascii")).toThrow(/offset/); + writeTest.write("o", 1, "ascii"); + writeTest.write("d", 2, "ascii"); + writeTest.write("e", 3, "ascii"); + writeTest.write("j", 4, "ascii"); + expect(writeTest.toString()).toBe("nodejs"); +}); - assert.strictEqual(asciiString.length, b.write(asciiString, offset, "ascii")); - const asciiSlice = b.toString("ascii", offset, offset + asciiString.length); - assert.strictEqual(asciiString, asciiSlice); +it("ASCII slice", () => { + const buf = Buffer.allocUnsafe(256); + const str = "hello world"; + for (let i = 0; i < str.length; i++) { + buf[i] = str.charCodeAt(i); } + expect(buf.toString("ascii", 0, str.length)).toBe(str); - { - const asciiString = "hello world"; - const offset = 100; + const offset = 100; + expect(buf.write(str, offset, "ascii")).toBe(str.length); + expect(buf.toString("ascii", offset, offset + str.length)).toBe(str); - const sliceA = b.slice(offset, offset + asciiString.length); - const sliceB = b.slice(offset, offset + asciiString.length); - for (let i = 0; i < asciiString.length; i++) { - assert.strictEqual(sliceA[i], sliceB[i]); - } + const slice1 = buf.slice(offset, offset + str.length); + const slice2 = buf.slice(offset, offset + str.length); + for (let i = 0; i < str.length; i++) { + expect(slice1[i]).toBe(slice2[i]); } +}); - // UTF-8 slice test - { - const utf8String = "¡hέlló wôrld!"; - const offset = 100; +it("UTF-8 slice", () => { + const b = Buffer.allocUnsafe(256); + const utf8String = "¡hέlló wôrld!"; + const offset = 100; - b.write(utf8String, 0, Buffer.byteLength(utf8String), "utf8"); - let utf8Slice = b.toString("utf8", 0, Buffer.byteLength(utf8String)); - assert.strictEqual(utf8String, utf8Slice); + b.write(utf8String, 0, Buffer.byteLength(utf8String), "utf8"); + expect(b.toString("utf8", 0, Buffer.byteLength(utf8String))).toBe(utf8String); - assert.strictEqual(Buffer.byteLength(utf8String), b.write(utf8String, offset, "utf8")); - utf8Slice = b.toString("utf8", offset, offset + Buffer.byteLength(utf8String)); - assert.strictEqual(utf8String, utf8Slice); + expect(b.write(utf8String, offset, "utf8")).toBe(Buffer.byteLength(utf8String)); + expect(b.toString("utf8", offset, offset + Buffer.byteLength(utf8String))).toBe(utf8String); - const sliceA = b.slice(offset, offset + Buffer.byteLength(utf8String)); - const sliceB = b.slice(offset, offset + Buffer.byteLength(utf8String)); - for (let i = 0; i < Buffer.byteLength(utf8String); i++) { - assert.strictEqual(sliceA[i], sliceB[i]); - } + const sliceA = b.slice(offset, offset + Buffer.byteLength(utf8String)); + const sliceB = b.slice(offset, offset + Buffer.byteLength(utf8String)); + for (let i = 0; i < Buffer.byteLength(utf8String); i++) { + expect(sliceA[i]).toBe(sliceB[i]); } - { - const slice = b.slice(100, 150); - assert.strictEqual(slice.length, 50); - for (let i = 0; i < 50; i++) { - assert.strictEqual(b[100 + i], slice[i]); - } + const slice = b.slice(100, 150); + expect(slice.length).toBe(50); + for (let i = 0; i < 50; i++) { + expect(b[100 + i]).toBe(slice[i]); } +}); - { - // Make sure only top level parent propagates from allocPool - const b = Buffer.allocUnsafe(5); - const c = b.slice(0, 4); - const d = c.slice(0, 2); - assert.strictEqual(b.parent, c.parent); - assert.strictEqual(b.parent, d.parent); - } +it("only top level parent propagates from allocPool", () => { + const b = Buffer.allocUnsafe(5); + const c = b.slice(0, 4); + const d = c.slice(0, 2); + expect(b.parent).toBe(c.parent); + expect(b.parent).toBe(d.parent); +}); - { - // Also from a non-pooled instance - const b = Buffer.allocUnsafeSlow(5); - const c = b.slice(0, 4); - const d = c.slice(0, 2); - assert.strictEqual(c.parent, d.parent); - } +it("only top level parent propagates from a non-pooled instance", () => { + const b = Buffer.allocUnsafeSlow(5); + const c = b.slice(0, 4); + const d = c.slice(0, 2); + expect(c.parent).toBe(d.parent); +}); - { - // Bug regression test - const testValue = "\u00F6\u65E5\u672C\u8A9E"; // ö日本語 - const buffer = Buffer.allocUnsafe(32); - const size = buffer.write(testValue, 0, "utf8"); - const slice = buffer.toString("utf8", 0, size); - assert.strictEqual(slice, testValue); - } +it("UTF-8 write() & slice()", () => { + const testValue = "\u00F6\u65E5\u672C\u8A9E"; // ö日本語 + const buffer = Buffer.allocUnsafe(32); + const size = buffer.write(testValue, 0, "utf8"); + const slice = buffer.toString("utf8", 0, size); + expect(slice).toBe(testValue); +}); - { - // Test triple slice - const a = Buffer.allocUnsafe(8); - for (let i = 0; i < 8; i++) a[i] = i; - const b = a.slice(4, 8); - assert.strictEqual(b[0], 4); - assert.strictEqual(b[1], 5); - assert.strictEqual(b[2], 6); - assert.strictEqual(b[3], 7); - const c = b.slice(2, 4); - assert.strictEqual(c[0], 6); - assert.strictEqual(c[1], 7); - } +it("triple slice", () => { + const a = Buffer.allocUnsafe(8); + for (let i = 0; i < 8; i++) a[i] = i; + const b = a.slice(4, 8); + expect(b[0]).toBe(4); + expect(b[1]).toBe(5); + expect(b[2]).toBe(6); + expect(b[3]).toBe(7); + const c = b.slice(2, 4); + expect(c[0]).toBe(6); + expect(c[1]).toBe(7); +}); - { - const d = Buffer.from([23, 42, 255]); - assert.strictEqual(d.length, 3); - assert.strictEqual(d[0], 23); - assert.strictEqual(d[1], 42); - assert.strictEqual(d[2], 255); - assert.deepStrictEqual(d, Buffer.from(d)); - } +it("Buffer.from() with encoding", () => { + const b = Buffer.from([23, 42, 255]); + expect(b.length).toBe(3); + expect(b[0]).toBe(23); + expect(b[1]).toBe(42); + expect(b[2]).toBe(255); + expect(Buffer.from(b)).toStrictEqual(b); - { - // Test for proper UTF-8 Encoding - const e = Buffer.from("über"); - assert.deepStrictEqual(e, Buffer.from([195, 188, 98, 101, 114])); - } + // Test for proper UTF-8 Encoding + expect(Buffer.from("über")).toStrictEqual(Buffer.from([195, 188, 98, 101, 114])); - { - // Test for proper ascii Encoding, length should be 4 - const f = Buffer.from("über", "ascii"); - assert.deepStrictEqual(f, Buffer.from([252, 98, 101, 114])); - } + // Test for proper ascii Encoding, length should be 4 + expect(Buffer.from("über", "ascii")).toStrictEqual(Buffer.from([252, 98, 101, 114])); ["ucs2", "ucs-2", "utf16le", "utf-16le"].forEach(encoding => { - { - // Test for proper UTF16LE encoding, length should be 8 - const f = Buffer.from("über", encoding); - assert.deepStrictEqual(f, Buffer.from([252, 0, 98, 0, 101, 0, 114, 0])); - } - - { - // Length should be 12 - const f = Buffer.from("привет", encoding); - assert.deepStrictEqual(f, Buffer.from([63, 4, 64, 4, 56, 4, 50, 4, 53, 4, 66, 4])); - assert.strictEqual(f.toString(encoding), "привет"); - } - - { - const f = Buffer.from([0, 0, 0, 0, 0]); - assert.strictEqual(f.length, 5); - const size = f.write("あいうえお", encoding); - assert.strictEqual(size, 4); - assert.deepStrictEqual(f, Buffer.from([0x42, 0x30, 0x44, 0x30, 0x00])); - } + // Test for proper UTF16LE encoding, length should be 8 + expect(Buffer.from("über", encoding)).toStrictEqual(Buffer.from([252, 0, 98, 0, 101, 0, 114, 0])); + + // Length should be 12 + const b = Buffer.from("привет", encoding); + expect(b).toStrictEqual(Buffer.from([63, 4, 64, 4, 56, 4, 50, 4, 53, 4, 66, 4])); + expect(b.toString(encoding)).toBe("привет"); + + const c = Buffer.from([0, 0, 0, 0, 0]); + expect(c.length).toBe(5); + expect(c.write("あいうえお", encoding)).toBe(4); + expect(c).toStrictEqual(Buffer.from([0x42, 0x30, 0x44, 0x30, 0x00])); }); - { - const f = Buffer.from("\uD83D\uDC4D", "utf-16le"); // THUMBS UP SIGN (U+1F44D) - assert.strictEqual(f.length, 4); - assert.deepStrictEqual(f, Buffer.from("3DD84DDC", "hex")); - } + const c = Buffer.from("\uD83D\uDC4D", "utf-16le"); // THUMBS UP SIGN (U+1F44D) + expect(c.length).toBe(4); + expect(c).toStrictEqual(Buffer.from("3DD84DDC", "hex")); +}); - // Test construction from arrayish object - { - const arrayIsh = { 0: 0, 1: 1, 2: 2, 3: 3, length: 4 }; - let g = Buffer.from(arrayIsh); - assert.deepStrictEqual(g, Buffer.from([0, 1, 2, 3])); - const strArrayIsh = { 0: "0", 1: "1", 2: "2", 3: "3", length: 4 }; - g = Buffer.from(strArrayIsh); - assert.deepStrictEqual(g, Buffer.from([0, 1, 2, 3])); - } +it("construction from arrayish object", () => { + const arrayIsh = { 0: 0, 1: 1, 2: 2, 3: 3, length: 4 }; + expect(Buffer.from(arrayIsh)).toStrictEqual(Buffer.from([0, 1, 2, 3])); + const strArrayIsh = { 0: "0", 1: "1", 2: "2", 3: "3", length: 4 }; + expect(Buffer.from(strArrayIsh)).toStrictEqual(Buffer.from([0, 1, 2, 3])); +}); - // - // Test toString('base64') - // - assert.strictEqual(Buffer.from("Man").toString("base64"), "TWFu"); - assert.strictEqual(Buffer.from("Woman").toString("base64"), "V29tYW4="); +it("toString('base64')", () => { + expect(Buffer.from("Man").toString("base64")).toBe("TWFu"); + expect(Buffer.from("Woman").toString("base64")).toBe("V29tYW4="); +}); - // - // Test toString('base64url') - // - assert.strictEqual(Buffer.from("Man").toString("base64url"), "TWFu"); - assert.strictEqual(Buffer.from("Woman").toString("base64url"), "V29tYW4"); +it("toString('base64url')", () => { + expect(Buffer.from("Man").toString("base64url")).toBe("TWFu"); + expect(Buffer.from("Woman").toString("base64url")).toBe("V29tYW4"); +}); - { - // Test that regular and URL-safe base64 both work both ways - const expected = [0xff, 0xff, 0xbe, 0xff, 0xef, 0xbf, 0xfb, 0xef, 0xff]; - assert.deepStrictEqual(Buffer.from("//++/++/++//", "base64"), Buffer.from(expected)); - assert.deepStrictEqual(Buffer.from("__--_--_--__", "base64"), Buffer.from(expected)); - assert.deepStrictEqual(Buffer.from("//++/++/++//", "base64url"), Buffer.from(expected)); - assert.deepStrictEqual(Buffer.from("__--_--_--__", "base64url"), Buffer.from(expected)); - } +it("regular and URL-safe base64 work both ways", () => { + const expected = [0xff, 0xff, 0xbe, 0xff, 0xef, 0xbf, 0xfb, 0xef, 0xff]; + expect(Buffer.from("//++/++/++//", "base64")).toStrictEqual(Buffer.from(expected)); + expect(Buffer.from("__--_--_--__", "base64")).toStrictEqual(Buffer.from(expected)); + expect(Buffer.from("//++/++/++//", "base64url")).toStrictEqual(Buffer.from(expected)); + expect(Buffer.from("__--_--_--__", "base64url")).toStrictEqual(Buffer.from(expected)); +}); - const base64flavors = ["base64", "base64url"]; +it("regular and URL-safe base64 work both ways with padding", () => { + const expected = [0xff, 0xff, 0xbe, 0xff, 0xef, 0xbf, 0xfb, 0xef, 0xff, 0xfb]; + expect(Buffer.from("//++/++/++//+w==", "base64")).toStrictEqual(Buffer.from(expected)); + expect(Buffer.from("//++/++/++//+w==", "base64")).toStrictEqual(Buffer.from(expected)); + expect(Buffer.from("//++/++/++//+w==", "base64url")).toStrictEqual(Buffer.from(expected)); + expect(Buffer.from("//++/++/++//+w==", "base64url")).toStrictEqual(Buffer.from(expected)); +}); - { - // Test that regular and URL-safe base64 both work both ways with padding - const expected = [0xff, 0xff, 0xbe, 0xff, 0xef, 0xbf, 0xfb, 0xef, 0xff, 0xfb]; - assert.deepStrictEqual(Buffer.from("//++/++/++//+w==", "base64"), Buffer.from(expected)); - assert.deepStrictEqual(Buffer.from("//++/++/++//+w==", "base64"), Buffer.from(expected)); - assert.deepStrictEqual(Buffer.from("//++/++/++//+w==", "base64url"), Buffer.from(expected)); - assert.deepStrictEqual(Buffer.from("//++/++/++//+w==", "base64url"), Buffer.from(expected)); - } +it("big example (base64 & base64url)", () => { + const quote = + "Man is distinguished, not only by his reason, but by this " + + "singular passion from other animals, which is a lust " + + "of the mind, that by a perseverance of delight in the " + + "continued and indefatigable generation of knowledge, " + + "exceeds the short vehemence of any carnal pleasure."; + const expected = + "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb" + + "24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlci" + + "BhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQ" + + "gYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu" + + "dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZ" + + "GdlLCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm" + + "5hbCBwbGVhc3VyZS4="; + + expect(Buffer.from(quote).toString("base64")).toBe(expected); + expect(Buffer.from(quote).toString("base64url")).toBe( + expected.replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", ""), + ); +}); - { - // big example - const quote = - "Man is distinguished, not only by his reason, but by this " + - "singular passion from other animals, which is a lust " + - "of the mind, that by a perseverance of delight in the " + - "continued and indefatigable generation of knowledge, " + - "exceeds the short vehemence of any carnal pleasure."; - const expected = - "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb" + - "24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlci" + - "BhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQ" + - "gYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu" + - "dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZ" + - "GdlLCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm" + - "5hbCBwbGVhc3VyZS4="; - assert.strictEqual(Buffer.from(quote).toString("base64"), expected); - assert.strictEqual( - Buffer.from(quote).toString("base64url"), - expected.replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", ""), - ); +function forEachBase64(label, test) { + ["base64", "base64url"].forEach(encoding => it(`${label} (${encoding})`, test.bind(null, encoding))); +} - base64flavors.forEach(encoding => { - let b = Buffer.allocUnsafe(1024); - let bytesWritten = b.write(expected, 0, encoding); - assert.strictEqual(quote.length, bytesWritten); - assert.strictEqual(quote, b.toString("ascii", 0, quote.length)); - - // Check that the base64 decoder ignores whitespace - const expectedWhite = - `${expected.slice(0, 60)} \n` + - `${expected.slice(60, 120)} \n` + - `${expected.slice(120, 180)} \n` + - `${expected.slice(180, 240)} \n` + - `${expected.slice(240, 300)}\n` + - `${expected.slice(300, 360)}\n`; - b = Buffer.allocUnsafe(1024); - bytesWritten = b.write(expectedWhite, 0, encoding); - assert.strictEqual(quote.length, bytesWritten); - assert.strictEqual(quote, b.toString("ascii", 0, quote.length)); - - // Check that the base64 decoder on the constructor works - // even in the presence of whitespace. - b = Buffer.from(expectedWhite, encoding); - assert.strictEqual(quote.length, b.length); - assert.strictEqual(quote, b.toString("ascii", 0, quote.length)); - - // Check that the base64 decoder ignores illegal chars - const expectedIllegal = - expected.slice(0, 60) + - " \x80" + - expected.slice(60, 120) + - " \xff" + - expected.slice(120, 180) + - " \x00" + - expected.slice(180, 240) + - " \x98" + - expected.slice(240, 300) + - "\x03" + - expected.slice(300, 360); - b = Buffer.from(expectedIllegal, encoding); - assert.strictEqual(quote.length, b.length); - assert.strictEqual(quote, b.toString("ascii", 0, quote.length)); - }); - } +forEachBase64("big example", encoding => { + const quote = + "Man is distinguished, not only by his reason, but by this " + + "singular passion from other animals, which is a lust " + + "of the mind, that by a perseverance of delight in the " + + "continued and indefatigable generation of knowledge, " + + "exceeds the short vehemence of any carnal pleasure."; + const expected = + "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb" + + "24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlci" + + "BhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQ" + + "gYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu" + + "dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZ" + + "GdlLCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm" + + "5hbCBwbGVhc3VyZS4="; - base64flavors.forEach(encoding => { - assert.strictEqual(Buffer.from("", encoding).toString(), ""); - assert.strictEqual(Buffer.from("K", encoding).toString(), ""); - - // multiple-of-4 with padding - assert.strictEqual(Buffer.from("Kg==", encoding).toString(), "*"); - assert.strictEqual(Buffer.from("Kio=", encoding).toString(), "*".repeat(2)); - assert.strictEqual(Buffer.from("Kioq", encoding).toString(), "*".repeat(3)); - assert.strictEqual(Buffer.from("KioqKg==", encoding).toString(), "*".repeat(4)); - assert.strictEqual(Buffer.from("KioqKio=", encoding).toString(), "*".repeat(5)); - assert.strictEqual(Buffer.from("KioqKioq", encoding).toString(), "*".repeat(6)); - assert.strictEqual(Buffer.from("KioqKioqKg==", encoding).toString(), "*".repeat(7)); - assert.strictEqual(Buffer.from("KioqKioqKio=", encoding).toString(), "*".repeat(8)); - assert.strictEqual(Buffer.from("KioqKioqKioq", encoding).toString(), "*".repeat(9)); - assert.strictEqual(Buffer.from("KioqKioqKioqKg==", encoding).toString(), "*".repeat(10)); - assert.strictEqual(Buffer.from("KioqKioqKioqKio=", encoding).toString(), "*".repeat(11)); - assert.strictEqual(Buffer.from("KioqKioqKioqKioq", encoding).toString(), "*".repeat(12)); - assert.strictEqual(Buffer.from("KioqKioqKioqKioqKg==", encoding).toString(), "*".repeat(13)); - assert.strictEqual(Buffer.from("KioqKioqKioqKioqKio=", encoding).toString(), "*".repeat(14)); - assert.strictEqual(Buffer.from("KioqKioqKioqKioqKioq", encoding).toString(), "*".repeat(15)); - assert.strictEqual(Buffer.from("KioqKioqKioqKioqKioqKg==", encoding).toString(), "*".repeat(16)); - assert.strictEqual(Buffer.from("KioqKioqKioqKioqKioqKio=", encoding).toString(), "*".repeat(17)); - assert.strictEqual(Buffer.from("KioqKioqKioqKioqKioqKioq", encoding).toString(), "*".repeat(18)); - assert.strictEqual(Buffer.from("KioqKioqKioqKioqKioqKioqKg==", encoding).toString(), "*".repeat(19)); - assert.strictEqual(Buffer.from("KioqKioqKioqKioqKioqKioqKio=", encoding).toString(), "*".repeat(20)); - - // No padding, not a multiple of 4 - assert.strictEqual(Buffer.from("Kg", encoding).toString(), "*"); - assert.strictEqual(Buffer.from("Kio", encoding).toString(), "*".repeat(2)); - assert.strictEqual(Buffer.from("KioqKg", encoding).toString(), "*".repeat(4)); - assert.strictEqual(Buffer.from("KioqKio", encoding).toString(), "*".repeat(5)); - assert.strictEqual(Buffer.from("KioqKioqKg", encoding).toString(), "*".repeat(7)); - assert.strictEqual(Buffer.from("KioqKioqKio", encoding).toString(), "*".repeat(8)); - assert.strictEqual(Buffer.from("KioqKioqKioqKg", encoding).toString(), "*".repeat(10)); - assert.strictEqual(Buffer.from("KioqKioqKioqKio", encoding).toString(), "*".repeat(11)); - assert.strictEqual(Buffer.from("KioqKioqKioqKioqKg", encoding).toString(), "*".repeat(13)); - assert.strictEqual(Buffer.from("KioqKioqKioqKioqKio", encoding).toString(), "*".repeat(14)); - assert.strictEqual(Buffer.from("KioqKioqKioqKioqKioqKg", encoding).toString(), "*".repeat(16)); - assert.strictEqual(Buffer.from("KioqKioqKioqKioqKioqKio", encoding).toString(), "*".repeat(17)); - assert.strictEqual(Buffer.from("KioqKioqKioqKioqKioqKioqKg", encoding).toString(), "*".repeat(19)); - assert.strictEqual(Buffer.from("KioqKioqKioqKioqKioqKioqKio", encoding).toString(), "*".repeat(20)); - }); + const b = Buffer.allocUnsafe(1024); + expect(b.write(expected, 0, encoding)).toBe(quote.length); + expect(b.toString("ascii", 0, quote.length)).toBe(quote); + + // Check that the base64 decoder ignores whitespace + const white = + `${expected.slice(0, 60)} \n` + + `${expected.slice(60, 120)} \n` + + `${expected.slice(120, 180)} \n` + + `${expected.slice(180, 240)} \n` + + `${expected.slice(240, 300)}\n` + + `${expected.slice(300, 360)}\n`; + const c = Buffer.allocUnsafe(1024); + expect(c.write(white, 0, encoding)).toBe(quote.length); + expect(c.toString("ascii", 0, quote.length)).toBe(quote); + + // Check that the base64 decoder on the constructor works + // even in the presence of whitespace. + const d = Buffer.from(white, encoding); + expect(d.length).toBe(quote.length); + expect(d.toString("ascii", 0, quote.length)).toBe(quote); + + // Check that the base64 decoder ignores illegal chars + const illegal = + expected.slice(0, 60) + + " \x80" + + expected.slice(60, 120) + + " \xff" + + expected.slice(120, 180) + + " \x00" + + expected.slice(180, 240) + + " \x98" + + expected.slice(240, 300) + + "\x03" + + expected.slice(300, 360); + const e = Buffer.from(illegal, encoding); + expect(e.length).toBe(quote.length); + expect(e.toString("ascii", 0, quote.length)).toBe(quote); +}); +forEachBase64("padding", encoding => { + expect(Buffer.from("", encoding).toString()).toBe(""); + expect(Buffer.from("K", encoding).toString()).toBe(""); + // multiple-of-4 with padding + expect(Buffer.from("Kg==", encoding).toString()).toBe("*"); + expect(Buffer.from("Kio=", encoding).toString()).toBe("*".repeat(2)); + expect(Buffer.from("Kioq", encoding).toString()).toBe("*".repeat(3)); + expect(Buffer.from("KioqKg==", encoding).toString()).toBe("*".repeat(4)); + expect(Buffer.from("KioqKio=", encoding).toString()).toBe("*".repeat(5)); + expect(Buffer.from("KioqKioq", encoding).toString()).toBe("*".repeat(6)); + expect(Buffer.from("KioqKioqKg==", encoding).toString()).toBe("*".repeat(7)); + expect(Buffer.from("KioqKioqKio=", encoding).toString()).toBe("*".repeat(8)); + expect(Buffer.from("KioqKioqKioq", encoding).toString()).toBe("*".repeat(9)); + expect(Buffer.from("KioqKioqKioqKg==", encoding).toString()).toBe("*".repeat(10)); + expect(Buffer.from("KioqKioqKioqKio=", encoding).toString()).toBe("*".repeat(11)); + expect(Buffer.from("KioqKioqKioqKioq", encoding).toString()).toBe("*".repeat(12)); + expect(Buffer.from("KioqKioqKioqKioqKg==", encoding).toString()).toBe("*".repeat(13)); + expect(Buffer.from("KioqKioqKioqKioqKio=", encoding).toString()).toBe("*".repeat(14)); + expect(Buffer.from("KioqKioqKioqKioqKioq", encoding).toString()).toBe("*".repeat(15)); + expect(Buffer.from("KioqKioqKioqKioqKioqKg==", encoding).toString()).toBe("*".repeat(16)); + expect(Buffer.from("KioqKioqKioqKioqKioqKio=", encoding).toString()).toBe("*".repeat(17)); + expect(Buffer.from("KioqKioqKioqKioqKioqKioq", encoding).toString()).toBe("*".repeat(18)); + expect(Buffer.from("KioqKioqKioqKioqKioqKioqKg==", encoding).toString()).toBe("*".repeat(19)); + expect(Buffer.from("KioqKioqKioqKioqKioqKioqKio=", encoding).toString()).toBe("*".repeat(20)); + // No padding, not a multiple of 4 + expect(Buffer.from("Kg", encoding).toString()).toBe("*"); + expect(Buffer.from("Kio", encoding).toString()).toBe("*".repeat(2)); + expect(Buffer.from("KioqKg", encoding).toString()).toBe("*".repeat(4)); + expect(Buffer.from("KioqKio", encoding).toString()).toBe("*".repeat(5)); + expect(Buffer.from("KioqKioqKg", encoding).toString()).toBe("*".repeat(7)); + expect(Buffer.from("KioqKioqKio", encoding).toString()).toBe("*".repeat(8)); + expect(Buffer.from("KioqKioqKioqKg", encoding).toString()).toBe("*".repeat(10)); + expect(Buffer.from("KioqKioqKioqKio", encoding).toString()).toBe("*".repeat(11)); + expect(Buffer.from("KioqKioqKioqKioqKg", encoding).toString()).toBe("*".repeat(13)); + expect(Buffer.from("KioqKioqKioqKioqKio", encoding).toString()).toBe("*".repeat(14)); + expect(Buffer.from("KioqKioqKioqKioqKioqKg", encoding).toString()).toBe("*".repeat(16)); + expect(Buffer.from("KioqKioqKioqKioqKioqKio", encoding).toString()).toBe("*".repeat(17)); + expect(Buffer.from("KioqKioqKioqKioqKioqKioqKg", encoding).toString()).toBe("*".repeat(19)); + expect(Buffer.from("KioqKioqKioqKioqKioqKioqKio", encoding).toString()).toBe("*".repeat(20)); // Handle padding graciously, multiple-of-4 or not - assert.strictEqual(Buffer.from("72INjkR5fchcxk9+VgdGPFJDxUBFR5/rMFsghgxADiw==", "base64").length, 32); - assert.strictEqual(Buffer.from("72INjkR5fchcxk9-VgdGPFJDxUBFR5_rMFsghgxADiw==", "base64url").length, 32); - assert.strictEqual(Buffer.from("72INjkR5fchcxk9+VgdGPFJDxUBFR5/rMFsghgxADiw=", "base64").length, 32); - assert.strictEqual(Buffer.from("72INjkR5fchcxk9-VgdGPFJDxUBFR5_rMFsghgxADiw=", "base64url").length, 32); - assert.strictEqual(Buffer.from("72INjkR5fchcxk9+VgdGPFJDxUBFR5/rMFsghgxADiw", "base64").length, 32); - assert.strictEqual(Buffer.from("72INjkR5fchcxk9-VgdGPFJDxUBFR5_rMFsghgxADiw", "base64url").length, 32); - assert.strictEqual(Buffer.from("w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg==", "base64").length, 31); - assert.strictEqual(Buffer.from("w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg==", "base64url").length, 31); - assert.strictEqual(Buffer.from("w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg=", "base64").length, 31); - assert.strictEqual(Buffer.from("w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg=", "base64url").length, 31); - assert.strictEqual(Buffer.from("w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg", "base64").length, 31); - assert.strictEqual(Buffer.from("w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg", "base64url").length, 31); + expect(Buffer.from("72INjkR5fchcxk9+VgdGPFJDxUBFR5/rMFsghgxADiw==", encoding).length).toBe(32); + expect(Buffer.from("72INjkR5fchcxk9-VgdGPFJDxUBFR5_rMFsghgxADiw==", encoding).length).toBe(32); + expect(Buffer.from("72INjkR5fchcxk9+VgdGPFJDxUBFR5/rMFsghgxADiw=", encoding).length).toBe(32); + expect(Buffer.from("72INjkR5fchcxk9-VgdGPFJDxUBFR5_rMFsghgxADiw=", encoding).length).toBe(32); + expect(Buffer.from("72INjkR5fchcxk9+VgdGPFJDxUBFR5/rMFsghgxADiw", encoding).length).toBe(32); + expect(Buffer.from("72INjkR5fchcxk9-VgdGPFJDxUBFR5_rMFsghgxADiw", encoding).length).toBe(32); + expect(Buffer.from("w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg==", encoding).length).toBe(31); + expect(Buffer.from("w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg=", encoding).length).toBe(31); + expect(Buffer.from("w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg", encoding).length).toBe(31); +}); - { - // This string encodes single '.' character in UTF-16 - const dot = Buffer.from("//4uAA==", "base64"); - assert.strictEqual(dot[0], 0xff); - assert.strictEqual(dot[1], 0xfe); - assert.strictEqual(dot[2], 0x2e); - assert.strictEqual(dot[3], 0x00); - assert.strictEqual(dot.toString("base64"), "//4uAA=="); - } +it("encodes single '.' character in UTF-16", () => { + const padded = Buffer.from("//4uAA==", "base64"); + expect(padded[0]).toBe(0xff); + expect(padded[1]).toBe(0xfe); + expect(padded[2]).toBe(0x2e); + expect(padded[3]).toBe(0x00); + expect(padded.toString("base64")).toBe("//4uAA=="); + + const dot = Buffer.from("//4uAA", "base64url"); + expect(dot[0]).toBe(0xff); + expect(dot[1]).toBe(0xfe); + expect(dot[2]).toBe(0x2e); + expect(dot[3]).toBe(0x00); + expect(dot.toString("base64url")).toBe("__4uAA"); +}); - { - // This string encodes single '.' character in UTF-16 - const dot = Buffer.from("//4uAA", "base64url"); - assert.strictEqual(dot[0], 0xff); - assert.strictEqual(dot[1], 0xfe); - assert.strictEqual(dot[2], 0x2e); - assert.strictEqual(dot[3], 0x00); - assert.strictEqual(dot.toString("base64url"), "__4uAA"); - } +// https://github.com/joyent/node/issues/402 +it("writing base64 at a position > 0 should not mangle the result", () => { + const segments = ["TWFkbmVzcz8h", "IFRoaXM=", "IGlz", "IG5vZGUuanMh"]; + const b = Buffer.allocUnsafe(64); + let pos = 0; - { - // Writing base64 at a position > 0 should not mangle the result. - // - // https://github.com/joyent/node/issues/402 - const segments = ["TWFkbmVzcz8h", "IFRoaXM=", "IGlz", "IG5vZGUuanMh"]; - const b = Buffer.allocUnsafe(64); - let pos = 0; - - for (let i = 0; i < segments.length; ++i) { - pos += b.write(segments[i], pos, "base64"); - } - assert.strictEqual(b.toString("latin1", 0, pos), "Madness?! This is node.js!"); + for (let i = 0; i < segments.length; ++i) { + pos += b.write(segments[i], pos, "base64"); } + expect(b.toString("latin1", 0, pos)).toBe("Madness?! This is node.js!"); +}); - { - // Writing base64url at a position > 0 should not mangle the result. - // - // https://github.com/joyent/node/issues/402 - const segments = ["TWFkbmVzcz8h", "IFRoaXM", "IGlz", "IG5vZGUuanMh"]; - const b = Buffer.allocUnsafe(64); - let pos = 0; - - for (let i = 0; i < segments.length; ++i) { - pos += b.write(segments[i], pos, "base64url"); - } - assert.strictEqual(b.toString("latin1", 0, pos), "Madness?! This is node.js!"); +// https://github.com/joyent/node/issues/402 +it("writing base64url at a position > 0 should not mangle the result", () => { + const segments = ["TWFkbmVzcz8h", "IFRoaXM", "IGlz", "IG5vZGUuanMh"]; + const b = Buffer.allocUnsafe(64); + let pos = 0; + + for (let i = 0; i < segments.length; ++i) { + pos += b.write(segments[i], pos, "base64url"); } + expect(b.toString("latin1", 0, pos)).toBe("Madness?! This is node.js!"); +}); +it("regression tests from Node.js", () => { // Regression test for https://github.com/nodejs/node/issues/3496. - assert.strictEqual(Buffer.from("=bad".repeat(1e4), "base64").length, 0); - + expect(Buffer.from("=bad".repeat(1e4), "base64").length).toBe(0); // Regression test for https://github.com/nodejs/node/issues/11987. - assert.deepStrictEqual(Buffer.from("w0 ", "base64"), Buffer.from("w0", "base64")); - + expect(Buffer.from("w0 ", "base64")).toStrictEqual(Buffer.from("w0", "base64")); // Regression test for https://github.com/nodejs/node/issues/13657. - assert.deepStrictEqual(Buffer.from(" YWJvcnVtLg", "base64"), Buffer.from("YWJvcnVtLg", "base64")); - - { - // Creating buffers larger than pool size. - const l = Buffer.poolSize + 5; - const s = "h".repeat(l); - const b = Buffer.from(s); + expect(Buffer.from(" YWJvcnVtLg", "base64")).toStrictEqual(Buffer.from("YWJvcnVtLg", "base64")); + // issue GH-3416 + Buffer.from(Buffer.allocUnsafe(0), 0, 0); + // Regression test for https://github.com/nodejs/node-v0.x-archive/issues/5482: + // should throw but not assert in C++ land. + expect(() => Buffer.from("", "buffer")).toThrow(/encoding/); +}); - for (let i = 0; i < l; i++) { - assert.strictEqual(b[i], "h".charCodeAt(0)); - } +it("creating buffers larger than pool size", () => { + const l = Buffer.poolSize + 5; + const s = "h".repeat(l); + const b = Buffer.from(s); - const sb = b.toString(); - assert.strictEqual(sb.length, s.length); - assert.strictEqual(sb, s); + for (let i = 0; i < l; i++) { + expect(b[i]).toBe("h".charCodeAt(0)); } - { - // test hex toString - const hexb = Buffer.allocUnsafe(256); - for (let i = 0; i < 256; i++) { - hexb[i] = i; - } - const hexStr = hexb.toString("hex"); - assert.strictEqual( - hexStr, - "000102030405060708090a0b0c0d0e0f" + - "101112131415161718191a1b1c1d1e1f" + - "202122232425262728292a2b2c2d2e2f" + - "303132333435363738393a3b3c3d3e3f" + - "404142434445464748494a4b4c4d4e4f" + - "505152535455565758595a5b5c5d5e5f" + - "606162636465666768696a6b6c6d6e6f" + - "707172737475767778797a7b7c7d7e7f" + - "808182838485868788898a8b8c8d8e8f" + - "909192939495969798999a9b9c9d9e9f" + - "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf" + - "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf" + - "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf" + - "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf" + - "e0e1e2e3e4e5e6e7e8e9eaebecedeeef" + - "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", - ); + const sb = b.toString(); + expect(sb.length).toBe(s.length); + expect(sb).toBe(s); +}); - const hexb2 = Buffer.from(hexStr, "hex"); - for (let i = 0; i < 256; i++) { - assert.strictEqual(hexb2[i], hexb[i]); - } +it("hex toString()", () => { + const hexb = Buffer.allocUnsafe(256); + for (let i = 0; i < 256; i++) { + hexb[i] = i; + } + const hexStr = hexb.toString("hex"); + expect(hexStr).toBe( + "000102030405060708090a0b0c0d0e0f" + + "101112131415161718191a1b1c1d1e1f" + + "202122232425262728292a2b2c2d2e2f" + + "303132333435363738393a3b3c3d3e3f" + + "404142434445464748494a4b4c4d4e4f" + + "505152535455565758595a5b5c5d5e5f" + + "606162636465666768696a6b6c6d6e6f" + + "707172737475767778797a7b7c7d7e7f" + + "808182838485868788898a8b8c8d8e8f" + + "909192939495969798999a9b9c9d9e9f" + + "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf" + + "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf" + + "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf" + + "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf" + + "e0e1e2e3e4e5e6e7e8e9eaebecedeeef" + + "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", + ); + + const hexb2 = Buffer.from(hexStr, "hex"); + for (let i = 0; i < 256; i++) { + expect(hexb2[i]).toBe(hexb[i]); } +}); - // Test single hex character is discarded. - assert.strictEqual(Buffer.from("A", "hex").length, 0); +it("single hex character is discarded", () => { + expect(Buffer.from("A", "hex").length).toBe(0); +}); - // Test that if a trailing character is discarded, rest of string is processed. - assert.deepStrictEqual(Buffer.from("Abx", "hex"), Buffer.from("Ab", "hex")); +it("if a trailing character is discarded, rest of string is processed", () => { + expect(Buffer.from("Abx", "hex")).toEqual(Buffer.from("Ab", "hex")); +}); - // Test single base64 char encodes as 0. - assert.strictEqual(Buffer.from("A", "base64").length, 0); +it("single base64 char encodes as 0", () => { + expect(Buffer.from("A", "base64").length).toBe(0); +}); - { - // Test an invalid slice end. - const b = Buffer.from([1, 2, 3, 4, 5]); - const b2 = b.toString("hex", 1, 10000); - const b3 = b.toString("hex", 1, 5); - const b4 = b.toString("hex", 1); - assert.strictEqual(b2, b3); - assert.strictEqual(b2, b4); - } +it("invalid slice end", () => { + const b = Buffer.from([1, 2, 3, 4, 5]); + const b2 = b.toString("hex", 1, 10000); + const b3 = b.toString("hex", 1, 5); + const b4 = b.toString("hex", 1); + expect(b2).toBe(b3); + expect(b2).toBe(b4); +}); +it("slice()", () => { function buildBuffer(data) { if (Array.isArray(data)) { const buffer = Buffer.allocUnsafe(data.length); @@ -601,522 +542,419 @@ it("Buffer.alloc", () => { } const x = buildBuffer([0x81, 0xa3, 0x66, 0x6f, 0x6f, 0xa3, 0x62, 0x61, 0x72]); + expect(x).toStrictEqual(Buffer.from([0x81, 0xa3, 0x66, 0x6f, 0x6f, 0xa3, 0x62, 0x61, 0x72])); + + const a = x.slice(4); + expect(a.length).toBe(5); + expect(a[0]).toBe(0x6f); + expect(a[1]).toBe(0xa3); + expect(a[2]).toBe(0x62); + expect(a[3]).toBe(0x61); + expect(a[4]).toBe(0x72); + + const b = x.slice(0); + expect(b.length).toBe(x.length); + + const c = x.slice(0, 4); + expect(c.length).toBe(4); + expect(c[0]).toBe(0x81); + expect(c[1]).toBe(0xa3); + + const d = x.slice(0, 9); + expect(d.length).toBe(9); + + const e = x.slice(1, 4); + expect(e.length).toBe(3); + expect(e[0]).toBe(0xa3); + + const f = x.slice(2, 4); + expect(f.length).toBe(2); + expect(f[0]).toBe(0x66); + expect(f[1]).toBe(0x6f); +}); - // assert.strictEqual(x.inspect(), ""); +function forEachUnicode(label, test) { + ["ucs2", "ucs-2", "utf16le", "utf-16le"].forEach(encoding => it(`${label} (${encoding})`, test.bind(null, encoding))); +} - { - const z = x.slice(4); - assert.strictEqual(z.length, 5); - assert.strictEqual(z[0], 0x6f); - assert.strictEqual(z[1], 0xa3); - assert.strictEqual(z[2], 0x62); - assert.strictEqual(z[3], 0x61); - assert.strictEqual(z[4], 0x72); - } +forEachUnicode("write()", encoding => { + const b = Buffer.allocUnsafe(10); + b.write("あいうえお", encoding); + expect(b.toString(encoding)).toBe("あいうえお"); +}); - { - const z = x.slice(0); - assert.strictEqual(z.length, x.length); - } +forEachUnicode("write() with offset", encoding => { + const b = Buffer.allocUnsafe(11); + b.write("あいうえお", 1, encoding); + expect(b.toString(encoding, 1)).toBe("あいうえお"); +}); - { - const z = x.slice(0, 4); - assert.strictEqual(z.length, 4); - assert.strictEqual(z[0], 0x81); - assert.strictEqual(z[1], 0xa3); - } +it("latin1 encoding should write only one byte per character", () => { + const b = Buffer.from([0xde, 0xad, 0xbe, 0xef]); + b.write(String.fromCharCode(0xffff), 0, "latin1"); + expect(b[0]).toBe(0xff); + expect(b[1]).toBe(0xad); + expect(b[2]).toBe(0xbe); + expect(b[3]).toBe(0xef); + b.write(String.fromCharCode(0xaaee), 0, "latin1"); + expect(b[0]).toBe(0xee); + expect(b[1]).toBe(0xad); + expect(b[2]).toBe(0xbe); + expect(b[3]).toBe(0xef); +}); - { - const z = x.slice(0, 9); - assert.strictEqual(z.length, 9); - } +it("binary encoding should write only one byte per character", () => { + const b = Buffer.from([0xde, 0xad, 0xbe, 0xef]); + b.write(String.fromCharCode(0xffff), 0, "latin1"); + expect(b[0]).toBe(0xff); + expect(b[1]).toBe(0xad); + expect(b[2]).toBe(0xbe); + expect(b[3]).toBe(0xef); + b.write(String.fromCharCode(0xaaee), 0, "latin1"); + expect(b[0]).toBe(0xee); + expect(b[1]).toBe(0xad); + expect(b[2]).toBe(0xbe); + expect(b[3]).toBe(0xef); +}); - { - const z = x.slice(1, 4); - assert.strictEqual(z.length, 3); - assert.strictEqual(z[0], 0xa3); - } +it("UTF-8 string includes null character", () => { + // https://github.com/nodejs/node-v0.x-archive/pull/1210 + expect(Buffer.from("\0").length).toBe(1); + expect(Buffer.from("\0\0").length).toBe(2); +}); - { - const z = x.slice(2, 4); - assert.strictEqual(z.length, 2); - assert.strictEqual(z[0], 0x66); - assert.strictEqual(z[1], 0x6f); - } +it("truncate write() at character boundary", () => { + const buf = Buffer.allocUnsafe(2); + expect(buf.write("")).toBe(0); // 0bytes + expect(buf.write("\0")).toBe(1); // 1byte (v8 adds null terminator) + expect(buf.write("a\0")).toBe(2); // 1byte * 2 + expect(buf.write("あ")).toBe(0); // 3bytes + expect(buf.write("\0あ")).toBe(1); // 1byte + 3bytes + expect(buf.write("\0\0あ")).toBe(2); // 1byte * 2 + 3bytes + + const buf2 = Buffer.allocUnsafe(10); + expect(buf2.write("あいう")).toBe(9); // 3bytes * 3 (v8 adds null term.) + expect(buf2.write("あいう\0")).toBe(10); // 3bytes * 3 + 1byte +}); - ["ucs2", "ucs-2", "utf16le", "utf-16le"].forEach(encoding => { - const b = Buffer.allocUnsafe(10); - b.write("あいうえお", encoding); - assert.strictEqual(b.toString(encoding), "あいうえお"); - }); +it("write() with maxLength", () => { + // https://github.com/nodejs/node-v0.x-archive/issues/243 + const buf = Buffer.allocUnsafe(4); + buf.fill(0xff); + expect(buf.write("abcd", 1, 2, "utf8")).toBe(2); + expect(buf[0]).toBe(0xff); + expect(buf[1]).toBe(0x61); + expect(buf[2]).toBe(0x62); + expect(buf[3]).toBe(0xff); + + buf.fill(0xff); + expect(buf.write("abcd", 1, 4)).toBe(3); + expect(buf[0]).toBe(0xff); + expect(buf[1]).toBe(0x61); + expect(buf[2]).toBe(0x62); + expect(buf[3]).toBe(0x63); + + buf.fill(0xff); + expect(buf.write("abcd", 1, 2, "utf8")).toBe(2); + expect(buf[0]).toBe(0xff); + expect(buf[1]).toBe(0x61); + expect(buf[2]).toBe(0x62); + expect(buf[3]).toBe(0xff); + + buf.fill(0xff); + expect(buf.write("abcdef", 1, 2, "hex")).toBe(2); + expect(buf[0]).toBe(0xff); + expect(buf[1]).toBe(0xab); + expect(buf[2]).toBe(0xcd); + expect(buf[3]).toBe(0xff); ["ucs2", "ucs-2", "utf16le", "utf-16le"].forEach(encoding => { - const b = Buffer.allocUnsafe(11); - b.write("あいうえお", 1, encoding); - assert.strictEqual(b.toString(encoding, 1), "あいうえお"); - }); - - { - // latin1 encoding should write only one byte per character. - const b = Buffer.from([0xde, 0xad, 0xbe, 0xef]); - let s = String.fromCharCode(0xffff); - b.write(s, 0, "latin1"); - assert.strictEqual(b[0], 0xff); - assert.strictEqual(b[1], 0xad); - assert.strictEqual(b[2], 0xbe); - assert.strictEqual(b[3], 0xef); - s = String.fromCharCode(0xaaee); - b.write(s, 0, "latin1"); - assert.strictEqual(b[0], 0xee); - assert.strictEqual(b[1], 0xad); - assert.strictEqual(b[2], 0xbe); - assert.strictEqual(b[3], 0xef); - } - - { - // Binary encoding should write only one byte per character. - const b = Buffer.from([0xde, 0xad, 0xbe, 0xef]); - let s = String.fromCharCode(0xffff); - b.write(s, 0, "latin1"); - assert.strictEqual(b[0], 0xff); - assert.strictEqual(b[1], 0xad); - assert.strictEqual(b[2], 0xbe); - assert.strictEqual(b[3], 0xef); - s = String.fromCharCode(0xaaee); - b.write(s, 0, "latin1"); - assert.strictEqual(b[0], 0xee); - assert.strictEqual(b[1], 0xad); - assert.strictEqual(b[2], 0xbe); - assert.strictEqual(b[3], 0xef); - } - - { - // https://github.com/nodejs/node-v0.x-archive/pull/1210 - // Test UTF-8 string includes null character - let buf = Buffer.from("\0"); - assert.strictEqual(buf.length, 1); - buf = Buffer.from("\0\0"); - assert.strictEqual(buf.length, 2); - } - - { - const buf = Buffer.allocUnsafe(2); - assert.strictEqual(buf.write(""), 0); // 0bytes - assert.strictEqual(buf.write("\0"), 1); // 1byte (v8 adds null terminator) - assert.strictEqual(buf.write("a\0"), 2); // 1byte * 2 - assert.strictEqual(buf.write("あ"), 0); // 3bytes - assert.strictEqual(buf.write("\0あ"), 1); // 1byte + 3bytes - assert.strictEqual(buf.write("\0\0あ"), 2); // 1byte * 2 + 3bytes - } - - { - const buf = Buffer.allocUnsafe(10); - assert.strictEqual(buf.write("あいう"), 9); // 3bytes * 3 (v8 adds null term.) - assert.strictEqual(buf.write("あいう\0"), 10); // 3bytes * 3 + 1byte - } - - { - // https://github.com/nodejs/node-v0.x-archive/issues/243 - // Test write() with maxLength - const buf = Buffer.allocUnsafe(4); - buf.fill(0xff); - assert.strictEqual(buf.write("abcd", 1, 2, "utf8"), 2); - assert.strictEqual(buf[0], 0xff); - assert.strictEqual(buf[1], 0x61); - assert.strictEqual(buf[2], 0x62); - assert.strictEqual(buf[3], 0xff); - - buf.fill(0xff); - assert.strictEqual(buf.write("abcd", 1, 4), 3); - assert.strictEqual(buf[0], 0xff); - assert.strictEqual(buf[1], 0x61); - assert.strictEqual(buf[2], 0x62); - assert.strictEqual(buf[3], 0x63); - - buf.fill(0xff); - assert.strictEqual(buf.write("abcd", 1, 2, "utf8"), 2); - assert.strictEqual(buf[0], 0xff); - assert.strictEqual(buf[1], 0x61); - assert.strictEqual(buf[2], 0x62); - assert.strictEqual(buf[3], 0xff); - buf.fill(0xff); - assert.strictEqual(buf.write("abcdef", 1, 2, "hex"), 2); - assert.strictEqual(buf[0], 0xff); - assert.strictEqual(buf[1], 0xab); - assert.strictEqual(buf[2], 0xcd); - assert.strictEqual(buf[3], 0xff); - - ["ucs2", "ucs-2", "utf16le", "utf-16le"].forEach(encoding => { - buf.fill(0xff); - assert.strictEqual(buf.write("abcd", 0, 2, encoding), 2); - assert.strictEqual(buf[0], 0x61); - assert.strictEqual(buf[1], 0x00); - assert.strictEqual(buf[2], 0xff); - assert.strictEqual(buf[3], 0xff); - }); - } - - { - // Test offset returns are correct - const b = Buffer.allocUnsafe(16); - assert.strictEqual(b.writeUInt32LE(0, 0), 4); - assert.strictEqual(b.writeUInt16LE(0, 4), 6); - assert.strictEqual(b.writeUInt8(0, 6), 7); - assert.strictEqual(b.writeInt8(0, 7), 8); - assert.strictEqual(b.writeDoubleLE(0, 8), 16); - } - - { - // Test unmatched surrogates not producing invalid utf8 output - // ef bf bd = utf-8 representation of unicode replacement character - // see https://codereview.chromium.org/121173009/ - let buf = Buffer.from("ab\ud800cd", "utf8"); - assert.strictEqual(buf[0], 0x61); - assert.strictEqual(buf[1], 0x62); - assert.strictEqual(buf[2], 0xef); - assert.strictEqual(buf[3], 0xbf); - assert.strictEqual(buf[4], 0xbd); - assert.strictEqual(buf[5], 0x63); - assert.strictEqual(buf[6], 0x64); - - buf = Buffer.from("abcd\ud800", "utf8"); + expect(buf.write("abcd", 0, 2, encoding)).toBe(2); expect(buf[0]).toBe(0x61); - expect(buf[1]).toBe(0x62); - expect(buf[2]).toBe(0x63); - expect(buf[3]).toBe(0x64); - expect(buf[4]).toBe(0xef); - expect(buf[5]).toBe(0xbf); - expect(buf[6]).toBe(0xbd); - - buf = Buffer.from("\ud800abcd", "utf8"); - expect(buf[0]).toBe(0xef); - expect(buf[1]).toBe(0xbf); - expect(buf[2]).toBe(0xbd); - expect(buf[3]).toBe(0x61); - expect(buf[4]).toBe(0x62); - expect(buf[5]).toBe(0x63); - expect(buf[6]).toBe(0x64); - } + expect(buf[1]).toBe(0x00); + expect(buf[2]).toBe(0xff); + expect(buf[3]).toBe(0xff); + }); +}); - { - // Test for buffer overrun - const buf = Buffer.from([0, 0, 0, 0, 0]); // length: 5 - const sub = buf.slice(0, 4); // length: 4 - assert.strictEqual(sub.write("12345", "latin1"), 4); - assert.strictEqual(buf[4], 0); - assert.strictEqual(sub.write("12345", "binary"), 4); - assert.strictEqual(buf[4], 0); - } +it("offset returns are correct", () => { + const b = Buffer.allocUnsafe(16); + expect(b.writeUInt32LE(0, 0)).toBe(4); + expect(b.writeUInt16LE(0, 4)).toBe(6); + expect(b.writeUInt8(0, 6)).toBe(7); + expect(b.writeInt8(0, 7)).toBe(8); + expect(b.writeDoubleLE(0, 8)).toBe(16); +}); - { - // Test alloc with fill option - const buf = Buffer.alloc(5, "800A", "hex"); - assert.strictEqual(buf[0], 128); - assert.strictEqual(buf[1], 10); - assert.strictEqual(buf[2], 128); - assert.strictEqual(buf[3], 10); - assert.strictEqual(buf[4], 128); - } +it("unmatched surrogates should not produce invalid utf8 output", () => { + // ef bf bd = utf-8 representation of unicode replacement character + // see https://codereview.chromium.org/121173009/ + let buf = Buffer.from("ab\ud800cd", "utf8"); + expect(buf[0]).toBe(0x61); + expect(buf[1]).toBe(0x62); + expect(buf[2]).toBe(0xef); + expect(buf[3]).toBe(0xbf); + expect(buf[4]).toBe(0xbd); + expect(buf[5]).toBe(0x63); + expect(buf[6]).toBe(0x64); + + buf = Buffer.from("abcd\ud800", "utf8"); + expect(buf[0]).toBe(0x61); + expect(buf[1]).toBe(0x62); + expect(buf[2]).toBe(0x63); + expect(buf[3]).toBe(0x64); + expect(buf[4]).toBe(0xef); + expect(buf[5]).toBe(0xbf); + expect(buf[6]).toBe(0xbd); + + buf = Buffer.from("\ud800abcd", "utf8"); + expect(buf[0]).toBe(0xef); + expect(buf[1]).toBe(0xbf); + expect(buf[2]).toBe(0xbd); + expect(buf[3]).toBe(0x61); + expect(buf[4]).toBe(0x62); + expect(buf[5]).toBe(0x63); + expect(buf[6]).toBe(0x64); +}); - // Check for fractional length args, junk length args, etc. - // https://github.com/joyent/node/issues/1758 +it("buffer overrun", () => { + const buf = Buffer.from([0, 0, 0, 0, 0]); // length: 5 + const sub = buf.slice(0, 4); // length: 4 + expect(sub.write("12345", "latin1")).toBe(4); + expect(buf[4]).toBe(0); + expect(sub.write("12345", "binary")).toBe(4); + expect(buf[4]).toBe(0); +}); +it("alloc with fill option", () => { + const buf = Buffer.alloc(5, "800A", "hex"); + expect(buf[0]).toBe(128); + expect(buf[1]).toBe(10); + expect(buf[2]).toBe(128); + expect(buf[3]).toBe(10); + expect(buf[4]).toBe(128); +}); + +// https://github.com/joyent/node/issues/1758 +it("check for fractional length args, junk length args, etc.", () => { // Call .fill() first, stops valgrind warning about uninitialized memory reads. Buffer.allocUnsafe(3.3).fill().toString(); // Throws bad argument error in commit 43cb4ec Buffer.alloc(3.3).fill().toString(); - assert.strictEqual(Buffer.allocUnsafe(3.3).length, 3); - assert.strictEqual(Buffer.from({ length: 3.3 }).length, 3); - assert.strictEqual(Buffer.from({ length: "BAM" }).length, 0); - + expect(Buffer.allocUnsafe(3.3).length).toBe(3); + expect(Buffer.from({ length: 3.3 }).length).toBe(3); + expect(Buffer.from({ length: "BAM" }).length).toBe(0); // Make sure that strings are not coerced to numbers. - assert.strictEqual(Buffer.from("99").length, 2); - assert.strictEqual(Buffer.from("13.37").length, 5); - + expect(Buffer.from("99").length).toBe(2); + expect(Buffer.from("13.37").length).toBe(5); // Ensure that the length argument is respected. ["ascii", "utf8", "hex", "base64", "latin1", "binary"].forEach(enc => { - assert.strictEqual(Buffer.allocUnsafe(1).write("aaaaaa", 0, 1, enc), 1); + expect(Buffer.allocUnsafe(1).write("aaaaaa", 0, 1, enc)).toBe(1); }); + // Regression test, guard against buffer overrun in the base64 decoder. + const a = Buffer.allocUnsafe(3); + const b = Buffer.from("xxx"); + a.write("aaaaaaaa", "base64"); + expect(b.toString()).toBe("xxx"); +}); - { - // Regression test, guard against buffer overrun in the base64 decoder. - const a = Buffer.allocUnsafe(3); - const b = Buffer.from("xxx"); - a.write("aaaaaaaa", "base64"); - assert.strictEqual(b.toString(), "xxx"); - } - - // issue GH-3416 - Buffer.from(Buffer.allocUnsafe(0), 0, 0); - +it("buffer overflow", () => { // issue GH-5587 - assert.throws(() => Buffer.alloc(8).writeFloatLE(0, 5), outOfRangeError); - assert.throws(() => Buffer.alloc(16).writeDoubleLE(0, 9), outOfRangeError); - + expect(() => Buffer.alloc(8).writeFloatLE(0, 5)).toThrow(RangeError); + expect(() => Buffer.alloc(16).writeDoubleLE(0, 9)).toThrow(RangeError); // Attempt to overflow buffers, similar to previous bug in array buffers - assert.throws(() => Buffer.allocUnsafe(8).writeFloatLE(0.0, 0xffffffff), outOfRangeError); - assert.throws(() => Buffer.allocUnsafe(8).writeFloatLE(0.0, 0xffffffff), outOfRangeError); - + expect(() => Buffer.allocUnsafe(8).writeFloatLE(0.0, 0xffffffff)).toThrow(RangeError); + expect(() => Buffer.allocUnsafe(8).writeFloatLE(0.0, 0xffffffff)).toThrow(RangeError); // Ensure negative values can't get past offset - assert.throws(() => Buffer.allocUnsafe(8).writeFloatLE(0.0, -1), outOfRangeError); - assert.throws(() => Buffer.allocUnsafe(8).writeFloatLE(0.0, -1), outOfRangeError); - - // Test for common write(U)IntLE/BE - { - let buf = Buffer.allocUnsafe(3); - buf.writeUIntLE(0x123456, 0, 3); - assert.deepStrictEqual(buf.toJSON().data, [0x56, 0x34, 0x12]); - assert.strictEqual(buf.readUIntLE(0, 3), 0x123456); - - buf.fill(0xff); - buf.writeUIntBE(0x123456, 0, 3); - assert.deepStrictEqual(buf.toJSON().data, [0x12, 0x34, 0x56]); - assert.strictEqual(buf.readUIntBE(0, 3), 0x123456); - - buf.fill(0xff); - buf.writeIntLE(0x123456, 0, 3); - assert.deepStrictEqual(buf.toJSON().data, [0x56, 0x34, 0x12]); - assert.strictEqual(buf.readIntLE(0, 3), 0x123456); - - buf.fill(0xff); - buf.writeIntBE(0x123456, 0, 3); - assert.deepStrictEqual(buf.toJSON().data, [0x12, 0x34, 0x56]); - assert.strictEqual(buf.readIntBE(0, 3), 0x123456); - - buf.fill(0xff); - buf.writeIntLE(-0x123456, 0, 3); - assert.deepStrictEqual(buf.toJSON().data, [0xaa, 0xcb, 0xed]); - assert.strictEqual(buf.readIntLE(0, 3), -0x123456); - - buf.fill(0xff); - buf.writeIntBE(-0x123456, 0, 3); - assert.deepStrictEqual(buf.toJSON().data, [0xed, 0xcb, 0xaa]); - assert.strictEqual(buf.readIntBE(0, 3), -0x123456); - - buf.fill(0xff); - buf.writeIntLE(-0x123400, 0, 3); - assert.deepStrictEqual(buf.toJSON().data, [0x00, 0xcc, 0xed]); - assert.strictEqual(buf.readIntLE(0, 3), -0x123400); - - buf.fill(0xff); - buf.writeIntBE(-0x123400, 0, 3); - assert.deepStrictEqual(buf.toJSON().data, [0xed, 0xcc, 0x00]); - assert.strictEqual(buf.readIntBE(0, 3), -0x123400); - - buf.fill(0xff); - buf.writeIntLE(-0x120000, 0, 3); - assert.deepStrictEqual(buf.toJSON().data, [0x00, 0x00, 0xee]); - assert.strictEqual(buf.readIntLE(0, 3), -0x120000); - - buf.fill(0xff); - buf.writeIntBE(-0x120000, 0, 3); - assert.deepStrictEqual(buf.toJSON().data, [0xee, 0x00, 0x00]); - assert.strictEqual(buf.readIntBE(0, 3), -0x120000); - - buf = Buffer.allocUnsafe(5); - buf.writeUIntLE(0x1234567890, 0, 5); - assert.deepStrictEqual(buf.toJSON().data, [0x90, 0x78, 0x56, 0x34, 0x12]); - assert.strictEqual(buf.readUIntLE(0, 5), 0x1234567890); - - buf.fill(0xff); - buf.writeUIntBE(0x1234567890, 0, 5); - assert.deepStrictEqual(buf.toJSON().data, [0x12, 0x34, 0x56, 0x78, 0x90]); - assert.strictEqual(buf.readUIntBE(0, 5), 0x1234567890); - - buf.fill(0xff); - buf.writeIntLE(0x1234567890, 0, 5); - assert.deepStrictEqual(buf.toJSON().data, [0x90, 0x78, 0x56, 0x34, 0x12]); - assert.strictEqual(buf.readIntLE(0, 5), 0x1234567890); - - buf.fill(0xff); - buf.writeIntBE(0x1234567890, 0, 5); - assert.deepStrictEqual(buf.toJSON().data, [0x12, 0x34, 0x56, 0x78, 0x90]); - assert.strictEqual(buf.readIntBE(0, 5), 0x1234567890); - - buf.fill(0xff); - buf.writeIntLE(-0x1234567890, 0, 5); - assert.deepStrictEqual(buf.toJSON().data, [0x70, 0x87, 0xa9, 0xcb, 0xed]); - assert.strictEqual(buf.readIntLE(0, 5), -0x1234567890); - - buf.fill(0xff); - buf.writeIntBE(-0x1234567890, 0, 5); - assert.deepStrictEqual(buf.toJSON().data, [0xed, 0xcb, 0xa9, 0x87, 0x70]); - assert.strictEqual(buf.readIntBE(0, 5), -0x1234567890); - - buf.fill(0xff); - buf.writeIntLE(-0x0012000000, 0, 5); - assert.deepStrictEqual(buf.toJSON().data, [0x00, 0x00, 0x00, 0xee, 0xff]); - assert.strictEqual(buf.readIntLE(0, 5), -0x0012000000); - - buf.fill(0xff); - buf.writeIntBE(-0x0012000000, 0, 5); - assert.deepStrictEqual(buf.toJSON().data, [0xff, 0xee, 0x00, 0x00, 0x00]); - assert.strictEqual(buf.readIntBE(0, 5), -0x0012000000); - } + expect(() => Buffer.allocUnsafe(8).writeFloatLE(0.0, -1)).toThrow(RangeError); + expect(() => Buffer.allocUnsafe(8).writeFloatLE(0.0, -1)).toThrow(RangeError); +}); - // Regression test for https://github.com/nodejs/node-v0.x-archive/issues/5482: - // should throw but not assert in C++ land. - assert.throws(() => Buffer.from("", "buffer"), { - code: "ERR_UNKNOWN_ENCODING", - name: "TypeError", - message: "Unknown encoding: buffer", - }); +it("common write{U}IntLE/BE()", () => { + let buf = Buffer.allocUnsafe(3); + buf.writeUIntLE(0x123456, 0, 3); + expect(buf.toJSON().data).toEqual([0x56, 0x34, 0x12]); + expect(buf.readUIntLE(0, 3)).toBe(0x123456); + + buf.fill(0xff); + buf.writeUIntBE(0x123456, 0, 3); + expect(buf.toJSON().data).toEqual([0x12, 0x34, 0x56]); + expect(buf.readUIntBE(0, 3)).toBe(0x123456); + + buf.fill(0xff); + buf.writeIntLE(0x123456, 0, 3); + expect(buf.toJSON().data).toEqual([0x56, 0x34, 0x12]); + expect(buf.readIntLE(0, 3)).toBe(0x123456); + + buf.fill(0xff); + buf.writeIntBE(0x123456, 0, 3); + expect(buf.toJSON().data).toEqual([0x12, 0x34, 0x56]); + expect(buf.readIntBE(0, 3)).toBe(0x123456); + + buf.fill(0xff); + buf.writeIntLE(-0x123456, 0, 3); + expect(buf.toJSON().data).toEqual([0xaa, 0xcb, 0xed]); + expect(buf.readIntLE(0, 3)).toBe(-0x123456); + + buf.fill(0xff); + buf.writeIntBE(-0x123456, 0, 3); + expect(buf.toJSON().data).toEqual([0xed, 0xcb, 0xaa]); + expect(buf.readIntBE(0, 3)).toBe(-0x123456); + + buf.fill(0xff); + buf.writeIntLE(-0x123400, 0, 3); + expect(buf.toJSON().data).toEqual([0x00, 0xcc, 0xed]); + expect(buf.readIntLE(0, 3)).toBe(-0x123400); + + buf.fill(0xff); + buf.writeIntBE(-0x123400, 0, 3); + expect(buf.toJSON().data).toEqual([0xed, 0xcc, 0x00]); + expect(buf.readIntBE(0, 3)).toBe(-0x123400); + + buf.fill(0xff); + buf.writeIntLE(-0x120000, 0, 3); + expect(buf.toJSON().data).toEqual([0x00, 0x00, 0xee]); + expect(buf.readIntLE(0, 3)).toBe(-0x120000); + + buf.fill(0xff); + buf.writeIntBE(-0x120000, 0, 3); + expect(buf.toJSON().data).toEqual([0xee, 0x00, 0x00]); + expect(buf.readIntBE(0, 3)).toBe(-0x120000); + + buf = Buffer.allocUnsafe(5); + buf.writeUIntLE(0x1234567890, 0, 5); + expect(buf.toJSON().data).toEqual([0x90, 0x78, 0x56, 0x34, 0x12]); + expect(buf.readUIntLE(0, 5)).toBe(0x1234567890); + + buf.fill(0xff); + buf.writeUIntBE(0x1234567890, 0, 5); + expect(buf.toJSON().data).toEqual([0x12, 0x34, 0x56, 0x78, 0x90]); + expect(buf.readUIntBE(0, 5)).toBe(0x1234567890); + + buf.fill(0xff); + buf.writeIntLE(0x1234567890, 0, 5); + expect(buf.toJSON().data).toEqual([0x90, 0x78, 0x56, 0x34, 0x12]); + expect(buf.readIntLE(0, 5)).toBe(0x1234567890); + + buf.fill(0xff); + buf.writeIntBE(0x1234567890, 0, 5); + expect(buf.toJSON().data).toEqual([0x12, 0x34, 0x56, 0x78, 0x90]); + expect(buf.readIntBE(0, 5)).toBe(0x1234567890); + + buf.fill(0xff); + buf.writeIntLE(-0x1234567890, 0, 5); + expect(buf.toJSON().data).toEqual([0x70, 0x87, 0xa9, 0xcb, 0xed]); + expect(buf.readIntLE(0, 5)).toBe(-0x1234567890); + + buf.fill(0xff); + buf.writeIntBE(-0x1234567890, 0, 5); + expect(buf.toJSON().data).toEqual([0xed, 0xcb, 0xa9, 0x87, 0x70]); + expect(buf.readIntBE(0, 5)).toBe(-0x1234567890); + + buf.fill(0xff); + buf.writeIntLE(-0x0012000000, 0, 5); + expect(buf.toJSON().data).toEqual([0x00, 0x00, 0x00, 0xee, 0xff]); + expect(buf.readIntLE(0, 5)).toBe(-0x0012000000); + + buf.fill(0xff); + buf.writeIntBE(-0x0012000000, 0, 5); + expect(buf.toJSON().data).toEqual([0xff, 0xee, 0x00, 0x00, 0x00]); + expect(buf.readIntBE(0, 5)).toBe(-0x0012000000); +}); +it("construct buffer from buffer", () => { // Regression test for https://github.com/nodejs/node-v0.x-archive/issues/6111. // Constructing a buffer from another buffer should a) work, and b) not corrupt // the source buffer. - { - const a = [...Array(128).keys()]; // [0, 1, 2, 3, ... 126, 127] - const b = Buffer.from(a); - const c = Buffer.from(b); - assert.strictEqual(b.length, a.length); - assert.strictEqual(c.length, a.length); - for (let i = 0, k = a.length; i < k; ++i) { - assert.strictEqual(a[i], i); - assert.strictEqual(b[i], i); - assert.strictEqual(c[i], i); - } + const a = [...Array(128).keys()]; // [0, 1, 2, 3, ... 126, 127] + const b = Buffer.from(a); + const c = Buffer.from(b); + expect(b.length).toBe(a.length); + expect(c.length).toBe(a.length); + for (let i = 0, k = a.length; i < k; ++i) { + expect(a[i]).toBe(i); + expect(b[i]).toBe(i); + expect(c[i]).toBe(i); } +}); - // if (common.hasCrypto) { - // eslint-disable-line node-core/crypto-check - // Test truncation after decode +it("truncation after decode", () => { const crypto = require("crypto"); - const b1 = Buffer.from("YW55=======", "base64"); - const b2 = Buffer.from("YW55", "base64"); - - assert.strictEqual( - crypto.createHash("sha1").update(b1).digest("hex"), - crypto.createHash("sha1").update(b2).digest("hex"), + expect(crypto.createHash("sha1").update(Buffer.from("YW55=======", "base64")).digest("hex")).toBe( + crypto.createHash("sha1").update(Buffer.from("YW55", "base64")).digest("hex"), ); - // } else { - // common.printSkipMessage("missing crypto"); - // } +}); +it("Buffer,poolSize", () => { const ps = Buffer.poolSize; Buffer.poolSize = 0; - assert(Buffer.allocUnsafe(1).parent instanceof ArrayBuffer); + expect(Buffer.allocUnsafe(1).parent instanceof ArrayBuffer).toBe(true); Buffer.poolSize = ps; - assert.throws(() => Buffer.allocUnsafe(10).copy(), { - code: "ERR_INVALID_ARG_TYPE", - name: "TypeError", - message: 'The "target" argument must be an instance of Buffer or ' + "Uint8Array. Received undefined", - }); + expect(() => Buffer.allocUnsafe(10).copy()).toThrow(TypeError); - assert.throws(() => Buffer.from(), { - name: "TypeError", - message: - "The first argument must be of type string or an instance of " + - "Buffer, ArrayBuffer, or Array or an Array-like Object. Received undefined", - }); - assert.throws(() => Buffer.from(null), { - name: "TypeError", - message: - "The first argument must be of type string or an instance of " + - "Buffer, ArrayBuffer, or Array or an Array-like Object. Received null", - }); + expect(() => Buffer.from()).toThrow(TypeError); + expect(() => Buffer.from(null)).toThrow(TypeError); +}); - // Test prototype getters don't throw - assert.strictEqual(Buffer.prototype.parent, undefined); - assert.strictEqual(Buffer.prototype.offset, undefined); - assert.strictEqual(SlowBuffer.prototype.parent, undefined); - assert.strictEqual(SlowBuffer.prototype.offset, undefined); +it("prototype getters should not throw", () => { + expect(Buffer.prototype.parent).toBeUndefined(); + expect(Buffer.prototype.offset).toBeUndefined(); + expect(SlowBuffer.prototype.parent).toBeUndefined(); + expect(SlowBuffer.prototype.offset).toBeUndefined(); +}); - { - // Test that large negative Buffer length inputs don't affect the pool offset. - // Use the fromArrayLike() variant here because it's more lenient - // about its input and passes the length directly to allocate(). - assert.deepStrictEqual(Buffer.from({ length: -Buffer.poolSize }), Buffer.from("")); - assert.deepStrictEqual(Buffer.from({ length: -100 }), Buffer.from("")); - - // Check pool offset after that by trying to write string into the pool. - Buffer.from("abc"); - } +it("large negative Buffer length inputs should not affect pool offset", () => { + // Use the fromArrayLike() variant here because it's more lenient + // about its input and passes the length directly to allocate(). + expect(Buffer.from({ length: -Buffer.poolSize })).toStrictEqual(Buffer.from("")); + expect(Buffer.from({ length: -100 })).toStrictEqual(Buffer.from("")); - // Test that ParseArrayIndex handles full uint32 - { - const errMsg = common.expectsError({ - code: "ERR_BUFFER_OUT_OF_BOUNDS", - name: "RangeError", - message: '"offset" is outside of buffer bounds', - }); - assert.throws(() => Buffer.from(new ArrayBuffer(0), -1 >>> 0), errMsg); - } + // Check pool offset after that by trying to write string into the pool. + Buffer.from("abc"); +}); + +it("ParseArrayIndex() should handle full uint32", () => { + expect(() => Buffer.from(new ArrayBuffer(0), -1 >>> 0)).toThrow(RangeError); +}); - // ParseArrayIndex() should reject values that don't fit in a 32 bits size_t. - assert.throws(() => { +it("ParseArrayIndex() should reject values that don't fit in a 32 bits size_t", () => { + expect(() => { const a = Buffer.alloc(1); const b = Buffer.alloc(1); a.copy(b, 0, 0x100000000, 0x100000001); - }, outOfRangeError); + }).toThrow(RangeError); +}); - // Unpooled buffer (replaces SlowBuffer) - { - const ubuf = Buffer.allocUnsafeSlow(10); - assert(ubuf); - assert(ubuf.buffer); - assert.strictEqual(ubuf.buffer.byteLength, 10); - } +it("unpooled buffer (replaces SlowBuffer)", () => { + const ubuf = Buffer.allocUnsafeSlow(10); + expect(ubuf).toBeTruthy(); + expect(ubuf.buffer).toBeTruthy(); + expect(ubuf.buffer.byteLength).toBe(10); +}); - // Regression test to verify that an empty ArrayBuffer does not throw. +it("verify that an empty ArrayBuffer does not throw", () => { Buffer.from(new ArrayBuffer()); +}); - // Test that ArrayBuffer from a different context is detected correctly. - // const arrayBuf = vm.runInNewContext("new ArrayBuffer()"); - // Buffer.from(arrayBuf); - // Buffer.from({ buffer: arrayBuf }); - - assert.throws(() => Buffer.alloc({ valueOf: () => 1 }), /"size" argument must be of type number/); - assert.throws(() => Buffer.alloc({ valueOf: () => -1 }), /"size" argument must be of type number/); - - assert.strictEqual(Buffer.prototype.toLocaleString, Buffer.prototype.toString); - { - const buf = Buffer.from("test"); - assert.strictEqual(buf.toLocaleString(), buf.toString()); - } - - assert.throws( - () => { - Buffer.alloc(0x1000, "This is not correctly encoded", "hex"); - }, - { - code: "ERR_INVALID_ARG_VALUE", - name: "TypeError", - }, - ); - - assert.throws( - () => { - Buffer.alloc(0x1000, "c", "hex"); - }, - { - code: "ERR_INVALID_ARG_VALUE", - name: "TypeError", - }, - ); +it("alloc() should throw on non-numeric size", () => { + expect(() => Buffer.alloc({ valueOf: () => 1 })).toThrow(TypeError); + expect(() => Buffer.alloc({ valueOf: () => -1 })).toThrow(TypeError); +}); - assert.throws( - () => { - Buffer.alloc(1, Buffer.alloc(0)); - }, - { - code: "ERR_INVALID_ARG_VALUE", - name: "TypeError", - }, - ); +it("toLocaleString()", () => { + const buf = Buffer.from("test"); + expect(buf.toLocaleString()).toBe(buf.toString()); + // expect(Buffer.prototype.toLocaleString).toBe(Buffer.prototype.toString); +}); - assert.throws( - () => { - Buffer.alloc(40, "x", 20); - }, - { - code: "ERR_INVALID_ARG_TYPE", - name: "TypeError", - }, - ); +it("alloc() should throw on invalid data", () => { + expect(() => Buffer.alloc(0x1000, "This is not correctly encoded", "hex")).toThrow(TypeError); + expect(() => Buffer.alloc(0x1000, "c", "hex")).toThrow(TypeError); + expect(() => Buffer.alloc(1, Buffer.alloc(0))).toThrow(TypeError); + expect(() => Buffer.alloc(40, "x", 20)).toThrow(TypeError); }); it("Buffer.toJSON()", () => { @@ -2064,8 +1902,58 @@ it("Buffer.fill (Node.js tests)", () => { const buf1 = Buffer.allocUnsafe(SIZE); const buf2 = Buffer.allocUnsafe(SIZE); - function assertEqual(a, b) { - expect(a).toEqual(b); + function bufReset() { + buf1.fill(0); + buf2.fill(0); + } + + // This is mostly accurate. Except write() won't write partial bytes to the + // string while fill() blindly copies bytes into memory. To account for that an + // error will be thrown if not all the data can be written, and the SIZE has + // been massaged to work with the input characters. + function writeToFill(string, offset, end, encoding) { + if (typeof offset === "string") { + encoding = offset; + offset = 0; + end = buf2.length; + } else if (typeof end === "string") { + encoding = end; + end = buf2.length; + } else if (end === undefined) { + end = buf2.length; + } + + // Should never be reached. + if (offset < 0 || end > buf2.length) throw new ERR_OUT_OF_RANGE(); + + if (end <= offset) return buf2; + + offset >>>= 0; + end >>>= 0; + expect(offset <= buf2.length).toBe(true); + + // Convert "end" to "length" (which write understands). + const length = end - offset < 0 ? 0 : end - offset; + + let wasZero = false; + do { + const written = buf2.write(string, offset, length, encoding); + offset += written; + // Safety check in case write falls into infinite loop. + if (written === 0) { + if (wasZero) throw new Error("Could not write all data to Buffer at " + offset); + else wasZero = true; + } + } while (offset < buf2.length); + + return buf2; + } + + function testBufs(string, offset, length, encoding) { + bufReset(); + buf1.fill.apply(buf1, arguments); + // Swap bytes on BE archs for ucs2 encoding. + expect(buf1.fill.apply(buf1, arguments)).toStrictEqual(writeToFill.apply(null, arguments)); } // Default encoding @@ -2101,7 +1989,7 @@ it("Buffer.fill (Node.js tests)", () => { testBufs("\u0222aa", 8, 1, "utf8"); testBufs("a\u0234b\u0235c\u0236", 4, 1, "utf8"); testBufs("a\u0234b\u0235c\u0236", 12, 1, "utf8"); - assertEqual(Buffer.allocUnsafe(1).fill(0).fill("\u0222")[0], 0xc8); + expect(Buffer.allocUnsafe(1).fill(0).fill("\u0222")[0]).toBe(0xc8); // BINARY testBufs("abc", "binary"); @@ -2152,7 +2040,7 @@ it("Buffer.fill (Node.js tests)", () => { testBufs("\u0222aa", 8, 1, "ucs2"); testBufs("a\u0234b\u0235c\u0236", 4, 1, "ucs2"); testBufs("a\u0234b\u0235c\u0236", 12, 1, "ucs2"); - assertEqual(Buffer.allocUnsafe(1).fill("\u0222", "ucs2")[0], 0x22); + expect(Buffer.allocUnsafe(1).fill("\u0222", "ucs2")[0]).toBe(0x22); // HEX testBufs("616263", "hex"); @@ -2214,199 +2102,136 @@ it("Buffer.fill (Node.js tests)", () => { testBufs("yKJhYQ", 8, 1, "base64url"); testBufs("Yci0Ysi1Y8i2", 4, 1, "base64url"); testBufs("Yci0Ysi1Y8i2", 12, 1, "base64url"); +}); - // Buffer - function deepStrictEqualValues(buf, arr) { - for (const [index, value] of buf.entries()) { - expect(value).toStrictEqual(arr[index]); - } - } - - const buf2Fill = Buffer.allocUnsafe(1).fill(2); - deepStrictEqualValues(genBuffer(4, [buf2Fill]), [2, 2, 2, 2]); - deepStrictEqualValues(genBuffer(4, [buf2Fill, 1]), [0, 2, 2, 2]); - deepStrictEqualValues(genBuffer(4, [buf2Fill, 1, 3]), [0, 2, 2, 0]); - deepStrictEqualValues(genBuffer(4, [buf2Fill, 1, 1]), [0, 0, 0, 0]); - const hexBufFill = Buffer.allocUnsafe(2).fill(0).fill("0102", "hex"); - deepStrictEqualValues(genBuffer(4, [hexBufFill]), [1, 2, 1, 2]); - deepStrictEqualValues(genBuffer(4, [hexBufFill, 1]), [0, 1, 2, 1]); - deepStrictEqualValues(genBuffer(4, [hexBufFill, 1, 3]), [0, 1, 2, 0]); - deepStrictEqualValues(genBuffer(4, [hexBufFill, 1, 1]), [0, 0, 0, 0]); - - // Check exceptions - [ - [0, -1], - [0, 0, buf1.length + 1], - ["", -1], - ["", 0, buf1.length + 1], - ["", 1, -1], - ].forEach(args => { - expect(() => buf1.fill(...args)).toThrow(); - }); - - expect(() => buf1.fill("a", 0, buf1.length, "node rocks!")).toThrow(); - - [ - ["a", 0, 0, NaN], - ["a", 0, 0, false], - ].forEach(args => { - expect(() => buf1.fill(...args)).toThrow(); - }); - - expect(() => buf1.fill("a", 0, 0, "foo")).toThrow(); - +it("fill() repeat pattern", () => { function genBuffer(size, args) { const b = Buffer.allocUnsafe(size); return b.fill(0).fill.apply(b, args); } - function bufReset() { - buf1.fill(0); - buf2.fill(0); - } - - // This is mostly accurate. Except write() won't write partial bytes to the - // string while fill() blindly copies bytes into memory. To account for that an - // error will be thrown if not all the data can be written, and the SIZE has - // been massaged to work with the input characters. - function writeToFill(string, offset, end, encoding) { - if (typeof offset === "string") { - encoding = offset; - offset = 0; - end = buf2.length; - } else if (typeof end === "string") { - encoding = end; - end = buf2.length; - } else if (end === undefined) { - end = buf2.length; - } - - // Should never be reached. - if (offset < 0 || end > buf2.length) throw new ERR_OUT_OF_RANGE(); - - if (end <= offset) return buf2; - - offset >>>= 0; - end >>>= 0; - expect(offset <= buf2.length).toBe(true); - - // Convert "end" to "length" (which write understands). - const length = end - offset < 0 ? 0 : end - offset; - - let wasZero = false; - do { - const written = buf2.write(string, offset, length, encoding); - offset += written; - // Safety check in case write falls into infinite loop. - if (written === 0) { - if (wasZero) throw new Error("Could not write all data to Buffer at " + offset); - else wasZero = true; - } - } while (offset < buf2.length); - - return buf2; - } + const buf2Fill = Buffer.allocUnsafe(1).fill(2); + expect(genBuffer(4, [buf2Fill])).toStrictEqual(Buffer.from([2, 2, 2, 2])); + expect(genBuffer(4, [buf2Fill, 1])).toStrictEqual(Buffer.from([0, 2, 2, 2])); + expect(genBuffer(4, [buf2Fill, 1, 3])).toStrictEqual(Buffer.from([0, 2, 2, 0])); + expect(genBuffer(4, [buf2Fill, 1, 1])).toStrictEqual(Buffer.from([0, 0, 0, 0])); + const hexBufFill = Buffer.allocUnsafe(2).fill(0).fill("0102", "hex"); + expect(genBuffer(4, [hexBufFill])).toStrictEqual(Buffer.from([1, 2, 1, 2])); + expect(genBuffer(4, [hexBufFill, 1])).toStrictEqual(Buffer.from([0, 1, 2, 1])); + expect(genBuffer(4, [hexBufFill, 1, 3])).toStrictEqual(Buffer.from([0, 1, 2, 0])); + expect(genBuffer(4, [hexBufFill, 1, 1])).toStrictEqual(Buffer.from([0, 0, 0, 0])); +}); - function testBufs(string, offset, length, encoding) { - bufReset(); - buf1.fill.apply(buf1, arguments); - // Swap bytes on BE archs for ucs2 encoding. - expect(buf1.fill.apply(buf1, arguments)).toStrictEqual(writeToFill.apply(null, arguments)); - } +it("fill() should throw on invalid arguments", () => { + // Check exceptions + const buf = Buffer.allocUnsafe(16); + expect(() => buf.fill(0, -1)).toThrow(RangeError); + expect(() => buf.fill(0, 0, buf.length + 1)).toThrow(RangeError); + expect(() => buf.fill("", -1)).toThrow(RangeError); + expect(() => buf.fill("", 0, buf.length + 1)).toThrow(RangeError); + expect(() => buf.fill("", 1, -1)).toThrow(RangeError); + expect(() => buf.fill("a", 0, buf.length, "node rocks!")).toThrow(TypeError); + expect(() => buf.fill("a", 0, 0, NaN)).toThrow(TypeError); + expect(() => buf.fill("a", 0, 0, false)).toThrow(TypeError); + expect(() => buf.fill("a", 0, 0, "foo")).toThrow(TypeError); // Make sure these throw. expect(() => Buffer.allocUnsafe(8).fill("a", -1)).toThrow(); expect(() => Buffer.allocUnsafe(8).fill("a", 0, 9)).toThrow(); +}); +it("fill() should not hang indefinitely", () => { // Make sure this doesn't hang indefinitely. Buffer.allocUnsafe(8).fill(""); Buffer.alloc(8, ""); +}); - { - const buf = Buffer.alloc(64, 10); - for (let i = 0; i < buf.length; i++) assertEqual(buf[i], 10); +it("fill() repeat byte", () => { + const buf = Buffer.alloc(64, 10); + for (let i = 0; i < buf.length; i++) expect(buf[i]).toBe(10); - buf.fill(11, 0, buf.length >> 1); - for (let i = 0; i < buf.length >> 1; i++) assertEqual(buf[i], 11); - for (let i = (buf.length >> 1) + 1; i < buf.length; i++) assertEqual(buf[i], 10); + buf.fill(11, 0, buf.length >> 1); + for (let i = 0; i < buf.length >> 1; i++) expect(buf[i]).toBe(11); + for (let i = (buf.length >> 1) + 1; i < buf.length; i++) expect(buf[i]).toBe(10); - buf.fill("h"); - for (let i = 0; i < buf.length; i++) assertEqual(buf[i], "h".charCodeAt(0)); + buf.fill("h"); + for (let i = 0; i < buf.length; i++) expect(buf[i]).toBe("h".charCodeAt(0)); - buf.fill(0); - for (let i = 0; i < buf.length; i++) assertEqual(buf[i], 0); + buf.fill(0); + for (let i = 0; i < buf.length; i++) expect(buf[i]).toBe(0); - buf.fill(null); - for (let i = 0; i < buf.length; i++) assertEqual(buf[i], 0); + buf.fill(null); + for (let i = 0; i < buf.length; i++) expect(buf[i]).toBe(0); - buf.fill(1, 16, 32); - for (let i = 0; i < 16; i++) assertEqual(buf[i], 0); - for (let i = 16; i < 32; i++) assertEqual(buf[i], 1); - for (let i = 32; i < buf.length; i++) assertEqual(buf[i], 0); - } + buf.fill(1, 16, 32); + for (let i = 0; i < 16; i++) expect(buf[i]).toBe(0); + for (let i = 16; i < 32; i++) expect(buf[i]).toBe(1); + for (let i = 32; i < buf.length; i++) expect(buf[i]).toBe(0); +}); - { - const buf = Buffer.alloc(10, "abc"); - assertEqual(buf.toString(), "abcabcabca"); - buf.fill("է"); - assertEqual(buf.toString(), "էէէէէ"); - } +it("alloc() repeat pattern", () => { + const buf = Buffer.alloc(10, "abc"); + expect(buf.toString()).toBe("abcabcabca"); + buf.fill("է"); + expect(buf.toString()).toBe("էէէէէ"); +}); +it("fill() should properly check `start` & `end`", () => { // // Testing process.binding. Make sure "start" is properly checked for range // // errors. - // assert.throws( - // () => { - // internalBinding("buffer").fill(Buffer.alloc(1), 1, -1, 0, 1); - // }, - // { code: "ERR_OUT_OF_RANGE" }, - // ); + // expect(() => internalBinding("buffer").fill(Buffer.alloc(1), 1, -1, 0, 1)).toThrow(RangeError); // Make sure "end" is properly checked, even if it's magically mangled using // Symbol.toPrimitive. - { - expect(() => { - const end = { - [Symbol.toPrimitive]() { - return 1; - }, - }; - Buffer.alloc(1).fill(Buffer.alloc(1), 0, end); - }).toThrow(); - } + expect(() => { + const end = { + [Symbol.toPrimitive]() { + return 1; + }, + }; + Buffer.alloc(1).fill(Buffer.alloc(1), 0, end); + }).toThrow(TypeError); // Testing process.binding. Make sure "end" is properly checked for range // errors. - // assert.throws( - // () => { - // internalBinding("buffer").fill(Buffer.alloc(1), 1, 1, -2, 1); - // }, - // { code: "ERR_OUT_OF_RANGE" }, - // ); - - // Test that bypassing 'length' won't cause an abort. - expect(() => { - const buf = Buffer.from("w00t"); - Object.defineProperty(buf, "length", { - value: 1337, - enumerable: true, - }); - buf.fill(""); - }).toThrow(); + // expect(() => internalBinding("buffer").fill(Buffer.alloc(1), 1, 1, -2, 1)).toThrow(RangeError); +}); + +it("bypassing `length` should not cause an abort", () => { + const buf = Buffer.from("w00t"); + expect(buf).toStrictEqual(Buffer.from([119, 48, 48, 116])); + Object.defineProperty(buf, "length", { + value: 1337, + enumerable: true, + }); + // Node.js throws here, but we can handle it just fine + buf.fill(""); + expect(buf).toStrictEqual(Buffer.from([0, 0, 0, 0])); +}); - assertEqual(Buffer.allocUnsafeSlow(16).fill("ab", "utf16le"), Buffer.from("61006200610062006100620061006200", "hex")); +it("allocUnsafeSlow().fill()", () => { + expect(Buffer.allocUnsafeSlow(16).fill("ab", "utf16le")).toStrictEqual( + Buffer.from("61006200610062006100620061006200", "hex"), + ); - assertEqual(Buffer.allocUnsafeSlow(15).fill("ab", "utf16le"), Buffer.from("610062006100620061006200610062", "hex")); + expect(Buffer.allocUnsafeSlow(15).fill("ab", "utf16le")).toStrictEqual( + Buffer.from("610062006100620061006200610062", "hex"), + ); - assertEqual(Buffer.allocUnsafeSlow(16).fill("ab", "utf16le"), Buffer.from("61006200610062006100620061006200", "hex")); - assertEqual(Buffer.allocUnsafeSlow(16).fill("a", "utf16le"), Buffer.from("61006100610061006100610061006100", "hex")); + expect(Buffer.allocUnsafeSlow(16).fill("ab", "utf16le")).toStrictEqual( + Buffer.from("61006200610062006100620061006200", "hex"), + ); + expect(Buffer.allocUnsafeSlow(16).fill("a", "utf16le")).toStrictEqual( + Buffer.from("61006100610061006100610061006100", "hex"), + ); - assertEqual(Buffer.allocUnsafeSlow(16).fill("a", "utf16le").toString("utf16le"), "a".repeat(8)); - assertEqual(Buffer.allocUnsafeSlow(16).fill("a", "latin1").toString("latin1"), "a".repeat(16)); - assertEqual(Buffer.allocUnsafeSlow(16).fill("a", "utf8").toString("utf8"), "a".repeat(16)); + expect(Buffer.allocUnsafeSlow(16).fill("a", "utf16le").toString("utf16le")).toBe("a".repeat(8)); + expect(Buffer.allocUnsafeSlow(16).fill("a", "latin1").toString("latin1")).toBe("a".repeat(16)); + expect(Buffer.allocUnsafeSlow(16).fill("a", "utf8").toString("utf8")).toBe("a".repeat(16)); - assertEqual(Buffer.allocUnsafeSlow(16).fill("Љ", "utf16le").toString("utf16le"), "Љ".repeat(8)); - assertEqual(Buffer.allocUnsafeSlow(16).fill("Љ", "latin1").toString("latin1"), "\t".repeat(16)); - assertEqual(Buffer.allocUnsafeSlow(16).fill("Љ", "utf8").toString("utf8"), "Љ".repeat(8)); + expect(Buffer.allocUnsafeSlow(16).fill("Љ", "utf16le").toString("utf16le")).toBe("Љ".repeat(8)); + expect(Buffer.allocUnsafeSlow(16).fill("Љ", "latin1").toString("latin1")).toBe("\t".repeat(16)); + expect(Buffer.allocUnsafeSlow(16).fill("Љ", "utf8").toString("utf8")).toBe("Љ".repeat(8)); expect(() => { const buf = Buffer.from("a".repeat(1000)); @@ -2415,115 +2240,116 @@ it("Buffer.fill (Node.js tests)", () => { }).toThrow(); }); -test("Buffer.byteLength", () => { - const SlowBuffer = require("buffer").SlowBuffer; - - [[32, "latin1"], [NaN, "utf8"], [{}, "latin1"], []].forEach(args => { - assert.throws(() => Buffer.byteLength(...args)); - }); +it("ArrayBuffer.isView()", () => { + expect(ArrayBuffer.isView(new Buffer(10))).toBe(true); + expect(ArrayBuffer.isView(new SlowBuffer(10))).toBe(true); + expect(ArrayBuffer.isView(Buffer.alloc(10))).toBe(true); + expect(ArrayBuffer.isView(Buffer.allocUnsafe(10))).toBe(true); + expect(ArrayBuffer.isView(Buffer.allocUnsafeSlow(10))).toBe(true); + expect(ArrayBuffer.isView(Buffer.from(""))).toBe(true); +}); - assert.strictEqual(Buffer.byteLength("", undefined, true), 0); +it("Buffer.byteLength()", () => { + expect(() => Buffer.byteLength(32, "latin1")).toThrow(TypeError); + expect(() => Buffer.byteLength(NaN, "utf8")).toThrow(TypeError); + expect(() => Buffer.byteLength({}, "latin1")).toThrow(TypeError); + expect(() => Buffer.byteLength()).toThrow(TypeError); - assert(ArrayBuffer.isView(new Buffer(10))); - assert(ArrayBuffer.isView(new SlowBuffer(10))); - assert(ArrayBuffer.isView(Buffer.alloc(10))); - assert(ArrayBuffer.isView(Buffer.allocUnsafe(10))); - assert(ArrayBuffer.isView(Buffer.allocUnsafeSlow(10))); - assert(ArrayBuffer.isView(Buffer.from(""))); + expect(Buffer.byteLength("", undefined, true)).toBe(0); // buffer const incomplete = Buffer.from([0xe4, 0xb8, 0xad, 0xe6, 0x96]); - assert.strictEqual(Buffer.byteLength(incomplete), 5); + expect(Buffer.byteLength(incomplete)).toBe(5); const ascii = Buffer.from("abc"); - assert.strictEqual(Buffer.byteLength(ascii), 3); + expect(Buffer.byteLength(ascii)).toBe(3); // ArrayBuffer const buffer = new ArrayBuffer(8); - assert.strictEqual(Buffer.byteLength(buffer), 8); + expect(Buffer.byteLength(buffer)).toBe(8); // TypedArray const int8 = new Int8Array(8); - assert.strictEqual(Buffer.byteLength(int8), 8); + expect(Buffer.byteLength(int8)).toBe(8); const uint8 = new Uint8Array(8); - assert.strictEqual(Buffer.byteLength(uint8), 8); + expect(Buffer.byteLength(uint8)).toBe(8); const uintc8 = new Uint8ClampedArray(2); - assert.strictEqual(Buffer.byteLength(uintc8), 2); + expect(Buffer.byteLength(uintc8)).toBe(2); const int16 = new Int16Array(8); - assert.strictEqual(Buffer.byteLength(int16), 16); + expect(Buffer.byteLength(int16)).toBe(16); const uint16 = new Uint16Array(8); - assert.strictEqual(Buffer.byteLength(uint16), 16); + expect(Buffer.byteLength(uint16)).toBe(16); const int32 = new Int32Array(8); - assert.strictEqual(Buffer.byteLength(int32), 32); + expect(Buffer.byteLength(int32)).toBe(32); const uint32 = new Uint32Array(8); - assert.strictEqual(Buffer.byteLength(uint32), 32); + expect(Buffer.byteLength(uint32)).toBe(32); const float32 = new Float32Array(8); - assert.strictEqual(Buffer.byteLength(float32), 32); + expect(Buffer.byteLength(float32)).toBe(32); const float64 = new Float64Array(8); - assert.strictEqual(Buffer.byteLength(float64), 64); + expect(Buffer.byteLength(float64)).toBe(64); // DataView const dv = new DataView(new ArrayBuffer(2)); - assert.strictEqual(Buffer.byteLength(dv), 2); + expect(Buffer.byteLength(dv)).toBe(2); // Special case: zero length string - assert.strictEqual(Buffer.byteLength("", "ascii"), 0); - assert.strictEqual(Buffer.byteLength("", "HeX"), 0); + expect(Buffer.byteLength("", "ascii")).toBe(0); + expect(Buffer.byteLength("", "HeX")).toBe(0); // utf8 - assert.strictEqual(Buffer.byteLength("∑éllö wørl∂!", "utf-8"), 19); - assert.strictEqual(Buffer.byteLength("κλμνξο", "utf8"), 12); - assert.strictEqual(Buffer.byteLength("挵挶挷挸挹", "utf-8"), 15); - assert.strictEqual(Buffer.byteLength("𠝹𠱓𠱸", "UTF8"), 12); + expect(Buffer.byteLength("∑éllö wørl∂!", "utf-8")).toBe(19); + expect(Buffer.byteLength("κλμνξο", "utf8")).toBe(12); + expect(Buffer.byteLength("挵挶挷挸挹", "utf-8")).toBe(15); + expect(Buffer.byteLength("𠝹𠱓𠱸", "UTF8")).toBe(12); // Without an encoding, utf8 should be assumed - assert.strictEqual(Buffer.byteLength("hey there"), 9); - assert.strictEqual(Buffer.byteLength("𠱸挶νξ#xx :)"), 17); - assert.strictEqual(Buffer.byteLength("hello world", ""), 11); + expect(Buffer.byteLength("hey there")).toBe(9); + expect(Buffer.byteLength("𠱸挶νξ#xx :)")).toBe(17); + expect(Buffer.byteLength("hello world", "")).toBe(11); // It should also be assumed with unrecognized encoding - assert.strictEqual(Buffer.byteLength("hello world", "abc"), 11); - assert.strictEqual(Buffer.byteLength("ßœ∑≈", "unkn0wn enc0ding"), 10); + expect(Buffer.byteLength("hello world", "abc")).toBe(11); + expect(Buffer.byteLength("ßœ∑≈", "unkn0wn enc0ding")).toBe(10); // base64 - assert.strictEqual(Buffer.byteLength("aGVsbG8gd29ybGQ=", "base64"), 11); - assert.strictEqual(Buffer.byteLength("aGVsbG8gd29ybGQ=", "BASE64"), 11); - assert.strictEqual(Buffer.byteLength("bm9kZS5qcyByb2NrcyE=", "base64"), 14); - assert.strictEqual(Buffer.byteLength("aGkk", "base64"), 3); - assert.strictEqual(Buffer.byteLength("bHNrZGZsa3NqZmtsc2xrZmFqc2RsZmtqcw==", "base64"), 25); + expect(Buffer.byteLength("aGVsbG8gd29ybGQ=", "base64")).toBe(11); + expect(Buffer.byteLength("aGVsbG8gd29ybGQ=", "BASE64")).toBe(11); + expect(Buffer.byteLength("bm9kZS5qcyByb2NrcyE=", "base64")).toBe(14); + expect(Buffer.byteLength("aGkk", "base64")).toBe(3); + expect(Buffer.byteLength("bHNrZGZsa3NqZmtsc2xrZmFqc2RsZmtqcw==", "base64")).toBe(25); // base64url - assert.strictEqual(Buffer.byteLength("aGVsbG8gd29ybGQ", "base64url"), 11); - assert.strictEqual(Buffer.byteLength("aGVsbG8gd29ybGQ", "BASE64URL"), 11); - assert.strictEqual(Buffer.byteLength("bm9kZS5qcyByb2NrcyE", "base64url"), 14); - assert.strictEqual(Buffer.byteLength("aGkk", "base64url"), 3); - assert.strictEqual(Buffer.byteLength("bHNrZGZsa3NqZmtsc2xrZmFqc2RsZmtqcw", "base64url"), 25); + expect(Buffer.byteLength("aGVsbG8gd29ybGQ", "base64url")).toBe(11); + expect(Buffer.byteLength("aGVsbG8gd29ybGQ", "BASE64URL")).toBe(11); + expect(Buffer.byteLength("bm9kZS5qcyByb2NrcyE", "base64url")).toBe(14); + expect(Buffer.byteLength("aGkk", "base64url")).toBe(3); + expect(Buffer.byteLength("bHNrZGZsa3NqZmtsc2xrZmFqc2RsZmtqcw", "base64url")).toBe(25); // special padding - assert.strictEqual(Buffer.byteLength("aaa=", "base64"), 2); - assert.strictEqual(Buffer.byteLength("aaaa==", "base64"), 3); - assert.strictEqual(Buffer.byteLength("aaa=", "base64url"), 2); - assert.strictEqual(Buffer.byteLength("aaaa==", "base64url"), 3); - assert.strictEqual(Buffer.byteLength("Il était tué", "utf8"), 14); - assert.strictEqual(Buffer.byteLength("Il était tué"), 14); + expect(Buffer.byteLength("aaa=", "base64")).toBe(2); + expect(Buffer.byteLength("aaaa==", "base64")).toBe(3); + expect(Buffer.byteLength("aaa=", "base64url")).toBe(2); + expect(Buffer.byteLength("aaaa==", "base64url")).toBe(3); + expect(Buffer.byteLength("Il était tué", "utf8")).toBe(14); + expect(Buffer.byteLength("Il était tué")).toBe(14); ["ascii", "latin1", "binary"] .reduce((es, e) => es.concat(e, e.toUpperCase()), []) .forEach(encoding => { - assert.strictEqual(Buffer.byteLength("Il était tué", encoding), 12); + expect(Buffer.byteLength("Il était tué", encoding)).toBe(12); }); ["ucs2", "ucs-2", "utf16le", "utf-16le"] .reduce((es, e) => es.concat(e, e.toUpperCase()), []) .forEach(encoding => { - assert.strictEqual(Buffer.byteLength("Il était tué", encoding), 24); + expect(Buffer.byteLength("Il était tué", encoding)).toBe(24); }); // Test that ArrayBuffer from a different context is detected correctly // const arrayBuf = vm.runInNewContext("new ArrayBuffer()"); - // assert.strictEqual(Buffer.byteLength(arrayBuf), 0); + // expect(Buffer.byteLength(arrayBuf)).toBe(0); // Verify that invalid encodings are treated as utf8 for (let i = 1; i < 10; i++) { const encoding = String(i).repeat(i); - assert.ok(!Buffer.isEncoding(encoding)); - assert.strictEqual(Buffer.byteLength("foo", encoding), Buffer.byteLength("foo", "utf8")); + expect(Buffer.isEncoding(encoding)).toBe(false); + expect(Buffer.byteLength("foo", encoding)).toBe(Buffer.byteLength("foo", "utf8")); } }); From 80b7f806b775c4154c9c2c25dcbdbe86c5dbfd37 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Wed, 8 Mar 2023 08:30:53 +0200 Subject: [PATCH 059/255] run `bun test` after macOS builds (#2343) --- .github/workflows/bun-mac-aarch64.yml | 72 ++++++++++++++++++++++ .github/workflows/bun-mac-x64-baseline.yml | 1 + .github/workflows/bun-mac-x64.yml | 72 ++++++++++++++++++++++ 3 files changed, 145 insertions(+) diff --git a/.github/workflows/bun-mac-aarch64.yml b/.github/workflows/bun-mac-aarch64.yml index a6c19825ef84..642f20d0ccc1 100644 --- a/.github/workflows/bun-mac-aarch64.yml +++ b/.github/workflows/bun-mac-aarch64.yml @@ -384,3 +384,75 @@ jobs: name: "Canary (${{github.sha}})" tag: "canary" artifacts: "${{runner.temp}}/release/${{matrix.tag}}.zip,${{runner.temp}}/release/${{matrix.tag}}-profile.zip" + macOS-test: + name: Tests ${{matrix.tag}} + runs-on: ${{ matrix.runner }} + needs: [macOS] + if: github.event_name == 'pull_request' + timeout-minutes: 10 + outputs: + failing_tests: ${{ steps.test.outputs.failing_tests }} + failing_tests_count: ${{ steps.test.outputs.failing_tests_count }} + strategy: + fail-fast: false + matrix: + include: + - tag: bun-darwin-aarch64 + runner: macos-arm64 + steps: + - id: checkout + name: Checkout + uses: actions/checkout@v3 + with: + submodules: false + - id: download + name: Download + uses: actions/download-artifact@v3 + with: + name: ${{matrix.tag}} + path: ${{runner.temp}}/release + - id: install + name: Install + run: | + cd ${{runner.temp}}/release + unzip ${{matrix.tag}}.zip + cd ${{matrix.tag}} + chmod +x bun + sudo mv bun /usr/local/bin/bun + bun --version + - id: test + name: Test (node runner) + # if: ${{github.event.inputs.use_bun == 'false'}} + run: | + bun install + bun install --cwd test + bun install --cwd packages/bun-internal-test + node packages/bun-internal-test/src/runner.node.mjs || true + - name: Comment on PR + if: steps.test.outputs.failing_tests != '' && github.event_name == 'pull_request' + uses: thollander/actions-comment-pull-request@v2 + with: + comment_tag: test-failures-${{matrix.tag}} + message: | + ❌ @${{ github.actor }} ${{ steps.test.outputs.failing_tests_count }} files with test failures on ${{ matrix.tag }}: + + ${{ steps.test.outputs.failing_tests }} + + **[View test output](https://github.com/oven-sh/bun/actions/runs/${{github.run_id}})** + + [#${{github.sha}}](https://github.com/oven-sh/bun/commits/${{github.sha}}) + - name: Uncomment on PR + if: steps.test.outputs.failing_tests == '' && github.event_name == 'pull_request' + uses: thollander/actions-comment-pull-request@v2 + with: + comment_tag: test-failures-${{matrix.tag}} + mode: upsert + create_if_not_exists: false + message: | + ✅ test failures on ${{ matrix.tag }} have been resolved. + + [#${{github.sha}}](https://github.com/oven-sh/bun/commits/${{github.sha}}) + - id: fail + name: Fail the build + if: steps.test.outputs.failing_tests != '' + run: exit 1 diff --git a/.github/workflows/bun-mac-x64-baseline.yml b/.github/workflows/bun-mac-x64-baseline.yml index b2430e93b77c..919333fa5c39 100644 --- a/.github/workflows/bun-mac-x64-baseline.yml +++ b/.github/workflows/bun-mac-x64-baseline.yml @@ -402,6 +402,7 @@ jobs: matrix: include: - tag: bun-darwin-x64-baseline + runner: macos-11 steps: - id: checkout name: Checkout diff --git a/.github/workflows/bun-mac-x64.yml b/.github/workflows/bun-mac-x64.yml index e4998a43ea1c..434c43e4e835 100644 --- a/.github/workflows/bun-mac-x64.yml +++ b/.github/workflows/bun-mac-x64.yml @@ -390,3 +390,75 @@ jobs: name: "Canary (${{github.sha}})" tag: "canary" artifacts: "${{runner.temp}}/release/${{matrix.tag}}.zip,${{runner.temp}}/release/${{matrix.tag}}-profile.zip" + macOS-test: + name: Tests ${{matrix.tag}} + runs-on: ${{ matrix.runner }} + needs: [macOS] + if: github.event_name == 'pull_request' + timeout-minutes: 10 + outputs: + failing_tests: ${{ steps.test.outputs.failing_tests }} + failing_tests_count: ${{ steps.test.outputs.failing_tests_count }} + strategy: + fail-fast: false + matrix: + include: + - tag: bun-darwin-x64 + runner: macos-11 + steps: + - id: checkout + name: Checkout + uses: actions/checkout@v3 + with: + submodules: false + - id: download + name: Download + uses: actions/download-artifact@v3 + with: + name: ${{matrix.tag}} + path: ${{runner.temp}}/release + - id: install + name: Install + run: | + cd ${{runner.temp}}/release + unzip ${{matrix.tag}}.zip + cd ${{matrix.tag}} + chmod +x bun + sudo mv bun /usr/local/bin/bun + bun --version + - id: test + name: Test (node runner) + # if: ${{github.event.inputs.use_bun == 'false'}} + run: | + bun install + bun install --cwd test + bun install --cwd packages/bun-internal-test + node packages/bun-internal-test/src/runner.node.mjs || true + - name: Comment on PR + if: steps.test.outputs.failing_tests != '' && github.event_name == 'pull_request' + uses: thollander/actions-comment-pull-request@v2 + with: + comment_tag: test-failures-${{matrix.tag}} + message: | + ❌ @${{ github.actor }} ${{ steps.test.outputs.failing_tests_count }} files with test failures on ${{ matrix.tag }}: + + ${{ steps.test.outputs.failing_tests }} + + **[View test output](https://github.com/oven-sh/bun/actions/runs/${{github.run_id}})** + + [#${{github.sha}}](https://github.com/oven-sh/bun/commits/${{github.sha}}) + - name: Uncomment on PR + if: steps.test.outputs.failing_tests == '' && github.event_name == 'pull_request' + uses: thollander/actions-comment-pull-request@v2 + with: + comment_tag: test-failures-${{matrix.tag}} + mode: upsert + create_if_not_exists: false + message: | + ✅ test failures on ${{ matrix.tag }} have been resolved. + + [#${{github.sha}}](https://github.com/oven-sh/bun/commits/${{github.sha}}) + - id: fail + name: Fail the build + if: steps.test.outputs.failing_tests != '' + run: exit 1 From c04f5821e816815954fcba7738ab29a0be32f551 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Tue, 7 Mar 2023 22:59:29 -0800 Subject: [PATCH 060/255] "binary" is an alias of "latin1" Fixes https://github.com/oven-sh/bun/issues/2110 --- src/bun.js/node/types.zig | 7 +++++-- test/js/node/crypto/node-crypto.test.js | 6 ++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/bun.js/node/types.zig b/src/bun.js/node/types.zig index b70ad9ea3b4f..6bee1243fa6c 100644 --- a/src/bun.js/node/types.zig +++ b/src/bun.js/node/types.zig @@ -374,7 +374,7 @@ pub const SliceOrBuffer = union(Tag) { } const encoding: Encoding = brk: { - if (encoding_value.isEmpty()) + if (encoding_value.isEmptyOrUndefinedOrNull()) break :brk .utf8; var encoding_str = encoding_value.toSlice(global, allocator); if (encoding_str.len == 0) @@ -442,7 +442,10 @@ pub const Encoding = enum(u8) { Eight.case("utf-8"), Eight.case("utf8") => Encoding.utf8, Eight.case("ucs-2"), Eight.case("ucs2") => Encoding.ucs2, Eight.case("utf16-le"), Eight.case("utf16le") => Encoding.utf16le, - Eight.case("latin1") => Encoding.latin1, + + // "binary" is an alias for "latin1" + Eight.case("binary"), Eight.case("latin1") => Encoding.latin1, + Eight.case("ascii") => Encoding.ascii, Eight.case("base64") => Encoding.base64, Eight.case("hex") => Encoding.hex, diff --git a/test/js/node/crypto/node-crypto.test.js b/test/js/node/crypto/node-crypto.test.js index f148f4fe9f0d..324a2790fbf3 100644 --- a/test/js/node/crypto/node-crypto.test.js +++ b/test/js/node/crypto/node-crypto.test.js @@ -27,3 +27,9 @@ it("web crypto", async () => { crypto.getRandomValues(bytes); await crypto.subtle.digest("SHA-256", bytes); }); + +// https://github.com/oven-sh/bun/issues/2110 +it("hash regression #2110", () => { + var s = "6fbf7e2948e0c2f29eaacac1733546a4af5ca482"; + expect(crypto.createHash("sha1").update(s, "binary").digest("hex")).toBe("e7c8b3c6f114c523d07ee355c534ee9bef3c044b"); +}); From f97c8acf6cbcd598198266740d71de12d8d32885 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Wed, 8 Mar 2023 00:18:14 -0800 Subject: [PATCH 061/255] More spec compliant `Blob.prototype.type` (#2340) * Make `Blob.prototype. type` more spec compliant * Add a few more checks for isNumber() * Fix `make headers` * Safer JSValue.isString() * More tests for blob.slice * Make `Blob.prototype.type` more spec compliant * Add isASCII check * Fix types * Fix failing type test * Update blob.zig * Update blob.zig * Fix .eql check on empty values --------- Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> --- packages/bun-types/bun.d.ts | 20 +++- packages/bun-types/globals.d.ts | 24 +++- packages/bun-types/tests/array.test-d.ts | 4 +- src/bun.js/api/server.zig | 4 +- src/bun.js/base.zig | 2 + src/bun.js/bindings/bindings.cpp | 4 - src/bun.js/bindings/bindings.zig | 15 ++- src/bun.js/bindings/headers-cpp.h | 2 +- src/bun.js/bindings/headers.h | 3 +- src/bun.js/bindings/headers.zig | 1 - src/bun.js/javascript.zig | 4 + src/bun.js/rare_data.zig | 12 ++ src/bun.js/webcore/blob.zig | 138 ++++++++++++++++------- src/bun.js/webcore/streams.zig | 18 ++- src/http/mime_type.zig | 17 +++ test/js/web/fetch/blob.test.ts | 56 +++++++++ 16 files changed, 261 insertions(+), 63 deletions(-) create mode 100644 test/js/web/fetch/blob.test.ts diff --git a/packages/bun-types/bun.d.ts b/packages/bun-types/bun.d.ts index 19b46d60176b..37f4b72a0853 100644 --- a/packages/bun-types/bun.d.ts +++ b/packages/bun-types/bun.d.ts @@ -620,8 +620,26 @@ declare module "bun" { * * @param begin - start offset in bytes * @param end - absolute offset in bytes (relative to 0) + * @param contentType - MIME type for the new FileBlob */ - slice(begin?: number, end?: number): FileBlob; + slice(begin?: number, end?: number, contentType?: string): FileBlob; + + /** + * Offset any operation on the file starting at `begin` + * + * Similar to [`TypedArray.subarray`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/subarray). Does not copy the file, open the file, or modify the file. + * + * If `begin` > 0, {@link Bun.write()} will be slower on macOS + * + * @param begin - start offset in bytes + * @param contentType - MIME type for the new FileBlob + */ + slice(begin?: number, contentType?: string): FileBlob; + + /** + * @param contentType - MIME type for the new FileBlob + */ + slice(contentType?: string): FileBlob; /** * Incremental writer for files and pipes. diff --git a/packages/bun-types/globals.d.ts b/packages/bun-types/globals.d.ts index 2dd86f91129e..aab4921bdff1 100644 --- a/packages/bun-types/globals.d.ts +++ b/packages/bun-types/globals.d.ts @@ -565,7 +565,29 @@ declare class Blob implements BlobInterface { * @param end The index that sets the end of the view. * */ - slice(begin?: number, end?: number): Blob; + slice(begin?: number, end?: number, contentType?: string): Blob; + + /** + * Create a new view **without 🚫 copying** the underlying data. + * + * Similar to [`BufferSource.subarray`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BufferSource/subarray) + * + * @param begin The index that sets the beginning of the view. + * @param end The index that sets the end of the view. + * + */ + slice(begin?: number, contentType?: string): Blob; + + /** + * Create a new view **without 🚫 copying** the underlying data. + * + * Similar to [`BufferSource.subarray`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BufferSource/subarray) + * + * @param begin The index that sets the beginning of the view. + * @param end The index that sets the end of the view. + * + */ + slice(contentType?: string): Blob; /** * Read the data from the blob as a string. It will be decoded from UTF-8. diff --git a/packages/bun-types/tests/array.test-d.ts b/packages/bun-types/tests/array.test-d.ts index 65295b3b154a..f7ba9a6513d1 100644 --- a/packages/bun-types/tests/array.test-d.ts +++ b/packages/bun-types/tests/array.test-d.ts @@ -13,6 +13,4 @@ async function* listReleases() { } } -const releases = await Array.fromAsync(listReleases()); - -export {}; +export const releases = await Array.fromAsync(listReleases()); diff --git a/src/bun.js/api/server.zig b/src/bun.js/api/server.zig index 5da1b360f54b..3514ab599fff 100644 --- a/src/bun.js/api/server.zig +++ b/src/bun.js/api/server.zig @@ -376,7 +376,9 @@ pub const ServerConfig = struct { } if (arg.getTruthy(global, "maxRequestBodySize")) |max_request_body_size| { - args.max_request_body_size = @intCast(u64, @max(0, max_request_body_size.toInt64())); + if (max_request_body_size.isNumber()) { + args.max_request_body_size = @intCast(u64, @max(0, max_request_body_size.toInt64())); + } } if (arg.getTruthy(global, "error")) |onError| { diff --git a/src/bun.js/base.zig b/src/bun.js/base.zig index f9a52b7145a9..a411beac2b85 100644 --- a/src/bun.js/base.zig +++ b/src/bun.js/base.zig @@ -1773,6 +1773,8 @@ pub const ArrayBuffer = extern struct { } pub fn createEmpty(globalThis: *JSC.JSGlobalObject, comptime kind: JSC.JSValue.JSType) JSValue { + JSC.markBinding(@src()); + return switch (comptime kind) { .Uint8Array => Bun__createUint8ArrayForCopy(globalThis, null, 0, false), .ArrayBuffer => Bun__createArrayBufferForCopy(globalThis, null, 0), diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index 62c1848938d7..5c9cba7f0f5d 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -2455,10 +2455,6 @@ bool JSC__JSValue__isPrimitive(JSC__JSValue JSValue0) { return JSC::JSValue::decode(JSValue0).isPrimitive(); } -bool JSC__JSValue__isString(JSC__JSValue JSValue0) -{ - return JSC::JSValue::decode(JSValue0).isString(); -} bool JSC__JSValue__isSymbol(JSC__JSValue JSValue0) { return JSC::JSValue::decode(JSValue0).isSymbol(); diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig index 49ff393a205e..40fc43091a2e 100644 --- a/src/bun.js/bindings/bindings.zig +++ b/src/bun.js/bindings/bindings.zig @@ -159,6 +159,9 @@ pub const ZigString = extern struct { } pub fn eql(this: ZigString, other: ZigString) bool { + if (this.len == 0 or other.len == 0) + return this.len == other.len; + const left_utf16 = this.is16Bit(); const right_utf16 = other.is16Bit(); @@ -2276,6 +2279,10 @@ pub const JSGlobalObject = extern struct { return this.bunVM().allocator; } + pub fn throwOutOfMemory(this: *JSGlobalObject) void { + this.throwValue(this.createErrorInstance("Out of memory", .{})); + } + pub fn throwInvalidArguments( this: *JSGlobalObject, comptime fmt: string, @@ -3454,8 +3461,11 @@ pub const JSValue = enum(JSValueReprInt) { return res; } - pub fn isString(this: JSValue) bool { - return cppFn("isString", .{this}); + pub inline fn isString(this: JSValue) bool { + if (!this.isCell()) + return false; + + return jsType(this).isStringLike(); } pub fn isBigInt(this: JSValue) bool { return cppFn("isBigInt", .{this}); @@ -3968,7 +3978,6 @@ pub const JSValue = enum(JSValueReprInt) { "isObject", "isPrimitive", "isSameValue", - "isString", "isSymbol", "isTerminationException", "isUInt32AsAnyInt", diff --git a/src/bun.js/bindings/headers-cpp.h b/src/bun.js/bindings/headers-cpp.h index 4b195d7565d2..ae4ecaf5d6da 100644 --- a/src/bun.js/bindings/headers-cpp.h +++ b/src/bun.js/bindings/headers-cpp.h @@ -1,4 +1,4 @@ -//-- AUTOGENERATED FILE -- 1677776166 +//-- AUTOGENERATED FILE -- 1678254453 // clang-format off #pragma once diff --git a/src/bun.js/bindings/headers.h b/src/bun.js/bindings/headers.h index 422a71a70144..20b7fbedbb25 100644 --- a/src/bun.js/bindings/headers.h +++ b/src/bun.js/bindings/headers.h @@ -1,5 +1,5 @@ // clang-format off -//-- AUTOGENERATED FILE -- 1677776166 +//-- AUTOGENERATED FILE -- 1678254453 #pragma once #include @@ -331,7 +331,6 @@ CPP_DECL bool JSC__JSValue__isNumber(JSC__JSValue JSValue0); CPP_DECL bool JSC__JSValue__isObject(JSC__JSValue JSValue0); CPP_DECL bool JSC__JSValue__isPrimitive(JSC__JSValue JSValue0); CPP_DECL bool JSC__JSValue__isSameValue(JSC__JSValue JSValue0, JSC__JSValue JSValue1, JSC__JSGlobalObject* arg2); -CPP_DECL bool JSC__JSValue__isString(JSC__JSValue JSValue0); CPP_DECL bool JSC__JSValue__isSymbol(JSC__JSValue JSValue0); CPP_DECL bool JSC__JSValue__isTerminationException(JSC__JSValue JSValue0, JSC__VM* arg1); CPP_DECL bool JSC__JSValue__isUInt32AsAnyInt(JSC__JSValue JSValue0); diff --git a/src/bun.js/bindings/headers.zig b/src/bun.js/bindings/headers.zig index 0c968d362728..ce84d1e730a5 100644 --- a/src/bun.js/bindings/headers.zig +++ b/src/bun.js/bindings/headers.zig @@ -244,7 +244,6 @@ pub extern fn JSC__JSValue__isNumber(JSValue0: JSC__JSValue) bool; pub extern fn JSC__JSValue__isObject(JSValue0: JSC__JSValue) bool; pub extern fn JSC__JSValue__isPrimitive(JSValue0: JSC__JSValue) bool; pub extern fn JSC__JSValue__isSameValue(JSValue0: JSC__JSValue, JSValue1: JSC__JSValue, arg2: *bindings.JSGlobalObject) bool; -pub extern fn JSC__JSValue__isString(JSValue0: JSC__JSValue) bool; pub extern fn JSC__JSValue__isSymbol(JSValue0: JSC__JSValue) bool; pub extern fn JSC__JSValue__isTerminationException(JSValue0: JSC__JSValue, arg1: *bindings.VM) bool; pub extern fn JSC__JSValue__isUInt32AsAnyInt(JSValue0: JSC__JSValue) bool; diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig index 46a52289478d..c89c07583a13 100644 --- a/src/bun.js/javascript.zig +++ b/src/bun.js/javascript.zig @@ -450,6 +450,10 @@ pub const VirtualMachine = struct { return VMHolder.vm.?; } + pub fn mimeType(this: *VirtualMachine, str: []const u8) ?bun.HTTP.MimeType { + return this.rareData().mimeTypeFromString(this.allocator, str); + } + pub const GCLevel = enum(u3) { none = 0, mild = 1, diff --git a/src/bun.js/rare_data.zig b/src/bun.js/rare_data.zig index 253eb2cf2d96..238d447003d8 100644 --- a/src/bun.js/rare_data.zig +++ b/src/bun.js/rare_data.zig @@ -27,6 +27,18 @@ file_polls_: ?*JSC.FilePoll.HiveArray = null, global_dns_data: ?*JSC.DNS.GlobalData = null, +mime_types: ?bun.HTTP.MimeType.Map = null, + +pub fn mimeTypeFromString(this: *RareData, allocator: std.mem.Allocator, str: []const u8) ?bun.HTTP.MimeType { + if (this.mime_types == null) { + this.mime_types = bun.HTTP.MimeType.createHashTable( + allocator, + ) catch @panic("Out of memory"); + } + + return this.mime_types.?.get(str); +} + pub fn filePolls(this: *RareData, vm: *JSC.VirtualMachine) *JSC.FilePoll.HiveArray { return this.file_polls_ orelse { this.file_polls_ = vm.allocator.create(JSC.FilePoll.HiveArray) catch unreachable; diff --git a/src/bun.js/webcore/blob.zig b/src/bun.js/webcore/blob.zig index c610b199dc8f..5a41f5f8ceae 100644 --- a/src/bun.js/webcore/blob.zig +++ b/src/bun.js/webcore/blob.zig @@ -2391,7 +2391,7 @@ pub const Blob = struct { callframe: *JSC.CallFrame, ) callconv(.C) JSC.JSValue { var allocator = globalThis.allocator(); - var arguments_ = callframe.arguments(2); + var arguments_ = callframe.arguments(3); var args = arguments_.ptr[0..arguments_.len]; if (this.size == 0) { @@ -2410,51 +2410,84 @@ pub const Blob = struct { // If the optional end parameter is not used as a parameter when making this call, let relativeEnd be size. var relativeEnd: i64 = @intCast(i64, this.size); + if (args.ptr[0].isString()) { + args.ptr[2] = args.ptr[0]; + args.ptr[1] = .zero; + args.ptr[0] = .zero; + args.len = 3; + } else if (args.ptr[1].isString()) { + args.ptr[2] = args.ptr[1]; + args.ptr[1] = .zero; + args.len = 3; + } + var args_iter = JSC.Node.ArgumentsSlice.init(globalThis.bunVM(), args); if (args_iter.nextEat()) |start_| { - const start = start_.toInt64(); - if (start < 0) { - // If the optional start parameter is negative, let relativeStart be start + size. - relativeStart = @intCast(i64, @max(start + @intCast(i64, this.size), 0)); - } else { - // Otherwise, let relativeStart be start. - relativeStart = @min(@intCast(i64, start), @intCast(i64, this.size)); + if (start_.isNumber()) { + const start = start_.toInt64(); + if (start < 0) { + // If the optional start parameter is negative, let relativeStart be start + size. + relativeStart = @intCast(i64, @max(start +% @intCast(i64, this.size), 0)); + } else { + // Otherwise, let relativeStart be start. + relativeStart = @min(@intCast(i64, start), @intCast(i64, this.size)); + } } } if (args_iter.nextEat()) |end_| { - const end = end_.toInt64(); - // If end is negative, let relativeEnd be max((size + end), 0). - if (end < 0) { - // If the optional start parameter is negative, let relativeStart be start + size. - relativeEnd = @intCast(i64, @max(end + @intCast(i64, this.size), 0)); - } else { - // Otherwise, let relativeStart be start. - relativeEnd = @min(@intCast(i64, end), @intCast(i64, this.size)); + if (end_.isNumber()) { + const end = end_.toInt64(); + // If end is negative, let relativeEnd be max((size + end), 0). + if (end < 0) { + // If the optional start parameter is negative, let relativeStart be start + size. + relativeEnd = @intCast(i64, @max(end +% @intCast(i64, this.size), 0)); + } else { + // Otherwise, let relativeStart be start. + relativeEnd = @min(@intCast(i64, end), @intCast(i64, this.size)); + } } } var content_type: string = ""; + var content_type_was_allocated = false; if (args_iter.nextEat()) |content_type_| { - if (content_type_.isString()) { - var zig_str = content_type_.getZigString(globalThis); - var slicer = zig_str.toSlice(bun.default_allocator); - defer slicer.deinit(); - var slice = slicer.slice(); - var content_type_buf = allocator.alloc(u8, slice.len) catch unreachable; - content_type = strings.copyLowercase(slice, content_type_buf); + inner: { + if (content_type_.isString()) { + var zig_str = content_type_.getZigString(globalThis); + var slicer = zig_str.toSlice(bun.default_allocator); + defer slicer.deinit(); + var slice = slicer.slice(); + if (!strings.isAllASCII(slice)) { + break :inner; + } + + if (globalThis.bunVM().mimeType(slice)) |mime| { + content_type = mime.value; + break :inner; + } + + content_type_was_allocated = slice.len > 0; + var content_type_buf = allocator.alloc(u8, slice.len) catch unreachable; + content_type = strings.copyLowercase(slice, content_type_buf); + } } } - const len = @intCast(SizeType, @max(relativeEnd - relativeStart, 0)); + const len = @intCast(SizeType, @max(relativeEnd -| relativeStart, 0)); // This copies over the is_all_ascii flag // which is okay because this will only be a <= slice var blob = this.dupe(); blob.offset = @intCast(SizeType, relativeStart); blob.size = len; + + // infer the content type if it was not specified + if (content_type.len == 0 and this.content_type.len > 0 and !this.content_type_allocated) + content_type = this.content_type; + blob.content_type = content_type; - blob.content_type_allocated = content_type.len > 0; + blob.content_type_allocated = content_type_was_allocated; var blob_ = allocator.create(Blob) catch unreachable; blob_.* = blob; @@ -2474,19 +2507,35 @@ pub const Blob = struct { globalThis: *JSC.JSGlobalObject, value: JSC.JSValue, ) callconv(.C) bool { - var zig_str = value.getZigString(globalThis); - if (zig_str.is16Bit()) - return false; + var zig_str = if (value.isString()) + value.getZigString(globalThis) + else + ZigString.Empty; + + if (!zig_str.isAllASCII()) { + zig_str = ZigString.Empty; + } - var slice = zig_str.trimmedSlice(); - if (strings.eql(slice, this.content_type)) + if (zig_str.eql(ZigString.init(this.content_type))) { return true; + } const prev_content_type = this.content_type; { - defer if (this.content_type_allocated) bun.default_allocator.free(prev_content_type); - var content_type_buf = globalThis.allocator().alloc(u8, slice.len) catch unreachable; - this.content_type = strings.copyLowercase(slice, content_type_buf); + var slicer = zig_str.toSlice(bun.default_allocator); + defer slicer.deinit(); + const allocated = this.content_type_allocated; + defer if (allocated) bun.default_allocator.free(prev_content_type); + if (globalThis.bunVM().mimeType(slicer.slice())) |mime| { + this.content_type = mime.value; + this.content_type_allocated = false; + return true; + } + var content_type_buf = globalThis.allocator().alloc(u8, slicer.len) catch { + globalThis.throwOutOfMemory(); + return false; + }; + this.content_type = strings.copyLowercase(slicer.slice(), content_type_buf); } this.content_type_allocated = true; @@ -2602,13 +2651,22 @@ pub const Blob = struct { // Normative conditions for this member are provided // in the § 3.1 Constructors. if (options.get(globalThis, "type")) |content_type| { - if (content_type.isString()) { - var content_type_str = content_type.toSlice(globalThis, bun.default_allocator); - defer content_type_str.deinit(); - var slice = content_type_str.slice(); - var content_type_buf = allocator.alloc(u8, slice.len) catch unreachable; - blob.content_type = strings.copyLowercase(slice, content_type_buf); - blob.content_type_allocated = true; + inner: { + if (content_type.isString()) { + var content_type_str = content_type.toSlice(globalThis, bun.default_allocator); + defer content_type_str.deinit(); + var slice = content_type_str.slice(); + if (!strings.isAllASCII(slice)) { + break :inner; + } + if (globalThis.bunVM().mimeType(slice)) |mime| { + blob.content_type = mime.value; + break :inner; + } + var content_type_buf = allocator.alloc(u8, slice.len) catch unreachable; + blob.content_type = strings.copyLowercase(slice, content_type_buf); + blob.content_type_allocated = true; + } } } } diff --git a/src/bun.js/webcore/streams.zig b/src/bun.js/webcore/streams.zig index 748b4583c69e..b5f690a1df28 100644 --- a/src/bun.js/webcore/streams.zig +++ b/src/bun.js/webcore/streams.zig @@ -399,7 +399,8 @@ pub const StreamStart = union(Tag) { } if (value.get(globalThis, "chunkSize")) |chunkSize| { - return .{ .chunk_size = @intCast(Blob.SizeType, @truncate(i52, chunkSize.toInt64())) }; + if (chunkSize.isNumber()) + return .{ .chunk_size = @intCast(Blob.SizeType, @truncate(i52, chunkSize.toInt64())) }; } return .{ .empty = {} }; @@ -432,8 +433,10 @@ pub const StreamStart = union(Tag) { } if (value.get(globalThis, "highWaterMark")) |chunkSize| { - empty = false; - chunk_size = @intCast(JSC.WebCore.Blob.SizeType, @max(0, @truncate(i51, chunkSize.toInt64()))); + if (chunkSize.isNumber()) { + empty = false; + chunk_size = @intCast(JSC.WebCore.Blob.SizeType, @max(0, @truncate(i51, chunkSize.toInt64()))); + } } if (!empty) { @@ -450,7 +453,8 @@ pub const StreamStart = union(Tag) { var chunk_size: JSC.WebCore.Blob.SizeType = 0; if (value.get(globalThis, "highWaterMark")) |chunkSize| { - chunk_size = @intCast(JSC.WebCore.Blob.SizeType, @max(0, @truncate(i51, chunkSize.toInt64()))); + if (chunkSize.isNumber()) + chunk_size = @intCast(JSC.WebCore.Blob.SizeType, @max(0, @truncate(i51, chunkSize.toInt64()))); } if (value.get(globalThis, "path")) |path| { @@ -485,8 +489,10 @@ pub const StreamStart = union(Tag) { var chunk_size: JSC.WebCore.Blob.SizeType = 2048; if (value.get(globalThis, "highWaterMark")) |chunkSize| { - empty = false; - chunk_size = @intCast(JSC.WebCore.Blob.SizeType, @max(256, @truncate(i51, chunkSize.toInt64()))); + if (chunkSize.isNumber()) { + empty = false; + chunk_size = @intCast(JSC.WebCore.Blob.SizeType, @max(256, @truncate(i51, chunkSize.toInt64()))); + } } if (!empty) { diff --git a/src/http/mime_type.zig b/src/http/mime_type.zig index 3b3d8b961591..a79b5ebddc7f 100644 --- a/src/http/mime_type.zig +++ b/src/http/mime_type.zig @@ -18,6 +18,23 @@ const MimeType = @This(); value: string, category: Category, +pub const Map = bun.StringHashMap(MimeType); + +pub fn createHashTable(allocator: std.mem.Allocator) !Map { + @setCold(true); + + const decls = comptime std.meta.declarations(all); + + var map = Map.init(allocator); + try map.ensureTotalCapacity(@truncate(u32, decls.len)); + @setEvalBranchQuota(4000); + inline for (decls) |decl| { + map.putAssumeCapacityNoClobber(decl.name, @field(all, decl.name)); + } + + return map; +} + pub fn canOpenInEditor(this: MimeType) bool { if (this.category == .text or this.category.isCode()) return true; diff --git a/test/js/web/fetch/blob.test.ts b/test/js/web/fetch/blob.test.ts new file mode 100644 index 000000000000..51b9c0ea8e80 --- /dev/null +++ b/test/js/web/fetch/blob.test.ts @@ -0,0 +1,56 @@ +import { test, expect } from "bun:test"; + +test("Blob.slice", () => { + const blob = new Blob(["Bun", "Foo"]); + const b1 = blob.slice(0, 3, "Text/HTML"); + expect(b1 instanceof Blob).toBeTruthy(); + expect(b1.size).toBe(3); + expect(b1.type).toBe("text/html"); + const b2 = blob.slice(-1, 3); + expect(b2.size).toBe(0); + const b3 = blob.slice(100, 3); + expect(b3.size).toBe(0); + const b4 = blob.slice(0, 10); + expect(b4.size).toBe(blob.size); + + expect(blob.slice().size).toBe(blob.size); + expect(blob.slice(0).size).toBe(blob.size); + expect(blob.slice(NaN).size).toBe(blob.size); + expect(blob.slice(0, Infinity).size).toBe(blob.size); + expect(blob.slice(-Infinity).size).toBe(blob.size); + expect(blob.slice(0, NaN).size).toBe(0); + // @ts-expect-error + expect(blob.slice(Symbol(), "-123").size).toBe(6); + expect(blob.slice(Object.create(null), "-123").size).toBe(6); + // @ts-expect-error + expect(blob.slice(null, "-123").size).toBe(6); + expect(blob.slice(0, 10).size).toBe(blob.size); + expect(blob.slice("text/plain;charset=utf-8").type).toBe("text/plain;charset=utf-8"); +}); + +test("Blob.prototype.type setter", () => { + var blob = new Blob(["Bun", "Foo"], { type: "text/foo" }); + expect(blob.type).toBe("text/foo"); + blob.type = "text/bar"; + expect(blob.type).toBe("text/bar"); + blob.type = "text/baz"; + expect(blob.type).toBe("text/baz"); + blob.type = "text/baz; charset=utf-8"; + expect(blob.type).toBe("text/baz; charset=utf-8"); + // @ts-expect-error + blob.type = NaN; + expect(blob.type).toBe(""); + // @ts-expect-error + blob.type = Symbol(); + expect(blob.type).toBe(""); +}); + +test("new Blob", () => { + var blob = new Blob(["Bun", "Foo"], { type: "text/foo" }); + expect(blob.size).toBe(6); + expect(blob.type).toBe("text/foo"); + + blob = new Blob(["Bun", "Foo"], { type: "\u1234" }); + expect(blob.size).toBe(6); + expect(blob.type).toBe(""); +}); From 003a0a7faeeef8b6d3f2ee15cbbee28ff4069352 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Wed, 8 Mar 2023 00:18:08 -0800 Subject: [PATCH 062/255] Fix bug in test runner --- packages/bun-internal-test/src/runner.node.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bun-internal-test/src/runner.node.mjs b/packages/bun-internal-test/src/runner.node.mjs index eca8a9bb142a..60b11905f236 100644 --- a/packages/bun-internal-test/src/runner.node.mjs +++ b/packages/bun-internal-test/src/runner.node.mjs @@ -55,7 +55,7 @@ async function runTest(path) { stdout, stderr, status: exitCode, - } = spawnSync("bun", ["test", basename(path)], { + } = spawnSync("bun", ["test", path], { stdio: ["ignore", "pipe", "pipe"], timeout: 10_000, env: { From 795a6faffdfc96c013811cdc7cc79bac28cb8002 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Thu, 9 Mar 2023 00:59:37 -0800 Subject: [PATCH 063/255] Support `import()` expressions --- src/bundler/bundle_v2.zig | 81 ++++++++++++++++++++++++---------- src/js_printer.zig | 93 +++++++++++++++++++++++++++++++++++++-- src/runtime.js | 10 ++--- 3 files changed, 152 insertions(+), 32 deletions(-) diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 2800b91474e3..7197c61436ba 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -1644,6 +1644,22 @@ const LinkerContext = struct { return format != .cjs; } + pub fn shouldIncludePart(c: *LinkerContext, source_index: Index.Int, part: js_ast.Part) bool { + // As an optimization, ignore parts containing a single import statement to + // an internal non-wrapped file. These will be ignored anyway and it's a + // performance hit to spin up a goroutine only to discover this later. + if (part.stmts.len == 1) { + if (part.stmts[0].data == .s_import) { + const record = c.graph.ast.items(.import_records)[source_index].at(part.stmts[0].data.s_import.import_record_index); + if (record.source_index.isValid() and c.graph.meta.items(.flags)[record.source_index.get()].wrap == .none) { + return false; + } + } + } + + return true; + } + fn load(this: *LinkerContext, bundle: *BundleV2, entry_points: []Index, reachable: []Index) !void { this.parse_graph = &bundle.graph; @@ -1915,13 +1931,17 @@ const LinkerContext = struct { if (is_part_in_this_chunk) { is_file_in_chunk = true; - // if (can_be_split and part_index != js_ast.namespace_export_part_index and v.c.shouldIncludePart()) - var js_parts = if (source_index == Index.runtime.value) - &v.parts_prefix - else - &v.part_ranges; + if (can_be_split and + part_index != js_ast.namespace_export_part_index and + v.c.shouldIncludePart(source_index, part)) + { + var js_parts = if (source_index == Index.runtime.value) + &v.parts_prefix + else + &v.part_ranges; - appendOrExtendRange(js_parts, source_index, part_index); + appendOrExtendRange(js_parts, source_index, part_index); + } } } @@ -2846,21 +2866,22 @@ const LinkerContext = struct { var ptr = ns_export_dependencies.items.ptr + ns_export_dependencies.items.len; ns_export_dependencies.items.len += parts.len; - for (parts) |part_id| { + for (parts, ptr[0..parts.len]) |part_id, *dependency| { // Use a non-local dependency since this is likely from a different // file if it came in through an export star - ptr[0] = .{ + dependency.* = .{ .source_index = export_.data.source_index, .part_index = part_id, }; - ptr += 1; } } var declared_symbols = js_ast.DeclaredSymbol.List{}; var exports_ref = c.graph.ast.items(.exports_ref)[id]; - var export_stmts: []js_ast.Stmt = stmts.head[0 .. @as(usize, @boolToInt(needs_exports_variable)) + @as(usize, @boolToInt(properties.items.len > 0))]; - stmts.head = stmts.head[export_stmts.len..]; + var all_export_stmts: []js_ast.Stmt = stmts.head[0 .. @as(usize, @boolToInt(needs_exports_variable)) + @as(usize, @boolToInt(properties.items.len > 0))]; + stmts.head = stmts.head[all_export_stmts.len..]; + var remaining_stmts = all_export_stmts; + defer std.debug.assert(remaining_stmts.len == 0); // all must be used // Prefix this part with "var exports = {}" if this isn't a CommonJS entry point if (needs_exports_variable) { @@ -2875,14 +2896,15 @@ const LinkerContext = struct { ), .value = js_ast.Expr.allocate(allocator_, js_ast.E.Object, .{}, loc), }; - export_stmts[0] = js_ast.Stmt.allocate( + remaining_stmts[0] = js_ast.Stmt.allocate( allocator_, js_ast.S.Local, .{ .decls = decls, }, - export_stmts[0].loc, + loc, ); + remaining_stmts = remaining_stmts[1..]; declared_symbols.append(allocator_, .{ .ref = exports_ref, .is_top_level = true }) catch unreachable; } @@ -2896,7 +2918,7 @@ const LinkerContext = struct { js_ast.Expr.allocate(allocator_, js_ast.E.Object, .{ .properties = js_ast.G.Property.List.fromList(properties) }, loc), }; // the end incase we somehow get into a state where needs_export_variable is false but properties.len > 0 - export_stmts[export_stmts.len - 1] = js_ast.Stmt.allocate( + remaining_stmts[0] = js_ast.Stmt.allocate( allocator_, js_ast.S.SExpr, .{ @@ -2912,7 +2934,7 @@ const LinkerContext = struct { }, loc, ); - + remaining_stmts = remaining_stmts[1..]; // Make sure this file depends on the "__export" symbol const parts = c.topLevelSymbolsToPartsForRuntime(export_ref); ns_export_dependencies.ensureUnusedCapacity(parts.len) catch unreachable; @@ -2927,14 +2949,14 @@ const LinkerContext = struct { } // No need to generate a part if it'll be empty - if (export_stmts.len > 0) { + if (all_export_stmts.len > 0) { // - we must already have preallocated the parts array // - if the parts list is completely empty, we shouldn't have gotten here in the first place // Initialize the part that was allocated for us earlier. The information // here will be used after this during tree shaking. c.graph.ast.items(.parts)[id].slice()[js_ast.namespace_export_part_index] = .{ - .stmts = export_stmts, + .stmts = all_export_stmts, .symbol_uses = ns_export_symbol_uses, .dependencies = js_ast.Dependency.List.fromList(ns_export_dependencies), .declared_symbols = declared_symbols, @@ -4438,13 +4460,13 @@ const LinkerContext = struct { }; } - _ = toCommonJSRef; const print_options = js_printer.Options{ // TODO: IIFE .indent = 0, .allocator = allocator, - .to_module_ref = toESMRef, + .to_esm_ref = toESMRef, + .to_commonjs_ref = toCommonJSRef, // .require_ref = toCommonJSRef, .require_or_import_meta_for_source_callback = js_printer.RequireOrImportMeta.Callback.init(LinkerContext, requireOrImportMetaForSource, c), @@ -5377,6 +5399,7 @@ const LinkerContext = struct { .decls = std.ArrayList(G.Decl).init(temp_allocator), .allocator = temp_allocator, }; + var inner_stmts = stmts.all_stmts.items; // Hoist all top-level "var" and "function" declarations out of the closure { var end: usize = 0; @@ -5415,10 +5438,10 @@ const LinkerContext = struct { }, else => {}, } - stmts.all_stmts.items[end] = stmt; + inner_stmts[end] = stmt; end += 1; } - stmts.all_stmts.items.len = end; + inner_stmts.len = end; } if (!c.options.minify_syntax and hoisty.decls.items.len > 0) { @@ -5442,7 +5465,7 @@ const LinkerContext = struct { .args = &.{}, .is_async = is_async, .body = .{ - .stmts = stmts.all_stmts.items, + .stmts = inner_stmts, .loc = Logger.Loc.Empty, }, }, @@ -5457,7 +5480,7 @@ const LinkerContext = struct { .target = Expr.init( E.Identifier, E.Identifier{ - .ref = toESMRef, + .ref = c.esm_runtime_ref, }, Logger.Loc.Empty, ), @@ -5495,6 +5518,15 @@ const LinkerContext = struct { out_stmts = stmts.outside_wrapper_prefix.items; } + if (out_stmts.len == 0) { + return .{ + .result = .{ + .code = &[_]u8{}, + .source_map = null, + }, + }; + } + const parts_to_print = &[_]js_ast.Part{ js_ast.Part{ // .tag = .stmts, @@ -5507,7 +5539,8 @@ const LinkerContext = struct { .indent = 0, .allocator = allocator, - .to_module_ref = toESMRef, + .to_esm_ref = toESMRef, + .to_commonjs_ref = toCommonJSRef, .require_ref = runtimeRequireRef, .require_or_import_meta_for_source_callback = js_printer.RequireOrImportMeta.Callback.init( LinkerContext, diff --git a/src/js_printer.zig b/src/js_printer.zig index f70517da11d1..3d1af7c3949d 100644 --- a/src/js_printer.zig +++ b/src/js_printer.zig @@ -475,7 +475,8 @@ pub const SourceMapHandler = struct { pub const Options = struct { transform_imports: bool = true, - to_module_ref: Ref = Ref.None, + to_commonjs_ref: Ref = Ref.None, + to_esm_ref: Ref = Ref.None, require_ref: ?Ref = null, indent: usize = 0, externals: []u32 = &[_]u32{}, @@ -539,7 +540,7 @@ pub const RequireOrImportMeta = struct { callback: *const Fn = undefined, pub fn call(self: Callback, id: u32) RequireOrImportMeta { - return self.callback(self.ctx, id); + return self.callback(self.ctx.?, id); } pub fn init( @@ -1040,6 +1041,7 @@ fn NewPrinter( } pub fn printSymbol(p: *Printer, ref: Ref) void { + std.debug.assert(!ref.isNull()); const name = p.renamer.nameForSymbol(ref); p.printIdentifier(name); @@ -1529,9 +1531,10 @@ fn NewPrinter( p: *Printer, import_record_index: u32, leading_interior_comments: []G.Comment, - level: Level, + level_: Level, flags: ExprFlag.Set, ) void { + var level = level_; const wrap = level.gte(.new) or flags.contains(.forbid_call); if (wrap) p.print("("); defer if (wrap) p.print(")"); @@ -1560,6 +1563,89 @@ fn NewPrinter( } } + if (record.source_index.isValid()) { + var meta = p.options.requireOrImportMetaForSource(record.source_index.get()); + + // Don't need the namespace object if the result is unused anyway + if (!flags.contains(.expr_result_is_unused)) { + meta.exports_ref = Ref.None; + } + + // Internal "import()" of async ESM + if (record.kind == .dynamic and meta.is_wrapper_async) { + p.printSpaceBeforeIdentifier(); + p.printSymbol(meta.wrapper_ref); + p.print("()"); + if (meta.exports_ref.isValid()) { + _ = p.printDotThenPrefix(); + p.printSpaceBeforeIdentifier(); + p.printSymbol(meta.exports_ref); + p.printDotThenSuffix(); + } + return; + } + + // Internal "require()" or "import()" + if (record.kind == .dynamic) { + p.printSpaceBeforeIdentifier(); + p.print("Promise.resolve()"); + + level = p.printDotThenPrefix(); + } + defer if (record.kind == .dynamic) p.printDotThenSuffix(); + + // Make sure the comma operator is propertly wrapped + + if (meta.exports_ref.isValid() and level.gte(.comma)) { + p.print("()"); + } + defer if (meta.exports_ref.isValid() and level.gte(.comma)) p.print(")"); + + // Wrap this with a call to "__toESM()" if this is a CommonJS file + const wrap_with_to_esm = record.wrap_with_to_esm; + if (wrap_with_to_esm) { + p.printSpaceBeforeIdentifier(); + p.printSymbol(p.options.to_esm_ref); + p.print("("); + } + + // Call the wrapper + p.printSpaceBeforeIdentifier(); + p.printSymbol(meta.wrapper_ref); + p.print("()"); + + // Return the namespace object if this is an ESM file + if (meta.exports_ref.isValid()) { + p.print(","); + p.printSpace(); + + // Wrap this with a call to "__toCommonJS()" if this is an ESM file + const wrap_with_to_cjs = record.wrap_with_to_commonjs; + if (wrap_with_to_cjs) { + p.printSpaceBeforeIdentifier(); + p.printSymbol(p.options.to_commonjs_ref); + p.print("("); + } + p.printSymbol(meta.exports_ref); + if (wrap_with_to_cjs) { + p.print(")"); + } + } + + if (wrap_with_to_esm) { + // Finish the call to "__toESM()" + // TODO: + // if (p.module_type.isESM()) { + // p.print(","); + // p.printSpace(); + // p.print("1"); + // } + p.print(")"); + } + + return; + } + const is_external = std.mem.indexOfScalar( u32, p.options.externals, @@ -1599,6 +1685,7 @@ fn NewPrinter( p.addSourceMapping(record.range.loc); p.printSpaceBeforeIdentifier(); + // Allow it to fail at runtime, if it should p.print("import("); p.printImportRecordPath(record); diff --git a/src/runtime.js b/src/runtime.js index b452fd724556..4699b07b8aa2 100644 --- a/src/runtime.js +++ b/src/runtime.js @@ -237,6 +237,11 @@ export var __toESM = (mod, isNodeMode) => { ); }; +export var __esm = (fn, res) => + function __init() { + return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])((fn = 0))), res; + }; + // Converts the module from ESM to CommonJS export var __toCommonJS = /* @__PURE__ */ (cache => { return (mod, temp) => { @@ -246,8 +251,3 @@ export var __toCommonJS = /* @__PURE__ */ (cache => { ); }; })(typeof WeakMap !== "undefined" ? new WeakMap() : 0); - -export var __esm = (fn, res) => - function __init() { - return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])((fn = 0))), res; - }; From a318f716acdc4b0c40ac414c6c72dbe52d57ddb5 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Thu, 9 Mar 2023 20:10:05 -0800 Subject: [PATCH 064/255] Implement `require()` --- src/bundler/bundle_v2.zig | 51 +++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 7197c61436ba..fc6a9ffcd33c 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -1987,10 +1987,14 @@ const LinkerContext = struct { visitor.visit(order.source_index); } - chunk.content.javascript.files_in_chunk_order = visitor.files.items; var parts_in_chunk_order = try this.allocator.alloc(PartRange, visitor.part_ranges.items.len + visitor.parts_prefix.items.len); - std.mem.copy(PartRange, parts_in_chunk_order, visitor.parts_prefix.items); - std.mem.copy(PartRange, parts_in_chunk_order[visitor.parts_prefix.items.len..], visitor.part_ranges.items); + bun.concat( + PartRange, + parts_in_chunk_order, + &.{ visitor.parts_prefix.items, visitor.part_ranges.items }, + ); + chunk.content.javascript.files_in_chunk_order = visitor.files.items; + chunk.content.javascript.parts_in_chunk_in_order = parts_in_chunk_order; } @@ -2773,7 +2777,7 @@ const LinkerContext = struct { const needs_exports_variable = c.graph.meta.items(.flags)[id].needs_exports_variable; const stmts_count = - // 3 statements for every export + // 2 statements for every export export_aliases.len * 2 + // + 1 if there are non-zero exports @as(usize, @boolToInt(export_aliases.len > 0)) + @@ -3081,7 +3085,7 @@ const LinkerContext = struct { for (other_parts) |other_part_index| { var local = local_dependencies.getOrPut(@intCast(u32, other_part_index)) catch unreachable; - if (!local.found_existing or other_part_index != part_index) { + if (!local.found_existing or local.value_ptr.* != part_index) { local.value_ptr.* = @intCast(u32, part_index); // note: if we crash on append, it is due to threadlocal heaps in mimalloc part.dependencies.push( @@ -4023,11 +4027,12 @@ const LinkerContext = struct { newline_before_comment = compile_result.code().len > 0; } - if (entry_point_tail.code().len > 0) { + const tail_code = entry_point_tail.code(); + if (tail_code.len > 0) { // Stick the entry point tail at the end of the file. Deliberately don't // include any source mapping information for this because it's automatically // generated and doesn't correspond to a location in the input file. - j.push(entry_point_tail.code()); + j.push(tail_code); } // Put the cross-chunk suffix inside the IIFE @@ -5272,10 +5277,10 @@ const LinkerContext = struct { // Only include the arguments that are actually used var args = std.ArrayList(js_ast.G.Arg).initCapacity( temp_allocator, - @boolToInt(ast.uses_exports_ref) + @boolToInt(ast.uses_module_ref), + if (ast.uses_module_ref or ast.uses_exports_ref) 2 else 0, ) catch unreachable; - if (ast.uses_exports_ref) { + if (ast.uses_module_ref or ast.uses_exports_ref) { args.appendAssumeCapacity( js_ast.G.Arg{ .binding = js_ast.Binding.alloc( @@ -5287,20 +5292,20 @@ const LinkerContext = struct { ), }, ); - } - if (ast.uses_module_ref) { - args.appendAssumeCapacity( - js_ast.G.Arg{ - .binding = js_ast.Binding.alloc( - temp_allocator, - js_ast.B.Identifier{ - .ref = ast.module_ref.?, - }, - Logger.Loc.Empty, - ), - }, - ); + if (ast.uses_module_ref) { + args.appendAssumeCapacity( + js_ast.G.Arg{ + .binding = js_ast.Binding.alloc( + temp_allocator, + js_ast.B.Identifier{ + .ref = ast.module_ref.?, + }, + Logger.Loc.Empty, + ), + }, + ); + } } // TODO: variants of the runtime functions @@ -5323,7 +5328,7 @@ const LinkerContext = struct { .target = Expr.init( E.Identifier, E.Identifier{ - .ref = toCommonJSRef, + .ref = c.cjs_runtime_ref, }, Logger.Loc.Empty, ), From ee435c69d6362e42335c6abb42efcd15717084e0 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Thu, 9 Mar 2023 20:32:35 -0800 Subject: [PATCH 065/255] clean up bit_set.zig slightly --- src/bit_set.zig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/bit_set.zig b/src/bit_set.zig index 83f47c6239a1..33e0ffed39bb 100644 --- a/src/bit_set.zig +++ b/src/bit_set.zig @@ -849,8 +849,8 @@ pub const DynamicBitSetUnmanaged = struct { bulk_mask_index = start_mask_index; } - while (bulk_mask_index < end_mask_index) : (bulk_mask_index += 1) { - self.masks[bulk_mask_index] = std.math.boolMask(MaskInt, value); + for (self.masks[bulk_mask_index..end_mask_index]) |*mask| { + mask.* = std.math.boolMask(MaskInt, value); } if (end_bit > 0) { @@ -1306,7 +1306,7 @@ pub const IteratorOptions = struct { }; // The iterator is reusable between several bit set types -fn BitSetIterator(comptime MaskInt: type, comptime options: IteratorOptions) type { +pub fn BitSetIterator(comptime MaskInt: type, comptime options: IteratorOptions) type { const ShiftInt = std.math.Log2Int(MaskInt); const kind = options.kind; const direction = options.direction; From 505b1ac5b2ff2a35b40ee7a977e42072696e107d Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Thu, 9 Mar 2023 20:32:58 -0800 Subject: [PATCH 066/255] Move some things around --- src/js_ast.zig | 97 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 59 insertions(+), 38 deletions(-) diff --git a/src/js_ast.zig b/src/js_ast.zig index 23f15b1529a9..00ad78b26277 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -2400,6 +2400,15 @@ pub const Expr = struct { loc: logger.Loc, data: Data, + pub fn isAnonymousNamed(expr: Expr) bool { + return switch (expr.data) { + .e_arrow => true, + .e_function => |func| func.func.name == null, + .e_class => |class| class.class_name == null, + else => false, + }; + } + pub fn clone(this: Expr, allocator: std.mem.Allocator) !Expr { return .{ .loc = this.loc, @@ -2735,23 +2744,6 @@ pub const Expr = struct { return [2]f64{ left.e_number.value, right.e_number.value }; } - pub fn isAnonymousNamed(e: *Expr) bool { - switch (e.data) { - .e_arrow => { - return true; - }, - .e_function => |func| { - return func.func.name == null; - }, - .e_class => |class| { - return class.class_name == null; - }, - else => { - return false; - }, - } - } - pub var icount: usize = 0; // We don't need to dynamically allocate booleans @@ -5247,21 +5239,49 @@ pub const DeclaredSymbol = struct { is_top_level: bool = false, pub const List = struct { - refs: std.ArrayListUnmanaged(Ref) = .{}, + refs_list: std.ArrayListUnmanaged(Ref) = .{}, is_top_level: bun.bit_set.DynamicBitSetUnmanaged = .{}, + pub fn refs(this: *const List) []Ref { + return this.refs_list.items; + } + + pub fn toOwnedSlice(this: *List) List { + var new = this.*; + if (this.is_top_level.bit_length > this.refs_list.items.len) + new.is_top_level.setRangeValue( + .{ + .start = this.refs_list.items.len, + .end = this.is_top_level.bit_length, + }, + false, + ); + + this.* = .{}; + return new; + } + pub fn clone(this: *const List, allocator: std.mem.Allocator) !List { - var refs = try this.refs.clone(allocator); - errdefer refs.deinit(allocator); + var refs_list = try this.refs_list.clone(allocator); + errdefer refs_list.deinit(allocator); + var top = try this.is_top_level.clone(allocator); + if (top.bit_length > this.refs_list.items.len) + top.setRangeValue( + .{ + .start = this.refs_list.items.len, + .end = top.bit_length, + }, + false, + ); return List{ - .refs = refs, - .is_top_level = try this.is_top_level.clone(allocator), + .refs_list = refs_list, + .is_top_level = top, }; } pub inline fn len(this: List) usize { - return this.refs.items.len; + return this.refs().len; } pub fn append(this: *List, allocator: std.mem.Allocator, entry: DeclaredSymbol) !void { @@ -5275,8 +5295,8 @@ pub const DeclaredSymbol = struct { } pub fn appendListAssumeCapacity(this: *List, other: List) void { - var j = other.refs.items.len; - this.refs.appendSliceAssumeCapacity(other.refs.items); + var j = other.refs().len; + this.refs_list.appendSliceAssumeCapacity(other.refs()); var is_top_level = &this.is_top_level; var iter = other.is_top_level.iterator(.{}); @@ -5287,40 +5307,40 @@ pub const DeclaredSymbol = struct { } pub fn appendAssumeCapacity(this: *List, entry: DeclaredSymbol) void { - this.refs.appendAssumeCapacity(entry.ref); - this.is_top_level.setValue(this.refs.items.len - 1, entry.is_top_level); + this.refs_list.appendAssumeCapacity(entry.ref); + this.is_top_level.setValue(this.refs().len - 1, entry.is_top_level); } pub fn ensureTotalCapacity(this: *List, allocator: std.mem.Allocator, count: usize) !void { - try this.ensureUnusedCapacity(allocator, this.refs.capacity -| this.refs.items.len +| count); + try this.ensureUnusedCapacity(allocator, this.refs_list.capacity -| this.refs().len +| count); } pub fn ensureUnusedCapacity(this: *List, allocator: std.mem.Allocator, count: usize) !void { - try this.refs.ensureUnusedCapacity(allocator, count); - if (this.is_top_level.capacity() < this.refs.capacity) { + try this.refs_list.ensureUnusedCapacity(allocator, count); + if (this.is_top_level.capacity() < this.refs_list.capacity) { var prev = this.is_top_level; - this.is_top_level = try bun.bit_set.DynamicBitSetUnmanaged.initEmpty(allocator, this.refs.capacity); + this.is_top_level = try bun.bit_set.DynamicBitSetUnmanaged.initEmpty(allocator, this.refs_list.capacity); prev.copyInto(this.is_top_level); prev.deinit(allocator); } } pub fn clearRetainingCapacity(this: *List) void { - this.refs.clearRetainingCapacity(); + this.refs_list.clearRetainingCapacity(); this.is_top_level.setAll(false); } pub fn deinit(this: *List, allocator: std.mem.Allocator) void { - this.refs.deinit(allocator); + this.refs_list.deinit(allocator); this.is_top_level.deinit(allocator); } pub fn initCapacity(allocator: std.mem.Allocator, capacity: usize) !List { - var refs = try std.ArrayListUnmanaged(Ref).initCapacity(allocator, capacity); - errdefer refs.deinit(allocator); - var is_top_level = try bun.bit_set.DynamicBitSetUnmanaged.initEmpty(allocator, refs.capacity); + var refs_list = try std.ArrayListUnmanaged(Ref).initCapacity(allocator, capacity); + errdefer refs_list.deinit(allocator); + var is_top_level = try bun.bit_set.DynamicBitSetUnmanaged.initEmpty(allocator, refs_list.capacity); return List{ - .refs = refs, + .refs_list = refs_list, .is_top_level = is_top_level, }; } @@ -5337,10 +5357,11 @@ pub const DeclaredSymbol = struct { }; pub fn forEachTopLevelSymbol(decls: *List, ctx: anytype, comptime Fn: anytype) void { - var refs = decls.refs.items; + var refs = decls.refs(); std.debug.assert(refs.len <= decls.is_top_level.capacity()); var iter = decls.is_top_level.iterator(.{ .direction = .forward }); while (iter.next()) |index| { + std.debug.assert(index < refs.len); const ref = refs[index]; Fn(ctx, ref); } From bcff5184ef7d5109be277bddf52fb4f1a36294ab Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Thu, 9 Mar 2023 20:33:26 -0800 Subject: [PATCH 067/255] misc cleanup --- src/js_parser.zig | 89 +++++++++++++++++++++-------------------------- 1 file changed, 39 insertions(+), 50 deletions(-) diff --git a/src/js_parser.zig b/src/js_parser.zig index ba3116ec31e4..f389cdbcb4a9 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -4274,7 +4274,7 @@ fn NewParser_( for (symbol_use_refs, symbol_use_values) |ref, prev| { symbols[ref.innerIndex()].use_count_estimate -|= prev.count_estimate; } - const declared_refs = part.declared_symbols.refs.items; + const declared_refs = part.declared_symbols.refs(); for (declared_refs) |declared| { symbols[declared.innerIndex()].use_count_estimate = 0; // } @@ -5519,8 +5519,11 @@ fn NewParser_( } } - for (0..scope.children.slice().len) |i| { - p.hoistSymbols(scope.children.mut(i).*); + { + const children = scope.children.slice(); + for (children) |child| { + p.hoistSymbols(child); + } } } @@ -12922,14 +12925,14 @@ fn NewParser_( for (previous_parts, 0..) |*previous_part, j| { if (previous_part.stmts.len == 0) continue; - var refs = previous_part.declared_symbols.refs.items; + var refs = previous_part.declared_symbols.refs(); for (refs) |ref| { if (p.symbol_uses.contains(ref)) { // we move this part to our other file for (previous_parts[0..j]) |*this_part| { if (this_part.stmts.len == 0) continue; - const other_refs = this_part.declared_symbols.refs.items; + const other_refs = this_part.declared_symbols.refs(); for (other_refs) |other_ref| { if (previous_part.symbol_uses.contains(other_ref)) { @@ -12962,7 +12965,7 @@ fn NewParser_( try parts.append(js_ast.Part{ .stmts = _stmts, .symbol_uses = p.symbol_uses, - .declared_symbols = p.declared_symbols.clone(p.allocator) catch unreachable, + .declared_symbols = p.declared_symbols.toOwnedSlice(), .import_record_indices = bun.BabyList(u32).init( p.import_records_for_current_part.toOwnedSlice( p.allocator, @@ -12972,7 +12975,6 @@ fn NewParser_( .can_be_removed_if_unused = p.stmtsCanBeRemovedIfUnused(_stmts), }); p.symbol_uses = .{}; - p.declared_symbols.clearRetainingCapacity(); } else if (p.declared_symbols.len() > 0 or p.symbol_uses.count() > 0) { // if the part is dead, invalidate all the usage counts @@ -13795,7 +13797,7 @@ fn NewParser_( const is_call_target = @as(Expr.Tag, p.call_target) == .e_binary and expr.data.e_binary == p.call_target.e_binary; // const is_stmt_expr = @as(Expr.Tag, p.stmt_expr_value) == .e_binary and expr.data.e_binary == p.stmt_expr_value.e_binary; - const was_anonymous_named_expr = p.isAnonymousNamedExpr(e_.right); + const was_anonymous_named_expr = e_.right.isAnonymousNamed(); if (comptime jsx_transform_type == .macro) { if (e_.op == Op.Code.bin_instanceof and (e_.right.data == .e_jsx_element or e_.left.data == .e_jsx_element)) { @@ -14554,7 +14556,7 @@ fn NewParser_( }, .e_binary => |e2| { if (in.assign_target != .none and e2.op == .bin_assign) { - const was_anonymous_named_expr = p.isAnonymousNamedExpr(e2.right); + const was_anonymous_named_expr = e2.right.isAnonymousNamed(); e2.left = p.visitExprInOut(e2.left, ExprIn{ .assign_target = .replace }); e2.right = p.visitExpr(e2.right); @@ -14628,7 +14630,7 @@ fn NewParser_( } if (property.initializer != null) { - const was_anonymous_named_expr = p.isAnonymousNamedExpr(property.initializer orelse unreachable); + const was_anonymous_named_expr = property.initializer.?.isAnonymousNamed(); property.initializer = p.visitExpr(property.initializer.?); if (property.value) |val| { @@ -15906,7 +15908,7 @@ fn NewParser_( switch (data.value) { .expr => |expr| { - const was_anonymous_named_expr = p.isAnonymousNamedExpr(expr); + const was_anonymous_named_expr = expr.isAnonymousNamed(); data.value.expr = p.visitExpr(expr); @@ -16739,7 +16741,7 @@ fn NewParser_( if (decls[i].value != null) { var val = decls[i].value.?; - const was_anonymous_named_expr = p.isAnonymousNamedExpr(val); + const was_anonymous_named_expr = val.isAnonymousNamed(); var replacement: ?*const RuntimeFeatures.ReplaceableExport = null; const prev_macro_call_count = p.macro_call_count; @@ -17421,27 +17423,13 @@ fn NewParser_( loc: logger.Loc, ref: Ref, ) Expr { - p.relocated_top_level_vars.append(p.allocator, LocRef{ .loc = loc, .ref = ref }) catch unreachable; - var _ref = ref; - p.recordUsage(_ref); - return Expr.initIdentifier(_ref, loc); - } + // There was a Zig stage1 bug here we had to copy `ref` into a local + // const variable or else the result would be wrong + // I remember that bug in particular took hours, possibly days to uncover. - fn isAnonymousNamedExpr(_: *P, expr: ExprNodeIndex) bool { - switch (expr.data) { - .e_arrow => { - return true; - }, - .e_function => |func| { - return func.func.name == null; - }, - .e_class => |class| { - return class.class_name == null; - }, - else => { - return false; - }, - } + p.relocated_top_level_vars.append(p.allocator, LocRef{ .loc = loc, .ref = ref }) catch unreachable; + p.recordUsage(ref); + return Expr.initIdentifier(ref, loc); } fn valueForDefine(p: *P, loc: logger.Loc, assign_target: js_ast.AssignTarget, is_delete_target: bool, define_data: *const DefineData) Expr { @@ -17555,7 +17543,7 @@ fn NewParser_( for (bind.items) |*item| { p.visitBinding(item.binding, duplicate_arg_check); if (item.default_value) |default_value| { - const was_anonymous_named_expr = p.isAnonymousNamedExpr(default_value); + const was_anonymous_named_expr = default_value.isAnonymousNamed(); item.default_value = p.visitExpr(default_value); switch (item.binding.data) { @@ -17579,7 +17567,7 @@ fn NewParser_( p.visitBinding(property.value, duplicate_arg_check); if (property.default_value) |default_value| { - const was_anonymous_named_expr = p.isAnonymousNamedExpr(default_value); + const was_anonymous_named_expr = default_value.isAnonymousNamed(); property.default_value = p.visitExpr(default_value); switch (property.value.data) { @@ -17797,7 +17785,7 @@ fn NewParser_( if (property.value) |val| { if (name_to_keep) |name| { - const was_anon = p.isAnonymousNamedExpr(val); + const was_anon = val.isAnonymousNamed(); property.value = p.maybeKeepExprSymbolName(p.visitExpr(val), name, was_anon); } else { property.value = p.visitExpr(val); @@ -17813,7 +17801,7 @@ fn NewParser_( if (property.initializer) |val| { // if (property.flags.is_static and ) if (name_to_keep) |name| { - const was_anon = p.isAnonymousNamedExpr(val); + const was_anon = val.isAnonymousNamed(); property.initializer = p.maybeKeepExprSymbolName(p.visitExpr(val), name, was_anon); } else { property.initializer = p.visitExpr(val); @@ -18449,7 +18437,7 @@ fn NewParser_( } const bundling = p.options.bundle; - var parts_end: usize = 0; + var parts_end: usize = @as(usize, @boolToInt(bundling)); // Handle import paths after the whole file has been visited because we need // symbol usage counts to be able to remove unused type-only imports in // TypeScript code. @@ -18457,7 +18445,7 @@ fn NewParser_( var kept_import_equals = false; var removed_import_equals = false; - var i: usize = 0; + var i: usize = parts_end; // Potentially remove some statements, then filter out parts to remove any // with no statements while (i < parts.len) : (i += 1) { @@ -18470,7 +18458,7 @@ fn NewParser_( removed_import_equals = removed_import_equals or result.removed_import_equals; part.stmts = result.stmts; - if (part.stmts.len > 0 or (i == js_ast.namespace_export_part_index and bundling)) { + if (part.stmts.len > 0) { if (p.module_scope.contains_direct_eval and part.declared_symbols.len() > 0) { // If this file contains a direct call to "eval()", all parts that // declare top-level symbols must be kept since the eval'd code may @@ -18494,7 +18482,6 @@ fn NewParser_( parts[parts_end] = part; parts_end += 1; } - p.declared_symbols.clearRetainingCapacity(); } // We need to iterate multiple times if an import-equals statement was @@ -18504,6 +18491,7 @@ fn NewParser_( } } + // leave the first part in there for namespace export when bundling parts = parts[0..parts_end]; // Do a second pass for exported items now that imported items are filled out @@ -18559,19 +18547,19 @@ fn NewParser_( var imports_list_i: u32 = 0; var exports_list_i: u32 = 0; - for (part.stmts, 0..) |_, i| { - switch (part.stmts[i].data) { + for (part.stmts) |*stmt| { + switch (stmt.data) { .s_import => { - imports_list[imports_list_i] = part.stmts[i]; - part.stmts[i] = Stmt.empty(); - part.stmts[i].loc = imports_list[imports_list_i].loc; + imports_list[imports_list_i] = stmt.*; + stmt.* = Stmt.empty(); + stmt.loc = imports_list[imports_list_i].loc; imports_list_i += 1; }, .s_export_star, .s_export_from => { - exports_list[exports_list_i] = part.stmts[i]; - part.stmts[i] = Stmt.empty(); - part.stmts[i].loc = exports_list[exports_list_i].loc; + exports_list[exports_list_i] = stmt.*; + stmt.* = Stmt.empty(); + stmt.loc = exports_list[exports_list_i].loc; exports_list_i += 1; }, else => {}, @@ -19050,8 +19038,9 @@ fn NewParser_( // linked list in the map. This is the case for multiple "var" // declarations with the same name, for example. var ref = input; - while (ctx.symbols[ref.innerIndex()].link.isValid()) { - ref = ctx.symbols[ref.innerIndex()].link; + var symbol_ref = &ctx.symbols[ref.innerIndex()]; + while (symbol_ref.link.isValid()) : (symbol_ref = &ctx.symbols[ref.innerIndex()]) { + ref = symbol_ref.link; } var entry = ctx.top_level_symbols_to_parts.getOrPut(ctx.allocator, ref) catch unreachable; From 080f22963bec382d8b643be3afaf24afc6d3e73e Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Thu, 9 Mar 2023 20:34:43 -0800 Subject: [PATCH 068/255] Cleanup some things --- src/runtime.js | 111 +++++++++++++++++---------------------- src/string_immutable.zig | 2 +- src/util.zig | 13 +++++ 3 files changed, 63 insertions(+), 63 deletions(-) diff --git a/src/runtime.js b/src/runtime.js index 4699b07b8aa2..d58203ee76ec 100644 --- a/src/runtime.js +++ b/src/runtime.js @@ -1,5 +1,5 @@ -const tagSymbol = Symbol.for("CommonJSTransformed"); -const cjsRequireSymbol = Symbol.for("CommonJS"); +var tagSymbol; +var cjsRequireSymbol; var __create = Object.create; var __descs = Object.getOwnPropertyDescriptors; var __defProp = Object.defineProperty; @@ -8,20 +8,50 @@ var __hasOwnProp = Object.prototype.hasOwnProperty; var __getOwnPropNames = Object.getOwnPropertyNames; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; -export var __markAsModule = target => __defProp(target, "__esModule", { value: true, configurable: true }); - -export var __reExport = (target, mod, copyDefault, desc) => { - if ((mod && typeof mod === "object") || typeof mod === "function") - for (let key of __getOwnPropNames(mod)) - if (!__hasOwnProp.call(target, key) && (copyDefault || key !== "default")) - __defProp(target, key, { - get: () => mod[key], - configurable: true, - enumerable: !(desc = __getOwnPropDesc(mod, key)) || desc.enumerable, +export var __copyProps = (to, from, except, desc) => { + if ((from && typeof from === "object") || typeof from === "function") + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { + get: () => from[key], + enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable, }); - return target; + + return to; }; +export var __markAsModule = target => __defProp(target, "__esModule", { value: true, configurable: true }); + +// This is used to implement "export * from" statements. It copies properties +// from the imported module to the current module's ESM export object. If the +// current module is an entry point and the target format is CommonJS, we +// also copy the properties to "module.exports" in addition to our module's +// internal ESM export object. +export var __reExport = (target, mod, secondTarget) => ( + __copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default") +); + +// Converts the module from CommonJS to ESM. When in node mode (i.e. in an +// ".mjs" file, package.json has "type: module", or the "__esModule" export +// in the CommonJS file is falsy or missing), the "default" property is +// overridden to point to the original CommonJS exports object instead. +export var __toESM = (mod, isNodeMode, target) => ( + (target = mod != null ? __create(__getProtoOf(mod)) : {}), + __copyProps( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod, + ) +); + +// Converts the module from ESM to CommonJS. This clones the input module +// object with the addition of a non-enumerable "__esModule" property set +// to "true", which overwrites any existing export named "__esModule". +export var __toCommonJS = mod => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + // lazy require to prevent loading one icon from a design system export var $$lzy = (target, mod, props) => { for (let key in props) { @@ -35,25 +65,16 @@ export var $$lzy = (target, mod, props) => { return target; }; -export var __toModule = mod => { - return __reExport( - __markAsModule( - __defProp( - mod != null ? __create(__getProtoOf(mod)) : {}, - "default", - mod && mod.__esModule && "default" in mod - ? { get: () => mod.default, enumerable: true, configurable: true } - : { value: mod, enumerable: true, configurable: true }, - ), - ), - mod, - ); -}; +// When you do know the module is CJS +export var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports); -export var __commonJS = (cb, name) => { +// When you don't know if the module is going to be ESM or CJS +export var __cJS2eSM = (cb, name) => { var mod; var origExports; var has_run = false; + tagSymbol ??= Symbol.for("CommonJSTransformed"); + cjsRequireSymbol ??= Symbol.for("CommonJS"); const requireFunction = function load() { if (has_run) { @@ -122,8 +143,6 @@ export var __commonJS = (cb, name) => { return requireFunction; }; -export var __cJS2eSM = __commonJS; - export var __internalIsCommonJSNamespace = namespace => namespace != null && typeof namespace === "object" && @@ -215,39 +234,7 @@ export var __decorateClass = (decorators, target, key, kind) => { export var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index); -// Converts the module from CommonJS to ESM -export var __toESM = (mod, isNodeMode) => { - return __reExport( - __markAsModule( - __defProp( - mod != null ? __create(__getProtoOf(mod)) : {}, - "default", - - // If the importer is not in node compatibility mode and this is an ESM - // file that has been converted to a CommonJS file using a Babel- - // compatible transform (i.e. "__esModule" has been set), then forward - // "default" to the export named "default". Otherwise set "default" to - // "module.exports" for node compatibility. - !isNodeMode && mod && mod.__esModule - ? { get: () => mod.default, enumerable: true } - : { value: mod, enumerable: true }, - ), - ), - mod, - ); -}; - export var __esm = (fn, res) => function __init() { return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])((fn = 0))), res; }; - -// Converts the module from ESM to CommonJS -export var __toCommonJS = /* @__PURE__ */ (cache => { - return (mod, temp) => { - return ( - (cache && cache.get(mod)) || - ((temp = __reExport(__markAsModule({}), mod, /* copyDefault */ 1)), cache && cache.set(mod, temp), temp) - ); - }; -})(typeof WeakMap !== "undefined" ? new WeakMap() : 0); diff --git a/src/string_immutable.zig b/src/string_immutable.zig index 4a8753ab7415..eb261b8e4f23 100644 --- a/src/string_immutable.zig +++ b/src/string_immutable.zig @@ -3845,7 +3845,7 @@ pub fn join(slices: []const string, delimiter: string, allocator: std.mem.Alloca pub fn order(a: []const u8, b: []const u8) std.math.Order { const len = @min(a.len, b.len); const cmp = bun.C.memcmp(a.ptr, b.ptr, len); - return switch (cmp) { + return switch (std.math.sign(cmp)) { 0 => std.math.order(a.len, b.len), 1 => .gt, -1 => .lt, diff --git a/src/util.zig b/src/util.zig index 615732106ad1..8e06df9a1e6a 100644 --- a/src/util.zig +++ b/src/util.zig @@ -1,5 +1,6 @@ // Things that maybe should go in Zig standard library at some point const std = @import("std"); +const bun = @import("bun"); pub fn Key(comptime Map: type) type { return FieldType(Map.KV, "key").?; @@ -170,6 +171,18 @@ pub inline fn from( return fromSlice(Array, allocator, []const Of(Array), @as([]const Of(Array), default)); } +pub fn concat( + comptime T: type, + dest: []T, + src: []const []const T, +) void { + var remain = dest; + for (src) |group| { + bun.copy(T, remain[0..group.len], group); + remain = remain[group.len..]; + } +} + pub fn fromSlice( comptime Array: type, allocator: std.mem.Allocator, From 48838f80fb51e9378631695c816234555078f49c Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Thu, 9 Mar 2023 22:11:45 -0800 Subject: [PATCH 069/255] Fix a lot of stuff --- src/bundler/bundle_v2.zig | 15 ++++- src/cli/bun_command.zig | 127 ++++++++++++++++++++------------------ src/js_parser.zig | 61 ++++++++++++++---- src/js_printer.zig | 90 ++++++++++++++++++--------- src/options.zig | 4 ++ src/renamer.zig | 17 +++++ src/runtime.js | 30 ++++----- 7 files changed, 228 insertions(+), 116 deletions(-) diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index fc6a9ffcd33c..99220679c8b8 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -2411,7 +2411,7 @@ const LinkerContext = struct { // parallel and can't safely mutate the "importsToBind" map of another file. if (flag.needs_exports_variable) { if (!runtime_export_symbol_ref.isValid()) { - runtime_export_symbol_ref = this.graph.ast.items(.module_scope)[Index.runtime.get()].members.get("__export").?.ref; + runtime_export_symbol_ref = this.runtimeFunction("__export"); } std.debug.assert(runtime_export_symbol_ref.isValid()); @@ -5822,7 +5822,13 @@ const LinkerContext = struct { part.is_live = true; if (comptime bun.Environment.allow_assert) - debug("markPartLiveForTreeShaking({d}): {s}:{d}", .{ id, c.parse_graph.input_files.get(id).source.path.text, part_index }); + debug("markPartLiveForTreeShaking({d}): {s}:{d} = {d}, {s}", .{ + id, + c.parse_graph.input_files.get(id).source.path.text, + part_index, + if (part.stmts.len > 0) part.stmts[0].loc.start else Logger.Loc.Empty.start, + if (part.stmts.len > 0) @tagName(part.stmts[0].data) else @tagName(Stmt.empty().data), + }); for (part.dependencies.slice()) |dependency| { const _id = dependency.source_index.get(); @@ -5874,6 +5880,9 @@ const LinkerContext = struct { break :loop; } } + + const prev_import_ref = tracker.import_ref; + const prev_source_index = tracker.source_index.get(); c.cycle_detector.append(tracker.*) catch unreachable; // Resolve the import by one step @@ -5896,7 +5905,7 @@ const LinkerContext = struct { // property access. Don't do this if the namespace reference is invalid // though. This is the case for star imports, where the import is the // namespace. - const named_import: js_ast.NamedImport = named_imports[other_id].get(tracker.import_ref).?; + const named_import: js_ast.NamedImport = named_imports[prev_source_index].get(prev_import_ref).?; if (named_import.namespace_ref != null and named_import.namespace_ref.?.isValid()) { if (result.kind == .normal) { diff --git a/src/cli/bun_command.zig b/src/cli/bun_command.zig index dff3836126b7..7861a564b289 100644 --- a/src/cli/bun_command.zig +++ b/src/cli/bun_command.zig @@ -191,77 +191,84 @@ pub const BunCommand = struct { ); { - defer Output.flush(); - var writer = Output.errorWriter(); + dump: { + defer Output.flush(); + var writer = Output.errorWriter(); - const root_path = ctx.args.output_dir orelse "out"; - const root_dir = try std.fs.cwd().makeOpenPathIterable(root_path, .{}); - var all_paths = try ctx.allocator.alloc([]const u8, output_files.items.len); - var max_path_len: usize = 0; - for (all_paths, output_files.items) |*dest, src| { - dest.* = src.input.text; - } + if ((ctx.args.output_dir orelse "").len == 0 and output_files.items.len == 1 and output_files.items[0].value == .buffer) { + try writer.writeAll(output_files.items[0].value.buffer); + break :dump; + } - var from_path = resolve_path.longestCommonPath(all_paths); + const root_path = ctx.args.output_dir orelse "out"; + const root_dir = try std.fs.cwd().makeOpenPathIterable(root_path, .{}); + var all_paths = try ctx.allocator.alloc([]const u8, output_files.items.len); + var max_path_len: usize = 0; + for (all_paths, output_files.items) |*dest, src| { + dest.* = src.input.text; + } - for (output_files.items) |f| { - max_path_len = std.math.max( - std.math.max(from_path.len, f.input.text.len) + 2 - from_path.len, - max_path_len, - ); - } + var from_path = resolve_path.longestCommonPath(all_paths); - // On posix, file handles automatically close on process exit by the OS - // Closing files shows up in profiling. - // So don't do that unless we actually need to. - // const do_we_need_to_close = !FeatureFlags.store_file_descriptors or (@intCast(usize, root_dir.fd) + open_file_limit) < output_files.items.len; + for (output_files.items) |f| { + max_path_len = std.math.max( + std.math.max(from_path.len, f.input.text.len) + 2 - from_path.len, + max_path_len, + ); + } + + // On posix, file handles automatically close on process exit by the OS + // Closing files shows up in profiling. + // So don't do that unless we actually need to. + // const do_we_need_to_close = !FeatureFlags.store_file_descriptors or (@intCast(usize, root_dir.fd) + open_file_limit) < output_files.items.len; - var filepath_buf: [bun.MAX_PATH_BYTES]u8 = undefined; - filepath_buf[0] = '.'; - filepath_buf[1] = '/'; + var filepath_buf: [bun.MAX_PATH_BYTES]u8 = undefined; + filepath_buf[0] = '.'; + filepath_buf[1] = '/'; - for (output_files.items) |f| { - var rel_path: []const u8 = undefined; - switch (f.value) { - // easy mode: write the buffer - .buffer => |value| { - rel_path = f.input.text; - if (f.input.text.len > from_path.len) { - rel_path = resolve_path.relative(from_path, f.input.text); - if (std.fs.path.dirname(rel_path)) |parent| { - if (parent.len > root_path.len) { - try root_dir.dir.makePath(parent); + for (output_files.items) |f| { + var rel_path: []const u8 = undefined; + switch (f.value) { + // easy mode: write the buffer + .buffer => |value| { + rel_path = f.input.text; + if (f.input.text.len > from_path.len) { + rel_path = resolve_path.relative(from_path, f.input.text); + if (std.fs.path.dirname(rel_path)) |parent| { + if (parent.len > root_path.len) { + try root_dir.dir.makePath(parent); + } } } - } - try root_dir.dir.writeFile(rel_path, value); - }, - .move => |value| { - // const primary = f.input.text[from_path.len..]; - // bun.copy(u8, filepath_buf[2..], primary); - // rel_path = filepath_buf[0 .. primary.len + 2]; - rel_path = value.pathname; + try root_dir.dir.writeFile(rel_path, value); + }, + .move => |value| { + // const primary = f.input.text[from_path.len..]; + // bun.copy(u8, filepath_buf[2..], primary); + // rel_path = filepath_buf[0 .. primary.len + 2]; + rel_path = value.pathname; - // try f.moveTo(result.outbase, constStrToU8(rel_path), root_dir.fd); - }, - .copy => |value| { - rel_path = value.pathname; + // try f.moveTo(result.outbase, constStrToU8(rel_path), root_dir.fd); + }, + .copy => |value| { + rel_path = value.pathname; - try f.copyTo(root_path, bun.constStrToU8(rel_path), root_dir.dir.fd); - }, - .noop => {}, - .pending => unreachable, - } + try f.copyTo(root_path, bun.constStrToU8(rel_path), root_dir.dir.fd); + }, + .noop => {}, + .pending => unreachable, + } - // Print summary - _ = try writer.write("\n"); - const padding_count = 2 + (std.math.max(rel_path.len, max_path_len) - rel_path.len); - try writer.writeByteNTimes(' ', 2); - try writer.writeAll(rel_path); - try writer.writeByteNTimes(' ', padding_count); - const size = @intToFloat(f64, f.size) / 1000.0; - try std.fmt.formatFloatDecimal(size, .{ .precision = 2 }, writer); - try writer.writeAll(" KB\n"); + // Print summary + _ = try writer.write("\n"); + const padding_count = 2 + (std.math.max(rel_path.len, max_path_len) - rel_path.len); + try writer.writeByteNTimes(' ', 2); + try writer.writeAll(rel_path); + try writer.writeByteNTimes(' ', padding_count); + const size = @intToFloat(f64, f.size) / 1000.0; + try std.fmt.formatFloatDecimal(size, .{ .precision = 2 }, writer); + try writer.writeAll(" KB\n"); + } } } diff --git a/src/js_parser.zig b/src/js_parser.zig index f389cdbcb4a9..6332e7ca07da 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -712,7 +712,42 @@ pub const ImportScanner = struct { }) catch unreachable; } } + + p.named_imports.ensureUnusedCapacity( + st.items.len + @as( + usize, + @boolToInt(st.default_name != null), + ), + ) catch unreachable; + + if (st.default_name) |default| { + p.named_imports.putAssumeCapacity( + default.ref.?, + .{ + .alias = "default", + .alias_loc = default.loc, + .namespace_ref = namespace_ref, + .import_record_index = st.import_record_index, + }, + ); + } + + for (st.items) |item| { + const name: LocRef = item.name; + const name_ref = name.ref.?; + + p.named_imports.putAssumeCapacity( + name_ref, + js_ast.NamedImport{ + .alias = item.alias, + .alias_loc = name.loc, + .namespace_ref = namespace_ref, + .import_record_index = st.import_record_index, + }, + ); + } } else { + // ESM requires live bindings // CommonJS does not require live bindings // We load ESM in browsers & in Bun.js @@ -743,22 +778,26 @@ pub const ImportScanner = struct { .was_originally_property_access = st.star_name_loc != null and existing_items.contains(symbol.original_name), }; } + + if (record.was_originally_require) { + var symbol = &p.symbols.items[namespace_ref.innerIndex()]; + symbol.namespace_alias = G.NamespaceAlias{ + .namespace_ref = namespace_ref, + .alias = "", + .import_record_index = st.import_record_index, + .was_originally_property_access = false, + }; + } } try p.import_records_for_current_part.append(allocator, st.import_record_index); - if (st.star_name_loc != null) { - record.contains_import_star = true; - } + record.contains_import_star = record.contains_import_star or st.star_name_loc != null; + record.contains_default_alias = record.contains_default_alias or st.default_name != null; - if (record.was_originally_require) { - var symbol = &p.symbols.items[namespace_ref.innerIndex()]; - symbol.namespace_alias = G.NamespaceAlias{ - .namespace_ref = namespace_ref, - .alias = "", - .import_record_index = st.import_record_index, - .was_originally_property_access = false, - }; + for (st.items) |*item| { + record.contains_default_alias = record.contains_default_alias or strings.eqlComptime(item.alias, "default"); + record.contains_es_module_alias = record.contains_es_module_alias or strings.eqlComptime(item.alias, "__esModule"); } }, diff --git a/src/js_printer.zig b/src/js_printer.zig index 3d1af7c3949d..6dae7217b6fc 100644 --- a/src/js_printer.zig +++ b/src/js_printer.zig @@ -493,6 +493,8 @@ pub const Options = struct { require_or_import_meta_for_source_callback: RequireOrImportMeta.Callback = .{}, + module_type: options.OutputFormat = .preserve, + // TODO: remove this // The reason for this is: // 1. You're bundling a React component @@ -1031,7 +1033,6 @@ fn NewPrinter( } } - // noop for now pub inline fn addSourceMapping(printer: *Printer, location: logger.Loc) void { if (comptime !generate_source_map) { return; @@ -1567,7 +1568,7 @@ fn NewPrinter( var meta = p.options.requireOrImportMetaForSource(record.source_index.get()); // Don't need the namespace object if the result is unused anyway - if (!flags.contains(.expr_result_is_unused)) { + if (flags.contains(.expr_result_is_unused)) { meta.exports_ref = Ref.None; } @@ -1597,7 +1598,7 @@ fn NewPrinter( // Make sure the comma operator is propertly wrapped if (meta.exports_ref.isValid() and level.gte(.comma)) { - p.print("()"); + p.print("("); } defer if (meta.exports_ref.isValid() and level.gte(.comma)) p.print(")"); @@ -1622,7 +1623,6 @@ fn NewPrinter( // Wrap this with a call to "__toCommonJS()" if this is an ESM file const wrap_with_to_cjs = record.wrap_with_to_commonjs; if (wrap_with_to_cjs) { - p.printSpaceBeforeIdentifier(); p.printSymbol(p.options.to_commonjs_ref); p.print("("); } @@ -1633,13 +1633,11 @@ fn NewPrinter( } if (wrap_with_to_esm) { - // Finish the call to "__toESM()" - // TODO: - // if (p.module_type.isESM()) { - // p.print(","); - // p.printSpace(); - // p.print("1"); - // } + if (p.options.module_type.isESM()) { + p.print(","); + p.printSpace(); + p.print("1"); + } p.print(")"); } @@ -2457,34 +2455,70 @@ fn NewPrinter( p.printUndefined(level); didPrint = true; } else if (symbol.namespace_alias) |namespace| { - const import_record = p.importRecord(namespace.import_record_index); - if ((comptime is_inside_bundle) or import_record.is_bundled or namespace.was_originally_property_access) { - var wrap = false; - didPrint = true; + if (namespace.import_record_index < p.import_records.len) { + const import_record = p.importRecord(namespace.import_record_index); + if ((comptime is_inside_bundle) or import_record.is_bundled or 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.addSourceMapping(expr.loc); + p.printNamespaceAlias(import_record.*, namespace); + + if (wrap) { + p.print(")"); + } + } else if (import_record.was_originally_require and import_record.path.is_disabled) { + p.addSourceMapping(expr.loc); - if (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 (import_record.handles_import_errors) { + p.printRequireError(import_record.path.text); + } else { + p.printDisabledImport(); + } + didPrint = true; } + } + + if (!didPrint) { + didPrint = true; + + const wrap = if (p.call_target) |target| + e.was_originally_identifier and (target == .e_identifier and + target.e_identifier.ref.eql(expr.data.e_import_identifier.ref)) + else + false; if (wrap) { p.printWhitespacer(ws("(0, ")); } + + p.printSpaceBeforeIdentifier(); p.addSourceMapping(expr.loc); - p.printNamespaceAlias(import_record.*, namespace); + p.printSymbol(namespace.namespace_ref); + const alias = namespace.alias; + if (p.canPrintIdentifier(alias)) { + p.print("."); + // TODO: addSourceMappingForName + p.printIdentifier(alias); + } else { + p.print("["); + // TODO: addSourceMappingForName + // p.addSourceMappingForName(alias); + p.printQuotedUTF8(alias, true); + p.print("]"); + } if (wrap) { p.print(")"); } - } else if (import_record.was_originally_require and import_record.path.is_disabled) { - p.addSourceMapping(expr.loc); - - if (import_record.handles_import_errors) { - p.printRequireError(import_record.path.text); - } else { - p.printDisabledImport(); - } - didPrint = true; } } } diff --git a/src/options.zig b/src/options.zig index 81f4ac604b91..2e9c93f589c5 100644 --- a/src/options.zig +++ b/src/options.zig @@ -1198,6 +1198,10 @@ pub const OutputFormat = enum { pub fn keepES6ImportExportSyntax(this: OutputFormat) bool { return this == .esm; } + + pub inline fn isESM(this: OutputFormat) bool { + return this == .esm; + } }; /// BundleOptions is used when ResolveMode is not set to "disable". diff --git a/src/renamer.zig b/src/renamer.zig index f2db3e59935d..b72a4b93b637 100644 --- a/src/renamer.zig +++ b/src/renamer.zig @@ -22,6 +22,8 @@ pub const NoOpRenamer = struct { return NoOpRenamer{ .symbols = symbols, .source = source }; } + pub const originalName = nameForSymbol; + pub fn nameForSymbol(renamer: *NoOpRenamer, ref: Ref) string { if (ref.isSourceContentsSlice()) { return renamer.source.contents[ref.sourceIndex() .. ref.sourceIndex() + ref.innerIndex()]; @@ -59,6 +61,12 @@ pub const Renamer = union(enum) { }; } + pub fn originalName(renamer: Renamer, ref: Ref) ?string { + return switch (renamer) { + inline else => |r| r.originalName(ref), + }; + } + pub fn deinit(renamer: Renamer) void { switch (renamer) { .NumberRenamer => |r| r.deinit(), @@ -85,6 +93,15 @@ pub const NumberRenamer = struct { }; } + pub fn originalName(r: *NumberRenamer, ref: Ref) string { + if (ref.isSourceContentsSlice()) { + unreachable; + } + + const resolved = r.symbols.follow(ref); + return r.symbols.getConst(resolved).?.original_name; + } + pub fn assignName(r: *NumberRenamer, scope: *NumberScope, input_ref: Ref) void { const ref = r.symbols.follow(input_ref); diff --git a/src/runtime.js b/src/runtime.js index d58203ee76ec..eac6cd65e8a5 100644 --- a/src/runtime.js +++ b/src/runtime.js @@ -8,7 +8,7 @@ var __hasOwnProp = Object.prototype.hasOwnProperty; var __getOwnPropNames = Object.getOwnPropertyNames; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; -export var __copyProps = (to, from, except, desc) => { +var __copyProps = (to, from, except, desc) => { if ((from && typeof from === "object") || typeof from === "function") for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) @@ -27,25 +27,30 @@ export var __markAsModule = target => __defProp(target, "__esModule", { value: t // current module is an entry point and the target format is CommonJS, we // also copy the properties to "module.exports" in addition to our module's // internal ESM export object. -export var __reExport = (target, mod, secondTarget) => ( - __copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default") -); +export var __reExport = (target, mod, secondTarget) => { + __copyProps(target, mod, "default"); + return secondTarget && __copyProps(secondTarget, mod, "default"); +}; // Converts the module from CommonJS to ESM. When in node mode (i.e. in an // ".mjs" file, package.json has "type: module", or the "__esModule" export // in the CommonJS file is falsy or missing), the "default" property is // overridden to point to the original CommonJS exports object instead. -export var __toESM = (mod, isNodeMode, target) => ( - (target = mod != null ? __create(__getProtoOf(mod)) : {}), - __copyProps( +export var __toESM = (mod, isNodeMode, target) => { + target = mod != null ? __create(__getProtoOf(mod)) : {}; + const mode = + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target; + return __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. - isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mode, mod, - ) -); + void 0, + void 0, + ); +}; // Converts the module from ESM to CommonJS. This clones the input module // object with the addition of a non-enumerable "__esModule" property set @@ -234,7 +239,4 @@ export var __decorateClass = (decorators, target, key, kind) => { export var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index); -export var __esm = (fn, res) => - function __init() { - return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])((fn = 0))), res; - }; +export var __esm = (fn, res) => () => (fn && (res = fn((fn = 0))), res); From 4dd8f1aaf68b97472612b47b57d1dfaa9867b3ad Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Thu, 9 Mar 2023 22:37:06 -0800 Subject: [PATCH 070/255] Fix `module.exports.fn = fn;` in ESM entry point --- src/bundler/bundle_v2.zig | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 99220679c8b8..56a0e2d0f2d3 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -4171,19 +4171,14 @@ const LinkerContext = struct { .{ .default_name = .{ .loc = Logger.Loc.Empty, + .ref = ast.wrapper_ref.?, }, .value = .{ - .stmt = Stmt.alloc( - S.SExpr, - .{ - .value = Expr.init( - E.Call, - E.Call{ - .target = Expr.initIdentifier( - ast.wrapper_ref.?, - Logger.Loc.Empty, - ), - }, + .expr = Expr.init( + E.Call, + E.Call{ + .target = Expr.initIdentifier( + ast.wrapper_ref.?, Logger.Loc.Empty, ), }, From 733ff12a0b0f0087b6cacdf482b441965b08b6c9 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Thu, 9 Mar 2023 22:37:19 -0800 Subject: [PATCH 071/255] Fix crash due when printing file --- src/bundler/bundle_v2.zig | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 56a0e2d0f2d3..762ac0456879 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -2332,18 +2332,14 @@ const LinkerContext = struct { var count: usize = 0; if (is_entry_point and this.options.output_format == .esm) { for (aliases) |alias| { - count += std.fmt.count("{}", .{strings.fmtIdentifier(alias)}); + count += std.fmt.count("export_{}", .{strings.fmtIdentifier(alias)}); } - count *= "export_".len; } - var ident_fmt_len: usize = 0; - if (wrap == .esm or (wrap != .cjs and export_kind != .cjs)) { - ident_fmt_len += if (source.identifier_name.len > 0) - source.identifier_name.len - else - std.fmt.count("{}", .{source.fmtIdentifier()}); - } + const ident_fmt_len: usize = if (source.identifier_name.len > 0) + source.identifier_name.len + else + std.fmt.count("{}", .{source.fmtIdentifier()}); if (wrap == .esm) { count += "init_".len + ident_fmt_len; @@ -2396,9 +2392,9 @@ const LinkerContext = struct { // aesthetics and is not about correctness. This is done here because by // this point, we know the CommonJS status will not change further. if (wrap != .cjs and export_kind != .cjs) { - const exports_name = bufPrint(buf, "exports_{s}", .{source.fmtIdentifier()}) catch unreachable; + const exports_name = bufPrint(buf, "exports_{any}", .{source.fmtIdentifier()}) catch unreachable; buf = buf[exports_name.len..]; - const module_name = bufPrint(buf, "module_{s}", .{source.fmtIdentifier()}) catch unreachable; + const module_name = bufPrint(buf, "module_{any}", .{source.fmtIdentifier()}) catch unreachable; buf = buf[module_name.len..]; if (exports_symbol != null) exports_symbol.?.original_name = exports_name; @@ -4467,7 +4463,6 @@ const LinkerContext = struct { .allocator = allocator, .to_esm_ref = toESMRef, .to_commonjs_ref = toCommonJSRef, - // .require_ref = toCommonJSRef, .require_or_import_meta_for_source_callback = js_printer.RequireOrImportMeta.Callback.init(LinkerContext, requireOrImportMetaForSource, c), .minify_whitespace = c.options.minify_whitespace, From b39795096896073a3489b88c220f996c8a1cc80b Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Thu, 9 Mar 2023 23:53:48 -0800 Subject: [PATCH 072/255] Fix issue with class names --- src/bundler/bundle_v2.zig | 34 ++++++++++++++++++---------------- src/js_parser.zig | 38 ++++++++++++++++++++------------------ 2 files changed, 38 insertions(+), 34 deletions(-) diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 762ac0456879..c8c9839f1770 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -2803,26 +2803,29 @@ const LinkerContext = struct { // Exports of imports need EImportIdentifier in case they need to be re- // written to a property access later on // note: this is stack allocated - var value: js_ast.Expr = undefined; - if (c.graph.symbols.getConst(export_.data.import_ref).?.namespace_alias != null) { - value = js_ast.Expr.init( - js_ast.E.ImportIdentifier, - js_ast.E.ImportIdentifier{ - .ref = export_.data.import_ref, - }, - loc, - ); - } else { - value = js_ast.Expr.init( + const value: js_ast.Expr = brk: { + if (c.graph.symbols.getConst(export_.data.import_ref)) |symbol| { + if (symbol.namespace_alias != null) { + break :brk js_ast.Expr.init( + js_ast.E.ImportIdentifier, + js_ast.E.ImportIdentifier{ + .ref = export_.data.import_ref, + }, + loc, + ); + } + } + + break :brk js_ast.Expr.init( js_ast.E.Identifier, js_ast.E.Identifier{ .ref = export_.data.import_ref, }, loc, ); - } + }; - var block = stmts.eat1( + const block = stmts.eat1( js_ast.Stmt.allocate(allocator_, js_ast.S.Block, .{ .stmts = stmts.eat1( js_ast.Stmt.allocate( @@ -5163,8 +5166,7 @@ const LinkerContext = struct { ) js_printer.PrintResult { // var file = &c.graph.files.items(.input_file)[part.source_index.get()]; var parts: []js_ast.Part = c.graph.ast.items(.parts)[part_range.source_index.get()].slice()[part_range.part_index_begin..part_range.part_index_end]; - const resolved_exports: []ResolvedExports = c.graph.meta.items(.resolved_exports); - _ = resolved_exports; + // const resolved_exports: []ResolvedExports = c.graph.meta.items(.resolved_exports); const all_flags: []const JSMeta.Flags = c.graph.meta.items(.flags); const flags = all_flags[part_range.source_index.get()]; const wrapper_part_index = c.graph.meta.items(.wrapper_part_index)[part_range.source_index.get()]; @@ -5427,7 +5429,7 @@ const LinkerContext = struct { stmt.loc, ); }, - .s_function => { + .s_class, .s_function => { stmts.outside_wrapper_prefix.append(stmt) catch unreachable; continue; }, diff --git a/src/js_parser.zig b/src/js_parser.zig index 6332e7ca07da..9822879e19e1 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -17298,7 +17298,7 @@ fn NewParser_( } } - if (!prop.flags.contains(.is_method) and prop.key.?.data != .e_private_identifier and prop.ts_decorators.len > 0) { + if (prop.kind != .class_static_block and !prop.flags.contains(.is_method) and prop.key.?.data != .e_private_identifier and prop.ts_decorators.len > 0) { // remove decorated fields without initializers to avoid assigning undefined. const initializer = if (prop.initializer) |initializer_value| initializer_value else continue; @@ -17721,20 +17721,20 @@ fn NewParser_( var class_name_ref: Ref = if (class.class_name != null) class.class_name.?.ref.? else - p.newSymbol(.other, "this") catch unreachable; + Ref.None; var shadow_ref = Ref.None; if (!class_name_ref.eql(Ref.None)) { // are not allowed to assign to this symbol (it throws a TypeError). - const name = p.symbols.items[class_name_ref.innerIndex()].original_name; - var identifier = p.allocator.alloc(u8, name.len + 1) catch unreachable; - bun.copy(u8, identifier[1..identifier.len], name); - identifier[0] = '_'; - shadow_ref = p.newSymbol(Symbol.Kind.cconst, identifier) catch unreachable; - p.recordDeclaredSymbol(shadow_ref) catch unreachable; + // const name = p.symbols.items[class_name_ref.innerIndex()].original_name; + // var identifier = p.allocator.alloc(u8, name.len + 1) catch unreachable; + // bun.copy(u8, identifier[1..identifier.len], name); + // identifier[0] = '_'; + // shadow_ref = p.newSymbol(Symbol.Kind.cconst, identifier) catch unreachable; + // p.recordDeclaredSymbol(shadow_ref) catch unreachable; if (class.class_name) |class_name| { - p.current_scope.members.put(p.allocator, identifier, Scope.Member{ .loc = class_name.loc, .ref = shadow_ref }) catch unreachable; + p.current_scope.members.put(p.allocator, p.loadNameFromRef(class_name.ref.?), Scope.Member{ .loc = class_name.loc, .ref = class_name.ref.? }) catch unreachable; } } @@ -17908,15 +17908,17 @@ fn NewParser_( } } - if (!shadow_ref.eql(Ref.None)) { - if (p.symbols.items[shadow_ref.innerIndex()].use_count_estimate == 0) { - // Don't generate a shadowing name if one isn't needed - shadow_ref = Ref.None; - } else if (class.class_name) |_| { - // If there was originally no class name but something inside needed one - // (e.g. there was a static property initializer that referenced "this"), - // store our generated name so the class expression ends up with a name. - class.class_name = LocRef{ .loc = name_scope_loc, .ref = class_name_ref }; + if (p.symbols.items[shadow_ref.innerIndex()].use_count_estimate == 0) { + // Don't generate a shadowing name if one isn't needed + shadow_ref = class_name_ref; + } else if (class.class_name == null) { + // If there was originally no class name but something inside needed one + // (e.g. there was a static property initializer that referenced "this"), + // store our generated name so the class expression ends up with a name. + class.class_name = LocRef{ .loc = name_scope_loc, .ref = class_name_ref }; + if (class_name_ref.isNull()) { + class_name_ref = p.newSymbol(.other, "_this") catch unreachable; + class.class_name.?.ref = class_name_ref; p.current_scope.generated.push(p.allocator, class_name_ref) catch unreachable; p.recordDeclaredSymbol(class_name_ref) catch unreachable; } From ff2cf5b805d133ceccdb6733dca0e869818f8e6d Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Fri, 10 Mar 2023 00:27:45 -0800 Subject: [PATCH 073/255] Fix issue with `export default identifier` --- src/js_parser.zig | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/js_parser.zig b/src/js_parser.zig index 9822879e19e1..8f7a50be2dbf 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -878,6 +878,7 @@ pub const ImportScanner = struct { defer { if (st.default_name.ref) |ref| { p.recordExport(st.default_name.loc, "default", ref) catch {}; + p.recordUsage(ref); } } @@ -15927,8 +15928,10 @@ fn NewParser_( } }, .s_export_default => |data| { - if (data.default_name.ref) |ref| { - try p.recordDeclaredSymbol(ref); + defer { + if (data.default_name.ref) |ref| { + p.recordDeclaredSymbol(ref) catch unreachable; + } } var mark_for_replace: bool = false; @@ -15996,6 +15999,10 @@ fn NewParser_( stmts.append(p.s(S.SExpr{ .value = p.callRuntime(expr.loc, "__exportDefault", export_default_args) }, expr.loc)) catch unreachable; return; } + + if (data.default_name.ref.?.isSourceContentsSlice()) { + data.default_name = createDefaultName(p, data.value.expr.loc) catch unreachable; + } }, .stmt => |s2| { @@ -16050,6 +16057,10 @@ fn NewParser_( return; } + if (data.default_name.ref.?.isSourceContentsSlice()) { + data.default_name = createDefaultName(p, stmt.loc) catch unreachable; + } + stmts.append(stmt.*) catch unreachable; // if (func.func.name != null and func.func.name.?.ref != null) { @@ -16105,6 +16116,10 @@ fn NewParser_( return; } + if (data.default_name.ref.?.isSourceContentsSlice()) { + data.default_name = createDefaultName(p, stmt.loc) catch unreachable; + } + stmts.append(stmt.*) catch unreachable; return; }, From 0e0e2e7bf4b63515389dccca95581781d6deeae7 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Fri, 10 Mar 2023 00:29:07 -0800 Subject: [PATCH 074/255] Update js_parser.zig --- src/js_parser.zig | 1 - 1 file changed, 1 deletion(-) diff --git a/src/js_parser.zig b/src/js_parser.zig index 8f7a50be2dbf..8f4a7e121089 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -878,7 +878,6 @@ pub const ImportScanner = struct { defer { if (st.default_name.ref) |ref| { p.recordExport(st.default_name.loc, "default", ref) catch {}; - p.recordUsage(ref); } } From 983f3c5722e51216d72fcc979bfdaed92fba9828 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Fri, 10 Mar 2023 03:40:49 -0800 Subject: [PATCH 075/255] optimization: inline single-property object acceses and arrays --- src/feature_flags.zig | 2 ++ src/js_parser.zig | 63 ++++++++++++++++++++++++++++++++++--------- 2 files changed, 53 insertions(+), 12 deletions(-) diff --git a/src/feature_flags.zig b/src/feature_flags.zig index f626b725b47d..2f942fb41ff6 100644 --- a/src/feature_flags.zig +++ b/src/feature_flags.zig @@ -113,3 +113,5 @@ pub const hardcode_localhost_to_127_0_0_1 = true; pub const support_jsxs_in_jsx_transform = false; pub const use_simdutf = !@import("bun").JSC.is_bindgen; + +pub const inline_properties_in_transpiler = true; diff --git a/src/js_parser.zig b/src/js_parser.zig index 8f4a7e121089..de3cb5509208 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -13399,6 +13399,7 @@ fn NewParser_( return p.handleIdentifier(expr.loc, e_, original_name, IdentifierOpts{ .assign_target = in.assign_target, .is_delete_target = is_delete_target, + .is_call_target = @as(Expr.Tag, p.call_target) == .e_identifier and expr.data.e_identifier.ref.eql(p.call_target.e_identifier.ref), .was_originally_identifier = true, }); }, @@ -14329,6 +14330,23 @@ fn NewParser_( if (literal.len > index) { return p.newExpr(E.String{ .data = literal[index .. index + 1] }, expr.loc); } + } else if ((comptime FeatureFlags.inline_properties_in_transpiler) and + // Input: + // + // [123][0] + // + // Output: + // + // 123 + in.assign_target == .none and + !is_delete_target and + e_.optional_chain == null and + target.data == .e_array and + e_.index.data == .e_number and + target.data.e_array.items.len == 1 and + e_.index.data.e_number.value == 0.0) + { + return target.data.e_array.items.ptr[0]; } // Create an error for assigning to an import namespace when bundling. Even // though this is a run-time error, we make it a compile-time error when @@ -14623,10 +14641,7 @@ fn NewParser_( var has_spread = false; var has_proto = false; - var i: usize = 0; - while (i < e_.properties.len) : (i += 1) { - var property = e_.properties.ptr[i]; - + for (e_.properties.slice()) |*property| { if (property.kind != .spread) { property.key = p.visitExpr(property.key orelse Global.panic("Expected property key", .{})); const key = property.key.?; @@ -14682,8 +14697,6 @@ fn NewParser_( } } } - - e_.properties.ptr[i] = property; } }, .e_import => |e_| { @@ -15712,6 +15725,33 @@ fn NewParser_( return p.newExpr(E.Number{ .value = @intToFloat(f64, str.len()) }, loc); } }, + .e_object => |obj| { + if (comptime FeatureFlags.inline_properties_in_transpiler) { + // + // Rewrite a property access like this: + // { f: () => {} }.f + // To: + // () => {} + // + // To avoid thinking too much about edgecases, only do this for: + // 1) Objects with a single property + // 2) Not a method, not a computed property + if (obj.properties.len == 1 and + !identifier_opts.is_delete_target and + identifier_opts.assign_target == .none) + { + const prop: G.Property = obj.properties.ptr[0]; + if (prop.value != null and + prop.flags.count() == 0 and + prop.key != null and + prop.key.?.data == .e_string and + prop.key.?.data.e_string.eql([]const u8, name)) + { + return prop.value.?; + } + } + } + }, else => {}, } @@ -19076,17 +19116,16 @@ fn NewParser_( if (p.options.bundle) { // Each part tracks the other parts it depends on within this file - var i: u32 = 0; - const count = @truncate(u32, parts.len); - while (i < count) : (i += 1) { - var decls = &parts[i].declared_symbols; + for (parts, 0..) |*part, i| { var ctx_ = .{ .allocator = p.allocator, .top_level_symbols_to_parts = &top_level_symbols_to_parts, .symbols = p.symbols.items, - .part_index = i, + .part_index = @truncate(u32, i), }; const Ctx = @TypeOf(ctx_); + var decls = &part.declared_symbols; + const Iterator = struct { pub fn next(ctx: Ctx, input: Ref) void { // If this symbol was merged, use the symbol at the end of the @@ -19094,7 +19133,7 @@ fn NewParser_( // declarations with the same name, for example. var ref = input; var symbol_ref = &ctx.symbols[ref.innerIndex()]; - while (symbol_ref.link.isValid()) : (symbol_ref = &ctx.symbols[ref.innerIndex()]) { + while (symbol_ref.hasLink()) : (symbol_ref = &ctx.symbols[ref.innerIndex()]) { ref = symbol_ref.link; } From c83a9a0e239ebb0ce058a0e68fbeed26245964fc Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Fri, 10 Mar 2023 04:50:19 -0800 Subject: [PATCH 076/255] Fix undefined memory in renamed symbols list --- src/renamer.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renamer.zig b/src/renamer.zig index b72a4b93b637..8971108303e5 100644 --- a/src/renamer.zig +++ b/src/renamer.zig @@ -125,8 +125,8 @@ pub const NumberRenamer = struct { const new_len = @max(inner.len, ref.innerIndex() + 1); if (inner.cap < new_len) { - const prev_cap = inner.cap; - inner.ensureUnusedCapacity(r.allocator, new_len) catch unreachable; + const prev_cap = inner.len; + inner.ensureUnusedCapacity(r.allocator, new_len - prev_cap) catch unreachable; const to_write = inner.ptr[prev_cap..inner.cap]; @memset(std.mem.sliceAsBytes(to_write).ptr, 0, std.mem.sliceAsBytes(to_write).len); } From 86c00e237c29fe49866a2a2f254be2099cd21aa6 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Fri, 10 Mar 2023 05:04:50 -0800 Subject: [PATCH 077/255] Handle call target --- src/js_parser.zig | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/js_parser.zig b/src/js_parser.zig index de3cb5509208..9a40962e9a06 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -14324,7 +14324,13 @@ fn NewParser_( } } // "foo"[2] - } else if (e_.optional_chain == null and target.data == .e_string and e_.index.data == .e_number and target.data.e_string.isUTF8() and e_.index.data.e_number.value >= 0) { + } else if ((comptime FeatureFlags.inline_properties_in_transpiler) and + e_.optional_chain == null and + target.data == .e_string and + e_.index.data == .e_number and + target.data.e_string.isUTF8() and + e_.index.data.e_number.value >= 0) + { const literal = target.data.e_string.slice(p.allocator); const index = e_.index.data.e_number.toUsize(); if (literal.len > index) { @@ -14340,11 +14346,13 @@ fn NewParser_( // 123 in.assign_target == .none and !is_delete_target and - e_.optional_chain == null and + !is_call_target and + // target should already be on the stack target.data == .e_array and - e_.index.data == .e_number and target.data.e_array.items.len == 1 and - e_.index.data.e_number.value == 0.0) + e_.index.data == .e_number and + e_.index.data.e_number.value == 0.0 and + e_.optional_chain == null) { return target.data.e_array.items.ptr[0]; } @@ -15738,7 +15746,7 @@ fn NewParser_( // 2) Not a method, not a computed property if (obj.properties.len == 1 and !identifier_opts.is_delete_target and - identifier_opts.assign_target == .none) + identifier_opts.assign_target == .none and !identifier_opts.is_call_target) { const prop: G.Property = obj.properties.ptr[0]; if (prop.value != null and From 7158c48a6983f3e9f4bc5e31ca52830fa9aa7649 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Fri, 10 Mar 2023 05:21:01 -0800 Subject: [PATCH 078/255] wip --- src/bundler/bundle_v2.zig | 84 +++++++++++++++++++++------------------ 1 file changed, 46 insertions(+), 38 deletions(-) diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index c8c9839f1770..400fb6c32a4f 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -275,7 +275,7 @@ pub const BundleV2 = struct { } if (comptime Environment.allow_assert) { - Output.prettyErrorln("Reachable count: {d} / {d}", .{ visitor.reachable.items.len, this.graph.input_files.len }); + Output.prettyln("Reachable count: {d} / {d}", .{ visitor.reachable.items.len, this.graph.input_files.len }); } return visitor.reachable.toOwnedSlice(); @@ -1315,6 +1315,10 @@ const LinkerGraph = struct { return LinkerGraph{ .allocator = allocator, .files_live = try bun.bit_set.DynamicBitSetUnmanaged.initEmpty(allocator, file_count) }; } + pub fn runtimeFunction(this: *const LinkerGraph, name: string) Ref { + return this.ast.items(.named_exports)[Index.runtime.value].get(name).?.ref; + } + pub fn generateNewSymbol(this: *LinkerGraph, source_index: u32, kind: Symbol.Kind, original_name: string) Ref { var source_symbols = &this.symbols.symbols_for_source.slice()[source_index]; @@ -1346,7 +1350,7 @@ const LinkerGraph = struct { ) !void { if (count > 0) debug("generateRuntimeSymbolImportAndUse({s}) for {d}", .{ name, source_index }); - const ref = graph.ast.items(.module_scope)[Index.runtime.get()].members.get(name).?.ref; + const ref = graph.runtimeFunction(name); try graph.generateSymbolImportAndUse( source_index, entry_point_part_index.get(), @@ -1408,7 +1412,7 @@ const LinkerGraph = struct { } pub fn generateSymbolImportAndUse( g: *LinkerGraph, - id: u32, + source_index: u32, part_index: u32, ref: Ref, use_count: u32, @@ -1417,32 +1421,33 @@ const LinkerGraph = struct { if (use_count == 0) return; // Mark this symbol as used by this part - var parts_list = g.ast.items(.parts)[id].slice(); + var parts_list = g.ast.items(.parts)[source_index].slice(); var part: *js_ast.Part = &parts_list[part_index]; - - var uses = part.symbol_uses.getOrPut(g.allocator, ref) catch unreachable; - if (uses.found_existing) { - uses.value_ptr.count_estimate += use_count; + var uses = part.symbol_uses; + var entry = uses.getOrPut(g.allocator, ref) catch unreachable; + if (entry.found_existing) { + entry.value_ptr.count_estimate += use_count; } else { - uses.value_ptr.* = .{ .count_estimate = use_count }; + entry.value_ptr.* = .{ .count_estimate = use_count }; } + part.symbol_uses = uses; - const exports_ref = g.ast.items(.exports_ref)[id]; - const module_ref = g.ast.items(.module_ref)[id].?; + const exports_ref = g.ast.items(.exports_ref)[source_index]; + const module_ref = g.ast.items(.module_ref)[source_index].?; if (ref.eql(exports_ref)) { - g.ast.items(.uses_exports_ref)[id] = true; + g.ast.items(.uses_exports_ref)[source_index] = true; } if (ref.eql(module_ref)) { - g.ast.items(.uses_module_ref)[id] = true; + g.ast.items(.uses_module_ref)[source_index] = true; } // null ref shouldn't be there. std.debug.assert(!ref.isEmpty()); // Track that this specific symbol was imported - if (source_index_to_import_from.get() != id) { - try g.meta.items(.imports_to_bind)[id].put(g.allocator, ref, .{ + if (source_index_to_import_from.get() != source_index) { + try g.meta.items(.imports_to_bind)[source_index].put(g.allocator, ref, .{ .data = .{ .source_index = source_index_to_import_from, .import_ref = ref, @@ -2429,30 +2434,30 @@ const LinkerContext = struct { const import_id = import_source_index; const ref = import_ref.*; - const named_import = named_imports[id].get(ref) orelse continue; + if (named_imports[id].get(ref)) |named_import| { + for (named_import.local_parts_with_uses.slice()) |part_index| { + var part: *js_ast.Part = &parts[part_index]; + const parts_declaring_symbol = this.topLevelSymbolsToParts(import_id, import.data.import_ref); - for (named_import.local_parts_with_uses.slice()) |part_index| { - var part: *js_ast.Part = &parts[part_index]; - const parts_declaring_symbol = this.topLevelSymbolsToParts(import_id, import.data.import_ref); + part.dependencies.ensureUnusedCapacity( + this.allocator, + parts_declaring_symbol.len + @as(usize, import.re_exports.len), + ) catch unreachable; - part.dependencies.ensureUnusedCapacity( - this.allocator, - parts_declaring_symbol.len + @as(usize, import.re_exports.len), - ) catch unreachable; + // Depend on the file containing the imported symbol + for (parts_declaring_symbol) |resolved_part_index| { + part.dependencies.appendAssumeCapacity( + .{ + .source_index = Index.source(import_source_index), + .part_index = resolved_part_index, + }, + ); + } - // Depend on the file containing the imported symbol - for (parts_declaring_symbol) |resolved_part_index| { - part.dependencies.appendAssumeCapacity( - .{ - .source_index = Index.source(import_source_index), - .part_index = resolved_part_index, - }, - ); + // Also depend on any files that re-exported this symbol in between the + // file containing the import and the file containing the imported symbol + part.dependencies.appendSliceAssumeCapacity(import.re_exports.slice()); } - - // Also depend on any files that re-exported this symbol in between the - // file containing the import and the file containing the imported symbol - part.dependencies.appendSliceAssumeCapacity(import.re_exports.slice()); } // Merge these symbols so they will share the same name @@ -3064,7 +3069,8 @@ const LinkerContext = struct { defer local_dependencies.deinit(); var parts = &c.graph.ast.items(.parts)[id]; var parts_slice: []js_ast.Part = parts.slice(); - var named_imports: *js_ast.Ast.NamedImports = &c.graph.ast.items(.named_imports)[id]; + var named_imports: js_ast.Ast.NamedImports = c.graph.ast.items(.named_imports)[id]; + defer c.graph.ast.items(.named_imports)[id] = named_imports; for (parts_slice, 0..) |*part, part_index| { // TODO: inline const TypeScript enum here @@ -3073,6 +3079,8 @@ const LinkerContext = struct { // note: if we crash on append, it is due to threadlocal heaps in mimalloc const symbol_uses = part.symbol_uses.keys(); + + // Now that we know this, we can determine cross-part dependencies for (symbol_uses, 0..) |ref, j| { if (comptime Environment.allow_assert) { std.debug.assert(part.symbol_uses.values()[j].count_estimate > 0); @@ -5148,7 +5156,7 @@ const LinkerContext = struct { } fn runtimeFunction(c: *LinkerContext, name: []const u8) Ref { - return c.graph.ast.items(.module_scope)[Index.runtime.value].members.get(name).?.ref; + return c.graph.runtimeFunction(name); } fn generateCodeForFileInChunkJS( @@ -6349,7 +6357,7 @@ const LinkerContext = struct { source_index: Index.Int, ) void { var named_imports = named_imports_ptr.cloneWithAllocator(c.allocator) catch unreachable; - defer named_imports.deinit(); + defer named_imports_ptr.* = named_imports; const Sorter = struct { imports: *JSAst.NamedImports, From 7bd3647083a21c5493aae9bfd1eed42a267704e0 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Fri, 10 Mar 2023 06:11:53 -0800 Subject: [PATCH 079/255] Inline it --- src/runtime.js | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/runtime.js b/src/runtime.js index eac6cd65e8a5..adc3e6c203eb 100644 --- a/src/runtime.js +++ b/src/runtime.js @@ -38,18 +38,21 @@ export var __reExport = (target, mod, secondTarget) => { // overridden to point to the original CommonJS exports object instead. export var __toESM = (mod, isNodeMode, target) => { target = mod != null ? __create(__getProtoOf(mod)) : {}; - const mode = + const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target; - return __copyProps( - // If the importer is in node compatibility mode or this is not an ESM - // file that has been converted to a CommonJS file using a Babel- - // compatible transform (i.e. "__esModule" has not been set), then set - // "default" to the CommonJS "module.exports" for node compatibility. - mode, - mod, - void 0, - void 0, - ); + + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + for (let key of __getOwnPropNames(mod)) + if (!__hasOwnProp.call(to, key)) + __defProp(to, key, { + get: () => mod[key], + enumerable: true, + }); + + return to; }; // Converts the module from ESM to CommonJS. This clones the input module From 324ecbb65446c6bb7a5b33b3f53eaa8708cdb8cd Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Sat, 11 Mar 2023 21:21:03 -0800 Subject: [PATCH 080/255] Fix undefined memory issue when reclaiming blocks in ast --- src/js_ast.zig | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/js_ast.zig b/src/js_ast.zig index 00ad78b26277..00d2673a4bdc 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -111,19 +111,30 @@ pub fn NewBaseStore(comptime Union: anytype, comptime count: usize) type { pub fn reclaim() []*Block { if (_self.overflow.used == 0) return &[_]*Block{}; - var used = _self.overflow.allocator.dupe(*Block, _self.overflow.slice()) catch unreachable; - var new_head = _self.overflow.allocator.create(Block) catch unreachable; - new_head.* = .{}; - var to_move = _self.overflow.ptrs[0.._self.overflow.allocated][_self.overflow.used..]; - if (to_move.len > 0) { + + // This returns the list of maxed out blocks + var used_list = _self.overflow.slice(); + + // The last block may be partially used. + if (_self.overflow.allocated > _self.overflow.used and to_move.len > 0 and to_move.ptr[0].used > 0) { to_move = to_move[1..]; + used_list.len += 1; + } + + var used = _self.overflow.allocator.dupe(*Block, used_list) catch unreachable; + + for (to_move, _self.overflow.ptrs[0..to_move.len]) |b, *out| { + b.* = Block{ + .items = undefined, + .used = 0, + }; + out.* = b; } - bun.copy(*Block, _self.overflow.ptrs[1..], to_move); - _self.overflow.ptrs[0] = new_head; - _self.overflow.allocated = 1 + @truncate(Overflow.UsedSize, to_move.len); - reset(); + _self.overflow.allocated = @truncate(Overflow.UsedSize, to_move.len); + _self.overflow.used = 0; + return used; } From 7e1605afeebe1a6eff6ef35161860b949598b1d3 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Sat, 11 Mar 2023 21:21:28 -0800 Subject: [PATCH 081/255] Halt linking on any parse errors --- src/bundler/bundle_v2.zig | 9 +++++++-- src/cli.zig | 4 ++-- src/cli/bun_command.zig | 13 +++++++++++-- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 400fb6c32a4f..5d80c79b7107 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -525,6 +525,10 @@ pub const BundleV2 = struct { this.graph.pool.pool.schedule(batch); this.waitForParse(); + if (this.bundler.log.errors > 0) { + return error.BuildFailed; + } + this.linker.allocator = this.bundler.allocator; this.linker.graph.allocator = this.bundler.allocator; this.linker.graph.ast = try this.graph.ast.clone(this.linker.allocator); @@ -3789,14 +3793,15 @@ const LinkerContext = struct { const toESMRef = c.graph.symbols.follow(runtime_members.get("__toESM").?.ref); const runtimeRequireRef = c.graph.symbols.follow(runtime_members.get("__require").?.ref); + js_ast.Expr.Data.Store.create(bun.default_allocator); + js_ast.Stmt.Data.Store.create(bun.default_allocator); + var r = try c.renameSymbolsInChunk(allocator, chunk, repr.files_in_chunk_order); defer r.deinit(); const part_ranges = repr.parts_in_chunk_in_order; var stmts = StmtList.init(allocator); defer stmts.deinit(); - js_ast.Expr.Data.Store.create(allocator); - js_ast.Stmt.Data.Store.create(allocator); var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); var compile_results = std.ArrayList(CompileResult).initCapacity(allocator, part_ranges.len) catch unreachable; diff --git a/src/cli.zig b/src/cli.zig index 3b05b6eb75d0..8c3f484b11ee 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -467,7 +467,7 @@ pub const Arguments = struct { var output_dir: ?string = null; const production = false; - if (cmd == .BuildCommand) { + if (cmd == .BuildCommand or cmd == .BunCommand) { if (args.option("--outdir")) |outdir| { if (outdir.len > 0) { output_dir = outdir; @@ -1455,7 +1455,7 @@ pub const Command = struct { pub fn params(comptime cmd: Tag) []const Arguments.ParamType { return &comptime switch (cmd) { - Command.Tag.BuildCommand => Arguments.build_params, + Command.Tag.BunCommand, Command.Tag.BuildCommand => Arguments.build_params, else => Arguments.params, }; } diff --git a/src/cli/bun_command.zig b/src/cli/bun_command.zig index 7861a564b289..1f4c6509993d 100644 --- a/src/cli/bun_command.zig +++ b/src/cli/bun_command.zig @@ -178,7 +178,7 @@ pub const BunCommand = struct { // Always generate the client-only bundle // we can revisit this decision if people ask - const output_files = try BundleV2.generate( + const output_files = BundleV2.generate( &this_bundler, allocator, loaded_framework, @@ -188,7 +188,16 @@ pub const BunCommand = struct { ctx.debug.package_bundle_map, bun.JSC.AnyEventLoop.init(ctx.allocator), std.crypto.random.int(u64), - ); + ) catch |err| { + if (log.msgs.items.len > 0) { + try log.printForLogLevel(Output.errorWriter()); + } else { + try Output.errorWriter().print("error: {s}", .{@errorName(err)}); + } + + Output.flush(); + Global.exit(1); + }; { dump: { From f9273aa44a2f32306e76ab1f4dfeb612df9f9936 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Sat, 11 Mar 2023 21:33:28 -0800 Subject: [PATCH 082/255] alias --- src/js_ast.zig | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/js_ast.zig b/src/js_ast.zig index 00d2673a4bdc..a918ed010ab0 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -109,22 +109,28 @@ pub fn NewBaseStore(comptime Union: anytype, comptime count: usize) type { pub threadlocal var _self: *Self = undefined; pub fn reclaim() []*Block { - if (_self.overflow.used == 0) return &[_]*Block{}; + var overflow = &_self.overflow; + + if (overflow.used == 0) { + if (overflow.allocated == 0 or overflow.ptrs[0].used == 0) { + return &.{}; + } + } + + var to_move = overflow.ptrs[0..overflow.allocated][overflow.used..]; - var to_move = _self.overflow.ptrs[0.._self.overflow.allocated][_self.overflow.used..]; - // This returns the list of maxed out blocks - var used_list = _self.overflow.slice(); + var used_list = overflow.slice(); // The last block may be partially used. - if (_self.overflow.allocated > _self.overflow.used and to_move.len > 0 and to_move.ptr[0].used > 0) { + if (overflow.allocated > overflow.used and to_move.len > 0 and to_move.ptr[0].used > 0) { to_move = to_move[1..]; used_list.len += 1; } - var used = _self.overflow.allocator.dupe(*Block, used_list) catch unreachable; + var used = overflow.allocator.dupe(*Block, used_list) catch unreachable; - for (to_move, _self.overflow.ptrs[0..to_move.len]) |b, *out| { + for (to_move, overflow.ptrs[0..to_move.len]) |b, *out| { b.* = Block{ .items = undefined, .used = 0, @@ -132,8 +138,8 @@ pub fn NewBaseStore(comptime Union: anytype, comptime count: usize) type { out.* = b; } - _self.overflow.allocated = @truncate(Overflow.UsedSize, to_move.len); - _self.overflow.used = 0; + overflow.allocated = @truncate(Overflow.UsedSize, to_move.len); + overflow.used = 0; return used; } From 597faf66c7b31d7b2d62ce2408385cef74662ac3 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Sun, 12 Mar 2023 00:44:26 -0800 Subject: [PATCH 083/255] Rename `enable_bundling` to `enable_legacy_bundling` --- src/bun.js/module_loader.zig | 2 +- src/bundler.zig | 2 +- src/bundler/generate_node_modules_bundle.zig | 2 +- src/js_parser.zig | 26 ++++++++++---------- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/bun.js/module_loader.zig b/src/bun.js/module_loader.zig index 2024d40313ce..966a72bc0b28 100644 --- a/src/bun.js/module_loader.zig +++ b/src/bun.js/module_loader.zig @@ -1579,7 +1579,7 @@ pub const ModuleLoader = struct { var jsx = bundler.options.jsx; jsx.parse = false; var opts = js_parser.Parser.Options.init(jsx, .js); - opts.enable_bundling = false; + opts.enable_legacy_bundling = false; opts.transform_require_to_import = false; opts.features.dynamic_require = true; opts.can_import_from_bundle = bundler.options.node_modules_bundle != null; diff --git a/src/bundler.zig b/src/bundler.zig index f384febdac7b..485d5bb918c0 100644 --- a/src/bundler.zig +++ b/src/bundler.zig @@ -1369,7 +1369,7 @@ pub const Bundler = struct { jsx.parse = loader.isJSX(); var opts = js_parser.Parser.Options.init(jsx, loader); - opts.enable_bundling = false; + opts.enable_legacy_bundling = false; opts.transform_require_to_import = bundler.options.allow_runtime and !bundler.options.platform.isBun(); opts.features.allow_runtime = bundler.options.allow_runtime; opts.features.trim_unused_imports = bundler.options.trim_unused_imports orelse loader.isTypeScript(); diff --git a/src/bundler/generate_node_modules_bundle.zig b/src/bundler/generate_node_modules_bundle.zig index 81a14c101922..7dc2cb945ba4 100644 --- a/src/bundler/generate_node_modules_bundle.zig +++ b/src/bundler/generate_node_modules_bundle.zig @@ -1310,7 +1310,7 @@ pub fn processFile(this: *GenerateNodeModuleBundle, worker: *ThreadPool.Worker, opts.transform_require_to_import = false; opts.features.dynamic_require = bundler.options.platform.isBun(); - opts.enable_bundling = true; + opts.enable_legacy_bundling = true; opts.warn_about_unbundled_modules = false; opts.macro_context = &worker.data.macro_context; opts.features.auto_import_jsx = jsx.parse; diff --git a/src/js_parser.zig b/src/js_parser.zig index 9a40962e9a06..3c39836420c9 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -654,7 +654,7 @@ pub const ImportScanner = struct { } const namespace_ref = st.namespace_ref; - const convert_star_to_clause = !p.options.bundle or (!p.options.enable_bundling and !p.options.can_import_from_bundle and p.symbols.items[namespace_ref.innerIndex()].use_count_estimate == 0); + const convert_star_to_clause = !p.options.bundle or (!p.options.enable_legacy_bundling and !p.options.can_import_from_bundle and p.symbols.items[namespace_ref.innerIndex()].use_count_estimate == 0); if (convert_star_to_clause and !keep_unused_imports) { st.star_name_loc = null; @@ -2345,7 +2345,7 @@ pub const Parser = struct { warn_about_unbundled_modules: bool = true, // Used when bundling node_modules - enable_bundling: bool = false, + enable_legacy_bundling: bool = false, transform_require_to_import: bool = true, moduleType: ModuleType = ModuleType.esm, @@ -2638,7 +2638,7 @@ pub const Parser = struct { } else if (uses_exports_ref or uses_module_ref or p.has_top_level_return) { exports_kind = .cjs; if (!p.options.bundle) { - if (p.options.transform_require_to_import or (p.options.features.dynamic_require and !p.options.enable_bundling)) { + if (p.options.transform_require_to_import or (p.options.features.dynamic_require and !p.options.enable_legacy_bundling)) { var args = p.allocator.alloc(Expr, 2) catch unreachable; if (p.runtime_imports.__exportDefault == null and p.has_export_default) { @@ -2890,7 +2890,7 @@ pub const Parser = struct { // We do not mark this as .require becuase we are already wrapping it manually. // unless it's bun and you're not bundling - const use_automatic_identifier = (p.options.can_import_from_bundle or p.options.enable_bundling or !p.options.features.allow_runtime); + const use_automatic_identifier = (p.options.can_import_from_bundle or p.options.enable_legacy_bundling or !p.options.features.allow_runtime); const import_record_kind = if (use_automatic_identifier) ImportKind.internal else ImportKind.require; const import_record_id = p.addImportRecord(import_record_kind, loc, p.options.jsx.import_source); @@ -3230,7 +3230,7 @@ pub const Parser = struct { if (!p.options.jsx.use_embedded_refresh_runtime) { if (comptime Environment.allow_assert) - assert(!p.options.enable_bundling); + assert(!p.options.enable_legacy_bundling); var declared_symbols = DeclaredSymbol.List{}; try declared_symbols.ensureTotalCapacity(p.allocator, 1); const loc = logger.Loc.Empty; @@ -3275,7 +3275,7 @@ pub const Parser = struct { else => {}, } - if (p.options.enable_bundling) p.resolveBundlingSymbols(); + if (p.options.enable_legacy_bundling) p.resolveBundlingSymbols(); } var runtime_imports_iter = p.runtime_imports.iter(); @@ -3306,7 +3306,7 @@ pub const Parser = struct { // - when HMR is enabled, we always need to import the runtime for HMRClient and HMRModule. // - when HMR is not enabled, we only need any runtime imports if we're importing require() if (p.options.features.allow_runtime and - !p.options.enable_bundling and + !p.options.enable_legacy_bundling and (p.has_called_runtime or p.options.features.hot_module_reloading or has_cjs_imports)) { const before_start = before.items.len; @@ -5259,7 +5259,7 @@ fn NewParser_( var generated_symbols_count: u32 = 3; - if (p.options.enable_bundling) { + if (p.options.enable_legacy_bundling) { generated_symbols_count += 4; } @@ -16039,7 +16039,7 @@ fn NewParser_( } // When bundling, replace ExportDefault with __exportDefault(exportsRef, expr); - if (p.options.enable_bundling) { + if (p.options.enable_legacy_bundling) { var export_default_args = p.allocator.alloc(Expr, 2) catch unreachable; export_default_args[0] = p.@"module.exports"(expr.loc); export_default_args[1] = data.value.expr; @@ -16080,14 +16080,14 @@ fn NewParser_( } // When bundling, replace ExportDefault with __exportDefault(exportsRef, expr); - if (p.options.enable_bundling) { + if (p.options.enable_legacy_bundling) { var export_default_args = p.allocator.alloc(Expr, 2) catch unreachable; export_default_args[0] = p.@"module.exports"(data.value.expr.loc); export_default_args[1] = data.value.expr; stmts.append(p.s(S.SExpr{ .value = p.callRuntime(data.value.expr.loc, "__exportDefault", export_default_args) }, data.value.expr.loc)) catch unreachable; return; } - } else if (p.options.enable_bundling) { + } else if (p.options.enable_legacy_bundling) { var export_default_args = p.allocator.alloc(Expr, 2) catch unreachable; export_default_args[0] = p.@"module.exports"(s2.loc); @@ -16132,14 +16132,14 @@ fn NewParser_( } // When bundling, replace ExportDefault with __exportDefault(exportsRef, expr); - if (p.options.enable_bundling) { + if (p.options.enable_legacy_bundling) { var export_default_args = p.allocator.alloc(Expr, 2) catch unreachable; export_default_args[0] = p.@"module.exports"(data.value.expr.loc); export_default_args[1] = data.value.expr; stmts.append(p.s(S.SExpr{ .value = p.callRuntime(data.value.expr.loc, "__exportDefault", export_default_args) }, data.value.expr.loc)) catch unreachable; return; } - } else if (p.options.enable_bundling) { + } else if (p.options.enable_legacy_bundling) { var export_default_args = p.allocator.alloc(Expr, 2) catch unreachable; export_default_args[0] = p.@"module.exports"(s2.loc); From 7ae0cdbdd1cd4a27ec1049865cbfde675eaaede4 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Sun, 12 Mar 2023 00:45:21 -0800 Subject: [PATCH 084/255] Workaround anonymous struct literal zig bug --- src/js_ast.zig | 2 +- src/js_parser.zig | 142 ++++++++++++++++++++++++---------------------- 2 files changed, 74 insertions(+), 70 deletions(-) diff --git a/src/js_ast.zig b/src/js_ast.zig index a918ed010ab0..15ed73ebaa72 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -5312,7 +5312,7 @@ pub const DeclaredSymbol = struct { } pub fn appendListAssumeCapacity(this: *List, other: List) void { - var j = other.refs().len; + const j = other.refs().len; this.refs_list.appendSliceAssumeCapacity(other.refs()); var is_top_level = &this.is_top_level; var iter = other.is_top_level.iterator(.{}); diff --git a/src/js_parser.zig b/src/js_parser.zig index 3c39836420c9..23550ac4eaa4 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -2198,9 +2198,10 @@ const FnOnlyDataVisit = struct { this_capture_ref: ?Ref = null, arguments_capture_ref: ?Ref = null, - // Inside a static class property initializer, "this" expressions should be - // replaced with the class name. - this_class_static_ref: ?Ref = null, + /// This is a reference to the enclosing class name if there is one. It's used + /// to implement "this" and "super" references. A name is automatically generated + /// if one is missing so this will always be present inside a class body. + class_name_ref: ?*Ref = null, // If we're inside an async arrow function and async functions are not // supported, then we will have to convert that arrow function to a generator @@ -3004,7 +3005,7 @@ pub const Parser = struct { const dot_call_target = brk: { // var react = $aopaSD123(); - if (p.options.can_import_from_bundle or p.options.enable_bundling or !p.options.features.allow_runtime) { + if (p.options.can_import_from_bundle or p.options.enable_legacy_bundling or !p.options.features.allow_runtime) { break :brk classic_identifier; } else if (p.options.features.dynamic_require) { break :brk p.newExpr(E.Require{ .import_record_index = import_record_id }, loc); @@ -5297,7 +5298,7 @@ fn NewParser_( p.jest.afterAll = try p.declareCommonJSSymbol(.unbound, "afterAll"); } - if (p.options.enable_bundling) { + if (p.options.enable_legacy_bundling) { p.runtime_imports.@"$$m" = try p.declareGeneratedSymbol(.other, "$$m"); p.runtime_imports.@"$$lzy" = try p.declareGeneratedSymbol(.other, "$$lzy"); @@ -13174,6 +13175,7 @@ fn NewParser_( } fn recordDeclaredSymbol(p: *P, ref: Ref) !void { + std.debug.assert(ref.isSymbol()); try p.declared_symbols.append(p.allocator, DeclaredSymbol{ .ref = ref, .is_top_level = p.current_scope == p.module_scope, @@ -13243,9 +13245,9 @@ fn NewParser_( fn valueForThis(p: *P, loc: logger.Loc) ?Expr { // Substitute "this" if we're inside a static class property initializer - if (p.fn_only_data_visit.this_class_static_ref) |ref| { - p.recordUsage(ref); - return p.newExpr(E.Identifier{ .ref = ref }, loc); + if (p.fn_only_data_visit.class_name_ref) |ref| { + p.recordUsage(ref.*); + return p.newExpr(E.Identifier{ .ref = ref.* }, loc); } // oroigianlly was !=- modepassthrough @@ -14974,8 +14976,7 @@ fn NewParser_( return expr; } - // This might be wrong. - _ = p.visitClass(expr.loc, e_); + _ = p.visitClass(expr.loc, e_, Ref.None); }, else => {}, } @@ -16117,7 +16118,7 @@ fn NewParser_( return; }, .s_class => |class| { - _ = p.visitClass(s2.loc, &class.class); + _ = p.visitClass(s2.loc, &class.class, data.default_name.ref.?); if (p.is_control_flow_dead) return; @@ -16176,7 +16177,7 @@ fn NewParser_( } }, .s_export_equals => |data| { - if (p.options.enable_bundling) { + if (p.options.enable_legacy_bundling) { var export_default_args = p.allocator.alloc(Expr, 2) catch unreachable; export_default_args[0] = p.@"module.exports"(stmt.loc); export_default_args[1] = data.value; @@ -16625,7 +16626,7 @@ fn NewParser_( } } - _ = p.visitClass(stmt.loc, &data.class); + _ = p.visitClass(stmt.loc, &data.class, Ref.None); // Remove the export flag inside a namespace const was_export_inside_namespace = data.is_export and p.enclosing_namespace_arg_ref != null; @@ -17765,7 +17766,7 @@ fn NewParser_( return res; } - fn visitClass(p: *P, name_scope_loc: logger.Loc, class: *G.Class) Ref { + fn visitClass(p: *P, name_scope_loc: logger.Loc, class: *G.Class, default_name_ref: Ref) Ref { if (only_scan_imports_and_do_not_visit) { @compileError("only_scan_imports_and_do_not_visit must not run this."); } @@ -17780,25 +17781,22 @@ fn NewParser_( const old_enclosing_class_keyword = p.enclosing_class_keyword; p.enclosing_class_keyword = class.class_keyword; p.current_scope.recursiveSetStrictMode(.implicit_strict_mode_class); - var class_name_ref: Ref = if (class.class_name != null) - class.class_name.?.ref.? - else - Ref.None; - var shadow_ref = Ref.None; - if (!class_name_ref.eql(Ref.None)) { - // are not allowed to assign to this symbol (it throws a TypeError). - // const name = p.symbols.items[class_name_ref.innerIndex()].original_name; - // var identifier = p.allocator.alloc(u8, name.len + 1) catch unreachable; - // bun.copy(u8, identifier[1..identifier.len], name); - // identifier[0] = '_'; - // shadow_ref = p.newSymbol(Symbol.Kind.cconst, identifier) catch unreachable; - // p.recordDeclaredSymbol(shadow_ref) catch unreachable; - if (class.class_name) |class_name| { - p.current_scope.members.put(p.allocator, p.loadNameFromRef(class_name.ref.?), Scope.Member{ .loc = class_name.loc, .ref = class_name.ref.? }) catch unreachable; - } + // Insert a shadowing name that spans the whole class, which matches + // JavaScript's semantics. The class body (and extends clause) "captures" the + // original value of the name. This matters for class statements because the + // symbol can be re-assigned to something else later. The captured values + // must be the original value of the name, not the re-assigned value. + // Use "const" for this symbol to match JavaScript run-time semantics. You + // are not allowed to assign to this symbol (it throws a TypeError). + if (class.class_name) |name| { + shadow_ref = name.ref.?; + } else { + const name_str: []const u8 = if (default_name_ref.isNull()) "_this" else "_default"; + shadow_ref = p.newSymbol(.cconst, name_str) catch unreachable; } + p.recordDeclaredSymbol(shadow_ref) catch unreachable; if (class.extends) |extends| { class.extends = p.visitExpr(extends); @@ -17820,7 +17818,7 @@ fn NewParser_( var old_fn_or_arrow_data = p.fn_or_arrow_data_visit; var old_fn_only_data = p.fn_only_data_visit; p.fn_or_arrow_data_visit = .{}; - p.fn_only_data_visit = .{ .is_this_nested = true, .is_new_target_allowed = true }; + p.fn_only_data_visit = .{ .is_this_nested = true, .is_new_target_allowed = true, .class_name_ref = &shadow_ref }; p.pushScopeForVisitPass(.class_static_init, property.class_static_block.?.loc) catch unreachable; @@ -17854,12 +17852,12 @@ fn NewParser_( // The value of "this" is shadowed inside property values const old_is_this_captured = p.fn_only_data_visit.is_this_nested; - const old_this = p.fn_only_data_visit.this_class_static_ref; + const old_class_name_ref = p.fn_only_data_visit.class_name_ref; p.fn_only_data_visit.is_this_nested = true; p.fn_only_data_visit.is_new_target_allowed = true; - p.fn_only_data_visit.this_class_static_ref = null; + p.fn_only_data_visit.class_name_ref = &shadow_ref; defer p.fn_only_data_visit.is_this_nested = old_is_this_captured; - defer p.fn_only_data_visit.this_class_static_ref = old_this; + defer p.fn_only_data_visit.class_name_ref = old_class_name_ref; // We need to explicitly assign the name to the property initializer if it // will be transformed such that it is no longer an inline initializer. @@ -17971,19 +17969,22 @@ fn NewParser_( } if (p.symbols.items[shadow_ref.innerIndex()].use_count_estimate == 0) { - // Don't generate a shadowing name if one isn't needed - shadow_ref = class_name_ref; - } else if (class.class_name == null) { // If there was originally no class name but something inside needed one // (e.g. there was a static property initializer that referenced "this"), // store our generated name so the class expression ends up with a name. - class.class_name = LocRef{ .loc = name_scope_loc, .ref = class_name_ref }; - if (class_name_ref.isNull()) { + shadow_ref = Ref.None; + } else if (class.class_name == null) { + var class_name_ref = default_name_ref; + if (default_name_ref.isNull()) { class_name_ref = p.newSymbol(.other, "_this") catch unreachable; - class.class_name.?.ref = class_name_ref; p.current_scope.generated.push(p.allocator, class_name_ref) catch unreachable; p.recordDeclaredSymbol(class_name_ref) catch unreachable; } + + class.class_name = LocRef{ + .ref = class_name_ref, + .loc = name_scope_loc, + }; } // class name scope @@ -18548,11 +18549,11 @@ fn NewParser_( var kept_import_equals = false; var removed_import_equals = false; - var i: usize = parts_end; + const begin = parts_end; // Potentially remove some statements, then filter out parts to remove any // with no statements - while (i < parts.len) : (i += 1) { - var part = parts[i]; + for (parts[begin..]) |part_| { + var part = part_; p.import_records_for_current_part.clearRetainingCapacity(); p.declared_symbols.clearRetainingCapacity(); @@ -19122,39 +19123,42 @@ fn NewParser_( }; if (p.options.bundle) { + const Ctx = struct { + allocator: std.mem.Allocator, + top_level_symbols_to_parts: *js_ast.Ast.TopLevelSymbolToParts, + symbols: []const js_ast.Symbol, + part_index: u32, + + pub fn next(ctx: @This(), input: Ref) void { + // If this symbol was merged, use the symbol at the end of the + // linked list in the map. This is the case for multiple "var" + // declarations with the same name, for example. + var ref = input; + var symbol_ref = &ctx.symbols[ref.innerIndex()]; + while (symbol_ref.hasLink()) : (symbol_ref = &ctx.symbols[ref.innerIndex()]) { + ref = symbol_ref.link; + } + + var entry = ctx.top_level_symbols_to_parts.getOrPut(ctx.allocator, ref) catch unreachable; + if (!entry.found_existing) { + entry.value_ptr.* = .{}; + } + + entry.value_ptr.push(ctx.allocator, @truncate(u32, ctx.part_index)) catch unreachable; + } + }; // Each part tracks the other parts it depends on within this file - for (parts, 0..) |*part, i| { - var ctx_ = .{ + for (parts, 0..) |*part, part_index| { + var decls = &part.declared_symbols; + const ctx = Ctx{ .allocator = p.allocator, .top_level_symbols_to_parts = &top_level_symbols_to_parts, .symbols = p.symbols.items, - .part_index = @truncate(u32, i), - }; - const Ctx = @TypeOf(ctx_); - var decls = &part.declared_symbols; - - const Iterator = struct { - pub fn next(ctx: Ctx, input: Ref) void { - // If this symbol was merged, use the symbol at the end of the - // linked list in the map. This is the case for multiple "var" - // declarations with the same name, for example. - var ref = input; - var symbol_ref = &ctx.symbols[ref.innerIndex()]; - while (symbol_ref.hasLink()) : (symbol_ref = &ctx.symbols[ref.innerIndex()]) { - ref = symbol_ref.link; - } - - var entry = ctx.top_level_symbols_to_parts.getOrPut(ctx.allocator, ref) catch unreachable; - if (!entry.found_existing) { - entry.value_ptr.* = .{}; - } - - entry.value_ptr.push(ctx.allocator, @truncate(u32, ctx.part_index)) catch unreachable; - } + .part_index = @truncate(u32, part_index), }; - DeclaredSymbol.forEachTopLevelSymbol(decls, ctx_, Iterator.next); + DeclaredSymbol.forEachTopLevelSymbol(decls, ctx, Ctx.next); } // Pulling in the exports of this module always pulls in the export part From a11501575e70c1819d23521a1863d9dd53f3028a Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Sun, 12 Mar 2023 03:55:00 -0700 Subject: [PATCH 085/255] Use slower approach (without bitset) because it doesn't break after 8 symbols --- src/js_ast.zig | 93 +++++++++++++++----------------------------------- 1 file changed, 27 insertions(+), 66 deletions(-) diff --git a/src/js_ast.zig b/src/js_ast.zig index 15ed73ebaa72..de0214c7ae51 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -5256,49 +5256,25 @@ pub const DeclaredSymbol = struct { is_top_level: bool = false, pub const List = struct { - refs_list: std.ArrayListUnmanaged(Ref) = .{}, - is_top_level: bun.bit_set.DynamicBitSetUnmanaged = .{}, + entries: bun.MultiArrayList(DeclaredSymbol) = .{}, pub fn refs(this: *const List) []Ref { - return this.refs_list.items; + return this.entries.items(.ref); } pub fn toOwnedSlice(this: *List) List { var new = this.*; - if (this.is_top_level.bit_length > this.refs_list.items.len) - new.is_top_level.setRangeValue( - .{ - .start = this.refs_list.items.len, - .end = this.is_top_level.bit_length, - }, - false, - ); this.* = .{}; return new; } pub fn clone(this: *const List, allocator: std.mem.Allocator) !List { - var refs_list = try this.refs_list.clone(allocator); - errdefer refs_list.deinit(allocator); - var top = try this.is_top_level.clone(allocator); - if (top.bit_length > this.refs_list.items.len) - top.setRangeValue( - .{ - .start = this.refs_list.items.len, - .end = top.bit_length, - }, - false, - ); - - return List{ - .refs_list = refs_list, - .is_top_level = top, - }; + return List{ .entries = try this.entries.clone(allocator) }; } pub inline fn len(this: List) usize { - return this.refs().len; + return this.entries.len; } pub fn append(this: *List, allocator: std.mem.Allocator, entry: DeclaredSymbol) !void { @@ -5312,54 +5288,33 @@ pub const DeclaredSymbol = struct { } pub fn appendListAssumeCapacity(this: *List, other: List) void { - const j = other.refs().len; - this.refs_list.appendSliceAssumeCapacity(other.refs()); - var is_top_level = &this.is_top_level; - var iter = other.is_top_level.iterator(.{}); - - // TODO: optimize this - while (iter.next()) |i| { - is_top_level.set(j + i); - } + this.entries.appendListAssumeCapacity(other.entries); } pub fn appendAssumeCapacity(this: *List, entry: DeclaredSymbol) void { - this.refs_list.appendAssumeCapacity(entry.ref); - this.is_top_level.setValue(this.refs().len - 1, entry.is_top_level); + this.entries.appendAssumeCapacity(entry); } pub fn ensureTotalCapacity(this: *List, allocator: std.mem.Allocator, count: usize) !void { - try this.ensureUnusedCapacity(allocator, this.refs_list.capacity -| this.refs().len +| count); + try this.entries.ensureTotalCapacity(allocator, count); } pub fn ensureUnusedCapacity(this: *List, allocator: std.mem.Allocator, count: usize) !void { - try this.refs_list.ensureUnusedCapacity(allocator, count); - if (this.is_top_level.capacity() < this.refs_list.capacity) { - var prev = this.is_top_level; - this.is_top_level = try bun.bit_set.DynamicBitSetUnmanaged.initEmpty(allocator, this.refs_list.capacity); - prev.copyInto(this.is_top_level); - prev.deinit(allocator); - } + try this.entries.ensureUnusedCapacity(allocator, count); } pub fn clearRetainingCapacity(this: *List) void { - this.refs_list.clearRetainingCapacity(); - this.is_top_level.setAll(false); + this.entries.clearRetainingCapacity(); } pub fn deinit(this: *List, allocator: std.mem.Allocator) void { - this.refs_list.deinit(allocator); - this.is_top_level.deinit(allocator); + this.entries.deinit(allocator); } pub fn initCapacity(allocator: std.mem.Allocator, capacity: usize) !List { - var refs_list = try std.ArrayListUnmanaged(Ref).initCapacity(allocator, capacity); - errdefer refs_list.deinit(allocator); - var is_top_level = try bun.bit_set.DynamicBitSetUnmanaged.initEmpty(allocator, refs_list.capacity); - return List{ - .refs_list = refs_list, - .is_top_level = is_top_level, - }; + var entries = bun.MultiArrayList(DeclaredSymbol){}; + try entries.ensureUnusedCapacity(allocator, capacity); + return List{ .entries = entries }; } pub fn fromSlice(allocator: std.mem.Allocator, entries: []const DeclaredSymbol) !List { @@ -5373,16 +5328,22 @@ pub const DeclaredSymbol = struct { } }; - pub fn forEachTopLevelSymbol(decls: *List, ctx: anytype, comptime Fn: anytype) void { - var refs = decls.refs(); - std.debug.assert(refs.len <= decls.is_top_level.capacity()); - var iter = decls.is_top_level.iterator(.{ .direction = .forward }); - while (iter.next()) |index| { - std.debug.assert(index < refs.len); - const ref = refs[index]; - Fn(ctx, ref); + fn forEachTopLevelSymbolWithType(decls: *List, comptime Ctx: type, ctx: Ctx, comptime Fn: fn (Ctx, Ref) void) void { + var entries = decls.entries.slice(); + const is_top_level = entries.items(.is_top_level); + const refs = entries.items(.ref); + + // TODO: SIMD + for (is_top_level, refs) |top, ref| { + if (top) { + @call(.always_inline, Fn, .{ ctx, ref }); + } } } + + pub fn forEachTopLevelSymbol(decls: *List, ctx: anytype, comptime Fn: anytype) void { + forEachTopLevelSymbolWithType(decls, @TypeOf(ctx), ctx, Fn); + } }; pub const Dependency = struct { From 06135510599fe94b9243768d66de88e3d4a7b8fc Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Sun, 12 Mar 2023 03:59:34 -0700 Subject: [PATCH 086/255] Fix incorrectly-renaming statically defined symbols --- src/bundler/bundle_v2.zig | 36 +++++++++++------- src/js_parser.zig | 77 +++++++++++++++++++++++++++++---------- src/multi_array_list.zig | 8 +++- 3 files changed, 86 insertions(+), 35 deletions(-) diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 5d80c79b7107..e4c531125bfd 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -1370,20 +1370,17 @@ const LinkerGraph = struct { part: js_ast.Part, ) !u32 { var parts: *js_ast.Part.List = &graph.ast.items(.parts)[id]; - const part_id = parts.len; + const part_id = @truncate(u32, parts.len); try parts.push(graph.allocator, part); var top_level_symbol_to_parts_overlay: ?*TopLevelSymbolToParts = null; - var ctx = .{ - .graph = graph, - .id = id, - .part_id = part_id, - .top_level_symbol_to_parts_overlay = &top_level_symbol_to_parts_overlay, - }; - const Ctx = @TypeOf(ctx); - const Iterator = struct { - pub fn next(self: *Ctx, ref: Ref) void { + graph: *LinkerGraph, + id: u32, + top_level_symbol_to_parts_overlay: *?*TopLevelSymbolToParts, + part_id: u32, + + pub fn next(self: *@This(), ref: Ref) void { var overlay = brk: { if (self.top_level_symbol_to_parts_overlay.*) |out| { break :brk out; @@ -1410,6 +1407,13 @@ const LinkerGraph = struct { } }; + var ctx = Iterator{ + .graph = graph, + .id = id, + .part_id = part_id, + .top_level_symbol_to_parts_overlay = &top_level_symbol_to_parts_overlay, + }; + js_ast.DeclaredSymbol.forEachTopLevelSymbol(&parts.ptr[part_id].declared_symbols, &ctx, Iterator.next); return part_id; @@ -1475,11 +1479,15 @@ const LinkerGraph = struct { } pub fn topLevelSymbolToParts(g: *LinkerGraph, id: u32, ref: Ref) []u32 { - var list: BabyList(u32) = g.meta.items(.top_level_symbol_to_parts_overlay)[id].get(ref) orelse - g.ast.items(.top_level_symbols_to_parts)[id].get(ref) orelse - return &.{}; + if (g.meta.items(.top_level_symbol_to_parts_overlay)[id].get(ref)) |overlay| { + return overlay.slice(); + } + + if (g.ast.items(.top_level_symbols_to_parts)[id].get(ref)) |list| { + return list.slice(); + } - return list.slice(); + return &.{}; } pub fn load(this: *LinkerGraph, entry_points: []const Index, sources: []const Logger.Source) !void { diff --git a/src/js_parser.zig b/src/js_parser.zig index 23550ac4eaa4..2257bdc5c422 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -47,6 +47,7 @@ fn _disabledAssert(_: bool) void { } const assert = if (Environment.allow_assert) std.debug.assert else _disabledAssert; +const debug = Output.scoped(.JSParser, false); const ExprListLoc = struct { list: ExprNodeList, loc: logger.Loc, @@ -3736,6 +3737,8 @@ fn NewParser_( allow_in: bool = false, allow_private_identifiers: bool = false, + is_dot_define_match: bool = false, + has_top_level_return: bool = false, latest_return_had_semicolon: bool = false, has_import_meta: bool = false, @@ -5914,10 +5917,14 @@ fn NewParser_( // function foo(): void; // function foo(): void {} // - if (name) |*name_| { - const kind = if (is_generator or is_async) Symbol.Kind.generator_or_async_function else Symbol.Kind.hoisted_function; - name_.ref = try p.declareSymbol(kind, name_.loc, nameText); - func.name = name_.*; + if (name != null) { + const kind = if (is_generator or is_async) + Symbol.Kind.generator_or_async_function + else + Symbol.Kind.hoisted_function; + + name.?.ref = try p.declareSymbol(kind, name.?.loc, nameText); + func.name = name; } func.flags.setPresent(.has_if_scope, hasIfScope); @@ -13323,6 +13330,7 @@ fn NewParser_( if (p.define.dots.get("meta")) |meta| { for (meta) |define| { + // TODO: clean up how we do define matches if (p.isDotDefineMatch(expr, define.parts)) { // Substitute user-specified defines return p.valueForDefine(expr.loc, in.assign_target, is_delete_target, &define.data); @@ -17560,24 +17568,33 @@ fn NewParser_( }; } - // This function is recursive - // But it shouldn't be that long - fn isDotDefineMatch(p: *P, expr: Expr, parts: []const string) bool { + fn isDotDefineMatchInner(p: *P, expr: Expr, parts: []const string) ?Ref { switch (expr.data) { .e_dot => |ex| { if (parts.len > 1) { if (ex.optional_chain != null) { - return false; + return null; } // Intermediates must be dot expressions const last = parts.len - 1; const is_tail_match = strings.eql(parts[last], ex.name); - return is_tail_match and p.isDotDefineMatch(ex.target, parts[0..last]); + if (is_tail_match) { + if (p.isDotDefineMatchInner(ex.target, parts[0..last])) |ref| { + if (last == 0) { + ex.target.data.e_identifier.ref = ref; + } + + return ref; + } + } } }, .e_import_meta => { - return parts.len == 2 and strings.eqlComptime(parts[0], "import") and strings.eqlComptime(parts[1], "meta"); + if (parts.len == 2 and strings.eqlComptime(parts[0], "import") and strings.eqlComptime(parts[1], "meta")) { + p.is_dot_define_match = true; + return null; + } }, // Note: this behavior differs from esbuild // esbuild does not try to match index accessors @@ -17586,12 +17603,20 @@ fn NewParser_( .e_index => |index| { if (parts.len > 1 and index.index.data == .e_string and index.index.data.e_string.isUTF8()) { if (index.optional_chain != null) { - return false; + return null; } const last = parts.len - 1; const is_tail_match = strings.eql(parts[last], index.index.data.e_string.slice(p.allocator)); - return is_tail_match and p.isDotDefineMatch(index.target, parts[0..last]); + if (is_tail_match) { + if (p.isDotDefineMatchInner(index.target, parts[0..last])) |ref| { + if (last == 0) { + index.target.data.e_identifier.ref = ref; + } + + return ref; + } + } } }, .e_identifier => |ex| { @@ -17600,24 +17625,37 @@ fn NewParser_( if (parts.len == 1) { const name = p.loadNameFromRef(ex.ref); if (!strings.eql(name, parts[0])) { - return false; + return null; } const result = p.findSymbol(expr.loc, name) catch return false; // We must not be in a "with" statement scope if (result.is_inside_with_scope) { - return false; + p.is_dot_define_match = false; + + // preserve the updated Ref + return result.ref; } // The last symbol must be unbound - return p.symbols.items[result.ref.innerIndex()].kind == .unbound; + p.is_dot_define_match = p.symbols.items[result.ref.innerIndex()].kind == .unbound; + + // preserve the updated Ref + return result.ref; } }, else => {}, } - return false; + return null; + } + + fn isDotDefineMatch(p: *P, expr: Expr, parts: []const string) bool { + _ = p.isDotDefineMatchInner(expr, parts); + const result = p.is_dot_define_match; + p.is_dot_define_match = false; + return result; } fn visitBinding(p: *P, binding: BindingNodeIndex, duplicate_arg_check: ?*StringVoidMap) void { @@ -18615,7 +18653,7 @@ fn NewParser_( } if (p.options.tree_shaking) { - p.treeShake(&parts, commonjs_wrapper_expr != null or p.options.features.hot_module_reloading or p.options.enable_bundling); + p.treeShake(&parts, commonjs_wrapper_expr != null or p.options.features.hot_module_reloading or p.options.enable_legacy_bundling); } if (commonjs_wrapper_expr) |commonjs_wrapper| { @@ -19105,6 +19143,7 @@ fn NewParser_( part.stmts = _stmts[0 .. imports_list.len + toplevel_stmts.len + exports_from.len]; } else if (p.options.features.hot_module_reloading) {} var top_level_symbols_to_parts = js_ast.Ast.TopLevelSymbolToParts{}; + var top_level = &top_level_symbols_to_parts; const wrapper_ref = brk: { if (p.options.bundle) { break :brk p.newSymbol( @@ -19153,7 +19192,7 @@ fn NewParser_( var decls = &part.declared_symbols; const ctx = Ctx{ .allocator = p.allocator, - .top_level_symbols_to_parts = &top_level_symbols_to_parts, + .top_level_symbols_to_parts = top_level, .symbols = p.symbols.items, .part_index = @truncate(u32, part_index), }; @@ -19164,7 +19203,7 @@ fn NewParser_( // Pulling in the exports of this module always pulls in the export part { - var entry = top_level_symbols_to_parts.getOrPut(p.allocator, p.exports_ref) catch unreachable; + var entry = top_level.getOrPut(p.allocator, p.exports_ref) catch unreachable; if (!entry.found_existing) { entry.value_ptr.* = .{}; diff --git a/src/multi_array_list.zig b/src/multi_array_list.zig index 85a6789f482f..e4249ae4ba88 100644 --- a/src/multi_array_list.zig +++ b/src/multi_array_list.zig @@ -450,8 +450,7 @@ pub fn MultiArrayList(comptime S: type) type { } } - pub fn appendList(this: *Self, allocator: std.mem.Allocator, other: Self) !void { - try this.ensureUnusedCapacity(allocator, other.len); + pub fn appendListAssumeCapacity(this: *Self, other: Self) void { const offset = this.len; this.len += other.len; const other_slice = other.slice(); @@ -464,6 +463,11 @@ pub fn MultiArrayList(comptime S: type) type { } } + pub fn appendList(this: *Self, allocator: std.mem.Allocator, other: Self) !void { + try this.ensureUnusedCapacity(allocator, other.len); + this.appendListAssumeCapacity(other); + } + fn allocatedBytes(self: Self) []align(@alignOf(S)) u8 { return self.bytes[0..capacityInBytes(self.capacity)]; } From ab3d347f19c9cb74bd19e8676ab129737a84b82e Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Sun, 12 Mar 2023 03:59:51 -0700 Subject: [PATCH 087/255] Handle more edgecases in our bit_set fork --- src/bit_set.zig | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/bit_set.zig b/src/bit_set.zig index 33e0ffed39bb..8d6063759dfa 100644 --- a/src/bit_set.zig +++ b/src/bit_set.zig @@ -878,17 +878,29 @@ pub const DynamicBitSetUnmanaged = struct { /// same bit_length. pub fn toggleSet(self: *Self, toggles: Self) void { if (comptime Environment.allow_assert) std.debug.assert(toggles.bit_length == self.bit_length); + const bit_length = self.bit_length; + if (bit_length == 0) return; const num_masks = numMasks(self.bit_length); for (self.masks[0..num_masks], toggles.masks[0..num_masks]) |*mask, other_mask| { mask.* ^= other_mask; } + + const padding_bits = num_masks * @bitSizeOf(MaskInt) - bit_length; + const last_item_mask = (~@as(MaskInt, 0)) >> @intCast(ShiftInt, padding_bits); + self.masks[num_masks - 1] &= last_item_mask; } pub fn setAll(self: *Self, value: bool) void { + const bit_length = self.bit_length; + if (bit_length == 0) return; const num_masks = numMasks(self.bit_length); for (self.masks[0..num_masks]) |*mask| { mask.* = std.math.boolMask(MaskInt, value); } + + const padding_bits = num_masks * @bitSizeOf(MaskInt) - bit_length; + const last_item_mask = (~@as(MaskInt, 0)) >> @intCast(ShiftInt, padding_bits); + self.masks[num_masks - 1] &= last_item_mask; } /// Flips every bit in the bit set. @@ -1067,7 +1079,7 @@ pub const DynamicBitSetUnmanaged = struct { }; pub const AutoBitSet = union(enum) { - const Static = ArrayBitSet(usize, (@bitSizeOf(DynamicBitSetUnmanaged) - 1)); + pub const Static = ArrayBitSet(usize, (@bitSizeOf(DynamicBitSetUnmanaged) - 1)); static: Static, dynamic: DynamicBitSetUnmanaged, From 731fc460feb92f5fd63cbe6ae3f7b31b50a8a8b8 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Sun, 12 Mar 2023 06:53:37 -0700 Subject: [PATCH 088/255] Reduce number of allocations for `define` --- src/defines-table.zig | 1328 +++++++++++++++++++++-------------------- src/defines.zig | 123 ++-- 2 files changed, 731 insertions(+), 720 deletions(-) diff --git a/src/defines-table.zig b/src/defines-table.zig index fe86baf7f6c4..b4d7eed49a27 100644 --- a/src/defines-table.zig +++ b/src/defines-table.zig @@ -8,6 +8,8 @@ const MutableString = bun.MutableString; const stringZ = bun.stringZ; const default_allocator = bun.default_allocator; const C = bun.C; +const std = @import("std"); +const defines = @import("./defines.zig"); // If something is in this list, then a direct identifier expression or property // access chain matching this will be assumed to have no side effects and will @@ -24,18 +26,6 @@ const C = bun.C; // these functions has any side effects. It only says something about // referencing these function without calling them. pub const GlobalDefinesKey = [_][]const string{ - // These global identifiers should exist in all JavaScript environments. This - // deliberately omits "NaN", "Infinity", and "undefined" because these are - // treated as automatically-inlined constants instead of identifiers. - &[_]string{"Array"}, - &[_]string{"Boolean"}, - &[_]string{"Function"}, - &[_]string{"Math"}, - &[_]string{"Number"}, - &[_]string{"Object"}, - &[_]string{"RegExp"}, - &[_]string{"String"}, - // Object: Static methods // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object#Static_methods &[_]string{ "Object", "assign" }, @@ -124,69 +114,6 @@ pub const GlobalDefinesKey = [_][]const string{ &[_]string{ "Math", "tanh" }, &[_]string{ "Math", "trunc" }, - // Other globals present in both the browser and node (except "eval" because - // it has special behavior) - &[_]string{"AbortController"}, - &[_]string{"AbortSignal"}, - &[_]string{"AggregateError"}, - &[_]string{"ArrayBuffer"}, - &[_]string{"BigInt"}, - &[_]string{"DataView"}, - &[_]string{"Date"}, - &[_]string{"Error"}, - &[_]string{"EvalError"}, - &[_]string{"Event"}, - &[_]string{"EventTarget"}, - &[_]string{"Float32Array"}, - &[_]string{"Float64Array"}, - &[_]string{"Int16Array"}, - &[_]string{"Int32Array"}, - &[_]string{"Int8Array"}, - &[_]string{"Intl"}, - &[_]string{"JSON"}, - &[_]string{"Map"}, - &[_]string{"MessageChannel"}, - &[_]string{"MessageEvent"}, - &[_]string{"MessagePort"}, - &[_]string{"Promise"}, - &[_]string{"Proxy"}, - &[_]string{"RangeError"}, - &[_]string{"ReferenceError"}, - &[_]string{"Reflect"}, - &[_]string{"Set"}, - &[_]string{"Symbol"}, - &[_]string{"SyntaxError"}, - &[_]string{"TextDecoder"}, - &[_]string{"TextEncoder"}, - &[_]string{"TypeError"}, - &[_]string{"URIError"}, - &[_]string{"URL"}, - &[_]string{"URLSearchParams"}, - &[_]string{"Uint16Array"}, - &[_]string{"Uint32Array"}, - &[_]string{"Uint8Array"}, - &[_]string{"Uint8ClampedArray"}, - &[_]string{"WeakMap"}, - &[_]string{"WeakSet"}, - &[_]string{"WebAssembly"}, - &[_]string{"clearInterval"}, - &[_]string{"clearTimeout"}, - &[_]string{"console"}, - &[_]string{"decodeURI"}, - &[_]string{"decodeURIComponent"}, - &[_]string{"encodeURI"}, - &[_]string{"encodeURIComponent"}, - &[_]string{"escape"}, - &[_]string{"globalThis"}, - &[_]string{"isFinite"}, - &[_]string{"isNaN"}, - &[_]string{"parseFloat"}, - &[_]string{"parseInt"}, - &[_]string{"queueMicrotask"}, - &[_]string{"setInterval"}, - &[_]string{"setTimeout"}, - &[_]string{"unescape"}, - &[_]string{ "Symbol", "for" }, &[_]string{ "Symbol", "keyFor" }, @@ -227,122 +154,229 @@ pub const GlobalDefinesKey = [_][]const string{ &[_]string{ "console", "timeLog" }, &[_]string{ "console", "trace" }, &[_]string{ "console", "warn" }, +}; + +const pure_global_identifier_define = defines.IdentifierDefine{ + .valueless = true, + .can_be_removed_if_unused = true, + .value = .{ .e_undefined = .{} }, +}; +const js_ast = bun.JSAst; + +const identifiers = struct { + const nan_val = js_ast.E.Number{ .value = std.math.nan_f64 }; + + const inf_val = js_ast.E.Number{ .value = std.math.inf_f64 }; + + // Step 2. Swap in certain literal values because those can be constant folded + pub const @"undefined" = defines.IdentifierDefine{ + .value = js_ast.Expr.Data{ .e_undefined = .{} }, + .valueless = false, + .can_be_removed_if_unused = true, + }; + pub const NaN = defines.IdentifierDefine{ + .value = js_ast.Expr.Data{ .e_number = nan_val }, + }; + pub const Infinity = defines.IdentifierDefine{ + .value = js_ast.Expr.Data{ .e_number = inf_val }, + }; +}; + +pub const pure_global_identifiers = .{ + .{ "NaN", identifiers.NaN }, + .{ "Infinity", identifiers.Infinity }, + .{ "undefined", identifiers.undefined }, + + // These global identifiers should exist in all JavaScript environments. This + // deliberately omits "NaN", "Infinity", and "undefined" because these are + // treated as automatically-inlined constants instead of identifiers. + .{ "Array", pure_global_identifier_define }, + .{ "Boolean", pure_global_identifier_define }, + .{ "Function", pure_global_identifier_define }, + .{ "Math", pure_global_identifier_define }, + .{ "Number", pure_global_identifier_define }, + .{ "Object", pure_global_identifier_define }, + .{ "RegExp", pure_global_identifier_define }, + .{ "String", pure_global_identifier_define }, + + // Other globals present in both the browser and node (except "eval" because + // it has special behavior) + .{ "AbortController", pure_global_identifier_define }, + .{ "AbortSignal", pure_global_identifier_define }, + .{ "AggregateError", pure_global_identifier_define }, + .{ "ArrayBuffer", pure_global_identifier_define }, + .{ "BigInt", pure_global_identifier_define }, + .{ "DataView", pure_global_identifier_define }, + .{ "Date", pure_global_identifier_define }, + .{ "Error", pure_global_identifier_define }, + .{ "EvalError", pure_global_identifier_define }, + .{ "Event", pure_global_identifier_define }, + .{ "EventTarget", pure_global_identifier_define }, + .{ "Float32Array", pure_global_identifier_define }, + .{ "Float64Array", pure_global_identifier_define }, + .{ "Int16Array", pure_global_identifier_define }, + .{ "Int32Array", pure_global_identifier_define }, + .{ "Int8Array", pure_global_identifier_define }, + .{ "Intl", pure_global_identifier_define }, + .{ "JSON", pure_global_identifier_define }, + .{ "Map", pure_global_identifier_define }, + .{ "MessageChannel", pure_global_identifier_define }, + .{ "MessageEvent", pure_global_identifier_define }, + .{ "MessagePort", pure_global_identifier_define }, + .{ "Promise", pure_global_identifier_define }, + .{ "Proxy", pure_global_identifier_define }, + .{ "RangeError", pure_global_identifier_define }, + .{ "ReferenceError", pure_global_identifier_define }, + .{ "Reflect", pure_global_identifier_define }, + .{ "Set", pure_global_identifier_define }, + .{ "Symbol", pure_global_identifier_define }, + .{ "SyntaxError", pure_global_identifier_define }, + .{ "TextDecoder", pure_global_identifier_define }, + .{ "TextEncoder", pure_global_identifier_define }, + .{ "TypeError", pure_global_identifier_define }, + .{ "URIError", pure_global_identifier_define }, + .{ "URL", pure_global_identifier_define }, + .{ "URLSearchParams", pure_global_identifier_define }, + .{ "Uint16Array", pure_global_identifier_define }, + .{ "Uint32Array", pure_global_identifier_define }, + .{ "Uint8Array", pure_global_identifier_define }, + .{ "Uint8ClampedArray", pure_global_identifier_define }, + .{ "WeakMap", pure_global_identifier_define }, + .{ "WeakSet", pure_global_identifier_define }, + .{ "WebAssembly", pure_global_identifier_define }, + .{ "clearInterval", pure_global_identifier_define }, + .{ "clearTimeout", pure_global_identifier_define }, + .{ "console", pure_global_identifier_define }, + .{ "decodeURI", pure_global_identifier_define }, + .{ "decodeURIComponent", pure_global_identifier_define }, + .{ "encodeURI", pure_global_identifier_define }, + .{ "encodeURIComponent", pure_global_identifier_define }, + .{ "escape", pure_global_identifier_define }, + .{ "globalThis", pure_global_identifier_define }, + .{ "isFinite", pure_global_identifier_define }, + .{ "isNaN", pure_global_identifier_define }, + .{ "parseFloat", pure_global_identifier_define }, + .{ "parseInt", pure_global_identifier_define }, + .{ "queueMicrotask", pure_global_identifier_define }, + .{ "setInterval", pure_global_identifier_define }, + .{ "setTimeout", pure_global_identifier_define }, + .{ "unescape", pure_global_identifier_define }, // CSSOM APIs - &[_]string{"CSSAnimation"}, - &[_]string{"CSSFontFaceRule"}, - &[_]string{"CSSImportRule"}, - &[_]string{"CSSKeyframeRule"}, - &[_]string{"CSSKeyframesRule"}, - &[_]string{"CSSMediaRule"}, - &[_]string{"CSSNamespaceRule"}, - &[_]string{"CSSPageRule"}, - &[_]string{"CSSRule"}, - &[_]string{"CSSRuleList"}, - &[_]string{"CSSStyleDeclaration"}, - &[_]string{"CSSStyleRule"}, - &[_]string{"CSSStyleSheet"}, - &[_]string{"CSSSupportsRule"}, - &[_]string{"CSSTransition"}, + .{ "CSSAnimation", pure_global_identifier_define }, + .{ "CSSFontFaceRule", pure_global_identifier_define }, + .{ "CSSImportRule", pure_global_identifier_define }, + .{ "CSSKeyframeRule", pure_global_identifier_define }, + .{ "CSSKeyframesRule", pure_global_identifier_define }, + .{ "CSSMediaRule", pure_global_identifier_define }, + .{ "CSSNamespaceRule", pure_global_identifier_define }, + .{ "CSSPageRule", pure_global_identifier_define }, + .{ "CSSRule", pure_global_identifier_define }, + .{ "CSSRuleList", pure_global_identifier_define }, + .{ "CSSStyleDeclaration", pure_global_identifier_define }, + .{ "CSSStyleRule", pure_global_identifier_define }, + .{ "CSSStyleSheet", pure_global_identifier_define }, + .{ "CSSSupportsRule", pure_global_identifier_define }, + .{ "CSSTransition", pure_global_identifier_define }, // SVG DOM - &[_]string{"SVGAElement"}, - &[_]string{"SVGAngle"}, - &[_]string{"SVGAnimateElement"}, - &[_]string{"SVGAnimateMotionElement"}, - &[_]string{"SVGAnimateTransformElement"}, - &[_]string{"SVGAnimatedAngle"}, - &[_]string{"SVGAnimatedBoolean"}, - &[_]string{"SVGAnimatedEnumeration"}, - &[_]string{"SVGAnimatedInteger"}, - &[_]string{"SVGAnimatedLength"}, - &[_]string{"SVGAnimatedLengthList"}, - &[_]string{"SVGAnimatedNumber"}, - &[_]string{"SVGAnimatedNumberList"}, - &[_]string{"SVGAnimatedPreserveAspectRatio"}, - &[_]string{"SVGAnimatedRect"}, - &[_]string{"SVGAnimatedString"}, - &[_]string{"SVGAnimatedTransformList"}, - &[_]string{"SVGAnimationElement"}, - &[_]string{"SVGCircleElement"}, - &[_]string{"SVGClipPathElement"}, - &[_]string{"SVGComponentTransferFunctionElement"}, - &[_]string{"SVGDefsElement"}, - &[_]string{"SVGDescElement"}, - &[_]string{"SVGElement"}, - &[_]string{"SVGEllipseElement"}, - &[_]string{"SVGFEBlendElement"}, - &[_]string{"SVGFEColorMatrixElement"}, - &[_]string{"SVGFEComponentTransferElement"}, - &[_]string{"SVGFECompositeElement"}, - &[_]string{"SVGFEConvolveMatrixElement"}, - &[_]string{"SVGFEDiffuseLightingElement"}, - &[_]string{"SVGFEDisplacementMapElement"}, - &[_]string{"SVGFEDistantLightElement"}, - &[_]string{"SVGFEDropShadowElement"}, - &[_]string{"SVGFEFloodElement"}, - &[_]string{"SVGFEFuncAElement"}, - &[_]string{"SVGFEFuncBElement"}, - &[_]string{"SVGFEFuncGElement"}, - &[_]string{"SVGFEFuncRElement"}, - &[_]string{"SVGFEGaussianBlurElement"}, - &[_]string{"SVGFEImageElement"}, - &[_]string{"SVGFEMergeElement"}, - &[_]string{"SVGFEMergeNodeElement"}, - &[_]string{"SVGFEMorphologyElement"}, - &[_]string{"SVGFEOffsetElement"}, - &[_]string{"SVGFEPointLightElement"}, - &[_]string{"SVGFESpecularLightingElement"}, - &[_]string{"SVGFESpotLightElement"}, - &[_]string{"SVGFETileElement"}, - &[_]string{"SVGFETurbulenceElement"}, - &[_]string{"SVGFilterElement"}, - &[_]string{"SVGForeignObjectElement"}, - &[_]string{"SVGGElement"}, - &[_]string{"SVGGeometryElement"}, - &[_]string{"SVGGradientElement"}, - &[_]string{"SVGGraphicsElement"}, - &[_]string{"SVGImageElement"}, - &[_]string{"SVGLength"}, - &[_]string{"SVGLengthList"}, - &[_]string{"SVGLineElement"}, - &[_]string{"SVGLinearGradientElement"}, - &[_]string{"SVGMPathElement"}, - &[_]string{"SVGMarkerElement"}, - &[_]string{"SVGMaskElement"}, - &[_]string{"SVGMatrix"}, - &[_]string{"SVGMetadataElement"}, - &[_]string{"SVGNumber"}, - &[_]string{"SVGNumberList"}, - &[_]string{"SVGPathElement"}, - &[_]string{"SVGPatternElement"}, - &[_]string{"SVGPoint"}, - &[_]string{"SVGPointList"}, - &[_]string{"SVGPolygonElement"}, - &[_]string{"SVGPolylineElement"}, - &[_]string{"SVGPreserveAspectRatio"}, - &[_]string{"SVGRadialGradientElement"}, - &[_]string{"SVGRect"}, - &[_]string{"SVGRectElement"}, - &[_]string{"SVGSVGElement"}, - &[_]string{"SVGScriptElement"}, - &[_]string{"SVGSetElement"}, - &[_]string{"SVGStopElement"}, - &[_]string{"SVGStringList"}, - &[_]string{"SVGStyleElement"}, - &[_]string{"SVGSwitchElement"}, - &[_]string{"SVGSymbolElement"}, - &[_]string{"SVGTSpanElement"}, - &[_]string{"SVGTextContentElement"}, - &[_]string{"SVGTextElement"}, - &[_]string{"SVGTextPathElement"}, - &[_]string{"SVGTextPositioningElement"}, - &[_]string{"SVGTitleElement"}, - &[_]string{"SVGTransform"}, - &[_]string{"SVGTransformList"}, - &[_]string{"SVGUnitTypes"}, - &[_]string{"SVGUseElement"}, - &[_]string{"SVGViewElement"}, + .{ "SVGAElement", pure_global_identifier_define }, + .{ "SVGAngle", pure_global_identifier_define }, + .{ "SVGAnimateElement", pure_global_identifier_define }, + .{ "SVGAnimateMotionElement", pure_global_identifier_define }, + .{ "SVGAnimateTransformElement", pure_global_identifier_define }, + .{ "SVGAnimatedAngle", pure_global_identifier_define }, + .{ "SVGAnimatedBoolean", pure_global_identifier_define }, + .{ "SVGAnimatedEnumeration", pure_global_identifier_define }, + .{ "SVGAnimatedInteger", pure_global_identifier_define }, + .{ "SVGAnimatedLength", pure_global_identifier_define }, + .{ "SVGAnimatedLengthList", pure_global_identifier_define }, + .{ "SVGAnimatedNumber", pure_global_identifier_define }, + .{ "SVGAnimatedNumberList", pure_global_identifier_define }, + .{ "SVGAnimatedPreserveAspectRatio", pure_global_identifier_define }, + .{ "SVGAnimatedRect", pure_global_identifier_define }, + .{ "SVGAnimatedString", pure_global_identifier_define }, + .{ "SVGAnimatedTransformList", pure_global_identifier_define }, + .{ "SVGAnimationElement", pure_global_identifier_define }, + .{ "SVGCircleElement", pure_global_identifier_define }, + .{ "SVGClipPathElement", pure_global_identifier_define }, + .{ "SVGComponentTransferFunctionElement", pure_global_identifier_define }, + .{ "SVGDefsElement", pure_global_identifier_define }, + .{ "SVGDescElement", pure_global_identifier_define }, + .{ "SVGElement", pure_global_identifier_define }, + .{ "SVGEllipseElement", pure_global_identifier_define }, + .{ "SVGFEBlendElement", pure_global_identifier_define }, + .{ "SVGFEColorMatrixElement", pure_global_identifier_define }, + .{ "SVGFEComponentTransferElement", pure_global_identifier_define }, + .{ "SVGFECompositeElement", pure_global_identifier_define }, + .{ "SVGFEConvolveMatrixElement", pure_global_identifier_define }, + .{ "SVGFEDiffuseLightingElement", pure_global_identifier_define }, + .{ "SVGFEDisplacementMapElement", pure_global_identifier_define }, + .{ "SVGFEDistantLightElement", pure_global_identifier_define }, + .{ "SVGFEDropShadowElement", pure_global_identifier_define }, + .{ "SVGFEFloodElement", pure_global_identifier_define }, + .{ "SVGFEFuncAElement", pure_global_identifier_define }, + .{ "SVGFEFuncBElement", pure_global_identifier_define }, + .{ "SVGFEFuncGElement", pure_global_identifier_define }, + .{ "SVGFEFuncRElement", pure_global_identifier_define }, + .{ "SVGFEGaussianBlurElement", pure_global_identifier_define }, + .{ "SVGFEImageElement", pure_global_identifier_define }, + .{ "SVGFEMergeElement", pure_global_identifier_define }, + .{ "SVGFEMergeNodeElement", pure_global_identifier_define }, + .{ "SVGFEMorphologyElement", pure_global_identifier_define }, + .{ "SVGFEOffsetElement", pure_global_identifier_define }, + .{ "SVGFEPointLightElement", pure_global_identifier_define }, + .{ "SVGFESpecularLightingElement", pure_global_identifier_define }, + .{ "SVGFESpotLightElement", pure_global_identifier_define }, + .{ "SVGFETileElement", pure_global_identifier_define }, + .{ "SVGFETurbulenceElement", pure_global_identifier_define }, + .{ "SVGFilterElement", pure_global_identifier_define }, + .{ "SVGForeignObjectElement", pure_global_identifier_define }, + .{ "SVGGElement", pure_global_identifier_define }, + .{ "SVGGeometryElement", pure_global_identifier_define }, + .{ "SVGGradientElement", pure_global_identifier_define }, + .{ "SVGGraphicsElement", pure_global_identifier_define }, + .{ "SVGImageElement", pure_global_identifier_define }, + .{ "SVGLength", pure_global_identifier_define }, + .{ "SVGLengthList", pure_global_identifier_define }, + .{ "SVGLineElement", pure_global_identifier_define }, + .{ "SVGLinearGradientElement", pure_global_identifier_define }, + .{ "SVGMPathElement", pure_global_identifier_define }, + .{ "SVGMarkerElement", pure_global_identifier_define }, + .{ "SVGMaskElement", pure_global_identifier_define }, + .{ "SVGMatrix", pure_global_identifier_define }, + .{ "SVGMetadataElement", pure_global_identifier_define }, + .{ "SVGNumber", pure_global_identifier_define }, + .{ "SVGNumberList", pure_global_identifier_define }, + .{ "SVGPathElement", pure_global_identifier_define }, + .{ "SVGPatternElement", pure_global_identifier_define }, + .{ "SVGPoint", pure_global_identifier_define }, + .{ "SVGPointList", pure_global_identifier_define }, + .{ "SVGPolygonElement", pure_global_identifier_define }, + .{ "SVGPolylineElement", pure_global_identifier_define }, + .{ "SVGPreserveAspectRatio", pure_global_identifier_define }, + .{ "SVGRadialGradientElement", pure_global_identifier_define }, + .{ "SVGRect", pure_global_identifier_define }, + .{ "SVGRectElement", pure_global_identifier_define }, + .{ "SVGSVGElement", pure_global_identifier_define }, + .{ "SVGScriptElement", pure_global_identifier_define }, + .{ "SVGSetElement", pure_global_identifier_define }, + .{ "SVGStopElement", pure_global_identifier_define }, + .{ "SVGStringList", pure_global_identifier_define }, + .{ "SVGStyleElement", pure_global_identifier_define }, + .{ "SVGSwitchElement", pure_global_identifier_define }, + .{ "SVGSymbolElement", pure_global_identifier_define }, + .{ "SVGTSpanElement", pure_global_identifier_define }, + .{ "SVGTextContentElement", pure_global_identifier_define }, + .{ "SVGTextElement", pure_global_identifier_define }, + .{ "SVGTextPathElement", pure_global_identifier_define }, + .{ "SVGTextPositioningElement", pure_global_identifier_define }, + .{ "SVGTitleElement", pure_global_identifier_define }, + .{ "SVGTransform", pure_global_identifier_define }, + .{ "SVGTransformList", pure_global_identifier_define }, + .{ "SVGUnitTypes", pure_global_identifier_define }, + .{ "SVGUseElement", pure_global_identifier_define }, + .{ "SVGViewElement", pure_global_identifier_define }, // Other browser APIs // @@ -364,463 +398,465 @@ pub const GlobalDefinesKey = [_][]const string{ // - localStorage // - sessionStorage // - &[_]string{"AnalyserNode"}, - &[_]string{"Animation"}, - &[_]string{"AnimationEffect"}, - &[_]string{"AnimationEvent"}, - &[_]string{"AnimationPlaybackEvent"}, - &[_]string{"AnimationTimeline"}, - &[_]string{"Attr"}, - &[_]string{"Audio"}, - &[_]string{"AudioBuffer"}, - &[_]string{"AudioBufferSourceNode"}, - &[_]string{"AudioDestinationNode"}, - &[_]string{"AudioListener"}, - &[_]string{"AudioNode"}, - &[_]string{"AudioParam"}, - &[_]string{"AudioProcessingEvent"}, - &[_]string{"AudioScheduledSourceNode"}, - &[_]string{"BarProp"}, - &[_]string{"BeforeUnloadEvent"}, - &[_]string{"BiquadFilterNode"}, - &[_]string{"Blob"}, - &[_]string{"BlobEvent"}, - &[_]string{"ByteLengthQueuingStrategy"}, - &[_]string{"CDATASection"}, - &[_]string{"CSS"}, - &[_]string{"CanvasGradient"}, - &[_]string{"CanvasPattern"}, - &[_]string{"CanvasRenderingContext2D"}, - &[_]string{"ChannelMergerNode"}, - &[_]string{"ChannelSplitterNode"}, - &[_]string{"CharacterData"}, - &[_]string{"ClipboardEvent"}, - &[_]string{"CloseEvent"}, - &[_]string{"Comment"}, - &[_]string{"CompositionEvent"}, - &[_]string{"ConvolverNode"}, - &[_]string{"CountQueuingStrategy"}, - &[_]string{"Crypto"}, - &[_]string{"CustomElementRegistry"}, - &[_]string{"CustomEvent"}, - &[_]string{"DOMException"}, - &[_]string{"DOMImplementation"}, - &[_]string{"DOMMatrix"}, - &[_]string{"DOMMatrixReadOnly"}, - &[_]string{"DOMParser"}, - &[_]string{"DOMPoint"}, - &[_]string{"DOMPointReadOnly"}, - &[_]string{"DOMQuad"}, - &[_]string{"DOMRect"}, - &[_]string{"DOMRectList"}, - &[_]string{"DOMRectReadOnly"}, - &[_]string{"DOMStringList"}, - &[_]string{"DOMStringMap"}, - &[_]string{"DOMTokenList"}, - &[_]string{"DataTransfer"}, - &[_]string{"DataTransferItem"}, - &[_]string{"DataTransferItemList"}, - &[_]string{"DelayNode"}, - &[_]string{"Document"}, - &[_]string{"DocumentFragment"}, - &[_]string{"DocumentTimeline"}, - &[_]string{"DocumentType"}, - &[_]string{"DragEvent"}, - &[_]string{"DynamicsCompressorNode"}, - &[_]string{"Element"}, - &[_]string{"ErrorEvent"}, - &[_]string{"EventSource"}, - &[_]string{"File"}, - &[_]string{"FileList"}, - &[_]string{"FileReader"}, - &[_]string{"FocusEvent"}, - &[_]string{"FontFace"}, - &[_]string{"FormData"}, - &[_]string{"GainNode"}, - &[_]string{"Gamepad"}, - &[_]string{"GamepadButton"}, - &[_]string{"GamepadEvent"}, - &[_]string{"Geolocation"}, - &[_]string{"GeolocationPositionError"}, - &[_]string{"HTMLAllCollection"}, - &[_]string{"HTMLAnchorElement"}, - &[_]string{"HTMLAreaElement"}, - &[_]string{"HTMLAudioElement"}, - &[_]string{"HTMLBRElement"}, - &[_]string{"HTMLBaseElement"}, - &[_]string{"HTMLBodyElement"}, - &[_]string{"HTMLButtonElement"}, - &[_]string{"HTMLCanvasElement"}, - &[_]string{"HTMLCollection"}, - &[_]string{"HTMLDListElement"}, - &[_]string{"HTMLDataElement"}, - &[_]string{"HTMLDataListElement"}, - &[_]string{"HTMLDetailsElement"}, - &[_]string{"HTMLDirectoryElement"}, - &[_]string{"HTMLDivElement"}, - &[_]string{"HTMLDocument"}, - &[_]string{"HTMLElement"}, - &[_]string{"HTMLEmbedElement"}, - &[_]string{"HTMLFieldSetElement"}, - &[_]string{"HTMLFontElement"}, - &[_]string{"HTMLFormControlsCollection"}, - &[_]string{"HTMLFormElement"}, - &[_]string{"HTMLFrameElement"}, - &[_]string{"HTMLFrameSetElement"}, - &[_]string{"HTMLHRElement"}, - &[_]string{"HTMLHeadElement"}, - &[_]string{"HTMLHeadingElement"}, - &[_]string{"HTMLHtmlElement"}, - &[_]string{"HTMLIFrameElement"}, - &[_]string{"HTMLImageElement"}, - &[_]string{"HTMLInputElement"}, - &[_]string{"HTMLLIElement"}, - &[_]string{"HTMLLabelElement"}, - &[_]string{"HTMLLegendElement"}, - &[_]string{"HTMLLinkElement"}, - &[_]string{"HTMLMapElement"}, - &[_]string{"HTMLMarqueeElement"}, - &[_]string{"HTMLMediaElement"}, - &[_]string{"HTMLMenuElement"}, - &[_]string{"HTMLMetaElement"}, - &[_]string{"HTMLMeterElement"}, - &[_]string{"HTMLModElement"}, - &[_]string{"HTMLOListElement"}, - &[_]string{"HTMLObjectElement"}, - &[_]string{"HTMLOptGroupElement"}, - &[_]string{"HTMLOptionElement"}, - &[_]string{"HTMLOptionsCollection"}, - &[_]string{"HTMLOutputElement"}, - &[_]string{"HTMLParagraphElement"}, - &[_]string{"HTMLParamElement"}, - &[_]string{"HTMLPictureElement"}, - &[_]string{"HTMLPreElement"}, - &[_]string{"HTMLProgressElement"}, - &[_]string{"HTMLQuoteElement"}, - &[_]string{"HTMLScriptElement"}, - &[_]string{"HTMLSelectElement"}, - &[_]string{"HTMLSlotElement"}, - &[_]string{"HTMLSourceElement"}, - &[_]string{"HTMLSpanElement"}, - &[_]string{"HTMLStyleElement"}, - &[_]string{"HTMLTableCaptionElement"}, - &[_]string{"HTMLTableCellElement"}, - &[_]string{"HTMLTableColElement"}, - &[_]string{"HTMLTableElement"}, - &[_]string{"HTMLTableRowElement"}, - &[_]string{"HTMLTableSectionElement"}, - &[_]string{"HTMLTemplateElement"}, - &[_]string{"HTMLTextAreaElement"}, - &[_]string{"HTMLTimeElement"}, - &[_]string{"HTMLTitleElement"}, - &[_]string{"HTMLTrackElement"}, - &[_]string{"HTMLUListElement"}, - &[_]string{"HTMLUnknownElement"}, - &[_]string{"HTMLVideoElement"}, - &[_]string{"HashChangeEvent"}, - &[_]string{"Headers"}, - &[_]string{"History"}, - &[_]string{"IDBCursor"}, - &[_]string{"IDBCursorWithValue"}, - &[_]string{"IDBDatabase"}, - &[_]string{"IDBFactory"}, - &[_]string{"IDBIndex"}, - &[_]string{"IDBKeyRange"}, - &[_]string{"IDBObjectStore"}, - &[_]string{"IDBOpenDBRequest"}, - &[_]string{"IDBRequest"}, - &[_]string{"IDBTransaction"}, - &[_]string{"IDBVersionChangeEvent"}, - &[_]string{"Image"}, - &[_]string{"ImageData"}, - &[_]string{"InputEvent"}, - &[_]string{"IntersectionObserver"}, - &[_]string{"IntersectionObserverEntry"}, - &[_]string{"KeyboardEvent"}, - &[_]string{"KeyframeEffect"}, - &[_]string{"Location"}, - &[_]string{"MediaCapabilities"}, - &[_]string{"MediaElementAudioSourceNode"}, - &[_]string{"MediaEncryptedEvent"}, - &[_]string{"MediaError"}, - &[_]string{"MediaList"}, - &[_]string{"MediaQueryList"}, - &[_]string{"MediaQueryListEvent"}, - &[_]string{"MediaRecorder"}, - &[_]string{"MediaSource"}, - &[_]string{"MediaStream"}, - &[_]string{"MediaStreamAudioDestinationNode"}, - &[_]string{"MediaStreamAudioSourceNode"}, - &[_]string{"MediaStreamTrack"}, - &[_]string{"MediaStreamTrackEvent"}, - &[_]string{"MimeType"}, - &[_]string{"MimeTypeArray"}, - &[_]string{"MouseEvent"}, - &[_]string{"MutationEvent"}, - &[_]string{"MutationObserver"}, - &[_]string{"MutationRecord"}, - &[_]string{"NamedNodeMap"}, - &[_]string{"Navigator"}, - &[_]string{"Node"}, - &[_]string{"NodeFilter"}, - &[_]string{"NodeIterator"}, - &[_]string{"NodeList"}, - &[_]string{"Notification"}, - &[_]string{"OfflineAudioCompletionEvent"}, - &[_]string{"Option"}, - &[_]string{"OscillatorNode"}, - &[_]string{"PageTransitionEvent"}, - &[_]string{"Path2D"}, - &[_]string{"Performance"}, - &[_]string{"PerformanceEntry"}, - &[_]string{"PerformanceMark"}, - &[_]string{"PerformanceMeasure"}, - &[_]string{"PerformanceNavigation"}, - &[_]string{"PerformanceObserver"}, - &[_]string{"PerformanceObserverEntryList"}, - &[_]string{"PerformanceResourceTiming"}, - &[_]string{"PerformanceTiming"}, - &[_]string{"PeriodicWave"}, - &[_]string{"Plugin"}, - &[_]string{"PluginArray"}, - &[_]string{"PointerEvent"}, - &[_]string{"PopStateEvent"}, - &[_]string{"ProcessingInstruction"}, - &[_]string{"ProgressEvent"}, - &[_]string{"PromiseRejectionEvent"}, - &[_]string{"RTCCertificate"}, - &[_]string{"RTCDTMFSender"}, - &[_]string{"RTCDTMFToneChangeEvent"}, - &[_]string{"RTCDataChannel"}, - &[_]string{"RTCDataChannelEvent"}, - &[_]string{"RTCIceCandidate"}, - &[_]string{"RTCPeerConnection"}, - &[_]string{"RTCPeerConnectionIceEvent"}, - &[_]string{"RTCRtpReceiver"}, - &[_]string{"RTCRtpSender"}, - &[_]string{"RTCRtpTransceiver"}, - &[_]string{"RTCSessionDescription"}, - &[_]string{"RTCStatsReport"}, - &[_]string{"RTCTrackEvent"}, - &[_]string{"RadioNodeList"}, - &[_]string{"Range"}, - &[_]string{"ReadableStream"}, - &[_]string{"Request"}, - &[_]string{"ResizeObserver"}, - &[_]string{"ResizeObserverEntry"}, - &[_]string{"Response"}, - &[_]string{"Screen"}, - &[_]string{"ScriptProcessorNode"}, - &[_]string{"SecurityPolicyViolationEvent"}, - &[_]string{"Selection"}, - &[_]string{"ShadowRoot"}, - &[_]string{"SourceBuffer"}, - &[_]string{"SourceBufferList"}, - &[_]string{"SpeechSynthesisEvent"}, - &[_]string{"SpeechSynthesisUtterance"}, - &[_]string{"StaticRange"}, - &[_]string{"Storage"}, - &[_]string{"StorageEvent"}, - &[_]string{"StyleSheet"}, - &[_]string{"StyleSheetList"}, - &[_]string{"Text"}, - &[_]string{"TextMetrics"}, - &[_]string{"TextTrack"}, - &[_]string{"TextTrackCue"}, - &[_]string{"TextTrackCueList"}, - &[_]string{"TextTrackList"}, - &[_]string{"TimeRanges"}, - &[_]string{"TrackEvent"}, - &[_]string{"TransitionEvent"}, - &[_]string{"TreeWalker"}, - &[_]string{"UIEvent"}, - &[_]string{"VTTCue"}, - &[_]string{"ValidityState"}, - &[_]string{"VisualViewport"}, - &[_]string{"WaveShaperNode"}, - &[_]string{"WebGLActiveInfo"}, - &[_]string{"WebGLBuffer"}, - &[_]string{"WebGLContextEvent"}, - &[_]string{"WebGLFramebuffer"}, - &[_]string{"WebGLProgram"}, - &[_]string{"WebGLQuery"}, - &[_]string{"WebGLRenderbuffer"}, - &[_]string{"WebGLRenderingContext"}, - &[_]string{"WebGLSampler"}, - &[_]string{"WebGLShader"}, - &[_]string{"WebGLShaderPrecisionFormat"}, - &[_]string{"WebGLSync"}, - &[_]string{"WebGLTexture"}, - &[_]string{"WebGLUniformLocation"}, - &[_]string{"WebKitCSSMatrix"}, - &[_]string{"WebSocket"}, - &[_]string{"WheelEvent"}, - &[_]string{"Window"}, - &[_]string{"Worker"}, - &[_]string{"XMLDocument"}, - &[_]string{"XMLHttpRequest"}, - &[_]string{"XMLHttpRequestEventTarget"}, - &[_]string{"XMLHttpRequestUpload"}, - &[_]string{"XMLSerializer"}, - &[_]string{"XPathEvaluator"}, - &[_]string{"XPathExpression"}, - &[_]string{"XPathResult"}, - &[_]string{"XSLTProcessor"}, - &[_]string{"alert"}, - &[_]string{"atob"}, - &[_]string{"blur"}, - &[_]string{"btoa"}, - &[_]string{"cancelAnimationFrame"}, - &[_]string{"captureEvents"}, - &[_]string{"close"}, - &[_]string{"closed"}, - &[_]string{"confirm"}, - &[_]string{"customElements"}, - &[_]string{"devicePixelRatio"}, - &[_]string{"document"}, - &[_]string{"event"}, - &[_]string{"fetch"}, - &[_]string{"find"}, - &[_]string{"focus"}, - &[_]string{"frameElement"}, - &[_]string{"frames"}, - &[_]string{"getComputedStyle"}, - &[_]string{"getSelection"}, - &[_]string{"history"}, - &[_]string{"indexedDB"}, - &[_]string{"isSecureContext"}, - &[_]string{"length"}, - &[_]string{"location"}, - &[_]string{"locationbar"}, - &[_]string{"matchMedia"}, - &[_]string{"menubar"}, - &[_]string{"moveBy"}, - &[_]string{"moveTo"}, - &[_]string{"name"}, - &[_]string{"navigator"}, - &[_]string{"onabort"}, - &[_]string{"onafterprint"}, - &[_]string{"onanimationend"}, - &[_]string{"onanimationiteration"}, - &[_]string{"onanimationstart"}, - &[_]string{"onbeforeprint"}, - &[_]string{"onbeforeunload"}, - &[_]string{"onblur"}, - &[_]string{"oncanplay"}, - &[_]string{"oncanplaythrough"}, - &[_]string{"onchange"}, - &[_]string{"onclick"}, - &[_]string{"oncontextmenu"}, - &[_]string{"oncuechange"}, - &[_]string{"ondblclick"}, - &[_]string{"ondrag"}, - &[_]string{"ondragend"}, - &[_]string{"ondragenter"}, - &[_]string{"ondragleave"}, - &[_]string{"ondragover"}, - &[_]string{"ondragstart"}, - &[_]string{"ondrop"}, - &[_]string{"ondurationchange"}, - &[_]string{"onemptied"}, - &[_]string{"onended"}, - &[_]string{"onerror"}, - &[_]string{"onfocus"}, - &[_]string{"ongotpointercapture"}, - &[_]string{"onhashchange"}, - &[_]string{"oninput"}, - &[_]string{"oninvalid"}, - &[_]string{"onkeydown"}, - &[_]string{"onkeypress"}, - &[_]string{"onkeyup"}, - &[_]string{"onlanguagechange"}, - &[_]string{"onload"}, - &[_]string{"onloadeddata"}, - &[_]string{"onloadedmetadata"}, - &[_]string{"onloadstart"}, - &[_]string{"onlostpointercapture"}, - &[_]string{"onmessage"}, - &[_]string{"onmousedown"}, - &[_]string{"onmouseenter"}, - &[_]string{"onmouseleave"}, - &[_]string{"onmousemove"}, - &[_]string{"onmouseout"}, - &[_]string{"onmouseover"}, - &[_]string{"onmouseup"}, - &[_]string{"onoffline"}, - &[_]string{"ononline"}, - &[_]string{"onpagehide"}, - &[_]string{"onpageshow"}, - &[_]string{"onpause"}, - &[_]string{"onplay"}, - &[_]string{"onplaying"}, - &[_]string{"onpointercancel"}, - &[_]string{"onpointerdown"}, - &[_]string{"onpointerenter"}, - &[_]string{"onpointerleave"}, - &[_]string{"onpointermove"}, - &[_]string{"onpointerout"}, - &[_]string{"onpointerover"}, - &[_]string{"onpointerup"}, - &[_]string{"onpopstate"}, - &[_]string{"onprogress"}, - &[_]string{"onratechange"}, - &[_]string{"onrejectionhandled"}, - &[_]string{"onreset"}, - &[_]string{"onresize"}, - &[_]string{"onscroll"}, - &[_]string{"onseeked"}, - &[_]string{"onseeking"}, - &[_]string{"onselect"}, - &[_]string{"onstalled"}, - &[_]string{"onstorage"}, - &[_]string{"onsubmit"}, - &[_]string{"onsuspend"}, - &[_]string{"ontimeupdate"}, - &[_]string{"ontoggle"}, - &[_]string{"ontransitioncancel"}, - &[_]string{"ontransitionend"}, - &[_]string{"ontransitionrun"}, - &[_]string{"ontransitionstart"}, - &[_]string{"onunhandledrejection"}, - &[_]string{"onunload"}, - &[_]string{"onvolumechange"}, - &[_]string{"onwaiting"}, - &[_]string{"onwebkitanimationend"}, - &[_]string{"onwebkitanimationiteration"}, - &[_]string{"onwebkitanimationstart"}, - &[_]string{"onwebkittransitionend"}, - &[_]string{"onwheel"}, - &[_]string{"open"}, - &[_]string{"opener"}, - &[_]string{"origin"}, - &[_]string{"outerHeight"}, - &[_]string{"outerWidth"}, - &[_]string{"parent"}, - &[_]string{"performance"}, - &[_]string{"personalbar"}, - &[_]string{"postMessage"}, - &[_]string{"print"}, - &[_]string{"prompt"}, - &[_]string{"releaseEvents"}, - &[_]string{"requestAnimationFrame"}, - &[_]string{"resizeBy"}, - &[_]string{"resizeTo"}, - &[_]string{"screen"}, - &[_]string{"screenLeft"}, - &[_]string{"screenTop"}, - &[_]string{"screenX"}, - &[_]string{"screenY"}, - &[_]string{"scroll"}, - &[_]string{"scrollBy"}, - &[_]string{"scrollTo"}, - &[_]string{"scrollbars"}, - &[_]string{"self"}, - &[_]string{"speechSynthesis"}, - &[_]string{"status"}, - &[_]string{"statusbar"}, - &[_]string{"stop"}, - &[_]string{"toolbar"}, - &[_]string{"top"}, - &[_]string{"webkitURL"}, - &[_]string{"window"}, + .{ "AnalyserNode", pure_global_identifier_define }, + .{ "Animation", pure_global_identifier_define }, + .{ "AnimationEffect", pure_global_identifier_define }, + .{ "AnimationEvent", pure_global_identifier_define }, + .{ "AnimationPlaybackEvent", pure_global_identifier_define }, + .{ "AnimationTimeline", pure_global_identifier_define }, + .{ "Attr", pure_global_identifier_define }, + .{ "Audio", pure_global_identifier_define }, + .{ "AudioBuffer", pure_global_identifier_define }, + .{ "AudioBufferSourceNode", pure_global_identifier_define }, + .{ "AudioDestinationNode", pure_global_identifier_define }, + .{ "AudioListener", pure_global_identifier_define }, + .{ "AudioNode", pure_global_identifier_define }, + .{ "AudioParam", pure_global_identifier_define }, + .{ "AudioProcessingEvent", pure_global_identifier_define }, + .{ "AudioScheduledSourceNode", pure_global_identifier_define }, + .{ "BarProp", pure_global_identifier_define }, + .{ "BeforeUnloadEvent", pure_global_identifier_define }, + .{ "BiquadFilterNode", pure_global_identifier_define }, + .{ "Blob", pure_global_identifier_define }, + .{ "BlobEvent", pure_global_identifier_define }, + .{ "ByteLengthQueuingStrategy", pure_global_identifier_define }, + .{ "CDATASection", pure_global_identifier_define }, + .{ "CSS", pure_global_identifier_define }, + .{ "CanvasGradient", pure_global_identifier_define }, + .{ "CanvasPattern", pure_global_identifier_define }, + .{ "CanvasRenderingContext2D", pure_global_identifier_define }, + .{ "ChannelMergerNode", pure_global_identifier_define }, + .{ "ChannelSplitterNode", pure_global_identifier_define }, + .{ "CharacterData", pure_global_identifier_define }, + .{ "ClipboardEvent", pure_global_identifier_define }, + .{ "CloseEvent", pure_global_identifier_define }, + .{ "Comment", pure_global_identifier_define }, + .{ "CompositionEvent", pure_global_identifier_define }, + .{ "ConvolverNode", pure_global_identifier_define }, + .{ "CountQueuingStrategy", pure_global_identifier_define }, + .{ "Crypto", pure_global_identifier_define }, + .{ "CustomElementRegistry", pure_global_identifier_define }, + .{ "CustomEvent", pure_global_identifier_define }, + .{ "DOMException", pure_global_identifier_define }, + .{ "DOMImplementation", pure_global_identifier_define }, + .{ "DOMMatrix", pure_global_identifier_define }, + .{ "DOMMatrixReadOnly", pure_global_identifier_define }, + .{ "DOMParser", pure_global_identifier_define }, + .{ "DOMPoint", pure_global_identifier_define }, + .{ "DOMPointReadOnly", pure_global_identifier_define }, + .{ "DOMQuad", pure_global_identifier_define }, + .{ "DOMRect", pure_global_identifier_define }, + .{ "DOMRectList", pure_global_identifier_define }, + .{ "DOMRectReadOnly", pure_global_identifier_define }, + .{ "DOMStringList", pure_global_identifier_define }, + .{ "DOMStringMap", pure_global_identifier_define }, + .{ "DOMTokenList", pure_global_identifier_define }, + .{ "DataTransfer", pure_global_identifier_define }, + .{ "DataTransferItem", pure_global_identifier_define }, + .{ "DataTransferItemList", pure_global_identifier_define }, + .{ "DelayNode", pure_global_identifier_define }, + .{ "Document", pure_global_identifier_define }, + .{ "DocumentFragment", pure_global_identifier_define }, + .{ "DocumentTimeline", pure_global_identifier_define }, + .{ "DocumentType", pure_global_identifier_define }, + .{ "DragEvent", pure_global_identifier_define }, + .{ "DynamicsCompressorNode", pure_global_identifier_define }, + .{ "Element", pure_global_identifier_define }, + .{ "ErrorEvent", pure_global_identifier_define }, + .{ "EventSource", pure_global_identifier_define }, + .{ "File", pure_global_identifier_define }, + .{ "FileList", pure_global_identifier_define }, + .{ "FileReader", pure_global_identifier_define }, + .{ "FocusEvent", pure_global_identifier_define }, + .{ "FontFace", pure_global_identifier_define }, + .{ "FormData", pure_global_identifier_define }, + .{ "GainNode", pure_global_identifier_define }, + .{ "Gamepad", pure_global_identifier_define }, + .{ "GamepadButton", pure_global_identifier_define }, + .{ "GamepadEvent", pure_global_identifier_define }, + .{ "Geolocation", pure_global_identifier_define }, + .{ "GeolocationPositionError", pure_global_identifier_define }, + .{ "HTMLAllCollection", pure_global_identifier_define }, + .{ "HTMLAnchorElement", pure_global_identifier_define }, + .{ "HTMLAreaElement", pure_global_identifier_define }, + .{ "HTMLAudioElement", pure_global_identifier_define }, + .{ "HTMLBRElement", pure_global_identifier_define }, + .{ "HTMLBaseElement", pure_global_identifier_define }, + .{ "HTMLBodyElement", pure_global_identifier_define }, + .{ "HTMLButtonElement", pure_global_identifier_define }, + .{ "HTMLCanvasElement", pure_global_identifier_define }, + .{ "HTMLCollection", pure_global_identifier_define }, + .{ "HTMLDListElement", pure_global_identifier_define }, + .{ "HTMLDataElement", pure_global_identifier_define }, + .{ "HTMLDataListElement", pure_global_identifier_define }, + .{ "HTMLDetailsElement", pure_global_identifier_define }, + .{ "HTMLDirectoryElement", pure_global_identifier_define }, + .{ "HTMLDivElement", pure_global_identifier_define }, + .{ "HTMLDocument", pure_global_identifier_define }, + .{ "HTMLElement", pure_global_identifier_define }, + .{ "HTMLEmbedElement", pure_global_identifier_define }, + .{ "HTMLFieldSetElement", pure_global_identifier_define }, + .{ "HTMLFontElement", pure_global_identifier_define }, + .{ "HTMLFormControlsCollection", pure_global_identifier_define }, + .{ "HTMLFormElement", pure_global_identifier_define }, + .{ "HTMLFrameElement", pure_global_identifier_define }, + .{ "HTMLFrameSetElement", pure_global_identifier_define }, + .{ "HTMLHRElement", pure_global_identifier_define }, + .{ "HTMLHeadElement", pure_global_identifier_define }, + .{ "HTMLHeadingElement", pure_global_identifier_define }, + .{ "HTMLHtmlElement", pure_global_identifier_define }, + .{ "HTMLIFrameElement", pure_global_identifier_define }, + .{ "HTMLImageElement", pure_global_identifier_define }, + .{ "HTMLInputElement", pure_global_identifier_define }, + .{ "HTMLLIElement", pure_global_identifier_define }, + .{ "HTMLLabelElement", pure_global_identifier_define }, + .{ "HTMLLegendElement", pure_global_identifier_define }, + .{ "HTMLLinkElement", pure_global_identifier_define }, + .{ "HTMLMapElement", pure_global_identifier_define }, + .{ "HTMLMarqueeElement", pure_global_identifier_define }, + .{ "HTMLMediaElement", pure_global_identifier_define }, + .{ "HTMLMenuElement", pure_global_identifier_define }, + .{ "HTMLMetaElement", pure_global_identifier_define }, + .{ "HTMLMeterElement", pure_global_identifier_define }, + .{ "HTMLModElement", pure_global_identifier_define }, + .{ "HTMLOListElement", pure_global_identifier_define }, + .{ "HTMLObjectElement", pure_global_identifier_define }, + .{ "HTMLOptGroupElement", pure_global_identifier_define }, + .{ "HTMLOptionElement", pure_global_identifier_define }, + .{ "HTMLOptionsCollection", pure_global_identifier_define }, + .{ "HTMLOutputElement", pure_global_identifier_define }, + .{ "HTMLParagraphElement", pure_global_identifier_define }, + .{ "HTMLParamElement", pure_global_identifier_define }, + .{ "HTMLPictureElement", pure_global_identifier_define }, + .{ "HTMLPreElement", pure_global_identifier_define }, + .{ "HTMLProgressElement", pure_global_identifier_define }, + .{ "HTMLQuoteElement", pure_global_identifier_define }, + .{ "HTMLScriptElement", pure_global_identifier_define }, + .{ "HTMLSelectElement", pure_global_identifier_define }, + .{ "HTMLSlotElement", pure_global_identifier_define }, + .{ "HTMLSourceElement", pure_global_identifier_define }, + .{ "HTMLSpanElement", pure_global_identifier_define }, + .{ "HTMLStyleElement", pure_global_identifier_define }, + .{ "HTMLTableCaptionElement", pure_global_identifier_define }, + .{ "HTMLTableCellElement", pure_global_identifier_define }, + .{ "HTMLTableColElement", pure_global_identifier_define }, + .{ "HTMLTableElement", pure_global_identifier_define }, + .{ "HTMLTableRowElement", pure_global_identifier_define }, + .{ "HTMLTableSectionElement", pure_global_identifier_define }, + .{ "HTMLTemplateElement", pure_global_identifier_define }, + .{ "HTMLTextAreaElement", pure_global_identifier_define }, + .{ "HTMLTimeElement", pure_global_identifier_define }, + .{ "HTMLTitleElement", pure_global_identifier_define }, + .{ "HTMLTrackElement", pure_global_identifier_define }, + .{ "HTMLUListElement", pure_global_identifier_define }, + .{ "HTMLUnknownElement", pure_global_identifier_define }, + .{ "HTMLVideoElement", pure_global_identifier_define }, + .{ "HashChangeEvent", pure_global_identifier_define }, + .{ "Headers", pure_global_identifier_define }, + .{ "History", pure_global_identifier_define }, + .{ "IDBCursor", pure_global_identifier_define }, + .{ "IDBCursorWithValue", pure_global_identifier_define }, + .{ "IDBDatabase", pure_global_identifier_define }, + .{ "IDBFactory", pure_global_identifier_define }, + .{ "IDBIndex", pure_global_identifier_define }, + .{ "IDBKeyRange", pure_global_identifier_define }, + .{ "IDBObjectStore", pure_global_identifier_define }, + .{ "IDBOpenDBRequest", pure_global_identifier_define }, + .{ "IDBRequest", pure_global_identifier_define }, + .{ "IDBTransaction", pure_global_identifier_define }, + .{ "IDBVersionChangeEvent", pure_global_identifier_define }, + .{ "Image", pure_global_identifier_define }, + .{ "ImageData", pure_global_identifier_define }, + .{ "InputEvent", pure_global_identifier_define }, + .{ "IntersectionObserver", pure_global_identifier_define }, + .{ "IntersectionObserverEntry", pure_global_identifier_define }, + .{ "KeyboardEvent", pure_global_identifier_define }, + .{ "KeyframeEffect", pure_global_identifier_define }, + .{ "Location", pure_global_identifier_define }, + .{ "MediaCapabilities", pure_global_identifier_define }, + .{ "MediaElementAudioSourceNode", pure_global_identifier_define }, + .{ "MediaEncryptedEvent", pure_global_identifier_define }, + .{ "MediaError", pure_global_identifier_define }, + .{ "MediaList", pure_global_identifier_define }, + .{ "MediaQueryList", pure_global_identifier_define }, + .{ "MediaQueryListEvent", pure_global_identifier_define }, + .{ "MediaRecorder", pure_global_identifier_define }, + .{ "MediaSource", pure_global_identifier_define }, + .{ "MediaStream", pure_global_identifier_define }, + .{ "MediaStreamAudioDestinationNode", pure_global_identifier_define }, + .{ "MediaStreamAudioSourceNode", pure_global_identifier_define }, + .{ "MediaStreamTrack", pure_global_identifier_define }, + .{ "MediaStreamTrackEvent", pure_global_identifier_define }, + .{ "MimeType", pure_global_identifier_define }, + .{ "MimeTypeArray", pure_global_identifier_define }, + .{ "MouseEvent", pure_global_identifier_define }, + .{ "MutationEvent", pure_global_identifier_define }, + .{ "MutationObserver", pure_global_identifier_define }, + .{ "MutationRecord", pure_global_identifier_define }, + .{ "NamedNodeMap", pure_global_identifier_define }, + .{ "Navigator", pure_global_identifier_define }, + .{ "Node", pure_global_identifier_define }, + .{ "NodeFilter", pure_global_identifier_define }, + .{ "NodeIterator", pure_global_identifier_define }, + .{ "NodeList", pure_global_identifier_define }, + .{ "Notification", pure_global_identifier_define }, + .{ "OfflineAudioCompletionEvent", pure_global_identifier_define }, + .{ "Option", pure_global_identifier_define }, + .{ "OscillatorNode", pure_global_identifier_define }, + .{ "PageTransitionEvent", pure_global_identifier_define }, + .{ "Path2D", pure_global_identifier_define }, + .{ "Performance", pure_global_identifier_define }, + .{ "PerformanceEntry", pure_global_identifier_define }, + .{ "PerformanceMark", pure_global_identifier_define }, + .{ "PerformanceMeasure", pure_global_identifier_define }, + .{ "PerformanceNavigation", pure_global_identifier_define }, + .{ "PerformanceObserver", pure_global_identifier_define }, + .{ "PerformanceObserverEntryList", pure_global_identifier_define }, + .{ "PerformanceResourceTiming", pure_global_identifier_define }, + .{ "PerformanceTiming", pure_global_identifier_define }, + .{ "PeriodicWave", pure_global_identifier_define }, + .{ "Plugin", pure_global_identifier_define }, + .{ "PluginArray", pure_global_identifier_define }, + .{ "PointerEvent", pure_global_identifier_define }, + .{ "PopStateEvent", pure_global_identifier_define }, + .{ "ProcessingInstruction", pure_global_identifier_define }, + .{ "ProgressEvent", pure_global_identifier_define }, + .{ "PromiseRejectionEvent", pure_global_identifier_define }, + .{ "RTCCertificate", pure_global_identifier_define }, + .{ "RTCDTMFSender", pure_global_identifier_define }, + .{ "RTCDTMFToneChangeEvent", pure_global_identifier_define }, + .{ "RTCDataChannel", pure_global_identifier_define }, + .{ "RTCDataChannelEvent", pure_global_identifier_define }, + .{ "RTCIceCandidate", pure_global_identifier_define }, + .{ "RTCPeerConnection", pure_global_identifier_define }, + .{ "RTCPeerConnectionIceEvent", pure_global_identifier_define }, + .{ "RTCRtpReceiver", pure_global_identifier_define }, + .{ "RTCRtpSender", pure_global_identifier_define }, + .{ "RTCRtpTransceiver", pure_global_identifier_define }, + .{ "RTCSessionDescription", pure_global_identifier_define }, + .{ "RTCStatsReport", pure_global_identifier_define }, + .{ "RTCTrackEvent", pure_global_identifier_define }, + .{ "RadioNodeList", pure_global_identifier_define }, + .{ "Range", pure_global_identifier_define }, + .{ "ReadableStream", pure_global_identifier_define }, + .{ "Request", pure_global_identifier_define }, + .{ "ResizeObserver", pure_global_identifier_define }, + .{ "ResizeObserverEntry", pure_global_identifier_define }, + .{ "Response", pure_global_identifier_define }, + .{ "Screen", pure_global_identifier_define }, + .{ "ScriptProcessorNode", pure_global_identifier_define }, + .{ "SecurityPolicyViolationEvent", pure_global_identifier_define }, + .{ "Selection", pure_global_identifier_define }, + .{ "ShadowRoot", pure_global_identifier_define }, + .{ "SourceBuffer", pure_global_identifier_define }, + .{ "SourceBufferList", pure_global_identifier_define }, + .{ "SpeechSynthesisEvent", pure_global_identifier_define }, + .{ "SpeechSynthesisUtterance", pure_global_identifier_define }, + .{ "StaticRange", pure_global_identifier_define }, + .{ "Storage", pure_global_identifier_define }, + .{ "StorageEvent", pure_global_identifier_define }, + .{ "StyleSheet", pure_global_identifier_define }, + .{ "StyleSheetList", pure_global_identifier_define }, + .{ "Text", pure_global_identifier_define }, + .{ "TextMetrics", pure_global_identifier_define }, + .{ "TextTrack", pure_global_identifier_define }, + .{ "TextTrackCue", pure_global_identifier_define }, + .{ "TextTrackCueList", pure_global_identifier_define }, + .{ "TextTrackList", pure_global_identifier_define }, + .{ "TimeRanges", pure_global_identifier_define }, + .{ "TrackEvent", pure_global_identifier_define }, + .{ "TransitionEvent", pure_global_identifier_define }, + .{ "TreeWalker", pure_global_identifier_define }, + .{ "UIEvent", pure_global_identifier_define }, + .{ "VTTCue", pure_global_identifier_define }, + .{ "ValidityState", pure_global_identifier_define }, + .{ "VisualViewport", pure_global_identifier_define }, + .{ "WaveShaperNode", pure_global_identifier_define }, + .{ "WebGLActiveInfo", pure_global_identifier_define }, + .{ "WebGLBuffer", pure_global_identifier_define }, + .{ "WebGLContextEvent", pure_global_identifier_define }, + .{ "WebGLFramebuffer", pure_global_identifier_define }, + .{ "WebGLProgram", pure_global_identifier_define }, + .{ "WebGLQuery", pure_global_identifier_define }, + .{ "WebGLRenderbuffer", pure_global_identifier_define }, + .{ "WebGLRenderingContext", pure_global_identifier_define }, + .{ "WebGLSampler", pure_global_identifier_define }, + .{ "WebGLShader", pure_global_identifier_define }, + .{ "WebGLShaderPrecisionFormat", pure_global_identifier_define }, + .{ "WebGLSync", pure_global_identifier_define }, + .{ "WebGLTexture", pure_global_identifier_define }, + .{ "WebGLUniformLocation", pure_global_identifier_define }, + .{ "WebKitCSSMatrix", pure_global_identifier_define }, + .{ "WebSocket", pure_global_identifier_define }, + .{ "WheelEvent", pure_global_identifier_define }, + .{ "Window", pure_global_identifier_define }, + .{ "Worker", pure_global_identifier_define }, + .{ "XMLDocument", pure_global_identifier_define }, + .{ "XMLHttpRequest", pure_global_identifier_define }, + .{ "XMLHttpRequestEventTarget", pure_global_identifier_define }, + .{ "XMLHttpRequestUpload", pure_global_identifier_define }, + .{ "XMLSerializer", pure_global_identifier_define }, + .{ "XPathEvaluator", pure_global_identifier_define }, + .{ "XPathExpression", pure_global_identifier_define }, + .{ "XPathResult", pure_global_identifier_define }, + .{ "XSLTProcessor", pure_global_identifier_define }, + .{ "alert", pure_global_identifier_define }, + .{ "atob", pure_global_identifier_define }, + .{ "blur", pure_global_identifier_define }, + .{ "btoa", pure_global_identifier_define }, + .{ "cancelAnimationFrame", pure_global_identifier_define }, + .{ "captureEvents", pure_global_identifier_define }, + .{ "close", pure_global_identifier_define }, + .{ "closed", pure_global_identifier_define }, + .{ "confirm", pure_global_identifier_define }, + .{ "customElements", pure_global_identifier_define }, + .{ "devicePixelRatio", pure_global_identifier_define }, + .{ "document", pure_global_identifier_define }, + .{ "event", pure_global_identifier_define }, + .{ "fetch", pure_global_identifier_define }, + .{ "find", pure_global_identifier_define }, + .{ "focus", pure_global_identifier_define }, + .{ "frameElement", pure_global_identifier_define }, + .{ "frames", pure_global_identifier_define }, + .{ "getComputedStyle", pure_global_identifier_define }, + .{ "getSelection", pure_global_identifier_define }, + .{ "history", pure_global_identifier_define }, + .{ "indexedDB", pure_global_identifier_define }, + .{ "isSecureContext", pure_global_identifier_define }, + .{ "length", pure_global_identifier_define }, + .{ "location", pure_global_identifier_define }, + .{ "locationbar", pure_global_identifier_define }, + .{ "matchMedia", pure_global_identifier_define }, + .{ "menubar", pure_global_identifier_define }, + .{ "moveBy", pure_global_identifier_define }, + .{ "moveTo", pure_global_identifier_define }, + .{ "name", pure_global_identifier_define }, + .{ "navigator", pure_global_identifier_define }, + .{ "onabort", pure_global_identifier_define }, + .{ "onafterprint", pure_global_identifier_define }, + .{ "onanimationend", pure_global_identifier_define }, + .{ "onanimationiteration", pure_global_identifier_define }, + .{ "onanimationstart", pure_global_identifier_define }, + .{ "onbeforeprint", pure_global_identifier_define }, + .{ "onbeforeunload", pure_global_identifier_define }, + .{ "onblur", pure_global_identifier_define }, + .{ "oncanplay", pure_global_identifier_define }, + .{ "oncanplaythrough", pure_global_identifier_define }, + .{ "onchange", pure_global_identifier_define }, + .{ "onclick", pure_global_identifier_define }, + .{ "oncontextmenu", pure_global_identifier_define }, + .{ "oncuechange", pure_global_identifier_define }, + .{ "ondblclick", pure_global_identifier_define }, + .{ "ondrag", pure_global_identifier_define }, + .{ "ondragend", pure_global_identifier_define }, + .{ "ondragenter", pure_global_identifier_define }, + .{ "ondragleave", pure_global_identifier_define }, + .{ "ondragover", pure_global_identifier_define }, + .{ "ondragstart", pure_global_identifier_define }, + .{ "ondrop", pure_global_identifier_define }, + .{ "ondurationchange", pure_global_identifier_define }, + .{ "onemptied", pure_global_identifier_define }, + .{ "onended", pure_global_identifier_define }, + .{ "onerror", pure_global_identifier_define }, + .{ "onfocus", pure_global_identifier_define }, + .{ "ongotpointercapture", pure_global_identifier_define }, + .{ "onhashchange", pure_global_identifier_define }, + .{ "oninput", pure_global_identifier_define }, + .{ "oninvalid", pure_global_identifier_define }, + .{ "onkeydown", pure_global_identifier_define }, + .{ "onkeypress", pure_global_identifier_define }, + .{ "onkeyup", pure_global_identifier_define }, + .{ "onlanguagechange", pure_global_identifier_define }, + .{ "onload", pure_global_identifier_define }, + .{ "onloadeddata", pure_global_identifier_define }, + .{ "onloadedmetadata", pure_global_identifier_define }, + .{ "onloadstart", pure_global_identifier_define }, + .{ "onlostpointercapture", pure_global_identifier_define }, + .{ "onmessage", pure_global_identifier_define }, + .{ "onmousedown", pure_global_identifier_define }, + .{ "onmouseenter", pure_global_identifier_define }, + .{ "onmouseleave", pure_global_identifier_define }, + .{ "onmousemove", pure_global_identifier_define }, + .{ "onmouseout", pure_global_identifier_define }, + .{ "onmouseover", pure_global_identifier_define }, + .{ "onmouseup", pure_global_identifier_define }, + .{ "onoffline", pure_global_identifier_define }, + .{ "ononline", pure_global_identifier_define }, + .{ "onpagehide", pure_global_identifier_define }, + .{ "onpageshow", pure_global_identifier_define }, + .{ "onpause", pure_global_identifier_define }, + .{ "onplay", pure_global_identifier_define }, + .{ "onplaying", pure_global_identifier_define }, + .{ "onpointercancel", pure_global_identifier_define }, + .{ "onpointerdown", pure_global_identifier_define }, + .{ "onpointerenter", pure_global_identifier_define }, + .{ "onpointerleave", pure_global_identifier_define }, + .{ "onpointermove", pure_global_identifier_define }, + .{ "onpointerout", pure_global_identifier_define }, + .{ "onpointerover", pure_global_identifier_define }, + .{ "onpointerup", pure_global_identifier_define }, + .{ "onpopstate", pure_global_identifier_define }, + .{ "onprogress", pure_global_identifier_define }, + .{ "onratechange", pure_global_identifier_define }, + .{ "onrejectionhandled", pure_global_identifier_define }, + .{ "onreset", pure_global_identifier_define }, + .{ "onresize", pure_global_identifier_define }, + .{ "onscroll", pure_global_identifier_define }, + .{ "onseeked", pure_global_identifier_define }, + .{ "onseeking", pure_global_identifier_define }, + .{ "onselect", pure_global_identifier_define }, + .{ "onstalled", pure_global_identifier_define }, + .{ "onstorage", pure_global_identifier_define }, + .{ "onsubmit", pure_global_identifier_define }, + .{ "onsuspend", pure_global_identifier_define }, + .{ "ontimeupdate", pure_global_identifier_define }, + .{ "ontoggle", pure_global_identifier_define }, + .{ "ontransitioncancel", pure_global_identifier_define }, + .{ "ontransitionend", pure_global_identifier_define }, + .{ "ontransitionrun", pure_global_identifier_define }, + .{ "ontransitionstart", pure_global_identifier_define }, + .{ "onunhandledrejection", pure_global_identifier_define }, + .{ "onunload", pure_global_identifier_define }, + .{ "onvolumechange", pure_global_identifier_define }, + .{ "onwaiting", pure_global_identifier_define }, + .{ "onwebkitanimationend", pure_global_identifier_define }, + .{ "onwebkitanimationiteration", pure_global_identifier_define }, + .{ "onwebkitanimationstart", pure_global_identifier_define }, + .{ "onwebkittransitionend", pure_global_identifier_define }, + .{ "onwheel", pure_global_identifier_define }, + .{ "open", pure_global_identifier_define }, + .{ "opener", pure_global_identifier_define }, + .{ "origin", pure_global_identifier_define }, + .{ "outerHeight", pure_global_identifier_define }, + .{ "outerWidth", pure_global_identifier_define }, + .{ "parent", pure_global_identifier_define }, + .{ "performance", pure_global_identifier_define }, + .{ "personalbar", pure_global_identifier_define }, + .{ "postMessage", pure_global_identifier_define }, + .{ "print", pure_global_identifier_define }, + .{ "prompt", pure_global_identifier_define }, + .{ "releaseEvents", pure_global_identifier_define }, + .{ "requestAnimationFrame", pure_global_identifier_define }, + .{ "resizeBy", pure_global_identifier_define }, + .{ "resizeTo", pure_global_identifier_define }, + .{ "screen", pure_global_identifier_define }, + .{ "screenLeft", pure_global_identifier_define }, + .{ "screenTop", pure_global_identifier_define }, + .{ "screenX", pure_global_identifier_define }, + .{ "screenY", pure_global_identifier_define }, + .{ "scroll", pure_global_identifier_define }, + .{ "scrollBy", pure_global_identifier_define }, + .{ "scrollTo", pure_global_identifier_define }, + .{ "scrollbars", pure_global_identifier_define }, + .{ "self", pure_global_identifier_define }, + .{ "speechSynthesis", pure_global_identifier_define }, + .{ "status", pure_global_identifier_define }, + .{ "statusbar", pure_global_identifier_define }, + .{ "stop", pure_global_identifier_define }, + .{ "toolbar", pure_global_identifier_define }, + .{ "top", pure_global_identifier_define }, + .{ "webkitURL", pure_global_identifier_define }, + .{ "window", pure_global_identifier_define }, }; + +pub const pure_global_identifier_map = bun.ComptimeStringMap(defines.IdentifierDefine, pure_global_identifiers); diff --git a/src/defines.zig b/src/defines.zig index f3a6c5e2bb94..8bc57d8c028f 100644 --- a/src/defines.zig +++ b/src/defines.zig @@ -17,6 +17,7 @@ const C = bun.C; const Ref = @import("ast/base.zig").Ref; const GlobalDefinesKey = @import("./defines-table.zig").GlobalDefinesKey; +const table = @import("./defines-table.zig"); const Globals = struct { pub const Undefined = js_ast.E.Undefined{}; @@ -37,13 +38,6 @@ pub const RawDefines = bun.StringArrayHashMap(string); pub const UserDefines = bun.StringHashMap(DefineData); pub const UserDefinesArray = bun.StringArrayHashMap(DefineData); -const Rewrites = struct { - pub const global = "global"; - pub const globalThis = "globalThis"; -}; - -var globalThisIdentifier = js_ast.E.Identifier{}; - pub const DefineData = struct { value: js_ast.Expr.Data, valueless: bool = false, @@ -193,16 +187,24 @@ pub const DotDefine = struct { }; // var nan_val = try allocator.create(js_ast.E.Number); -var nan_val = js_ast.E.Number{ .value = std.math.nan_f64 }; -var inf_val = js_ast.E.Number{ .value = std.math.inf_f64 }; +const nan_val = js_ast.E.Number{ .value = std.math.nan_f64 }; +const inf_val = js_ast.E.Number{ .value = std.math.inf_f64 }; pub const Define = struct { identifiers: bun.StringHashMap(IdentifierDefine), dots: bun.StringHashMap([]DotDefine), allocator: std.mem.Allocator, + pub fn forIdentifier(this: *const Define, name: []const u8) ?IdentifierDefine { + if (this.identifiers.get(name)) |data| { + return data; + } + + return table.pure_global_identifier_map.get(name); + } + pub fn insertFromIterator(define: *Define, allocator: std.mem.Allocator, comptime Iterator: type, iter: Iterator) !void { - while (iter.next()) |user_define| { + outer: while (iter.next()) |user_define| { const user_define_key = user_define.key_ptr.*; // If it has a dot, then it's a DotDefine. // e.g. process.env.NODE_ENV @@ -217,36 +219,34 @@ pub const Define = struct { parts[i] = split; } parts[i] = tail; - var didFind = false; var initial_values: []DotDefine = &([_]DotDefine{}); // "NODE_ENV" - if (define.dots.getEntry(tail)) |entry| { - for (entry.value_ptr.*) |*part| { + var gpe_entry = try define.dots.getOrPut(tail); + + if (gpe_entry.found_existing) { + for (gpe_entry.value_ptr.*) |*part| { // ["process", "env"] === ["process", "env"] (if that actually worked) if (arePartsEqual(part.parts, parts)) { part.data = part.data.merge(user_define.value_ptr.*); - didFind = true; - break; + continue :outer; } } - initial_values = entry.value_ptr.*; + initial_values = gpe_entry.value_ptr.*; } - if (!didFind) { - var list = try std.ArrayList(DotDefine).initCapacity(allocator, initial_values.len + 1); - if (initial_values.len > 0) { - list.appendSliceAssumeCapacity(initial_values); - } - - list.appendAssumeCapacity(DotDefine{ - .data = user_define.value_ptr.*, - // TODO: do we need to allocate this? - .parts = parts, - }); - try define.dots.put(tail, try list.toOwnedSlice()); + var list = try std.ArrayList(DotDefine).initCapacity(allocator, initial_values.len + 1); + if (initial_values.len > 0) { + list.appendSliceAssumeCapacity(initial_values); } + + list.appendAssumeCapacity(DotDefine{ + .data = user_define.value_ptr.*, + // TODO: do we need to allocate this? + .parts = parts, + }); + gpe_entry.value_ptr.* = try list.toOwnedSlice(); } else { // e.g. IS_BROWSER @@ -260,55 +260,37 @@ pub const Define = struct { define.allocator = allocator; define.identifiers = bun.StringHashMap(IdentifierDefine).init(allocator); define.dots = bun.StringHashMap([]DotDefine).init(allocator); - try define.identifiers.ensureTotalCapacity(641 + 2 + 1 + 2); try define.dots.ensureTotalCapacity(124); - var val = js_ast.Expr.Data{ - .e_undefined = .{}, + const value_define = DefineData{ + .value = .{ .e_undefined = .{} }, + .valueless = true, + .can_be_removed_if_unused = true, }; - - var value_define = DefineData{ .value = val, .valueless = true }; // Step 1. Load the globals into the hash tables for (GlobalDefinesKey) |global| { - if (global.len == 1) { - define.identifiers.putAssumeCapacityNoClobber(global[0], value_define); + const key = global[global.len - 1]; + var gpe = try define.dots.getOrPut(key); + if (gpe.found_existing) { + var list = try std.ArrayList(DotDefine).initCapacity(allocator, gpe.value_ptr.*.len + 1); + list.appendSliceAssumeCapacity(gpe.value_ptr.*); + list.appendAssumeCapacity(DotDefine{ + .parts = global[0..global.len], + .data = value_define, + }); + + gpe.value_ptr.* = try list.toOwnedSlice(); } else { - const key = global[global.len - 1]; - // TODO: move this to comptime - if (define.dots.getEntry(key)) |entry| { - var list = try std.ArrayList(DotDefine).initCapacity(allocator, entry.value_ptr.*.len + 1); - list.appendSliceAssumeCapacity(entry.value_ptr.*); - list.appendAssumeCapacity(DotDefine{ - .parts = global[0..global.len], - .data = value_define, - }); - - define.dots.putAssumeCapacity(key, try list.toOwnedSlice()); - } else { - var list = try std.ArrayList(DotDefine).initCapacity(allocator, 1); - list.appendAssumeCapacity(DotDefine{ - .parts = global[0..global.len], - .data = value_define, - }); + var list = try std.ArrayList(DotDefine).initCapacity(allocator, 1); + list.appendAssumeCapacity(DotDefine{ + .parts = global[0..global.len], + .data = value_define, + }); - define.dots.putAssumeCapacity(key, try list.toOwnedSlice()); - } + gpe.value_ptr.* = try list.toOwnedSlice(); } } - // Step 2. Swap in certain literal values because those can be constant folded - define.identifiers.putAssumeCapacity("undefined", .{ - .value = val, - .valueless = false, - .can_be_removed_if_unused = true, - }); - define.identifiers.putAssumeCapacity("NaN", DefineData{ - .value = js_ast.Expr.Data{ .e_number = nan_val }, - }); - define.identifiers.putAssumeCapacity("Infinity", DefineData{ - .value = js_ast.Expr.Data{ .e_number = inf_val }, - }); - // Step 3. Load user data into hash tables // At this stage, user data has already been validated. if (_user_defines) |user_defines| { @@ -323,13 +305,6 @@ pub const Define = struct { try define.insertFromIterator(allocator, @TypeOf(&iter), &iter); } - { - var global_entry = define.identifiers.getOrPutAssumeCapacity(Rewrites.global); - if (!global_entry.found_existing) { - global_entry.value_ptr.* = DefineData{ .value = js_ast.Expr.Data{ .e_identifier = globalThisIdentifier }, .original_name = Rewrites.globalThis }; - } - } - return define; } }; From 493b1bd79a29f21a61a4414ba6baceeb938f8d90 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Sun, 12 Mar 2023 06:53:50 -0700 Subject: [PATCH 089/255] Do not rename unbound symbols --- src/js_ast.zig | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/js_ast.zig b/src/js_ast.zig index de0214c7ae51..385e79341e10 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -784,11 +784,12 @@ pub const Symbol = struct { }; pub fn slotNamespace(this: *const Symbol) SlotNamespace { - if (this.must_not_be_renamed) { + const kind = this.kind; + + if (kind == .unbound or this.must_not_be_renamed) { return .must_not_be_renamed; } - const kind = this.kind; if (kind.isPrivate()) { return .private_name; } From 1b1570a231c081a95296998f91dbf8a35fa5806a Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Sun, 12 Mar 2023 06:54:38 -0700 Subject: [PATCH 090/255] Clean up dot defines a little more --- src/js_parser.zig | 70 ++++++++++++++--------------------------------- 1 file changed, 21 insertions(+), 49 deletions(-) diff --git a/src/js_parser.zig b/src/js_parser.zig index 2257bdc5c422..22ce3b726745 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -4435,6 +4435,10 @@ fn NewParser_( } pub fn findSymbol(p: *P, loc: logger.Loc, name: string) !FindSymbolResult { + return findSymbolWithRecordUsage(p, loc, name, true); + } + + pub fn findSymbolWithRecordUsage(p: *P, loc: logger.Loc, name: string, record_usage: bool) !FindSymbolResult { var declare_loc: logger.Loc = undefined; var is_inside_with_scope = false; // This function can show up in profiling. @@ -4499,7 +4503,7 @@ fn NewParser_( } // Track how many times we've referenced this symbol - p.recordUsage(ref); + if (record_usage) p.recordUsage(ref); return FindSymbolResult{ .ref = ref, @@ -13373,8 +13377,8 @@ fn NewParser_( // Substitute user-specified defines for unbound symbols if (p.symbols.items[e_.ref.innerIndex()].kind == .unbound and !result.is_inside_with_scope and !is_delete_target) { - if (p.define.identifiers.get(name)) |def| { - if (!def.isUndefined()) { + if (p.define.forIdentifier(name)) |def| { + if (!def.valueless) { const newvalue = p.valueForDefine(expr.loc, in.assign_target, is_delete_target, &def); // Don't substitute an identifier for a non-identifier if this is an @@ -14324,7 +14328,7 @@ fn NewParser_( if (p.define.dots.get(literal)) |parts| { for (parts) |define| { if (p.isDotDefineMatch(expr, define.parts)) { - if (!define.data.isUndefined()) { + if (!define.data.valueless) { return p.valueForDefine(expr.loc, in.assign_target, is_delete_target, &define.data); } @@ -14494,7 +14498,7 @@ fn NewParser_( if (p.isDotDefineMatch(expr, define.parts)) { if (in.assign_target == .none) { // Substitute user-specified defines - if (!define.data.isUndefined()) { + if (!define.data.valueless) { return p.valueForDefine(expr.loc, in.assign_target, is_delete_target, &define.data); } } @@ -17568,33 +17572,22 @@ fn NewParser_( }; } - fn isDotDefineMatchInner(p: *P, expr: Expr, parts: []const string) ?Ref { + fn isDotDefineMatch(p: *P, expr: Expr, parts: []const string) bool { switch (expr.data) { .e_dot => |ex| { if (parts.len > 1) { if (ex.optional_chain != null) { - return null; + return false; } // Intermediates must be dot expressions const last = parts.len - 1; const is_tail_match = strings.eql(parts[last], ex.name); - if (is_tail_match) { - if (p.isDotDefineMatchInner(ex.target, parts[0..last])) |ref| { - if (last == 0) { - ex.target.data.e_identifier.ref = ref; - } - - return ref; - } - } + return is_tail_match and p.isDotDefineMatch(ex.target, parts[0..last]); } }, .e_import_meta => { - if (parts.len == 2 and strings.eqlComptime(parts[0], "import") and strings.eqlComptime(parts[1], "meta")) { - p.is_dot_define_match = true; - return null; - } + return parts.len == 2 and strings.eqlComptime(parts[0], "import") and strings.eqlComptime(parts[1], "meta"); }, // Note: this behavior differs from esbuild // esbuild does not try to match index accessors @@ -17603,20 +17596,13 @@ fn NewParser_( .e_index => |index| { if (parts.len > 1 and index.index.data == .e_string and index.index.data.e_string.isUTF8()) { if (index.optional_chain != null) { - return null; + return false; } const last = parts.len - 1; const is_tail_match = strings.eql(parts[last], index.index.data.e_string.slice(p.allocator)); - if (is_tail_match) { - if (p.isDotDefineMatchInner(index.target, parts[0..last])) |ref| { - if (last == 0) { - index.target.data.e_identifier.ref = ref; - } - - return ref; - } - } + return is_tail_match and + p.isDotDefineMatch(index.target, parts[0..last]); } }, .e_identifier => |ex| { @@ -17625,37 +17611,23 @@ fn NewParser_( if (parts.len == 1) { const name = p.loadNameFromRef(ex.ref); if (!strings.eql(name, parts[0])) { - return null; + return false; } - const result = p.findSymbol(expr.loc, name) catch return false; + const result = p.findSymbolWithRecordUsage(expr.loc, name, false) catch return false; // We must not be in a "with" statement scope if (result.is_inside_with_scope) { - p.is_dot_define_match = false; - - // preserve the updated Ref - return result.ref; + return false; } - // The last symbol must be unbound - p.is_dot_define_match = p.symbols.items[result.ref.innerIndex()].kind == .unbound; - - // preserve the updated Ref - return result.ref; + return p.symbols.items[result.ref.innerIndex()].kind == .unbound; } }, else => {}, } - return null; - } - - fn isDotDefineMatch(p: *P, expr: Expr, parts: []const string) bool { - _ = p.isDotDefineMatchInner(expr, parts); - const result = p.is_dot_define_match; - p.is_dot_define_match = false; - return result; + return false; } fn visitBinding(p: *P, binding: BindingNodeIndex, duplicate_arg_check: ?*StringVoidMap) void { From 2284578ebba1efd0cfe86018a996264273999fc8 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Sun, 12 Mar 2023 07:05:36 -0700 Subject: [PATCH 091/255] Make the generated names prettier --- src/js_parser.zig | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/js_parser.zig b/src/js_parser.zig index 22ce3b726745..3bb3d95ee4d1 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -5374,7 +5374,7 @@ fn NewParser_( // This won't work for adversarial cases pub fn resolveGeneratedSymbol(p: *P, generated_symbol: *GeneratedSymbol) void { - if (generated_symbol.ref.isNull()) return; + if (generated_symbol.ref.isNull() or p.options.bundle) return; if (p.symbols.items[generated_symbol.primary.innerIndex()].use_count_estimate == 0 and p.symbols.items[generated_symbol.primary.innerIndex()].link.isNull()) @@ -9696,6 +9696,15 @@ fn NewParser_( fn declareGeneratedSymbol(p: *P, kind: Symbol.Kind, comptime name: string) !GeneratedSymbol { const static = @field(StaticSymbolName.List, name); + if (p.options.bundle) { + const ref = try declareSymbolMaybeGenerated(p, .other, logger.Loc.Empty, static.primary, true); + return GeneratedSymbol{ + .backup = ref, + .primary = ref, + .ref = ref, + }; + } + return GeneratedSymbol{ .backup = try declareSymbolMaybeGenerated(p, .other, logger.Loc.Empty, static.backup, true), .primary = try declareSymbolMaybeGenerated(p, .other, logger.Loc.Empty, static.primary, true), From 6288584e6b9ff71bbd58c27626a4613ee4a8a557 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Sun, 12 Mar 2023 10:35:43 -0700 Subject: [PATCH 092/255] Workaround runtime symbol missing issue --- src/bundler/bundle_v2.zig | 76 +++++++++++++++++++++++---------------- src/js_parser.zig | 72 +++++++++++++++++++++---------------- 2 files changed, 88 insertions(+), 60 deletions(-) diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index e4c531125bfd..960a07cdc0ff 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -1394,13 +1394,22 @@ const LinkerGraph = struct { var entry = overlay.getOrPut(self.graph.allocator, ref) catch unreachable; if (!entry.found_existing) { - entry.value_ptr.* = bun.from( - BabyList(u32), - self.graph.allocator, - &[_]u32{ - self.part_id, - }, - ) catch unreachable; + if (self.graph.ast.items(.top_level_symbols_to_parts)[self.id].get(ref)) |original_parts| { + var list = std.ArrayList(u32).init(self.graph.allocator); + list.ensureTotalCapacityPrecise(original_parts.len + 1) catch unreachable; + list.appendSliceAssumeCapacity(original_parts.slice()); + list.appendAssumeCapacity(self.part_id); + + entry.value_ptr.* = BabyList(u32).init(list.items); + } else { + entry.value_ptr.* = bun.from( + BabyList(u32), + self.graph.allocator, + &[_]u32{ + self.part_id, + }, + ) catch unreachable; + } } else { entry.value_ptr.push(self.graph.allocator, self.part_id) catch unreachable; } @@ -1455,7 +1464,8 @@ const LinkerGraph = struct { // Track that this specific symbol was imported if (source_index_to_import_from.get() != source_index) { - try g.meta.items(.imports_to_bind)[source_index].put(g.allocator, ref, .{ + var to_bind = &g.meta.items(.imports_to_bind)[source_index]; + try to_bind.put(g.allocator, ref, .{ .data = .{ .source_index = source_index_to_import_from, .import_ref = ref, @@ -2244,10 +2254,6 @@ const LinkerContext = struct { const source_index = source_index_.get(); const id = source_index; - // ignore the runtime - if (source_index == Index.runtime.get()) - continue; - // not a JS ast or empty if (id >= named_imports.len) { continue; @@ -2290,7 +2296,6 @@ const LinkerContext = struct { wrapped_ref, &wrapper_part_indices[id], source_index, - id, ); } } @@ -5450,7 +5455,7 @@ const LinkerContext = struct { stmt.loc, ); }, - .s_class, .s_function => { + .s_function => { stmts.outside_wrapper_prefix.append(stmt) catch unreachable; continue; }, @@ -5843,24 +5848,24 @@ const LinkerContext = struct { if (part.stmts.len > 0) @tagName(part.stmts[0].data) else @tagName(Stmt.empty().data), }); + // Include the file containing this part + c.markFileLiveForTreeShaking( + id, + side_effects, + parts, + import_records, + entry_point_kinds, + ); + for (part.dependencies.slice()) |dependency| { - const _id = dependency.source_index.get(); - if (c.markPartLiveForTreeShaking( + _ = c.markPartLiveForTreeShaking( dependency.part_index, - _id, + dependency.source_index.get(), side_effects, parts, import_records, entry_point_kinds, - )) { - c.markFileLiveForTreeShaking( - _id, - side_effects, - parts, - import_records, - entry_point_kinds, - ); - } + ); } return true; @@ -6120,7 +6125,6 @@ const LinkerContext = struct { wrapper_ref: Ref, wrapper_part_index: *Index, source_index: Index.Int, - id: u32, ) void { switch (wrap) { // If this is a CommonJS file, we're going to need to generate a wrapper @@ -6140,6 +6144,18 @@ const LinkerContext = struct { .cjs => { const common_js_parts = c.topLevelSymbolsToPartsForRuntime(c.cjs_runtime_ref); + var total_dependencies_count = common_js_parts.len; + var runtime_parts = c.graph.ast.items(.parts)[Index.runtime.get()].slice(); + + for (common_js_parts) |part_id| { + var part: *js_ast.Part = &runtime_parts[part_id]; + var symbol_refs = part.symbol_uses.keys(); + for (symbol_refs) |ref| { + if (ref.eql(c.cjs_runtime_ref)) continue; + total_dependencies_count += c.topLevelSymbolsToPartsForRuntime(ref).len; + } + } + // generate a dummy part that depends on the "__commonJS" symbol var dependencies = c.allocator.alloc(js_ast.Dependency, common_js_parts.len) catch unreachable; for (common_js_parts, dependencies) |part, *cjs| { @@ -6162,9 +6178,9 @@ const LinkerContext = struct { .declared_symbols = js_ast.DeclaredSymbol.List.fromSlice( c.allocator, &[_]js_ast.DeclaredSymbol{ - .{ .ref = c.graph.ast.items(.exports_ref)[id], .is_top_level = true }, - .{ .ref = c.graph.ast.items(.module_ref)[id].?, .is_top_level = true }, - .{ .ref = c.graph.ast.items(.wrapper_ref)[id].?, .is_top_level = true }, + .{ .ref = c.graph.ast.items(.exports_ref)[source_index], .is_top_level = true }, + .{ .ref = c.graph.ast.items(.module_ref)[source_index].?, .is_top_level = true }, + .{ .ref = c.graph.ast.items(.wrapper_ref)[source_index].?, .is_top_level = true }, }, ) catch unreachable, .dependencies = Dependency.List.init(dependencies), diff --git a/src/js_parser.zig b/src/js_parser.zig index 3bb3d95ee4d1..09209b500fa1 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -3737,8 +3737,6 @@ fn NewParser_( allow_in: bool = false, allow_private_identifiers: bool = false, - is_dot_define_match: bool = false, - has_top_level_return: bool = false, latest_return_had_semicolon: bool = false, has_import_meta: bool = false, @@ -4438,7 +4436,7 @@ fn NewParser_( return findSymbolWithRecordUsage(p, loc, name, true); } - pub fn findSymbolWithRecordUsage(p: *P, loc: logger.Loc, name: string, record_usage: bool) !FindSymbolResult { + pub fn findSymbolWithRecordUsage(p: *P, loc: logger.Loc, name: string, comptime record_usage: bool) !FindSymbolResult { var declare_loc: logger.Loc = undefined; var is_inside_with_scope = false; // This function can show up in profiling. @@ -4475,6 +4473,14 @@ fn NewParser_( // Allocate an "unbound" symbol p.checkForNonBMPCodePoint(loc, name); + if (comptime !record_usage) { + return FindSymbolResult{ + .ref = Ref.None, + .declare_loc = loc, + .is_inside_with_scope = is_inside_with_scope, + }; + } + var gpe = p.module_scope.getOrPutMemberWithHash(allocator, name, hash) catch unreachable; // I don't think this happens? @@ -4503,7 +4509,7 @@ fn NewParser_( } // Track how many times we've referenced this symbol - if (record_usage) p.recordUsage(ref); + if (comptime record_usage) p.recordUsage(ref); return FindSymbolResult{ .ref = ref, @@ -17596,7 +17602,7 @@ fn NewParser_( } }, .e_import_meta => { - return parts.len == 2 and strings.eqlComptime(parts[0], "import") and strings.eqlComptime(parts[1], "meta"); + return (parts.len == 2 and strings.eqlComptime(parts[0], "import") and strings.eqlComptime(parts[1], "meta")); }, // Note: this behavior differs from esbuild // esbuild does not try to match index accessors @@ -17610,8 +17616,7 @@ fn NewParser_( const last = parts.len - 1; const is_tail_match = strings.eql(parts[last], index.index.data.e_string.slice(p.allocator)); - return is_tail_match and - p.isDotDefineMatch(index.target, parts[0..last]); + return is_tail_match and p.isDotDefineMatch(index.target, parts[0..last]); } }, .e_identifier => |ex| { @@ -17630,7 +17635,13 @@ fn NewParser_( return false; } - return p.symbols.items[result.ref.innerIndex()].kind == .unbound; + return + // TODO: figure out why this is needed when bundling + // The problem is all the top-level vars are getting removed when they're not actually side effect free + !p.source.index.isRuntime() and + // when there's actually no symbol by that name, we return Ref.None + // If a symbol had already existed by that name, we return .unbound + (result.ref.isNull() or p.symbols.items[result.ref.innerIndex()].kind == .unbound); } }, else => {}, @@ -18555,9 +18566,9 @@ fn NewParser_( var parts = _parts; // Insert an import statement for any runtime imports we generated - if (p.options.tree_shaking and p.options.features.trim_unused_imports) { - p.treeShake(&parts, false); - } + // if (p.options.tree_shaking and p.options.features.trim_unused_imports) { + // p.treeShake(&parts, false); + // } const bundling = p.options.bundle; var parts_end: usize = @as(usize, @boolToInt(bundling)); @@ -18633,9 +18644,9 @@ fn NewParser_( } } - if (p.options.tree_shaking) { - p.treeShake(&parts, commonjs_wrapper_expr != null or p.options.features.hot_module_reloading or p.options.enable_legacy_bundling); - } + // if (p.options.tree_shaking) { + // p.treeShake(&parts, commonjs_wrapper_expr != null or p.options.features.hot_module_reloading or p.options.enable_legacy_bundling); + // } if (commonjs_wrapper_expr) |commonjs_wrapper| { var part = &parts[parts.len - 1]; @@ -19125,22 +19136,6 @@ fn NewParser_( } else if (p.options.features.hot_module_reloading) {} var top_level_symbols_to_parts = js_ast.Ast.TopLevelSymbolToParts{}; var top_level = &top_level_symbols_to_parts; - const wrapper_ref = brk: { - if (p.options.bundle) { - break :brk p.newSymbol( - .other, - std.fmt.allocPrint( - p.allocator, - "require_{any}", - .{ - p.source.fmtIdentifier(), - }, - ) catch unreachable, - ) catch unreachable; - } - - break :brk @as(?Ref, null); - }; if (p.options.bundle) { const Ctx = struct { @@ -19194,6 +19189,23 @@ fn NewParser_( } } + const wrapper_ref = brk: { + if (p.options.bundle) { + break :brk p.newSymbol( + .other, + std.fmt.allocPrint( + p.allocator, + "require_{any}", + .{ + p.source.fmtIdentifier(), + }, + ) catch unreachable, + ) catch unreachable; + } + + break :brk @as(?Ref, null); + }; + return .{ .allocator = p.allocator, .runtime_imports = p.runtime_imports, From 42e69a91af589e18dff3e4f4ab13e75828c3daeb Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Mon, 13 Mar 2023 00:30:47 -0700 Subject: [PATCH 093/255] Fail the build on errors --- src/bundler/bundle_v2.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 960a07cdc0ff..9a105596a32d 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -525,7 +525,7 @@ pub const BundleV2 = struct { this.graph.pool.pool.schedule(batch); this.waitForParse(); - if (this.bundler.log.errors > 0) { + if (this.bundler.log.msgs.items.len > 0) { return error.BuildFailed; } From 9406f8440070bbe66aae974be197dc4543d7afd2 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Mon, 13 Mar 2023 03:31:52 -0700 Subject: [PATCH 094/255] Support export * from --- src/bundler/bundle_v2.zig | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 9a105596a32d..10e3abf83580 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -2218,8 +2218,7 @@ const LinkerContext = struct { .import_records_list = import_records_list, .export_star_records = export_star_import_records, - // TODO: - .imports_to_bind = &.{}, + .imports_to_bind = this.graph.meta.items(.imports_to_bind), .source_index_stack = std.ArrayList(u32).initCapacity(this.allocator, 32) catch unreachable, .exports_kind = exports_kind, @@ -4955,10 +4954,10 @@ const LinkerContext = struct { stmt.loc, ), ); - - // Remove the export star statement - continue; } + + // Remove the export star statement + continue; } }, @@ -5974,10 +5973,10 @@ const LinkerContext = struct { }, .no_match => { // Report mismatched imports and exports - const symbol = c.graph.symbols.get(tracker.import_ref).?; - const named_import: js_ast.NamedImport = named_imports[other_id].get(tracker.import_ref).?; + const symbol = c.graph.symbols.get(prev_import_ref).?; + const named_import: js_ast.NamedImport = named_imports[prev_source_index].get(prev_import_ref).?; - const source = c.source_(tracker.source_index.get()); + const source = c.source_(prev_source_index); const next_source = c.source_(next_tracker.source_index.get()); const r = source.rangeOfIdentifier(named_import.alias_loc.?); @@ -6527,6 +6526,7 @@ const LinkerContext = struct { } this.source_index_stack.append(source_index) catch unreachable; + const stack_end_pos = this.source_index_stack.items.len; const id = source_index; const import_records = this.import_records_list[id].slice(); @@ -6557,7 +6557,7 @@ const LinkerContext = struct { continue; // This export star is shadowed if any file in the stack has a matching real named export - for (this.source_index_stack.items) |prev| { + for (this.source_index_stack.items[stack_end_pos..]) |prev| { if (this.named_exports[prev].contains(alias)) { continue :next_export; } From b6733d0d1f4d8cfa60454eb6514819b13bc3a63c Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Mon, 13 Mar 2023 03:42:10 -0700 Subject: [PATCH 095/255] Support `--outfile` --- src/cli.zig | 10 ++++++++++ src/cli/bun_command.zig | 9 +++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/cli.zig b/src/cli.zig index 8c3f484b11ee..8fd6d00adb71 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -193,6 +193,7 @@ pub const Arguments = struct { const build_only_params = [_]ParamType{ clap.parseParam("--sourcemap ? Build with sourcemaps - 'inline', 'external', or 'none'") catch unreachable, clap.parseParam("--outdir Default to \"dist\" if multiple files") catch unreachable, + clap.parseParam("--outfile Write to a file") catch unreachable, }; const build_params_public = public_params ++ build_only_params; @@ -466,12 +467,17 @@ pub const Arguments = struct { // var output_dir = args.option("--outdir"); var output_dir: ?string = null; const production = false; + var output_file: ?string = null; if (cmd == .BuildCommand or cmd == .BunCommand) { if (args.option("--outdir")) |outdir| { if (outdir.len > 0) { output_dir = outdir; } + } else if (args.option("--outfile")) |outfile| { + if (outfile.len > 0) { + output_file = outfile; + } } if (args.option("--sourcemap")) |setting| { @@ -683,6 +689,9 @@ pub const Arguments = struct { } opts.output_dir = output_dir; + if (output_file != null) + ctx.debug.output_file = output_file.?; + return opts; } }; @@ -838,6 +847,7 @@ pub const Command = struct { package_bundle_map: bun.StringArrayHashMapUnmanaged(options.BundlePackage) = bun.StringArrayHashMapUnmanaged(options.BundlePackage){}, test_directory: []const u8 = "", + output_file: []const u8 = "", }; pub const Context = struct { diff --git a/src/cli/bun_command.zig b/src/cli/bun_command.zig index 1f4c6509993d..cb77a39ba188 100644 --- a/src/cli/bun_command.zig +++ b/src/cli/bun_command.zig @@ -203,13 +203,18 @@ pub const BunCommand = struct { dump: { defer Output.flush(); var writer = Output.errorWriter(); + var output_dir = ctx.args.output_dir orelse ""; + if (ctx.debug.output_file.len > 0 and output_files.items.len == 1 and output_files.items[0].value == .buffer) { + output_dir = std.fs.path.dirname(ctx.debug.output_file) orelse "."; + output_files.items[0].input.text = std.fs.path.basename(ctx.debug.output_file); + } - if ((ctx.args.output_dir orelse "").len == 0 and output_files.items.len == 1 and output_files.items[0].value == .buffer) { + if (output_dir.len == 0 and output_files.items.len == 1 and output_files.items[0].value == .buffer) { try writer.writeAll(output_files.items[0].value.buffer); break :dump; } - const root_path = ctx.args.output_dir orelse "out"; + const root_path = output_dir; const root_dir = try std.fs.cwd().makeOpenPathIterable(root_path, .{}); var all_paths = try ctx.allocator.alloc([]const u8, output_files.items.len); var max_path_len: usize = 0; From 8aae5758abad72bb90033882c07883fa85fa33c1 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Mon, 13 Mar 2023 03:43:39 -0700 Subject: [PATCH 096/255] partially fix renaming --- src/renamer.zig | 99 +++++++++++++++++++++++++++--------------- src/string_mutable.zig | 2 +- 2 files changed, 66 insertions(+), 35 deletions(-) diff --git a/src/renamer.zig b/src/renamer.zig index 8971108303e5..f146882399e4 100644 --- a/src/renamer.zig +++ b/src/renamer.zig @@ -105,7 +105,7 @@ pub const NumberRenamer = struct { pub fn assignName(r: *NumberRenamer, scope: *NumberScope, input_ref: Ref) void { const ref = r.symbols.follow(input_ref); - // Don't rename the symbol more than once + // Don't rename the same symbol more than once var inner: *bun.BabyList(string) = r.names.mut(input_ref.sourceIndex()); if (inner.len > ref.innerIndex() and inner.at(ref.innerIndex()).len > 0) return; @@ -115,16 +115,10 @@ pub const NumberRenamer = struct { return; } - // TODO: MustStartWithCapitalLetterForJSX - - var name = scope.findUnusedName(r.allocator, symbol.original_name); - - if (strings.eqlLong(name, symbol.original_name, true)) { - return; - } + const name = scope.findUnusedName(r.allocator, symbol.original_name) orelse return; const new_len = @max(inner.len, ref.innerIndex() + 1); - if (inner.cap < new_len) { + if (inner.cap <= new_len) { const prev_cap = inner.len; inner.ensureUnusedCapacity(r.allocator, new_len - prev_cap) catch unreachable; const to_write = inner.ptr[prev_cap..inner.cap]; @@ -167,19 +161,25 @@ pub const NumberRenamer = struct { } pub fn assignNamesRecursiveWithNumberScope(r: *NumberRenamer, s: *NumberScope, scope: *js_ast.Scope, source_index: u32, sorted: *std.ArrayList(u32)) void { - sorted.clearRetainingCapacity(); - sorted.ensureUnusedCapacity(scope.members.count()) catch unreachable; - var value_iter = scope.members.valueIterator(); - while (value_iter.next()) |value_ref| { - if (comptime Environment.allow_assert) - std.debug.assert(!value_ref.ref.isSourceContentsSlice()); - - sorted.appendAssumeCapacity(value_ref.ref.innerIndex()); - } - std.sort.sort(u32, sorted.items, void{}, std.sort.asc(u32)); + { + sorted.clearRetainingCapacity(); + sorted.ensureUnusedCapacity(scope.members.count()) catch unreachable; + sorted.items.len = scope.members.count(); + var remaining = sorted.items; + var value_iter = scope.members.valueIterator(); + while (value_iter.next()) |value_ref| { + if (comptime Environment.allow_assert) + std.debug.assert(!value_ref.ref.isSourceContentsSlice()); - for (sorted.items) |inner_index| { - r.assignName(s, Ref.init(@intCast(Ref.Int, inner_index), source_index, false)); + remaining[0] = value_ref.ref.innerIndex(); + remaining = remaining[1..]; + } + std.debug.assert(remaining.len == 0); + std.sort.sort(u32, sorted.items, void{}, std.sort.asc(u32)); + + for (sorted.items) |inner_index| { + r.assignName(s, Ref.init(@intCast(Ref.Int, inner_index), source_index, false)); + } } for (scope.generated.slice()) |ref| { @@ -275,16 +275,17 @@ pub const NumberRenamer = struct { if (comptime Environment.allow_assert) std.debug.assert(JSLexer.isIdentifier(name)); - var s: ?*NumberScope = this; - // avoid rehashing the same string over for each scope const ctx = bun.StringHashMapContext.pre(name); + if (this.name_counts.getAdapted(name, ctx)) |count| { + return .{ .same_scope = count }; + } + + var s: ?*NumberScope = this.parent; + while (s) |scope| : (s = scope.parent) { - if (scope.name_counts.getAdapted(name, ctx)) |count| { - if (scope == this) { - return .{ .same_scope = count }; - } + if (scope.name_counts.containsAdapted(name, ctx)) { return .{ .used = void{} }; } } @@ -294,7 +295,7 @@ pub const NumberRenamer = struct { }; /// Caller must use an arena allocator - pub fn findUnusedName(this: *NumberScope, allocator: std.mem.Allocator, input_name: []const u8) string { + pub fn findUnusedName(this: *NumberScope, allocator: std.mem.Allocator, input_name: []const u8) ?string { var name = bun.MutableString.ensureValidIdentifier(input_name, allocator) catch unreachable; switch (NameUse.find(this, name)) { @@ -314,16 +315,29 @@ pub const NumberRenamer = struct { const prefix = name; + tries += 1; + var mutable_name = MutableString.initEmpty(allocator); - mutable_name.growIfNeeded(prefix.len + 10) catch unreachable; + mutable_name.growIfNeeded(prefix.len + 4) catch unreachable; mutable_name.appendSlice(prefix) catch unreachable; mutable_name.appendInt(tries) catch unreachable; - tries += 1; - switch (NameUse.find(this, mutable_name.toOwnedSliceLeaky())) { .unused => { name = mutable_name.toOwnedSliceLeaky(); + + if (use == .same_scope) { + var existing = this.name_counts.getOrPut(allocator, prefix) catch unreachable; + if (!existing.found_existing) { + if (strings.eqlLong(input_name, prefix, true)) { + existing.key_ptr.* = input_name; + } else { + existing.key_ptr.* = allocator.dupe(u8, prefix) catch unreachable; + } + } + + existing.value_ptr.* = tries; + } }, else => |cur_use| { while (true) { @@ -335,7 +349,16 @@ pub const NumberRenamer = struct { switch (NameUse.find(this, mutable_name.toOwnedSliceLeaky())) { .unused => { if (cur_use == .same_scope) { - this.name_counts.put(allocator, prefix, tries) catch unreachable; + var existing = this.name_counts.getOrPut(allocator, prefix) catch unreachable; + if (!existing.found_existing) { + if (strings.eqlLong(input_name, prefix, true)) { + existing.key_ptr.* = input_name; + } else { + existing.key_ptr.* = allocator.dupe(u8, prefix) catch unreachable; + } + } + + existing.value_ptr.* = tries; } name = mutable_name.toOwnedSliceLeaky(); @@ -351,8 +374,12 @@ pub const NumberRenamer = struct { // Each name starts off with a count of 1 so that the first collision with // "name" is called "name2" - this.name_counts.put(allocator, name, 1) catch unreachable; + if (strings.eqlLong(name, input_name, true)) { + this.name_counts.putNoClobber(allocator, input_name, 1) catch unreachable; + return null; + } + this.name_counts.putNoClobber(allocator, name, 1) catch unreachable; return name; } }; @@ -418,7 +445,11 @@ pub fn computeInitialReservedNames( "Require", }; - try names.ensureTotalCapacity(allocator, @truncate(u32, JSLexer.Keywords.keys().len + JSLexer.StrictModeReservedWords.keys().len + 1 + extras.len)); + try names.ensureTotalCapacityContext( + allocator, + @truncate(u32, JSLexer.Keywords.keys().len + JSLexer.StrictModeReservedWords.keys().len + 1 + extras.len), + bun.StringHashMapContext{}, + ); for (JSLexer.Keywords.keys()) |keyword| { names.putAssumeCapacity(keyword, 1); diff --git a/src/string_mutable.zig b/src/string_mutable.zig index 8457e7f12ba2..3a6918b5c525 100644 --- a/src/string_mutable.zig +++ b/src/string_mutable.zig @@ -100,7 +100,7 @@ pub const MutableString = struct { } } - if (!needs_gap and str.len >= 3 and str.len <= 10) { + if (!needs_gap) { return JSLexerTables.StrictModeReservedWordsRemap.get(str) orelse str; } From 410434f05992fc7bc196194e7a07c2c79242f991 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Mon, 13 Mar 2023 09:26:31 -0700 Subject: [PATCH 097/255] fanicer symbol renaming impl --- src/renamer.zig | 127 ++++++++++++++++++++++++++++++------------------ 1 file changed, 80 insertions(+), 47 deletions(-) diff --git a/src/renamer.zig b/src/renamer.zig index f146882399e4..5fc8c56566f1 100644 --- a/src/renamer.zig +++ b/src/renamer.zig @@ -80,11 +80,16 @@ pub const NumberRenamer = struct { names: bun.BabyList(bun.BabyList(string)) = .{}, allocator: std.mem.Allocator, temp_allocator: std.mem.Allocator, + number_scope_pool: bun.HiveArray(NumberScope, 128).Fallback, + arena: std.heap.ArenaAllocator, root: NumberScope = .{}, + name_stack_fallback: std.heap.StackFallbackAllocator(512) = undefined, + name_temp_allocator: std.mem.Allocator = undefined, pub fn deinit(self: *NumberRenamer) void { self.names.deinitWithAllocator(self.allocator); self.root.deinit(self.temp_allocator); + self.arena.deinit(); } pub fn toRenamer(this: *NumberRenamer) Renamer { @@ -106,7 +111,7 @@ pub const NumberRenamer = struct { const ref = r.symbols.follow(input_ref); // Don't rename the same symbol more than once - var inner: *bun.BabyList(string) = r.names.mut(input_ref.sourceIndex()); + var inner: *bun.BabyList(string) = r.names.mut(ref.sourceIndex()); if (inner.len > ref.innerIndex() and inner.at(ref.innerIndex()).len > 0) return; // Don't rename unbound symbols, symbols marked as reserved names, labels, or private names @@ -115,7 +120,8 @@ pub const NumberRenamer = struct { return; } - const name = scope.findUnusedName(r.allocator, symbol.original_name) orelse return; + r.name_stack_fallback.fixed_buffer_allocator.end_index = 0; + const name = scope.findUnusedName(r.allocator, r.name_temp_allocator, symbol.original_name) orelse return; const new_len = @max(inner.len, ref.innerIndex() + 1); if (inner.cap <= new_len) { @@ -140,8 +146,22 @@ pub const NumberRenamer = struct { .allocator = allocator, .temp_allocator = temp_allocator, .names = try bun.BabyList(bun.BabyList(string)).initCapacity(allocator, symbols.symbols_for_source.len), + .number_scope_pool = undefined, + .arena = std.heap.ArenaAllocator.init(temp_allocator), }; + renamer.name_stack_fallback = .{ + .buffer = undefined, + .fallback_allocator = renamer.arena.allocator(), + .fixed_buffer_allocator = undefined, + }; + renamer.name_temp_allocator = renamer.name_stack_fallback.get(); + renamer.number_scope_pool = bun.HiveArray(NumberScope, 128).Fallback.init(renamer.arena.allocator()); renamer.root.name_counts = root_names; + if (comptime Environment.allow_assert) { + if (std.os.getenv("BUN_DUMP_SYMBOLS") != null) + symbols.dump(); + } + renamer.names.len = symbols.symbols_for_source.len; for (renamer.names.slice()) |*inner| { inner.* = .{}; @@ -150,17 +170,26 @@ pub const NumberRenamer = struct { } pub fn assignNamesRecursive(r: *NumberRenamer, scope: *js_ast.Scope, source_index: u32, parent: ?*NumberScope, sorted: *std.ArrayList(u32)) void { - var s_ = NumberScope{ + var s = r.number_scope_pool.get(); + s.* = NumberScope{ .parent = parent, .name_counts = .{}, }; - var s = &s_; - defer s.deinit(r.temp_allocator); + defer { + s.deinit(r.temp_allocator); + r.number_scope_pool.put(s); + } assignNamesRecursiveWithNumberScope(r, s, scope, source_index, sorted); } - pub fn assignNamesRecursiveWithNumberScope(r: *NumberRenamer, s: *NumberScope, scope: *js_ast.Scope, source_index: u32, sorted: *std.ArrayList(u32)) void { + fn assignNamesInScope( + r: *NumberRenamer, + s: *NumberScope, + scope: *js_ast.Scope, + source_index: u32, + sorted: *std.ArrayList(u32), + ) void { { sorted.clearRetainingCapacity(); sorted.ensureUnusedCapacity(scope.members.count()) catch unreachable; @@ -185,19 +214,44 @@ pub const NumberRenamer = struct { for (scope.generated.slice()) |ref| { r.assignName(s, ref); } + } - // We only need one number scope per scope level, so we can reuse the same one for all children - var child_scope_ = NumberScope{ - .parent = s, - .name_counts = .{}, - }; - var child_scope = &child_scope_; - defer child_scope.deinit(r.temp_allocator); + pub fn assignNamesRecursiveWithNumberScope(r: *NumberRenamer, initial_scope: *NumberScope, scope_: *js_ast.Scope, source_index: u32, sorted: *std.ArrayList(u32)) void { + var s = initial_scope; + var scope = scope_; + + // Ignore function argument scopes + if (scope.kind == .function_args and scope.children.len == 1) { + scope = scope.children.ptr[0]; + std.debug.assert(scope.kind == .function_body); + } + + while (true) { + if (scope.members.count() > 0 or scope.generated.len > 0) { + var new_child_scope = r.number_scope_pool.get(); + new_child_scope.* = .{ + .parent = s, + .name_counts = .{}, + }; + s = new_child_scope; + + r.assignNamesInScope(s, scope, source_index, sorted); + } + + if (scope.children.len == 1) { + scope = scope.children.ptr[0]; + if (scope.kind == .function_args and scope.children.len == 1) { + scope = scope.children.ptr[0]; + std.debug.assert(scope.kind == .function_body); + } + } else { + break; + } + } // Symbols in child scopes may also have to be renamed to avoid conflicts for (scope.children.slice()) |child| { - child_scope.name_counts.clearRetainingCapacity(); - r.assignNamesRecursiveWithNumberScope(child_scope, child, source_index, sorted); + r.assignNamesRecursiveWithNumberScope(s, child, source_index, sorted); } } @@ -210,32 +264,6 @@ pub const NumberRenamer = struct { js_ast.DeclaredSymbol.forEachTopLevelSymbol(&decls, r, addTopLevelSymbol); } - pub fn assignNamesByScope(r: *NumberRenamer, nested_scopes: *js_ast.Scope.NestedScopeMap) void { - // TODO: parallelize this - var sorted_ = std.ArrayList(u32).init(r.temp_allocator); - var sorted = &sorted_; - defer sorted.deinit(); - - var scope_iter = nested_scopes.iterator(); - var s_ = NumberScope{ - .parent = &r.root, - .name_counts = .{}, - }; - var s = &s_; - - while (scope_iter.next()) |entry| { - var scopes_list = entry.value_ptr.*; - defer entry.value_ptr.deinitWithAllocator(nested_scopes.allocator); - const scopes = scopes_list.slice(); - const source_index = entry.key_ptr.*; - - for (scopes) |scope| { - s.name_counts.clearRetainingCapacity(); - r.assignNamesRecursiveWithNumberScope(s, scope, source_index, sorted); - } - } - } - pub fn nameForSymbol(renamer: *NumberRenamer, ref: Ref) string { if (ref.isSourceContentsSlice()) { unreachable; @@ -243,11 +271,14 @@ pub const NumberRenamer = struct { const resolved = renamer.symbols.follow(ref); + const source_index = resolved.sourceIndex(); + const inner_index = resolved.innerIndex(); + const renamed_list = renamer.names - .at(resolved.sourceIndex()); + .at(source_index); - if (renamed_list.len > resolved.innerIndex()) { - const renamed = renamed_list.at(resolved.innerIndex()).*; + if (renamed_list.len > inner_index) { + const renamed = renamed_list.at(inner_index).*; if (renamed.len > 0) { return renamed; } @@ -295,8 +326,8 @@ pub const NumberRenamer = struct { }; /// Caller must use an arena allocator - pub fn findUnusedName(this: *NumberScope, allocator: std.mem.Allocator, input_name: []const u8) ?string { - var name = bun.MutableString.ensureValidIdentifier(input_name, allocator) catch unreachable; + pub fn findUnusedName(this: *NumberScope, allocator: std.mem.Allocator, temp_allocator: std.mem.Allocator, input_name: []const u8) ?string { + var name = bun.MutableString.ensureValidIdentifier(input_name, temp_allocator) catch unreachable; switch (NameUse.find(this, name)) { .unused => {}, @@ -317,7 +348,7 @@ pub const NumberRenamer = struct { tries += 1; - var mutable_name = MutableString.initEmpty(allocator); + var mutable_name = MutableString.initEmpty(temp_allocator); mutable_name.growIfNeeded(prefix.len + 4) catch unreachable; mutable_name.appendSlice(prefix) catch unreachable; mutable_name.appendInt(tries) catch unreachable; @@ -379,6 +410,8 @@ pub const NumberRenamer = struct { return null; } + name = allocator.dupe(u8, name) catch unreachable; + this.name_counts.putNoClobber(allocator, name, 1) catch unreachable; return name; } From dc7fd5ec47e7c9ca4d76b498ee8eb4389ce56011 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Mon, 13 Mar 2023 09:26:49 -0700 Subject: [PATCH 098/255] misc, extremely revertible cleanup --- src/js_parser.zig | 70 ++++++++++++++++++++--------------------------- 1 file changed, 29 insertions(+), 41 deletions(-) diff --git a/src/js_parser.zig b/src/js_parser.zig index 09209b500fa1..bf34e2c9f278 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -76,6 +76,8 @@ const NodeFallbackModules = @import("./node_fallbacks.zig"); const RefExprMap = std.ArrayHashMapUnmanaged(Ref, Expr, RefHashCtx, false); +const arguments_str: string = "arguments"; + // Dear reader, // There are some things you should know about this file to make it easier for humans to read // "P" is the internal parts of the parser @@ -5383,14 +5385,14 @@ fn NewParser_( if (generated_symbol.ref.isNull() or p.options.bundle) return; if (p.symbols.items[generated_symbol.primary.innerIndex()].use_count_estimate == 0 and - p.symbols.items[generated_symbol.primary.innerIndex()].link.isNull()) + p.symbols.items[generated_symbol.primary.innerIndex()].hasLink()) { p.symbols.items[generated_symbol.ref.innerIndex()].original_name = p.symbols.items[generated_symbol.primary.innerIndex()].original_name; return; } if (p.symbols.items[generated_symbol.backup.innerIndex()].use_count_estimate == 0 and - p.symbols.items[generated_symbol.backup.innerIndex()].link.isNull()) + p.symbols.items[generated_symbol.backup.innerIndex()].hasLink()) { p.symbols.items[generated_symbol.ref.innerIndex()].original_name = p.symbols.items[generated_symbol.backup.innerIndex()].original_name; return; @@ -6131,7 +6133,7 @@ fn NewParser_( // this if it wasn't already declared above because arguments are allowed to // be called "arguments", in which case the real "arguments" is inaccessible. if (!p.current_scope.members.contains("arguments")) { - func.arguments_ref = p.declareSymbolMaybeGenerated(.arguments, func.open_parens_loc, "arguments", true) catch unreachable; + func.arguments_ref = p.declareSymbolMaybeGenerated(.arguments, func.open_parens_loc, arguments_str, false) catch unreachable; p.symbols.items[func.arguments_ref.?.innerIndex()].must_not_be_renamed = true; } @@ -6976,6 +6978,7 @@ fn NewParser_( try p.symbols.append(Symbol{ .kind = kind, .original_name = identifier, + .debug_mode_source_index = if (comptime Environment.allow_assert) p.source.index.get() else 0, }); if (is_typescript_enabled) { @@ -9728,7 +9731,7 @@ fn NewParser_( if (comptime !is_generated) { // Forbid declaring a symbol with a reserved word in strict mode - if (p.isStrictMode() and js_lexer.StrictModeReservedWords.has(name)) { + if (p.isStrictMode() and name.ptr != arguments_str.ptr and js_lexer.StrictModeReservedWords.has(name)) { try p.markStrictModeFeature(.reserved_word, js_lexer.rangeOfIdentifier(p.source, loc), name); } } @@ -9787,11 +9790,6 @@ fn NewParser_( // else => unreachable, } } else { - // Ensure that EImportIdentifier is created for the symbol in handleIdentifier - if (symbol.kind == .import and kind != .import) { - try p.is_import_item.put(p.allocator, ref, {}); - } - p.symbols.items[ref.innerIndex()].link = existing.ref; } } @@ -9944,8 +9942,8 @@ fn NewParser_( }, .b_array => |bind| { - for (bind.items, 0..) |_, i| { - p.declareBinding(kind, &bind.items[i].binding, opts) catch unreachable; + for (bind.items) |*item| { + p.declareBinding(kind, &item.binding, opts) catch unreachable; } }, @@ -12954,11 +12952,11 @@ fn NewParser_( for (p.relocated_top_level_vars.items) |*local| { // Follow links because "var" declarations may be merged due to hoisting while (local.ref != null) { - const link = p.symbols.items[local.ref.?.innerIndex()].link; - if (link.isNull()) { + var symbol = &p.symbols.items[local.ref.?.innerIndex()]; + if (!symbol.hasLink()) { break; } - local.ref = link; + local.ref = symbol.link; } const ref = local.ref orelse continue; var declaration_entry = try already_declared.getOrPut(already_declared_allocator, ref); @@ -14796,9 +14794,8 @@ fn NewParser_( if (is_macro_ref) p.options.ignore_dce_annotations = true; - for (e_.args.slice(), 0..) |_, i| { - const arg = e_.args.ptr[i]; - e_.args.ptr[i] = p.visitExpr(arg); + for (e_.args.slice()) |*arg| { + arg.* = p.visitExpr(arg.*); } } @@ -15035,28 +15032,26 @@ fn NewParser_( duplicate_args_check = StringVoidMap.get(bun.default_allocator); } - var i: usize = 0; var duplicate_args_check_ptr: ?*StringVoidMap = if (duplicate_args_check != null) &duplicate_args_check.?.data else null; - while (i < args.len) : (i += 1) { - if (args[i].ts_decorators.len > 0) { - args[i].ts_decorators = p.visitTSDecorators(args[i].ts_decorators); + for (args) |*arg| { + if (arg.ts_decorators.len > 0) { + arg.ts_decorators = p.visitTSDecorators(arg.ts_decorators); } - p.visitBinding(args[i].binding, duplicate_args_check_ptr); - if (args[i].default) |default| { - args[i].default = p.visitExpr(default); + p.visitBinding(arg.binding, duplicate_args_check_ptr); + if (arg.default) |default| { + arg.default = p.visitExpr(default); } } } pub fn visitTSDecorators(p: *P, decs: ExprNodeList) ExprNodeList { - var i: usize = 0; - while (i < decs.len) : (i += 1) { - decs.ptr[i] = p.visitExpr(decs.ptr[i]); + for (decs.slice()) |*dec| { + dec.* = p.visitExpr(dec.*); } return decs; @@ -17043,15 +17038,13 @@ fn NewParser_( var array = expr.data.e_array; array.items.len = @min(array.items.len, @truncate(u32, bound_array.items.len)); - var slice = array.items.slice(); - for (bound_array.items[0..array.items.len], 0..) |item, item_i| { - const child_expr = slice[item_i]; + for (bound_array.items[0..array.items.len], array.items.slice()) |item, *child_expr| { if (item.binding.data == .b_missing) { - slice[item_i] = p.newExpr(E.Missing{}, expr.loc); + child_expr.* = p.newExpr(E.Missing{}, expr.loc); continue; } - p.visitBindingAndExprForMacro(item.binding, child_expr); + p.visitBindingAndExprForMacro(item.binding, child_expr.*); } } }, @@ -17434,7 +17427,7 @@ fn NewParser_( if (class.extends != null) { const target = p.newExpr(E.Super{}, stmt.loc); - const arguments_ref = p.newSymbol(.unbound, "arguments") catch unreachable; + const arguments_ref = p.newSymbol(.unbound, arguments_str) catch unreachable; p.current_scope.generated.push(p.allocator, arguments_ref) catch unreachable; const super = p.newExpr(E.Spread{ .value = p.newExpr(E.Identifier{ .ref = arguments_ref }, stmt.loc) }, stmt.loc); @@ -17826,6 +17819,7 @@ fn NewParser_( const name_str: []const u8 = if (default_name_ref.isNull()) "_this" else "_default"; shadow_ref = p.newSymbol(.cconst, name_str) catch unreachable; } + p.recordDeclaredSymbol(shadow_ref) catch unreachable; if (class.extends) |extends| { @@ -18004,17 +17998,11 @@ fn NewParser_( // store our generated name so the class expression ends up with a name. shadow_ref = Ref.None; } else if (class.class_name == null) { - var class_name_ref = default_name_ref; - if (default_name_ref.isNull()) { - class_name_ref = p.newSymbol(.other, "_this") catch unreachable; - p.current_scope.generated.push(p.allocator, class_name_ref) catch unreachable; - p.recordDeclaredSymbol(class_name_ref) catch unreachable; - } - class.class_name = LocRef{ - .ref = class_name_ref, + .ref = shadow_ref, .loc = name_scope_loc, }; + p.recordDeclaredSymbol(shadow_ref) catch unreachable; } // class name scope From 60d50541edab4ca8b9abe83b174c5038eaedb4a6 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Mon, 13 Mar 2023 09:27:30 -0700 Subject: [PATCH 099/255] Fix up some bugs with symbol renaming --- src/bundler/bundle_v2.zig | 19 +++++-------- src/js_ast.zig | 56 +++++++++++++++++++++++++++------------ 2 files changed, 45 insertions(+), 30 deletions(-) diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 10e3abf83580..f92c742680d5 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -1326,11 +1326,12 @@ const LinkerGraph = struct { pub fn generateNewSymbol(this: *LinkerGraph, source_index: u32, kind: Symbol.Kind, original_name: string) Ref { var source_symbols = &this.symbols.symbols_for_source.slice()[source_index]; - const ref = Ref.init( + var ref = Ref.init( @truncate(Ref.Int, source_symbols.len), @truncate(Ref.Int, source_index), false, ); + ref.tag = .symbol; // TODO: will this crash on resize due to using threadlocal mimalloc heap? source_symbols.push( @@ -3674,11 +3675,6 @@ const LinkerContext = struct { } } - var child_number_scope_ = renamer.NumberRenamer.NumberScope{ - .parent = &r.root, - }; - var child_number_scope = &child_number_scope_; - defer child_number_scope.deinit(r.temp_allocator); var sorted_ = std.ArrayList(u32).init(r.temp_allocator); var sorted = &sorted_; defer sorted.deinit(); @@ -3751,7 +3747,7 @@ const LinkerContext = struct { } } } - r.assignNamesRecursiveWithNumberScope(child_number_scope, &all_module_scopes[source_index], source_index, sorted); + r.assignNamesRecursiveWithNumberScope(&r.root, &all_module_scopes[source_index], source_index, sorted); continue; }, @@ -3781,8 +3777,9 @@ const LinkerContext = struct { r.addTopLevelDeclaredSymbols(part.declared_symbols); for (part.scopes) |scope| { - r.assignNamesRecursiveWithNumberScope(child_number_scope, scope, source_index, sorted); + r.assignNamesRecursiveWithNumberScope(&r.root, scope, source_index, sorted); } + r.number_scope_pool.hive.available = @TypeOf(r.number_scope_pool.hive.available).initFull(); } } @@ -6406,11 +6403,7 @@ const LinkerContext = struct { // Re-use memory for the cycle detector c.cycle_detector.clearRetainingCapacity(); - const import_ref = Ref{ - .inner_index = ref.innerIndex(), - .source_index = @truncate(Ref.Int, source_index), - .tag = ref.tag, - }; + const import_ref = ref; var import_tracker = ImportData{ .data = .{ diff --git a/src/js_ast.zig b/src/js_ast.zig index 385e79341e10..65e5a87b2111 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -775,6 +775,8 @@ pub const Symbol = struct { // private_symbol_must_be_lowered: bool = false, + debug_mode_source_index: if (Environment.allow_assert) Index.Int else u0 = 0, + pub const SlotNamespace = enum { default, label, @@ -802,7 +804,7 @@ pub const Symbol = struct { } pub inline fn hasLink(this: *const Symbol) bool { - return !this.link.isNull(); + return this.link.tag != .invalid; } pub const Kind = enum { @@ -949,6 +951,26 @@ pub const Symbol = struct { // "Ref" for more detail. symbols_for_source: NestedList = NestedList{}, + pub fn dump(this: Map) void { + defer Output.flush(); + for (this.symbols_for_source.slice(), 0..) |symbols, i| { + Output.prettyln("\n\n-- Source ID: {d} ({d} symbols) --\n\n", .{ i, symbols.len }); + for (symbols.slice(), 0..) |symbol, inner_index| { + Output.prettyln( + " name: {s}\n tag: {s}\n {any}\n", + .{ + symbol.original_name, @tagName(symbol.kind), + if (symbol.hasLink()) symbol.link else Ref{ + .source_index = @truncate(Ref.Int, i), + .inner_index = @truncate(Ref.Int, inner_index), + .tag = .symbol, + }, + }, + ); + } + } + } + pub fn assignChunkIndex(this: *Map, decls_: DeclaredSymbol.List, chunk_index: u32) void { const Iterator = struct { map: *Map, @@ -1031,27 +1053,25 @@ pub const Symbol = struct { pub fn followAll(symbols: *Map) void { for (symbols.symbols_for_source.slice()) |list| { for (list.slice()) |*symbol| { - if (symbol.hasLink()) { - symbol.link = follow(symbols, symbol.link); - } + if (!symbol.hasLink()) continue; + symbol.link = follow(symbols, symbol.link); } } } pub fn follow(symbols: *const Map, ref: Ref) Ref { - if (symbols.get(ref)) |symbol| { - const link = symbol.link; - if (link.isNull()) - return ref; + var symbol = symbols.get(ref) orelse return ref; + if (!symbol.hasLink()) { + return ref; + } - if (link.eql(ref)) { - symbol.link = ref; - } + const link = follow(symbols, symbol.link); - return symbol.link; - } else { - return ref; + if (!symbol.link.eql(link)) { + symbol.link = link; } + + return link; } }; @@ -5598,10 +5618,12 @@ pub const Scope = struct { // "var foo; function foo() {}" // "function foo() {} var foo;" // "function *foo() {} function *foo() {}" but not "{ function *foo() {} function *foo() {} }" - if (Symbol.isKindHoistedOrFunction(new) and Symbol.isKindHoistedOrFunction(existing) and (scope.kind == .entry or scope.kind == .function_body or - (Symbol.isKindHoisted(new) and Symbol.isKindHoisted(existing)))) + if (Symbol.isKindHoistedOrFunction(new) and + Symbol.isKindHoistedOrFunction(existing) and + (scope.kind == .entry or scope.kind == .function_body or scope.kind == .function_args or + (new == existing and Symbol.isKindHoisted(existing)))) { - return .keep_existing; + return .replace_with_new; } // "get #foo() {} set #foo() {}" From 124d0450203a3e73b94cb69f2c6b98db0f84c642 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Mon, 13 Mar 2023 09:27:41 -0700 Subject: [PATCH 100/255] formatting --- src/ast/base.zig | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/ast/base.zig b/src/ast/base.zig index bbc5bdafc4fb..634bd9852b28 100644 --- a/src/ast/base.zig +++ b/src/ast/base.zig @@ -187,6 +187,8 @@ pub const Index = packed struct(u32) { }; pub const Ref = packed struct(u64) { + inner_index: Int = 0, + tag: enum(u2) { invalid, allocated_name, @@ -195,10 +197,9 @@ pub const Ref = packed struct(u64) { } = .invalid, source_index: Int = 0, - inner_index: Int = 0, pub inline fn isEmpty(this: Ref) bool { - return @bitCast(u64, this) == 0; + return this.asU64() == 0; } pub const ArrayHashCtx = RefHashCtx; @@ -220,11 +221,11 @@ pub const Ref = packed struct(u64) { pub fn format(ref: Ref, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { try std.fmt.format( writer, - "Ref: sourceIndex = {d}, innerIndex = {d}, is_source_contents_slice = {}", + "Ref[{d}, {d}, {s}]", .{ ref.sourceIndex(), ref.innerIndex(), - ref.isSourceContentsSlice(), + @tagName(ref.tag), }, ); } @@ -269,17 +270,11 @@ pub const Ref = packed struct(u64) { } pub inline fn asU64(key: Ref) u64 { - // This type isn't quite a u64 because it is used in a few other packed structs which have variables in them - // But, there are some footguns with the stage1 implementation of packed structs - // so it is safer to do comparisons as u64 - // but we want to ensure that the value of the unused bits in the u64 are 0 - // i have not looked at the assembly to verify that the unused bits default to 0 - // so we set it to u64 0 just to be sure return @bitCast(u64, key); } pub inline fn hash64(key: Ref) u64 { - return std.hash.Wyhash.hash(0, &@bitCast([8]u8, @bitCast(u64, key))); + return std.hash.Wyhash.hash(0, &@bitCast([8]u8, key.asU64())); } pub fn eql(ref: Ref, b: Ref) bool { From 8af6f28d7d8c9134065a6b68b68c59d903b0bc1f Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Tue, 14 Mar 2023 07:03:41 -0700 Subject: [PATCH 101/255] Update launch.json --- .vscode/launch.json | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/.vscode/launch.json b/.vscode/launch.json index 3f0af19bdd80..c71b937dcf34 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -121,6 +121,31 @@ "BUN_CONFIG_MINIFY_WHITESPACE": "1" } }, + { + "type": "lldb", + "request": "launch", + "name": "bun build debug out.js", + "program": "bun-debug", + "args": ["--outfile=out.js", "bun", "${file}"], + "cwd": "${file}/../", + "console": "internalConsole", + "env": { + "BUN_CONFIG_MINIFY_WHITESPACE": "1" + } + }, + { + "type": "lldb", + "request": "launch", + "name": "bun build debug stdout", + "program": "bun-debug", + "args": ["bun", "${file}"], + "cwd": "${file}/../", + "console": "internalConsole", + "env": { + "BUN_DEBUG_QUIET_LOGS": "1", + "BUN_DUMP_SYMBOLS": "1" + } + }, { "type": "lldb", "request": "launch", From 38dfe592ff8629046a6c1e7c98123e81256cedeb Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Tue, 14 Mar 2023 11:05:47 -0700 Subject: [PATCH 102/255] Parse `__PURE__` comments --- src/js_ast.zig | 4 +-- src/js_lexer.zig | 94 +++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 88 insertions(+), 10 deletions(-) diff --git a/src/js_ast.zig b/src/js_ast.zig index 65e5a87b2111..f5e70f427848 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -5232,8 +5232,8 @@ pub const Ast = struct { }; pub const Span = struct { - text: string, - range: logger.Range, + text: string = "", + range: logger.Range = .{}, }; pub const ExportsKind = enum { diff --git a/src/js_lexer.zig b/src/js_lexer.zig index 3205a0822154..cd652a12f6b3 100644 --- a/src/js_lexer.zig +++ b/src/js_lexer.zig @@ -1069,6 +1069,7 @@ fn NewLexer_( pub fn next(lexer: *LexerType) !void { lexer.has_newline_before = lexer.end == 0; + lexer.has_pure_comment_before = false; while (true) { lexer.start = lexer.end; @@ -1784,16 +1785,17 @@ fn NewLexer_( fn scanCommentText(lexer: *LexerType) void { const text = lexer.source.contents[lexer.start..lexer.end]; - const has_preserve_annotation = text.len > 2 and text[2] == '!'; - const is_multiline_comment = text[1] == '*'; + const has_legal_annotation = text.len > 2 and text[2] == '!'; + const is_multiline_comment = text.len > 1 and text[1] == '*'; // Omit the trailing "*/" from the checks below - var endCommentText = text.len; - if (is_multiline_comment) { - endCommentText -= 2; - } + const end_comment_text = + if (is_multiline_comment) + text.len - 2 + else + text.len; - if (has_preserve_annotation or lexer.preserve_all_comments_before) { + if (has_legal_annotation or lexer.preserve_all_comments_before) { if (is_multiline_comment) { // text = lexer.removeMultilineCommentIndent(lexer.source.contents[0..lexer.start], text); } @@ -1803,10 +1805,85 @@ fn NewLexer_( .loc = lexer.loc(), }) catch unreachable; } + + // TODO: @jsx annotations + if (lexer.has_pure_comment_before) + return; + + var rest = text[0..end_comment_text]; + const end = rest.ptr + rest.len; + + if (comptime Environment.enableSIMD) { + const wrapped_len = rest.len - (rest.len % strings.ascii_vector_size); + const comment_end = rest.ptr + wrapped_len; + while (rest.ptr != comment_end) { + const vec: strings.AsciiVector = rest.ptr[0..strings.ascii_vector_size].*; + const hashtag = vec & @splat(strings.ascii_vector_size, @as(u8, '#')); + const at = vec & @splat(strings.ascii_vector_size, @as(u8, '@')); + + if (@reduce(.Max, hashtag | at) > 0) { + rest.len = @ptrToInt(end) - @ptrToInt(rest.ptr); + for (@as([strings.ascii_vector_size]u8, vec), 0..) |c, i| { + switch (c) { + '@', '#' => { + if (!lexer.has_pure_comment_before) { + if (strings.indexOf(rest[i + 1 ..], "__PURE__")) |pure_i| { + const pure_prefix = rest[i + 1 ..][0..pure_i]; + const pure_suffix = rest[i + 1 ..][pure_i + "__PURE__".len ..]; + const has_prefix = pure_prefix.len == 0 or (strings.isAllASCII(pure_prefix) and !isIdentifierStart(pure_prefix[pure_prefix.len - 1])); + + // TODO: unicode whitespace for pure comments + // This misses non-ascii whitespace, but that's fine for now + const has_suffix = pure_suffix.len == 0 or !isIdentifierContinue(pure_suffix[0]); + lexer.has_pure_comment_before = has_prefix and has_suffix; + + // TODO: handle JSX annotations + if (lexer.has_pure_comment_before) + return; + } + } + }, + else => {}, + } + } + } + + rest.ptr += strings.ascii_vector_size; + } + rest.len = @ptrToInt(end) - @ptrToInt(rest.ptr); + } + + if (comptime Environment.allow_assert) + std.debug.assert(rest.len == 0 or bun.isSliceInBuffer(rest, text)); + + while (rest.len > 0) { + const c = rest[0]; + rest = rest[1..]; + switch (c) { + '@', '#' => { + if (!lexer.has_pure_comment_before) { + if (strings.indexOf(rest, "__PURE__")) |pure_i| { + const pure_prefix = rest[0..pure_i]; + const pure_suffix = rest[pure_i + "__PURE__".len ..]; + const has_prefix = pure_prefix.len == 0 or (strings.isAllASCII(pure_prefix) and !isIdentifierStart(pure_prefix[pure_prefix.len - 1])); + + // TODO: unicode whitespace for pure comments + // This misses non-ascii whitespace, but that's fine for now + const has_suffix = pure_suffix.len == 0 or !isIdentifierContinue(pure_suffix[0]); + lexer.has_pure_comment_before = (has_prefix and has_suffix); + + // TODO: handle JSX annotations + if (lexer.has_pure_comment_before) + return; + } + } + }, + else => {}, + } + } } // TODO: implement this - // it's too complicated to handle all the edgecases right now given the state of Zig's standard library pub fn removeMultilineCommentIndent(_: *LexerType, _: string, text: string) string { return text; } @@ -3079,6 +3156,7 @@ fn indexOfInterestingCharacterInStringLiteral(text_: []const u8, quote: u8) ?usi return null; } + test "isIdentifier" { const expect = std.testing.expect; try expect(!isIdentifierStart(0x2029)); From a733b0c66b9e49e850bae748a9a663a395f6f8ce Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Tue, 14 Mar 2023 11:18:52 -0700 Subject: [PATCH 103/255] clean up simd code for pure comments --- src/js_lexer.zig | 19 ++++++++++++++++--- src/string_immutable.zig | 12 ++++++------ 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/js_lexer.zig b/src/js_lexer.zig index cd652a12f6b3..da445235a591 100644 --- a/src/js_lexer.zig +++ b/src/js_lexer.zig @@ -1806,6 +1806,10 @@ fn NewLexer_( }) catch unreachable; } + // tsconfig.json doesn't care about annotations + if (comptime is_json) + return; + // TODO: @jsx annotations if (lexer.has_pure_comment_before) return; @@ -1818,11 +1822,20 @@ fn NewLexer_( const comment_end = rest.ptr + wrapped_len; while (rest.ptr != comment_end) { const vec: strings.AsciiVector = rest.ptr[0..strings.ascii_vector_size].*; - const hashtag = vec & @splat(strings.ascii_vector_size, @as(u8, '#')); - const at = vec & @splat(strings.ascii_vector_size, @as(u8, '@')); - if (@reduce(.Max, hashtag | at) > 0) { + // lookahead for any # or @ characters + const hashtag = @bitCast(strings.AsciiVectorU1, vec == @splat(strings.ascii_vector_size, @as(u8, '#'))); + const at = @bitCast(strings.AsciiVectorU1, vec == @splat(strings.ascii_vector_size, @as(u8, '@'))); + + if (@reduce(.Max, hashtag + at) == 1) { rest.len = @ptrToInt(end) - @ptrToInt(rest.ptr); + if (comptime Environment.allow_assert) { + std.debug.assert( + strings.containsChar(&@as([strings.ascii_vector_size]u8, vec), '#') or + strings.containsChar(&@as([strings.ascii_vector_size]u8, vec), '@'), + ); + } + for (@as([strings.ascii_vector_size]u8, vec), 0..) |c, i| { switch (c) { '@', '#' => { diff --git a/src/string_immutable.zig b/src/string_immutable.zig index eb261b8e4f23..656b0d44a3d5 100644 --- a/src/string_immutable.zig +++ b/src/string_immutable.zig @@ -2943,12 +2943,12 @@ pub const max_16_ascii = @splat(ascii_vector_size, @as(u8, 127)); pub const min_16_ascii = @splat(ascii_vector_size, @as(u8, 0x20)); pub const max_u16_ascii = @splat(ascii_u16_vector_size, @as(u16, 127)); pub const min_u16_ascii = @splat(ascii_u16_vector_size, @as(u16, 0x20)); -pub const AsciiVector = std.meta.Vector(ascii_vector_size, u8); -pub const AsciiVectorSmall = std.meta.Vector(8, u8); -pub const AsciiVectorU1 = std.meta.Vector(ascii_vector_size, u1); -pub const AsciiVectorU1Small = std.meta.Vector(8, u1); -pub const AsciiVectorU16U1 = std.meta.Vector(ascii_u16_vector_size, u1); -pub const AsciiU16Vector = std.meta.Vector(ascii_u16_vector_size, u16); +pub const AsciiVector = @Vector(ascii_vector_size, u8); +pub const AsciiVectorSmall = @Vector(8, u8); +pub const AsciiVectorU1 = @Vector(ascii_vector_size, u1); +pub const AsciiVectorU1Small = @Vector(8, u1); +pub const AsciiVectorU16U1 = @Vector(ascii_u16_vector_size, u1); +pub const AsciiU16Vector = @Vector(ascii_u16_vector_size, u16); pub const max_4_ascii = @splat(4, @as(u8, 127)); pub fn isAllASCII(slice: []const u8) bool { if (bun.FeatureFlags.use_simdutf) From 4f028607f99a947ca6ac977e28f1c1b2b60e3f12 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Tue, 14 Mar 2023 18:14:56 -0700 Subject: [PATCH 104/255] changes to merge --- src/bun.js/test/jest.zig | 6 +++--- src/string_immutable.zig | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/bun.js/test/jest.zig b/src/bun.js/test/jest.zig index d8d4fb2468e2..a8c930aa40b8 100644 --- a/src/bun.js/test/jest.zig +++ b/src/bun.js/test/jest.zig @@ -613,13 +613,13 @@ pub const Snapshots = struct { var ast = if (parse_result.ok) parse_result.ast else return error.ParseError; defer ast.deinit(); - if (ast.exports_ref == null) return; - const exports_ref = ast.exports_ref.?; + if (ast.exports_ref.isNull()) return; + const exports_ref = ast.exports_ref; // TODO: when common js transform changes, keep this updated or add flag to support this version const export_default = brk: { - for (ast.parts) |part| { + for (ast.parts.slice()) |part| { for (part.stmts) |stmt| { if (stmt.data == .s_export_default and stmt.data.s_export_default.value == .expr) { break :brk stmt.data.s_export_default.value.expr; diff --git a/src/string_immutable.zig b/src/string_immutable.zig index 656b0d44a3d5..e6f982d753d6 100644 --- a/src/string_immutable.zig +++ b/src/string_immutable.zig @@ -42,10 +42,12 @@ pub fn toUTF16Literal(comptime str: []const u8) []const u16 { const OptionalUsize = std.meta.Int(.unsigned, @bitSizeOf(usize) - 1); pub fn indexOfAny(self: string, comptime str: anytype) ?OptionalUsize { inline for (str) |a| { - if (indexOf(self, a)) |i| { + if (indexOfChar(self, a)) |i| { return @intCast(OptionalUsize, i); } } + + return null; } pub fn indexOfAny16(self: []const u16, comptime str: anytype) ?OptionalUsize { for (self, 0..) |c, i| { From 692712dad2e2c3b45ffc0a9e7d055a9df21eb3a6 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Tue, 14 Mar 2023 18:54:30 -0700 Subject: [PATCH 105/255] workaround runtime issue --- src/runtime.js | 45 ++++++++++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/src/runtime.js b/src/runtime.js index adc3e6c203eb..acdd6189ba7d 100644 --- a/src/runtime.js +++ b/src/runtime.js @@ -8,18 +8,6 @@ var __hasOwnProp = Object.prototype.hasOwnProperty; var __getOwnPropNames = Object.getOwnPropertyNames; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; -var __copyProps = (to, from, except, desc) => { - if ((from && typeof from === "object") || typeof from === "function") - for (let key of __getOwnPropNames(from)) - if (!__hasOwnProp.call(to, key) && key !== except) - __defProp(to, key, { - get: () => from[key], - enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable, - }); - - return to; -}; - export var __markAsModule = target => __defProp(target, "__esModule", { value: true, configurable: true }); // This is used to implement "export * from" statements. It copies properties @@ -28,8 +16,23 @@ export var __markAsModule = target => __defProp(target, "__esModule", { value: t // also copy the properties to "module.exports" in addition to our module's // internal ESM export object. export var __reExport = (target, mod, secondTarget) => { - __copyProps(target, mod, "default"); - return secondTarget && __copyProps(secondTarget, mod, "default"); + for (let key of __getOwnPropNames(mod)) + if (!__hasOwnProp.call(target, key) && key !== "default") + __defProp(target, key, { + get: () => mod[key], + enumerable: true, + }); + + if (secondTarget) { + for (let key of __getOwnPropNames(mod)) + if (!__hasOwnProp.call(secondTarget, key) && key !== "default") + __defProp(secondTarget, key, { + get: () => mod[key], + enumerable: true, + }); + + return secondTarget; + } }; // Converts the module from CommonJS to ESM. When in node mode (i.e. in an @@ -58,7 +61,19 @@ export var __toESM = (mod, isNodeMode, target) => { // Converts the module from ESM to CommonJS. This clones the input module // object with the addition of a non-enumerable "__esModule" property set // to "true", which overwrites any existing export named "__esModule". -export var __toCommonJS = mod => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +export var __toCommonJS = from => { + var to = __defProp({}, "__esModule", { value: true }); + var desc = { enumerable: false }; + if ((from && typeof from === "object") || typeof from === "function") + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key)) + __defProp(to, key, { + get: () => from[key], + enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable, + }); + + return to; +}; // lazy require to prevent loading one icon from a design system export var $$lzy = (target, mod, props) => { From 5473f2204e0e9b25eb2556d155d0bdd2a58fd362 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Wed, 15 Mar 2023 10:41:22 -0700 Subject: [PATCH 106/255] Fix issue with `export * as` not propagating correctly --- src/js_parser.zig | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/js_parser.zig b/src/js_parser.zig index bf34e2c9f278..1d1433f5696c 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -1040,6 +1040,8 @@ pub const ImportScanner = struct { .is_exported = true, }); try p.recordExport(alias.loc, alias.original_name, st.namespace_ref); + var record = &p.import_records.items[st.import_record_index]; + record.contains_import_star = true; } else { // "export * from 'path'" try p.export_star_import_records.append(allocator, st.import_record_index); @@ -15981,20 +15983,20 @@ fn NewParser_( return; } } - // "import * as ns from 'path'" - // "export {ns}" - // jarred: For now, just always do this transform. - // because Safari doesn't support it and I've seen cases where this breaks + if (!p.options.bundle) { + // "import * as ns from 'path'" + // "export {ns}" - p.recordUsage(data.namespace_ref); - try stmts.ensureTotalCapacity(stmts.items.len + 2); - stmts.appendAssumeCapacity(p.s(S.Import{ .namespace_ref = data.namespace_ref, .star_name_loc = alias.loc, .import_record_index = data.import_record_index }, stmt.loc)); + p.recordUsage(data.namespace_ref); + try stmts.ensureTotalCapacity(stmts.items.len + 2); + stmts.appendAssumeCapacity(p.s(S.Import{ .namespace_ref = data.namespace_ref, .star_name_loc = alias.loc, .import_record_index = data.import_record_index }, stmt.loc)); - var items = try List(js_ast.ClauseItem).initCapacity(p.allocator, 1); - items.appendAssumeCapacity(js_ast.ClauseItem{ .alias = alias.original_name, .original_name = alias.original_name, .alias_loc = alias.loc, .name = LocRef{ .loc = alias.loc, .ref = data.namespace_ref } }); - stmts.appendAssumeCapacity(p.s(S.ExportClause{ .items = items.toOwnedSlice(p.allocator) catch @panic("TODO"), .is_single_line = true }, stmt.loc)); - return; + var items = try List(js_ast.ClauseItem).initCapacity(p.allocator, 1); + items.appendAssumeCapacity(js_ast.ClauseItem{ .alias = alias.original_name, .original_name = alias.original_name, .alias_loc = alias.loc, .name = LocRef{ .loc = alias.loc, .ref = data.namespace_ref } }); + stmts.appendAssumeCapacity(p.s(S.ExportClause{ .items = items.toOwnedSlice(p.allocator) catch @panic("TODO"), .is_single_line = true }, stmt.loc)); + return; + } } }, .s_export_default => |data| { From f8ac1d2f32589d783e527dced78a08d32ebfd1af Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Wed, 15 Mar 2023 10:41:35 -0700 Subject: [PATCH 107/255] Make all top-level declarations `var` when bundling --- src/js_parser.zig | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/js_parser.zig b/src/js_parser.zig index 1d1433f5696c..8d24a0e7c8fe 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -875,6 +875,11 @@ pub const ImportScanner = struct { if (p.options.features.hot_module_reloading and st.is_export) { st.is_export = false; } + + // when bundling, all top-level variables become var + if (p.options.bundle) { + st.kind = .k_var; + } }, .s_export_default => |st| { // This is defer'd so that we still record export default for identifiers From 0a83a228309f575b75175c70ddeedb98008a005c Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Wed, 15 Mar 2023 11:57:06 -0700 Subject: [PATCH 108/255] Fix missing prefix --- src/string_immutable.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/string_immutable.zig b/src/string_immutable.zig index e6f982d753d6..c7cd43eb3058 100644 --- a/src/string_immutable.zig +++ b/src/string_immutable.zig @@ -186,7 +186,7 @@ pub const FormatValidIdentifier = struct { if (needs_gap) { needs_gap = false; - + if (start_i > 0) try writer.writeAll(self.name[0..start_i]); var slice = self.name[start_i..]; iterator = strings.CodepointIterator.init(slice); cursor = strings.CodepointIterator.Cursor{}; From 33b619515daaa61f7e3b7ce1fa85b8685d2be891 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Wed, 15 Mar 2023 11:57:18 -0700 Subject: [PATCH 109/255] Fix assigning to stack copy --- src/js_parser.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js_parser.zig b/src/js_parser.zig index 8d24a0e7c8fe..eec4bf3012b6 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -15703,7 +15703,7 @@ fn NewParser_( import_items.put(name, new_item) catch unreachable; p.is_import_item.put(p.allocator, new_item.ref.?, {}) catch unreachable; - var symbol = p.symbols.items[new_item.ref.?.innerIndex()]; + var symbol = &p.symbols.items[new_item.ref.?.innerIndex()]; // Mark this as generated in case it's missing. We don't want to // generate errors for missing import items that are automatically From 0e487796b19645517af51fba14217783d175a04f Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Wed, 15 Mar 2023 12:46:06 -0700 Subject: [PATCH 110/255] Fix missing runtime symbol --- src/bundler/bundle_v2.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index f92c742680d5..3f7fa29118ea 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -2427,7 +2427,7 @@ const LinkerContext = struct { // Include the "__export" symbol from the runtime if it was used in the // previous step. The previous step can't do this because it's running in // parallel and can't safely mutate the "importsToBind" map of another file. - if (flag.needs_exports_variable) { + if (flag.needs_export_symbol_from_runtime) { if (!runtime_export_symbol_ref.isValid()) { runtime_export_symbol_ref = this.runtimeFunction("__export"); } From 9a8128e940c4df721e3b2ddefb535ebcbdf6d1d2 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Wed, 15 Mar 2023 12:46:21 -0700 Subject: [PATCH 111/255] Fix bug with namespace exports --- src/js_printer.zig | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/js_printer.zig b/src/js_printer.zig index 6dae7217b6fc..602dd764e337 100644 --- a/src/js_printer.zig +++ b/src/js_printer.zig @@ -2700,6 +2700,12 @@ fn NewPrinter( p.printSpace(); flags.insert(.forbid_in); + + // this feels like a hack? I think something is wrong here. + if (e.op == .bin_assign) { + flags.remove(.expr_result_is_unused); + } + p.printExpr(e.right, right_level, flags); if (wrap) { From 00914262478618b8178fe5119f952e38e990e0ee Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Wed, 15 Mar 2023 12:59:54 -0700 Subject: [PATCH 112/255] Dramatically reduce allocations --- src/renamer.zig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/renamer.zig b/src/renamer.zig index 5fc8c56566f1..3df96c944f90 100644 --- a/src/renamer.zig +++ b/src/renamer.zig @@ -219,6 +219,10 @@ pub const NumberRenamer = struct { pub fn assignNamesRecursiveWithNumberScope(r: *NumberRenamer, initial_scope: *NumberScope, scope_: *js_ast.Scope, source_index: u32, sorted: *std.ArrayList(u32)) void { var s = initial_scope; var scope = scope_; + defer if (s != initial_scope) { + s.deinit(r.temp_allocator); + r.number_scope_pool.put(s); + }; // Ignore function argument scopes if (scope.kind == .function_args and scope.children.len == 1) { From 3f8175ae1c2e538f3cf2c02a1cdc8fd440b391cd Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Wed, 15 Mar 2023 15:30:21 -0700 Subject: [PATCH 113/255] Update launch.json --- .vscode/launch.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 63ad3932898c..be41724de905 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -142,8 +142,8 @@ "cwd": "${file}/../", "console": "internalConsole", "env": { - "BUN_DEBUG_QUIET_LOGS": "1", - "BUN_DUMP_SYMBOLS": "1" + "BUN_DEBUG_QUIET_LOGS": "1" + // "BUN_DUMP_SYMBOLS": "1" } }, { From ef9fe5ba0c633ed872976fea343ae4a89e5bde53 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Wed, 15 Mar 2023 17:32:27 -0700 Subject: [PATCH 114/255] Add missing flags --- src/js_parser.zig | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/js_parser.zig b/src/js_parser.zig index eec4bf3012b6..96e41b09484b 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -1069,6 +1069,13 @@ pub const ImportScanner = struct { .is_exported = true, }); try p.recordExport(item.name.loc, item.alias, ref); + + var record = &p.import_records.items[st.import_record_index]; + if (strings.eqlComptime(item.original_name, "default")) { + record.contains_default_alias = true; + } else if (strings.eqlComptime(item.original_name, "__esModule")) { + record.contains_es_module_alias = true; + } } }, else => {}, @@ -19184,7 +19191,7 @@ fn NewParser_( } } - const wrapper_ref = brk: { + const wrapper_ref: ?Ref = brk: { if (p.options.bundle) { break :brk p.newSymbol( .other, From 060937edf08dcad7ff4befcbdfbd4a1dcddf1684 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Wed, 15 Mar 2023 17:43:32 -0700 Subject: [PATCH 115/255] Update js_parser.zig --- src/js_parser.zig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/js_parser.zig b/src/js_parser.zig index 96e41b09484b..fecdc227a97a 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -676,6 +676,7 @@ pub const ImportScanner = struct { result.* = alias; } strings.sortDesc(sorted); + p.named_imports.ensureUnusedCapacity(sorted.len) catch unreachable; // Create named imports for these property accesses. This will // cause missing imports to generate useful warnings. @@ -1054,7 +1055,7 @@ pub const ImportScanner = struct { }, .s_export_from => |st| { try p.import_records_for_current_part.append(allocator, st.import_record_index); - + p.named_imports.ensureUnusedCapacity(st.items.len) catch unreachable; for (st.items) |item| { const ref = item.name.ref orelse p.panic("Expected export from item to have a name {any}", .{st}); // Note that the imported alias is not item.Alias, which is the From 7f5074749dcf3e1cd6b964b38b0f80e02e873a56 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Wed, 15 Mar 2023 18:41:31 -0700 Subject: [PATCH 116/255] small cleanup --- src/js_ast.zig | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/js_ast.zig b/src/js_ast.zig index f5e70f427848..ce668001929b 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -366,7 +366,7 @@ pub const Binding = struct { } pub fn toExpr(binding: *const Binding, wrapper: anytype) Expr { - var loc = binding.loc; + const loc = binding.loc; switch (binding.data) { .b_missing => { @@ -399,24 +399,26 @@ pub const Binding = struct { var properties = wrapper .allocator .alloc(G.Property, b.properties.len) catch unreachable; - var i: usize = 0; - while (i < properties.len) : (i += 1) { - const item = b.properties[i]; - properties[i] = G.Property{ + for (properties, b.properties) |*property, item| { + property.* = .{ .flags = item.flags, .key = item.key, .kind = if (item.flags.contains(.is_spread)) - G.Property.Kind.spread + .spread else - G.Property.Kind.normal, + .normal, .value = toExpr(&item.value, wrapper), .initializer = item.default_value, }; } - return Expr.init(E.Object, E.Object{ - .properties = G.Property.List.init(properties), - .is_single_line = b.is_single_line, - }, loc); + return Expr.init( + E.Object, + E.Object{ + .properties = G.Property.List.init(properties), + .is_single_line = b.is_single_line, + }, + loc, + ); }, else => { Global.panic("Interanl error", .{}); From 313b48a04573db30500efb85b2c8a83d8bee6505 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Wed, 15 Mar 2023 18:45:21 -0700 Subject: [PATCH 117/255] Make the export name better --- src/js_parser.zig | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/js_parser.zig b/src/js_parser.zig index fecdc227a97a..d887d1f330ba 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -7499,15 +7499,32 @@ fn NewParser_( } const import_record_index = p.addImportRecord(.stmt, parsedPath.loc, parsedPath.text); - var path_name = fs.PathName.init(strings.append(p.allocator, "import_", parsedPath.text) catch unreachable); - const namespace_ref = p.storeNameInRef(path_name.nonUniqueNameString(p.allocator) catch unreachable) catch unreachable; + const path_name = fs.PathName.init(parsedPath.text); + const namespace_ref = p.storeNameInRef( + std.fmt.allocPrint( + p.allocator, + "import_{any}", + .{ + path_name.fmtIdentifier(), + }, + ) catch unreachable, + ) catch unreachable; if (comptime track_symbol_usage_during_parse_pass) { // In the scan pass, we need _some_ way of knowing *not* to mark as unused p.import_records.items[import_record_index].calls_runtime_re_export_fn = true; } + p.current_scope.is_after_const_local_prefix = true; - return p.s(S.ExportFrom{ .items = export_clause.clauses, .is_single_line = export_clause.is_single_line, .namespace_ref = namespace_ref, .import_record_index = import_record_index }, loc); + return p.s( + S.ExportFrom{ + .items = export_clause.clauses, + .is_single_line = export_clause.is_single_line, + .namespace_ref = namespace_ref, + .import_record_index = import_record_index, + }, + loc, + ); } try p.lexer.expectOrInsertSemicolon(); From e97eeb1f948f09f85768454020bad273ab578058 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Wed, 15 Mar 2023 18:45:33 -0700 Subject: [PATCH 118/255] Fix unnecessary `var foo = foo` --- src/bundler/bundle_v2.zig | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 3f7fa29118ea..6f278dbc93df 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -2942,7 +2942,6 @@ const LinkerContext = struct { js_ast.Expr.initIdentifier(exports_ref, loc), js_ast.Expr.allocate(allocator_, js_ast.E.Object, .{ .properties = js_ast.G.Property.List.fromList(properties) }, loc), }; - // the end incase we somehow get into a state where needs_export_variable is false but properties.len > 0 remaining_stmts[0] = js_ast.Stmt.allocate( allocator_, js_ast.S.SExpr, @@ -5396,13 +5395,6 @@ const LinkerContext = struct { }, loc, ), - .value = Expr.init( - E.Identifier, - E.Identifier{ - .ref = ref, - }, - loc, - ), }, ) catch unreachable; return Expr.init( From f2968136b0149fe923652973bbef8034b722f88c Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Thu, 16 Mar 2023 22:22:26 -0700 Subject: [PATCH 119/255] Implement CommonJS -> ESM conversion --- src/js_ast.zig | 84 +++++++++++++++++------- src/js_parser.zig | 158 ++++++++++++++++++++++++++++++++++++++++++--- src/js_printer.zig | 20 ++++++ 3 files changed, 230 insertions(+), 32 deletions(-) diff --git a/src/js_ast.zig b/src/js_ast.zig index ce668001929b..f9e44dbfb114 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -1283,34 +1283,38 @@ pub const E = struct { } }; - // This is similar to an EIdentifier but it represents a reference to an ES6 - // import item. - // - // Depending on how the code is linked, the file containing this EImportIdentifier - // may or may not be in the same module group as the file it was imported from. - // - // If it's the same module group than we can just merge the import item symbol - // with the corresponding symbol that was imported, effectively renaming them - // to be the same thing and statically binding them together. - // - // But if it's a different module group, then the import must be dynamically - // evaluated using a property access off the corresponding namespace symbol, - // which represents the result of a require() call. - // - // It's stored as a separate type so it's not easy to confuse with a plain - // identifier. For example, it'd be bad if code trying to convert "{x: x}" into - // "{x}" shorthand syntax wasn't aware that the "x" in this case is actually - // "{x: importedNamespace.x}". This separate type forces code to opt-in to - // doing this instead of opt-out. + /// This is similar to an `Identifier` but it represents a reference to an ES6 + /// import item. + /// + /// Depending on how the code is linked, the file containing this EImportIdentifier + /// may or may not be in the same module group as the file it was imported from. + /// + /// If it's the same module group than we can just merge the import item symbol + /// with the corresponding symbol that was imported, effectively renaming them + /// to be the same thing and statically binding them together. + /// + /// But if it's a different module group, then the import must be dynamically + /// evaluated using a property access off the corresponding namespace symbol, + /// which represents the result of a require() call. + /// + /// It's stored as a separate type so it's not easy to confuse with a plain + /// identifier. For example, it'd be bad if code trying to convert "{x: x}" into + /// "{x}" shorthand syntax wasn't aware that the "x" in this case is actually + /// "{x: importedNamespace.x}". This separate type forces code to opt-in to + /// doing this instead of opt-out. pub const ImportIdentifier = struct { ref: Ref = Ref.None, - // If true, this was originally an identifier expression such as "foo". If - // false, this could potentially have been a member access expression such - // as "ns.foo" off of an imported namespace object. + /// If true, this was originally an identifier expression such as "foo". If + /// false, this could potentially have been a member access expression such + /// as "ns.foo" off of an imported namespace object. was_originally_identifier: bool = false, }; + pub const CommonJSExportIdentifier = struct { + ref: Ref = Ref.None, + }; + // This is similar to EIdentifier but it represents class-private fields and // methods. It can be used where computed properties can be used, such as // EIndex and Property. @@ -2999,6 +3003,17 @@ pub const Expr = struct { }, }; }, + E.CommonJSExportIdentifier => { + return Expr{ + .loc = loc, + .data = Data{ + .e_commonjs_export_identifier = .{ + .ref = st.ref, + }, + }, + }; + }, + E.PrivateIdentifier => { return Expr{ .loc = loc, @@ -3371,6 +3386,16 @@ pub const Expr = struct { }, }; }, + E.CommonJSExportIdentifier => { + return Expr{ + .loc = loc, + .data = Data{ + .e_commonjs_export_identifier = .{ + .ref = st.ref, + }, + }, + }; + }, E.PrivateIdentifier => { return Expr{ .loc = loc, @@ -3562,6 +3587,8 @@ pub const Expr = struct { e_class, e_require, + e_commonjs_export_identifier, + // This should never make it to the printer inline_identifier, @@ -4204,6 +4231,7 @@ pub const Expr = struct { e_identifier: E.Identifier, e_import_identifier: E.ImportIdentifier, e_private_identifier: E.PrivateIdentifier, + e_commonjs_export_identifier: E.CommonJSExportIdentifier, e_boolean: E.Boolean, e_number: E.Number, @@ -5148,7 +5176,7 @@ pub const Ast = struct { approximate_newline_count: usize = 0, has_lazy_export: bool = false, - runtime_imports: Runtime.Imports, + runtime_imports: Runtime.Imports = .{}, runtime_import_record_id: ?u32 = null, needs_runtime: bool = false, @@ -5203,6 +5231,16 @@ pub const Ast = struct { allocator: std.mem.Allocator, top_level_symbols_to_parts: TopLevelSymbolToParts = .{}, + commonjs_named_exports: CommonJSNamedExports = .{}, + + redirect_import_record_index: ?u32 = null, + + pub const CommonJSNamedExport = struct { + loc_ref: LocRef, + needs_decl: bool = true, + }; + pub const CommonJSNamedExports = bun.StringArrayHashMapUnmanaged(CommonJSNamedExport); + pub const NamedImports = std.ArrayHashMap(Ref, NamedImport, RefHashCtx, true); pub const NamedExports = bun.StringArrayHashMap(NamedExport); diff --git a/src/js_parser.zig b/src/js_parser.zig index d887d1f330ba..f71fcee8737a 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -2645,6 +2645,16 @@ pub const Parser = struct { var did_import_fast_refresh = false; + if (p.commonjs_named_exports.count() > 0) { + const export_refs = p.commonjs_named_exports.values(); + + if (!p.commonjs_named_exports_deoptimized and p.es6_export_keyword.len == 0) { + p.es6_export_keyword.loc = export_refs[0].loc_ref.loc; + p.es6_export_keyword.len = 5; + } + } else if (p.options.bundle and parts.items.len == 1) { + } + // Analyze cross-part dependencies for tree shaking and code splitting var exports_kind = js_ast.ExportsKind.none; const uses_exports_ref = p.symbols.items[p.exports_ref.innerIndex()].use_count_estimate > 0; @@ -3797,6 +3807,10 @@ fn NewParser_( declared_symbols_for_reuse: DeclaredSymbol.List = .{}, runtime_imports: RuntimeImports = RuntimeImports{}, + commonjs_named_exports: js_ast.Ast.CommonJSNamedExports = .{}, + commonjs_named_exports_deoptimized: bool = false, + commonjs_named_exports_needs_conversion: u32 = std.math.maxInt(u32), + parse_pass_symbol_uses: ParsePassSymbolUsageType = undefined, // duplicate_case_checker: void, // non_bmp_identifiers: StringBoolMap, @@ -12763,6 +12777,9 @@ fn NewParser_( .e_import_identifier => |ident| { break :brk p.newExpr(E.String{ .data = p.loadNameFromRef(ident.ref) }, expr.loc); }, + .e_commonjs_export_identifier => |ident| { + break :brk p.newExpr(E.String{ .data = p.loadNameFromRef(ident.ref) }, expr.loc); + }, .e_identifier => |ident| { break :brk p.newExpr(E.String{ .data = p.loadNameFromRef(ident.ref) }, expr.loc); }, @@ -13295,6 +13312,11 @@ fn NewParser_( return func; } + fn deoptimizeCommonJSNamedExports(p: *P) void { + // exists for debugging + p.commonjs_named_exports_deoptimized = true; + } + fn maybeKeepExprSymbolName(p: *P, expr: Expr, original_name: string, was_anonymous_named_expr: bool) Expr { return if (was_anonymous_named_expr) p.keepExprSymbolName(expr, original_name) else expr; } @@ -13318,6 +13340,7 @@ fn NewParser_( // Instead of doing this at runtime using "fn.call(module.exports)", we // do it at compile time using expression substitution here. p.recordUsage(p.exports_ref); + p.deoptimizeCommonJSNamedExports(); return p.newExpr(E.Identifier{ .ref = p.exports_ref }, loc); } } @@ -14308,7 +14331,6 @@ fn NewParser_( .has_chain_parent = (e_.optional_chain orelse js_ast.OptionalChain.start) == js_ast.OptionalChain.ccontinue, }); e_.target = target; - switch (e_.index.data) { .e_private_identifier => |_private| { var private = _private; @@ -14578,6 +14600,7 @@ fn NewParser_( } e_.target = p.visitExpr(e_.target); + if (e_.optional_chain == null) { if (p.maybeRewritePropertyAccess( expr.loc, @@ -14586,6 +14609,7 @@ fn NewParser_( e_.name_loc, .{ .is_call_target = is_call_target, + .assign_target = in.assign_target, // .is_template_tag = p.template_tag != null, }, )) |_expr| { @@ -15229,7 +15253,7 @@ fn NewParser_( return true; } }, - .e_import_identifier => { + .e_commonjs_export_identifier, .e_import_identifier => { // References to an ES6 import item are always side-effect free in an // ECMAScript environment. @@ -15760,13 +15784,80 @@ fn NewParser_( } } - // Rewrite "module.require()" to "require()" for Webpack compatibility. - // See https://github.com/webpack/webpack/pull/7750 for more info. - // This also makes correctness a little easier. - if (identifier_opts.is_call_target and id.ref.eql(p.module_ref) and strings.eqlComptime(name, "require")) { - p.ignoreUsage(p.module_ref); - p.recordUsage(p.require_ref); - return p.newExpr(E.Identifier{ .ref = p.require_ref }, name_loc); + if (!p.is_control_flow_dead and id.ref.eql(p.module_ref)) { + // Rewrite "module.require()" to "require()" for Webpack compatibility. + // See https://github.com/webpack/webpack/pull/7750 for more info. + // This also makes correctness a little easier. + if (identifier_opts.is_call_target and strings.eqlComptime(name, "require")) { + p.ignoreUsage(p.module_ref); + p.recordUsage(p.require_ref); + return p.newExpr(E.Identifier{ .ref = p.require_ref }, name_loc); + } else if (!p.commonjs_named_exports_deoptimized and strings.eqlComptime(name, "exports")) { + // Deoptimizations: + // delete module.exports + // module.exports(); + + if (identifier_opts.is_call_target or identifier_opts.is_delete_target or identifier_opts.assign_target != .none) { + p.deoptimizeCommonJSNamedExports(); + return null; + } + + // rewrite `module.exports` to `exports` + return p.newExpr(E.Identifier{ .ref = p.exports_ref }, name_loc); + } else if (strings.eqlComptime(name, "id") and identifier_opts.assign_target == .none) { + // inline module.id + p.ignoreUsage(p.module_ref); + return p.newExpr(E.String.init(p.source.path.text), name_loc); + } else if (strings.eqlComptime(name, "filename") and identifier_opts.assign_target == .none) { + // inline module.filename + p.ignoreUsage(p.module_ref); + return p.newExpr(E.String.init(p.source.path.name.filename), name_loc); + } else if (strings.eqlComptime(name, "path") and identifier_opts.assign_target == .none) { + // inline module.path + p.ignoreUsage(p.module_ref); + return p.newExpr(E.String.init(p.source.path.pretty), name_loc); + } + } + + if (!p.is_control_flow_dead and id.ref.eql(p.exports_ref) and !p.commonjs_named_exports_deoptimized) { + if (identifier_opts.is_delete_target) { + p.deoptimizeCommonJSNamedExports(); + return null; + } + + var named_export_entry = p.commonjs_named_exports.getOrPut(p.allocator, name) catch unreachable; + if (!named_export_entry.found_existing) { + const new_ref = p.newSymbol( + .other, + if (p.options.bundle) + MutableString.ensureValidIdentifier( + name, + p.allocator, + ) catch unreachable + else + std.fmt.allocPrint(p.allocator, "exports_{any}", .{strings.fmtIdentifier(name)}) catch unreachable, + ) catch unreachable; + named_export_entry.value_ptr.* = .{ + .loc_ref = LocRef{ + .loc = name_loc, + .ref = new_ref, + }, + .needs_decl = true, + }; + if (p.commonjs_named_exports_needs_conversion == std.math.maxInt(u32)) + p.commonjs_named_exports_needs_conversion = @truncate(u32, p.commonjs_named_exports.count() - 1); + } + + const ref = named_export_entry.value_ptr.*.loc_ref.ref.?; + p.ignoreUsage(id.ref); + p.recordUsage(ref); + + return p.newExpr( + E.CommonJSExportIdentifier{ + .ref = ref, + }, + name_loc, + ); } // If this is a known enum value, inline the value of the enum @@ -16343,9 +16434,54 @@ fn NewParser_( }, .s_expr => |data| { p.stmt_expr_value = data.value.data; + const is_top_level = p.current_scope == p.module_scope; + + p.commonjs_named_exports_needs_conversion = if (is_top_level) + std.math.maxInt(u32) + else + p.commonjs_named_exports_needs_conversion; + data.value = p.visitExpr(data.value); + // simplify unused data.value = SideEffects.simpifyUnusedExpr(p, data.value) orelse data.value.toEmpty(); + + if (is_top_level) { + if (data.value.data == .e_binary) { + const to_convert = p.commonjs_named_exports_needs_conversion; + if (to_convert != std.math.maxInt(u32)) { + p.commonjs_named_exports_needs_conversion = std.math.maxInt(u32); + convert: { + const bin: *E.Binary = data.value.data.e_binary; + if (bin.op == .bin_assign and bin.left.data == .e_commonjs_export_identifier) { + var last = &p.commonjs_named_exports.values()[to_convert]; + if (!last.needs_decl) break :convert; + last.needs_decl = false; + + var decls = p.allocator.alloc(Decl, 1) catch unreachable; + const ref = bin.left.data.e_commonjs_export_identifier.ref; + decls[0] = .{ + .binding = p.b(B.Identifier{ .ref = ref }, bin.left.loc), + .value = bin.right, + }; + p.module_scope.generated.push(p.allocator, ref) catch unreachable; + p.recordDeclaredSymbol(ref) catch unreachable; + p.ignoreUsage(ref); + p.es6_export_keyword.loc = stmt.loc; + p.es6_export_keyword.len = 5; + const new_stmt = p.s(S.Local{ + .kind = .k_var, + .is_export = true, + .decls = decls, + }, stmt.loc); + stmts.append(new_stmt) catch unreachable; + + return; + } + } + } + } + } }, .s_throw => |data| { data.value = p.visitExpr(data.value); @@ -19257,6 +19393,7 @@ fn NewParser_( false, // .top_Level_await_keyword = p.top_level_await_keyword, .bun_plugin = p.bun_plugin, + .commonjs_named_exports = p.commonjs_named_exports, }; } @@ -19312,6 +19449,9 @@ fn NewParser_( .scopes_in_order = scope_order, .needs_jsx_import = if (comptime only_scan_imports_and_do_not_visit) false else NeedsJSXType{}, .lexer = lexer, + + // Only enable during bundling + .commonjs_named_exports_deoptimized = !opts.bundle, }; this.symbols = std.ArrayList(Symbol).init(allocator); diff --git a/src/js_printer.zig b/src/js_printer.zig index 602dd764e337..dc3cea6f2264 100644 --- a/src/js_printer.zig +++ b/src/js_printer.zig @@ -489,6 +489,9 @@ pub const Options = struct { source_map_handler: ?SourceMapHandler = null, css_import_behavior: Api.CssInJsBehavior = Api.CssInJsBehavior.facade, + commonjs_named_exports: js_ast.Ast.CommonJSNamedExports = .{}, + commonjs_named_exports_ref: Ref = Ref.None, + minify_whitespace: bool = false, require_or_import_meta_for_source_callback: RequireOrImportMeta.Callback = .{}, @@ -1810,6 +1813,23 @@ fn NewPrinter( p.addSourceMapping(expr.loc); p.print("import.meta"); }, + .e_commonjs_export_identifier => |id| { + p.printSpaceBeforeIdentifier(); + p.addSourceMapping(expr.loc); + + for (p.options.commonjs_named_exports.keys(), p.options.commonjs_named_exports.values()) |key, value| { + if (value.loc_ref.ref.?.eql(id.ref)) { + if (value.needs_decl) { + p.printSymbol(p.options.commonjs_named_exports_ref); + p.print("."); + p.print(key); + } else { + p.printSymbol(value.loc_ref.ref.?); + } + break; + } + } + }, .e_new => |e| { const has_pure_comment = e.can_be_unwrapped_if_unused; const wrap = level.gte(.call) or (has_pure_comment and level.gte(.postfix)); From 077f4be5c7753deb2e7e5d4e8fe5b1781ded3e11 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Thu, 16 Mar 2023 22:25:01 -0700 Subject: [PATCH 120/255] Implement module redirects --- root.zig | 1 + src/bundler/bundle_v2.zig | 50 ++++++++++++--- src/bundler/generate_node_modules_bundle.zig | 4 +- src/js_parser.zig | 67 +++++++++++++++++++- src/main.zig | 2 +- src/runtime.zig | 2 + 6 files changed, 110 insertions(+), 16 deletions(-) diff --git a/root.zig b/root.zig index 6075118829c0..5d4baefae84e 100644 --- a/root.zig +++ b/root.zig @@ -1,4 +1,5 @@ pub usingnamespace @import("src/main.zig"); +pub const bun = @import("src/bun.zig"); pub const content = struct { pub const error_js_path = "packages/bun-error/dist/index.js"; pub const error_js = @embedFile(error_js_path); diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 6f278dbc93df..2937f341fad9 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -183,9 +183,6 @@ pub const ThreadPool = struct { this.heap = ThreadlocalArena.init() catch unreachable; this.allocator = this.heap.allocator(); var allocator = this.allocator; - if (Environment.isDebug) { - Output.prettyln("Thread started.\n", .{}); - } // Ensure we're using the same allocator for the worker's data. js_ast.Expr.Data.Store.deinit(); @@ -235,6 +232,8 @@ pub const BundleV2 = struct { reachable: std.ArrayList(Index), visited: bun.bit_set.DynamicBitSet = undefined, all_import_records: []ImportRecord.List, + redirects: []?u32, + redirect_map: PathToSourceIndexMap, // Find all files reachable from all entry points. This order should be // deterministic given that the entry point order is deterministic, since the @@ -250,12 +249,25 @@ pub const BundleV2 = struct { const import_record_list_id = source_index; // when there are no import records, v index will be invalid if (import_record_list_id.get() < v.all_import_records.len) { - for (v.all_import_records[import_record_list_id.get()].slice()) |*import_record| { + var import_records = v.all_import_records[import_record_list_id.get()].slice(); + for (import_records) |*import_record| { const other_source = import_record.source_index; if (other_source.isValid()) { - v.visit(other_source); + if (v.redirects[other_source.get()]) |redirect_id| { + var other_import_records = v.all_import_records[other_source.get()].slice(); + import_record.source_index = other_import_records[redirect_id].source_index; + } + + v.visit(import_record.source_index); } } + + // Redirects replace the source file with another file + if (v.redirects[source_index.get()]) |redirect_id| { + const redirect_source_index = v.all_import_records[source_index.get()].slice()[redirect_id].source_index.get(); + v.visit(Index.source(redirect_source_index)); + return; + } } // Each file must come after its dependencies @@ -266,7 +278,9 @@ pub const BundleV2 = struct { var visitor = Visitor{ .reachable = try std.ArrayList(Index).initCapacity(this.graph.allocator, this.graph.entry_points.items.len + 1), .visited = try bun.bit_set.DynamicBitSet.initEmpty(this.graph.allocator, this.graph.input_files.len), + .redirects = this.graph.ast.items(.redirect_import_record_index), .all_import_records = this.graph.ast.items(.import_records), + .redirect_map = this.graph.path_to_source_index_map, }; defer visitor.visited.deinit(); @@ -274,9 +288,9 @@ pub const BundleV2 = struct { visitor.visit(entry_point); } - if (comptime Environment.allow_assert) { - Output.prettyln("Reachable count: {d} / {d}", .{ visitor.reachable.items.len, this.graph.input_files.len }); - } + // if (comptime Environment.allow_assert) { + // Output.prettyln("Reachable count: {d} / {d}", .{ visitor.reachable.items.len, this.graph.input_files.len }); + // } return visitor.reachable.toOwnedSlice(); } @@ -609,14 +623,25 @@ pub const BundleV2 = struct { graph.input_files.append(graph.allocator, new_input_file) catch unreachable; graph.ast.append(graph.allocator, js_ast.Ast.empty) catch unreachable; batch.push(ThreadPoolLib.Batch.from(&entry.value_ptr.task)); + diff += 1; } } var import_records = result.ast.import_records.clone(this.graph.allocator) catch unreachable; - for (import_records.slice()) |*record| { + for (import_records.slice(), 0..) |*record, i| { if (graph.path_to_source_index_map.get(wyhash(0, record.path.text))) |source_index| { record.source_index.value = source_index; + + if (result.ast.redirect_import_record_index) |compare| { + if (compare == @truncate(u32, i)) { + graph.path_to_source_index_map.put( + graph.allocator, + bun.hash(result.source.path.text), + source_index, + ) catch unreachable; + } + } } } result.ast.import_records = import_records; @@ -1245,7 +1270,7 @@ pub const Graph = struct { source_index_map: std.AutoArrayHashMapUnmanaged(Index.Int, Ref.Int) = .{}, /// Stable source index mapping - path_to_source_index_map: std.HashMapUnmanaged(u64, Index.Int, IdentityContext(u64), 80) = .{}, + path_to_source_index_map: PathToSourceIndexMap = .{}, pub const InputFile = struct { source: Logger.Source, @@ -1256,6 +1281,8 @@ pub const Graph = struct { }; }; +const PathToSourceIndexMap = std.HashMapUnmanaged(u64, Index.Int, IdentityContext(u64), 80); + const EntryPoint = struct { // This may be an absolute path or a relative path. If absolute, it will // eventually be turned into a relative path by computing the path relative @@ -5549,6 +5576,9 @@ const LinkerContext = struct { // TODO: IIFE .indent = 0, + .commonjs_named_exports = ast.commonjs_named_exports, + .commonjs_named_exports_ref = ast.exports_ref, + .allocator = allocator, .to_esm_ref = toESMRef, .to_commonjs_ref = toCommonJSRef, diff --git a/src/bundler/generate_node_modules_bundle.zig b/src/bundler/generate_node_modules_bundle.zig index 7dc2cb945ba4..25cfe912dfe8 100644 --- a/src/bundler/generate_node_modules_bundle.zig +++ b/src/bundler/generate_node_modules_bundle.zig @@ -184,9 +184,7 @@ pub const ThreadPool = struct { pub fn run(this: *Worker) void { Output.Source.configureThread(); this.thread_id = std.Thread.getCurrentId(); - if (Environment.isDebug) { - Output.prettyln("Thread started.\n", .{}); - } + js_ast.Expr.Data.Store.create(this.generator.allocator); js_ast.Stmt.Data.Store.create(this.generator.allocator); this.data = this.generator.allocator.create(WorkerData) catch unreachable; diff --git a/src/js_parser.zig b/src/js_parser.zig index f71fcee8737a..8a0edafe07f1 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -2653,6 +2653,69 @@ pub const Parser = struct { p.es6_export_keyword.len = 5; } } else if (p.options.bundle and parts.items.len == 1) { + // Specially handle modules shaped like this: + // CommonJS: + // + // if (process.env.NODE_ENV === 'production') + // module.exports = require('./foo.prod.js') + // else + // module.exports = require('./foo.dev.js') + // + // ESM: + // + // export * from 'react'; + // + var part = &parts.items[0]; + if (part.stmts.len == 1) { + var stmt: Stmt = part.stmts[0]; + if (p.symbols.items[p.module_ref.innerIndex()].use_count_estimate == 1) { + if (stmt.data == .s_expr) { + const value: Expr = stmt.data.s_expr.value; + + if (value.data == .e_binary) { + const bin = value.data.e_binary; + const left = bin.left; + const right = bin.right; + if (bin.op == .bin_assign and + right.data == .e_require and + left.data == .e_dot and + strings.eqlComptime(left.data.e_dot.name, "exports") and + left.data.e_dot.target.data == .e_identifier and + left.data.e_dot.target.data.e_identifier.ref.eql(p.module_ref)) + { + return js_ast.Result{ + .ok = true, + .ast = js_ast.Ast{ + .allocator = p.allocator, + .import_records = ImportRecord.List.init(p.import_records.items), + .redirect_import_record_index = right.data.e_require.import_record_index, + .named_imports = p.named_imports, + .named_exports = p.named_exports, + }, + }; + } + } + } + } else if (p.es6_export_keyword.len > 0) { + switch (stmt.data) { + .s_export_star => |star| { + if (star.alias == null) { + return js_ast.Result{ + .ok = true, + .ast = .{ + .allocator = p.allocator, + .import_records = ImportRecord.List.init(p.import_records.items), + .redirect_import_record_index = star.import_record_index, + .named_imports = p.named_imports, + .named_exports = p.named_exports, + }, + }; + } + }, + else => {}, + } + } + } } // Analyze cross-part dependencies for tree shaking and code splitting @@ -9597,11 +9660,11 @@ fn NewParser_( isDirectivePrologue = true; if (str.eqlComptime("use strict")) { - skip = p.options.features.dynamic_require or skip; + skip = true; // Track "use strict" directives p.current_scope.strict_mode = .explicit_strict_mode; } else if (str.eqlComptime("use asm")) { - skip = p.options.features.dynamic_require or skip; + skip = true; stmt.data = Prefill.Data.SEmpty; } } diff --git a/src/main.zig b/src/main.zig index ba3fd97d94d5..0c7cf958422c 100644 --- a/src/main.zig +++ b/src/main.zig @@ -12,6 +12,7 @@ pub fn panic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, addr const CrashReporter = @import("./crash_reporter.zig"); pub fn main() void { + const bun = @import("root").bun; const Output = bun.Output; const Environment = bun.Environment; @@ -42,4 +43,3 @@ test "panic" { } pub const build_options = @import("build_options"); -pub const bun = @import("./bun.zig"); diff --git a/src/runtime.zig b/src/runtime.zig index cfc9574ed5c6..3ea35a88055c 100644 --- a/src/runtime.zig +++ b/src/runtime.zig @@ -294,6 +294,8 @@ pub const Runtime = struct { inject_jest_globals: bool = false, + commonjs_named_exports: bool = true, + /// Instead of jsx("div", {}, void 0) /// -> /// { From 7fd37b942f8e2140a47b08fb094af5e33aad1d1f Mon Sep 17 00:00:00 2001 From: dave caruso Date: Fri, 17 Mar 2023 03:05:51 -0400 Subject: [PATCH 121/255] Port esbuild bundler tests for new bundler (#2380) * started porting esbuild tests * clean up test names and api before moving on * port tests using a program i wrote * replace todo generated comment * fix generated tests not including some files * work on tests * [github web editor] add define, external, inject, minifySyntax, minifyWhitespace options. * get most of the todo comments out of the way, but expectBundled does not handle most of the cases * continue working on esbuild tests * use test.skip for unsupported tests --- .devcontainer/devcontainer.json | 3 +- .vscode/extensions.json | 8 +- test/bun.lockb | Bin 34025 -> 34629 bytes test/bundler/esbuild/bundler_css.test.ts | 527 ++ test/bundler/esbuild/bundler_dce.test.ts | 2748 ++++++++ test/bundler/esbuild/bundler_default.test.ts | 5720 +++++++++++++++++ .../esbuild/bundler_importstar.test.ts | 1061 +++ .../esbuild/bundler_importstar_ts.test.ts | 304 + test/bundler/esbuild/bundler_loader.test.ts | 770 +++ test/bundler/esbuild/bundler_lower.test.ts | 1808 ++++++ .../esbuild/bundler_packagejson.test.ts | 1878 ++++++ .../bundler/esbuild/bundler_splitting.test.ts | 382 ++ test/bundler/esbuild/bundler_ts.test.ts | 1637 +++++ test/bundler/esbuild/bundler_tsconfig.test.ts | 1564 +++++ test/bundler/esbuild/expectBundled.ts | 560 ++ test/bundler/esbuild/run-single-test.sh | 15 + test/package.json | 5 +- 17 files changed, 18987 insertions(+), 3 deletions(-) create mode 100644 test/bundler/esbuild/bundler_css.test.ts create mode 100644 test/bundler/esbuild/bundler_dce.test.ts create mode 100644 test/bundler/esbuild/bundler_default.test.ts create mode 100644 test/bundler/esbuild/bundler_importstar.test.ts create mode 100644 test/bundler/esbuild/bundler_importstar_ts.test.ts create mode 100644 test/bundler/esbuild/bundler_loader.test.ts create mode 100644 test/bundler/esbuild/bundler_lower.test.ts create mode 100644 test/bundler/esbuild/bundler_packagejson.test.ts create mode 100644 test/bundler/esbuild/bundler_splitting.test.ts create mode 100644 test/bundler/esbuild/bundler_ts.test.ts create mode 100644 test/bundler/esbuild/bundler_tsconfig.test.ts create mode 100644 test/bundler/esbuild/expectBundled.ts create mode 100644 test/bundler/esbuild/run-single-test.sh diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 9d9a4c5b82e2..f96eebb15967 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -25,7 +25,8 @@ "/home/ubuntu/vscode-zig.vsix", "vadimcn.vscode-lldb", "esbenp.prettier-vscode", - "xaver.clang-format" + "xaver.clang-format", + "bierner.comment-tagged-templates" ], "postCreateCommand": "cd /build/bun; bash /build/getting-started.sh; cat /build/README.md", diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 40f4162250e2..d91095be320d 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,3 +1,9 @@ { - "recommendations": ["ziglang.vscode-zig", "esbenp.prettier-vscode", "xaver.clang-format", "vadimcn.vscode-lldb"] + "recommendations": [ + "ziglang.vscode-zig", + "esbenp.prettier-vscode", + "xaver.clang-format", + "vadimcn.vscode-lldb", + "bierner.comment-tagged-templates" + ] } diff --git a/test/bun.lockb b/test/bun.lockb index ced88bdbb23f81215cd042a8d8c16ef3a530cc6e..a4ad4c2d3ccb24fb94b2aaf15742bead2337bdf2 100755 GIT binary patch delta 5798 zcmds5dsLNG7C-00RW5KrK#30|z;_B);3C*yTrvq2A3Wl5k%yPdMeqfA(IT6<*Jo;? z*=}s0e6EUGmNl8>qo#Dtlr}lbrfHICTn*W>uySQJt=`RVe-BZ|HO-1y{V`{K`|Ri0 z=bZh0`+Vn|-u0$GY%<+ryDnh(Gn=X`Ubkmo^T;I(5>f=Ms#?M~( zaQ|ANmU&ys6HNCNZ@E3uw*!JuA^hHDo`tO*Ar>~*yS$A;Btsqy`EIG{J-4?k@p!#r znQKWK`?z2dA_V%@wiZuHlUF<*pxG~KTI5NouM@k#v)4I&PBoizvbhnStG&$NbU*}xZCP({T)45x{JOCjUSTt8BfZ6p+z*gX9jGMb| zbZLfl;9340^mzQw!Vgca3r5Uu*YTr}v!6wt*2d;~uh-W;PW&Ce>!j!GE)3cKi)yF&MZPm^*w^=U)Wo_KyRz-TlBrd>~fo28(n9r_R@V>OG5Fg}8F&aH2*6 zvsFV~7%K!Gf2NKTfw`jq;CSG3iV%4Guj}|Aa18j}%<~L3f#4A@(-rV5tot4gIBW_! z|7J_?*&r^4h!}2W}red6ub_He0WO7;@az1sS zewMmXe@05MLyje9u*1?AD8xkAn#g6zk~^szn)gTvambP6L|sN5sJBu#>c5Z@>M(^4 zpvur3^TYw@gsKN-%SP%Bb(o(9pG(!2Z22iUtq${8%r2Eqhi1!4>b5%MMpD8Y@_BNi z{+c?%9OjH5Au{Q7a5e^~ggeX+f=^ak9HWkKhlO8S4u)o?T_IUAhm=7M%WaV5s#9}W zv*g>eOSiebIMRHdP1{g6)A%q@*#4f zK1Cg$25Op0oxP|XhxE{7t)Mi!!=SXb{Hac(_-iHwf|#i`-KvV% z@jIYqt1{aVL%ST5=Hw_S&2b14L~~aNN|SYf()^tTrL{;weiW$wZU?2=9Ra1;g&~bp znYA2L2FDy_AoYZfqr!nf@^0!F>ae^Do%SjAShFl9BpJVbK}nHWrd)+8<8#dOk*XC` z9iMGJdXb8a6QWR+%?DMZQpZ4*snoSY4XPE?Et(cou}Z}v?`Nn~11ME%Jp?NIk|B=w z;XC1lL~uUiJ5Vt%DqOCH*$!*DS{Nq)QZ)85%=tYY!1l;jwOj>b?L%P&a+RZ}c6uW) z7sl*hGBYSw!&qxXzN-BxSnFSPz#UE19bFBxei{J(Uok_sW6b<)ooCE;g*wleBT=m5 zTXgwFtoJW23NA6=C3KF?|0m2HRss0+H3B%23jr)&1mJQh<^eUUQv7?Fz?Ux8=z^aa z`~D{#^USvZc;>$b@XWUYutJKR04_V#`bv!WUtRIza`xBn?BVy{kM!X&^ecOOW;poi z>7(}FpX~AVFMYKB`;k3}e}B@a-`Qj2{~CQJKR5Pqhd(`iemqNd>Jz&X3-NvU_fqVe zH|mS{+?%>yE&RGmw4|>cnK)$Dk&^UxUz`^I>C1Qh^Rea6ET0swTrO+Kg}RmwqMl0;`Q_3@P5I>%Gu28b@(ZM!l&R&io^D0$q2p6$FO=FiMT0&M zMuCHxrf&^MdhoEXPYpV|pHlJbU4HnaKr6#AfOgc+N35lj)q!_&5d z+5zAcS_`-funw>uumP|U&<@~{YzEvzp5hYUDp0Edym<--;KayBLEh2wQIbz(d{X1n zoWM?xPjBK{Kq4RsFbpsPFcQFH9}O4-xDGH5kPNT__}9b>`%QqG0XG7$t(l|#Zfsx{ z131Jy-$?*o1$cim9Z&!m58(aFOu#I_-2e_C0XX;^#2WyGdZ130(}t2!)&_`pw^9kH z*tfrAoC#UkQ<{K$`mS_zu&96>CAX2%%f=R9q<-T_86;~`ZCSQdZTyI{e@)e~xhrM_ zpEJptG@IRKpQvtCFf+gV-7~c_lLCvONwuLBVngj^iej9XjWesNXP@+N*#sOH-!5Ar zcTjb?5@p=Kd}STBe@O1mPfYS&T3?=oqv4)%C5|J_{UpEo=0{JQjLI{~G+TPAEe+=! zdZ%2Gd33}bP2m;svWQYDW?~sxUZLQu*Hfd=-ilbcl#W-}A4;8mC=uqg$&K zs&~f9N6F_@a9r(x^d&k0`ZanV^jj2HsmL>At+rERWvskFt19hr#(htnvunjuXE#qY z$y9^_K@yMC>y`G)dY(9Eq$OBH&)x@j4K~&^p};N zZ2ZSB)rhCsGK6@JHdiZXRa>LP85cr%ZIe3>AIY<-R@d`j>gaT}T{crhje>LNX|&L4 zt(|cZwEXkGUhmt}Qo(abSLe`0RCC!J^f(Q!orS2lYwdcZpQtU;W)jEWH}-Y@<4@)s zs=sAYBKyj)rDLR8glEiMsmDE;dgt2X{{212BJJ?8`#FBmf%Xh>l8RgkzS0FQCC<2j zDb6`LZfJZ&S0LP{W!ciw#46h1QYNT3HfX#(F7?nGz3$3By>X|LI=Qp-wx-CWl*@pk0{>|FJLp33onJN_~3cQY|PVXWRjK-)nuEAE@1}y~uh0^-D~p zN9v0&eUWstCsr;amq(GCXt_s;+T5a><_}Cr2dk!aF+ouVLrSCm46O7xl(Y=2>u_|ijhCT!1er?gL%+QtI znFx>3X(1IfD8U=}0EV)mkrp@P1|QJbC)Uz|hD2WKMJ`|LddF)E)xJ{=o|q_&9-pEM fQ?Q;RJKuSH=bn4s+-&M@Hnk+g4SOe`B{%ZhlxJJ^zx3GDKi-HOJjd(5Clq+$JyvuTJMmgr7J4!&KgOIfjj{6?LyZ(R@bd^ zIGs|XeU*!Sd}@-Ufza2x>KyU4PN}`0ZojN{nIpckT-pYn_0D>GeS_2CJPZ3^@P7u@ z+H)+g)SBot1T7RRV$Ai1+ntmtMw=HK+=nioKdZ@@zE>JcfL!AzuI-i5I6C{21UN z;3EvV378#UK`#)j=R7bs__o3S9+=;62WGoXz!7c`jfO#)VKB$w|Fy>$R&4};v>^@| zDoJ=eUn@Gk0L%>>1C9pn0LJ5KG4N{ODDZY*4m=y!%#kLuqVr(C!zNnYzb+Gt(Q!oM_+^^7VZ}3VQv$S)sh@r0da`P!vprR^rGuKouqALcN>1P=7)_ zsKdzSuZm1^q25DXs4r5FziR#hFD4=!K}Y>lL=3sis#r{2s5__!^(C?es3MwN0jjy! z4=V(=CTcRL2%;Wn-Y45YRYZ{sbtZM8Zl)g8Z;>rfHQkbFO<YYIvc?^4$wRSrYCOk+Q$WdXD% zI8A;5f&^L@oGPEwR8s;|O_qUlC^${zlPy#g&yg!sHGhmOm;_%E3HTpEJ&+Y+2I6?m7~dGBfQ$>pmZ+>LFsnig3|r{ z1R0^ro(84+I|oXCV+8UbOY^q`ly3JbDBVsVTQr%NO+CX@b2E5-(TU&`^HFd-&k{`y zO)=eyqC?SXavTz}kY0~Ym7990bD-vGvN#JiSdIEQs0AAJaW7>ZCQ0*kEvQ_LItOZw zMvX#Bv(`#w1N_{)7cg)>VzFo#>q0Ac!)&K#r;d5|9HX1xfjM{M0c@XO=J{8(fDJ?{GWdI8Zg3%hr=<$OBl#$R4wU8Gk?)Z$Zq4v}rwe;W>AP$}|L-SV`aD~-@kft- zh_t&~w#>V~bn#`wzs;IXWwhULYM7BlH8X*IDQ4?xAotsraAIu7(DVbTKf|@f6L?vC!%yCO?acfUpmnBbu zeuRv|5mH+cs2Y}6)>hPF>6IjVQ>d?H?!I<-pZ>61{)fH$&%y9_j7wh)wW+gp&Nn7% znKPN5%n1}`I-L{t|5t~a@gp`6C%p7|`tZFrk9OzIc9((UeUdkSE@c2O&$|*AJAh^V z08azBlml4CHo*XXzrw(L92yDW;sCJSWusY79!v3goZMg?mo|c`^QOq0|J$h}??IWj zppA4oFHG44>L~!X_Y7b&fOo#-bTcn+DsLk}0RE@LpDO>FN;oBJrwE*Q`OlF579|`< zB^)}X;eZ&x2*3k?2dQ;_m^%*C!vGA6_EqI?&{V*5fC}Jm$QZy_z)}FOJnqIM0Ds>3 z+cFD~35W;qCnO6n2ha@QacKpV0eEau0k0VSe`tY3#d)J-J~S0|F3ulg5{1-U5JN8( zj0`S@uy6q?l*%glu;5`DUKnXHgGH>KiogG(G&|NW*Ca|36A}|r@QY44g$k`N9FITE zKNL2_Z-phs$kW*UZp&sma2b|K2}ucw7-vidYkGnnI2e)s>n}~>333$0TD&(78*}`Z zPu{R|p-J!yN$?X+dx{h>iVmZmNPjD`is@utm@Veh#YGCX&y@?UqK5XOuBTgz75rpg z1KmO)c`2&s03&KD20c<+DO z9v>@zcKn-*S|rIF`&k;iC|i6=rHiZ<@9j}}(eIjGxwK`XNhBs%*+^=qmlj!Xi%y~N z#p`?d1B6+0=q)QV5Ryi!H%qa~<2DYZ-yZ%{kve2Oho=uGKQ zQBI$Gd0nd8S-jUoYri^cbH7km$T5x6V%kM3%I=8C7@sf89ueGgD@N97uQo<7hGMD| z&7!SW@>v-un#aAd6?=8&R1Lp3?xV-F` zA0kUiNk~eP%E(@!h(=lqEA0{o-ga9O553o2vCwDLPA^v|$=*w!pdr({ckgd}%cu8V z3mucL%=}TeDcz?zPdDM&dyVwbhN_IDUwnAfr#Dejr6M9It5UIeFQHEF85Diuq3#@? z)iiQfD#_kUtEFYXxplU+YpPH0z1Av^ykQQB3BT^sc<2;7d#}Ns^n0 { + itBundled("css/CSSEntryPoint", { + // GENERATED + files: { + "/entry.css": /* css */ ` + body { + background: white; + color: black } + `, + }, + }); + itBundled("css/CSSAtImportMissing", { + files: { + "/entry.css": `@import "./missing.css";`, + }, + bundleErrors: { + "/entry.css": ['Could not resolve "./missing.css"'], + }, + }); + itBundled("css/CSSAtImportExternal", { + // GENERATED + files: { + "/entry.css": /* css */ ` + @import "./internal.css"; + @import "./external1.css"; + @import "./external2.css"; + @import "./charset1.css"; + @import "./charset2.css"; + @import "./external5.css" screen; + `, + "/internal.css": /* css */ ` + @import "./external5.css" print; + .before { color: red } + `, + "/charset1.css": /* css */ ` + @charset "UTF-8"; + @import "./external3.css"; + @import "./external4.css"; + @import "./external5.css"; + @import "https://www.example.com/style1.css"; + @import "https://www.example.com/style2.css"; + @import "https://www.example.com/style3.css" print; + .middle { color: green } + `, + "/charset2.css": /* css */ ` + @charset "UTF-8"; + @import "./external3.css"; + @import "./external5.css" screen; + @import "https://www.example.com/style1.css"; + @import "https://www.example.com/style3.css"; + .after { color: blue } + `, + }, + }); + itBundled("css/CSSAtImport", { + // GENERATED + files: { + "/entry.css": /* css */ ` + @import "./a.css"; + @import "./b.css"; + .entry { color: red } + `, + "/a.css": /* css */ ` + @import "./shared.css"; + .a { color: green } + `, + "/b.css": /* css */ ` + @import "./shared.css"; + .b { color: blue } + `, + "/shared.css": `.shared { color: black }`, + }, + }); + itBundled("css/CSSFromJSMissingImport", { + // GENERATED + files: { + "/entry.js": /* js */ ` + import {missing} from "./a.css" + console.log(missing) + `, + "/a.css": `.a { color: red }`, + }, + /* TODO FIX expectedCompileLog: `entry.js: ERROR: No matching export in "a.css" for import "missing" + `, */ + }); + itBundled("css/CSSFromJSMissingStarImport", { + // GENERATED + files: { + "/entry.js": /* js */ ` + import * as ns from "./a.css" + console.log(ns.missing) + `, + "/a.css": `.a { color: red }`, + }, + }); + itBundled("css/ImportCSSFromJS", { + // GENERATED + files: { + "/entry.js": /* js */ ` + import "./a.js" + import "./b.js" + `, + "/a.js": /* js */ ` + import "./a.css"; + console.log('a') + `, + "/a.css": `.a { color: red }`, + "/b.js": /* js */ ` + import "./b.css"; + console.log('b') + `, + "/b.css": `.b { color: blue }`, + }, + }); + itBundled("css/ImportCSSFromJSWriteToStdout", { + // GENERATED + files: { + "/entry.js": `import "./entry.css"`, + "/entry.css": `.entry { color: red }`, + }, + /* TODO FIX expectedScanLog: `entry.js: ERROR: Cannot import "entry.css" into a JavaScript file without an output path configured + `, */ + }); + itBundled("css/ImportJSFromCSS", { + // GENERATED + files: { + "/entry.js": `export default 123`, + "/entry.css": `@import "./entry.js";`, + }, + entryPoints: ["/entry.css"], + /* TODO FIX expectedScanLog: `entry.css: ERROR: Cannot import "entry.js" into a CSS file + NOTE: An "@import" rule can only be used to import another CSS file, and "entry.js" is not a CSS file (it was loaded with the "js" loader). + `, */ + }); + itBundled("css/ImportJSONFromCSS", { + // GENERATED + files: { + "/entry.json": `{}`, + "/entry.css": `@import "./entry.json";`, + }, + entryPoints: ["/entry.css"], + /* TODO FIX expectedScanLog: `entry.css: ERROR: Cannot import "entry.json" into a CSS file + NOTE: An "@import" rule can only be used to import another CSS file, and "entry.json" is not a CSS file (it was loaded with the "json" loader). + `, */ + }); + itBundled("css/MissingImportURLInCSS", { + // GENERATED + files: { + "/src/entry.css": /* css */ ` + a { background: url(./one.png); } + b { background: url("./two.png"); } + `, + }, + /* TODO FIX expectedScanLog: `src/entry.css: ERROR: Could not resolve "./one.png" + src/entry.css: ERROR: Could not resolve "./two.png" + `, */ + }); + itBundled("css/ExternalImportURLInCSS", { + // GENERATED + files: { + "/src/entry.css": /* css */ ` + div:after { + content: 'If this is recognized, the path should become "../src/external.png"'; + background: url(./external.png); + } + + /* These URLs should be external automatically */ + a { background: url(http://example.com/images/image.png) } + b { background: url(https://example.com/images/image.png) } + c { background: url(//example.com/images/image.png) } + d { background: url(data:image/png;base64,iVBORw0KGgo=) } + path { fill: url(#filter) } + `, + }, + }); + itBundled("css/InvalidImportURLInCSS", { + // GENERATED + files: { + "/entry.css": /* css */ ` + a { + background: url(./js.js); + background: url("./jsx.jsx"); + background: url(./ts.ts); + background: url('./tsx.tsx'); + background: url(./json.json); + background: url(./css.css); + } + `, + "/js.js": `export default 123`, + "/jsx.jsx": `export default 123`, + "/ts.ts": `export default 123`, + "/tsx.tsx": `export default 123`, + "/json.json": `{ "test": true }`, + "/css.css": `a { color: red }`, + }, + /* TODO FIX expectedScanLog: `entry.css: ERROR: Cannot use "js.js" as a URL + NOTE: You can't use a "url()" token to reference the file "js.js" because it was loaded with the "js" loader, which doesn't provide a URL to embed in the resulting CSS. + entry.css: ERROR: Cannot use "jsx.jsx" as a URL + NOTE: You can't use a "url()" token to reference the file "jsx.jsx" because it was loaded with the "jsx" loader, which doesn't provide a URL to embed in the resulting CSS. + entry.css: ERROR: Cannot use "ts.ts" as a URL + NOTE: You can't use a "url()" token to reference the file "ts.ts" because it was loaded with the "ts" loader, which doesn't provide a URL to embed in the resulting CSS. + entry.css: ERROR: Cannot use "tsx.tsx" as a URL + NOTE: You can't use a "url()" token to reference the file "tsx.tsx" because it was loaded with the "tsx" loader, which doesn't provide a URL to embed in the resulting CSS. + entry.css: ERROR: Cannot use "json.json" as a URL + NOTE: You can't use a "url()" token to reference the file "json.json" because it was loaded with the "json" loader, which doesn't provide a URL to embed in the resulting CSS. + entry.css: ERROR: Cannot use "css.css" as a URL + NOTE: You can't use a "url()" token to reference a CSS file, and "css.css" is a CSS file (it was loaded with the "css" loader). + `, */ + }); + itBundled("css/TextImportURLInCSSText", { + // GENERATED + files: { + "/entry.css": /* css */ ` + a { + background: url(./example.txt); + } + `, + "/example.txt": `This is some text.`, + }, + }); + itBundled("css/DataURLImportURLInCSS", { + // GENERATED + files: { + "/entry.css": /* css */ ` + a { + background: url(./example.png); + } + `, + "/example.png": `\x89\x50\x4E\x47\x0D\x0A\x1A\x0A`, + }, + }); + itBundled("css/BinaryImportURLInCSS", { + // GENERATED + files: { + "/entry.css": /* css */ ` + a { + background: url(./example.png); + } + `, + "/example.png": `\x89\x50\x4E\x47\x0D\x0A\x1A\x0A`, + }, + }); + itBundled("css/Base64ImportURLInCSS", { + // GENERATED + files: { + "/entry.css": /* css */ ` + a { + background: url(./example.png); + } + `, + "/example.png": `\x89\x50\x4E\x47\x0D\x0A\x1A\x0A`, + }, + }); + itBundled("css/FileImportURLInCSS", { + // GENERATED + files: { + "/entry.css": /* css */ ` + @import "./one.css"; + @import "./two.css"; + `, + "/one.css": `a { background: url(./example.data) }`, + "/two.css": `b { background: url(./example.data) }`, + "/example.data": `This is some data.`, + }, + }); + itBundled("css/IgnoreURLsInAtRulePrelude", { + // GENERATED + files: { + "/entry.css": /* css */ ` + /* This should not generate a path resolution error */ + @supports (background: url(ignored.png)) { + a { color: red } + } + `, + }, + }); + itBundled("css/PackageURLsInCSS", { + // GENERATED + files: { + "/entry.css": /* css */ ` + @import "test.css"; + + a { background: url(a/1.png); } + b { background: url(b/2.png); } + c { background: url(c/3.png); } + `, + "/test.css": `.css { color: red }`, + "/a/1.png": `a-1`, + "/node_modules/b/2.png": `b-2-node_modules`, + "/c/3.png": `c-3`, + "/node_modules/c/3.png": `c-3-node_modules`, + }, + }); + itBundled("css/CSSAtImportExtensionOrderCollision", { + // GENERATED + files: { + "/entry.css": `@import "./test";`, + "/test.js": `console.log('js')`, + "/test.css": `.css { color: red }`, + }, + outfile: "/out.css", + extensionOrder: [".js", ".css"], + }); + itBundled("css/CSSAtImportExtensionOrderCollisionUnsupported", { + // GENERATED + files: { + "/entry.css": `@import "./test";`, + "/test.js": `console.log('js')`, + "/test.sass": `// some code`, + }, + outfile: "/out.css", + extensionOrder: [".js", ".sass"], + bundleErrors: { + "/entry.css": ['ERROR: No loader is configured for ".sass" files: test.sass'], + }, + }); + itBundled("css/CSSAtImportConditionsNoBundle", { + // GENERATED + files: { + "/entry.css": `@import "./print.css" print;`, + }, + mode: "passthrough", + }); + itBundled("css/CSSAtImportConditionsBundleExternal", { + // GENERATED + files: { + "/entry.css": `@import "https://example.com/print.css" print;`, + }, + }); + itBundled("css/CSSAtImportConditionsBundleExternalConditionWithURL", { + // GENERATED + files: { + "/entry.css": `@import "https://example.com/foo.css" (foo: url("foo.png")) and (bar: url("bar.png"));`, + }, + }); + itBundled("css/CSSAtImportConditionsBundle", { + // GENERATED + files: { + "/entry.css": `@import "./print.css" print;`, + "/print.css": `body { color: red }`, + }, + /* TODO FIX expectedScanLog: `entry.css: ERROR: Bundling with conditional "@import" rules is not currently supported + `, */ + }); + itBundled("css/CSSAndJavaScriptCodeSplittingIssue1064", { + // GENERATED + files: { + "/a.js": /* js */ ` + import shared from './shared.js' + console.log(shared() + 1) + `, + "/b.js": /* js */ ` + import shared from './shared.js' + console.log(shared() + 2) + `, + "/c.css": /* css */ ` + @import "./shared.css"; + body { color: red } + `, + "/d.css": /* css */ ` + @import "./shared.css"; + body { color: blue } + `, + "/shared.js": `export default function() { return 3 }`, + "/shared.css": `body { background: black }`, + }, + entryPoints: ["/a.js", "/b.js", "/c.css", "/d.css"], + format: "esm", + splitting: true, + }); + itBundled("css/CSSExternalQueryAndHashNoMatchIssue1822", { + // GENERATED + files: { + "/entry.css": /* css */ ` + a { background: url(foo/bar.png?baz) } + b { background: url(foo/bar.png#baz) } + `, + }, + outfile: "/out.css", + /* TODO FIX expectedScanLog: `entry.css: ERROR: Could not resolve "foo/bar.png?baz" + NOTE: You can mark the path "foo/bar.png?baz" as external to exclude it from the bundle, which will remove this error. + entry.css: ERROR: Could not resolve "foo/bar.png#baz" + NOTE: You can mark the path "foo/bar.png#baz" as external to exclude it from the bundle, which will remove this error. + `, */ + }); + itBundled("css/CSSExternalQueryAndHashMatchIssue1822", { + // GENERATED + files: { + "/entry.css": /* css */ ` + a { background: url(foo/bar.png?baz) } + b { background: url(foo/bar.png#baz) } + `, + }, + outfile: "/out.css", + }); + itBundled("css/CSSNestingOldBrowser", { + // GENERATED + files: { + "/nested-@layer.css": `a { @layer base { color: red; } }`, + "/nested-@media.css": `a { @media screen { color: red; } }`, + "/nested-ampersand-twice.css": `a { &, & { color: red; } }`, + "/nested-ampersand-first.css": `a { &, b { color: red; } }`, + "/nested-attribute.css": `a { [href] { color: red; } }`, + "/nested-colon.css": `a { :hover { color: red; } }`, + "/nested-dot.css": `a { .cls { color: red; } }`, + "/nested-greaterthan.css": `a { > b { color: red; } }`, + "/nested-hash.css": `a { #id { color: red; } }`, + "/nested-plus.css": `a { + b { color: red; } }`, + "/nested-tilde.css": `a { ~ b { color: red; } }`, + "/toplevel-ampersand-twice.css": `&, & { color: red; }`, + "/toplevel-ampersand-first.css": `&, a { color: red; }`, + "/toplevel-ampersand-second.css": `a, & { color: red; }`, + "/toplevel-attribute.css": `[href] { color: red; }`, + "/toplevel-colon.css": `:hover { color: red; }`, + "/toplevel-dot.css": `.cls { color: red; }`, + "/toplevel-greaterthan.css": `> b { color: red; }`, + "/toplevel-hash.css": `#id { color: red; }`, + "/toplevel-plus.css": `+ b { color: red; }`, + "/toplevel-tilde.css": `~ b { color: red; }`, + }, + entryPoints: [ + "/nested-@layer.css", + "/nested-@media.css", + "/nested-ampersand-twice.css", + "/nested-ampersand-first.css", + "/nested-attribute.css", + "/nested-colon.css", + "/nested-dot.css", + "/nested-greaterthan.css", + "/nested-hash.css", + "/nested-plus.css", + "/nested-tilde.css", + "/toplevel-ampersand-twice.css", + "/toplevel-ampersand-first.css", + "/toplevel-ampersand-second.css", + "/toplevel-attribute.css", + "/toplevel-colon.css", + "/toplevel-dot.css", + "/toplevel-greaterthan.css", + "/toplevel-hash.css", + "/toplevel-plus.css", + "/toplevel-tilde.css", + ], + unsupportedCSSFeatures: "Nesting", + /* TODO FIX expectedScanLog: `nested-@layer.css: WARNING: CSS nesting syntax is not supported in the configured target environment (chrome10) + nested-@media.css: WARNING: CSS nesting syntax is not supported in the configured target environment (chrome10) + nested-ampersand-first.css: WARNING: CSS nesting syntax is not supported in the configured target environment (chrome10) + nested-ampersand-twice.css: WARNING: CSS nesting syntax is not supported in the configured target environment (chrome10) + nested-attribute.css: WARNING: CSS nesting syntax is not supported in the configured target environment (chrome10) + nested-colon.css: WARNING: CSS nesting syntax is not supported in the configured target environment (chrome10) + nested-dot.css: WARNING: CSS nesting syntax is not supported in the configured target environment (chrome10) + nested-greaterthan.css: WARNING: CSS nesting syntax is not supported in the configured target environment (chrome10) + nested-hash.css: WARNING: CSS nesting syntax is not supported in the configured target environment (chrome10) + nested-plus.css: WARNING: CSS nesting syntax is not supported in the configured target environment (chrome10) + nested-tilde.css: WARNING: CSS nesting syntax is not supported in the configured target environment (chrome10) + toplevel-ampersand-first.css: WARNING: CSS nesting syntax is not supported in the configured target environment (chrome10) + toplevel-ampersand-second.css: WARNING: CSS nesting syntax is not supported in the configured target environment (chrome10) + toplevel-ampersand-twice.css: WARNING: CSS nesting syntax is not supported in the configured target environment (chrome10) + toplevel-ampersand-twice.css: WARNING: CSS nesting syntax is not supported in the configured target environment (chrome10) + toplevel-greaterthan.css: WARNING: CSS nesting syntax is not supported in the configured target environment (chrome10) + toplevel-plus.css: WARNING: CSS nesting syntax is not supported in the configured target environment (chrome10) + toplevel-tilde.css: WARNING: CSS nesting syntax is not supported in the configured target environment (chrome10) + `, */ + }); + itBundled("css/MetafileCSSBundleTwoToOne", { + files: { + "/foo/entry.js": /* js */ ` + import '../common.css' + console.log('foo') + `, + "/bar/entry.js": /* js */ ` + import '../common.css' + console.log('bar') + `, + "/common.css": `body { color: red }`, + }, + metafile: true, + entryPoints: ["/foo/entry.js", "/bar/entry.js"], + entryNames: "[ext]/[hash]", + outdir: "/", + }); + itBundled("css/DeduplicateRules", { + // GENERATED + files: { + "/yes0.css": `a { color: red; color: green; color: red }`, + "/yes1.css": `a { color: red } a { color: green } a { color: red }`, + "/yes2.css": `@media screen { a { color: red } } @media screen { a { color: red } }`, + "/no0.css": `@media screen { a { color: red } } @media screen { & a { color: red } }`, + "/no1.css": `@media screen { a { color: red } } @media screen { a[x] { color: red } }`, + "/no2.css": `@media screen { a { color: red } } @media screen { a.x { color: red } }`, + "/no3.css": `@media screen { a { color: red } } @media screen { a#x { color: red } }`, + "/no4.css": `@media screen { a { color: red } } @media screen { a:x { color: red } }`, + "/no5.css": `@media screen { a:x { color: red } } @media screen { a:x(y) { color: red } }`, + "/no6.css": `@media screen { a b { color: red } } @media screen { a + b { color: red } }`, + "/across-files.css": `@import 'across-files-0.css'; @import 'across-files-1.css'; @import 'across-files-2.css';`, + "/across-files-0.css": `a { color: red; color: red }`, + "/across-files-1.css": `a { color: green }`, + "/across-files-2.css": `a { color: red }`, + "/across-files-url.css": `@import 'across-files-url-0.css'; @import 'across-files-url-1.css'; @import 'across-files-url-2.css';`, + "/across-files-url-0.css": `@import 'http://example.com/some.css'; @font-face { src: url(http://example.com/some.font); }`, + "/across-files-url-1.css": `@font-face { src: url(http://example.com/some.other.font); }`, + "/across-files-url-2.css": `@font-face { src: url(http://example.com/some.font); }`, + }, + entryPoints: [ + "/yes0.css", + "/yes1.css", + "/yes2.css", + "/no0.css", + "/no1.css", + "/no2.css", + "/no3.css", + "/no4.css", + "/no5.css", + "/no6.css", + "/across-files.css", + "/across-files-url.css", + ], + }); +}); diff --git a/test/bundler/esbuild/bundler_dce.test.ts b/test/bundler/esbuild/bundler_dce.test.ts new file mode 100644 index 000000000000..1c0ae054b7f4 --- /dev/null +++ b/test/bundler/esbuild/bundler_dce.test.ts @@ -0,0 +1,2748 @@ +import { test, describe } from "bun:test"; +import { expectBundled, itBundled } from "./expectBundled"; + +// Tests ported from: +// https://github.com/evanw/esbuild/blob/main/internal/bundler_tests/bundler_dce_test.go + +// For debug, all files are written to $TEMP/bun-bundle-tests/dce + +describe("bundler", () => { + itBundled("dce/PackageJsonSideEffectsFalseKeepNamedImportES6", { + // GENERATED + files: { + "/Users/user/project/src/entry.js": /* js */ ` + import {foo} from "demo-pkg" + console.log(foo) + `, + "/Users/user/project/node_modules/demo-pkg/index.js": /* js */ ` + export const foo = 123 + console.log('hello') + `, + "/Users/user/project/node_modules/demo-pkg/package.json": /* json */ ` + { + "sideEffects": false + } + `, + }, + }); + itBundled("dce/PackageJsonSideEffectsFalseKeepNamedImportCommonJS", { + // GENERATED + files: { + "/Users/user/project/src/entry.js": /* js */ ` + import {foo} from "demo-pkg" + console.log(foo) + `, + "/Users/user/project/node_modules/demo-pkg/index.js": /* js */ ` + exports.foo = 123 + console.log('hello') + `, + "/Users/user/project/node_modules/demo-pkg/package.json": /* json */ ` + { + "sideEffects": false + } + `, + }, + }); + itBundled("dce/PackageJsonSideEffectsFalseKeepStarImportES6", { + // GENERATED + files: { + "/Users/user/project/src/entry.js": /* js */ ` + import * as ns from "demo-pkg" + console.log(ns) + `, + "/Users/user/project/node_modules/demo-pkg/index.js": /* js */ ` + export const foo = 123 + console.log('hello') + `, + "/Users/user/project/node_modules/demo-pkg/package.json": /* json */ ` + { + "sideEffects": false + } + `, + }, + }); + itBundled("dce/PackageJsonSideEffectsFalseKeepStarImportCommonJS", { + // GENERATED + files: { + "/Users/user/project/src/entry.js": /* js */ ` + import * as ns from "demo-pkg" + console.log(ns) + `, + "/Users/user/project/node_modules/demo-pkg/index.js": /* js */ ` + exports.foo = 123 + console.log('hello') + `, + "/Users/user/project/node_modules/demo-pkg/package.json": /* json */ ` + { + "sideEffects": false + } + `, + }, + }); + itBundled("dce/PackageJsonSideEffectsTrueKeepES6", { + // GENERATED + files: { + "/Users/user/project/src/entry.js": /* js */ ` + import "demo-pkg" + console.log('unused import') + `, + "/Users/user/project/node_modules/demo-pkg/index.js": /* js */ ` + export const foo = 123 + console.log('hello') + `, + "/Users/user/project/node_modules/demo-pkg/package.json": /* json */ ` + { + "sideEffects": true + } + `, + }, + }); + itBundled("dce/PackageJsonSideEffectsTrueKeepCommonJS", { + // GENERATED + files: { + "/Users/user/project/src/entry.js": /* js */ ` + import "demo-pkg" + console.log('unused import') + `, + "/Users/user/project/node_modules/demo-pkg/index.js": /* js */ ` + exports.foo = 123 + console.log('hello') + `, + "/Users/user/project/node_modules/demo-pkg/package.json": /* json */ ` + { + "sideEffects": true + } + `, + }, + }); + itBundled("dce/PackageJsonSideEffectsFalseKeepBareImportAndRequireES6", { + // GENERATED + files: { + "/Users/user/project/src/entry.js": /* js */ ` + import "demo-pkg" + require('demo-pkg') + console.log('unused import') + `, + "/Users/user/project/node_modules/demo-pkg/index.js": /* js */ ` + export const foo = 123 + console.log('hello') + `, + "/Users/user/project/node_modules/demo-pkg/package.json": /* json */ ` + { + "sideEffects": false + } + `, + }, + /* TODO FIX expectedScanLog: `Users/user/project/src/entry.js: WARNING: Ignoring this import because "Users/user/project/node_modules/demo-pkg/index.js" was marked as having no side effects + Users/user/project/node_modules/demo-pkg/package.json: NOTE: "sideEffects" is false in the enclosing "package.json" file: + `, */ + }); + itBundled("dce/PackageJsonSideEffectsFalseKeepBareImportAndRequireCommonJS", { + // GENERATED + files: { + "/Users/user/project/src/entry.js": /* js */ ` + import "demo-pkg" + require('demo-pkg') + console.log('unused import') + `, + "/Users/user/project/node_modules/demo-pkg/index.js": /* js */ ` + exports.foo = 123 + console.log('hello') + `, + "/Users/user/project/node_modules/demo-pkg/package.json": /* json */ ` + { + "sideEffects": false + } + `, + }, + /* TODO FIX expectedScanLog: `Users/user/project/src/entry.js: WARNING: Ignoring this import because "Users/user/project/node_modules/demo-pkg/index.js" was marked as having no side effects + Users/user/project/node_modules/demo-pkg/package.json: NOTE: "sideEffects" is false in the enclosing "package.json" file: + `, */ + }); + itBundled("dce/PackageJsonSideEffectsFalseRemoveBareImportES6", { + // GENERATED + files: { + "/Users/user/project/src/entry.js": /* js */ ` + import "demo-pkg" + console.log('unused import') + `, + "/Users/user/project/node_modules/demo-pkg/index.js": /* js */ ` + export const foo = 123 + console.log('hello') + `, + "/Users/user/project/node_modules/demo-pkg/package.json": /* json */ ` + { + "sideEffects": false + } + `, + }, + /* TODO FIX expectedScanLog: `Users/user/project/src/entry.js: WARNING: Ignoring this import because "Users/user/project/node_modules/demo-pkg/index.js" was marked as having no side effects + Users/user/project/node_modules/demo-pkg/package.json: NOTE: "sideEffects" is false in the enclosing "package.json" file: + `, */ + }); + itBundled("dce/PackageJsonSideEffectsFalseRemoveBareImportCommonJS", { + // GENERATED + files: { + "/Users/user/project/src/entry.js": /* js */ ` + import "demo-pkg" + console.log('unused import') + `, + "/Users/user/project/node_modules/demo-pkg/index.js": /* js */ ` + exports.foo = 123 + console.log('hello') + `, + "/Users/user/project/node_modules/demo-pkg/package.json": /* json */ ` + { + "sideEffects": false + } + `, + }, + /* TODO FIX expectedScanLog: `Users/user/project/src/entry.js: WARNING: Ignoring this import because "Users/user/project/node_modules/demo-pkg/index.js" was marked as having no side effects + Users/user/project/node_modules/demo-pkg/package.json: NOTE: "sideEffects" is false in the enclosing "package.json" file: + `, */ + }); + itBundled("dce/PackageJsonSideEffectsFalseRemoveNamedImportES6", { + // GENERATED + files: { + "/Users/user/project/src/entry.js": /* js */ ` + import {foo} from "demo-pkg" + console.log('unused import') + `, + "/Users/user/project/node_modules/demo-pkg/index.js": /* js */ ` + export const foo = 123 + console.log('hello') + `, + "/Users/user/project/node_modules/demo-pkg/package.json": /* json */ ` + { + "sideEffects": false + } + `, + }, + }); + itBundled("dce/PackageJsonSideEffectsFalseRemoveNamedImportCommonJS", { + // GENERATED + files: { + "/Users/user/project/src/entry.js": /* js */ ` + import {foo} from "demo-pkg" + console.log('unused import') + `, + "/Users/user/project/node_modules/demo-pkg/index.js": /* js */ ` + exports.foo = 123 + console.log('hello') + `, + "/Users/user/project/node_modules/demo-pkg/package.json": /* json */ ` + { + "sideEffects": false + } + `, + }, + }); + itBundled("dce/PackageJsonSideEffectsFalseRemoveStarImportES6", { + // GENERATED + files: { + "/Users/user/project/src/entry.js": /* js */ ` + import * as ns from "demo-pkg" + console.log('unused import') + `, + "/Users/user/project/node_modules/demo-pkg/index.js": /* js */ ` + export const foo = 123 + console.log('hello') + `, + "/Users/user/project/node_modules/demo-pkg/package.json": /* json */ ` + { + "sideEffects": false + } + `, + }, + }); + itBundled("dce/PackageJsonSideEffectsFalseRemoveStarImportCommonJS", { + // GENERATED + files: { + "/Users/user/project/src/entry.js": /* js */ ` + import * as ns from "demo-pkg" + console.log('unused import') + `, + "/Users/user/project/node_modules/demo-pkg/index.js": /* js */ ` + exports.foo = 123 + console.log('hello') + `, + "/Users/user/project/node_modules/demo-pkg/package.json": /* json */ ` + { + "sideEffects": false + } + `, + }, + }); + itBundled("dce/PackageJsonSideEffectsArrayRemove", { + // GENERATED + files: { + "/Users/user/project/src/entry.js": /* js */ ` + import {foo} from "demo-pkg" + console.log('unused import') + `, + "/Users/user/project/node_modules/demo-pkg/index.js": /* js */ ` + export const foo = 123 + console.log('hello') + `, + "/Users/user/project/node_modules/demo-pkg/package.json": /* json */ ` + { + "sideEffects": [] + } + `, + }, + }); + itBundled("dce/PackageJsonSideEffectsArrayKeep", { + // GENERATED + files: { + "/Users/user/project/src/entry.js": /* js */ ` + import {foo} from "demo-pkg" + console.log('unused import') + `, + "/Users/user/project/node_modules/demo-pkg/index.js": /* js */ ` + export const foo = 123 + console.log('hello') + `, + "/Users/user/project/node_modules/demo-pkg/package.json": /* json */ ` + { + "sideEffects": ["./index.js"] + } + `, + }, + }); + itBundled("dce/PackageJsonSideEffectsArrayKeepMainUseModule", { + // GENERATED + files: { + "/Users/user/project/src/entry.js": /* js */ ` + import {foo} from "demo-pkg" + console.log('unused import') + `, + "/Users/user/project/node_modules/demo-pkg/index-main.js": /* js */ ` + export const foo = 123 + console.log('TEST FAILED') + `, + "/Users/user/project/node_modules/demo-pkg/index-module.js": /* js */ ` + export const foo = 123 + console.log('TEST FAILED') + `, + "/Users/user/project/node_modules/demo-pkg/package.json": /* json */ ` + { + "main": "index-main.js", + "module": "index-module.js", + "sideEffects": ["./index-main.js"] + } + `, + }, + }); + itBundled("dce/PackageJsonSideEffectsArrayKeepMainUseMain", { + // GENERATED + files: { + "/Users/user/project/src/entry.js": /* js */ ` + import {foo} from "demo-pkg" + console.log('unused import') + `, + "/Users/user/project/node_modules/demo-pkg/index-main.js": /* js */ ` + export const foo = 123 + console.log('this should be kept') + `, + "/Users/user/project/node_modules/demo-pkg/index-module.js": /* js */ ` + export const foo = 123 + console.log('TEST FAILED') + `, + "/Users/user/project/node_modules/demo-pkg/package.json": /* json */ ` + { + "main": "index-main.js", + "module": "index-module.js", + "sideEffects": ["./index-main.js"] + } + `, + }, + }); + itBundled("dce/PackageJsonSideEffectsArrayKeepMainImplicitModule", { + // GENERATED + files: { + "/Users/user/project/src/entry.js": /* js */ ` + import {foo} from "demo-pkg" + console.log('unused import') + `, + "/Users/user/project/node_modules/demo-pkg/index-main.js": /* js */ ` + export const foo = 123 + console.log('TEST FAILED') + `, + "/Users/user/project/node_modules/demo-pkg/index-module.js": /* js */ ` + export const foo = 123 + console.log('TEST FAILED') + `, + "/Users/user/project/node_modules/demo-pkg/package.json": /* json */ ` + { + "main": "index-main.js", + "module": "index-module.js", + "sideEffects": ["./index-main.js"] + } + `, + }, + }); + itBundled("dce/PackageJsonSideEffectsArrayKeepMainImplicitMain", { + // GENERATED + files: { + "/Users/user/project/src/entry.js": /* js */ ` + import {foo} from "demo-pkg" + import "./require-demo-pkg" + console.log('unused import') + `, + "/Users/user/project/src/require-demo-pkg.js": /* js */ ` + // This causes "index-main.js" to be selected + require('demo-pkg') + `, + "/Users/user/project/node_modules/demo-pkg/index-main.js": /* js */ ` + export const foo = 123 + console.log('this should be kept') + `, + "/Users/user/project/node_modules/demo-pkg/index-module.js": /* js */ ` + export const foo = 123 + console.log('TEST FAILED') + `, + "/Users/user/project/node_modules/demo-pkg/package.json": /* json */ ` + { + "main": "index-main.js", + "module": "index-module.js", + "sideEffects": ["./index-main.js"] + } + `, + }, + }); + itBundled("dce/PackageJsonSideEffectsArrayKeepModuleUseModule", { + // GENERATED + files: { + "/Users/user/project/src/entry.js": /* js */ ` + import {foo} from "demo-pkg" + console.log('unused import') + `, + "/Users/user/project/node_modules/demo-pkg/index-main.js": /* js */ ` + export const foo = 123 + console.log('TEST FAILED') + `, + "/Users/user/project/node_modules/demo-pkg/index-module.js": /* js */ ` + export const foo = 123 + console.log('this should be kept') + `, + "/Users/user/project/node_modules/demo-pkg/package.json": /* json */ ` + { + "main": "index-main.js", + "module": "index-module.js", + "sideEffects": ["./index-module.js"] + } + `, + }, + }); + itBundled("dce/PackageJsonSideEffectsArrayKeepModuleUseMain", { + // GENERATED + files: { + "/Users/user/project/src/entry.js": /* js */ ` + import {foo} from "demo-pkg" + console.log('unused import') + `, + "/Users/user/project/node_modules/demo-pkg/index-main.js": /* js */ ` + export const foo = 123 + console.log('TEST FAILED') + `, + "/Users/user/project/node_modules/demo-pkg/index-module.js": /* js */ ` + export const foo = 123 + console.log('TEST FAILED') + `, + "/Users/user/project/node_modules/demo-pkg/package.json": /* json */ ` + { + "main": "index-main.js", + "module": "index-module.js", + "sideEffects": ["./index-module.js"] + } + `, + }, + }); + itBundled("dce/PackageJsonSideEffectsArrayKeepModuleImplicitModule", { + // GENERATED + files: { + "/Users/user/project/src/entry.js": /* js */ ` + import {foo} from "demo-pkg" + console.log('unused import') + `, + "/Users/user/project/node_modules/demo-pkg/index-main.js": /* js */ ` + export const foo = 123 + console.log('TEST FAILED') + `, + "/Users/user/project/node_modules/demo-pkg/index-module.js": /* js */ ` + export const foo = 123 + console.log('this should be kept') + `, + "/Users/user/project/node_modules/demo-pkg/package.json": /* json */ ` + { + "main": "index-main.js", + "module": "index-module.js", + "sideEffects": ["./index-module.js"] + } + `, + }, + }); + itBundled("dce/PackageJsonSideEffectsArrayKeepModuleImplicitMain", { + // GENERATED + files: { + "/Users/user/project/src/entry.js": /* js */ ` + import {foo} from "demo-pkg" + import "./require-demo-pkg" + console.log('unused import') + `, + "/Users/user/project/src/require-demo-pkg.js": /* js */ ` + // This causes "index-main.js" to be selected + require('demo-pkg') + `, + "/Users/user/project/node_modules/demo-pkg/index-main.js": /* js */ ` + export const foo = 123 + console.log('this should be kept') + `, + "/Users/user/project/node_modules/demo-pkg/index-module.js": /* js */ ` + export const foo = 123 + console.log('TEST FAILED') + `, + "/Users/user/project/node_modules/demo-pkg/package.json": /* json */ ` + { + "main": "index-main.js", + "module": "index-module.js", + "sideEffects": ["./index-module.js"] + } + `, + }, + }); + itBundled("dce/PackageJsonSideEffectsArrayGlob", { + // GENERATED + files: { + "/Users/user/project/src/entry.js": /* js */ ` + import "demo-pkg/keep/this/file" + import "demo-pkg/remove/this/file" + `, + "/Users/user/project/node_modules/demo-pkg/keep/this/file.js": `console.log('this should be kept')`, + "/Users/user/project/node_modules/demo-pkg/remove/this/file.js": `console.log('TEST FAILED')`, + "/Users/user/project/node_modules/demo-pkg/package.json": /* json */ ` + { + "sideEffects": [ + "./ke?p/*/file.js", + "./remove/this/file.j", + "./re?ve/this/file.js" + ] + } + `, + }, + /* TODO FIX expectedScanLog: `Users/user/project/src/entry.js: WARNING: Ignoring this import because "Users/user/project/node_modules/demo-pkg/remove/this/file.js" was marked as having no side effects + Users/user/project/node_modules/demo-pkg/package.json: NOTE: It was excluded from the "sideEffects" array in the enclosing "package.json" file: + `, */ + }); + itBundled("dce/PackageJsonSideEffectsNestedDirectoryRemove", { + // GENERATED + files: { + "/Users/user/project/src/entry.js": /* js */ ` + import {foo} from "demo-pkg/a/b/c" + console.log('unused import') + `, + "/Users/user/project/node_modules/demo-pkg/package.json": /* json */ ` + { + "sideEffects": false + } + `, + "/Users/user/project/node_modules/demo-pkg/a/b/c/index.js": /* js */ ` + export const foo = 123 + console.log('hello') + `, + }, + }); + itBundled("dce/PackageJsonSideEffectsKeepExportDefaultExpr", { + // GENERATED + files: { + "/Users/user/project/src/entry.js": /* js */ ` + import foo from "demo-pkg" + console.log(foo) + `, + "/Users/user/project/node_modules/demo-pkg/index.js": `export default exprWithSideEffects()`, + "/Users/user/project/node_modules/demo-pkg/package.json": /* json */ ` + { + "sideEffects": false + } + `, + }, + }); + itBundled("dce/PackageJsonSideEffectsFalseNoWarningInNodeModulesIssue999", { + // GENERATED + files: { + "/Users/user/project/src/entry.js": /* js */ ` + import "demo-pkg" + console.log('used import') + `, + "/Users/user/project/node_modules/demo-pkg/index.js": /* js */ ` + import "demo-pkg2" + console.log('unused import') + `, + "/Users/user/project/node_modules/demo-pkg2/index.js": /* js */ ` + export const foo = 123 + console.log('hello') + `, + "/Users/user/project/node_modules/demo-pkg2/package.json": /* json */ ` + { + "sideEffects": false + } + `, + }, + }); + itBundled("dce/PackageJsonSideEffectsFalseIntermediateFilesUnused", { + // GENERATED + files: { + "/Users/user/project/src/entry.js": `import {foo} from "demo-pkg"`, + "/Users/user/project/node_modules/demo-pkg/index.js": /* js */ ` + export {foo} from "./foo.js" + throw 'REMOVE THIS' + `, + "/Users/user/project/node_modules/demo-pkg/foo.js": `export const foo = 123`, + "/Users/user/project/node_modules/demo-pkg/package.json": `{ "sideEffects": false }`, + }, + }); + itBundled("dce/PackageJsonSideEffectsFalseIntermediateFilesUsed", { + // GENERATED + files: { + "/Users/user/project/src/entry.js": /* js */ ` + import {foo} from "demo-pkg" + console.log(foo) + `, + "/Users/user/project/node_modules/demo-pkg/index.js": /* js */ ` + export {foo} from "./foo.js" + throw 'keep this' + `, + "/Users/user/project/node_modules/demo-pkg/foo.js": `export const foo = 123`, + "/Users/user/project/node_modules/demo-pkg/package.json": `{ "sideEffects": false }`, + }, + }); + itBundled("dce/PackageJsonSideEffectsFalseIntermediateFilesChainAll", { + // GENERATED + files: { + "/Users/user/project/src/entry.js": /* js */ ` + import {foo} from "a" + console.log(foo) + `, + "/Users/user/project/node_modules/a/index.js": `export {foo} from "b"`, + "/Users/user/project/node_modules/a/package.json": `{ "sideEffects": false }`, + "/Users/user/project/node_modules/b/index.js": /* js */ ` + export {foo} from "c" + throw 'keep this' + `, + "/Users/user/project/node_modules/b/package.json": `{ "sideEffects": false }`, + "/Users/user/project/node_modules/c/index.js": `export {foo} from "d"`, + "/Users/user/project/node_modules/c/package.json": `{ "sideEffects": false }`, + "/Users/user/project/node_modules/d/index.js": `export const foo = 123`, + "/Users/user/project/node_modules/d/package.json": `{ "sideEffects": false }`, + }, + }); + itBundled("dce/PackageJsonSideEffectsFalseIntermediateFilesChainOne", { + // GENERATED + files: { + "/Users/user/project/src/entry.js": /* js */ ` + import {foo} from "a" + console.log(foo) + `, + "/Users/user/project/node_modules/a/index.js": `export {foo} from "b"`, + "/Users/user/project/node_modules/b/index.js": /* js */ ` + export {foo} from "c" + throw 'keep this' + `, + "/Users/user/project/node_modules/b/package.json": `{ "sideEffects": false }`, + "/Users/user/project/node_modules/c/index.js": `export {foo} from "d"`, + "/Users/user/project/node_modules/d/index.js": `export const foo = 123`, + }, + }); + itBundled("dce/PackageJsonSideEffectsFalseIntermediateFilesDiamond", { + // GENERATED + files: { + "/Users/user/project/src/entry.js": /* js */ ` + import {foo} from "a" + console.log(foo) + `, + "/Users/user/project/node_modules/a/index.js": /* js */ ` + export * from "b1" + export * from "b2" + `, + "/Users/user/project/node_modules/b1/index.js": /* js */ ` + export {foo} from "c" + throw 'keep this 1' + `, + "/Users/user/project/node_modules/b1/package.json": `{ "sideEffects": false }`, + "/Users/user/project/node_modules/b2/index.js": /* js */ ` + export {foo} from "c" + throw 'keep this 2' + `, + "/Users/user/project/node_modules/b2/package.json": `{ "sideEffects": false }`, + "/Users/user/project/node_modules/c/index.js": `export {foo} from "d"`, + "/Users/user/project/node_modules/d/index.js": `export const foo = 123`, + }, + }); + itBundled("dce/PackageJsonSideEffectsFalseOneFork", { + // GENERATED + files: { + "/Users/user/project/src/entry.js": `import("a").then(x => assert(x.foo === "foo"))`, + "/Users/user/project/node_modules/a/index.js": `export {foo} from "b"`, + "/Users/user/project/node_modules/b/index.js": /* js */ ` + export {foo, bar} from "c" + export {baz} from "d" + `, + "/Users/user/project/node_modules/b/package.json": `{ "sideEffects": false }`, + "/Users/user/project/node_modules/c/index.js": /* js */ ` + export let foo = "foo" + export let bar = "bar" + `, + "/Users/user/project/node_modules/d/index.js": `export let baz = "baz"`, + }, + }); + itBundled("dce/PackageJsonSideEffectsFalseAllFork", { + // GENERATED + files: { + "/Users/user/project/src/entry.js": `import("a").then(x => assert(x.foo === "foo"))`, + "/Users/user/project/node_modules/a/index.js": `export {foo} from "b"`, + "/Users/user/project/node_modules/b/index.js": /* js */ ` + export {foo, bar} from "c" + export {baz} from "d" + `, + "/Users/user/project/node_modules/b/package.json": `{ "sideEffects": false }`, + "/Users/user/project/node_modules/c/index.js": /* js */ ` + export let foo = "foo" + export let bar = "bar" + `, + "/Users/user/project/node_modules/c/package.json": `{ "sideEffects": false }`, + "/Users/user/project/node_modules/d/index.js": `export let baz = "baz"`, + "/Users/user/project/node_modules/d/package.json": `{ "sideEffects": false }`, + }, + }); + itBundled("dce/JSONLoaderRemoveUnused", { + // GENERATED + files: { + "/entry.js": /* js */ ` + import unused from "./example.json" + console.log('unused import') + `, + "/example.json": `{"data": true}`, + }, + }); + itBundled("dce/TextLoaderRemoveUnused", { + // GENERATED + files: { + "/entry.js": /* js */ ` + import unused from "./example.txt" + console.log('unused import') + `, + "/example.txt": `some data`, + }, + }); + itBundled("dce/Base64LoaderRemoveUnused", { + // GENERATED + files: { + "/entry.js": /* js */ ` + import unused from "./example.data" + console.log('unused import') + `, + "/example.data": `some data`, + }, + }); + itBundled("dce/DataURLLoaderRemoveUnused", { + // GENERATED + files: { + "/entry.js": /* js */ ` + import unused from "./example.data" + console.log('unused import') + `, + "/example.data": `some data`, + }, + }); + itBundled("dce/FileLoaderRemoveUnused", { + // GENERATED + files: { + "/entry.js": /* js */ ` + import unused from "./example.data" + console.log('unused import') + `, + "/example.data": `some data`, + }, + }); + itBundled("dce/RemoveUnusedImportMeta", { + // GENERATED + files: { + "/entry.js": /* js */ ` + function foo() { + console.log(import.meta.url, import.meta.path) + } + console.log('foo is unused') + `, + }, + }); + itBundled("dce/RemoveUnusedPureCommentCalls", { + // GENERATED + files: { + "/entry.js": /* js */ ` + function bar() {} + let bare = foo(bar); + + let at_yes = /* @__PURE__ */ foo(bar); + let at_no = /* @__PURE__ */ foo(bar()); + let new_at_yes = /* @__PURE__ */ new foo(bar); + let new_at_no = /* @__PURE__ */ new foo(bar()); + + let nospace_at_yes = /*@__PURE__*/ foo(bar); + let nospace_at_no = /*@__PURE__*/ foo(bar()); + let nospace_new_at_yes = /*@__PURE__*/ new foo(bar); + let nospace_new_at_no = /*@__PURE__*/ new foo(bar()); + + let num_yes = /* #__PURE__ */ foo(bar); + let num_no = /* #__PURE__ */ foo(bar()); + let new_num_yes = /* #__PURE__ */ new foo(bar); + let new_num_no = /* #__PURE__ */ new foo(bar()); + + let nospace_num_yes = /*#__PURE__*/ foo(bar); + let nospace_num_no = /*#__PURE__*/ foo(bar()); + let nospace_new_num_yes = /*#__PURE__*/ new foo(bar); + let nospace_new_num_no = /*#__PURE__*/ new foo(bar()); + + let dot_yes = /* @__PURE__ */ foo(sideEffect()).dot(bar); + let dot_no = /* @__PURE__ */ foo(sideEffect()).dot(bar()); + let new_dot_yes = /* @__PURE__ */ new foo(sideEffect()).dot(bar); + let new_dot_no = /* @__PURE__ */ new foo(sideEffect()).dot(bar()); + + let nested_yes = [1, /* @__PURE__ */ foo(bar), 2]; + let nested_no = [1, /* @__PURE__ */ foo(bar()), 2]; + let new_nested_yes = [1, /* @__PURE__ */ new foo(bar), 2]; + let new_nested_no = [1, /* @__PURE__ */ new foo(bar()), 2]; + + let single_at_yes = // @__PURE__ + foo(bar); + let single_at_no = // @__PURE__ + foo(bar()); + let new_single_at_yes = // @__PURE__ + new foo(bar); + let new_single_at_no = // @__PURE__ + new foo(bar()); + + let single_num_yes = // #__PURE__ + foo(bar); + let single_num_no = // #__PURE__ + foo(bar()); + let new_single_num_yes = // #__PURE__ + new foo(bar); + let new_single_num_no = // #__PURE__ + new foo(bar()); + + let bad_no = /* __PURE__ */ foo(bar); + let new_bad_no = /* __PURE__ */ new foo(bar); + + let parens_no = (/* @__PURE__ */ foo)(bar); + let new_parens_no = new (/* @__PURE__ */ foo)(bar); + + let exp_no = /* @__PURE__ */ foo() ** foo(); + let new_exp_no = /* @__PURE__ */ new foo() ** foo(); + `, + }, + }); + itBundled("dce/TreeShakingReactElements", { + // GENERATED + files: { + "/entry.jsx": /* jsx */ ` + function Foo() {} + + let a =
+ let b = {a} + let c = <>{b} + + let d =
+ let e = {d} + let f = <>{e} + console.log(f) + `, + }, + }); + itBundled("dce/DisableTreeShaking", { + // GENERATED + files: { + "/entry.jsx": /* jsx */ ` + import './remove-me' + function RemoveMe1() {} + let removeMe2 = 0 + class RemoveMe3 {} + + import './keep-me' + function KeepMe1() {} + let keepMe2 = + function keepMe3() { console.log('side effects') } + let keepMe4 = /* @__PURE__ */ keepMe3() + let keepMe5 = pure() + let keepMe6 = some.fn() + `, + "/remove-me.js": `export default 'unused'`, + "/keep-me/index.js": `console.log('side effects')`, + "/keep-me/package.json": `{ "sideEffects": false }`, + }, + // TODO: Unsure how to port this: https://github.com/evanw/esbuild/blob/main/internal/bundler_tests/bundler_dce_test.go#L1249 + ignoreDCEAnnotations: true, + define: { + pure: "???", + "some.fn": "???", + }, + }); + itBundled("dce/DeadCodeFollowingJump", { + // GENERATED + files: { + "/entry.js": /* js */ ` + function testReturn() { + if (true) return y + z() + if (FAIL) return FAIL + if (x) { var y } + function z() { KEEP_ME() } + return FAIL + } + + function testThrow() { + if (true) throw y + z() + if (FAIL) return FAIL + if (x) { var y } + function z() { KEEP_ME() } + return FAIL + } + + function testBreak() { + while (true) { + if (true) { + y + z() + break + } + if (FAIL) return FAIL + if (x) { var y } + function z() { KEEP_ME() } + return FAIL + } + } + + function testContinue() { + while (true) { + if (true) { + y + z() + continue + } + if (FAIL) return FAIL + if (x) { var y } + function z() { KEEP_ME() } + return FAIL + } + } + + function testStmts() { + return [a, b, c, d, e, f, g, h, i] + + while (x) { var a } + while (FAIL) { let FAIL } + + do { var b } while (x) + do { let FAIL } while (FAIL) + + for (var c; ;) ; + for (let FAIL; ;) ; + + for (var d in x) ; + for (let FAIL in FAIL) ; + + for (var e of x) ; + for (let FAIL of FAIL) ; + + if (x) { var f } + if (FAIL) { let FAIL } + + if (x) ; else { var g } + if (FAIL) ; else { let FAIL } + + { var h } + { let FAIL } + + x: { var i } + x: { let FAIL } + } + + testReturn() + testThrow() + testBreak() + testContinue() + testStmts() + `, + }, + }); + itBundled("dce/RemoveTrailingReturn", { + // GENERATED + files: { + "/entry.js": /* js */ ` + function foo() { + if (a) b() + return + } + function bar() { + if (a) b() + return KEEP_ME + } + export default [ + foo, + bar, + function () { + if (a) b() + return + }, + function () { + if (a) b() + return KEEP_ME + }, + () => { + if (a) b() + return + }, + () => { + if (a) b() + return KEEP_ME + }, + ] + `, + }, + minifySyntax: true, + }); + itBundled("dce/ImportReExportOfNamespaceImport", { + // GENERATED + files: { + "/Users/user/project/entry.js": /* js */ ` + import * as ns from 'pkg' + console.log(ns.foo) + `, + "/Users/user/project/node_modules/pkg/index.js": /* js */ ` + export { default as foo } from './foo' + export { default as bar } from './bar' + `, + "/Users/user/project/node_modules/pkg/package.json": `{ "sideEffects": false }`, + "/Users/user/project/node_modules/pkg/foo.js": `module.exports = 123`, + "/Users/user/project/node_modules/pkg/bar.js": `module.exports = 'abc'`, + }, + }); + itBundled("dce/TreeShakingImportIdentifier", { + // GENERATED + files: { + "/entry.js": /* js */ ` + import * as a from './a' + new a.Keep() + `, + "/a.js": /* js */ ` + import * as b from './b' + export class Keep extends b.Base {} + export class REMOVE extends b.Base {} + `, + "/b.js": `export class Base {}`, + }, + }); + itBundled("dce/TreeShakingObjectProperty", { + // GENERATED + files: { + "/entry.js": /* js */ ` + let remove1 = { x: 'x' } + let remove2 = { x() {} } + let remove3 = { get x() {} } + let remove4 = { set x(_) {} } + let remove5 = { async x() {} } + let remove6 = { ['x']: 'x' } + let remove7 = { ['x']() {} } + let remove8 = { get ['x']() {} } + let remove9 = { set ['x'](_) {} } + let remove10 = { async ['x']() {} } + let remove11 = { [0]: 'x' } + let remove12 = { [null]: 'x' } + let remove13 = { [undefined]: 'x' } + let remove14 = { [false]: 'x' } + let remove15 = { [0n]: 'x' } + let remove16 = { toString() {} } + + let keep1 = { x } + let keep2 = { x: x } + let keep3 = { ...x } + let keep4 = { [x]: 'x' } + let keep5 = { [x]() {} } + let keep6 = { get [x]() {} } + let keep7 = { set [x](_) {} } + let keep8 = { async [x]() {} } + let keep9 = { [{ toString() {} }]: 'x' } + `, + }, + treeShaking: true, + mode: "passthrough", + }); + itBundled("dce/TreeShakingClassProperty", { + // GENERATED + files: { + "/entry.js": /* js */ ` + let remove1 = class { x } + let remove2 = class { x = x } + let remove3 = class { x() {} } + let remove4 = class { get x() {} } + let remove5 = class { set x(_) {} } + let remove6 = class { async x() {} } + let remove7 = class { ['x'] = x } + let remove8 = class { ['x']() {} } + let remove9 = class { get ['x']() {} } + let remove10 = class { set ['x'](_) {} } + let remove11 = class { async ['x']() {} } + let remove12 = class { [0] = 'x' } + let remove13 = class { [null] = 'x' } + let remove14 = class { [undefined] = 'x' } + let remove15 = class { [false] = 'x' } + let remove16 = class { [0n] = 'x' } + let remove17 = class { toString() {} } + + let keep1 = class { [x] = 'x' } + let keep2 = class { [x]() {} } + let keep3 = class { get [x]() {} } + let keep4 = class { set [x](_) {} } + let keep5 = class { async [x]() {} } + let keep6 = class { [{ toString() {} }] = 'x' } + `, + }, + treeShaking: true, + mode: "passthrough", + }); + itBundled("dce/TreeShakingClassStaticProperty", { + // GENERATED + files: { + "/entry.js": /* js */ ` + let remove1 = class { static x } + let remove3 = class { static x() {} } + let remove4 = class { static get x() {} } + let remove5 = class { static set x(_) {} } + let remove6 = class { static async x() {} } + let remove8 = class { static ['x']() {} } + let remove9 = class { static get ['x']() {} } + let remove10 = class { static set ['x'](_) {} } + let remove11 = class { static async ['x']() {} } + let remove12 = class { static [0] = 'x' } + let remove13 = class { static [null] = 'x' } + let remove14 = class { static [undefined] = 'x' } + let remove15 = class { static [false] = 'x' } + let remove16 = class { static [0n] = 'x' } + let remove17 = class { static toString() {} } + + let keep1 = class { static x = x } + let keep2 = class { static ['x'] = x } + let keep3 = class { static [x] = 'x' } + let keep4 = class { static [x]() {} } + let keep5 = class { static get [x]() {} } + let keep6 = class { static set [x](_) {} } + let keep7 = class { static async [x]() {} } + let keep8 = class { static [{ toString() {} }] = 'x' } + `, + }, + treeShaking: true, + mode: "passthrough", + }); + itBundled("dce/TreeShakingUnaryOperators", { + // GENERATED + files: { + "/entry.js": /* js */ ` + // These operators may have side effects + let keep; + +keep; + -keep; + ~keep; + delete keep; + ++keep; + --keep; + keep++; + keep--; + + // These operators never have side effects + let REMOVE; + !REMOVE; + void REMOVE; + `, + }, + }); + itBundled("dce/TreeShakingBinaryOperators", { + // GENERATED + files: { + "/entry.js": /* js */ ` + // These operators may have side effects + let keep, keep2; + keep + keep2; + keep - keep2; + keep * keep2; + keep / keep2; + keep % keep2; + keep ** keep2; + keep < keep2; + keep <= keep2; + keep > keep2; + keep >= keep2; + keep in keep2; + keep instanceof keep2; + keep << keep2; + keep >> keep2; + keep >>> keep2; + keep == keep2; + keep != keep2; + keep | keep2; + keep & keep2; + keep ^ keep2; + keep = keep2; + keep += keep2; + keep -= keep2; + keep *= keep2; + keep /= keep2; + keep %= keep2; + keep **= keep2; + keep <<= keep2; + keep >>= keep2; + keep >>>= keep2; + keep |= keep2; + keep &= keep2; + keep ^= keep2; + keep ??= keep2; + keep ||= keep2; + keep &&= keep2; + + // These operators never have side effects + let REMOVE, REMOVE2; + REMOVE === REMOVE2; + REMOVE !== REMOVE2; + REMOVE, REMOVE2; + REMOVE ?? REMOVE2; + REMOVE || REMOVE2; + REMOVE && REMOVE2; + `, + }, + }); + itBundled("dce/TreeShakingNoBundleESM", { + // GENERATED + files: { + "/entry.js": /* js */ ` + function keep() {} + function unused() {} + keep() + `, + }, + format: "esm", + mode: "convertformat", + }); + itBundled("dce/TreeShakingNoBundleCJS", { + // GENERATED + files: { + "/entry.js": /* js */ ` + function keep() {} + function unused() {} + keep() + `, + }, + format: "cjs", + mode: "convertformat", + }); + itBundled("dce/TreeShakingNoBundleIIFE", { + // GENERATED + files: { + "/entry.js": /* js */ ` + function keep() {} + function REMOVE() {} + keep() + `, + }, + format: "iife", + mode: "convertformat", + }); + itBundled("dce/TreeShakingInESMWrapper", { + // GENERATED + files: { + "/entry.js": /* js */ ` + import {keep1} from './lib' + console.log(keep1(), require('./cjs')) + `, + "/cjs.js": /* js */ ` + import {keep2} from './lib' + export default keep2() + `, + "/lib.js": /* js */ ` + export let keep1 = () => 'keep1' + export let keep2 = () => 'keep2' + export let REMOVE = () => 'REMOVE' + `, + }, + format: "esm", + }); + itBundled("dce/DCETypeOf", { + // GENERATED + files: { + "/entry.js": /* js */ ` + // These should be removed because they have no side effects + typeof x_REMOVE + typeof v_REMOVE + typeof f_REMOVE + typeof g_REMOVE + typeof a_REMOVE + var v_REMOVE + function f_REMOVE() {} + function* g_REMOVE() {} + async function a_REMOVE() {} + + // These technically have side effects due to TDZ, but this is not currently handled + typeof c_remove + typeof l_remove + typeof s_remove + const c_remove = 0 + let l_remove + class s_remove {} + `, + }, + format: "esm", + }); + itBundled("dce/DCETypeOfEqualsString", { + // GENERATED + files: { + "/entry.js": /* js */ ` + var hasBar = typeof bar !== 'undefined' + if (false) console.log(hasBar) + `, + }, + format: "iife", + }); + itBundled("dce/DCETypeOfEqualsStringMangle", { + // GENERATED + files: { + "/entry.js": /* js */ ` + // Everything here should be removed as dead code due to tree shaking + var hasBar = typeof bar !== 'undefined' + if (false) console.log(hasBar) + `, + }, + format: "iife", + minifySyntax: true, + }); + itBundled("dce/DCETypeOfEqualsStringGuardCondition", { + // GENERATED + files: { + "/entry.js": /* js */ ` + // Everything here should be removed as dead code due to tree shaking + var REMOVE_1 = typeof x !== 'undefined' ? x : null + var REMOVE_1 = typeof x != 'undefined' ? x : null + var REMOVE_1 = typeof x === 'undefined' ? null : x + var REMOVE_1 = typeof x == 'undefined' ? null : x + var REMOVE_1 = typeof x !== 'undefined' && x + var REMOVE_1 = typeof x != 'undefined' && x + var REMOVE_1 = typeof x === 'undefined' || x + var REMOVE_1 = typeof x == 'undefined' || x + var REMOVE_1 = 'undefined' !== typeof x ? x : null + var REMOVE_1 = 'undefined' != typeof x ? x : null + var REMOVE_1 = 'undefined' === typeof x ? null : x + var REMOVE_1 = 'undefined' == typeof x ? null : x + var REMOVE_1 = 'undefined' !== typeof x && x + var REMOVE_1 = 'undefined' != typeof x && x + var REMOVE_1 = 'undefined' === typeof x || x + var REMOVE_1 = 'undefined' == typeof x || x + + // Everything here should be removed as dead code due to tree shaking + var REMOVE_2 = typeof x === 'object' ? x : null + var REMOVE_2 = typeof x == 'object' ? x : null + var REMOVE_2 = typeof x !== 'object' ? null : x + var REMOVE_2 = typeof x != 'object' ? null : x + var REMOVE_2 = typeof x === 'object' && x + var REMOVE_2 = typeof x == 'object' && x + var REMOVE_2 = typeof x !== 'object' || x + var REMOVE_2 = typeof x != 'object' || x + var REMOVE_2 = 'object' === typeof x ? x : null + var REMOVE_2 = 'object' == typeof x ? x : null + var REMOVE_2 = 'object' !== typeof x ? null : x + var REMOVE_2 = 'object' != typeof x ? null : x + var REMOVE_2 = 'object' === typeof x && x + var REMOVE_2 = 'object' == typeof x && x + var REMOVE_2 = 'object' !== typeof x || x + var REMOVE_2 = 'object' != typeof x || x + + // Everything here should be kept as live code because it has side effects + var keep_1 = typeof x !== 'object' ? x : null + var keep_1 = typeof x != 'object' ? x : null + var keep_1 = typeof x === 'object' ? null : x + var keep_1 = typeof x == 'object' ? null : x + var keep_1 = typeof x !== 'object' && x + var keep_1 = typeof x != 'object' && x + var keep_1 = typeof x === 'object' || x + var keep_1 = typeof x == 'object' || x + var keep_1 = 'object' !== typeof x ? x : null + var keep_1 = 'object' != typeof x ? x : null + var keep_1 = 'object' === typeof x ? null : x + var keep_1 = 'object' == typeof x ? null : x + var keep_1 = 'object' !== typeof x && x + var keep_1 = 'object' != typeof x && x + var keep_1 = 'object' === typeof x || x + var keep_1 = 'object' == typeof x || x + + // Everything here should be kept as live code because it has side effects + var keep_2 = typeof x !== 'undefined' ? y : null + var keep_2 = typeof x != 'undefined' ? y : null + var keep_2 = typeof x === 'undefined' ? null : y + var keep_2 = typeof x == 'undefined' ? null : y + var keep_2 = typeof x !== 'undefined' && y + var keep_2 = typeof x != 'undefined' && y + var keep_2 = typeof x === 'undefined' || y + var keep_2 = typeof x == 'undefined' || y + var keep_2 = 'undefined' !== typeof x ? y : null + var keep_2 = 'undefined' != typeof x ? y : null + var keep_2 = 'undefined' === typeof x ? null : y + var keep_2 = 'undefined' == typeof x ? null : y + var keep_2 = 'undefined' !== typeof x && y + var keep_2 = 'undefined' != typeof x && y + var keep_2 = 'undefined' === typeof x || y + var keep_2 = 'undefined' == typeof x || y + + // Everything here should be kept as live code because it has side effects + var keep_3 = typeof x !== 'undefined' ? null : x + var keep_3 = typeof x != 'undefined' ? null : x + var keep_3 = typeof x === 'undefined' ? x : null + var keep_3 = typeof x == 'undefined' ? x : null + var keep_3 = typeof x !== 'undefined' || x + var keep_3 = typeof x != 'undefined' || x + var keep_3 = typeof x === 'undefined' && x + var keep_3 = typeof x == 'undefined' && x + var keep_3 = 'undefined' !== typeof x ? null : x + var keep_3 = 'undefined' != typeof x ? null : x + var keep_3 = 'undefined' === typeof x ? x : null + var keep_3 = 'undefined' == typeof x ? x : null + var keep_3 = 'undefined' !== typeof x || x + var keep_3 = 'undefined' != typeof x || x + var keep_3 = 'undefined' === typeof x && x + var keep_3 = 'undefined' == typeof x && x + `, + }, + format: "iife", + }); + itBundled("dce/DCETypeOfCompareStringGuardCondition", { + // GENERATED + files: { + "/entry.js": /* js */ ` + // Everything here should be removed as dead code due to tree shaking + var REMOVE_1 = typeof x <= 'u' ? x : null + var REMOVE_1 = typeof x < 'u' ? x : null + var REMOVE_1 = typeof x >= 'u' ? null : x + var REMOVE_1 = typeof x > 'u' ? null : x + var REMOVE_1 = typeof x <= 'u' && x + var REMOVE_1 = typeof x < 'u' && x + var REMOVE_1 = typeof x >= 'u' || x + var REMOVE_1 = typeof x > 'u' || x + var REMOVE_1 = 'u' >= typeof x ? x : null + var REMOVE_1 = 'u' > typeof x ? x : null + var REMOVE_1 = 'u' <= typeof x ? null : x + var REMOVE_1 = 'u' < typeof x ? null : x + var REMOVE_1 = 'u' >= typeof x && x + var REMOVE_1 = 'u' > typeof x && x + var REMOVE_1 = 'u' <= typeof x || x + var REMOVE_1 = 'u' < typeof x || x + + // Everything here should be kept as live code because it has side effects + var keep_1 = typeof x <= 'u' ? y : null + var keep_1 = typeof x < 'u' ? y : null + var keep_1 = typeof x >= 'u' ? null : y + var keep_1 = typeof x > 'u' ? null : y + var keep_1 = typeof x <= 'u' && y + var keep_1 = typeof x < 'u' && y + var keep_1 = typeof x >= 'u' || y + var keep_1 = typeof x > 'u' || y + var keep_1 = 'u' >= typeof x ? y : null + var keep_1 = 'u' > typeof x ? y : null + var keep_1 = 'u' <= typeof x ? null : y + var keep_1 = 'u' < typeof x ? null : y + var keep_1 = 'u' >= typeof x && y + var keep_1 = 'u' > typeof x && y + var keep_1 = 'u' <= typeof x || y + var keep_1 = 'u' < typeof x || y + + // Everything here should be kept as live code because it has side effects + var keep_2 = typeof x <= 'u' ? null : x + var keep_2 = typeof x < 'u' ? null : x + var keep_2 = typeof x >= 'u' ? x : null + var keep_2 = typeof x > 'u' ? x : null + var keep_2 = typeof x <= 'u' || x + var keep_2 = typeof x < 'u' || x + var keep_2 = typeof x >= 'u' && x + var keep_2 = typeof x > 'u' && x + var keep_2 = 'u' >= typeof x ? null : x + var keep_2 = 'u' > typeof x ? null : x + var keep_2 = 'u' <= typeof x ? x : null + var keep_2 = 'u' < typeof x ? x : null + var keep_2 = 'u' >= typeof x || x + var keep_2 = 'u' > typeof x || x + var keep_2 = 'u' <= typeof x && x + var keep_2 = 'u' < typeof x && x + `, + }, + format: "iife", + }); + itBundled("dce/RemoveUnusedImports", { + // GENERATED + files: { + "/entry.js": /* js */ ` + import a from 'a' + import * as b from 'b' + import {c} from 'c' + `, + }, + minifySyntax: true, + mode: "passthrough", + }); + itBundled("dce/RemoveUnusedImportsEval", { + // GENERATED + files: { + "/entry.js": /* js */ ` + import a from 'a' + import * as b from 'b' + import {c} from 'c' + eval('foo(a, b, c)') + `, + }, + minifySyntax: true, + mode: "passthrough", + }); + itBundled("dce/RemoveUnusedImportsEvalTS", { + // GENERATED + files: { + "/entry.ts": /* ts */ ` + import a from 'a' + import * as b from 'b' + import {c} from 'c' + eval('foo(a, b, c)') + `, + }, + entryPoints: ["/entry.js"], + minifySyntax: true, + mode: "passthrough", + }); + itBundled("dce/DCEClassStaticBlocks", { + // GENERATED + files: { + "/entry.ts": /* ts */ ` + class A_REMOVE { + static {} + } + class B_REMOVE { + static { 123 } + } + class C_REMOVE { + static { /* @__PURE__*/ foo() } + } + class D_REMOVE { + static { try {} catch {} } + } + class E_REMOVE { + static { try { /* @__PURE__*/ foo() } catch {} } + } + class F_REMOVE { + static { try { 123 } catch { 123 } finally { 123 } } + } + + class A_keep { + static { foo } + } + class B_keep { + static { this.foo } + } + class C_keep { + static { try { foo } catch {} } + } + class D_keep { + static { try {} finally { foo } } + } + `, + }, + entryPoints: ["/entry.js"], + }); + itBundled("dce/DCEVarExports", { + // GENERATED + files: { + "/a.js": /* js */ ` + var foo = { bar: 123 } + module.exports = foo + `, + "/b.js": /* js */ ` + var exports = { bar: 123 } + module.exports = exports + `, + "/c.js": /* js */ ` + var module = { bar: 123 } + exports.foo = module + `, + }, + entryPoints: ["/a.js", "/b.js", "/c.js"], + }); + itBundled("dce/DCETemplateLiteral", { + // GENERATED + files: {}, + entryPoints: ["/entry.js"], + }); + itBundled("dce/TreeShakingLoweredClassStaticField", { + // GENERATED + files: { + "/entry.js": /* js */ ` + class REMOVE_ME { + static x = 'x' + static y = 'y' + static z = 'z' + } + function REMOVE_ME_TOO() { + new REMOVE_ME() + } + class KeepMe1 { + static x = 'x' + static y = sideEffects() + static z = 'z' + } + class KeepMe2 { + static x = 'x' + static y = 'y' + static z = 'z' + } + new KeepMe2() + `, + }, + }); + itBundled("dce/TreeShakingLoweredClassStaticFieldMinified", { + // GENERATED + files: { + "/entry.js": /* js */ ` + class REMOVE_ME { + static x = 'x' + static y = 'y' + static z = 'z' + } + function REMOVE_ME_TOO() { + new REMOVE_ME() + } + class KeepMe1 { + static x = 'x' + static y = sideEffects() + static z = 'z' + } + class KeepMe2 { + static x = 'x' + static y = 'y' + static z = 'z' + } + new KeepMe2() + `, + }, + unsupportedJSFeatures: "ClassField", + }); + itBundled("dce/TreeShakingLoweredClassStaticFieldAssignment", { + // GENERATED + files: { + "/entry.ts": /* ts */ ` + class KeepMe1 { + static x = 'x' + static y = 'y' + static z = 'z' + } + class KeepMe2 { + static x = 'x' + static y = sideEffects() + static z = 'z' + } + class KeepMe3 { + static x = 'x' + static y = 'y' + static z = 'z' + } + new KeepMe3() + `, + }, + entryPoints: ["/entry.js"], + unsupportedJSFeatures: "ClassField", + }); + itBundled("dce/InlineIdentityFunctionCalls", { + // GENERATED + files: { + "/identity.js": /* js */ ` + function DROP(x) { return x } + console.log(DROP(1)) + DROP(foo()) + DROP(1) + `, + "/identity-last.js": /* js */ ` + function DROP(x) { return [x] } + function DROP(x) { return x } + console.log(DROP(1)) + DROP(foo()) + DROP(1) + `, + "/identity-cross-module.js": /* js */ ` + import { DROP } from './identity-cross-module-def' + console.log(DROP(1)) + DROP(foo()) + DROP(1) + `, + "/identity-cross-module-def.js": `export function DROP(x) { return x }`, + "/identity-no-args.js": /* js */ ` + function keep(x) { return x } + console.log(keep()) + keep() + `, + "/identity-two-args.js": /* js */ ` + function keep(x) { return x } + console.log(keep(1, 2)) + keep(1, 2) + `, + "/identity-first.js": /* js */ ` + function keep(x) { return x } + function keep(x) { return [x] } + console.log(keep(1)) + keep(foo()) + keep(1) + `, + "/identity-generator.js": /* js */ ` + function* keep(x) { return x } + console.log(keep(1)) + keep(foo()) + keep(1) + `, + "/identity-async.js": /* js */ ` + async function keep(x) { return x } + console.log(keep(1)) + keep(foo()) + keep(1) + `, + "/reassign.js": /* js */ ` + function keep(x) { return x } + keep = reassigned + console.log(keep(1)) + keep(foo()) + keep(1) + `, + "/reassign-inc.js": /* js */ ` + function keep(x) { return x } + keep++ + console.log(keep(1)) + keep(foo()) + keep(1) + `, + "/reassign-div.js": /* js */ ` + function keep(x) { return x } + keep /= reassigned + console.log(keep(1)) + keep(foo()) + keep(1) + `, + "/reassign-array.js": /* js */ ` + function keep(x) { return x } + [keep] = reassigned + console.log(keep(1)) + keep(foo()) + keep(1) + `, + "/reassign-object.js": /* js */ ` + function keep(x) { return x } + ({keep} = reassigned) + console.log(keep(1)) + keep(foo()) + keep(1) + `, + "/not-identity-two-args.js": /* js */ ` + function keep(x, y) { return x } + console.log(keep(1)) + keep(foo()) + keep(1) + `, + "/not-identity-default.js": /* js */ ` + function keep(x = foo()) { return x } + console.log(keep(1)) + keep(foo()) + keep(1) + `, + "/not-identity-array.js": /* js */ ` + function keep([x]) { return x } + console.log(keep(1)) + keep(foo()) + keep(1) + `, + "/not-identity-object.js": /* js */ ` + function keep({x}) { return x } + console.log(keep(1)) + keep(foo()) + keep(1) + `, + "/not-identity-rest.js": /* js */ ` + function keep(...x) { return x } + console.log(keep(1)) + keep(foo()) + keep(1) + `, + "/not-identity-return.js": /* js */ ` + function keep(x) { return [x] } + console.log(keep(1)) + keep(foo()) + keep(1) + `, + }, + entryPoints: [ + "/identity.js", + "/identity-last.js", + "/identity-first.js", + "/identity-generator.js", + "/identity-async.js", + "/identity-cross-module.js", + "/identity-no-args.js", + "/identity-two-args.js", + "/reassign.js", + "/reassign-inc.js", + "/reassign-div.js", + "/reassign-array.js", + "/reassign-object.js", + "/not-identity-two-args.js", + "/not-identity-default.js", + "/not-identity-array.js", + "/not-identity-object.js", + "/not-identity-rest.js", + "/not-identity-return.js", + ], + }); + itBundled("dce/InlineEmptyFunctionCalls", { + // GENERATED + files: { + "/empty.js": /* js */ ` + function DROP() {} + console.log(DROP(foo(), bar())) + console.log(DROP(foo(), 1)) + console.log(DROP(1, foo())) + console.log(DROP(1)) + console.log(DROP()) + DROP(foo(), bar()) + DROP(foo(), 1) + DROP(1, foo()) + DROP(1) + DROP() + `, + "/empty-comma.js": /* js */ ` + function DROP() {} + console.log((DROP(), DROP(), foo())) + console.log((DROP(), foo(), DROP())) + console.log((foo(), DROP(), DROP())) + for (DROP(); DROP(); DROP()) DROP(); + DROP(), DROP(), foo(); + DROP(), foo(), DROP(); + foo(), DROP(), DROP(); + `, + "/empty-if-else.js": /* js */ ` + function DROP() {} + if (foo) { let bar = baz(); bar(); bar() } else DROP(); + `, + "/empty-last.js": /* js */ ` + function DROP() { return x } + function DROP() { return } + console.log(DROP()) + DROP() + `, + "/empty-cross-module.js": /* js */ ` + import { DROP } from './empty-cross-module-def' + console.log(DROP()) + DROP() + `, + "/empty-cross-module-def.js": `export function DROP() {}`, + "/empty-first.js": /* js */ ` + function keep() { return } + function keep() { return x } + console.log(keep()) + keep(foo()) + keep(1) + `, + "/empty-generator.js": /* js */ ` + function* keep() {} + console.log(keep()) + keep(foo()) + keep(1) + `, + "/empty-async.js": /* js */ ` + async function keep() {} + console.log(keep()) + keep(foo()) + keep(1) + `, + "/reassign.js": /* js */ ` + function keep() {} + keep = reassigned + console.log(keep()) + keep(foo()) + keep(1) + `, + "/reassign-inc.js": /* js */ ` + function keep() {} + keep++ + console.log(keep(1)) + keep(foo()) + keep(1) + `, + "/reassign-div.js": /* js */ ` + function keep() {} + keep /= reassigned + console.log(keep(1)) + keep(foo()) + keep(1) + `, + "/reassign-array.js": /* js */ ` + function keep() {} + [keep] = reassigned + console.log(keep(1)) + keep(foo()) + keep(1) + `, + "/reassign-object.js": /* js */ ` + function keep() {} + ({keep} = reassigned) + console.log(keep(1)) + keep(foo()) + keep(1) + `, + }, + entryPoints: [ + "/empty.js", + "/empty-comma.js", + "/empty-if-else.js", + "/empty-last.js", + "/empty-cross-module.js", + "/empty-first.js", + "/empty-generator.js", + "/empty-async.js", + "/reassign.js", + "/reassign-inc.js", + "/reassign-div.js", + "/reassign-array.js", + "/reassign-object.js", + ], + }); + itBundled("dce/InlineFunctionCallBehaviorChanges", { + // GENERATED + files: { + "/entry.js": ` + function empty() {} + function id(x) { return x } + + export let shouldBeWrapped = [ + id(foo.bar)(), + id(foo[bar])(), + id(foo?.bar)(), + id(foo?.[bar])(), + + (empty(), foo.bar)(), + (empty(), foo[bar])(), + (empty(), foo?.bar)(), + (empty(), foo?.[bar])(), + + id(eval)(), + id(eval)?.(), + (empty(), eval)(), + (empty(), eval)?.(), + + id(foo.bar)\` + "\`\`" + + `, + }, + mode: "passthrough", + }); + itBundled("dce/InlineFunctionCallForInitDecl", { + // GENERATED + files: { + "/entry.js": /* js */ ` + function empty() {} + function id(x) { return x } + + for (var y = empty(); false; ) ; + for (var z = id(123); false; ) ; + `, + }, + }); + itBundled("dce/ConstValueInliningNoBundle", { + // GENERATED + files: { + "/top-level.js": /* js */ ` + // These should be kept because they are top-level and tree shaking is not enabled + const n_keep = null + const u_keep = undefined + const i_keep = 1234567 + const f_keep = 123.456 + const s_keep = '' + + // Values should still be inlined + console.log( + // These are doubled to avoid the "inline const/let into next statement if used once" optimization + n_keep, n_keep, + u_keep, u_keep, + i_keep, i_keep, + f_keep, f_keep, + s_keep, s_keep, + ) + `, + "/nested-block.js": /* js */ ` + { + const REMOVE_n = null + const REMOVE_u = undefined + const REMOVE_i = 1234567 + const REMOVE_f = 123.456 + const s_keep = '' // String inlining is intentionally not supported right now + console.log( + // These are doubled to avoid the "inline const/let into next statement if used once" optimization + REMOVE_n, REMOVE_n, + REMOVE_u, REMOVE_u, + REMOVE_i, REMOVE_i, + REMOVE_f, REMOVE_f, + s_keep, s_keep, + ) + } + `, + "/nested-function.js": /* js */ ` + function nested() { + const REMOVE_n = null + const REMOVE_u = undefined + const REMOVE_i = 1234567 + const REMOVE_f = 123.456 + const s_keep = '' // String inlining is intentionally not supported right now + console.log( + // These are doubled to avoid the "inline const/let into next statement if used once" optimization + REMOVE_n, REMOVE_n, + REMOVE_u, REMOVE_u, + REMOVE_i, REMOVE_i, + REMOVE_f, REMOVE_f, + s_keep, s_keep, + ) + } + `, + "/namespace-export.ts": /* ts */ ` + namespace ns { + const x_REMOVE = 1 + export const y_keep = 2 + console.log( + x_REMOVE, x_REMOVE, + y_keep, y_keep, + ) + } + `, + "/comment-before.js": /* js */ ` + { + //! comment + const REMOVE = 1 + x = [REMOVE, REMOVE] + } + `, + "/directive-before.js": /* js */ ` + function nested() { + 'directive' + const REMOVE = 1 + x = [REMOVE, REMOVE] + } + `, + "/semicolon-before.js": /* js */ ` + { + ; + const REMOVE = 1 + x = [REMOVE, REMOVE] + } + `, + "/debugger-before.js": /* js */ ` + { + debugger + const REMOVE = 1 + x = [REMOVE, REMOVE] + } + `, + "/type-before.ts": /* ts */ ` + { + declare let x + const REMOVE = 1 + x = [REMOVE, REMOVE] + } + `, + "/exprs-before.js": /* js */ ` + function nested() { + const x = [, '', {}, 0n, /./, function() {}, () => {}] + const y_REMOVE = 1 + function foo() { + return y_REMOVE + } + } + `, + "/disabled-tdz.js": /* js */ ` + foo() + const x_keep = 1 + function foo() { + return x_keep + } + `, + "/backwards-reference-top-level.js": /* js */ ` + const x = y + const y = 1 + console.log( + x, x, + y, y, + ) + `, + "/backwards-reference-nested-function.js": /* js */ ` + function foo() { + const x = y + const y = 1 + console.log( + x, x, + y, y, + ) + } + `, + }, + entryPoints: [ + "/top-level.js", + "/nested-block.js", + "/nested-function.js", + "/namespace-export.ts", + "/comment-before.js", + "/directive-before.js", + "/semicolon-before.js", + "/debugger-before.js", + "/type-before.ts", + "/exprs-before.js", + "/disabled-tdz.js", + "/backwards-reference-top-level.js", + "/backwards-reference-nested-function.js", + ], + mode: "passthrough", + }); + itBundled("dce/ConstValueInliningBundle", { + // GENERATED + files: { + "/exported-entry.js": /* js */ ` + const x_REMOVE = 1 + export const y_keep = 2 + console.log( + x_REMOVE, + y_keep, + ) + `, + "/re-exported-entry.js": /* js */ ` + import { x_REMOVE, y_keep } from './re-exported-constants' + console.log(x_REMOVE, y_keep) + export { y_keep } + `, + "/re-exported-constants.js": /* js */ ` + export const x_REMOVE = 1 + export const y_keep = 2 + `, + "/re-exported-2-entry.js": `export { y_keep } from './re-exported-2-constants'`, + "/re-exported-2-constants.js": /* js */ ` + export const x_REMOVE = 1 + export const y_keep = 2 + `, + "/re-exported-star-entry.js": `export * from './re-exported-star-constants'`, + "/re-exported-star-constants.js": /* js */ ` + export const x_keep = 1 + export const y_keep = 2 + `, + "/cross-module-entry.js": /* js */ ` + import { x_REMOVE, y_keep } from './cross-module-constants' + console.log(x_REMOVE, y_keep) + `, + "/cross-module-constants.js": /* js */ ` + export const x_REMOVE = 1 + foo() + export const y_keep = 1 + export function foo() { + return [x_REMOVE, y_keep] + } + `, + "/print-shorthand-entry.js": /* js */ ` + import { foo, _bar } from './print-shorthand-constants' + // The inlined constants must still be present in the output! We don't + // want the printer to use the shorthand syntax here to refer to the + // name of the constant itself because the constant declaration is omitted. + console.log({ foo, _bar }) + `, + "/print-shorthand-constants.js": /* js */ ` + export const foo = 123 + export const _bar = -321 + `, + "/circular-import-entry.js": `import './circular-import-constants'`, + "/circular-import-constants.js": /* js */ ` + export const foo = 123 // Inlining should be prevented by the cycle + export function bar() { + return foo + } + import './circular-import-cycle' + `, + "/circular-import-cycle.js": /* js */ ` + import { bar } from './circular-import-constants' + console.log(bar()) // This accesses "foo" before it's initialized + `, + "/circular-re-export-entry.js": /* js */ ` + import { baz } from './circular-re-export-constants' + console.log(baz) + `, + "/circular-re-export-constants.js": /* js */ ` + export const foo = 123 // Inlining should be prevented by the cycle + export function bar() { + return foo + } + export { baz } from './circular-re-export-cycle' + `, + "/circular-re-export-cycle.js": /* js */ ` + export const baz = 0 + import { bar } from './circular-re-export-constants' + console.log(bar()) // This accesses "foo" before it's initialized + `, + "/circular-re-export-star-entry.js": `import './circular-re-export-star-constants'`, + "/circular-re-export-star-constants.js": /* js */ ` + export const foo = 123 // Inlining should be prevented by the cycle + export function bar() { + return foo + } + export * from './circular-re-export-star-cycle' + `, + "/circular-re-export-star-cycle.js": /* js */ ` + import { bar } from './circular-re-export-star-constants' + console.log(bar()) // This accesses "foo" before it's initialized + `, + "/non-circular-export-entry.js": /* js */ ` + import { foo, bar } from './non-circular-export-constants' + console.log(foo, bar()) + `, + "/non-circular-export-constants.js": /* js */ ` + const foo = 123 // Inlining should be prevented by the cycle + function bar() { + return foo + } + export { foo, bar } + `, + }, + entryPoints: [ + "/exported-entry.js", + "/re-exported-entry.js", + "/re-exported-2-entry.js", + "/re-exported-star-entry.js", + "/cross-module-entry.js", + "/print-shorthand-entry.js", + "/circular-import-entry.js", + "/circular-re-export-entry.js", + "/circular-re-export-star-entry.js", + "/non-circular-export-entry.js", + ], + format: "esm", + minifySyntax: true, + }); + itBundled("dce/ConstValueInliningAssign", { + // GENERATED + files: { + "/const-assign.js": /* js */ ` + const x = 1 + x = 2 + `, + "/const-update.js": /* js */ ` + const x = 1 + x += 2 + `, + }, + entryPoints: ["/const-assign.js", "/const-update.js"], + mode: "passthrough", + /* TODO FIX expectedScanLog: `const-assign.js: ERROR: Cannot assign to "x" because it is a constant + const-assign.js: NOTE: The symbol "x" was declared a constant here: + const-update.js: ERROR: Cannot assign to "x" because it is a constant + const-update.js: NOTE: The symbol "x" was declared a constant here: + `, */ + }); + itBundled("dce/ConstValueInliningDirectEval", { + // GENERATED + files: { + "/top-level-no-eval.js": /* js */ ` + const x = 1 + console.log(x, evil('x')) + `, + "/top-level-eval.js": /* js */ ` + const x = 1 + console.log(x, eval('x')) + `, + "/nested-no-eval.js": /* js */ ` + (() => { + const x = 1 + console.log(x, evil('x')) + })() + `, + "/nested-eval.js": /* js */ ` + (() => { + const x = 1 + console.log(x, eval('x')) + })() + `, + "/ts-namespace-no-eval.ts": /* ts */ ` + namespace y { + export const x = 1 + console.log(x, evil('x')) + } + `, + "/ts-namespace-eval.ts": /* ts */ ` + namespace z { + export const x = 1 + console.log(x, eval('x')) + } + `, + }, + entryPoints: [ + "/top-level-no-eval.js", + "/top-level-eval.js", + "/nested-no-eval.js", + "/nested-eval.js", + "/ts-namespace-no-eval.ts", + "/ts-namespace-eval.ts", + ], + mode: "passthrough", + }); + itBundled("dce/CrossModuleConstantFolding", { + // GENERATED + files: { + "/enum-constants.ts": /* ts */ ` + export enum x { + a = 3, + b = 6, + } + `, + "/enum-entry.ts": /* ts */ ` + import { x } from './enum-constants' + console.log([ + +x.b, + -x.b, + ~x.b, + !x.b, + typeof x.b, + ], [ + x.a + x.b, + x.a - x.b, + x.a * x.b, + x.a / x.b, + x.a % x.b, + x.a ** x.b, + ], [ + x.a < x.b, + x.a > x.b, + x.a <= x.b, + x.a >= x.b, + x.a == x.b, + x.a != x.b, + x.a === x.b, + x.a !== x.b, + ], [ + x.b << 1, + x.b >> 1, + x.b >>> 1, + ], [ + x.a & x.b, + x.a | x.b, + x.a ^ x.b, + ], [ + x.a && x.b, + x.a || x.b, + x.a ?? x.b, + ]) + `, + "/const-constants.js": /* js */ ` + export const a = 3 + export const b = 6 + `, + "/const-entry.js": /* js */ ` + import { a, b } from './const-constants' + console.log([ + +b, + -b, + ~b, + !b, + typeof b, + ], [ + a + b, + a - b, + a * b, + a / b, + a % b, + a ** b, + ], [ + a < b, + a > b, + a <= b, + a >= b, + a == b, + a != b, + a === b, + a !== b, + ], [ + b << 1, + b >> 1, + b >>> 1, + ], [ + a & b, + a | b, + a ^ b, + ], [ + a && b, + a || b, + a ?? b, + ]) + `, + "/nested-constants.ts": /* ts */ ` + export const a = 2 + export const b = 4 + export const c = 8 + export enum x { + a = 16, + b = 32, + c = 64, + } + `, + "/nested-entry.ts": /* ts */ ` + import { a, b, c, x } from './nested-constants' + console.log({ + 'should be 4': ~(~a & ~b) & (b | c), + 'should be 32': ~(~x.a & ~x.b) & (x.b | x.c), + }) + `, + }, + entryPoints: ["/enum-entry.ts", "/const-entry.js", "/nested-entry.ts"], + }); + itBundled("dce/MultipleDeclarationTreeShaking", { + // GENERATED + files: { + "/var2.js": /* js */ ` + var x = 1 + console.log(x) + var x = 2 + `, + "/var3.js": /* js */ ` + var x = 1 + console.log(x) + var x = 2 + console.log(x) + var x = 3 + `, + "/function2.js": /* js */ ` + function x() { return 1 } + console.log(x()) + function x() { return 2 } + `, + "/function3.js": /* js */ ` + function x() { return 1 } + console.log(x()) + function x() { return 2 } + console.log(x()) + function x() { return 3 } + `, + }, + entryPoints: ["/var2.js", "/var3.js", "/function2.js", "/function3.js"], + }); + itBundled("dce/MultipleDeclarationTreeShakingMinifySyntax", { + // GENERATED + files: { + "/var2.js": /* js */ ` + var x = 1 + console.log(x) + var x = 2 + `, + "/var3.js": /* js */ ` + var x = 1 + console.log(x) + var x = 2 + console.log(x) + var x = 3 + `, + "/function2.js": /* js */ ` + function x() { return 1 } + console.log(x()) + function x() { return 2 } + `, + "/function3.js": /* js */ ` + function x() { return 1 } + console.log(x()) + function x() { return 2 } + console.log(x()) + function x() { return 3 } + `, + }, + entryPoints: ["/var2.js", "/var3.js", "/function2.js", "/function3.js"], + }); + itBundled("dce/PureCallsWithSpread", { + // GENERATED + files: { + "/entry.js": /* js */ ` + /* @__PURE__ */ foo(...args); + /* @__PURE__ */ new foo(...args); + `, + }, + }); + itBundled("dce/TopLevelFunctionInliningWithSpread", { + // GENERATED + files: { + "/entry.js": /* js */ ` + function empty1() {} + function empty2() {} + function empty3() {} + + function identity1(x) { return x } + function identity2(x) { return x } + function identity3(x) { return x } + + empty1() + empty2(args) + empty3(...args) + + identity1() + identity2(args) + identity3(...args) + `, + "/inner.js": /* js */ ` + export function empty1() {} + export function empty2() {} + export function empty3() {} + + export function identity1(x) { return x } + export function identity2(x) { return x } + export function identity3(x) { return x } + `, + "/entry-outer.js": /* js */ ` + import { + empty1, + empty2, + empty3, + + identity1, + identity2, + identity3, + } from './inner.js' + + empty1() + empty2(args) + empty3(...args) + + identity1() + identity2(args) + identity3(...args) + `, + }, + entryPoints: ["/entry.js", "/entry-outer.js"], + }); + itBundled("dce/NestedFunctionInliningWithSpread", { + // GENERATED + files: { + "/entry.js": /* js */ ` + function empty1() {} + function empty2() {} + function empty3() {} + + function identity1(x) { return x } + function identity2(x) { return x } + function identity3(x) { return x } + + check( + empty1(), + empty2(args), + empty3(...args), + + identity1(), + identity2(args), + identity3(...args), + ) + `, + "/inner.js": /* js */ ` + export function empty1() {} + export function empty2() {} + export function empty3() {} + + export function identity1(x) { return x } + export function identity2(x) { return x } + export function identity3(x) { return x } + `, + "/entry-outer.js": /* js */ ` + import { + empty1, + empty2, + empty3, + + identity1, + identity2, + identity3, + } from './inner.js' + + check( + empty1(), + empty2(args), + empty3(...args), + + identity1(), + identity2(args), + identity3(...args), + ) + `, + }, + entryPoints: ["/entry.js", "/entry-outer.js"], + }); + itBundled("dce/PackageJsonSideEffectsFalseCrossPlatformSlash", { + // GENERATED + files: { + "/Users/user/project/src/entry.js": /* js */ ` + import "demo-pkg/foo" + import "demo-pkg/bar" + `, + "/Users/user/project/node_modules/demo-pkg/foo.js": `console.log('foo')`, + "/Users/user/project/node_modules/demo-pkg/bar/index.js": `console.log('bar')`, + "/Users/user/project/node_modules/demo-pkg/package.json": /* json */ ` + { + "sideEffects": [ + "**/foo.js", + "bar/index.js" + ] + } + `, + }, + }); + itBundled("dce/TreeShakingJSWithAssociatedCSS", { + // GENERATED + files: { + "/project/test.jsx": /* jsx */ ` + import { Button } from 'pkg/button' + import { Menu } from 'pkg/menu' + render(