Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

harness/deepEqual.js: Improve formatting and align with base assert #4253

Merged
merged 11 commits into from
Oct 16, 2024
Merged
23 changes: 18 additions & 5 deletions harness/assert.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,18 +101,31 @@ assert.throws = function (expectedErrorConstructor, func, message) {
throw new Test262Error(message);
};

assert._formatIdentityFreeValue = function formatIdentityFreeValue(value) {
switch (value === null ? 'null' : typeof value) {
case 'string':
return typeof JSON !== "undefined" ? JSON.stringify(value) : `"${value}"`;
case 'bigint':
return `${value}n`;
case 'number':
if (value === 0 && 1 / value === -Infinity) return '-0';
// falls through
case 'boolean':
case 'undefined':
case 'null':
return String(value);
}
};

assert._toString = function (value) {
var basic = assert._formatIdentityFreeValue(value);
if (basic) return basic;
try {
if (value === 0 && 1 / value === -Infinity) {
return '-0';
}

return String(value);
} catch (err) {
if (err.name === 'TypeError') {
return Object.prototype.toString.call(value);
}

throw err;
}
};
142 changes: 104 additions & 38 deletions harness/deepEqual.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,56 +14,122 @@ assert.deepEqual = function(actual, expected, message) {
);
};

let getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;

let join = arr => arr.join(', ');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let join = arr => arr.join(', ');
function join(arr) { return arr.join(', '); }

function stringFromTemplate(strings, ...subs) {
let parts = strings.map((str, i) => `${i === 0 ? '' : subs[i - 1]}${str}`);
return parts.join('');
}
function escapeKey(key) {
if (typeof key === 'symbol') return `[${String(key)}]`;
if (/^[a-zA-Z0-9_$]+$/.test(key)) return key;
return assert._formatIdentityFreeValue(key);
}

assert.deepEqual.format = function(value, seen) {
switch (typeof value) {
let basic = assert._formatIdentityFreeValue(value);
if (basic) return basic;
switch (value === null ? 'null' : typeof value) {
case 'string':
return typeof JSON !== "undefined" ? JSON.stringify(value) : `"${value}"`;
case 'bigint':
case 'number':
case 'boolean':
case 'symbol':
case 'bigint':
return value.toString();
case 'undefined':
return 'undefined';
case 'null':
assert(false, 'values without identity should use basic formatting');
break;
case 'symbol':
case 'function':
return `[Function${value.name ? `: ${value.name}` : ''}]`;
case 'object':
if (value === null) return 'null';
if (value instanceof Date) return `Date "${value.toISOString()}"`;
if (value instanceof RegExp) return value.toString();
if (!seen) {
seen = {
counter: 0,
map: new Map()
};
}
break;
default:
return typeof value;
}

let usage = seen.map.get(value);
if (usage) {
usage.used = true;
return `[Ref: #${usage.id}]`;
if (!seen) {
seen = {
counter: 0,
map: new Map()
};
}
let usage = seen.map.get(value);
if (usage) {
usage.used = true;
return `ref #${usage.id}`;
}
usage = { id: ++seen.counter, used: false };
seen.map.set(value, usage);

// Properly communicating multiple references requires deferred rendering of
// all identity-bearing values until the outermost format call finishes,
// because the current value can also in appear in a not-yet-visited part of
// the object graph (which, when visited, will update the usage object).
//
// To preserve readability of the desired output formatting, we accomplish
// this deferral using tagged template literals.
// Evaluation closes over the usage object and returns a function that accepts
// "mapper" arguments for rendering the corresponding substitution values and
// returns an object with only a toString method which will itself be invoked
// when trying to use the result as a string in assert.deepEqual.
//
// For convenience, any absent mapper is presumed to be `String`, and the
// function itself has a toString method that self-invokes with no mappers
// (allowing returning the function directly when every mapper is `String`).
function lazyResult(strings, ...subs) {
function acceptMappers(...mappers) {
function toString() {
let renderings = subs.map((sub, i) => (mappers[i] || String)(sub));
let rendered = stringFromTemplate(strings, ...renderings);
if (usage.used) rendered += ` as #${usage.id}`;
return rendered;
}

usage = { id: ++seen.counter, used: false };
seen.map.set(value, usage);
return { toString };
}

if (typeof Set !== "undefined" && value instanceof Set) {
return `Set {${Array.from(value).map(value => assert.deepEqual.format(value, seen)).join(', ')}}${usage.used ? ` as #${usage.id}` : ''}`;
}
if (typeof Map !== "undefined" && value instanceof Map) {
return `Map {${Array.from(value).map(pair => `${assert.deepEqual.format(pair[0], seen)} => ${assert.deepEqual.format(pair[1], seen)}}`).join(', ')}}${usage.used ? ` as #${usage.id}` : ''}`;
}
if (Array.isArray ? Array.isArray(value) : value instanceof Array) {
return `[${value.map(value => assert.deepEqual.format(value, seen)).join(', ')}]${usage.used ? ` as #${usage.id}` : ''}`;
}
let tag = Symbol.toStringTag in value ? value[Symbol.toStringTag] : 'Object';
if (tag === 'Object' && Object.getPrototypeOf(value) === null) {
tag = '[Object: null prototype]';
}
return `${tag ? `${tag} ` : ''}{ ${Object.keys(value).map(key => `${key.toString()}: ${assert.deepEqual.format(value[key], seen)}`).join(', ')} }${usage.used ? ` as #${usage.id}` : ''}`;
default:
return typeof value;
acceptMappers.toString = () => String(acceptMappers());
return acceptMappers;
}

let format = assert.deepEqual.format;
function lazyString(strings, ...subs) {
return { toString: () => stringFromTemplate(strings, ...subs) };
}

if (typeof value === 'function') {
return lazyResult`function${value.name ? ` ${String(value.name)}` : ''}`;
}
if (typeof value !== 'object') {
// probably a symbol
return lazyResult`${value}`;
}
if (Array.isArray ? Array.isArray(value) : value instanceof Array) {
return lazyResult`[${value.map(value => format(value, seen))}]`(join);
}
if (value instanceof Date) {
return lazyResult`Date(${format(value.toISOString(), seen)})`;
}
if (value instanceof Error) {
return lazyResult`error ${value.name || 'Error'}(${format(value.message, seen)})`;
}
if (value instanceof RegExp) {
return lazyResult`${value}`;
}
if (typeof Map !== "undefined" && value instanceof Map) {
let contents = Array.from(value).map(pair => lazyString`${format(pair[0], seen)} => ${format(pair[1], seen)}`);
return lazyResult`Map {${contents}}`(join);
}
if (typeof Set !== "undefined" && value instanceof Set) {
let contents = Array.from(value).map(value => format(value, seen));
return lazyResult`Set {${contents}}`(join);
}

let tag = Symbol.toStringTag && Symbol.toStringTag in value
? value[Symbol.toStringTag]
: Object.getPrototypeOf(value) === null ? '[Object: null prototype]' : 'Object';
let keys = Reflect.ownKeys(value).filter(key => getOwnPropertyDescriptor(value, key).enumerable);
let contents = keys.map(key => lazyString`${escapeKey(key)}: ${format(value[key], seen)}`);
return lazyResult`${tag ? `${tag} ` : ''}{${contents}}`(String, join);
};

assert.deepEqual._compare = (function () {
Expand Down
Loading