diff options
| author | Adam Mathes <adam@adammathes.com> | 2026-02-13 21:34:48 -0800 |
|---|---|---|
| committer | Adam Mathes <adam@adammathes.com> | 2026-02-13 21:34:48 -0800 |
| commit | 76cb9c2a39d477a64824a985ade40507e3bbade1 (patch) | |
| tree | 41e997aa9c6f538d3a136af61dae9424db2005a9 /vanilla/node_modules/webidl-conversions/lib/index.js | |
| parent | 819a39a21ac992b1393244a4c283bbb125208c69 (diff) | |
| download | neko-76cb9c2a39d477a64824a985ade40507e3bbade1.tar.gz neko-76cb9c2a39d477a64824a985ade40507e3bbade1.tar.bz2 neko-76cb9c2a39d477a64824a985ade40507e3bbade1.zip | |
feat(vanilla): add testing infrastructure and tests (NK-wjnczv)
Diffstat (limited to 'vanilla/node_modules/webidl-conversions/lib/index.js')
| -rw-r--r-- | vanilla/node_modules/webidl-conversions/lib/index.js | 436 |
1 files changed, 436 insertions, 0 deletions
diff --git a/vanilla/node_modules/webidl-conversions/lib/index.js b/vanilla/node_modules/webidl-conversions/lib/index.js new file mode 100644 index 0000000..2b96a69 --- /dev/null +++ b/vanilla/node_modules/webidl-conversions/lib/index.js @@ -0,0 +1,436 @@ +"use strict"; + +function makeException(ErrorType, message, options) { + if (options.globals) { + ErrorType = options.globals[ErrorType.name]; + } + return new ErrorType(`${options.context ? options.context : "Value"} ${message}.`); +} + +function toNumber(value, options) { + if (typeof value === "bigint") { + throw makeException(TypeError, "is a BigInt which cannot be converted to a number", options); + } + if (!options.globals) { + return Number(value); + } + return options.globals.Number(value); +} + +// Round x to the nearest integer, choosing the even integer if it lies halfway between two. +function evenRound(x) { + // There are four cases for numbers with fractional part being .5: + // + // case | x | floor(x) | round(x) | expected | x <> 0 | x % 1 | x & 1 | example + // 1 | 2n + 0.5 | 2n | 2n + 1 | 2n | > | 0.5 | 0 | 0.5 -> 0 + // 2 | 2n + 1.5 | 2n + 1 | 2n + 2 | 2n + 2 | > | 0.5 | 1 | 1.5 -> 2 + // 3 | -2n - 0.5 | -2n - 1 | -2n | -2n | < | -0.5 | 0 | -0.5 -> 0 + // 4 | -2n - 1.5 | -2n - 2 | -2n - 1 | -2n - 2 | < | -0.5 | 1 | -1.5 -> -2 + // (where n is a non-negative integer) + // + // Branch here for cases 1 and 4 + if ((x > 0 && (x % 1) === +0.5 && (x & 1) === 0) || + (x < 0 && (x % 1) === -0.5 && (x & 1) === 1)) { + return censorNegativeZero(Math.floor(x)); + } + + return censorNegativeZero(Math.round(x)); +} + +function integerPart(n) { + return censorNegativeZero(Math.trunc(n)); +} + +function sign(x) { + return x < 0 ? -1 : 1; +} + +function modulo(x, y) { + // https://tc39.github.io/ecma262/#eqn-modulo + // Note that http://stackoverflow.com/a/4467559/3191 does NOT work for large modulos + const signMightNotMatch = x % y; + if (sign(y) !== sign(signMightNotMatch)) { + return signMightNotMatch + y; + } + return signMightNotMatch; +} + +function censorNegativeZero(x) { + return x === 0 ? 0 : x; +} + +function createIntegerConversion(bitLength, { unsigned }) { + let lowerBound, upperBound; + if (unsigned) { + lowerBound = 0; + upperBound = 2 ** bitLength - 1; + } else { + lowerBound = -(2 ** (bitLength - 1)); + upperBound = 2 ** (bitLength - 1) - 1; + } + + const twoToTheBitLength = 2 ** bitLength; + const twoToOneLessThanTheBitLength = 2 ** (bitLength - 1); + + return (value, options = {}) => { + let x = toNumber(value, options); + x = censorNegativeZero(x); + + if (options.enforceRange) { + if (!Number.isFinite(x)) { + throw makeException(TypeError, "is not a finite number", options); + } + + x = integerPart(x); + + if (x < lowerBound || x > upperBound) { + throw makeException( + TypeError, + `is outside the accepted range of ${lowerBound} to ${upperBound}, inclusive`, + options + ); + } + + return x; + } + + if (!Number.isNaN(x) && options.clamp) { + x = Math.min(Math.max(x, lowerBound), upperBound); + x = evenRound(x); + return x; + } + + if (!Number.isFinite(x) || x === 0) { + return 0; + } + x = integerPart(x); + + // Math.pow(2, 64) is not accurately representable in JavaScript, so try to avoid these per-spec operations if + // possible. Hopefully it's an optimization for the non-64-bitLength cases too. + if (x >= lowerBound && x <= upperBound) { + return x; + } + + // These will not work great for bitLength of 64, but oh well. See the README for more details. + x = modulo(x, twoToTheBitLength); + if (!unsigned && x >= twoToOneLessThanTheBitLength) { + return x - twoToTheBitLength; + } + return x; + }; +} + +function createLongLongConversion(bitLength, { unsigned }) { + const upperBound = Number.MAX_SAFE_INTEGER; + const lowerBound = unsigned ? 0 : Number.MIN_SAFE_INTEGER; + const asBigIntN = unsigned ? BigInt.asUintN : BigInt.asIntN; + + return (value, options = {}) => { + let x = toNumber(value, options); + x = censorNegativeZero(x); + + if (options.enforceRange) { + if (!Number.isFinite(x)) { + throw makeException(TypeError, "is not a finite number", options); + } + + x = integerPart(x); + + if (x < lowerBound || x > upperBound) { + throw makeException( + TypeError, + `is outside the accepted range of ${lowerBound} to ${upperBound}, inclusive`, + options + ); + } + + return x; + } + + if (!Number.isNaN(x) && options.clamp) { + x = Math.min(Math.max(x, lowerBound), upperBound); + x = evenRound(x); + return x; + } + + if (!Number.isFinite(x) || x === 0) { + return 0; + } + + let xBigInt = BigInt(integerPart(x)); + xBigInt = asBigIntN(bitLength, xBigInt); + return Number(xBigInt); + }; +} + +exports.any = value => { + return value; +}; + +exports.undefined = () => { + return undefined; +}; + +exports.boolean = value => { + return Boolean(value); +}; + +exports.byte = createIntegerConversion(8, { unsigned: false }); +exports.octet = createIntegerConversion(8, { unsigned: true }); + +exports.short = createIntegerConversion(16, { unsigned: false }); +exports["unsigned short"] = createIntegerConversion(16, { unsigned: true }); + +exports.long = createIntegerConversion(32, { unsigned: false }); +exports["unsigned long"] = createIntegerConversion(32, { unsigned: true }); + +exports["long long"] = createLongLongConversion(64, { unsigned: false }); +exports["unsigned long long"] = createLongLongConversion(64, { unsigned: true }); + +exports.double = (value, options = {}) => { + const x = toNumber(value, options); + + if (!Number.isFinite(x)) { + throw makeException(TypeError, "is not a finite floating-point value", options); + } + + return x; +}; + +exports["unrestricted double"] = (value, options = {}) => { + const x = toNumber(value, options); + + return x; +}; + +exports.float = (value, options = {}) => { + const x = toNumber(value, options); + + if (!Number.isFinite(x)) { + throw makeException(TypeError, "is not a finite floating-point value", options); + } + + if (Object.is(x, -0)) { + return x; + } + + const y = Math.fround(x); + + if (!Number.isFinite(y)) { + throw makeException(TypeError, "is outside the range of a single-precision floating-point value", options); + } + + return y; +}; + +exports["unrestricted float"] = (value, options = {}) => { + const x = toNumber(value, options); + + if (isNaN(x)) { + return x; + } + + if (Object.is(x, -0)) { + return x; + } + + return Math.fround(x); +}; + +exports.DOMString = (value, options = {}) => { + if (options.treatNullAsEmptyString && value === null) { + return ""; + } + + if (typeof value === "symbol") { + throw makeException(TypeError, "is a symbol, which cannot be converted to a string", options); + } + + const StringCtor = options.globals ? options.globals.String : String; + return StringCtor(value); +}; + +exports.ByteString = (value, options = {}) => { + const x = exports.DOMString(value, options); + + // A Unicode regexp gives identical results here, but is ~5x slower on 16-bit strings: + // https://issues.chromium.org/issues/472892241 + // eslint-disable-next-line require-unicode-regexp + if (/[^\x00-\xFF]/.test(x)) { + throw makeException(TypeError, "is not a valid ByteString", options); + } + + return x; +}; + +exports.USVString = (value, options = {}) => { + return exports.DOMString(value, options).toWellFormed(); +}; + +exports.object = (value, options = {}) => { + if (value === null || (typeof value !== "object" && typeof value !== "function")) { + throw makeException(TypeError, "is not an object", options); + } + + return value; +}; + +const abByteLengthGetter = Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, "byteLength").get; +const sabByteLengthGetter = Object.getOwnPropertyDescriptor(SharedArrayBuffer.prototype, "byteLength").get; + +function isNonSharedArrayBuffer(value) { + try { + abByteLengthGetter.call(value); + return true; + } catch { + return false; + } +} + +function isSharedArrayBuffer(value) { + try { + sabByteLengthGetter.call(value); + return true; + } catch { + return false; + } +} + +const abResizableGetter = Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, "resizable").get; +const sabGrowableGetter = Object.getOwnPropertyDescriptor(SharedArrayBuffer.prototype, "growable").get; + +function isNonSharedArrayBufferResizable(value) { + try { + return abResizableGetter.call(value); + } catch { + return false; + } +} + +function isSharedArrayBufferGrowable(value) { + try { + return sabGrowableGetter.call(value); + } catch { + return false; + } +} + +function isArrayBufferDetached(value) { + try { + // eslint-disable-next-line no-new + new Uint8Array(value); + return false; + } catch { + return true; + } +} + +exports.ArrayBuffer = (value, options = {}) => { + if (!isNonSharedArrayBuffer(value)) { + throw makeException(TypeError, "is not an ArrayBuffer", options); + } + if (!options.allowResizable && isNonSharedArrayBufferResizable(value)) { + throw makeException(TypeError, "is a resizable ArrayBuffer", options); + } + if (isArrayBufferDetached(value)) { + throw makeException(TypeError, "is a detached ArrayBuffer", options); + } + + return value; +}; + +exports.SharedArrayBuffer = (value, options = {}) => { + if (!isSharedArrayBuffer(value)) { + throw makeException(TypeError, "is not a SharedArrayBuffer", options); + } + if (!options.allowResizable && isSharedArrayBufferGrowable(value)) { + throw makeException(TypeError, "is a growable SharedArrayBuffer", options); + } + + return value; +}; + +const dvByteLengthGetter = + Object.getOwnPropertyDescriptor(DataView.prototype, "byteLength").get; +exports.DataView = (value, options = {}) => { + try { + dvByteLengthGetter.call(value); + } catch { + throw makeException(TypeError, "is not a DataView", options); + } + return exports.ArrayBufferView(value, options); +}; + +// Returns the unforgeable `TypedArray` constructor name or `undefined`, +// if the `this` value isn't a valid `TypedArray` object. +// +// https://tc39.es/ecma262/#sec-get-%typedarray%.prototype-@@tostringtag +const typedArrayNameGetter = Object.getOwnPropertyDescriptor( + Object.getPrototypeOf(Uint8Array).prototype, + Symbol.toStringTag +).get; +[ + Int8Array, + Int16Array, + Int32Array, + Uint8Array, + Uint16Array, + Uint32Array, + Uint8ClampedArray, + Float32Array, + Float64Array +].forEach(func => { + const { name } = func; + const article = /^[AEIOU]/u.test(name) ? "an" : "a"; + exports[name] = (value, options = {}) => { + if (!ArrayBuffer.isView(value) || typedArrayNameGetter.call(value) !== name) { + throw makeException(TypeError, `is not ${article} ${name} object`, options); + } + return exports.ArrayBufferView(value, options); + }; +}); + +// Common definitions + +exports.ArrayBufferView = (value, options = {}) => { + if (!ArrayBuffer.isView(value)) { + throw makeException(TypeError, "is not a view on an ArrayBuffer or SharedArrayBuffer", options); + } + + if (!options.allowShared && isSharedArrayBuffer(value.buffer)) { + throw makeException(TypeError, "is a view on a SharedArrayBuffer, which is not allowed", options); + } + + if (!options.allowResizable) { + if (isNonSharedArrayBufferResizable(value.buffer)) { + throw makeException(TypeError, "is a view on a resizable ArrayBuffer, which is not allowed", options); + } else if (isSharedArrayBufferGrowable(value.buffer)) { + throw makeException(TypeError, "is a view on a growable SharedArrayBuffer, which is not allowed", options); + } + } + + if (isArrayBufferDetached(value.buffer)) { + throw makeException(TypeError, "is a view on a detached ArrayBuffer", options); + } + return value; +}; + +exports.BufferSource = (value, options = {}) => { + if (ArrayBuffer.isView(value)) { + return exports.ArrayBufferView(value, options); + } + + if (isNonSharedArrayBuffer(value)) { + return exports.ArrayBuffer(value, options); + } else if (options.allowShared && isSharedArrayBuffer(value)) { + return exports.SharedArrayBuffer(value, options); + } + + if (options.allowShared) { + throw makeException(TypeError, "is not an ArrayBuffer, SharedArrayBuffer, or a view on one", options); + } else { + throw makeException(TypeError, "is not an ArrayBuffer or a view on one", options); + } +}; + +exports.DOMTimeStamp = exports["unsigned long long"]; |
