summaryrefslogtreecommitdiff
path: root/ext/node/polyfills/_util
diff options
context:
space:
mode:
authorKenta Moriuchi <moriken@kimamass.com>2023-12-08 18:00:03 +0900
committerGitHub <noreply@github.com>2023-12-08 18:00:03 +0900
commitb24356d9b9827062bee35e720b5889a403ae89f3 (patch)
treedcbad198c448550dec1e79c9f26d3f79c4c6ec13 /ext/node/polyfills/_util
parent3a74fa60ca948b0bc1608ae0ad6e00236ccc846a (diff)
fix(ext/node): use primordials in ext/node/polyfills/_util (#21444)
Diffstat (limited to 'ext/node/polyfills/_util')
-rw-r--r--ext/node/polyfills/_util/_util_callbackify.ts29
-rw-r--r--ext/node/polyfills/_util/asserts.ts6
-rw-r--r--ext/node/polyfills/_util/async.ts12
-rw-r--r--ext/node/polyfills/_util/os.ts3
-rw-r--r--ext/node/polyfills/_util/std_asserts.ts160
-rw-r--r--ext/node/polyfills/_util/std_fmt_colors.ts30
-rw-r--r--ext/node/polyfills/_util/std_testing_diff.ts271
7 files changed, 344 insertions, 167 deletions
diff --git a/ext/node/polyfills/_util/_util_callbackify.ts b/ext/node/polyfills/_util/_util_callbackify.ts
index c60122b56..4962ead8f 100644
--- a/ext/node/polyfills/_util/_util_callbackify.ts
+++ b/ext/node/polyfills/_util/_util_callbackify.ts
@@ -21,12 +21,20 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// 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.
+import { primordials } from "ext:core/mod.js";
import { nextTick } from "ext:deno_node/_next_tick.ts";
+const {
+ ArrayPrototypePop,
+ Error,
+ FunctionPrototypeApply,
+ FunctionPrototypeBind,
+ ObjectDefineProperties,
+ ObjectGetOwnPropertyDescriptors,
+ PromisePrototypeThen,
+ TypeError,
+} = primordials;
class NodeFalsyValueRejectionError extends Error {
public reason: unknown;
@@ -98,25 +106,26 @@ function callbackify<ResultT>(
}
const callbackified = function (this: unknown, ...args: unknown[]) {
- const maybeCb = args.pop();
+ const maybeCb = ArrayPrototypePop(args);
if (typeof maybeCb !== "function") {
throw new NodeInvalidArgTypeError("last");
}
const cb = (...args: unknown[]) => {
- maybeCb.apply(this, args);
+ FunctionPrototypeApply(maybeCb, this, args);
};
- original.apply(this, args).then(
+ PromisePrototypeThen(
+ FunctionPrototypeApply(this, args),
(ret: unknown) => {
- nextTick(cb.bind(this, null, ret));
+ nextTick(FunctionPrototypeBind(cb, this, null, ret));
},
(rej: unknown) => {
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
// guards against the manipulation.
if (typeof descriptors.length.value === "number") {
@@ -125,7 +134,7 @@ function callbackify<ResultT>(
if (typeof descriptors.name.value === "string") {
descriptors.name.value += "Callbackified";
}
- Object.defineProperties(callbackified, descriptors);
+ ObjectDefineProperties(callbackified, descriptors);
return callbackified;
}
diff --git a/ext/node/polyfills/_util/asserts.ts b/ext/node/polyfills/_util/asserts.ts
index ad85ecbf2..7fb56049c 100644
--- a/ext/node/polyfills/_util/asserts.ts
+++ b/ext/node/polyfills/_util/asserts.ts
@@ -1,7 +1,9 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
-// TODO(petamoriken): enable prefer-primordials for node polyfills
-// deno-lint-ignore-file prefer-primordials
+import { primordials } from "ext:core/mod.js";
+const {
+ Error,
+} = primordials;
/** Assertion error class for node compat layer's internal code. */
export class NodeCompatAssertionError extends Error {
diff --git a/ext/node/polyfills/_util/async.ts b/ext/node/polyfills/_util/async.ts
index 5eeda2a37..d0e6e04e8 100644
--- a/ext/node/polyfills/_util/async.ts
+++ b/ext/node/polyfills/_util/async.ts
@@ -2,8 +2,12 @@
// This module is vendored from std/async/delay.ts
// (with some modifications)
-// TODO(petamoriken): enable prefer-primordials for node polyfills
-// deno-lint-ignore-file prefer-primordials
+import { primordials } from "ext:core/mod.js";
+import { clearTimeout, setTimeout } from "ext:deno_web/02_timers.js";
+const {
+ Promise,
+ PromiseReject,
+} = primordials;
/** Resolve a Promise after a given amount of milliseconds. */
export function delay(
@@ -12,12 +16,12 @@ export function delay(
): Promise<void> {
const { signal } = options;
if (signal?.aborted) {
- return Promise.reject(new DOMException("Delay was aborted.", "AbortError"));
+ return PromiseReject(signal.reason);
}
return new Promise((resolve, reject) => {
const abort = () => {
clearTimeout(i);
- reject(new DOMException("Delay was aborted.", "AbortError"));
+ reject(signal!.reason);
};
const done = () => {
signal?.removeEventListener("abort", abort);
diff --git a/ext/node/polyfills/_util/os.ts b/ext/node/polyfills/_util/os.ts
index ff5e2efd5..49fde7613 100644
--- a/ext/node/polyfills/_util/os.ts
+++ b/ext/node/polyfills/_util/os.ts
@@ -1,6 +1,7 @@
// 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";
diff --git a/ext/node/polyfills/_util/std_asserts.ts b/ext/node/polyfills/_util/std_asserts.ts
index 4258a7495..061b0515d 100644
--- a/ext/node/polyfills/_util/std_asserts.ts
+++ b/ext/node/polyfills/_util/std_asserts.ts
@@ -1,15 +1,43 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// vendored from std/assert/mod.ts
-// TODO(petamoriken): enable prefer-primordials for node polyfills
-// deno-lint-ignore-file prefer-primordials
-
+import { primordials } from "ext:core/mod.js";
+import { URLPrototype } from "ext:deno_url/00_url.js";
import { red } from "ext:deno_node/_util/std_fmt_colors.ts";
import {
buildMessage,
diff,
diffstr,
} 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
* make tests less flaky */
@@ -26,7 +54,7 @@ export function format(v: unknown): string {
// getters should be true in assertEquals.
getters: true,
})
- : `"${String(v).replace(/(?=["\\])/g, "\\")}"`;
+ : `"${StringPrototypeReplace(String(v), FORMAT_PATTERN, "\\")}"`;
}
const CAN_NOT_DISPLAY = "[Cannot display]";
@@ -38,56 +66,75 @@ export class AssertionError extends Error {
}
}
-function isKeyedCollection(x: unknown): x is Set<unknown> {
- return [Symbol.iterator, "size"].every((k) => k in (x as Set<unknown>));
+function isKeyedCollection(
+ x: unknown,
+): x is { size: number; entries(): Iterable<[unknown, unknown]> } {
+ return ReflectHas(x, SymbolIterator) && ReflectHas(x, "size");
}
/** Deep equality comparison used in assertions */
export function equal(c: unknown, d: unknown): boolean {
- const seen = new Map();
+ const seen = new SafeMap();
return (function compare(a: unknown, b: unknown): boolean {
// Have to render RegExp & Date for string comparison
// unless it's mistreated as object
if (
a &&
b &&
- ((a instanceof RegExp && b instanceof RegExp) ||
- (a instanceof URL && b instanceof URL))
+ ((ObjectPrototypeIsPrototypeOf(RegExpPrototype, a) &&
+ ObjectPrototypeIsPrototypeOf(RegExpPrototype, b)) ||
+ (ObjectPrototypeIsPrototypeOf(URLPrototype, a) &&
+ ObjectPrototypeIsPrototypeOf(URLPrototype, b)))
) {
return String(a) === String(b);
}
- if (a instanceof Date && b instanceof Date) {
- const aTime = a.getTime();
- const bTime = b.getTime();
+ if (
+ ObjectPrototypeIsPrototypeOf(DatePrototype, a) &&
+ ObjectPrototypeIsPrototypeOf(DatePrototype, b)
+ ) {
+ const aTime = DatePrototypeGetTime(a);
+ const bTime = DatePrototypeGetTime(b);
// Check for NaN equality manually since NaN is not
// equal to itself.
- if (Number.isNaN(aTime) && Number.isNaN(bTime)) {
+ if (NumberIsNaN(aTime) && NumberIsNaN(bTime)) {
return true;
}
return aTime === bTime;
}
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;
}
if (a && typeof a === "object" && b && typeof b === "object") {
if (a && b && !constructorsEqual(a, b)) {
return false;
}
- if (a instanceof WeakMap || b instanceof WeakMap) {
- if (!(a instanceof WeakMap && b instanceof WeakMap)) return false;
+ if (
+ ObjectPrototypeIsPrototypeOf(WeakMapPrototype, a) ||
+ ObjectPrototypeIsPrototypeOf(WeakMapPrototype, b)
+ ) {
+ if (
+ !(ObjectPrototypeIsPrototypeOf(WeakMapPrototype, a) &&
+ ObjectPrototypeIsPrototypeOf(WeakMapPrototype, b))
+ ) return false;
throw new TypeError("cannot compare WeakMap instances");
}
- if (a instanceof WeakSet || b instanceof WeakSet) {
- if (!(a instanceof WeakSet && b instanceof WeakSet)) return false;
+ if (
+ ObjectPrototypeIsPrototypeOf(WeakSetPrototype, a) ||
+ ObjectPrototypeIsPrototypeOf(WeakSetPrototype, b)
+ ) {
+ if (
+ !(ObjectPrototypeIsPrototypeOf(WeakSetPrototype, a) &&
+ ObjectPrototypeIsPrototypeOf(WeakSetPrototype, b))
+ ) return false;
throw new TypeError("cannot compare WeakSet instances");
}
if (seen.get(a) === b) {
return true;
}
- if (Object.keys(a || {}).length !== Object.keys(b || {}).length) {
+ if (ObjectKeys(a || {}).length !== ObjectKeys(b || {}).length) {
return false;
}
seen.set(a, b);
@@ -98,7 +145,10 @@ export function equal(c: unknown, d: unknown): boolean {
let unmatchedEntries = a.size;
+ // TODO(petamoriken): use primordials
+ // deno-lint-ignore prefer-primordials
for (const [aKey, aValue] of a.entries()) {
+ // deno-lint-ignore prefer-primordials
for (const [bKey, bValue] of b.entries()) {
/* Given that Map keys can be references, we need
* to ensure that they are also deeply equal */
@@ -111,27 +161,34 @@ export function equal(c: unknown, d: unknown): boolean {
}
}
}
-
return unmatchedEntries === 0;
}
+
const merged = { ...a, ...b };
- for (
- const key of [
- ...Object.getOwnPropertyNames(merged),
- ...Object.getOwnPropertySymbols(merged),
- ]
- ) {
+ const keys = ReflectOwnKeys(merged);
+ for (let i = 0; i < keys.length; ++i) {
+ const key = keys[i];
type Key = keyof typeof merged;
if (!compare(a && a[key as Key], b && b[key as Key])) {
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;
}
}
- if (a instanceof WeakRef || b instanceof WeakRef) {
- if (!(a instanceof WeakRef && b instanceof WeakRef)) return false;
- return compare(a.deref(), b.deref());
+
+ if (
+ ObjectPrototypeIsPrototypeOf(WeakRefPrototype, a) ||
+ ObjectPrototypeIsPrototypeOf(WeakRefPrototype, b)
+ ) {
+ if (
+ !(ObjectPrototypeIsPrototypeOf(WeakRefPrototype, a) &&
+ ObjectPrototypeIsPrototypeOf(WeakRefPrototype, b))
+ ) return false;
+ return compare(WeakRefPrototypeDeref(a), WeakRefPrototypeDeref(b));
}
return true;
}
@@ -166,8 +223,14 @@ export function assertEquals<T>(actual: T, expected: T, msg?: string) {
(typeof expected === "string");
const diffResult = stringDiff
? diffstr(actual as string, expected as string)
- : diff(actualString.split("\n"), expectedString.split("\n"));
- const diffMsg = buildMessage(diffResult, { stringDiff }).join("\n");
+ : diff(
+ StringPrototypeSplit(actualString, "\n"),
+ StringPrototypeSplit(expectedString, "\n"),
+ );
+ const diffMsg = ArrayPrototypeJoin(
+ buildMessage(diffResult, { stringDiff }),
+ "\n",
+ );
message = `Values are not equal:\n${diffMsg}`;
} catch {
message = `\n${red(red(CAN_NOT_DISPLAY))} + \n\n`;
@@ -209,7 +272,7 @@ export function assertStrictEquals<T>(
expected: T,
msg?: string,
): asserts actual is T {
- if (Object.is(actual, expected)) {
+ if (ObjectIs(actual, expected)) {
return;
}
@@ -222,10 +285,13 @@ export function assertStrictEquals<T>(
const expectedString = format(expected);
if (actualString === expectedString) {
- const withOffset = actualString
- .split("\n")
- .map((l) => ` ${l}`)
- .join("\n");
+ const withOffset = ArrayPrototypeJoin(
+ ArrayPrototypeMap(
+ StringPrototypeSplit(actualString, "\n"),
+ (l: string) => ` ${l}`,
+ ),
+ "\n",
+ );
message =
`Values have the same structure but are not reference-equal:\n\n${
red(withOffset)
@@ -236,8 +302,14 @@ export function assertStrictEquals<T>(
(typeof expected === "string");
const diffResult = stringDiff
? diffstr(actual as string, expected as string)
- : diff(actualString.split("\n"), expectedString.split("\n"));
- const diffMsg = buildMessage(diffResult, { stringDiff }).join("\n");
+ : diff(
+ StringPrototypeSplit(actualString, "\n"),
+ StringPrototypeSplit(expectedString, "\n"),
+ );
+ const diffMsg = ArrayPrototypeJoin(
+ buildMessage(diffResult, { stringDiff }),
+ "\n",
+ );
message = `Values are not strictly equal:\n${diffMsg}`;
} catch {
message = `\n${CAN_NOT_DISPLAY} + \n\n`;
@@ -255,7 +327,7 @@ export function assertNotStrictEquals<T>(
expected: T,
msg?: string,
) {
- if (!Object.is(actual, expected)) {
+ if (!ObjectIs(actual, expected)) {
return;
}
@@ -268,10 +340,11 @@ export function assertNotStrictEquals<T>(
* then throw. */
export function assertMatch(
actual: string,
+ // deno-lint-ignore prefer-primordials
expected: RegExp,
msg?: string,
) {
- if (!expected.test(actual)) {
+ if (!RegExpPrototypeTest(expected, actual)) {
if (!msg) {
msg = `actual: "${actual}" expected to match: "${expected}"`;
}
@@ -283,10 +356,11 @@ export function assertMatch(
* then throw. */
export function assertNotMatch(
actual: string,
+ // deno-lint-ignore prefer-primordials
expected: RegExp,
msg?: string,
) {
- if (expected.test(actual)) {
+ if (RegExpPrototypeTest(expected, actual)) {
if (!msg) {
msg = `actual: "${actual}" expected to not match: "${expected}"`;
}
diff --git a/ext/node/polyfills/_util/std_fmt_colors.ts b/ext/node/polyfills/_util/std_fmt_colors.ts
index 5632c05c1..df18526da 100644
--- a/ext/node/polyfills/_util/std_fmt_colors.ts
+++ b/ext/node/polyfills/_util/std_fmt_colors.ts
@@ -1,8 +1,15 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// This file is vendored from std/fmt/colors.ts
-// TODO(petamoriken): enable prefer-primordials for node polyfills
-// deno-lint-ignore-file prefer-primordials
+import { primordials } from "ext:core/mod.js";
+const {
+ ArrayPrototypeJoin,
+ MathMax,
+ MathMin,
+ MathTrunc,
+ SafeRegExp,
+ StringPrototypeReplace,
+} = primordials;
// TODO(kt3k): Initialize this at the start of runtime
// based on Deno.noColor
@@ -11,6 +18,7 @@ const noColor = false;
interface Code {
open: string;
close: string;
+ // deno-lint-ignore prefer-primordials
regexp: RegExp;
}
@@ -47,9 +55,9 @@ export function getColorEnabled(): boolean {
*/
function code(open: number[], close: number): Code {
return {
- open: `\x1b[${open.join(";")}m`,
+ open: `\x1b[${ArrayPrototypeJoin(open, ";")}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 {
return enabled
- ? `${code.open}${str.replace(code.regexp, code.open)}${code.close}`
+ ? `${code.open}${
+ StringPrototypeReplace(str, code.regexp, code.open)
+ }${code.close}`
: str;
}
@@ -401,7 +411,7 @@ export function bgBrightWhite(str: string): string {
* @param min number to truncate from
*/
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
-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)",
"(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))",
- ].join("|"),
+ ], "|"),
"g",
);
@@ -518,5 +528,5 @@ const ANSI_PATTERN = new RegExp(
* @param string to remove ANSI escape codes from
*/
export function stripColor(string: string): string {
- return string.replace(ANSI_PATTERN, "");
+ return StringPrototypeReplace(string, ANSI_PATTERN, "");
}
diff --git a/ext/node/polyfills/_util/std_testing_diff.ts b/ext/node/polyfills/_util/std_testing_diff.ts
index bcd619255..635ce1f29 100644
--- a/ext/node/polyfills/_util/std_testing_diff.ts
+++ b/ext/node/polyfills/_util/std_testing_diff.ts
@@ -1,9 +1,7 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// This file was vendored from std/testing/_diff.ts
-// TODO(petamoriken): enable prefer-primordials for node polyfills
-// deno-lint-ignore-file prefer-primordials
-
+import { primordials } from "ext:core/mod.js";
import {
bgGreen,
bgRed,
@@ -13,6 +11,30 @@ import {
red,
white,
} 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 {
y: number;
@@ -28,7 +50,7 @@ export enum DiffType {
export interface DiffResult<T> {
type: DiffType;
value: T;
- details?: Array<DiffResult<T>>;
+ details?: DiffResult<T>[];
}
const REMOVED = 1;
@@ -38,11 +60,11 @@ const ADDED = 3;
function createCommon<T>(A: T[], B: T[], reverse?: boolean): T[] {
const common = [];
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 (
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 {
return common;
}
@@ -55,44 +77,56 @@ function createCommon<T>(A: T[], B: T[], reverse?: boolean): T[] {
* @param A Actual 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 suffixCommon = createCommon(
- A.slice(prefixCommon.length),
- B.slice(prefixCommon.length),
+ const suffixCommon = ArrayPrototypeReverse(createCommon(
+ ArrayPrototypeSlice(A, prefixCommon.length),
+ ArrayPrototypeSlice(B, prefixCommon.length),
true,
- ).reverse();
+ ));
A = suffixCommon.length
- ? A.slice(prefixCommon.length, -suffixCommon.length)
- : A.slice(prefixCommon.length);
+ ? ArrayPrototypeSlice(A, prefixCommon.length, -suffixCommon.length)
+ : ArrayPrototypeSlice(A, prefixCommon.length);
B = suffixCommon.length
- ? B.slice(prefixCommon.length, -suffixCommon.length)
- : B.slice(prefixCommon.length);
+ ? ArrayPrototypeSlice(B, prefixCommon.length, -suffixCommon.length)
+ : ArrayPrototypeSlice(B, prefixCommon.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 N = B.length;
- if (!M && !N && !suffixCommon.length && !prefixCommon.length) return [];
- if (!N) {
+ if (
+ M === 0 && N === 0 && suffixCommon.length === 0 && prefixCommon.length === 0
+ ) return [];
+ if (N === 0) {
return [
- ...prefixCommon.map(
- (c): DiffResult<typeof c> => ({ type: DiffType.common, value: c }),
+ ...new SafeArrayIterator(
+ ArrayPrototypeMap(
+ prefixCommon,
+ (c: T): DiffResult<typeof c> => ({ type: DiffType.common, value: c }),
+ ),
),
- ...A.map(
- (a): DiffResult<typeof a> => ({
+ ...new SafeArrayIterator(
+ ArrayPrototypeMap(A, (a: T): DiffResult<typeof a> => ({
type: swapped ? DiffType.added : DiffType.removed,
value: a,
- }),
+ })),
),
- ...suffixCommon.map(
- (c): DiffResult<typeof c> => ({ type: DiffType.common, value: c }),
+ ...new SafeArrayIterator(
+ ArrayPrototypeMap(
+ suffixCommon,
+ (c: T): DiffResult<typeof c> => ({ type: DiffType.common, value: c }),
+ ),
),
];
}
const offset = N;
const delta = M - N;
const size = M + N + 1;
- const fp: FarthestPoint[] = Array.from(
+ const fp: FarthestPoint[] = ArrayFrom(
{ length: size },
() => ({ y: -1, id: -1 }),
);
@@ -114,13 +148,13 @@ export function diff<T>(A: T[], B: T[]): Array<DiffResult<T>> {
B: T[],
current: FarthestPoint,
swapped: boolean,
- ): Array<{
+ ): {
type: DiffType;
value: T;
- }> {
+ }[] {
const M = A.length;
const N = B.length;
- const result = [];
+ const result: DiffResult<T>[] = [];
let a = M - 1;
let b = N - 1;
let j = routes[current.id];
@@ -129,19 +163,19 @@ export function diff<T>(A: T[], B: T[]): Array<DiffResult<T>> {
if (!j && !type) break;
const prev = j;
if (type === REMOVED) {
- result.unshift({
+ ArrayPrototypeUnshift(result, {
type: swapped ? DiffType.removed : DiffType.added,
value: B[b],
});
b -= 1;
} else if (type === ADDED) {
- result.unshift({
+ ArrayPrototypeUnshift(result, {
type: swapped ? DiffType.added : DiffType.removed,
value: A[a],
});
a -= 1;
} else {
- result.unshift({ type: DiffType.common, value: A[a] });
+ ArrayPrototypeUnshift(result, { type: DiffType.common, value: A[a] });
a -= 1;
b -= 1;
}
@@ -234,16 +268,40 @@ export function diff<T>(A: T[], B: T[]): Array<DiffResult<T>> {
);
}
return [
- ...prefixCommon.map(
- (c): DiffResult<typeof c> => ({ type: DiffType.common, value: c }),
+ ...new SafeArrayIterator(
+ ArrayPrototypeMap(
+ prefixCommon,
+ (c: T): DiffResult<typeof c> => ({ type: DiffType.common, value: c }),
+ ),
),
- ...backTrace(A, B, fp[delta + offset], swapped),
- ...suffixCommon.map(
- (c): DiffResult<typeof c> => ({ type: DiffType.common, value: c }),
+ ...new SafeArrayIterator(backTrace(A, B, fp[delta + offset], swapped)),
+ ...new SafeArrayIterator(
+ 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
* Partially inspired from https://github.com/kpdecker/jsdiff
@@ -254,44 +312,44 @@ export function diffstr(A: string, B: string) {
function unescape(string: string): string {
// unescape invisible characters.
// ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#escape_sequences
- return string
- .replaceAll("\b", "\\b")
- .replaceAll("\f", "\\f")
- .replaceAll("\t", "\\t")
- .replaceAll("\v", "\\v")
- .replaceAll( // does not remove line breaks
- /\r\n|\r|\n/g,
- (str) => str === "\r" ? "\\r" : str === "\n" ? "\\n\n" : "\\r\\n\r\n",
- );
+ return StringPrototypeReplace(
+ StringPrototypeReplace(
+ string,
+ ESCAPE_PATTERN,
+ (c: string) => ESCAPE_MAP[c],
+ ),
+ LINE_BREAK_GLOBAL_PATTERN, // does not remove line breaks
+ (str: string) =>
+ str === "\r" ? "\\r" : str === "\n" ? "\\n\n" : "\\r\\n\r\n",
+ );
}
function tokenize(string: string, { wordDiff = false } = {}): string[] {
if (wordDiff) {
// Split string on whitespace symbols
- const tokens = string.split(/([^\S\r\n]+|[()[\]{}'"\r\n]|\b)/);
- // 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;
+ const tokens = StringPrototypeSplit(string, WHITESPACE_SYMBOL_PATTERN);
// 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++) {
if (
- !tokens[i + 1] && tokens[i + 2] && words.test(tokens[i]) &&
- words.test(tokens[i + 2])
+ !tokens[i + 1] && tokens[i + 2] &&
+ LATIN_CHARACTER_PATTERN.test(tokens[i]) &&
+ LATIN_CHARACTER_PATTERN.test(tokens[i + 2])
) {
tokens[i] += tokens[i + 2];
- tokens.splice(i + 1, 2);
+ ArrayPrototypeSplice(tokens, i + 1, 2);
i--;
}
}
- return tokens.filter((token) => token);
+ return ArrayPrototypeFilter(tokens, (token: string) => token);
} else {
// 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
- if (!lines[lines.length - 1]) {
- lines.pop();
+ if (lines[lines.length - 1] === "") {
+ ArrayPrototypePop(lines);
}
// Merge the content and line separators into single tokens
@@ -299,7 +357,7 @@ export function diffstr(A: string, B: string) {
if (i % 2) {
tokens[tokens.length - 1] += lines[i];
} else {
- tokens.push(lines[i]);
+ ArrayPrototypePush(tokens, lines[i]);
}
}
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
function createDetails(
line: DiffResult<string>,
- tokens: Array<DiffResult<string>>,
+ tokens: DiffResult<string>[],
) {
- return tokens.filter(({ type }) =>
- type === line.type || type === DiffType.common
- ).map((result, i, t) => {
- if (
- (result.type === DiffType.common) && (t[i - 1]) &&
- (t[i - 1]?.type === t[i + 1]?.type) && /\s+/.test(result.value)
- ) {
- return {
- ...result,
- type: t[i - 1].type,
- };
- }
- return result;
- });
+ return ArrayPrototypeMap(
+ ArrayPrototypeFilter(
+ tokens,
+ ({ type }: DiffResult<string>) =>
+ type === line.type || type === DiffType.common,
+ ),
+ (result: DiffResult<string>, i: number, t: DiffResult<string>[]) => {
+ if (
+ (result.type === DiffType.common) && (t[i - 1]) &&
+ (t[i - 1]?.type === t[i + 1]?.type) &&
+ WHITESPACE_PATTERN.test(result.value)
+ ) {
+ return {
+ ...result,
+ type: t[i - 1].type,
+ };
+ }
+ return result;
+ },
+ );
}
// Compute multi-line diff
@@ -334,32 +398,36 @@ export function diffstr(A: string, B: string) {
tokenize(`${unescape(B)}\n`),
);
- const added = [], removed = [];
- for (const result of diffResult) {
+ const added: DiffResult<string>[] = [], removed: DiffResult<string>[] = [];
+ for (let i = 0; i < diffResult.length; ++i) {
+ const result = diffResult[i];
if (result.type === DiffType.added) {
- added.push(result);
+ ArrayPrototypePush(added, result);
}
if (result.type === DiffType.removed) {
- removed.push(result);
+ ArrayPrototypePush(removed, result);
}
}
// Compute word-diff
const aLines = added.length < removed.length ? added : removed;
const bLines = aLines === removed ? added : removed;
- for (const a of aLines) {
- let tokens = [] as Array<DiffResult<string>>,
+ for (let i = 0; i < aLines.length; ++i) {
+ const a = aLines[i];
+ let tokens = [] as DiffResult<string>[],
b: undefined | DiffResult<string>;
// Search another diff line with at least one common token
- while (bLines.length) {
- b = bLines.shift();
+ while (bLines.length !== 0) {
+ b = ArrayPrototypeShift(bLines);
tokens = diff(
tokenize(a.value, { wordDiff: true }),
tokenize(b?.value ?? "", { wordDiff: true }),
);
if (
- tokens.some(({ type, value }) =>
- type === DiffType.common && value.trim().length
+ ArrayPrototypeSome(
+ tokens,
+ ({ type, value }) =>
+ type === DiffType.common && StringPrototypeTrim(value).length,
)
) {
break;
@@ -418,26 +486,35 @@ export function buildMessage(
{ stringDiff = false } = {},
): string[] {
const messages: string[] = [], diffMessages: string[] = [];
- messages.push("");
- messages.push("");
- messages.push(
+ ArrayPrototypePush(messages, "");
+ ArrayPrototypePush(messages, "");
+ ArrayPrototypePush(
+ messages,
` ${gray(bold("[Diff]"))} ${red(bold("Actual"))} / ${
green(bold("Expected"))
}`,
);
- messages.push("");
- messages.push("");
- diffResult.forEach((result: DiffResult<string>) => {
+ ArrayPrototypePush(messages, "");
+ ArrayPrototypePush(messages, "");
+ ArrayPrototypeForEach(diffResult, (result: DiffResult<string>) => {
const c = createColor(result.type);
- const line = result.details?.map((detail) =>
- detail.type !== DiffType.common
- ? createColor(detail.type, { background: true })(detail.value)
- : detail.value
- ).join("") ?? result.value;
- diffMessages.push(c(`${createSign(result.type)}${line}`));
+
+ const line = result.details != null
+ ? ArrayPrototypeJoin(
+ ArrayPrototypeMap(result.details, (detail) =>
+ detail.type !== DiffType.common
+ ? createColor(detail.type, { background: true })(detail.value)
+ : detail.value),
+ "",
+ )
+ : result.value;
+ ArrayPrototypePush(diffMessages, c(`${createSign(result.type)}${line}`));
});
- messages.push(...(stringDiff ? [diffMessages.join("")] : diffMessages));
- messages.push("");
+ ArrayPrototypePushApply(
+ messages,
+ stringDiff ? [ArrayPrototypeJoin(diffMessages, "")] : diffMessages,
+ );
+ ArrayPrototypePush(messages, "");
return messages;
}