diff --git a/src/Hash.js b/src/Hash.ts similarity index 73% rename from src/Hash.js rename to src/Hash.ts index d1c65f7978..069bfe8af7 100644 --- a/src/Hash.js +++ b/src/Hash.ts @@ -2,14 +2,16 @@ import { smi } from './Math'; const defaultValueOf = Object.prototype.valueOf; -export function hash(o) { +export function hash(o: unknown): number { // eslint-disable-next-line eqeqeq if (o == null) { return hashNullish(o); } + // @ts-expect-error don't care about object beeing typed as `{}` here if (typeof o.hashCode === 'function') { // Drop any high bits from accidentally long hash codes. + // @ts-expect-error don't care about object beeing typed as `{}` here return smi(o.hashCode(o)); } @@ -45,12 +47,12 @@ export function hash(o) { } } -function hashNullish(nullish) { +function hashNullish(nullish: null | undefined): number { return nullish === null ? 0x42108422 : /* undefined */ 0x42108423; } // Compress arbitrarily large numbers into smi hashes. -function hashNumber(n) { +function hashNumber(n: number): number { if (n !== n || n === Infinity) { return 0; } @@ -65,7 +67,7 @@ function hashNumber(n) { return smi(hash); } -function cachedHashString(string) { +function cachedHashString(string: string): number { let hashed = stringHashCache[string]; if (hashed === undefined) { hashed = hashString(string); @@ -80,7 +82,7 @@ function cachedHashString(string) { } // http://jsperf.com/hashing-strings -function hashString(string) { +function hashString(string: string): number { // 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], @@ -94,7 +96,7 @@ function hashString(string) { return smi(hashed); } -function hashSymbol(sym) { +function hashSymbol(sym: symbol): number { let hashed = symbolMap[sym]; if (hashed !== undefined) { return hashed; @@ -107,21 +109,25 @@ function hashSymbol(sym) { return hashed; } -function hashJSObj(obj) { - let hashed; +// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type +function hashJSObj(obj: object | Function): number { + let hashed: number | undefined; if (usingWeakMap) { + // @ts-expect-error weakMap is defined hashed = weakMap.get(obj); if (hashed !== undefined) { return hashed; } } + // @ts-expect-error used for old code, will be removed hashed = obj[UID_HASH_KEY]; if (hashed !== undefined) { return hashed; } if (!canDefineProperty) { + // @ts-expect-error used for old code, will be removed hashed = obj.propertyIsEnumerable && obj.propertyIsEnumerable[UID_HASH_KEY]; if (hashed !== undefined) { return hashed; @@ -136,6 +142,7 @@ function hashJSObj(obj) { hashed = nextHash(); if (usingWeakMap) { + // @ts-expect-error weakMap is defined weakMap.set(obj, hashed); } else if (isExtensible !== undefined && isExtensible(obj) === false) { throw new Error('Non-extensible objects are not allowed as keys.'); @@ -157,15 +164,19 @@ function hashJSObj(obj) { obj.propertyIsEnumerable = function () { return this.constructor.prototype.propertyIsEnumerable.apply( this, + // eslint-disable-next-line prefer-rest-params arguments ); }; + // @ts-expect-error used for old code, will be removed obj.propertyIsEnumerable[UID_HASH_KEY] = hashed; + // @ts-expect-error used for old code, will be removed } 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. + // @ts-expect-error used for old code, will be removed obj[UID_HASH_KEY] = hashed; } else { throw new Error('Unable to set a non-enumerable property on object.'); @@ -178,6 +189,7 @@ function hashJSObj(obj) { const isExtensible = Object.isExtensible; // True if Object.defineProperty works as expected. IE8 fails this test. +// TODO remove this as widely available https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty const canDefineProperty = (function () { try { Object.defineProperty({}, '@', {}); @@ -190,24 +202,30 @@ const canDefineProperty = (function () { // 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) { +// TODO remove this method as only used if `canDefineProperty` is false +function getIENodeHash(node: unknown): number | undefined { + // @ts-expect-error don't care if (node && node.nodeType > 0) { + // @ts-expect-error don't care switch (node.nodeType) { case 1: // Element + // @ts-expect-error don't care return node.uniqueID; case 9: // Document + // @ts-expect-error don't care return node.documentElement && node.documentElement.uniqueID; } } } -function valueOf(obj) { +function valueOf(obj: object): unknown { return obj.valueOf !== defaultValueOf && typeof obj.valueOf === 'function' - ? obj.valueOf(obj) + ? // @ts-expect-error weird the "obj" parameter as `valueOf` should not have a parameter + obj.valueOf(obj) : obj; } -function nextHash() { +function nextHash(): number { const nextHash = ++_objHashUID; if (_objHashUID & 0x40000000) { _objHashUID = 0; @@ -216,8 +234,9 @@ function nextHash() { } // If possible, use a WeakMap. +// TODO using WeakMap should be true everywhere now that WeakMap is widely supported: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap const usingWeakMap = typeof WeakMap === 'function'; -let weakMap; +let weakMap: WeakMap | undefined; if (usingWeakMap) { weakMap = new WeakMap(); } @@ -226,7 +245,8 @@ const symbolMap = Object.create(null); let _objHashUID = 0; -let UID_HASH_KEY = '__immutablehash__'; +// TODO remove string as Symbol is now widely supported: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol +let UID_HASH_KEY: string | symbol = '__immutablehash__' as const; if (typeof Symbol === 'function') { UID_HASH_KEY = Symbol(UID_HASH_KEY); } @@ -234,4 +254,4 @@ if (typeof Symbol === 'function') { const STRING_HASH_CACHE_MIN_STRLEN = 16; const STRING_HASH_CACHE_MAX_SIZE = 255; let STRING_HASH_CACHE_SIZE = 0; -let stringHashCache = {}; +let stringHashCache: { [key: string]: number } = {}; diff --git a/src/Iterator.js b/src/Iterator.js deleted file mode 100644 index 1954a3661c..0000000000 --- a/src/Iterator.js +++ /dev/null @@ -1,84 +0,0 @@ -export const ITERATE_KEYS = 0; -export const ITERATE_VALUES = 1; -export const ITERATE_ENTRIES = 2; - -const REAL_ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator; -const FAUX_ITERATOR_SYMBOL = '@@iterator'; - -export const ITERATOR_SYMBOL = REAL_ITERATOR_SYMBOL || FAUX_ITERATOR_SYMBOL; - -export class Iterator { - constructor(next) { - this.next = next; - } - - toString() { - return '[Iterator]'; - } -} - -Iterator.KEYS = ITERATE_KEYS; -Iterator.VALUES = ITERATE_VALUES; -Iterator.ENTRIES = ITERATE_ENTRIES; - -Iterator.prototype.inspect = Iterator.prototype.toSource = function () { - return this.toString(); -}; -Iterator.prototype[ITERATOR_SYMBOL] = function () { - return this; -}; - -export function iteratorValue(type, k, v, iteratorResult) { - const value = - type === ITERATE_KEYS ? k : type === ITERATE_VALUES ? v : [k, v]; - // eslint-disable-next-line @typescript-eslint/no-unused-expressions -- TODO enable eslint here - iteratorResult - ? (iteratorResult.value = value) - : (iteratorResult = { - value: value, - done: false, - }); - return iteratorResult; -} - -export function iteratorDone() { - return { value: undefined, done: true }; -} - -export function hasIterator(maybeIterable) { - if (Array.isArray(maybeIterable)) { - // IE11 trick as it does not support `Symbol.iterator` - return true; - } - - return !!getIteratorFn(maybeIterable); -} - -export function isIterator(maybeIterator) { - return maybeIterator && typeof maybeIterator.next === 'function'; -} - -export function getIterator(iterable) { - const iteratorFn = getIteratorFn(iterable); - return iteratorFn && iteratorFn.call(iterable); -} - -function getIteratorFn(iterable) { - const iteratorFn = - iterable && - ((REAL_ITERATOR_SYMBOL && iterable[REAL_ITERATOR_SYMBOL]) || - iterable[FAUX_ITERATOR_SYMBOL]); - if (typeof iteratorFn === 'function') { - return iteratorFn; - } -} - -export function isEntriesIterable(maybeIterable) { - const iteratorFn = getIteratorFn(maybeIterable); - return iteratorFn && iteratorFn === maybeIterable.entries; -} - -export function isKeysIterable(maybeIterable) { - const iteratorFn = getIteratorFn(maybeIterable); - return iteratorFn && iteratorFn === maybeIterable.keys; -} diff --git a/src/Iterator.ts b/src/Iterator.ts new file mode 100644 index 0000000000..6c3f0411db --- /dev/null +++ b/src/Iterator.ts @@ -0,0 +1,152 @@ +export const ITERATE_KEYS = 0; +export const ITERATE_VALUES = 1; +export const ITERATE_ENTRIES = 2; + +type IteratorType = + | typeof ITERATE_KEYS + | typeof ITERATE_VALUES + | typeof ITERATE_ENTRIES; + +// TODO Symbol is widely available in modern JavaScript environments, clean this +const REAL_ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator; +const FAUX_ITERATOR_SYMBOL = '@@iterator'; + +export const ITERATOR_SYMBOL: string | symbol = + REAL_ITERATOR_SYMBOL || FAUX_ITERATOR_SYMBOL; + +// @ts-expect-error: properties are not supported in buble +export class Iterator implements globalThis.Iterator { + // TODO activate when using babel as buble does not support static class fields + // static KEYS: number; + // static VALUES: number; + // static ENTRIES: number; + // next: () => IteratorResult; + // inspect!: () => string; + // toSource!: () => string; + + constructor(next: () => IteratorResult) { + // @ts-expect-error: properties are not supported in buble + this.next = next; + } + + toString() { + return '[Iterator]'; + } +} + +// @ts-expect-error: static properties are not supported in buble +Iterator.KEYS = ITERATE_KEYS; +// @ts-expect-error: static properties are not supported in buble +Iterator.VALUES = ITERATE_VALUES; +// @ts-expect-error: static properties are not supported in buble +Iterator.ENTRIES = ITERATE_ENTRIES; + +// @ts-expect-error: properties are not supported in buble +Iterator.prototype.inspect = Iterator.prototype.toSource = function () { + return this.toString(); +}; +// @ts-expect-error don't know how to type this +Iterator.prototype[ITERATOR_SYMBOL] = function () { + return this; +}; + +export function iteratorValue( + type: IteratorType, + k: K, + v?: undefined, + iteratorResult?: IteratorResult +): IteratorResult | undefined; +export function iteratorValue( + type: IteratorType, + k: K, + v: V, + iteratorResult?: IteratorResult +): IteratorResult | undefined; +export function iteratorValue( + type: typeof ITERATE_ENTRIES, + k: K, + v?: V, + iteratorResult?: IteratorResult<[K, V]> +): IteratorResult<[K, V]> | undefined; +export function iteratorValue( + type: IteratorType, + k: K, + v?: V, + iteratorResult?: + | IteratorResult + | IteratorResult + | IteratorResult<[K, V]> +): IteratorResult | IteratorResult | IteratorResult<[K, V]> | undefined { + const value = + type === ITERATE_KEYS ? k : type === ITERATE_VALUES ? v : [k, v]; + // eslint-disable-next-line @typescript-eslint/no-unused-expressions -- TODO enable eslint here + iteratorResult + ? (iteratorResult.value = value) + : (iteratorResult = { + // @ts-expect-error ensure value is not undefined + value: value, + done: false, + }); + + return iteratorResult; +} + +export function iteratorDone(): IteratorReturnResult { + return { value: undefined, done: true }; +} + +export function hasIterator( + maybeIterable: unknown +): maybeIterable is Iterable { + if (Array.isArray(maybeIterable)) { + // IE11 trick as it does not support `Symbol.iterator` + return true; + } + + return !!getIteratorFn(maybeIterable); +} + +export function isIterator( + maybeIterator: unknown +): maybeIterator is Iterator { + return !!( + maybeIterator && + // @ts-expect-error: maybeIterator is typed as `{}` + typeof maybeIterator.next === 'function' + ); +} + +export function getIterator(iterable: unknown): Iterator | undefined { + const iteratorFn = getIteratorFn(iterable); + return iteratorFn && iteratorFn.call(iterable); +} + +function getIteratorFn( + iterable: unknown +): (() => Iterator) | undefined { + const iteratorFn = + iterable && + // @ts-expect-error: maybeIterator is typed as `{}` + ((REAL_ITERATOR_SYMBOL && iterable[REAL_ITERATOR_SYMBOL]) || + // @ts-expect-error: maybeIterator is typed as `{}` + iterable[FAUX_ITERATOR_SYMBOL]); + if (typeof iteratorFn === 'function') { + return iteratorFn; + } +} + +export function isEntriesIterable( + maybeIterable: unknown +): maybeIterable is Iterable<[unknown, unknown]> { + const iteratorFn = getIteratorFn(maybeIterable); + // @ts-expect-error: maybeIterator is typed as `{}` + return iteratorFn && iteratorFn === maybeIterable.entries; +} + +export function isKeysIterable( + maybeIterable: unknown +): maybeIterable is Iterable { + const iteratorFn = getIteratorFn(maybeIterable); + // @ts-expect-error: maybeIterator is typed as `{}` + return iteratorFn && iteratorFn === maybeIterable.keys; +} diff --git a/src/Math.js b/src/Math.ts similarity index 72% rename from src/Math.js rename to src/Math.ts index 0075e73cd3..d1ebb53ad8 100644 --- a/src/Math.js +++ b/src/Math.ts @@ -1,7 +1,8 @@ +// TODO remove in v6 as Math.imul is widely available now: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/imul export const imul = typeof Math.imul === 'function' && Math.imul(0xffffffff, 2) === -2 ? Math.imul - : function imul(a, b) { + : function imul(a: number, b: number): number { a |= 0; // int b |= 0; // int const c = a & 0xffff; @@ -14,6 +15,6 @@ export const imul = // 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. -export function smi(i32) { +export function smi(i32: number): number { return ((i32 >>> 1) & 0x40000000) | (i32 & 0xbfffffff); } diff --git a/src/PairSorting.js b/src/PairSorting.js deleted file mode 100644 index a8d6e4e240..0000000000 --- a/src/PairSorting.js +++ /dev/null @@ -1,4 +0,0 @@ -export const PairSorting = { - LeftThenRight: -1, - RightThenLeft: +1, -}; diff --git a/src/PairSorting.ts b/src/PairSorting.ts new file mode 100644 index 0000000000..3d53df4a1d --- /dev/null +++ b/src/PairSorting.ts @@ -0,0 +1,7 @@ +/** + * Describes which item in a pair should be placed first when sorting + */ +export const PairSorting = { + LeftThenRight: -1, + RightThenLeft: +1, +} as const; diff --git a/src/toJS.js b/src/toJS.js deleted file mode 100644 index 2d820416c8..0000000000 --- a/src/toJS.js +++ /dev/null @@ -1,28 +0,0 @@ -import { Seq } from './Seq'; -import { isCollection } from './predicates/isCollection'; -import { isKeyed } from './predicates/isKeyed'; -import isDataStructure from './utils/isDataStructure'; - -export function toJS(value) { - if (!value || typeof value !== 'object') { - return value; - } - if (!isCollection(value)) { - if (!isDataStructure(value)) { - return value; - } - value = Seq(value); - } - if (isKeyed(value)) { - const result = {}; - value.__iterate((v, k) => { - result[k] = toJS(v); - }); - return result; - } - const result = []; - value.__iterate((v) => { - result.push(toJS(v)); - }); - return result; -} diff --git a/src/toJS.ts b/src/toJS.ts new file mode 100644 index 0000000000..aec59fbba9 --- /dev/null +++ b/src/toJS.ts @@ -0,0 +1,39 @@ +import type { Collection } from './Collection'; +import type { Record } from './Record'; +import { Seq } from './Seq'; +import { isCollection } from './predicates/isCollection'; +import { isKeyed } from './predicates/isKeyed'; +import isDataStructure from './utils/isDataStructure'; + +export function toJS( + value: Collection | Record +): Array | { [key: string]: unknown }; +export function toJS(value: unknown): unknown; +export function toJS( + value: unknown +): Array | { [key: string]: unknown } | unknown { + if (!value || typeof value !== 'object') { + return value; + } + if (!isCollection(value)) { + if (!isDataStructure(value)) { + return value; + } + // @ts-expect-error until Seq has been migrated to TypeScript + value = Seq(value); + } + if (isKeyed(value)) { + const result: { [key: string]: unknown } = {}; + // @ts-expect-error `__iterate` exists on all Keyed collections but method is not defined in the type + value.__iterate((v, k) => { + result[k] = toJS(v); + }); + return result; + } + const result: Array = []; + // @ts-expect-error value "should" be a non-keyed collection, but we may need to assert for stricter types + value.__iterate((v: unknown) => { + result.push(toJS(v)); + }); + return result; +}