UNPKG

fast-check

Version:

Property based testing framework for JavaScript (like QuickCheck)

270 lines (269 loc) 11.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.asyncToStringMethod = exports.toStringMethod = void 0; exports.hasToStringMethod = hasToStringMethod; exports.hasAsyncToStringMethod = hasAsyncToStringMethod; exports.stringifyInternal = stringifyInternal; exports.stringify = stringify; exports.possiblyAsyncStringify = possiblyAsyncStringify; exports.asyncStringify = asyncStringify; const globals_1 = require("./globals"); const safeArrayFrom = Array.from; const safeBufferIsBuffer = typeof Buffer !== 'undefined' ? Buffer.isBuffer : undefined; const safeJsonStringify = JSON.stringify; const safeNumberIsNaN = Number.isNaN; const safeObjectKeys = Object.keys; const safeObjectGetOwnPropertySymbols = Object.getOwnPropertySymbols; const safeObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; const safeObjectGetPrototypeOf = Object.getPrototypeOf; const safeNegativeInfinity = Number.NEGATIVE_INFINITY; const safePositiveInfinity = Number.POSITIVE_INFINITY; exports.toStringMethod = Symbol.for('fast-check/toStringMethod'); function hasToStringMethod(instance) { return (instance !== null && (typeof instance === 'object' || typeof instance === 'function') && exports.toStringMethod in instance && typeof instance[exports.toStringMethod] === 'function'); } exports.asyncToStringMethod = Symbol.for('fast-check/asyncToStringMethod'); function hasAsyncToStringMethod(instance) { return (instance !== null && (typeof instance === 'object' || typeof instance === 'function') && exports.asyncToStringMethod in instance && typeof instance[exports.asyncToStringMethod] === 'function'); } const findSymbolNameRegex = /^Symbol\((.*)\)$/; function getSymbolDescription(s) { if (s.description !== undefined) return s.description; const m = findSymbolNameRegex.exec((0, globals_1.String)(s)); return m && m[1].length ? m[1] : null; } function stringifyNumber(numValue) { switch (numValue) { case 0: return 1 / numValue === safeNegativeInfinity ? '-0' : '0'; case safeNegativeInfinity: return 'Number.NEGATIVE_INFINITY'; case safePositiveInfinity: return 'Number.POSITIVE_INFINITY'; default: return numValue === numValue ? (0, globals_1.String)(numValue) : 'Number.NaN'; } } function isSparseArray(arr) { let previousNumberedIndex = -1; for (const index in arr) { const numberedIndex = Number(index); if (numberedIndex !== previousNumberedIndex + 1) return true; previousNumberedIndex = numberedIndex; } return previousNumberedIndex + 1 !== arr.length; } function stringifyInternal(value, previousValues, getAsyncContent) { const currentValues = [...previousValues, value]; if (typeof value === 'object') { if ((0, globals_1.safeIndexOf)(previousValues, value) !== -1) { return '[cyclic]'; } } if (hasAsyncToStringMethod(value)) { const content = getAsyncContent(value); if (content.state === 'fulfilled') { return content.value; } } if (hasToStringMethod(value)) { try { return value[exports.toStringMethod](); } catch (err) { } } switch ((0, globals_1.safeToString)(value)) { case '[object Array]': { const arr = value; if (arr.length >= 50 && isSparseArray(arr)) { const assignments = []; for (const index in arr) { if (!safeNumberIsNaN(Number(index))) (0, globals_1.safePush)(assignments, `${index}:${stringifyInternal(arr[index], currentValues, getAsyncContent)}`); } return assignments.length !== 0 ? `Object.assign(Array(${arr.length}),{${(0, globals_1.safeJoin)(assignments, ',')}})` : `Array(${arr.length})`; } const stringifiedArray = (0, globals_1.safeJoin)((0, globals_1.safeMap)(arr, (v) => stringifyInternal(v, currentValues, getAsyncContent)), ','); return arr.length === 0 || arr.length - 1 in arr ? `[${stringifiedArray}]` : `[${stringifiedArray},]`; } case '[object BigInt]': return `${value}n`; case '[object Boolean]': { const unboxedToString = value == true ? 'true' : 'false'; return typeof value === 'boolean' ? unboxedToString : `new Boolean(${unboxedToString})`; } case '[object Date]': { const d = value; return safeNumberIsNaN((0, globals_1.safeGetTime)(d)) ? `new Date(NaN)` : `new Date(${safeJsonStringify((0, globals_1.safeToISOString)(d))})`; } case '[object Map]': return `new Map(${stringifyInternal(Array.from(value), currentValues, getAsyncContent)})`; case '[object Null]': return `null`; case '[object Number]': return typeof value === 'number' ? stringifyNumber(value) : `new Number(${stringifyNumber(Number(value))})`; case '[object Object]': { try { const toStringAccessor = value.toString; if (typeof toStringAccessor === 'function' && toStringAccessor !== Object.prototype.toString) { return value.toString(); } } catch (err) { return '[object Object]'; } const mapper = (k) => `${k === '__proto__' ? '["__proto__"]' : typeof k === 'symbol' ? `[${stringifyInternal(k, currentValues, getAsyncContent)}]` : safeJsonStringify(k)}:${stringifyInternal(value[k], currentValues, getAsyncContent)}`; const stringifiedProperties = [ ...(0, globals_1.safeMap)(safeObjectKeys(value), mapper), ...(0, globals_1.safeMap)((0, globals_1.safeFilter)(safeObjectGetOwnPropertySymbols(value), (s) => { const descriptor = safeObjectGetOwnPropertyDescriptor(value, s); return descriptor && descriptor.enumerable; }), mapper), ]; const rawRepr = '{' + (0, globals_1.safeJoin)(stringifiedProperties, ',') + '}'; if (safeObjectGetPrototypeOf(value) === null) { return rawRepr === '{}' ? 'Object.create(null)' : `Object.assign(Object.create(null),${rawRepr})`; } return rawRepr; } case '[object Set]': return `new Set(${stringifyInternal(Array.from(value), currentValues, getAsyncContent)})`; case '[object String]': return typeof value === 'string' ? safeJsonStringify(value) : `new String(${safeJsonStringify(value)})`; case '[object Symbol]': { const s = value; if (globals_1.Symbol.keyFor(s) !== undefined) { return `Symbol.for(${safeJsonStringify(globals_1.Symbol.keyFor(s))})`; } const desc = getSymbolDescription(s); if (desc === null) { return 'Symbol()'; } const knownSymbol = desc.startsWith('Symbol.') && globals_1.Symbol[desc.substring(7)]; return s === knownSymbol ? desc : `Symbol(${safeJsonStringify(desc)})`; } case '[object Promise]': { const promiseContent = getAsyncContent(value); switch (promiseContent.state) { case 'fulfilled': return `Promise.resolve(${stringifyInternal(promiseContent.value, currentValues, getAsyncContent)})`; case 'rejected': return `Promise.reject(${stringifyInternal(promiseContent.value, currentValues, getAsyncContent)})`; case 'pending': return `new Promise(() => {/*pending*/})`; case 'unknown': default: return `new Promise(() => {/*unknown*/})`; } } case '[object Error]': if (value instanceof Error) { return `new Error(${stringifyInternal(value.message, currentValues, getAsyncContent)})`; } break; case '[object Undefined]': return `undefined`; case '[object Int8Array]': case '[object Uint8Array]': case '[object Uint8ClampedArray]': case '[object Int16Array]': case '[object Uint16Array]': case '[object Int32Array]': case '[object Uint32Array]': case '[object Float32Array]': case '[object Float64Array]': case '[object BigInt64Array]': case '[object BigUint64Array]': { if (typeof safeBufferIsBuffer === 'function' && safeBufferIsBuffer(value)) { return `Buffer.from(${stringifyInternal(safeArrayFrom(value.values()), currentValues, getAsyncContent)})`; } const valuePrototype = safeObjectGetPrototypeOf(value); const className = valuePrototype && valuePrototype.constructor && valuePrototype.constructor.name; if (typeof className === 'string') { const typedArray = value; const valuesFromTypedArr = typedArray.values(); return `${className}.from(${stringifyInternal(safeArrayFrom(valuesFromTypedArr), currentValues, getAsyncContent)})`; } break; } } try { return value.toString(); } catch (_a) { return (0, globals_1.safeToString)(value); } } function stringify(value) { return stringifyInternal(value, [], () => ({ state: 'unknown', value: undefined })); } function possiblyAsyncStringify(value) { const stillPendingMarker = (0, globals_1.Symbol)(); const pendingPromisesForCache = []; const cache = new globals_1.Map(); function createDelay0() { let handleId = null; const cancel = () => { if (handleId !== null) { clearTimeout(handleId); } }; const delay = new Promise((resolve) => { handleId = setTimeout(() => { handleId = null; resolve(stillPendingMarker); }, 0); }); return { delay, cancel }; } const unknownState = { state: 'unknown', value: undefined }; const getAsyncContent = function getAsyncContent(data) { const cacheKey = data; if (cache.has(cacheKey)) { return cache.get(cacheKey); } const delay0 = createDelay0(); const p = exports.asyncToStringMethod in data ? Promise.resolve().then(() => data[exports.asyncToStringMethod]()) : data; p.catch(() => { }); pendingPromisesForCache.push(Promise.race([p, delay0.delay]).then((successValue) => { if (successValue === stillPendingMarker) cache.set(cacheKey, { state: 'pending', value: undefined }); else cache.set(cacheKey, { state: 'fulfilled', value: successValue }); delay0.cancel(); }, (errorValue) => { cache.set(cacheKey, { state: 'rejected', value: errorValue }); delay0.cancel(); })); cache.set(cacheKey, unknownState); return unknownState; }; function loop() { const stringifiedValue = stringifyInternal(value, [], getAsyncContent); if (pendingPromisesForCache.length === 0) { return stringifiedValue; } return Promise.all(pendingPromisesForCache.splice(0)).then(loop); } return loop(); } async function asyncStringify(value) { return Promise.resolve(possiblyAsyncStringify(value)); }