Compare commits

...

3 Commits

Author SHA1 Message Date
Jarred Sumner
148bed1513 Missing file 2023-11-21 20:42:36 -08:00
Jarred Sumner
5b08ed28a9 Update snapshot.zig 2023-11-21 17:40:50 -08:00
Jarred Sumner
21ca7be630 WIP redo some of snapshots 2023-11-21 17:37:51 -08:00
11 changed files with 343 additions and 181 deletions

View File

@@ -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 {

View File

@@ -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)});

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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,

View File

@@ -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 => {

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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);
}
}
}

View File

@@ -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);
});

View 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"
`;