mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 23:18:47 +00:00
Compare commits
3 Commits
dylan/byte
...
jarred/sna
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
148bed1513 | ||
|
|
5b08ed28a9 | ||
|
|
21ca7be630 |
@@ -3687,40 +3687,6 @@ pub const JSValue = enum(JSValueReprInt) {
|
||||
);
|
||||
|
||||
try buffered_writer.flush();
|
||||
|
||||
const count: usize = brk: {
|
||||
var total: usize = 0;
|
||||
var remain = out.list.items;
|
||||
while (strings.indexOfChar(remain, '`')) |i| {
|
||||
total += 1;
|
||||
remain = remain[i + 1 ..];
|
||||
}
|
||||
break :brk total;
|
||||
};
|
||||
|
||||
if (count > 0) {
|
||||
var result = try out.allocator.alloc(u8, count + out.list.items.len);
|
||||
var input = out.list.items;
|
||||
|
||||
var input_i: usize = 0;
|
||||
var result_i: usize = 0;
|
||||
while (strings.indexOfChar(input[input_i..], '`')) |i| {
|
||||
bun.copy(u8, result[result_i..], input[input_i .. input_i + i]);
|
||||
result_i += i;
|
||||
result[result_i] = '\\';
|
||||
result[result_i + 1] = '`';
|
||||
result_i += 2;
|
||||
input_i += i + 1;
|
||||
}
|
||||
|
||||
if (result_i != result.len) {
|
||||
bun.copy(u8, result[result_i..], input[input_i..]);
|
||||
}
|
||||
|
||||
out.deinit();
|
||||
out.list.items = result;
|
||||
out.list.capacity = result.len;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn jestPrettyFormat(this: JSValue, out: *MutableString, globalObject: *JSGlobalObject) !void {
|
||||
|
||||
@@ -221,44 +221,38 @@ pub const Expect = struct {
|
||||
pub fn getSnapshotName(this: *Expect, allocator: std.mem.Allocator, hint: string) ![]const u8 {
|
||||
const test_name = this.scope.tests.items[this.test_id].label;
|
||||
|
||||
var length: usize = 0;
|
||||
var builder = bun.StringBuilder{};
|
||||
|
||||
var curr_scope: ?*DescribeScope = this.scope;
|
||||
while (curr_scope) |scope| {
|
||||
if (scope.label.len > 0) {
|
||||
length += scope.label.len + 1;
|
||||
builder.count(scope.label);
|
||||
builder.count(" ");
|
||||
}
|
||||
curr_scope = scope.parent;
|
||||
}
|
||||
length += test_name.len;
|
||||
builder.count(test_name);
|
||||
if (hint.len > 0) {
|
||||
length += hint.len + 2;
|
||||
builder.count(hint);
|
||||
builder.count(": ");
|
||||
}
|
||||
|
||||
var buf = try allocator.alloc(u8, length);
|
||||
try builder.allocate(allocator);
|
||||
|
||||
var index = buf.len;
|
||||
if (hint.len > 0) {
|
||||
index -= hint.len;
|
||||
bun.copy(u8, buf[index..], hint);
|
||||
index -= test_name.len + 2;
|
||||
bun.copy(u8, buf[index..], test_name);
|
||||
bun.copy(u8, buf[index + test_name.len ..], ": ");
|
||||
} else {
|
||||
index -= test_name.len;
|
||||
bun.copy(u8, buf[index..], test_name);
|
||||
}
|
||||
// copy describe scopes in reverse order
|
||||
curr_scope = this.scope;
|
||||
while (curr_scope) |scope| {
|
||||
if (scope.label.len > 0) {
|
||||
index -= scope.label.len + 1;
|
||||
bun.copy(u8, buf[index..], scope.label);
|
||||
buf[index + scope.label.len] = ' ';
|
||||
builder.write(scope.label);
|
||||
builder.write(" ");
|
||||
}
|
||||
curr_scope = scope.parent;
|
||||
}
|
||||
builder.write(test_name);
|
||||
if (hint.len > 0) {
|
||||
builder.write(": ");
|
||||
builder.write(hint);
|
||||
}
|
||||
|
||||
return buf;
|
||||
return builder.written();
|
||||
}
|
||||
|
||||
pub fn finalize(
|
||||
@@ -2003,13 +1997,14 @@ pub const Expect = struct {
|
||||
globalObject.throwPretty(fmt, .{});
|
||||
}
|
||||
|
||||
var hint_string: ZigString = ZigString.Empty;
|
||||
var hint: ZigString.Slice = ZigString.Slice.empty;
|
||||
defer hint.deinit();
|
||||
var property_matchers: ?JSValue = null;
|
||||
switch (arguments.len) {
|
||||
0 => {},
|
||||
1 => {
|
||||
if (arguments[0].isString()) {
|
||||
arguments[0].toZigString(&hint_string, globalObject);
|
||||
hint = arguments[0].toBunString(globalObject).toUTF8(bun.default_allocator);
|
||||
} else if (arguments[0].isObject()) {
|
||||
property_matchers = arguments[0];
|
||||
}
|
||||
@@ -2025,14 +2020,11 @@ pub const Expect = struct {
|
||||
property_matchers = arguments[0];
|
||||
|
||||
if (arguments[1].isString()) {
|
||||
arguments[1].toZigString(&hint_string, globalObject);
|
||||
hint = arguments[1].toBunString(globalObject).toUTF8(bun.default_allocator);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
var hint = hint_string.toSlice(default_allocator);
|
||||
defer hint.deinit();
|
||||
|
||||
const value: JSValue = this.getValue(globalObject, thisValue, "toMatchSnapshot", "<green>properties<r><d>, <r>hint") orelse return .zero;
|
||||
|
||||
if (!value.isObject() and property_matchers != null) {
|
||||
@@ -2072,6 +2064,7 @@ pub const Expect = struct {
|
||||
|
||||
if (result) |saved_value| {
|
||||
var pretty_value: MutableString = MutableString.init(default_allocator, 0) catch unreachable;
|
||||
defer pretty_value.deinit();
|
||||
value.jestSnapshotPrettyFormat(&pretty_value, globalObject) catch {
|
||||
var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject };
|
||||
globalObject.throw("Failed to pretty format value: {s}", .{value.toFmt(globalObject, &formatter)});
|
||||
|
||||
@@ -17,7 +17,7 @@ const Expect = @import("./expect.zig").Expect;
|
||||
|
||||
pub const Snapshots = struct {
|
||||
const file_header = "// Bun Snapshot v1, https://goo.gl/fbAQLP\n";
|
||||
pub const ValuesHashMap = std.HashMap(usize, string, bun.IdentityContext(usize), std.hash_map.default_max_load_percentage);
|
||||
pub const ValuesHashMap = bun.StringHashMap(Entry);
|
||||
|
||||
allocator: std.mem.Allocator,
|
||||
update_snapshots: bool,
|
||||
@@ -28,15 +28,35 @@ pub const Snapshots = struct {
|
||||
|
||||
file_buf: *std.ArrayList(u8),
|
||||
values: *ValuesHashMap,
|
||||
counts: *bun.StringHashMap(usize),
|
||||
_current_file: ?File = null,
|
||||
snapshot_dir_path: ?string = null,
|
||||
|
||||
seen: SeenSnapshotsMap = SeenSnapshotsMap.init(bun.default_allocator),
|
||||
|
||||
const SeenSnapshotsMap = std.HashMap(u64, void, bun.IdentityContext(u64), 80);
|
||||
|
||||
pub const Entry = struct {
|
||||
text: []const u8 = "",
|
||||
counter: u32 = 0,
|
||||
|
||||
pub fn eql(this: *const Entry, other: *const Entry) bool {
|
||||
return strings.eqlLong(this.text, other.text, true) and this.counter == other.counter;
|
||||
}
|
||||
};
|
||||
|
||||
const File = struct {
|
||||
id: TestRunner.File.ID,
|
||||
file: std.fs.File,
|
||||
};
|
||||
|
||||
fn hashWithCount(name: []const u8, count: u32) u64 {
|
||||
var wy = std.hash.Wyhash.init(0);
|
||||
wy.update(name);
|
||||
var buf: [60]u8 = undefined;
|
||||
wy.update(std.fmt.bufPrint(&buf, " {d}", .{count}) catch unreachable);
|
||||
return wy.final();
|
||||
}
|
||||
|
||||
pub fn getOrPut(this: *Snapshots, expect: *Expect, value: JSValue, hint: string, globalObject: *JSC.JSGlobalObject) !?string {
|
||||
switch (try this.getSnapshotFile(expect.scope.file_id)) {
|
||||
.result => {},
|
||||
@@ -49,50 +69,67 @@ pub const Snapshots = struct {
|
||||
},
|
||||
}
|
||||
|
||||
const snapshot_name = try expect.getSnapshotName(this.allocator, hint);
|
||||
var snapshot_name = try expect.getSnapshotName(this.allocator, hint);
|
||||
this.total += 1;
|
||||
|
||||
var count_entry = try this.counts.getOrPut(snapshot_name);
|
||||
const counter = brk: {
|
||||
if (count_entry.found_existing) {
|
||||
this.allocator.free(snapshot_name);
|
||||
count_entry.value_ptr.* += 1;
|
||||
break :brk count_entry.value_ptr.*;
|
||||
var entry = try this.values.getOrPut(snapshot_name);
|
||||
|
||||
if (entry.found_existing) {
|
||||
this.allocator.free(snapshot_name);
|
||||
snapshot_name = entry.key_ptr.*;
|
||||
|
||||
entry.value_ptr.counter += 1;
|
||||
} else {
|
||||
entry.value_ptr.counter = 1;
|
||||
}
|
||||
|
||||
var counter: u32 = entry.value_ptr.counter;
|
||||
|
||||
{
|
||||
if (!this.update_snapshots) {
|
||||
const hash = hashWithCount(snapshot_name, counter);
|
||||
var seen = try this.seen.getOrPut(hash);
|
||||
|
||||
if (seen.found_existing) {
|
||||
var stack_fallback = std.heap.stackFallback(2048, this.allocator);
|
||||
var stack_fallback_allocator = stack_fallback.get();
|
||||
const temp_buf = try std.fmt.allocPrint(stack_fallback_allocator, "{s} {d}", .{ snapshot_name, entry.value_ptr.counter });
|
||||
defer stack_fallback_allocator.free(temp_buf);
|
||||
if (this.values.getPtr(temp_buf)) |existing| {
|
||||
return existing.text;
|
||||
}
|
||||
}
|
||||
}
|
||||
count_entry.value_ptr.* = 1;
|
||||
break :brk count_entry.value_ptr.*;
|
||||
};
|
||||
|
||||
const name = count_entry.key_ptr.*;
|
||||
|
||||
var counter_string_buf = [_]u8{0} ** 32;
|
||||
var counter_string = try std.fmt.bufPrint(&counter_string_buf, "{d}", .{counter});
|
||||
|
||||
var name_with_counter = try this.allocator.alloc(u8, name.len + 1 + counter_string.len);
|
||||
defer this.allocator.free(name_with_counter);
|
||||
bun.copy(u8, name_with_counter[0..name.len], name);
|
||||
name_with_counter[name.len] = ' ';
|
||||
bun.copy(u8, name_with_counter[name.len + 1 ..], counter_string);
|
||||
|
||||
const name_hash = bun.hash(name_with_counter);
|
||||
if (this.values.get(name_hash)) |expected| {
|
||||
return expected;
|
||||
}
|
||||
|
||||
// doesn't exist. append to file bytes and add to hashmap.
|
||||
var pretty_value = try MutableString.init(this.allocator, 0);
|
||||
defer pretty_value.deinit();
|
||||
|
||||
try value.jestSnapshotPrettyFormat(&pretty_value, globalObject);
|
||||
|
||||
const serialized_length = "\nexports[`".len + name_with_counter.len + "`] = `".len + pretty_value.list.items.len + "`;\n".len;
|
||||
const snapshot_name_len = std.fmt.count("{any} {d}", .{ strings.JavaScriptStringFormatter{ .str = snapshot_name }, counter });
|
||||
const value_len = std.fmt.count("{any}", .{strings.JavaScriptStringFormatter{ .str = pretty_value.list.items }});
|
||||
|
||||
const serialized_length = "\nexports[`".len + snapshot_name_len + "`] = `".len + value_len + "`;\n".len;
|
||||
try this.file_buf.ensureUnusedCapacity(serialized_length);
|
||||
this.file_buf.appendSliceAssumeCapacity("\nexports[`");
|
||||
this.file_buf.appendSliceAssumeCapacity(name_with_counter);
|
||||
|
||||
try this.file_buf.writer().print("{} {d}", .{ strings.JavaScriptStringFormatter{ .str = snapshot_name }, counter });
|
||||
|
||||
this.file_buf.appendSliceAssumeCapacity("`] = `");
|
||||
this.file_buf.appendSliceAssumeCapacity(pretty_value.list.items);
|
||||
|
||||
var escaped_value_i: usize = this.file_buf.items.len;
|
||||
try this.file_buf.writer().print("{}", .{strings.JavaScriptStringFormatter{ .str = pretty_value.list.items }});
|
||||
const escaped_value_len = this.file_buf.items.len - escaped_value_i;
|
||||
this.file_buf.appendSliceAssumeCapacity("`;\n");
|
||||
|
||||
this.added += 1;
|
||||
try this.values.put(name_hash, pretty_value.toOwnedSlice());
|
||||
|
||||
if (entry.found_existing) {
|
||||
this.allocator.free(entry.value_ptr.text);
|
||||
}
|
||||
entry.value_ptr.text = try this.allocator.dupe(u8, this.file_buf.items[escaped_value_i .. escaped_value_i + escaped_value_len]);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -121,6 +158,7 @@ pub const Snapshots = struct {
|
||||
const snapshot_file_path = snapshot_file_path_buf[0 .. snapshot_file_path_buf.len - remain.len :0];
|
||||
|
||||
const source = logger.Source.initPathString(snapshot_file_path, this.file_buf.items);
|
||||
vm.bundler.resetStore();
|
||||
|
||||
var parser = try js_parser.Parser.init(
|
||||
opts,
|
||||
@@ -163,18 +201,45 @@ pub const Snapshots = struct {
|
||||
if (left.data == .e_index and left.data.e_index.index.data == .e_string and left.data.e_index.target.data == .e_identifier) {
|
||||
const target: js_ast.E.Identifier = left.data.e_index.target.data.e_identifier;
|
||||
var index: *js_ast.E.String = left.data.e_index.index.data.e_string;
|
||||
if (target.ref.eql(exports_ref) and expr.value.data.e_binary.right.data == .e_string) {
|
||||
const key = index.slice(this.allocator);
|
||||
var value_string = expr.value.data.e_binary.right.data.e_string;
|
||||
const value = value_string.slice(this.allocator);
|
||||
defer {
|
||||
if (!index.isUTF8()) this.allocator.free(key);
|
||||
if (!value_string.isUTF8()) this.allocator.free(value);
|
||||
var right = &expr.value.data.e_binary.right;
|
||||
if (right.data == .e_template) {
|
||||
right.* = try right.data.e_template.toString(this.allocator, right.loc);
|
||||
}
|
||||
|
||||
if (right.data == .e_string) {
|
||||
if (target.ref.eql(exports_ref)) {
|
||||
const key = index.slice(this.allocator);
|
||||
var value_string = right.data.e_string;
|
||||
const value = value_string.slice(this.allocator);
|
||||
defer {
|
||||
if (!index.isUTF8()) this.allocator.free(key);
|
||||
if (!value_string.isUTF8()) this.allocator.free(value);
|
||||
}
|
||||
|
||||
var entry = try this.values.getOrPut(key);
|
||||
var value_to_use: []const u8 = "";
|
||||
if (entry.found_existing) {
|
||||
if (entry.value_ptr.text.len >= value.len) {
|
||||
bun.copy(u8, @constCast(entry.value_ptr.text)[0..value.len], value);
|
||||
if (comptime bun.Environment.allow_assert)
|
||||
if (entry.value_ptr.text.len > value.len)
|
||||
@memset(@constCast(entry.value_ptr.text)[value.len..], undefined);
|
||||
|
||||
value_to_use = entry.value_ptr.text[0..value.len];
|
||||
} else {
|
||||
this.allocator.free(entry.value_ptr.text);
|
||||
value_to_use = try this.allocator.dupe(u8, value);
|
||||
}
|
||||
} else {
|
||||
entry.key_ptr.* = try this.allocator.dupe(u8, key);
|
||||
value_to_use = try this.allocator.dupe(u8, value);
|
||||
}
|
||||
|
||||
entry.value_ptr.* = .{
|
||||
.text = value_to_use,
|
||||
};
|
||||
_ = try this.seen.getOrPut(std.hash.Wyhash.hash(0, key));
|
||||
}
|
||||
const value_clone = try this.allocator.alloc(u8, value.len);
|
||||
bun.copy(u8, value_clone, value);
|
||||
const name_hash = bun.hash(key);
|
||||
try this.values.put(name_hash, value_clone);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -193,19 +258,19 @@ pub const Snapshots = struct {
|
||||
return error.FailedToWriteSnapshotFile;
|
||||
};
|
||||
file.file.close();
|
||||
this.file_buf.clearAndFree();
|
||||
this.file_buf.clearRetainingCapacity();
|
||||
|
||||
var value_itr = this.values.valueIterator();
|
||||
while (value_itr.next()) |value| {
|
||||
this.allocator.free(value.*);
|
||||
this.allocator.free(value.text);
|
||||
}
|
||||
this.values.clearAndFree();
|
||||
|
||||
var count_key_itr = this.counts.keyIterator();
|
||||
while (count_key_itr.next()) |key| {
|
||||
var key_itr = this.values.keyIterator();
|
||||
while (key_itr.next()) |key| {
|
||||
this.allocator.free(key.*);
|
||||
}
|
||||
this.counts.clearAndFree();
|
||||
this.values.clearAndFree();
|
||||
this.seen.clearRetainingCapacity();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,10 +333,10 @@ pub const Snapshots = struct {
|
||||
if (length == 0) {
|
||||
try this.file_buf.appendSlice(file_header);
|
||||
} else {
|
||||
const buf = try this.allocator.alloc(u8, length);
|
||||
_ = try file.file.preadAll(buf, 0);
|
||||
try this.file_buf.appendSlice(buf);
|
||||
this.allocator.free(buf);
|
||||
try this.file_buf.ensureUnusedCapacity(length);
|
||||
var writable = this.file_buf.items.ptr[this.file_buf.items.len..this.file_buf.capacity];
|
||||
const wrote = try file.file.preadAll(writable, 0);
|
||||
this.file_buf.items.len += wrote;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
25
src/bun.zig
25
src/bun.zig
@@ -475,6 +475,18 @@ pub inline fn range(comptime min: anytype, comptime max: anytype) [max - min]usi
|
||||
};
|
||||
}
|
||||
|
||||
pub fn isValgrindEnabled() bool {
|
||||
const valgrind_status = &struct {
|
||||
pub var is_valgrind_enabled: ?bool = null;
|
||||
}.is_valgrind_enabled;
|
||||
|
||||
if (valgrind_status.* == null) {
|
||||
valgrind_status.* = std.valgrind.runningOnValgrind() != 0;
|
||||
}
|
||||
|
||||
return valgrind_status.*.?;
|
||||
}
|
||||
|
||||
pub fn copy(comptime Type: type, dest: []Type, src: []const Type) void {
|
||||
if (comptime Environment.allow_assert) std.debug.assert(dest.len >= src.len);
|
||||
if (@intFromPtr(src.ptr) == @intFromPtr(dest.ptr) or src.len == 0) return;
|
||||
@@ -485,6 +497,19 @@ pub fn copy(comptime Type: type, dest: []Type, src: []const Type) void {
|
||||
std.debug.assert(input.len > 0);
|
||||
std.debug.assert(output.len > 0);
|
||||
|
||||
if (comptime Environment.allow_assert) {
|
||||
if (!isValgrindEnabled()) {
|
||||
var undefined_memory: usize = 0;
|
||||
for (input) |byte| {
|
||||
undefined_memory += @as(usize, @intFromBool(byte == 0xAA));
|
||||
}
|
||||
|
||||
// alignment bytes might be undefined
|
||||
// but if the entire field is undefined, we have a problem
|
||||
std.debug.assert(undefined_memory != input.len);
|
||||
}
|
||||
}
|
||||
|
||||
const does_input_or_output_overlap = (@intFromPtr(input.ptr) < @intFromPtr(output.ptr) and
|
||||
@intFromPtr(input.ptr) + input.len > @intFromPtr(output.ptr)) or
|
||||
(@intFromPtr(output.ptr) < @intFromPtr(input.ptr) and
|
||||
|
||||
@@ -579,7 +579,6 @@ pub const TestCommand = struct {
|
||||
|
||||
var snapshot_file_buf = std.ArrayList(u8).init(ctx.allocator);
|
||||
var snapshot_values = Snapshots.ValuesHashMap.init(ctx.allocator);
|
||||
var snapshot_counts = bun.StringHashMap(usize).init(ctx.allocator);
|
||||
JSC.isBunTest = true;
|
||||
|
||||
var reporter = try ctx.allocator.create(CommandLineReporter);
|
||||
@@ -599,7 +598,6 @@ pub const TestCommand = struct {
|
||||
.update_snapshots = ctx.test_options.update_snapshots,
|
||||
.file_buf = &snapshot_file_buf,
|
||||
.values = &snapshot_values,
|
||||
.counts = &snapshot_counts,
|
||||
},
|
||||
},
|
||||
.callback = undefined,
|
||||
|
||||
@@ -2451,6 +2451,28 @@ pub const E = struct {
|
||||
};
|
||||
};
|
||||
|
||||
pub fn toString(
|
||||
this: *Template,
|
||||
allocator: std.mem.Allocator,
|
||||
loc: logger.Loc,
|
||||
) !Expr {
|
||||
var folded = this.fold(allocator, loc);
|
||||
|
||||
// if it's still a template, that means it has UTF16 chars, so we need to convert it to UTF8
|
||||
if (folded.data == .e_template and folded.data.e_template.head == .cooked) {
|
||||
try folded.data.e_template.head.cooked.toUTF8(allocator);
|
||||
return Expr.init(
|
||||
E.String,
|
||||
folded.data.e_template.head.cooked,
|
||||
loc,
|
||||
);
|
||||
}
|
||||
|
||||
std.debug.assert(folded.data == .e_string);
|
||||
|
||||
return folded;
|
||||
}
|
||||
|
||||
/// "`a${'b'}c`" => "`abc`"
|
||||
pub fn fold(
|
||||
this: *Template,
|
||||
@@ -7197,20 +7219,13 @@ pub const Macro = struct {
|
||||
return out;
|
||||
},
|
||||
|
||||
.JSON => {
|
||||
.toJSON, .JSON => {
|
||||
this.is_top_level = false;
|
||||
// if (console_tag.cell == .JSDate) {
|
||||
// // in the code for printing dates, it never exceeds this amount
|
||||
// var iso_string_buf = this.allocator.alloc(u8, 36) catch unreachable;
|
||||
// var str = JSC.ZigString.init("");
|
||||
// value.jsonStringify(this.global, 0, &str);
|
||||
// var out_buf: []const u8 = std.fmt.bufPrint(iso_string_buf, "{}", .{str}) catch "";
|
||||
// if (out_buf.len > 2) {
|
||||
// // trim the quotes
|
||||
// out_buf = out_buf[1 .. out_buf.len - 1];
|
||||
// }
|
||||
// return Expr.init(E.New, E.New{.target = Expr.init(E.Dot{.target = E}) })
|
||||
// }
|
||||
if (value.get(this.global, "toJSON")) |to_json_func| {
|
||||
if (to_json_func.isCell() and to_json_func.isCallable(this.global.vm())) {
|
||||
return this.run(to_json_func.callWithThis(this.global, value, &.{}));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
.Integer => {
|
||||
|
||||
@@ -3061,50 +3061,13 @@ fn NewPrinter(
|
||||
// Add one outer branch so the inner loop does fewer branches
|
||||
pub fn printUTF8StringEscapedQuotes(p: *Printer, str: string, c: u8) void {
|
||||
switch (c) {
|
||||
'`' => _printUTF8StringEscapedQuotes(p, str, '`'),
|
||||
'"' => _printUTF8StringEscapedQuotes(p, str, '"'),
|
||||
'\'' => _printUTF8StringEscapedQuotes(p, str, '\''),
|
||||
'`' => strings.printUTF8JavaScriptString(p, Printer.print, str, '`', void),
|
||||
'"' => strings.printUTF8JavaScriptString(p, Printer.print, str, '"', void),
|
||||
'\'' => strings.printUTF8JavaScriptString(p, Printer.print, str, '\'', void),
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn _printUTF8StringEscapedQuotes(p: *Printer, str: string, comptime c: u8) void {
|
||||
var utf8 = str;
|
||||
var i: usize = 0;
|
||||
// Walk the string searching for quote characters
|
||||
// Escape any we find
|
||||
// Skip over already-escaped strings
|
||||
var len = utf8.len;
|
||||
while (i < len) {
|
||||
switch (utf8[i]) {
|
||||
'\\' => i += 2,
|
||||
'$' => {
|
||||
if (comptime c == '`') {
|
||||
p.print(utf8[0..i]);
|
||||
p.print("\\$");
|
||||
utf8 = utf8[i + 1 ..];
|
||||
len = utf8.len;
|
||||
i = 0;
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
},
|
||||
c => {
|
||||
p.print(utf8[0..i]);
|
||||
p.print("\\" ++ &[_]u8{c});
|
||||
utf8 = utf8[i + 1 ..];
|
||||
len = utf8.len;
|
||||
i = 0;
|
||||
},
|
||||
|
||||
else => i += 1,
|
||||
}
|
||||
}
|
||||
if (utf8.len > 0) {
|
||||
p.print(utf8);
|
||||
}
|
||||
}
|
||||
|
||||
fn printBindingIdentifierName(p: *Printer, name: string, name_loc: logger.Loc) void {
|
||||
p.addSourceMapping(name_loc);
|
||||
|
||||
|
||||
@@ -50,6 +50,18 @@ pub fn append16(this: *StringBuilder, slice: []const u16) ?[:0]u8 {
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn write(this: *StringBuilder, slice: string) void {
|
||||
if (comptime Environment.allow_assert) {
|
||||
assert(this.len <= this.cap); // didn't count everything
|
||||
assert(this.ptr != null); // must call allocate first
|
||||
}
|
||||
|
||||
bun.copy(u8, this.ptr.?[this.len..this.cap], slice);
|
||||
this.len += slice.len;
|
||||
|
||||
if (comptime Environment.allow_assert) assert(this.len <= this.cap);
|
||||
}
|
||||
|
||||
pub fn append(this: *StringBuilder, slice: string) string {
|
||||
if (comptime Environment.allow_assert) {
|
||||
assert(this.len <= this.cap); // didn't count everything
|
||||
@@ -57,6 +69,7 @@ pub fn append(this: *StringBuilder, slice: string) string {
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -141,6 +154,14 @@ pub fn allocatedSlice(this: *StringBuilder) []u8 {
|
||||
return ptr[0..this.cap];
|
||||
}
|
||||
|
||||
pub fn written(this: *StringBuilder) []u8 {
|
||||
var ptr = this.ptr orelse return &[_]u8{};
|
||||
if (comptime Environment.allow_assert) {
|
||||
assert(this.cap > 0 and this.cap >= this.len);
|
||||
}
|
||||
return ptr[0..this.len];
|
||||
}
|
||||
|
||||
pub fn writable(this: *StringBuilder) []u8 {
|
||||
var ptr = this.ptr orelse return &[_]u8{};
|
||||
if (comptime Environment.allow_assert) {
|
||||
|
||||
@@ -64,15 +64,28 @@ pub fn indexOfAny(slice: string, comptime str: anytype) ?OptionalUsize {
|
||||
if (remaining.len == 0) return null;
|
||||
|
||||
if (comptime Environment.enableSIMD) {
|
||||
while (remaining.len >= ascii_vector_size) {
|
||||
const vec: AsciiVector = remaining[0..ascii_vector_size].*;
|
||||
var cmp: AsciiVectorU1 = @bitCast(vec == @as(AsciiVector, @splat(@as(u8, str[0]))));
|
||||
inline for (str[1..]) |c| {
|
||||
cmp |= @bitCast(vec == @as(AsciiVector, @splat(@as(u8, c))));
|
||||
const vecs: [str.len]AsciiVector = comptime brk: {
|
||||
var out: [str.len]AsciiVector = undefined;
|
||||
for (0..str.len) |i| {
|
||||
out[i] = @splat(str[i]);
|
||||
}
|
||||
|
||||
break :brk out;
|
||||
};
|
||||
|
||||
while (remaining.len >= ascii_vector_size) {
|
||||
const vec: AsciiVector = remaining[0..ascii_vector_size].*;
|
||||
const cmp: AsciiVector = brk: {
|
||||
var out: AsciiVector = @as(AsciiVectorU1, @bitCast(vec == vecs[0]));
|
||||
|
||||
inline for (vecs[1..]) |v| {
|
||||
out |= @as(AsciiVector, @as(AsciiVectorU1, @bitCast(vec == v)));
|
||||
}
|
||||
break :brk out;
|
||||
};
|
||||
|
||||
if (@reduce(.Max, cmp) > 0) {
|
||||
const bitmask = @as(AsciiVectorInt, @bitCast(cmp));
|
||||
const bitmask = @as(AsciiVectorInt, @bitCast(@as(AsciiVectorU1, @bitCast(cmp > @as(AsciiVector, @splat(0))))));
|
||||
const first = @ctz(bitmask);
|
||||
|
||||
return @as(OptionalUsize, @intCast(first + slice.len - remaining.len));
|
||||
@@ -83,7 +96,6 @@ pub fn indexOfAny(slice: string, comptime str: anytype) ?OptionalUsize {
|
||||
|
||||
if (comptime Environment.allow_assert) std.debug.assert(remaining.len < ascii_vector_size);
|
||||
}
|
||||
|
||||
for (remaining, 0..) |c, i| {
|
||||
if (strings.indexOfChar(str, c) != null) {
|
||||
return @as(OptionalUsize, @intCast(i + slice.len - remaining.len));
|
||||
@@ -3766,7 +3778,7 @@ pub fn indexOfNotChar(slice: []const u8, char: u8) ?u32 {
|
||||
while (remaining.len >= ascii_vector_size) {
|
||||
const vec: AsciiVector = remaining[0..ascii_vector_size].*;
|
||||
const cmp = @as(AsciiVector, @splat(char)) != vec;
|
||||
if (@reduce(.Max, @as(AsciiVectorU1, @bitCast(cmp))) > 0) {
|
||||
if (@reduce(.Or, cmp)) {
|
||||
const bitmask = @as(AsciiVectorInt, @bitCast(cmp));
|
||||
const first = @ctz(bitmask);
|
||||
return @as(u32, first) + @as(u32, @intCast(slice.len - remaining.len));
|
||||
@@ -4570,6 +4582,16 @@ pub fn NewCodePointIterator(comptime CodePointType: type, comptime zeroValue: co
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scanUntilNotIdentifier(iter: *Iterator) usize {
|
||||
while (iter.c > -1) {
|
||||
if (!bun.js_lexer.isIdentifierContinue(iter.nextCodepoint())) {
|
||||
return iter.i;
|
||||
}
|
||||
}
|
||||
|
||||
return iter.i;
|
||||
}
|
||||
|
||||
pub fn scanUntilQuotedValueOrEOF(iter: *Iterator, comptime quote: CodePointType) usize {
|
||||
while (iter.c > -1) {
|
||||
if (!switch (iter.nextCodepoint()) {
|
||||
@@ -5048,3 +5070,88 @@ pub fn mustEscapeYAMLString(contents: []const u8) bool {
|
||||
else => true,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn pathContainsNodeModulesFolder(path: []const u8) bool {
|
||||
var remain = path;
|
||||
while (strings.indexOfChar(remain, '/')) |char| {
|
||||
remain = remain[char + 1 ..];
|
||||
if (hasPrefixComptime(remain, "node_modules" ++ std.fs.path.sep_str)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
fn NewJavaScriptStringFormatter(comptime c: u8) type {
|
||||
return struct {
|
||||
const JavaScriptStringFormatter = @This();
|
||||
pub fn format(self: void, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
|
||||
return printUTF8JavaScriptString(self, @TypeOf(writer).print, comptime c, @typeInfo(@TypeOf(writer).print).Fn.return_type.?);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub const JavaScriptStringFormatter = struct {
|
||||
str: []const u8 = "",
|
||||
const fmtSingleQuote = NewJavaScriptStringFormatter('\'').format;
|
||||
const fmtDoubleQuote = NewJavaScriptStringFormatter('"').format;
|
||||
const fmtBacktick = NewJavaScriptStringFormatter('`').format;
|
||||
|
||||
pub fn format(self: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
|
||||
return printUTF8JavaScriptString(writer, @TypeOf(writer).writeAll, self.str, '`', @typeInfo(@TypeOf(@TypeOf(writer).writeAll)).Fn.return_type.?);
|
||||
}
|
||||
};
|
||||
|
||||
pub fn printUTF8JavaScriptString(p: anytype, comptime printFn: anytype, str: string, comptime c: u8, comptime ReturnType: type) ReturnType {
|
||||
var utf8 = str;
|
||||
var i: usize = 0;
|
||||
// Walk the string searching for quote characters
|
||||
// Escape any we find
|
||||
// Skip over already-escaped strings
|
||||
var len = utf8.len;
|
||||
while (i < len) {
|
||||
switch (utf8[i]) {
|
||||
'\\' => i += 2,
|
||||
'$' => {
|
||||
if (comptime c == '`') {
|
||||
if (comptime ReturnType != void) {
|
||||
try printFn(p, utf8[0..i]);
|
||||
try printFn(p, "\\$");
|
||||
} else {
|
||||
printFn(p, utf8[0..i]);
|
||||
printFn(p, "\\$");
|
||||
}
|
||||
|
||||
utf8 = utf8[i + 1 ..];
|
||||
len = utf8.len;
|
||||
i = 0;
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
},
|
||||
c => {
|
||||
if (comptime ReturnType != void) {
|
||||
try printFn(p, utf8[0..i]);
|
||||
try printFn(p, "\\" ++ &[_]u8{c});
|
||||
} else {
|
||||
printFn(p, utf8[0..i]);
|
||||
printFn(p, "\\" ++ &[_]u8{c});
|
||||
}
|
||||
|
||||
utf8 = utf8[i + 1 ..];
|
||||
len = utf8.len;
|
||||
i = 0;
|
||||
},
|
||||
|
||||
else => i += 1,
|
||||
}
|
||||
}
|
||||
if (utf8.len > 0) {
|
||||
if (comptime ReturnType != void) {
|
||||
try printFn(p, utf8);
|
||||
} else {
|
||||
printFn(p, utf8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { it, expect } from "bun:test";
|
||||
import { bunEnv, bunExe } from "harness";
|
||||
import { mkdirSync, rmSync, writeFileSync, readFileSync, mkdtempSync } from "fs";
|
||||
import { mkdirSync, rmSync, writeFileSync, readFileSync, mkdtempSync, realpathSync } from "fs";
|
||||
import { tmpdir } from "os";
|
||||
import { join } from "path";
|
||||
|
||||
@@ -10,7 +10,7 @@ it("macros should not lead to seg faults under any given input", async () => {
|
||||
// this test code follows the same structure as and
|
||||
// is based on the code for testing issue 4893
|
||||
|
||||
const testDir = mkdtempSync(join(tmpdir(), "issue3830-"));
|
||||
let testDir = mkdtempSync(join(tmpdir(), "issue3830-"));
|
||||
|
||||
// Clean up from prior runs if necessary
|
||||
rmSync(testDir, { recursive: true, force: true });
|
||||
@@ -19,6 +19,7 @@ it("macros should not lead to seg faults under any given input", async () => {
|
||||
mkdirSync(testDir, { recursive: true });
|
||||
writeFileSync(join(testDir, "macro.ts"), "export function fn(str) { return str; }");
|
||||
writeFileSync(join(testDir, "index.ts"), "import { fn } from './macro' assert { type: 'macro' };\nfn(`©${''}`);");
|
||||
testDir = realpathSync(testDir);
|
||||
|
||||
const { stderr, exitCode } = Bun.spawnSync({
|
||||
cmd: [bunExe(), "build", "--minify", join(testDir, "index.ts")],
|
||||
@@ -26,7 +27,6 @@ it("macros should not lead to seg faults under any given input", async () => {
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
expect(stderr.toString().trim()).toStartWith('error: "Cannot convert argument type to JS');
|
||||
expect(exitCode).not.toBe(0);
|
||||
expect(stderr.toString().trim().replaceAll(testDir, "[dir]")).toMatchSnapshot();
|
||||
expect(exitCode).toBe(1);
|
||||
});
|
||||
|
||||
9
test/regression/issue/__snapshots__/03830.test.ts.snap
Normal file
9
test/regression/issue/__snapshots__/03830.test.ts.snap
Normal file
@@ -0,0 +1,9 @@
|
||||
// Bun Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`macros should not lead to seg faults under any given input`] = `
|
||||
"error: "Cannot convert argument type to JS" error in macro
|
||||
|
||||
fn(\`©\${''}\`);
|
||||
^
|
||||
[dir]/index.ts:2:1 55"
|
||||
`;
|
||||
Reference in New Issue
Block a user