mirror of
https://github.com/denoland/deno.git
synced 2025-03-03 17:34:47 -05:00
fix(ext/node): use primordials in ext/node/polyfills/_util (#21444)
This commit is contained in:
parent
3a74fa60ca
commit
b24356d9b9
7 changed files with 344 additions and 167 deletions
|
@ -21,12 +21,20 @@
|
||||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
// TODO(petamoriken): enable prefer-primordials for node polyfills
|
|
||||||
// deno-lint-ignore-file prefer-primordials
|
|
||||||
|
|
||||||
// These are simplified versions of the "real" errors in Node.
|
// These are simplified versions of the "real" errors in Node.
|
||||||
|
|
||||||
|
import { primordials } from "ext:core/mod.js";
|
||||||
import { nextTick } from "ext:deno_node/_next_tick.ts";
|
import { nextTick } from "ext:deno_node/_next_tick.ts";
|
||||||
|
const {
|
||||||
|
ArrayPrototypePop,
|
||||||
|
Error,
|
||||||
|
FunctionPrototypeApply,
|
||||||
|
FunctionPrototypeBind,
|
||||||
|
ObjectDefineProperties,
|
||||||
|
ObjectGetOwnPropertyDescriptors,
|
||||||
|
PromisePrototypeThen,
|
||||||
|
TypeError,
|
||||||
|
} = primordials;
|
||||||
|
|
||||||
class NodeFalsyValueRejectionError extends Error {
|
class NodeFalsyValueRejectionError extends Error {
|
||||||
public reason: unknown;
|
public reason: unknown;
|
||||||
|
@ -98,25 +106,26 @@ function callbackify<ResultT>(
|
||||||
}
|
}
|
||||||
|
|
||||||
const callbackified = function (this: unknown, ...args: unknown[]) {
|
const callbackified = function (this: unknown, ...args: unknown[]) {
|
||||||
const maybeCb = args.pop();
|
const maybeCb = ArrayPrototypePop(args);
|
||||||
if (typeof maybeCb !== "function") {
|
if (typeof maybeCb !== "function") {
|
||||||
throw new NodeInvalidArgTypeError("last");
|
throw new NodeInvalidArgTypeError("last");
|
||||||
}
|
}
|
||||||
const cb = (...args: unknown[]) => {
|
const cb = (...args: unknown[]) => {
|
||||||
maybeCb.apply(this, args);
|
FunctionPrototypeApply(maybeCb, this, args);
|
||||||
};
|
};
|
||||||
original.apply(this, args).then(
|
PromisePrototypeThen(
|
||||||
|
FunctionPrototypeApply(this, args),
|
||||||
(ret: unknown) => {
|
(ret: unknown) => {
|
||||||
nextTick(cb.bind(this, null, ret));
|
nextTick(FunctionPrototypeBind(cb, this, null, ret));
|
||||||
},
|
},
|
||||||
(rej: unknown) => {
|
(rej: unknown) => {
|
||||||
rej = rej || new NodeFalsyValueRejectionError(rej);
|
rej = rej || new NodeFalsyValueRejectionError(rej);
|
||||||
nextTick(cb.bind(this, rej));
|
nextTick(FunctionPrototypeBind(cb, this, rej));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const descriptors = Object.getOwnPropertyDescriptors(original);
|
const descriptors = ObjectGetOwnPropertyDescriptors(original);
|
||||||
// It is possible to manipulate a functions `length` or `name` property. This
|
// It is possible to manipulate a functions `length` or `name` property. This
|
||||||
// guards against the manipulation.
|
// guards against the manipulation.
|
||||||
if (typeof descriptors.length.value === "number") {
|
if (typeof descriptors.length.value === "number") {
|
||||||
|
@ -125,7 +134,7 @@ function callbackify<ResultT>(
|
||||||
if (typeof descriptors.name.value === "string") {
|
if (typeof descriptors.name.value === "string") {
|
||||||
descriptors.name.value += "Callbackified";
|
descriptors.name.value += "Callbackified";
|
||||||
}
|
}
|
||||||
Object.defineProperties(callbackified, descriptors);
|
ObjectDefineProperties(callbackified, descriptors);
|
||||||
return callbackified;
|
return callbackified;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
// TODO(petamoriken): enable prefer-primordials for node polyfills
|
import { primordials } from "ext:core/mod.js";
|
||||||
// deno-lint-ignore-file prefer-primordials
|
const {
|
||||||
|
Error,
|
||||||
|
} = primordials;
|
||||||
|
|
||||||
/** Assertion error class for node compat layer's internal code. */
|
/** Assertion error class for node compat layer's internal code. */
|
||||||
export class NodeCompatAssertionError extends Error {
|
export class NodeCompatAssertionError extends Error {
|
||||||
|
|
|
@ -2,8 +2,12 @@
|
||||||
// This module is vendored from std/async/delay.ts
|
// This module is vendored from std/async/delay.ts
|
||||||
// (with some modifications)
|
// (with some modifications)
|
||||||
|
|
||||||
// TODO(petamoriken): enable prefer-primordials for node polyfills
|
import { primordials } from "ext:core/mod.js";
|
||||||
// deno-lint-ignore-file prefer-primordials
|
import { clearTimeout, setTimeout } from "ext:deno_web/02_timers.js";
|
||||||
|
const {
|
||||||
|
Promise,
|
||||||
|
PromiseReject,
|
||||||
|
} = primordials;
|
||||||
|
|
||||||
/** Resolve a Promise after a given amount of milliseconds. */
|
/** Resolve a Promise after a given amount of milliseconds. */
|
||||||
export function delay(
|
export function delay(
|
||||||
|
@ -12,12 +16,12 @@ export function delay(
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { signal } = options;
|
const { signal } = options;
|
||||||
if (signal?.aborted) {
|
if (signal?.aborted) {
|
||||||
return Promise.reject(new DOMException("Delay was aborted.", "AbortError"));
|
return PromiseReject(signal.reason);
|
||||||
}
|
}
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const abort = () => {
|
const abort = () => {
|
||||||
clearTimeout(i);
|
clearTimeout(i);
|
||||||
reject(new DOMException("Delay was aborted.", "AbortError"));
|
reject(signal!.reason);
|
||||||
};
|
};
|
||||||
const done = () => {
|
const done = () => {
|
||||||
signal?.removeEventListener("abort", abort);
|
signal?.removeEventListener("abort", abort);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
const { ops } = globalThis.__bootstrap.core;
|
import { core } from "ext:core/mod.js";
|
||||||
|
const ops = core.ops;
|
||||||
|
|
||||||
export type OSType = "windows" | "linux" | "darwin" | "freebsd" | "openbsd";
|
export type OSType = "windows" | "linux" | "darwin" | "freebsd" | "openbsd";
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,43 @@
|
||||||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||||
// vendored from std/assert/mod.ts
|
// vendored from std/assert/mod.ts
|
||||||
|
|
||||||
// TODO(petamoriken): enable prefer-primordials for node polyfills
|
import { primordials } from "ext:core/mod.js";
|
||||||
// deno-lint-ignore-file prefer-primordials
|
import { URLPrototype } from "ext:deno_url/00_url.js";
|
||||||
|
|
||||||
import { red } from "ext:deno_node/_util/std_fmt_colors.ts";
|
import { red } from "ext:deno_node/_util/std_fmt_colors.ts";
|
||||||
import {
|
import {
|
||||||
buildMessage,
|
buildMessage,
|
||||||
diff,
|
diff,
|
||||||
diffstr,
|
diffstr,
|
||||||
} from "ext:deno_node/_util/std_testing_diff.ts";
|
} from "ext:deno_node/_util/std_testing_diff.ts";
|
||||||
|
const {
|
||||||
|
DatePrototype,
|
||||||
|
ArrayPrototypeJoin,
|
||||||
|
ArrayPrototypeMap,
|
||||||
|
DatePrototypeGetTime,
|
||||||
|
Error,
|
||||||
|
NumberIsNaN,
|
||||||
|
Object,
|
||||||
|
ObjectIs,
|
||||||
|
ObjectKeys,
|
||||||
|
ObjectPrototypeIsPrototypeOf,
|
||||||
|
ReflectHas,
|
||||||
|
ReflectOwnKeys,
|
||||||
|
RegExpPrototype,
|
||||||
|
RegExpPrototypeTest,
|
||||||
|
SafeMap,
|
||||||
|
SafeRegExp,
|
||||||
|
String,
|
||||||
|
StringPrototypeReplace,
|
||||||
|
StringPrototypeSplit,
|
||||||
|
SymbolIterator,
|
||||||
|
TypeError,
|
||||||
|
WeakMapPrototype,
|
||||||
|
WeakSetPrototype,
|
||||||
|
WeakRefPrototype,
|
||||||
|
WeakRefPrototypeDeref,
|
||||||
|
} = primordials;
|
||||||
|
|
||||||
|
const FORMAT_PATTERN = new SafeRegExp(/(?=["\\])/g);
|
||||||
|
|
||||||
/** Converts the input into a string. Objects, Sets and Maps are sorted so as to
|
/** Converts the input into a string. Objects, Sets and Maps are sorted so as to
|
||||||
* make tests less flaky */
|
* make tests less flaky */
|
||||||
|
@ -26,7 +54,7 @@ export function format(v: unknown): string {
|
||||||
// getters should be true in assertEquals.
|
// getters should be true in assertEquals.
|
||||||
getters: true,
|
getters: true,
|
||||||
})
|
})
|
||||||
: `"${String(v).replace(/(?=["\\])/g, "\\")}"`;
|
: `"${StringPrototypeReplace(String(v), FORMAT_PATTERN, "\\")}"`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CAN_NOT_DISPLAY = "[Cannot display]";
|
const CAN_NOT_DISPLAY = "[Cannot display]";
|
||||||
|
@ -38,56 +66,75 @@ export class AssertionError extends Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isKeyedCollection(x: unknown): x is Set<unknown> {
|
function isKeyedCollection(
|
||||||
return [Symbol.iterator, "size"].every((k) => k in (x as Set<unknown>));
|
x: unknown,
|
||||||
|
): x is { size: number; entries(): Iterable<[unknown, unknown]> } {
|
||||||
|
return ReflectHas(x, SymbolIterator) && ReflectHas(x, "size");
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Deep equality comparison used in assertions */
|
/** Deep equality comparison used in assertions */
|
||||||
export function equal(c: unknown, d: unknown): boolean {
|
export function equal(c: unknown, d: unknown): boolean {
|
||||||
const seen = new Map();
|
const seen = new SafeMap();
|
||||||
return (function compare(a: unknown, b: unknown): boolean {
|
return (function compare(a: unknown, b: unknown): boolean {
|
||||||
// Have to render RegExp & Date for string comparison
|
// Have to render RegExp & Date for string comparison
|
||||||
// unless it's mistreated as object
|
// unless it's mistreated as object
|
||||||
if (
|
if (
|
||||||
a &&
|
a &&
|
||||||
b &&
|
b &&
|
||||||
((a instanceof RegExp && b instanceof RegExp) ||
|
((ObjectPrototypeIsPrototypeOf(RegExpPrototype, a) &&
|
||||||
(a instanceof URL && b instanceof URL))
|
ObjectPrototypeIsPrototypeOf(RegExpPrototype, b)) ||
|
||||||
|
(ObjectPrototypeIsPrototypeOf(URLPrototype, a) &&
|
||||||
|
ObjectPrototypeIsPrototypeOf(URLPrototype, b)))
|
||||||
) {
|
) {
|
||||||
return String(a) === String(b);
|
return String(a) === String(b);
|
||||||
}
|
}
|
||||||
if (a instanceof Date && b instanceof Date) {
|
if (
|
||||||
const aTime = a.getTime();
|
ObjectPrototypeIsPrototypeOf(DatePrototype, a) &&
|
||||||
const bTime = b.getTime();
|
ObjectPrototypeIsPrototypeOf(DatePrototype, b)
|
||||||
|
) {
|
||||||
|
const aTime = DatePrototypeGetTime(a);
|
||||||
|
const bTime = DatePrototypeGetTime(b);
|
||||||
// Check for NaN equality manually since NaN is not
|
// Check for NaN equality manually since NaN is not
|
||||||
// equal to itself.
|
// equal to itself.
|
||||||
if (Number.isNaN(aTime) && Number.isNaN(bTime)) {
|
if (NumberIsNaN(aTime) && NumberIsNaN(bTime)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return aTime === bTime;
|
return aTime === bTime;
|
||||||
}
|
}
|
||||||
if (typeof a === "number" && typeof b === "number") {
|
if (typeof a === "number" && typeof b === "number") {
|
||||||
return Number.isNaN(a) && Number.isNaN(b) || a === b;
|
return NumberIsNaN(a) && NumberIsNaN(b) || a === b;
|
||||||
}
|
}
|
||||||
if (Object.is(a, b)) {
|
if (ObjectIs(a, b)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (a && typeof a === "object" && b && typeof b === "object") {
|
if (a && typeof a === "object" && b && typeof b === "object") {
|
||||||
if (a && b && !constructorsEqual(a, b)) {
|
if (a && b && !constructorsEqual(a, b)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (a instanceof WeakMap || b instanceof WeakMap) {
|
if (
|
||||||
if (!(a instanceof WeakMap && b instanceof WeakMap)) return false;
|
ObjectPrototypeIsPrototypeOf(WeakMapPrototype, a) ||
|
||||||
|
ObjectPrototypeIsPrototypeOf(WeakMapPrototype, b)
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
!(ObjectPrototypeIsPrototypeOf(WeakMapPrototype, a) &&
|
||||||
|
ObjectPrototypeIsPrototypeOf(WeakMapPrototype, b))
|
||||||
|
) return false;
|
||||||
throw new TypeError("cannot compare WeakMap instances");
|
throw new TypeError("cannot compare WeakMap instances");
|
||||||
}
|
}
|
||||||
if (a instanceof WeakSet || b instanceof WeakSet) {
|
if (
|
||||||
if (!(a instanceof WeakSet && b instanceof WeakSet)) return false;
|
ObjectPrototypeIsPrototypeOf(WeakSetPrototype, a) ||
|
||||||
|
ObjectPrototypeIsPrototypeOf(WeakSetPrototype, b)
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
!(ObjectPrototypeIsPrototypeOf(WeakSetPrototype, a) &&
|
||||||
|
ObjectPrototypeIsPrototypeOf(WeakSetPrototype, b))
|
||||||
|
) return false;
|
||||||
throw new TypeError("cannot compare WeakSet instances");
|
throw new TypeError("cannot compare WeakSet instances");
|
||||||
}
|
}
|
||||||
if (seen.get(a) === b) {
|
if (seen.get(a) === b) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (Object.keys(a || {}).length !== Object.keys(b || {}).length) {
|
if (ObjectKeys(a || {}).length !== ObjectKeys(b || {}).length) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
seen.set(a, b);
|
seen.set(a, b);
|
||||||
|
@ -98,7 +145,10 @@ export function equal(c: unknown, d: unknown): boolean {
|
||||||
|
|
||||||
let unmatchedEntries = a.size;
|
let unmatchedEntries = a.size;
|
||||||
|
|
||||||
|
// TODO(petamoriken): use primordials
|
||||||
|
// deno-lint-ignore prefer-primordials
|
||||||
for (const [aKey, aValue] of a.entries()) {
|
for (const [aKey, aValue] of a.entries()) {
|
||||||
|
// deno-lint-ignore prefer-primordials
|
||||||
for (const [bKey, bValue] of b.entries()) {
|
for (const [bKey, bValue] of b.entries()) {
|
||||||
/* Given that Map keys can be references, we need
|
/* Given that Map keys can be references, we need
|
||||||
* to ensure that they are also deeply equal */
|
* to ensure that they are also deeply equal */
|
||||||
|
@ -111,27 +161,34 @@ export function equal(c: unknown, d: unknown): boolean {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return unmatchedEntries === 0;
|
return unmatchedEntries === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const merged = { ...a, ...b };
|
const merged = { ...a, ...b };
|
||||||
for (
|
const keys = ReflectOwnKeys(merged);
|
||||||
const key of [
|
for (let i = 0; i < keys.length; ++i) {
|
||||||
...Object.getOwnPropertyNames(merged),
|
const key = keys[i];
|
||||||
...Object.getOwnPropertySymbols(merged),
|
|
||||||
]
|
|
||||||
) {
|
|
||||||
type Key = keyof typeof merged;
|
type Key = keyof typeof merged;
|
||||||
if (!compare(a && a[key as Key], b && b[key as Key])) {
|
if (!compare(a && a[key as Key], b && b[key as Key])) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (((key in a) && (!(key in b))) || ((key in b) && (!(key in a)))) {
|
if (
|
||||||
|
(ReflectHas(a, key) && !ReflectHas(b, key)) ||
|
||||||
|
(ReflectHas(b, key) && !ReflectHas(a, key))
|
||||||
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (a instanceof WeakRef || b instanceof WeakRef) {
|
|
||||||
if (!(a instanceof WeakRef && b instanceof WeakRef)) return false;
|
if (
|
||||||
return compare(a.deref(), b.deref());
|
ObjectPrototypeIsPrototypeOf(WeakRefPrototype, a) ||
|
||||||
|
ObjectPrototypeIsPrototypeOf(WeakRefPrototype, b)
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
!(ObjectPrototypeIsPrototypeOf(WeakRefPrototype, a) &&
|
||||||
|
ObjectPrototypeIsPrototypeOf(WeakRefPrototype, b))
|
||||||
|
) return false;
|
||||||
|
return compare(WeakRefPrototypeDeref(a), WeakRefPrototypeDeref(b));
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -166,8 +223,14 @@ export function assertEquals<T>(actual: T, expected: T, msg?: string) {
|
||||||
(typeof expected === "string");
|
(typeof expected === "string");
|
||||||
const diffResult = stringDiff
|
const diffResult = stringDiff
|
||||||
? diffstr(actual as string, expected as string)
|
? diffstr(actual as string, expected as string)
|
||||||
: diff(actualString.split("\n"), expectedString.split("\n"));
|
: diff(
|
||||||
const diffMsg = buildMessage(diffResult, { stringDiff }).join("\n");
|
StringPrototypeSplit(actualString, "\n"),
|
||||||
|
StringPrototypeSplit(expectedString, "\n"),
|
||||||
|
);
|
||||||
|
const diffMsg = ArrayPrototypeJoin(
|
||||||
|
buildMessage(diffResult, { stringDiff }),
|
||||||
|
"\n",
|
||||||
|
);
|
||||||
message = `Values are not equal:\n${diffMsg}`;
|
message = `Values are not equal:\n${diffMsg}`;
|
||||||
} catch {
|
} catch {
|
||||||
message = `\n${red(red(CAN_NOT_DISPLAY))} + \n\n`;
|
message = `\n${red(red(CAN_NOT_DISPLAY))} + \n\n`;
|
||||||
|
@ -209,7 +272,7 @@ export function assertStrictEquals<T>(
|
||||||
expected: T,
|
expected: T,
|
||||||
msg?: string,
|
msg?: string,
|
||||||
): asserts actual is T {
|
): asserts actual is T {
|
||||||
if (Object.is(actual, expected)) {
|
if (ObjectIs(actual, expected)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,10 +285,13 @@ export function assertStrictEquals<T>(
|
||||||
const expectedString = format(expected);
|
const expectedString = format(expected);
|
||||||
|
|
||||||
if (actualString === expectedString) {
|
if (actualString === expectedString) {
|
||||||
const withOffset = actualString
|
const withOffset = ArrayPrototypeJoin(
|
||||||
.split("\n")
|
ArrayPrototypeMap(
|
||||||
.map((l) => ` ${l}`)
|
StringPrototypeSplit(actualString, "\n"),
|
||||||
.join("\n");
|
(l: string) => ` ${l}`,
|
||||||
|
),
|
||||||
|
"\n",
|
||||||
|
);
|
||||||
message =
|
message =
|
||||||
`Values have the same structure but are not reference-equal:\n\n${
|
`Values have the same structure but are not reference-equal:\n\n${
|
||||||
red(withOffset)
|
red(withOffset)
|
||||||
|
@ -236,8 +302,14 @@ export function assertStrictEquals<T>(
|
||||||
(typeof expected === "string");
|
(typeof expected === "string");
|
||||||
const diffResult = stringDiff
|
const diffResult = stringDiff
|
||||||
? diffstr(actual as string, expected as string)
|
? diffstr(actual as string, expected as string)
|
||||||
: diff(actualString.split("\n"), expectedString.split("\n"));
|
: diff(
|
||||||
const diffMsg = buildMessage(diffResult, { stringDiff }).join("\n");
|
StringPrototypeSplit(actualString, "\n"),
|
||||||
|
StringPrototypeSplit(expectedString, "\n"),
|
||||||
|
);
|
||||||
|
const diffMsg = ArrayPrototypeJoin(
|
||||||
|
buildMessage(diffResult, { stringDiff }),
|
||||||
|
"\n",
|
||||||
|
);
|
||||||
message = `Values are not strictly equal:\n${diffMsg}`;
|
message = `Values are not strictly equal:\n${diffMsg}`;
|
||||||
} catch {
|
} catch {
|
||||||
message = `\n${CAN_NOT_DISPLAY} + \n\n`;
|
message = `\n${CAN_NOT_DISPLAY} + \n\n`;
|
||||||
|
@ -255,7 +327,7 @@ export function assertNotStrictEquals<T>(
|
||||||
expected: T,
|
expected: T,
|
||||||
msg?: string,
|
msg?: string,
|
||||||
) {
|
) {
|
||||||
if (!Object.is(actual, expected)) {
|
if (!ObjectIs(actual, expected)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,10 +340,11 @@ export function assertNotStrictEquals<T>(
|
||||||
* then throw. */
|
* then throw. */
|
||||||
export function assertMatch(
|
export function assertMatch(
|
||||||
actual: string,
|
actual: string,
|
||||||
|
// deno-lint-ignore prefer-primordials
|
||||||
expected: RegExp,
|
expected: RegExp,
|
||||||
msg?: string,
|
msg?: string,
|
||||||
) {
|
) {
|
||||||
if (!expected.test(actual)) {
|
if (!RegExpPrototypeTest(expected, actual)) {
|
||||||
if (!msg) {
|
if (!msg) {
|
||||||
msg = `actual: "${actual}" expected to match: "${expected}"`;
|
msg = `actual: "${actual}" expected to match: "${expected}"`;
|
||||||
}
|
}
|
||||||
|
@ -283,10 +356,11 @@ export function assertMatch(
|
||||||
* then throw. */
|
* then throw. */
|
||||||
export function assertNotMatch(
|
export function assertNotMatch(
|
||||||
actual: string,
|
actual: string,
|
||||||
|
// deno-lint-ignore prefer-primordials
|
||||||
expected: RegExp,
|
expected: RegExp,
|
||||||
msg?: string,
|
msg?: string,
|
||||||
) {
|
) {
|
||||||
if (expected.test(actual)) {
|
if (RegExpPrototypeTest(expected, actual)) {
|
||||||
if (!msg) {
|
if (!msg) {
|
||||||
msg = `actual: "${actual}" expected to not match: "${expected}"`;
|
msg = `actual: "${actual}" expected to not match: "${expected}"`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,15 @@
|
||||||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||||
// This file is vendored from std/fmt/colors.ts
|
// This file is vendored from std/fmt/colors.ts
|
||||||
|
|
||||||
// TODO(petamoriken): enable prefer-primordials for node polyfills
|
import { primordials } from "ext:core/mod.js";
|
||||||
// deno-lint-ignore-file prefer-primordials
|
const {
|
||||||
|
ArrayPrototypeJoin,
|
||||||
|
MathMax,
|
||||||
|
MathMin,
|
||||||
|
MathTrunc,
|
||||||
|
SafeRegExp,
|
||||||
|
StringPrototypeReplace,
|
||||||
|
} = primordials;
|
||||||
|
|
||||||
// TODO(kt3k): Initialize this at the start of runtime
|
// TODO(kt3k): Initialize this at the start of runtime
|
||||||
// based on Deno.noColor
|
// based on Deno.noColor
|
||||||
|
@ -11,6 +18,7 @@ const noColor = false;
|
||||||
interface Code {
|
interface Code {
|
||||||
open: string;
|
open: string;
|
||||||
close: string;
|
close: string;
|
||||||
|
// deno-lint-ignore prefer-primordials
|
||||||
regexp: RegExp;
|
regexp: RegExp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,9 +55,9 @@ export function getColorEnabled(): boolean {
|
||||||
*/
|
*/
|
||||||
function code(open: number[], close: number): Code {
|
function code(open: number[], close: number): Code {
|
||||||
return {
|
return {
|
||||||
open: `\x1b[${open.join(";")}m`,
|
open: `\x1b[${ArrayPrototypeJoin(open, ";")}m`,
|
||||||
close: `\x1b[${close}m`,
|
close: `\x1b[${close}m`,
|
||||||
regexp: new RegExp(`\\x1b\\[${close}m`, "g"),
|
regexp: new SafeRegExp(`\\x1b\\[${close}m`, "g"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,7 +68,9 @@ function code(open: number[], close: number): Code {
|
||||||
*/
|
*/
|
||||||
function run(str: string, code: Code): string {
|
function run(str: string, code: Code): string {
|
||||||
return enabled
|
return enabled
|
||||||
? `${code.open}${str.replace(code.regexp, code.open)}${code.close}`
|
? `${code.open}${
|
||||||
|
StringPrototypeReplace(str, code.regexp, code.open)
|
||||||
|
}${code.close}`
|
||||||
: str;
|
: str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -401,7 +411,7 @@ export function bgBrightWhite(str: string): string {
|
||||||
* @param min number to truncate from
|
* @param min number to truncate from
|
||||||
*/
|
*/
|
||||||
function clampAndTruncate(n: number, max = 255, min = 0): number {
|
function clampAndTruncate(n: number, max = 255, min = 0): number {
|
||||||
return Math.trunc(Math.max(Math.min(n, max), min));
|
return MathTrunc(MathMax(MathMin(n, max), min));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -505,11 +515,11 @@ export function bgRgb24(str: string, color: number | Rgb): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/chalk/ansi-regex/blob/02fa893d619d3da85411acc8fd4e2eea0e95a9d9/index.js
|
// https://github.com/chalk/ansi-regex/blob/02fa893d619d3da85411acc8fd4e2eea0e95a9d9/index.js
|
||||||
const ANSI_PATTERN = new RegExp(
|
const ANSI_PATTERN = new SafeRegExp(
|
||||||
[
|
ArrayPrototypeJoin([
|
||||||
"[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)",
|
"[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)",
|
||||||
"(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))",
|
"(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))",
|
||||||
].join("|"),
|
], "|"),
|
||||||
"g",
|
"g",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -518,5 +528,5 @@ const ANSI_PATTERN = new RegExp(
|
||||||
* @param string to remove ANSI escape codes from
|
* @param string to remove ANSI escape codes from
|
||||||
*/
|
*/
|
||||||
export function stripColor(string: string): string {
|
export function stripColor(string: string): string {
|
||||||
return string.replace(ANSI_PATTERN, "");
|
return StringPrototypeReplace(string, ANSI_PATTERN, "");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||||
// This file was vendored from std/testing/_diff.ts
|
// This file was vendored from std/testing/_diff.ts
|
||||||
|
|
||||||
// TODO(petamoriken): enable prefer-primordials for node polyfills
|
import { primordials } from "ext:core/mod.js";
|
||||||
// deno-lint-ignore-file prefer-primordials
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
bgGreen,
|
bgGreen,
|
||||||
bgRed,
|
bgRed,
|
||||||
|
@ -13,6 +11,30 @@ import {
|
||||||
red,
|
red,
|
||||||
white,
|
white,
|
||||||
} from "ext:deno_node/_util/std_fmt_colors.ts";
|
} from "ext:deno_node/_util/std_fmt_colors.ts";
|
||||||
|
const {
|
||||||
|
ArrayFrom,
|
||||||
|
ArrayPrototypeFilter,
|
||||||
|
ArrayPrototypeForEach,
|
||||||
|
ArrayPrototypeJoin,
|
||||||
|
ArrayPrototypeMap,
|
||||||
|
ArrayPrototypePop,
|
||||||
|
ArrayPrototypePush,
|
||||||
|
ArrayPrototypePushApply,
|
||||||
|
ArrayPrototypeReverse,
|
||||||
|
ArrayPrototypeShift,
|
||||||
|
ArrayPrototypeSlice,
|
||||||
|
ArrayPrototypeSplice,
|
||||||
|
ArrayPrototypeSome,
|
||||||
|
ArrayPrototypeUnshift,
|
||||||
|
SafeArrayIterator,
|
||||||
|
SafeRegExp,
|
||||||
|
StringPrototypeSplit,
|
||||||
|
StringPrototypeReplace,
|
||||||
|
StringPrototypeTrim,
|
||||||
|
MathMin,
|
||||||
|
ObjectFreeze,
|
||||||
|
Uint32Array,
|
||||||
|
} = primordials;
|
||||||
|
|
||||||
interface FarthestPoint {
|
interface FarthestPoint {
|
||||||
y: number;
|
y: number;
|
||||||
|
@ -28,7 +50,7 @@ export enum DiffType {
|
||||||
export interface DiffResult<T> {
|
export interface DiffResult<T> {
|
||||||
type: DiffType;
|
type: DiffType;
|
||||||
value: T;
|
value: T;
|
||||||
details?: Array<DiffResult<T>>;
|
details?: DiffResult<T>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const REMOVED = 1;
|
const REMOVED = 1;
|
||||||
|
@ -38,11 +60,11 @@ const ADDED = 3;
|
||||||
function createCommon<T>(A: T[], B: T[], reverse?: boolean): T[] {
|
function createCommon<T>(A: T[], B: T[], reverse?: boolean): T[] {
|
||||||
const common = [];
|
const common = [];
|
||||||
if (A.length === 0 || B.length === 0) return [];
|
if (A.length === 0 || B.length === 0) return [];
|
||||||
for (let i = 0; i < Math.min(A.length, B.length); i += 1) {
|
for (let i = 0; i < MathMin(A.length, B.length); i += 1) {
|
||||||
if (
|
if (
|
||||||
A[reverse ? A.length - i - 1 : i] === B[reverse ? B.length - i - 1 : i]
|
A[reverse ? A.length - i - 1 : i] === B[reverse ? B.length - i - 1 : i]
|
||||||
) {
|
) {
|
||||||
common.push(A[reverse ? A.length - i - 1 : i]);
|
ArrayPrototypePush(common, A[reverse ? A.length - i - 1 : i]);
|
||||||
} else {
|
} else {
|
||||||
return common;
|
return common;
|
||||||
}
|
}
|
||||||
|
@ -55,44 +77,56 @@ function createCommon<T>(A: T[], B: T[], reverse?: boolean): T[] {
|
||||||
* @param A Actual value
|
* @param A Actual value
|
||||||
* @param B Expected value
|
* @param B Expected value
|
||||||
*/
|
*/
|
||||||
export function diff<T>(A: T[], B: T[]): Array<DiffResult<T>> {
|
export function diff<T>(A: T[], B: T[]): DiffResult<T>[] {
|
||||||
const prefixCommon = createCommon(A, B);
|
const prefixCommon = createCommon(A, B);
|
||||||
const suffixCommon = createCommon(
|
const suffixCommon = ArrayPrototypeReverse(createCommon(
|
||||||
A.slice(prefixCommon.length),
|
ArrayPrototypeSlice(A, prefixCommon.length),
|
||||||
B.slice(prefixCommon.length),
|
ArrayPrototypeSlice(B, prefixCommon.length),
|
||||||
true,
|
true,
|
||||||
).reverse();
|
));
|
||||||
A = suffixCommon.length
|
A = suffixCommon.length
|
||||||
? A.slice(prefixCommon.length, -suffixCommon.length)
|
? ArrayPrototypeSlice(A, prefixCommon.length, -suffixCommon.length)
|
||||||
: A.slice(prefixCommon.length);
|
: ArrayPrototypeSlice(A, prefixCommon.length);
|
||||||
B = suffixCommon.length
|
B = suffixCommon.length
|
||||||
? B.slice(prefixCommon.length, -suffixCommon.length)
|
? ArrayPrototypeSlice(B, prefixCommon.length, -suffixCommon.length)
|
||||||
: B.slice(prefixCommon.length);
|
: ArrayPrototypeSlice(B, prefixCommon.length);
|
||||||
const swapped = B.length > A.length;
|
const swapped = B.length > A.length;
|
||||||
[A, B] = swapped ? [B, A] : [A, B];
|
if (swapped) {
|
||||||
|
const temp = A;
|
||||||
|
A = B;
|
||||||
|
B = temp;
|
||||||
|
}
|
||||||
const M = A.length;
|
const M = A.length;
|
||||||
const N = B.length;
|
const N = B.length;
|
||||||
if (!M && !N && !suffixCommon.length && !prefixCommon.length) return [];
|
if (
|
||||||
if (!N) {
|
M === 0 && N === 0 && suffixCommon.length === 0 && prefixCommon.length === 0
|
||||||
|
) return [];
|
||||||
|
if (N === 0) {
|
||||||
return [
|
return [
|
||||||
...prefixCommon.map(
|
...new SafeArrayIterator(
|
||||||
(c): DiffResult<typeof c> => ({ type: DiffType.common, value: c }),
|
ArrayPrototypeMap(
|
||||||
|
prefixCommon,
|
||||||
|
(c: T): DiffResult<typeof c> => ({ type: DiffType.common, value: c }),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
...A.map(
|
...new SafeArrayIterator(
|
||||||
(a): DiffResult<typeof a> => ({
|
ArrayPrototypeMap(A, (a: T): DiffResult<typeof a> => ({
|
||||||
type: swapped ? DiffType.added : DiffType.removed,
|
type: swapped ? DiffType.added : DiffType.removed,
|
||||||
value: a,
|
value: a,
|
||||||
}),
|
})),
|
||||||
),
|
),
|
||||||
...suffixCommon.map(
|
...new SafeArrayIterator(
|
||||||
(c): DiffResult<typeof c> => ({ type: DiffType.common, value: c }),
|
ArrayPrototypeMap(
|
||||||
|
suffixCommon,
|
||||||
|
(c: T): DiffResult<typeof c> => ({ type: DiffType.common, value: c }),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
const offset = N;
|
const offset = N;
|
||||||
const delta = M - N;
|
const delta = M - N;
|
||||||
const size = M + N + 1;
|
const size = M + N + 1;
|
||||||
const fp: FarthestPoint[] = Array.from(
|
const fp: FarthestPoint[] = ArrayFrom(
|
||||||
{ length: size },
|
{ length: size },
|
||||||
() => ({ y: -1, id: -1 }),
|
() => ({ y: -1, id: -1 }),
|
||||||
);
|
);
|
||||||
|
@ -114,13 +148,13 @@ export function diff<T>(A: T[], B: T[]): Array<DiffResult<T>> {
|
||||||
B: T[],
|
B: T[],
|
||||||
current: FarthestPoint,
|
current: FarthestPoint,
|
||||||
swapped: boolean,
|
swapped: boolean,
|
||||||
): Array<{
|
): {
|
||||||
type: DiffType;
|
type: DiffType;
|
||||||
value: T;
|
value: T;
|
||||||
}> {
|
}[] {
|
||||||
const M = A.length;
|
const M = A.length;
|
||||||
const N = B.length;
|
const N = B.length;
|
||||||
const result = [];
|
const result: DiffResult<T>[] = [];
|
||||||
let a = M - 1;
|
let a = M - 1;
|
||||||
let b = N - 1;
|
let b = N - 1;
|
||||||
let j = routes[current.id];
|
let j = routes[current.id];
|
||||||
|
@ -129,19 +163,19 @@ export function diff<T>(A: T[], B: T[]): Array<DiffResult<T>> {
|
||||||
if (!j && !type) break;
|
if (!j && !type) break;
|
||||||
const prev = j;
|
const prev = j;
|
||||||
if (type === REMOVED) {
|
if (type === REMOVED) {
|
||||||
result.unshift({
|
ArrayPrototypeUnshift(result, {
|
||||||
type: swapped ? DiffType.removed : DiffType.added,
|
type: swapped ? DiffType.removed : DiffType.added,
|
||||||
value: B[b],
|
value: B[b],
|
||||||
});
|
});
|
||||||
b -= 1;
|
b -= 1;
|
||||||
} else if (type === ADDED) {
|
} else if (type === ADDED) {
|
||||||
result.unshift({
|
ArrayPrototypeUnshift(result, {
|
||||||
type: swapped ? DiffType.added : DiffType.removed,
|
type: swapped ? DiffType.added : DiffType.removed,
|
||||||
value: A[a],
|
value: A[a],
|
||||||
});
|
});
|
||||||
a -= 1;
|
a -= 1;
|
||||||
} else {
|
} else {
|
||||||
result.unshift({ type: DiffType.common, value: A[a] });
|
ArrayPrototypeUnshift(result, { type: DiffType.common, value: A[a] });
|
||||||
a -= 1;
|
a -= 1;
|
||||||
b -= 1;
|
b -= 1;
|
||||||
}
|
}
|
||||||
|
@ -234,16 +268,40 @@ export function diff<T>(A: T[], B: T[]): Array<DiffResult<T>> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
...prefixCommon.map(
|
...new SafeArrayIterator(
|
||||||
(c): DiffResult<typeof c> => ({ type: DiffType.common, value: c }),
|
ArrayPrototypeMap(
|
||||||
|
prefixCommon,
|
||||||
|
(c: T): DiffResult<typeof c> => ({ type: DiffType.common, value: c }),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
...backTrace(A, B, fp[delta + offset], swapped),
|
...new SafeArrayIterator(backTrace(A, B, fp[delta + offset], swapped)),
|
||||||
...suffixCommon.map(
|
...new SafeArrayIterator(
|
||||||
(c): DiffResult<typeof c> => ({ type: DiffType.common, value: c }),
|
ArrayPrototypeMap(
|
||||||
|
suffixCommon,
|
||||||
|
(c: T): DiffResult<typeof c> => ({ type: DiffType.common, value: c }),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ESCAPE_PATTERN = new SafeRegExp(/([\b\f\t\v])/g);
|
||||||
|
const ESCAPE_MAP = ObjectFreeze({
|
||||||
|
"\b": "\\b",
|
||||||
|
"\f": "\\f",
|
||||||
|
"\t": "\\t",
|
||||||
|
"\v": "\\v",
|
||||||
|
});
|
||||||
|
const LINE_BREAK_GLOBAL_PATTERN = new SafeRegExp(/\r\n|\r|\n/g);
|
||||||
|
|
||||||
|
const LINE_BREAK_PATTERN = new SafeRegExp(/(\n|\r\n)/);
|
||||||
|
const WHITESPACE_PATTERN = new SafeRegExp(/\s+/);
|
||||||
|
const WHITESPACE_SYMBOL_PATTERN = new SafeRegExp(
|
||||||
|
/([^\S\r\n]+|[()[\]{}'"\r\n]|\b)/,
|
||||||
|
);
|
||||||
|
const LATIN_CHARACTER_PATTERN = new SafeRegExp(
|
||||||
|
/^[a-zA-Z\u{C0}-\u{FF}\u{D8}-\u{F6}\u{F8}-\u{2C6}\u{2C8}-\u{2D7}\u{2DE}-\u{2FF}\u{1E00}-\u{1EFF}]+$/u,
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the differences between the actual and expected strings
|
* Renders the differences between the actual and expected strings
|
||||||
* Partially inspired from https://github.com/kpdecker/jsdiff
|
* Partially inspired from https://github.com/kpdecker/jsdiff
|
||||||
|
@ -254,44 +312,44 @@ export function diffstr(A: string, B: string) {
|
||||||
function unescape(string: string): string {
|
function unescape(string: string): string {
|
||||||
// unescape invisible characters.
|
// unescape invisible characters.
|
||||||
// ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#escape_sequences
|
// ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#escape_sequences
|
||||||
return string
|
return StringPrototypeReplace(
|
||||||
.replaceAll("\b", "\\b")
|
StringPrototypeReplace(
|
||||||
.replaceAll("\f", "\\f")
|
string,
|
||||||
.replaceAll("\t", "\\t")
|
ESCAPE_PATTERN,
|
||||||
.replaceAll("\v", "\\v")
|
(c: string) => ESCAPE_MAP[c],
|
||||||
.replaceAll( // does not remove line breaks
|
),
|
||||||
/\r\n|\r|\n/g,
|
LINE_BREAK_GLOBAL_PATTERN, // does not remove line breaks
|
||||||
(str) => str === "\r" ? "\\r" : str === "\n" ? "\\n\n" : "\\r\\n\r\n",
|
(str: string) =>
|
||||||
);
|
str === "\r" ? "\\r" : str === "\n" ? "\\n\n" : "\\r\\n\r\n",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function tokenize(string: string, { wordDiff = false } = {}): string[] {
|
function tokenize(string: string, { wordDiff = false } = {}): string[] {
|
||||||
if (wordDiff) {
|
if (wordDiff) {
|
||||||
// Split string on whitespace symbols
|
// Split string on whitespace symbols
|
||||||
const tokens = string.split(/([^\S\r\n]+|[()[\]{}'"\r\n]|\b)/);
|
const tokens = StringPrototypeSplit(string, WHITESPACE_SYMBOL_PATTERN);
|
||||||
// Extended Latin character set
|
|
||||||
const words =
|
|
||||||
/^[a-zA-Z\u{C0}-\u{FF}\u{D8}-\u{F6}\u{F8}-\u{2C6}\u{2C8}-\u{2D7}\u{2DE}-\u{2FF}\u{1E00}-\u{1EFF}]+$/u;
|
|
||||||
|
|
||||||
// Join boundary splits that we do not consider to be boundaries and merge empty strings surrounded by word chars
|
// Join boundary splits that we do not consider to be boundaries and merge empty strings surrounded by word chars
|
||||||
for (let i = 0; i < tokens.length - 1; i++) {
|
for (let i = 0; i < tokens.length - 1; i++) {
|
||||||
if (
|
if (
|
||||||
!tokens[i + 1] && tokens[i + 2] && words.test(tokens[i]) &&
|
!tokens[i + 1] && tokens[i + 2] &&
|
||||||
words.test(tokens[i + 2])
|
LATIN_CHARACTER_PATTERN.test(tokens[i]) &&
|
||||||
|
LATIN_CHARACTER_PATTERN.test(tokens[i + 2])
|
||||||
) {
|
) {
|
||||||
tokens[i] += tokens[i + 2];
|
tokens[i] += tokens[i + 2];
|
||||||
tokens.splice(i + 1, 2);
|
ArrayPrototypeSplice(tokens, i + 1, 2);
|
||||||
i--;
|
i--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return tokens.filter((token) => token);
|
return ArrayPrototypeFilter(tokens, (token: string) => token);
|
||||||
} else {
|
} else {
|
||||||
// Split string on new lines symbols
|
// Split string on new lines symbols
|
||||||
const tokens = [], lines = string.split(/(\n|\r\n)/);
|
const tokens: string[] = [],
|
||||||
|
lines: string[] = StringPrototypeSplit(string, LINE_BREAK_PATTERN);
|
||||||
|
|
||||||
// Ignore final empty token when text ends with a newline
|
// Ignore final empty token when text ends with a newline
|
||||||
if (!lines[lines.length - 1]) {
|
if (lines[lines.length - 1] === "") {
|
||||||
lines.pop();
|
ArrayPrototypePop(lines);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge the content and line separators into single tokens
|
// Merge the content and line separators into single tokens
|
||||||
|
@ -299,7 +357,7 @@ export function diffstr(A: string, B: string) {
|
||||||
if (i % 2) {
|
if (i % 2) {
|
||||||
tokens[tokens.length - 1] += lines[i];
|
tokens[tokens.length - 1] += lines[i];
|
||||||
} else {
|
} else {
|
||||||
tokens.push(lines[i]);
|
ArrayPrototypePush(tokens, lines[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return tokens;
|
return tokens;
|
||||||
|
@ -310,22 +368,28 @@ export function diffstr(A: string, B: string) {
|
||||||
// and merge "space-diff" if surrounded by word-diff for cleaner displays
|
// and merge "space-diff" if surrounded by word-diff for cleaner displays
|
||||||
function createDetails(
|
function createDetails(
|
||||||
line: DiffResult<string>,
|
line: DiffResult<string>,
|
||||||
tokens: Array<DiffResult<string>>,
|
tokens: DiffResult<string>[],
|
||||||
) {
|
) {
|
||||||
return tokens.filter(({ type }) =>
|
return ArrayPrototypeMap(
|
||||||
type === line.type || type === DiffType.common
|
ArrayPrototypeFilter(
|
||||||
).map((result, i, t) => {
|
tokens,
|
||||||
if (
|
({ type }: DiffResult<string>) =>
|
||||||
(result.type === DiffType.common) && (t[i - 1]) &&
|
type === line.type || type === DiffType.common,
|
||||||
(t[i - 1]?.type === t[i + 1]?.type) && /\s+/.test(result.value)
|
),
|
||||||
) {
|
(result: DiffResult<string>, i: number, t: DiffResult<string>[]) => {
|
||||||
return {
|
if (
|
||||||
...result,
|
(result.type === DiffType.common) && (t[i - 1]) &&
|
||||||
type: t[i - 1].type,
|
(t[i - 1]?.type === t[i + 1]?.type) &&
|
||||||
};
|
WHITESPACE_PATTERN.test(result.value)
|
||||||
}
|
) {
|
||||||
return result;
|
return {
|
||||||
});
|
...result,
|
||||||
|
type: t[i - 1].type,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute multi-line diff
|
// Compute multi-line diff
|
||||||
|
@ -334,32 +398,36 @@ export function diffstr(A: string, B: string) {
|
||||||
tokenize(`${unescape(B)}\n`),
|
tokenize(`${unescape(B)}\n`),
|
||||||
);
|
);
|
||||||
|
|
||||||
const added = [], removed = [];
|
const added: DiffResult<string>[] = [], removed: DiffResult<string>[] = [];
|
||||||
for (const result of diffResult) {
|
for (let i = 0; i < diffResult.length; ++i) {
|
||||||
|
const result = diffResult[i];
|
||||||
if (result.type === DiffType.added) {
|
if (result.type === DiffType.added) {
|
||||||
added.push(result);
|
ArrayPrototypePush(added, result);
|
||||||
}
|
}
|
||||||
if (result.type === DiffType.removed) {
|
if (result.type === DiffType.removed) {
|
||||||
removed.push(result);
|
ArrayPrototypePush(removed, result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute word-diff
|
// Compute word-diff
|
||||||
const aLines = added.length < removed.length ? added : removed;
|
const aLines = added.length < removed.length ? added : removed;
|
||||||
const bLines = aLines === removed ? added : removed;
|
const bLines = aLines === removed ? added : removed;
|
||||||
for (const a of aLines) {
|
for (let i = 0; i < aLines.length; ++i) {
|
||||||
let tokens = [] as Array<DiffResult<string>>,
|
const a = aLines[i];
|
||||||
|
let tokens = [] as DiffResult<string>[],
|
||||||
b: undefined | DiffResult<string>;
|
b: undefined | DiffResult<string>;
|
||||||
// Search another diff line with at least one common token
|
// Search another diff line with at least one common token
|
||||||
while (bLines.length) {
|
while (bLines.length !== 0) {
|
||||||
b = bLines.shift();
|
b = ArrayPrototypeShift(bLines);
|
||||||
tokens = diff(
|
tokens = diff(
|
||||||
tokenize(a.value, { wordDiff: true }),
|
tokenize(a.value, { wordDiff: true }),
|
||||||
tokenize(b?.value ?? "", { wordDiff: true }),
|
tokenize(b?.value ?? "", { wordDiff: true }),
|
||||||
);
|
);
|
||||||
if (
|
if (
|
||||||
tokens.some(({ type, value }) =>
|
ArrayPrototypeSome(
|
||||||
type === DiffType.common && value.trim().length
|
tokens,
|
||||||
|
({ type, value }) =>
|
||||||
|
type === DiffType.common && StringPrototypeTrim(value).length,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
break;
|
break;
|
||||||
|
@ -418,26 +486,35 @@ export function buildMessage(
|
||||||
{ stringDiff = false } = {},
|
{ stringDiff = false } = {},
|
||||||
): string[] {
|
): string[] {
|
||||||
const messages: string[] = [], diffMessages: string[] = [];
|
const messages: string[] = [], diffMessages: string[] = [];
|
||||||
messages.push("");
|
ArrayPrototypePush(messages, "");
|
||||||
messages.push("");
|
ArrayPrototypePush(messages, "");
|
||||||
messages.push(
|
ArrayPrototypePush(
|
||||||
|
messages,
|
||||||
` ${gray(bold("[Diff]"))} ${red(bold("Actual"))} / ${
|
` ${gray(bold("[Diff]"))} ${red(bold("Actual"))} / ${
|
||||||
green(bold("Expected"))
|
green(bold("Expected"))
|
||||||
}`,
|
}`,
|
||||||
);
|
);
|
||||||
messages.push("");
|
ArrayPrototypePush(messages, "");
|
||||||
messages.push("");
|
ArrayPrototypePush(messages, "");
|
||||||
diffResult.forEach((result: DiffResult<string>) => {
|
ArrayPrototypeForEach(diffResult, (result: DiffResult<string>) => {
|
||||||
const c = createColor(result.type);
|
const c = createColor(result.type);
|
||||||
const line = result.details?.map((detail) =>
|
|
||||||
detail.type !== DiffType.common
|
const line = result.details != null
|
||||||
? createColor(detail.type, { background: true })(detail.value)
|
? ArrayPrototypeJoin(
|
||||||
: detail.value
|
ArrayPrototypeMap(result.details, (detail) =>
|
||||||
).join("") ?? result.value;
|
detail.type !== DiffType.common
|
||||||
diffMessages.push(c(`${createSign(result.type)}${line}`));
|
? createColor(detail.type, { background: true })(detail.value)
|
||||||
|
: detail.value),
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
: result.value;
|
||||||
|
ArrayPrototypePush(diffMessages, c(`${createSign(result.type)}${line}`));
|
||||||
});
|
});
|
||||||
messages.push(...(stringDiff ? [diffMessages.join("")] : diffMessages));
|
ArrayPrototypePushApply(
|
||||||
messages.push("");
|
messages,
|
||||||
|
stringDiff ? [ArrayPrototypeJoin(diffMessages, "")] : diffMessages,
|
||||||
|
);
|
||||||
|
ArrayPrototypePush(messages, "");
|
||||||
|
|
||||||
return messages;
|
return messages;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue