Files
bun.sh/src/bun.js/node/node_assert.zig
taylor.fish 07cd45deae Refactor Zig imports and file structure (part 1) (#21270)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-07-22 17:51:38 -07:00

129 lines
5.1 KiB
Zig

/// Compare `actual` and `expected`, producing a diff that would turn `actual`
/// into `expected`.
///
/// Lines in the returned diff have the same encoding as `actual` and
/// `expected`. Lines borrow from these inputs, but the diff list itself must
/// be deallocated.
///
/// Use an arena allocator, otherwise this will leak memory.
///
/// ## Invariants
/// If not met, this function will panic.
/// - `actual` and `expected` are alive and have the same encoding.
pub fn myersDiff(
allocator: Allocator,
global: *jsc.JSGlobalObject,
actual: *const BunString,
expected: *const BunString,
// If true, strings that have a trailing comma but are otherwise equal are
// considered equal.
check_comma_disparity: bool,
// split `actual` and `expected` into lines before diffing
lines: bool,
) bun.JSError!jsc.JSValue {
// Short circuit on empty strings. Note that, in release builds where
// assertions are disabled, if `actual` and `expected` are both dead, this
// branch will be hit since dead strings have a length of 0. This should be
// moot since BunStrings with non-zero reference counds should never be
// dead.
if (actual.length() == 0 and expected.length() == 0) {
return try jsc.JSValue.createEmptyArray(global, 0);
}
const actual_encoding = actual.encoding();
const expected_encoding = expected.encoding();
if (lines) {
if (actual_encoding != expected_encoding) {
const actual_utf8 = actual.toUTF8WithoutRef(allocator);
defer actual_utf8.deinit();
const expected_utf8 = expected.toUTF8WithoutRef(allocator);
defer expected_utf8.deinit();
return diffLines(u8, allocator, global, actual_utf8.byteSlice(), expected_utf8.byteSlice(), check_comma_disparity);
}
return switch (actual_encoding) {
.latin1, .utf8 => diffLines(u8, allocator, global, actual.byteSlice(), expected.byteSlice(), check_comma_disparity),
.utf16 => diffLines(u16, allocator, global, actual.utf16(), expected.utf16(), check_comma_disparity),
};
}
if (actual_encoding != expected_encoding) {
const actual_utf8 = actual.toUTF8WithoutRef(allocator);
defer actual_utf8.deinit();
const expected_utf8 = expected.toUTF8WithoutRef(allocator);
defer expected_utf8.deinit();
return diffChars(u8, allocator, global, actual.byteSlice(), expected.byteSlice());
}
return switch (actual_encoding) {
.latin1, .utf8 => diffChars(u8, allocator, global, actual.byteSlice(), expected.byteSlice()),
.utf16 => diffChars(u16, allocator, global, actual.utf16(), expected.utf16()),
};
}
fn diffChars(
comptime T: type,
allocator: Allocator,
global: *jsc.JSGlobalObject,
actual: []const T,
expected: []const T,
) bun.JSError!jsc.JSValue {
const Differ = MyersDiff.Differ(T, .{ .check_comma_disparity = false });
const diff: MyersDiff.DiffList(T) = Differ.diff(allocator, actual, expected) catch |err| return mapDiffError(global, err);
return diffListToJS(T, global, diff);
}
fn diffLines(
comptime T: type,
allocator: Allocator,
global: *jsc.JSGlobalObject,
actual: []const T,
expected: []const T,
check_comma_disparity: bool,
) bun.JSError!jsc.JSValue {
var a = try MyersDiff.split(T, allocator, actual);
defer a.deinit(allocator);
var e = try MyersDiff.split(T, allocator, expected);
defer e.deinit(allocator);
const diff: MyersDiff.DiffList([]const T) = blk: {
if (check_comma_disparity) {
const Differ = MyersDiff.Differ([]const T, .{ .check_comma_disparity = true });
break :blk Differ.diff(allocator, a.items, e.items) catch |err| return mapDiffError(global, err);
} else {
const Differ = MyersDiff.Differ([]const T, .{ .check_comma_disparity = false });
break :blk Differ.diff(allocator, a.items, e.items) catch |err| return mapDiffError(global, err);
}
};
return diffListToJS([]const T, global, diff);
}
fn diffListToJS(comptime T: type, global: *jsc.JSGlobalObject, diff_list: MyersDiff.DiffList(T)) bun.JSError!jsc.JSValue {
var array = try jsc.JSValue.createEmptyArray(global, diff_list.items.len);
for (diff_list.items, 0..) |*line, i| {
try array.putIndex(global, @truncate(i), (try jsc.JSObject.createNullProto(line.*, global)).toJS());
}
return array;
}
fn mapDiffError(global: *jsc.JSGlobalObject, err: MyersDiff.Error) bun.JSError {
return switch (err) {
error.OutOfMemory => error.OutOfMemory,
error.DiffTooLarge => global.throwInvalidArguments("Diffing these two values would create a string that is too large. If this was intentional, please open a bug report on GitHub.", .{}),
error.InputsTooLarge => global.throwInvalidArguments("Input strings are too large to diff. Please open a bug report on GitHub.", .{}),
};
}
const MyersDiff = @import("./assert/myers_diff.zig");
const std = @import("std");
const Allocator = std.mem.Allocator;
const bun = @import("bun");
const BunString = bun.String;
const jsc = bun.jsc;
const JSValue = jsc.JSValue;