2025-05-12 05:38:44 +09:00

192 lines
6.4 KiB
JavaScript

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.hash = void 0;
exports.hashString = hashString;
// v8 has an optimization for storing 31-bit signed numbers.
// Values which have either 00 or 11 as the high order bits qualify.
// This function drops the highest order bit in a signed number, maintaining
// the sign bit.
function smi(i32) {
return ((i32 >>> 1) & 0x40000000) | (i32 & 0xbfffffff);
}
const defaultValueOf = Object.prototype.valueOf;
// If possible, use a WeakMap.
const usingWeakMap = typeof WeakMap === "function";
let weakMap;
if (usingWeakMap) {
weakMap = new WeakMap();
}
let objHashUID = 0;
let UID_HASH_KEY = "__immutablehash__";
if (typeof Symbol === "function") {
// @ts-expect-error ts-migrate(2322) FIXME: Type 'symbol' is not assignable to type 'string'.
UID_HASH_KEY = Symbol(UID_HASH_KEY);
}
const STRING_HASH_CACHE_MIN_STRLEN = 16;
const STRING_HASH_CACHE_MAX_SIZE = 255;
let STRING_HASH_CACHE_SIZE = 0;
let stringHashCache = {};
const hash = function hash(o) {
switch (typeof o) {
case "boolean":
// The hash values for built-in constants are a 1 value for each 5-byte
// shift region expect for the first, which encodes the value. This
// reduces the odds of a hash collision for these common values.
return o ? 0x42108421 : 0x42108420;
case "number":
return hashNumber(o);
case "string":
return o.length > STRING_HASH_CACHE_MIN_STRLEN ? cachedHashString(o) : hashString(o);
case "object":
case "function":
if (o === null) {
return 0x42108422;
}
if (typeof o.hashCode === "function") {
// Drop any high bits from accidentally long hash codes.
return smi(o.hashCode(o));
}
if (o.valueOf !== defaultValueOf && typeof o.valueOf === "function") {
o = o.valueOf(o);
}
return hashJSObj(o);
case "undefined":
return 0x42108423;
default:
if (typeof o.toString === "function") {
return hashString(o.toString());
}
throw new Error(`Value type ${typeof o} cannot be hashed.`);
}
};
exports.hash = hash;
// Compress arbitrarily large numbers into smi hashes.
function hashNumber(n) {
if (n !== n || n === Infinity) {
return 0;
}
let hash = n | 0;
if (hash !== n) {
hash ^= n * 0xffffffff;
}
while (n > 0xffffffff) {
n /= 0xffffffff;
hash ^= n;
}
return smi(hash);
}
function cachedHashString(string) {
let hashed = stringHashCache[string];
if (hashed === undefined) {
hashed = hashString(string);
if (STRING_HASH_CACHE_SIZE === STRING_HASH_CACHE_MAX_SIZE) {
STRING_HASH_CACHE_SIZE = 0;
stringHashCache = {};
}
STRING_HASH_CACHE_SIZE++;
stringHashCache[string] = hashed;
}
return hashed;
}
// http://jsperf.com/hashing-strings
function hashString(string) {
// This is the hash from JVM
// The hash code for a string is computed as
// s[0] * 31 ^ (n - 1) + s[1] * 31 ^ (n - 2) + ... + s[n - 1],
// where s[i] is the ith character of the string and n is the length of
// the string. We "mod" the result to make it between 0 (inclusive) and 2^31
// (exclusive) by dropping high bits.
let hashed = 0;
for (let ii = 0; ii < string.length; ii++) {
hashed = (31 * hashed + string.charCodeAt(ii)) | 0;
}
return smi(hashed);
}
function hashJSObj(obj) {
let hashed;
if (usingWeakMap) {
hashed = weakMap.get(obj);
if (hashed !== undefined) {
return hashed;
}
}
hashed = obj[UID_HASH_KEY];
if (hashed !== undefined) {
return hashed;
}
if (!canDefineProperty) {
hashed = obj.propertyIsEnumerable && obj.propertyIsEnumerable[UID_HASH_KEY];
if (hashed !== undefined) {
return hashed;
}
hashed = getIENodeHash(obj);
if (hashed !== undefined) {
return hashed;
}
}
hashed = ++objHashUID;
if (objHashUID & 0x40000000) {
objHashUID = 0;
}
if (usingWeakMap) {
weakMap.set(obj, hashed);
}
else if (isExtensible !== undefined && isExtensible(obj) === false) {
throw new Error("Non-extensible objects are not allowed as keys.");
}
else if (canDefineProperty) {
Object.defineProperty(obj, UID_HASH_KEY, {
enumerable: false,
configurable: false,
writable: false,
value: hashed
});
}
else if (obj.propertyIsEnumerable !== undefined &&
obj.propertyIsEnumerable === obj.constructor.prototype.propertyIsEnumerable) {
// Since we can't define a non-enumerable property on the object
// we'll hijack one of the less-used non-enumerable properties to
// save our hash on it. Since this is a function it will not show up in
// `JSON.stringify` which is what we want.
obj.propertyIsEnumerable = function () {
return this.constructor.prototype.propertyIsEnumerable.apply(this, arguments);
};
obj.propertyIsEnumerable[UID_HASH_KEY] = hashed;
}
else if (obj.nodeType !== undefined) {
// At this point we couldn't get the IE `uniqueID` to use as a hash
// and we couldn't use a non-enumerable property to exploit the
// dontEnum bug so we simply add the `UID_HASH_KEY` on the node
// itself.
obj[UID_HASH_KEY] = hashed;
}
else {
throw new Error("Unable to set a non-enumerable property on object.");
}
return hashed;
}
// Get references to ES5 object methods.
const isExtensible = Object.isExtensible;
// True if Object.defineProperty works as expected. IE8 fails this test.
const canDefineProperty = (function () {
try {
Object.defineProperty({}, "@", {});
return true;
}
catch (e) {
return false;
}
})();
// IE has a `uniqueID` property on DOM nodes. We can construct the hash from it
// and avoid memory leaks from the IE cloneNode bug.
function getIENodeHash(node) {
if (node && node.nodeType > 0) {
switch (node.nodeType) {
case 1: // Element
return node.uniqueID;
case 9: // Document
return node.documentElement && node.documentElement.uniqueID;
}
}
}