UNPKG

@augment-vir/common

Version:

A collection of augments, helpers types, functions, and classes for any JavaScript environment.

601 lines (600 loc) 25.2 kB
/* node:coverage disable */ /** * This file is copied from the * [safe-stable-stringify](https://npmjs.com/package/safe-stable-stringify) package, and modified so * that it can actually be imported, at: * https://github.com/BridgeAR/safe-stable-stringify/blob/8a02137ac933eff57dd6e49beb9ee766fe8dd372/index.js * * It has the following license: * * The MIT License (MIT) * * Copyright (c) Ruben Bridgewater * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ /* eslint-disable */ // @ts-nocheck const { hasOwnProperty } = Object.prototype; const strEscapeSequencesRegExp = /[\u0000-\u001f\u0022\u005c\ud800-\udfff]/; // Escape C0 control characters, double quotes, the backslash and every code // unit with a numeric value in the inclusive range 0xD800 to 0xDFFF. function strEscape(str) { // Some magic numbers that worked out fine while benchmarking with v8 8.0 if (str.length < 5000 && !strEscapeSequencesRegExp.test(str)) { return `"${str}"`; } return JSON.stringify(str); } function sort(array, comparator) { // Insertion sort is very efficient for small input sizes, but it has a bad // worst case complexity. Thus, use native array sort for bigger values. if (array.length > 2e2 || comparator) { return array.sort(comparator); } for (let i = 1; i < array.length; i++) { const currentValue = array[i]; let position = i; while (position !== 0 && array[position - 1] > currentValue) { array[position] = array[position - 1]; position--; } array[position] = currentValue; } return array; } const typedArrayPrototypeGetSymbolToStringTag = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(Object.getPrototypeOf(new Int8Array())), Symbol.toStringTag).get; function isTypedArrayWithEntries(value) { return typedArrayPrototypeGetSymbolToStringTag.call(value) !== undefined && value.length !== 0; } function stringifyTypedArray(array, separator, maximumBreadth) { if (array.length < maximumBreadth) { maximumBreadth = array.length; } const whitespace = separator === ',' ? '' : ' '; let res = `"0":${whitespace}${array[0]}`; for (let i = 1; i < maximumBreadth; i++) { res += `${separator}"${i}":${whitespace}${array[i]}`; } return res; } function getCircularValueOption(options) { if (hasOwnProperty.call(options, 'circularValue')) { const circularValue = options.circularValue; if (typeof circularValue === 'string') { return `"${circularValue}"`; } if (circularValue == null) { return circularValue; } if (circularValue === Error || circularValue === TypeError) { return { toString() { throw new TypeError('Converting circular structure to JSON'); }, }; } throw new TypeError('The "circularValue" argument must be of type string or the value null or undefined'); } return '"[Circular]"'; } function getDeterministicOption(options) { let value; if (hasOwnProperty.call(options, 'deterministic')) { value = options.deterministic; if (typeof value !== 'boolean' && typeof value !== 'function') { throw new TypeError('The "deterministic" argument must be of type boolean or comparator function'); } } return value === undefined ? true : value; } function getBooleanOption(options, key) { let value; if (hasOwnProperty.call(options, key)) { value = options[key]; if (typeof value !== 'boolean') { throw new TypeError(`The "${key}" argument must be of type boolean`); } } return value === undefined ? true : value; } function getPositiveIntegerOption(options, key) { let value; if (hasOwnProperty.call(options, key)) { value = options[key]; if (typeof value !== 'number') { throw new TypeError(`The "${key}" argument must be of type number`); } if (!Number.isInteger(value)) { throw new TypeError(`The "${key}" argument must be an integer`); } if (value < 1) { throw new RangeError(`The "${key}" argument must be >= 1`); } } return value === undefined ? Infinity : value; } function getItemCount(number) { if (number === 1) { return '1 item'; } return `${number} items`; } function getUniqueReplacerSet(replacerArray) { const replacerSet = new Set(); for (const value of replacerArray) { if (typeof value === 'string' || typeof value === 'number') { replacerSet.add(String(value)); } } return replacerSet; } function getStrictOption(options) { if (hasOwnProperty.call(options, 'strict')) { const value = options.strict; if (typeof value !== 'boolean') { throw new TypeError('The "strict" argument must be of type boolean'); } if (value) { return (value) => { let message = `Object can not safely be stringified. Received type ${typeof value}`; if (typeof value !== 'function') { message += ` (${value.toString()})`; } throw new Error(message); }; } } } export function configure(options) { options = { ...options }; const fail = getStrictOption(options); if (fail) { if (options.bigint === undefined) { options.bigint = false; } if (!('circularValue' in options)) { options.circularValue = Error; } } const circularValue = getCircularValueOption(options); const bigint = getBooleanOption(options, 'bigint'); const deterministic = getDeterministicOption(options); const comparator = typeof deterministic === 'function' ? deterministic : undefined; const maximumDepth = getPositiveIntegerOption(options, 'maximumDepth'); const maximumBreadth = getPositiveIntegerOption(options, 'maximumBreadth'); function stringifyFnReplacer(key, parent, stack, replacer, spacer, indentation) { let value = parent[key]; if (typeof value === 'object' && value !== null && typeof value.toJSON === 'function') { value = value.toJSON(key); } value = replacer.call(parent, key, value); switch (typeof value) { case 'string': return strEscape(value); case 'object': { if (value === null) { return 'null'; } if (stack.includes(value)) { return circularValue; } let res = ''; let join = ','; const originalIndentation = indentation; if (Array.isArray(value)) { if (value.length === 0) { return '[]'; } if (maximumDepth < stack.length + 1) { return '"[Array]"'; } stack.push(value); if (spacer !== '') { indentation += spacer; res += `\n${indentation}`; join = `,\n${indentation}`; } const maximumValuesToStringify = Math.min(value.length, maximumBreadth); let i = 0; for (; i < maximumValuesToStringify - 1; i++) { const tmp = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation); res += tmp === undefined ? 'null' : tmp; res += join; } const tmp = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation); res += tmp === undefined ? 'null' : tmp; if (value.length - 1 > maximumBreadth) { const removedKeys = value.length - maximumBreadth - 1; res += `${join}"... ${getItemCount(removedKeys)} not stringified"`; } if (spacer !== '') { res += `\n${originalIndentation}`; } stack.pop(); return `[${res}]`; } let keys = Object.keys(value); const keyLength = keys.length; if (keyLength === 0) { return '{}'; } if (maximumDepth < stack.length + 1) { return '"[Object]"'; } let whitespace = ''; let separator = ''; if (spacer !== '') { indentation += spacer; join = `,\n${indentation}`; whitespace = ' '; } const maximumPropertiesToStringify = Math.min(keyLength, maximumBreadth); if (deterministic && !isTypedArrayWithEntries(value)) { keys = sort(keys, comparator); } stack.push(value); for (let i = 0; i < maximumPropertiesToStringify; i++) { const key = keys[i]; const tmp = stringifyFnReplacer(key, value, stack, replacer, spacer, indentation); if (tmp !== undefined) { res += `${separator}${strEscape(key)}:${whitespace}${tmp}`; separator = join; } } if (keyLength > maximumBreadth) { const removedKeys = keyLength - maximumBreadth; res += `${separator}"...":${whitespace}"${getItemCount(removedKeys)} not stringified"`; separator = join; } if (spacer !== '' && separator.length > 1) { res = `\n${indentation}${res}\n${originalIndentation}`; } stack.pop(); return `{${res}}`; } case 'number': return isFinite(value) ? String(value) : fail ? fail(value) : 'null'; case 'boolean': return value ? 'true' : 'false'; case 'undefined': return undefined; case 'bigint': if (bigint) { return String(value); } // fallthrough default: return fail ? fail(value) : undefined; } } function stringifyArrayReplacer(key, value, stack, replacer, spacer, indentation) { if (typeof value === 'object' && value !== null && typeof value.toJSON === 'function') { value = value.toJSON(key); } switch (typeof value) { case 'string': return strEscape(value); case 'object': { if (value === null) { return 'null'; } if (stack.includes(value)) { return circularValue; } const originalIndentation = indentation; let res = ''; let join = ','; if (Array.isArray(value)) { if (value.length === 0) { return '[]'; } if (maximumDepth < stack.length + 1) { return '"[Array]"'; } stack.push(value); if (spacer !== '') { indentation += spacer; res += `\n${indentation}`; join = `,\n${indentation}`; } const maximumValuesToStringify = Math.min(value.length, maximumBreadth); let i = 0; for (; i < maximumValuesToStringify - 1; i++) { const tmp = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation); res += tmp === undefined ? 'null' : tmp; res += join; } const tmp = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation); res += tmp === undefined ? 'null' : tmp; if (value.length - 1 > maximumBreadth) { const removedKeys = value.length - maximumBreadth - 1; res += `${join}"... ${getItemCount(removedKeys)} not stringified"`; } if (spacer !== '') { res += `\n${originalIndentation}`; } stack.pop(); return `[${res}]`; } stack.push(value); let whitespace = ''; if (spacer !== '') { indentation += spacer; join = `,\n${indentation}`; whitespace = ' '; } let separator = ''; for (const key of replacer) { const tmp = stringifyArrayReplacer(key, value[key], stack, replacer, spacer, indentation); if (tmp !== undefined) { res += `${separator}${strEscape(key)}:${whitespace}${tmp}`; separator = join; } } if (spacer !== '' && separator.length > 1) { res = `\n${indentation}${res}\n${originalIndentation}`; } stack.pop(); return `{${res}}`; } case 'number': return isFinite(value) ? String(value) : fail ? fail(value) : 'null'; case 'boolean': return value ? 'true' : 'false'; case 'undefined': return undefined; case 'bigint': if (bigint) { return String(value); } // fallthrough default: return fail ? fail(value) : undefined; } } function stringifyIndent(key, value, stack, spacer, indentation) { switch (typeof value) { case 'string': return strEscape(value); case 'object': { if (value === null) { return 'null'; } if (typeof value.toJSON === 'function') { value = value.toJSON(key); // Prevent calling `toJSON` again. if (typeof value !== 'object') { return stringifyIndent(key, value, stack, spacer, indentation); } if (value === null) { return 'null'; } } if (stack.includes(value)) { return circularValue; } const originalIndentation = indentation; if (Array.isArray(value)) { if (value.length === 0) { return '[]'; } if (maximumDepth < stack.length + 1) { return '"[Array]"'; } stack.push(value); indentation += spacer; let res = `\n${indentation}`; const join = `,\n${indentation}`; const maximumValuesToStringify = Math.min(value.length, maximumBreadth); let i = 0; for (; i < maximumValuesToStringify - 1; i++) { const tmp = stringifyIndent(String(i), value[i], stack, spacer, indentation); res += tmp === undefined ? 'null' : tmp; res += join; } const tmp = stringifyIndent(String(i), value[i], stack, spacer, indentation); res += tmp === undefined ? 'null' : tmp; if (value.length - 1 > maximumBreadth) { const removedKeys = value.length - maximumBreadth - 1; res += `${join}"... ${getItemCount(removedKeys)} not stringified"`; } res += `\n${originalIndentation}`; stack.pop(); return `[${res}]`; } let keys = Object.keys(value); const keyLength = keys.length; if (keyLength === 0) { return '{}'; } if (maximumDepth < stack.length + 1) { return '"[Object]"'; } indentation += spacer; const join = `,\n${indentation}`; let res = ''; let separator = ''; let maximumPropertiesToStringify = Math.min(keyLength, maximumBreadth); if (isTypedArrayWithEntries(value)) { res += stringifyTypedArray(value, join, maximumBreadth); keys = keys.slice(value.length); maximumPropertiesToStringify -= value.length; separator = join; } if (deterministic) { keys = sort(keys, comparator); } stack.push(value); for (let i = 0; i < maximumPropertiesToStringify; i++) { const key = keys[i]; const tmp = stringifyIndent(key, value[key], stack, spacer, indentation); if (tmp !== undefined) { res += `${separator}${strEscape(key)}: ${tmp}`; separator = join; } } if (keyLength > maximumBreadth) { const removedKeys = keyLength - maximumBreadth; res += `${separator}"...": "${getItemCount(removedKeys)} not stringified"`; separator = join; } if (separator !== '') { res = `\n${indentation}${res}\n${originalIndentation}`; } stack.pop(); return `{${res}}`; } case 'number': return isFinite(value) ? String(value) : fail ? fail(value) : 'null'; case 'boolean': return value ? 'true' : 'false'; case 'undefined': return undefined; case 'bigint': if (bigint) { return String(value); } // fallthrough default: return fail ? fail(value) : undefined; } } function stringifySimple(key, value, stack) { switch (typeof value) { case 'string': return strEscape(value); case 'object': { if (value === null) { return 'null'; } if (typeof value.toJSON === 'function') { value = value.toJSON(key); // Prevent calling `toJSON` again if (typeof value !== 'object') { return stringifySimple(key, value, stack); } if (value === null) { return 'null'; } } if (stack.includes(value)) { return circularValue; } let res = ''; const hasLength = value.length !== undefined; if (hasLength && Array.isArray(value)) { if (value.length === 0) { return '[]'; } if (maximumDepth < stack.length + 1) { return '"[Array]"'; } stack.push(value); const maximumValuesToStringify = Math.min(value.length, maximumBreadth); let i = 0; for (; i < maximumValuesToStringify - 1; i++) { const tmp = stringifySimple(String(i), value[i], stack); res += tmp === undefined ? 'null' : tmp; res += ','; } const tmp = stringifySimple(String(i), value[i], stack); res += tmp === undefined ? 'null' : tmp; if (value.length - 1 > maximumBreadth) { const removedKeys = value.length - maximumBreadth - 1; res += `,"... ${getItemCount(removedKeys)} not stringified"`; } stack.pop(); return `[${res}]`; } let keys = Object.keys(value); const keyLength = keys.length; if (keyLength === 0) { return '{}'; } if (maximumDepth < stack.length + 1) { return '"[Object]"'; } let separator = ''; let maximumPropertiesToStringify = Math.min(keyLength, maximumBreadth); if (hasLength && isTypedArrayWithEntries(value)) { res += stringifyTypedArray(value, ',', maximumBreadth); keys = keys.slice(value.length); maximumPropertiesToStringify -= value.length; separator = ','; } if (deterministic) { keys = sort(keys, comparator); } stack.push(value); for (let i = 0; i < maximumPropertiesToStringify; i++) { const key = keys[i]; const tmp = stringifySimple(key, value[key], stack); if (tmp !== undefined) { res += `${separator}${strEscape(key)}:${tmp}`; separator = ','; } } if (keyLength > maximumBreadth) { const removedKeys = keyLength - maximumBreadth; res += `${separator}"...":"${getItemCount(removedKeys)} not stringified"`; } stack.pop(); return `{${res}}`; } case 'number': return isFinite(value) ? String(value) : fail ? fail(value) : 'null'; case 'boolean': return value ? 'true' : 'false'; case 'undefined': return undefined; case 'bigint': if (bigint) { return String(value); } // fallthrough default: return fail ? fail(value) : undefined; } } function stringify(value, replacer, space) { if (arguments.length > 1) { let spacer = ''; if (typeof space === 'number') { spacer = ' '.repeat(Math.min(space, 10)); } else if (typeof space === 'string') { spacer = space.slice(0, 10); } if (replacer != null) { if (typeof replacer === 'function') { return stringifyFnReplacer('', { '': value }, [], replacer, spacer, ''); } if (Array.isArray(replacer)) { return stringifyArrayReplacer('', value, [], getUniqueReplacerSet(replacer), spacer, ''); } } if (spacer.length !== 0) { return stringifyIndent('', value, [], spacer, ''); } } return stringifySimple('', value, []); } return stringify; }