192 lines
6.4 KiB
JavaScript
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;
|
|
}
|
|
}
|
|
}
|