From 3780a6621fac0365f85f97813e802e3e2de4bb68 Mon Sep 17 00:00:00 2001 From: nev21 <82737406+nev21@users.noreply.github.com> Date: Mon, 5 Feb 2024 23:19:02 -0800 Subject: [PATCH] Stop using internal functions for diagnostic to help with base minification for smaller usages (#239) - Reduce base minification usage --- README.md | 14 +- lib/src/funcs/fnProxy.ts | 4 +- lib/src/funcs/readArgs.ts | 10 +- lib/src/helpers/base.ts | 6 +- lib/src/helpers/cache.ts | 89 +++++++++ lib/src/helpers/customError.ts | 35 ++-- lib/src/helpers/diagnostics.ts | 72 +++++-- lib/src/helpers/encode.ts | 4 +- lib/src/helpers/environment.ts | 41 ++-- lib/src/helpers/extend.ts | 6 +- lib/src/helpers/lazy.ts | 23 +-- lib/src/helpers/perf.ts | 5 +- lib/src/helpers/safe.ts | 68 +++++++ lib/src/helpers/safe_get.ts | 13 +- lib/src/helpers/safe_lazy.ts | 8 +- lib/src/index.ts | 2 + lib/src/internal/constants.ts | 1 + lib/src/internal/global.ts | 7 +- lib/src/internal/unwrapFunction.ts | 31 +-- lib/src/iterator/forOf.ts | 6 +- lib/src/object/create.ts | 3 +- lib/src/object/define.ts | 1 + lib/src/object/has_own_prop.ts | 2 +- lib/src/object/is_plain_object.ts | 4 +- lib/src/object/object.ts | 10 +- lib/src/polyfills/object.ts | 3 +- lib/src/polyfills/symbol.ts | 10 +- lib/src/string/ends_with.ts | 6 +- lib/src/string/starts_with.ts | 3 +- lib/src/string/substring.ts | 7 +- lib/src/symbol/symbol.ts | 32 ++-- lib/src/timer/interval.ts | 4 +- lib/src/timer/timeout.ts | 13 +- lib/test/src/common/helpers/args.test.ts | 12 ++ lib/test/src/common/helpers/cache.test.ts | 181 ++++++++++++++++++ .../src/common/helpers/diagnostics.test.ts | 126 +++++++++++- lib/test/src/common/helpers/symbol.test.ts | 25 ++- .../common/internal/unwrapFunction.test.ts | 10 +- lib/test/src/common/object/object.test.ts | 21 +- lib/test/src/common/polyfills/split.test.ts | 15 +- package.json | 8 +- 41 files changed, 724 insertions(+), 217 deletions(-) create mode 100644 lib/src/helpers/cache.ts create mode 100644 lib/src/helpers/safe.ts create mode 100644 lib/test/src/common/helpers/cache.test.ts diff --git a/README.md b/README.md index 5bc184cf..8b9edbc7 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,8 @@ This is a collection of general JavaScript functions (written in and for TypeScr Support for standard JavaScript functions (ES5+) that are not support in all environments will be backed by internal polyfill implementations when not available. -### Test Environments +### Test Environments + - Node (16, 18, 20) - Browser (Chromium - headless) - Web Worker (Chromium - headless) @@ -29,7 +30,7 @@ See [Browser Support](#browser-support) for details. | Type | Functions / Helpers / Aliases / Polyfills |----------------------------|--------------------------------------------------- -| Runtime Environment Checks | [getDocument](https://nevware21.github.io/ts-utils/typedoc/functions/getDocument.html)(); [getGlobal](https://nevware21.github.io/ts-utils/typedoc/functions/getGlobal.html)(); [getHistory](https://nevware21.github.io/ts-utils/typedoc/functions/getHistory.html)(); [getInst](https://nevware21.github.io/ts-utils/typedoc/functions/getInst.html)(); [getNavigator](https://nevware21.github.io/ts-utils/typedoc/functions/getNavigator.html)(); [getPerformance](https://nevware21.github.io/ts-utils/typedoc/functions/getPerformance.html)(); [getWindow](https://nevware21.github.io/ts-utils/typedoc/functions/getWindow.html)(); [hasDocument](https://nevware21.github.io/ts-utils/typedoc/functions/hasDocument.html)(); [hasHistory](https://nevware21.github.io/ts-utils/typedoc/functions/hasHistory.html)(); [hasNavigator](https://nevware21.github.io/ts-utils/typedoc/functions/hasNavigator.html)(); [hasPerformance](https://nevware21.github.io/ts-utils/typedoc/functions/hasPerformance.html)(); [hasWindow](https://nevware21.github.io/ts-utils/typedoc/functions/hasWindow.html)(); [isNode](https://nevware21.github.io/ts-utils/typedoc/functions/isNode.html)(); [isWebWorker](https://nevware21.github.io/ts-utils/typedoc/functions/isWebWorker.html)(); [hasIdleCallback](https://nevware21.github.io/ts-utils/typedoc/functions/hasIdleCallback.html)(); [lazySafeGetInst](https://nevware21.github.io/ts-utils/typedoc/functions/lazySafeGetInst.html)(); +| Runtime Environment Checks | [getDocument](https://nevware21.github.io/ts-utils/typedoc/functions/getDocument.html)(); [getGlobal](https://nevware21.github.io/ts-utils/typedoc/functions/getGlobal.html)(); [getHistory](https://nevware21.github.io/ts-utils/typedoc/functions/getHistory.html)(); [getInst](https://nevware21.github.io/ts-utils/typedoc/functions/getInst.html)(); [getNavigator](https://nevware21.github.io/ts-utils/typedoc/functions/getNavigator.html)(); [getPerformance](https://nevware21.github.io/ts-utils/typedoc/functions/getPerformance.html)(); [getWindow](https://nevware21.github.io/ts-utils/typedoc/functions/getWindow.html)(); [hasDocument](https://nevware21.github.io/ts-utils/typedoc/functions/hasDocument.html)(); [hasHistory](https://nevware21.github.io/ts-utils/typedoc/functions/hasHistory.html)(); [hasNavigator](https://nevware21.github.io/ts-utils/typedoc/functions/hasNavigator.html)(); [hasPerformance](https://nevware21.github.io/ts-utils/typedoc/functions/hasPerformance.html)(); [hasWindow](https://nevware21.github.io/ts-utils/typedoc/functions/hasWindow.html)(); [isNode](https://nevware21.github.io/ts-utils/typedoc/functions/isNode.html)(); [isWebWorker](https://nevware21.github.io/ts-utils/typedoc/functions/isWebWorker.html)(); [hasIdleCallback](https://nevware21.github.io/ts-utils/typedoc/functions/hasIdleCallback.html)(); [lazySafeGetInst](https://nevware21.github.io/ts-utils/typedoc/functions/lazySafeGetInst.html)(); [safeGetInst](https://nevware21.github.io/ts-utils/typedoc/functions/safeGetInst.html)(); | Type Identity | [isArray](https://nevware21.github.io/ts-utils/typedoc/functions/isArray.html)(); [isArrayBuffer](https://nevware21.github.io/ts-utils/typedoc/functions/isArrayBuffer.html)(); [isBlob](https://nevware21.github.io/ts-utils/typedoc/functions/isBlob.html)(); [isBoolean](https://nevware21.github.io/ts-utils/typedoc/functions/isBoolean.html)(); [isDate](https://nevware21.github.io/ts-utils/typedoc/functions/isDate.html)(); [isError](https://nevware21.github.io/ts-utils/typedoc/functions/isError.html)(); [isFile](https://nevware21.github.io/ts-utils/typedoc/functions/isFile.html)(); [isFormData](https://nevware21.github.io/ts-utils/typedoc/functions/isFormData.html)(); [isFunction](https://nevware21.github.io/ts-utils/typedoc/functions/isFunction.html)(); [isIterable](https://nevware21.github.io/ts-utils/typedoc/functions/isIterable.html)(); [isIterator](https://nevware21.github.io/ts-utils/typedoc/functions/isIterator.html)(); [isNullOrUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isNullOrUndefined.html)(); [isNumber](https://nevware21.github.io/ts-utils/typedoc/functions/isNumber.html)(); [isObject](https://nevware21.github.io/ts-utils/typedoc/functions/isObject.html)(); [isPlainObject](https://nevware21.github.io/ts-utils/typedoc/functions/isPlainObject.html)(); [isPrimitive](https://nevware21.github.io/ts-utils/typedoc/functions/isPrimitive.html)(); [isPrimitiveType](https://nevware21.github.io/ts-utils/typedoc/functions/isPrimitiveType.html)(); [isPromise](https://nevware21.github.io/ts-utils/typedoc/functions/isPromise.html)(); [isPromiseLike](https://nevware21.github.io/ts-utils/typedoc/functions/isPromiseLike.html)(); [isThenable](https://nevware21.github.io/ts-utils/typedoc/functions/isThenable.html)(); [isRegExp](https://nevware21.github.io/ts-utils/typedoc/functions/isRegExp.html)(); [isStrictNullOrUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isStrictNullOrUndefined.html)(); [isStrictUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isStrictUndefined.html)(); [isString](https://nevware21.github.io/ts-utils/typedoc/functions/isString.html)(); [isTypeof](https://nevware21.github.io/ts-utils/typedoc/functions/isTypeof.html)(); [isUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isUndefined.html)(); | Value Check | [hasValue](https://nevware21.github.io/ts-utils/typedoc/functions/hasValue.html)(); [isDefined](https://nevware21.github.io/ts-utils/typedoc/functions/isDefined.html)(); [isNotTruthy](https://nevware21.github.io/ts-utils/typedoc/functions/isNotTruthy.html)(); [isNullOrUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isNullOrUndefined.html)(); [isStrictNullOrUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isStrictNullOrUndefined.html)(); [isStrictUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isStrictUndefined.html)(); [isTruthy](https://nevware21.github.io/ts-utils/typedoc/functions/isTruthy.html)(); [isUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isUndefined.html)(); | Value | [getValueByKey](https://nevware21.github.io/ts-utils/typedoc/functions/getValueByKey.html)(); [setValueByKey](https://nevware21.github.io/ts-utils/typedoc/functions/setValueByKey.html)(); [getValueByIter](https://nevware21.github.io/ts-utils/typedoc/functions/getValueByIter.html)(); [setValueByIter](https://nevware21.github.io/ts-utils/typedoc/functions/setValueByIter.html)(); [encodeAsJson](https://nevware21.github.io/ts-utils/typedoc/functions/encodeAsJson.html)(); [encodeAsHtml](https://nevware21.github.io/ts-utils/typedoc/functions/encodeAsHtml.html)(); [asString](https://nevware21.github.io/ts-utils/typedoc/functions/asString.html)(); [getIntValue](https://nevware21.github.io/ts-utils/typedoc/functions/getIntValue.html)(); [normalizeJsName](https://nevware21.github.io/ts-utils/typedoc/functions/normalizeJsName.html)(); @@ -46,8 +47,11 @@ See [Browser Support](#browser-support) for details. | Symbol | [WellKnownSymbols](https://nevware21.github.io/ts-utils/typedoc/enums/WellKnownSymbols.html) (const enum);
[getKnownSymbol](https://nevware21.github.io/ts-utils/typedoc/functions/getKnownSymbol.html)(); [getSymbol](https://nevware21.github.io/ts-utils/typedoc/functions/getSymbol.html)(); [hasSymbol](https://nevware21.github.io/ts-utils/typedoc/functions/hasSymbol.html)(); [isSymbol](https://nevware21.github.io/ts-utils/typedoc/functions/isSymbol.html)(); [newSymbol](https://nevware21.github.io/ts-utils/typedoc/functions/newSymbol.html)(); [symbolFor](https://nevware21.github.io/ts-utils/typedoc/functions/symbolFor.html)(); [symbolKeyFor](https://nevware21.github.io/ts-utils/typedoc/functions/symbolKeyFor.html)();
[polyGetKnownSymbol](https://nevware21.github.io/ts-utils/typedoc/functions/polyGetKnownSymbol.html)(); [polyNewSymbol](https://nevware21.github.io/ts-utils/typedoc/functions/polyNewSymbol.html)(); [polySymbolFor](https://nevware21.github.io/ts-utils/typedoc/functions/polySymbolFor.html)(); [polySymbolKeyFor](https://nevware21.github.io/ts-utils/typedoc/functions/polySymbolKeyFor.html)();

Polyfills are used to automatically backfill runtimes that do not support `Symbol`, not all of the Symbol functionality is provided. | Timer | [elapsedTime](https://nevware21.github.io/ts-utils/typedoc/functions/elapsedTime.html)(); [perfNow](https://nevware21.github.io/ts-utils/typedoc/functions/perfNow.html)(); [utcNow](https://nevware21.github.io/ts-utils/typedoc/functions/utcNow.html)(); [scheduleIdleCallback](https://nevware21.github.io/ts-utils/typedoc/functions/scheduleIdleCallback.html)(); [scheduleInterval](https://nevware21.github.io/ts-utils/typedoc/functions/scheduleInterval.html)(); [scheduleTimeout](https://nevware21.github.io/ts-utils/typedoc/functions/scheduleTimeout.html)(); [scheduleTimeoutWith](https://nevware21.github.io/ts-utils/typedoc/functions/scheduleTimeoutWith.html)(); [hasIdleCallback](https://nevware21.github.io/ts-utils/typedoc/functions/hasIdleCallback.html)();
For runtimes that don't support `requestIdleCallback` normal setTimeout() is used with the values from [`setDefaultIdleTimeout`](https://nevware21.github.io/ts-utils/typedoc/functions/setDefaultIdleTimeout.html)() and [`setDefaultMaxExecutionTime`](https://nevware21.github.io/ts-utils/typedoc/functions/setDefaultMaxExecutionTime.html)();
[polyUtcNow](https://nevware21.github.io/ts-utils/typedoc/functions/polyUtcNow.html)(); | Conversion | [encodeAsJson](https://nevware21.github.io/ts-utils/typedoc/functions/encodeAsJson.html)(); [encodeAsHtml](https://nevware21.github.io/ts-utils/typedoc/functions/encodeAsHtml.html)(); [asString](https://nevware21.github.io/ts-utils/typedoc/functions/asString.html)(); [getIntValue](https://nevware21.github.io/ts-utils/typedoc/functions/getIntValue.html)(); [normalizeJsName](https://nevware21.github.io/ts-utils/typedoc/functions/normalizeJsName.html)(); [strLetterCase](https://nevware21.github.io/ts-utils/typedoc/functions/strLetterCase.html)(); [strCamelCase](https://nevware21.github.io/ts-utils/typedoc/functions/strCamelCase.html)(); [strKebabCase](https://nevware21.github.io/ts-utils/typedoc/functions/strKebabCase.html)(); [strSnakeCase](https://nevware21.github.io/ts-utils/typedoc/functions/strSnakeCase.html)(); [strUpper](https://nevware21.github.io/ts-utils/typedoc/functions/strUpper.html)(); [strLower](https://nevware21.github.io/ts-utils/typedoc/functions/strLower.html)(); +| Cache | [createCachedValue](https://nevware21.github.io/ts-utils/typedoc/functions/createCachedValue.html)(); [createDeferredCachedValue](https://nevware21.github.io/ts-utils/typedoc/functions/createDeferredCachedValue.html)(); | Lazy | [getLazy](https://nevware21.github.io/ts-utils/typedoc/functions/getLazy.html)(); [getWritableLazy](https://nevware21.github.io/ts-utils/typedoc/functions/getWritableLazy.html)(); [lazySafeGetInst](https://nevware21.github.io/ts-utils/typedoc/functions/lazySafeGetInst.html)(); [safeGetLazy](https://nevware21.github.io/ts-utils/typedoc/functions/safeGetLazy.html)(); -| Safe | [safeGetLazy](https://nevware21.github.io/ts-utils/typedoc/functions/safeGetLazy.html)(); [safeGet](https://nevware21.github.io/ts-utils/typedoc/functions/safeGet.html)(); [lazySafeGetInst](https://nevware21.github.io/ts-utils/typedoc/functions/lazySafeGetInst.html)(); [safeGetLazy](https://nevware21.github.io/ts-utils/typedoc/functions/safeGetLazy.html)(); +| Safe | [safe](https://nevware21.github.io/ts-utils/typedoc/functions/safe.html)(); [safeGetLazy](https://nevware21.github.io/ts-utils/typedoc/functions/safeGetLazy.html)(); [safeGet](https://nevware21.github.io/ts-utils/typedoc/functions/safeGet.html)(); [lazySafeGetInst](https://nevware21.github.io/ts-utils/typedoc/functions/lazySafeGetInst.html)(); [safeGetLazy](https://nevware21.github.io/ts-utils/typedoc/functions/safeGetLazy.html)(); [safeGetInst](https://nevware21.github.io/ts-utils/typedoc/functions/safeGetInst.html)(); +| Diagnostic | [dumpObj](https://nevware21.github.io/ts-utils/typedoc/functions/dumpObj.html)(); + > Unless otherwise stated in the functions documentation polyfills are used to automatically backfill unsupported functions in older ES5 runtimes @@ -64,6 +68,10 @@ ie. It may or may not be ES6 depending on the runtime landscape and requests rec When we release v2.x the supported browser matrix will also shift as required to match the defined language level supported at that time. +## TypeScript Support + +This library is built using TypeScript v4.9.5 and uses some keywords that where added in v2.8, so while it is recommended that you use at least v4.9.5, but the definitions will require at least v2.8 and therefore this will be the current minimum support version. If there are issues with any versions of TypeScript please open an issue and we will review whether its possible to work around any limitations with any specific features. + ## Quickstart Install the npm packare: `npm install @nevware21/ts-utils --save` diff --git a/lib/src/funcs/fnProxy.ts b/lib/src/funcs/fnProxy.ts index 19df201a..3f508345 100644 --- a/lib/src/funcs/fnProxy.ts +++ b/lib/src/funcs/fnProxy.ts @@ -7,8 +7,8 @@ */ import { arrForEach } from "../array/forEach"; -import { arrSlice } from "../array/slice"; import { isArray, isFunction } from "../helpers/base"; +import { ArrProto, CALL, SLICE } from "../internal/constants"; import { fnApply } from "./fnApply"; import { fnBind } from "./fnBind"; import { ProxyFunctionDef, TypeFuncNames } from "./types"; @@ -66,7 +66,7 @@ export function createFnDeferredProxy any>(hostFn: return function() { // Capture the original arguments passed to the method - var theArgs = arrSlice(arguments); + var theArgs = ArrProto[SLICE][CALL](arguments); let theHost = hostFn(); return fnApply(theHost[funcName] as (...args: any) => any, theHost, theArgs); diff --git a/lib/src/funcs/readArgs.ts b/lib/src/funcs/readArgs.ts index 7a41e7d7..925434fb 100644 --- a/lib/src/funcs/readArgs.ts +++ b/lib/src/funcs/readArgs.ts @@ -12,9 +12,9 @@ import { iterForOf } from "../iterator/forOf"; import { objHasOwn } from "../object/has_own"; import { getKnownSymbol, hasSymbol } from "../symbol/symbol"; import { WellKnownSymbols } from "../symbol/well_known" -import { ILazyValue, getLazy } from "../helpers/lazy" +import { ICachedValue, createCachedValue } from "../helpers/cache"; -let _iterSymbol: ILazyValue; +let _iterSymbol: ICachedValue; /** * Read the arguments from the provided array, iterator / or generator function @@ -85,14 +85,12 @@ let _iterSymbol: ILazyValue; */ /*#__NO_SIDE_EFFECTS__*/ export function readArgs(theArgs: ArrayLike | Iterable, start?: number, end?: number): T[] { - if (!_iterSymbol) { - _iterSymbol = getLazy(() => hasSymbol() && getKnownSymbol(WellKnownSymbols.iterator)); - } - + if (!objHasOwn(theArgs, LENGTH)) { // Does not contain a length property so lets check if it's an iterable // IArgument is both ArrayLike and an iterable, so prefering to treat it as // an array for performance + !_iterSymbol && (_iterSymbol = createCachedValue(hasSymbol() && getKnownSymbol(WellKnownSymbols.iterator))); let iterFn = _iterSymbol.v && theArgs[_iterSymbol.v]; if (iterFn) { let values: T[] = []; diff --git a/lib/src/helpers/base.ts b/lib/src/helpers/base.ts index 8f8d8faf..e981a85d 100644 --- a/lib/src/helpers/base.ts +++ b/lib/src/helpers/base.ts @@ -6,7 +6,7 @@ * Licensed under the MIT license. */ -import { ArrCls, BOOLEAN, CALL, FUNCTION, NULL_VALUE, NUMBER, OBJECT, ObjProto, STRING, UNDEFINED, UNDEF_VALUE } from "../internal/constants"; +import { ArrCls, BOOLEAN, FUNCTION, NULL_VALUE, NUMBER, OBJECT, ObjProto, STRING, UNDEFINED, UNDEF_VALUE } from "../internal/constants"; import { safeGet } from "./safe_get"; const PRIMITIVE_TYPES = [ STRING, NUMBER, BOOLEAN, UNDEFINED, "symbol", "bigint" ]; @@ -67,7 +67,7 @@ export function _createObjIs(theName: string): (value: any) => value is T { */ /*#__NO_SIDE_EFFECTS__*/ export function objToString(value: any): string { - return ObjProto.toString[CALL](value); + return ObjProto.toString.call(value); } /** @@ -564,7 +564,7 @@ export function isPromise(value: any): value is Promise { */ /*#__NO_SIDE_EFFECTS__*/ export function isNotTruthy(value: any) { - return !value || !safeGet(() => (value && (0 + value)), value); + return !value || !isTruthy(value); } /** diff --git a/lib/src/helpers/cache.ts b/lib/src/helpers/cache.ts new file mode 100644 index 00000000..01588702 --- /dev/null +++ b/lib/src/helpers/cache.ts @@ -0,0 +1,89 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2024 Nevware21 + * Licensed under the MIT license. + */ + +import { objDefineProp, objDefineProperties } from "../object/define"; + +/** + * A generic interface for holding a cached value + * @since 0.10.5 + * @group Helpers + * @group Cache + * @typeparam T - The type of the value to be cached + * @example + * ```ts + * let cachedValue: ICachedValue = { + * v: "some value" + * }; + * ``` + */ +export interface ICachedValue { + /** + * Returns the current cached value + */ + v: T +} + +/** + * Create and return a readonly {@link ICachedValue} instance that is populated with the provided value. + * This is useful when you want to cache a previously fetched value and return it without having to re-fetch + * it again. + * This is a lightweight version of {@link getLazy} which does not support any expiration or invalidation, + * it also will not honor the {@link setBypassLazyCache} setting and will always return the provided value. + * @since 0.10.5 + * @group Helpers + * @group Cache + * @typeparam T - The type of the value to be cached + * @param value + * @returns A new {@link ICachedValue} instance which wraps the provided value + * @example + * ```ts + * let cachedValue = createCachedValue("some value"); + * // cachedValue.v === "some value" + * + * JSON.stringify(cachedValue) === '{"v":"some value"}'; + * ``` + */ +/*#__NO_SIDE_EFFECTS__*/ +export function createCachedValue(value: T): ICachedValue { + return objDefineProp({ + toJSON: () => value + } as any, "v", { value }) as ICachedValue; +} + +/** + * Create and return a readonly {@link ICachedValue} instance which will cache and return the value returned + * by the callback function. The callback function will only be called once, multiple access of the value + * will not cause re-execution of the callback as the result from the first call is cached internally. + * This is a lightweight version of {@link getLazy} which does not support any expiration or invalidation, + * it also will not honor the {@link setBypassLazyCache} setting and will always return the provided value. + * @since 0.10.5 + * @group Helpers + * @group Cache + * @typeparam T - The type of the value to be cached + * @param cb - The callback function to fetch the value to be lazily evaluated and cached + * @returns + */ +/*#__NO_SIDE_EFFECTS__*/ +export function createDeferredCachedValue(cb: () => T): ICachedValue { + return objDefineProp({} as ICachedValue, "v", { + get: function () { + let result = cb(); + objDefineProp(this, "v", { + value: result, + enumerable: true + }); + + return result; + }, + enumerable: true, + configurable: true + }); +} + + + diff --git a/lib/src/helpers/customError.ts b/lib/src/helpers/customError.ts index 46cf73b7..343b9a63 100644 --- a/lib/src/helpers/customError.ts +++ b/lib/src/helpers/customError.ts @@ -6,13 +6,13 @@ * Licensed under the MIT license. */ -import { arrSlice } from "../array/slice"; import { fnApply } from "../funcs/fnApply"; -import { CONSTRUCTOR, NAME, NULL_VALUE, PROTOTYPE } from "../internal/constants"; +import { ArrProto, CALL, CONSTRUCTOR, NAME, NULL_VALUE, PROTOTYPE, SLICE } from "../internal/constants"; import { objCreate } from "../object/create"; import { objDefine } from "../object/define"; import { objGetPrototypeOf } from "../object/object"; import { objSetPrototypeOf } from "../object/set_proto"; +import { safe } from "./safe"; /** * Defines the definition of the custom error constructor @@ -30,11 +30,11 @@ export interface CustomErrorConstructor extends ErrorCo * @ignore */ function _createCustomError(name: string, d: any, b: any): T { - _safeDefineName(d, name); + safe(objDefine, [ d, NAME, { v: name, c: true, e: false }]); d = objSetPrototypeOf(d, b); function __() { this.constructor = d; - _safeDefineName(this, name); + safe(objDefine, [this, NAME, { v: name, c: true, e: false }]); } d[PROTOTYPE] = b === NULL_VALUE ? objCreate(b) : ((__ as any)[PROTOTYPE] = b[PROTOTYPE], new (__ as any)()); @@ -42,21 +42,9 @@ function _createCustomError(name: string, d: any, b: any): T { return d; } -function _safeSetName(baseClass: any, name: string) { - try { - name && (baseClass[NAME] = name); - //name && (baseClass[PROTOTYPE][NAME] = name); - } catch(e) { - // Do nothing - } -} - -function _safeDefineName(target: any, name: string) { - try { - objDefine(target, NAME, { v: name, c: true, e: false }); - } catch (e) { - // Do nothing - } +function _setName(baseClass: any, name: string) { + name && (baseClass[NAME] = name); + //name && (baseClass[PROTOTYPE][NAME] = name); } /** @@ -139,9 +127,10 @@ export function createCustomError(name, function (this: any) { let _this = this; + let theArgs = arguments; try { - _safeSetName(theBaseClass, name); - let _self = fnApply(theBaseClass, _this, arrSlice(arguments)) || _this; + safe(_setName, [theBaseClass, name]); + let _self = fnApply(theBaseClass, _this, ArrProto[SLICE][CALL](theArgs)) || _this; if (_self !== _this) { // Looks like runtime error constructor reset the prototype chain, so restore it let orgProto = objGetPrototypeOf(_this); @@ -154,11 +143,11 @@ export function createCustomError:1:13', message: 'Hello Darkness', name: 'Error'" + * + * let errStrFmt = dumpObj(err, true); + * // errStrFmt === "[object Error]: {\n stack: "Error: Hello Darkness\n at :1:13",\n message: "Hello Darkness",\n name: "Error"\n}" + * + * let errStrFmt2 = dumpObj(err, 2); + * // errStrFmt2 === "[object Error]: {\n stack: "Error: Hello Darkness\n at :1:13",\n message: "Hello Darkness",\n name: "Error"\n}" + * + * let errStrFmt3 = dumpObj(err, 0); + * // errStrFmt3 === "[object Error]: { stack: "Error: Hello Darkness\n at :1:13", message: "Hello Darkness", name: "Error" }" + * + * ``` + * @see {@link dumpObj} */ /*#__NO_SIDE_EFFECTS__*/ export function dumpObj(object: any, format?: boolean | number): string { let propertyValueDump = EMPTY; - if (isError(object)) { - propertyValueDump = "{ stack: '" + object.stack + "', message: '" + object.message + "', name: '" + object.name + "'"; - } else { - try { - propertyValueDump = JSON.stringify(object, NULL_VALUE, format ? (isNumber(format) ? format : 4) : UNDEF_VALUE); - } catch(e) { - // Unable to convert object (probably circular) - propertyValueDump = " - " + dumpObj(e, format); - } + const objType = ObjProto[TO_STRING][CALL](object); + if (objType === ERROR_TYPE) { + object = { stack: asString(object.stack), message: asString(object.message), name: asString(object.name) }; + } + + try { + propertyValueDump = JSON.stringify(object, NULL_VALUE, format ? (((typeof format as unknown) === NUMBER) ? format as number : 4) : UNDEF_VALUE); + propertyValueDump = (propertyValueDump && propertyValueDump.replace(/"(\w+)"\s*:\s{0,1}/g, "$1: ")) || asString(object); + } catch(e) { + // Unable to convert object (probably circular) + propertyValueDump = " - " + dumpObj(e, format); } - return objToString(object) + ": " + propertyValueDump; + return objType + ": " + propertyValueDump; } diff --git a/lib/src/helpers/encode.ts b/lib/src/helpers/encode.ts index 2d38274d..573c35dd 100644 --- a/lib/src/helpers/encode.ts +++ b/lib/src/helpers/encode.ts @@ -6,7 +6,7 @@ * Licensed under the MIT license. */ -import { NULL_VALUE, UNDEF_VALUE } from "../internal/constants"; +import { NULL_VALUE, TO_STRING, UNDEF_VALUE } from "../internal/constants"; import { asString } from "../string/as_string"; import { strCamelCase } from "../string/conversion"; import { strPadStart } from "../string/pad"; @@ -126,7 +126,7 @@ export function encodeAsJson(value: T, format?: boolean | number): string { return "\\" + match; } - var hex = match.charCodeAt(0).toString(16); + var hex = match.charCodeAt(0)[TO_STRING](16); return "\\u" + strPadStart(strUpper(hex), 4, "0"); }) + DBL_QUOTE; } else { diff --git a/lib/src/helpers/environment.ts b/lib/src/helpers/environment.ts index b937fa32..ffb095bb 100644 --- a/lib/src/helpers/environment.ts +++ b/lib/src/helpers/environment.ts @@ -8,22 +8,23 @@ import { NULL_VALUE, UNDEF_VALUE } from "../internal/constants"; import { _getGlobalValue } from "../internal/global"; -import { safeGetLazy } from "./safe_lazy"; -import { ILazyValue, _globalLazyTestHooks } from "./lazy"; +import { ILazyValue, _globalLazyTestHooks, _initTestHooks, getLazy } from "./lazy"; +import { ICachedValue, createCachedValue } from "./cache"; +import { safe } from "./safe"; const WINDOW = "window"; declare let WorkerGlobalScope: any; declare let self: any; -let _cachedGlobal: ILazyValue; +let _cachedGlobal: ICachedValue; -let _cachedWindow: ILazyValue; -let _cachedDocument: ILazyValue; -let _cachedNavigator: ILazyValue; -let _cachedHistory: ILazyValue; -let _isWebWorker: ILazyValue; -let _isNode: ILazyValue; +let _cachedWindow: ICachedValue; +let _cachedDocument: ICachedValue; +let _cachedNavigator: ICachedValue; +let _cachedHistory: ICachedValue; +let _isWebWorker: ICachedValue; +let _isNode: ICachedValue; /** * Create and return an readonly {@link ILazyValue} instance which will cache and return the named global @@ -55,7 +56,7 @@ let _isNode: ILazyValue; */ /*#__NO_SIDE_EFFECTS__*/ export function lazySafeGetInst(name: string | number | symbol) : ILazyValue { - return safeGetLazy(() => getInst(name) || UNDEF_VALUE, UNDEF_VALUE); + return getLazy(() => safe(getInst, [name]).v || UNDEF_VALUE); } /** @@ -76,13 +77,15 @@ export function lazySafeGetInst(name: string | number | symbol) : ILazyValue< */ /*#__NO_SIDE_EFFECTS__*/ export function getGlobal(useCached?: boolean): Window { - (!_cachedGlobal || useCached === false || (_globalLazyTestHooks && _globalLazyTestHooks.lzy && !_cachedGlobal.b)) && (_cachedGlobal = safeGetLazy(_getGlobalValue, NULL_VALUE)); + !_globalLazyTestHooks && _initTestHooks(); + (!_cachedGlobal || useCached === false || _globalLazyTestHooks.lzy) && (_cachedGlobal = createCachedValue(safe(_getGlobalValue).v || NULL_VALUE)); return _cachedGlobal.v; } /** * Return the named global object if available, will return null if the object is not available. + * Unlike {@link safeGetInst} some environments may throw an exception if the global is not available. * @group Environment * @param name The globally named object, may be any valid property key (string, number or symbol) * @param useCached - [Optional] used for testing to bypass the cached lookup, when `true` this will @@ -135,7 +138,8 @@ export function hasDocument(): boolean { */ /*#__NO_SIDE_EFFECTS__*/ export function getDocument(): Document { - (!_cachedDocument || (_globalLazyTestHooks && _globalLazyTestHooks.lzy && !_cachedDocument.b)) && (_cachedDocument = lazySafeGetInst("document")); + !_globalLazyTestHooks && _initTestHooks(); + (!_cachedDocument || _globalLazyTestHooks.lzy) && (_cachedDocument = createCachedValue(safe(getInst, ["document"]).v)); return _cachedDocument.v; } @@ -157,7 +161,8 @@ export function hasWindow(): boolean { */ /*#__NO_SIDE_EFFECTS__*/ export function getWindow(): Window { - (!_cachedWindow || (_globalLazyTestHooks && _globalLazyTestHooks.lzy && !_cachedWindow.b)) && (_cachedWindow = lazySafeGetInst(WINDOW)); + !_globalLazyTestHooks && _initTestHooks(); + (!_cachedWindow || _globalLazyTestHooks.lzy) && (_cachedWindow = createCachedValue(safe(getInst, [WINDOW]).v)); return _cachedWindow.v; } @@ -179,7 +184,8 @@ export function hasNavigator(): boolean { */ /*#__NO_SIDE_EFFECTS__*/ export function getNavigator(): Navigator { - (!_cachedNavigator || (_globalLazyTestHooks && _globalLazyTestHooks.lzy && !_cachedNavigator.b)) && (_cachedNavigator = lazySafeGetInst("navigator")); + !_globalLazyTestHooks && _initTestHooks(); + (!_cachedNavigator || _globalLazyTestHooks.lzy) && (_cachedNavigator = createCachedValue(safe(getInst, ["navigator"]).v)); return _cachedNavigator.v; } @@ -201,7 +207,8 @@ export function hasHistory(): boolean { */ /*#__NO_SIDE_EFFECTS__*/ export function getHistory(): History | null { - (!_cachedHistory || (_globalLazyTestHooks && _globalLazyTestHooks.lzy && !_cachedHistory.b)) && (_cachedHistory = lazySafeGetInst("history")); + !_globalLazyTestHooks && _initTestHooks(); + (!_cachedHistory || _globalLazyTestHooks.lzy) && (_cachedHistory = createCachedValue(safe(getInst, ["history"]).v)); return _cachedHistory.v; } @@ -212,7 +219,7 @@ export function getHistory(): History | null { * @returns True if you are */ export function isNode(): boolean { - !_isNode && (_isNode = safeGetLazy(() => !!(process && (process.versions||{}).node), false)) + !_isNode && (_isNode = createCachedValue(!!safe(() => (process && (process.versions||{}).node)).v)); return _isNode.v; } @@ -223,7 +230,7 @@ export function isNode(): boolean { * @returns True if the environment you are in looks like a Web Worker */ export function isWebWorker(): boolean { - !_isWebWorker && (_isWebWorker = safeGetLazy(() => !!(self && self instanceof WorkerGlobalScope), false)); + !_isWebWorker && (_isWebWorker = createCachedValue(!!safe(() => self && self instanceof WorkerGlobalScope).v)); return _isWebWorker.v; } diff --git a/lib/src/helpers/extend.ts b/lib/src/helpers/extend.ts index ae150c7c..63047474 100644 --- a/lib/src/helpers/extend.ts +++ b/lib/src/helpers/extend.ts @@ -7,7 +7,7 @@ */ import { arrForEach } from "../array/forEach"; -import { arrSlice } from "../array/slice"; +import { ArrProto, CALL, SLICE } from "../internal/constants"; import { objCopyProps, objDeepCopy } from "../object/copy"; /** @@ -41,7 +41,7 @@ export function deepExtend(target: T, ...theArgs: any): T & any; * @returns - A new object or the original */ export function deepExtend(target: T, obj1?: T1, obj2?: T2, obj3?: T3, obj4?: T4, obj5?: T5, obj6?: T6): T & T1 & T2 & T3 & T4 & T5 & T6 { - return _doExtend(objDeepCopy(target) || {}, arrSlice(arguments)); + return _doExtend(objDeepCopy(target) || {}, ArrProto[SLICE][CALL](arguments)); } /** @@ -61,7 +61,7 @@ export function objExtend(target: T, ...theArgs: any): T & any; * @returns - A new object or the original */ export function objExtend(target: T, obj1?: T1, obj2?: T2, obj3?: T3, obj4?: T4, obj5?: T5, obj6?: T6): T & T1 & T2 & T3 & T4 & T5 & T6 { - return _doExtend(target || {}, arrSlice(arguments)); + return _doExtend(target || {}, ArrProto[SLICE][CALL](arguments)); } \ No newline at end of file diff --git a/lib/src/helpers/lazy.ts b/lib/src/helpers/lazy.ts index 7dbbc36d..b9d92871 100644 --- a/lib/src/helpers/lazy.ts +++ b/lib/src/helpers/lazy.ts @@ -6,9 +6,9 @@ * Licensed under the MIT license. */ -import { NULL_VALUE } from "../internal/constants"; import { _GlobalTestHooks, _getGlobalConfig } from "../internal/global"; import { objDefineProp } from "../object/define"; +import { ICachedValue } from "./cache"; /** * @internal @@ -16,9 +16,8 @@ import { objDefineProp } from "../object/define"; */ export let _globalLazyTestHooks: _GlobalTestHooks; -let _fetchLazyTestHooks = function() { +export function _initTestHooks() { _globalLazyTestHooks = _getGlobalConfig(); - _fetchLazyTestHooks = NULL_VALUE; } /** @@ -26,7 +25,7 @@ let _fetchLazyTestHooks = function() { * @since 0.4.5 * @group Lazy */ -export interface ILazyValue { +export interface ILazyValue extends ICachedValue { /** * Returns the current cached value from the lazy lookup, if the callback function has not yet occurred * accessing the value will cause the lazy evaluation to occur and the result will be returned. @@ -68,7 +67,7 @@ export interface ILazyValue { */ export function getLazy(cb: () => T): ILazyValue { let lazyValue = { } as ILazyValue; - _fetchLazyTestHooks && _fetchLazyTestHooks(); + !_globalLazyTestHooks && _initTestHooks(); lazyValue.b = _globalLazyTestHooks.lzy; objDefineProp(lazyValue, "v", { @@ -80,16 +79,10 @@ export function getLazy(cb: () => T): ILazyValue { objDefineProp(lazyValue, "v", { value: result }); - - if (lazyValue.b) { - delete lazyValue.b; - } - } - - if (_globalLazyTestHooks.lzy && lazyValue.b !== _globalLazyTestHooks.lzy) { - lazyValue.b = _globalLazyTestHooks.lzy; } + lazyValue.b = _globalLazyTestHooks.lzy; + return result; } }); @@ -106,7 +99,7 @@ export function getLazy(cb: () => T): ILazyValue { * @param newValue - When `true` will cause all new lazy implementations to bypass the cached lookup. */ export function setBypassLazyCache(newValue: boolean) { - _fetchLazyTestHooks && _fetchLazyTestHooks(); + !_globalLazyTestHooks && _initTestHooks(); _globalLazyTestHooks.lzy = newValue; } @@ -154,7 +147,7 @@ export function setBypassLazyCache(newValue: boolean) { */ export function getWritableLazy(cb: () => T): ILazyValue { let lazyValue = { } as ILazyValue; - _fetchLazyTestHooks && _fetchLazyTestHooks(); + !_globalLazyTestHooks && _initTestHooks(); lazyValue.b = _globalLazyTestHooks.lzy; let _setValue = (newValue: T) => { diff --git a/lib/src/helpers/perf.ts b/lib/src/helpers/perf.ts index c7a37dbd..7662ca60 100644 --- a/lib/src/helpers/perf.ts +++ b/lib/src/helpers/perf.ts @@ -8,7 +8,7 @@ import { utcNow } from "./date"; import { lazySafeGetInst } from "./environment"; -import { ILazyValue, _globalLazyTestHooks } from "./lazy"; +import { ILazyValue, _globalLazyTestHooks, _initTestHooks } from "./lazy"; let _perf: ILazyValue @@ -36,7 +36,8 @@ export function hasPerformance(): boolean { */ /*#__NO_SIDE_EFFECTS__*/ export function getPerformance(): Performance { - (!_perf || (!_perf.b && _globalLazyTestHooks && _globalLazyTestHooks.lzy)) && (_perf = lazySafeGetInst("performance")); + !_globalLazyTestHooks && _initTestHooks(); + (!_perf || (!_perf.b && _globalLazyTestHooks.lzy)) && (_perf = lazySafeGetInst("performance")); return _perf.v; } diff --git a/lib/src/helpers/safe.ts b/lib/src/helpers/safe.ts new file mode 100644 index 00000000..6fa9f208 --- /dev/null +++ b/lib/src/helpers/safe.ts @@ -0,0 +1,68 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2022 Nevware21 + * Licensed under the MIT license. + */ + +/** + * Infers the return type of the specified function + * @since 0.10.5 + * @group Safe + * @typeparam T - The type of the function which to infer the return type + */ +export type SafeReturnType any> = T extends (...args: any) => infer R ? R : any + +/** + * Defines the return value of the {@link safe} function, which is an object with either a value or an error + * @since 0.10.5 + * @group Safe + * @typeparam T - The type of the function to call + * @typeparam R - The return type of the function + */ +export interface ISafeReturn any> { + /** + * The value returned by the function call + */ + v?: SafeReturnType; + + /** + * The error thrown by the function call + */ + e?: Error; +} + +/** + * Call the specified function with zero or more individual arguments, the call will be wrapped in a try / catch + * block and the result returned wrapped in an {@link ISafeReturn} instance. If the function call throws an + * exception the {@link ISafeReturn.e error} property will contain the exception otherwise the {@link ISafeReturn.v value} + * property will contain the value returned from the function. + * @since 0.10.5 + * @group Safe + * @param func - The function to call + * @param argArray - An array of the arguments to pass to the function + * @returns The return value of the function or undefined if an exception is thrown + * @example + * ```ts + * let result = safe((value: string) => { + * return JSON.parse(value); + * }, ["{ invalid: json value"]); + * + * // result.e instanceof SyntaxError + * + * let result2 = safe((value: string) => { + * return JSON.parse(value); + * }, ["{ valid: json value"]); + * + * // result2.v === { valid: "json value" } + */ +export function safe any>(func: F, argArray?: any[]): ISafeReturn { + try { + return { + v: func.apply(this, argArray) + }; + } catch (e) { + return { e }; + } +} diff --git a/lib/src/helpers/safe_get.ts b/lib/src/helpers/safe_get.ts index ee70e49f..d6de42ce 100644 --- a/lib/src/helpers/safe_get.ts +++ b/lib/src/helpers/safe_get.ts @@ -6,6 +6,8 @@ * Licensed under the MIT license. */ +import { safe } from "./safe"; + /** * Function to safely execute a callback function, if the function throws the provided default * value will be returned. @@ -27,12 +29,7 @@ * ``` */ export function safeGet(cb: () => T, defValue: T): T { - let result = defValue; - try { - result = cb(); - } catch (e) { - // Do nothing - } - - return result; + let result = safe(cb); + + return result.e ? defValue : result.v; } diff --git a/lib/src/helpers/safe_lazy.ts b/lib/src/helpers/safe_lazy.ts index 031a86b1..284d93ce 100644 --- a/lib/src/helpers/safe_lazy.ts +++ b/lib/src/helpers/safe_lazy.ts @@ -7,7 +7,7 @@ */ import { getLazy, ILazyValue } from "./lazy"; -import { safeGet } from "./safe_get"; +import { safe } from "./safe"; /** * Create and return an readonly {@link ILazyValue} instance which will cache and return the value returned @@ -39,6 +39,10 @@ import { safeGet } from "./safe_get"; * * ``` */ +/*#__NO_SIDE_EFFECTS__*/ export function safeGetLazy(cb: () => T, defValue: T): ILazyValue { - return getLazy(() => safeGet(cb, defValue)); + return getLazy(() => { + let result = safe(cb); + return result.e ? defValue : result.v; + }); } diff --git a/lib/src/index.ts b/lib/src/index.ts index 8991826d..fdc9bd7f 100644 --- a/lib/src/index.ts +++ b/lib/src/index.ts @@ -29,6 +29,7 @@ export { isRegExp, isFile, isFormData, isBlob, isArrayBuffer, isPromiseLike, isPromise, isThenable, isNotTruthy, isTruthy, objToString, isStrictNullOrUndefined, isStrictUndefined, isError, isPrimitive, isPrimitiveType } from "./helpers/base"; +export { ICachedValue, createCachedValue, createDeferredCachedValue } from "./helpers/cache"; export { CustomErrorConstructor, createCustomError, throwUnsupported } from "./helpers/customError"; export { utcNow, polyUtcNow } from "./helpers/date"; export { dumpObj } from "./helpers/diagnostics"; @@ -48,6 +49,7 @@ export { IGetLength as GetLengthImpl, getLength } from "./helpers/length"; export { getIntValue } from "./helpers/number"; export { getPerformance, hasPerformance, elapsedTime, perfNow } from "./helpers/perf"; export { createFilenameRegex, createWildcardRegex, makeGlobRegex } from "./helpers/regexp"; +export { safe, ISafeReturn, SafeReturnType } from "./helpers/safe"; export { safeGet } from "./helpers/safe_get"; export { safeGetLazy } from "./helpers/safe_lazy"; export { throwError, throwTypeError, throwRangeError } from "./helpers/throw"; diff --git a/lib/src/internal/constants.ts b/lib/src/internal/constants.ts index 2876e5bd..3773826b 100644 --- a/lib/src/internal/constants.ts +++ b/lib/src/internal/constants.ts @@ -32,6 +32,7 @@ export const VALUE = "value"; export const NAME = "name"; export const SLICE = "slice"; export const CALL = "call"; +export const TO_STRING = "toString"; /** * @ignore diff --git a/lib/src/internal/global.ts b/lib/src/internal/global.ts index 9990cbd6..c99bd90f 100644 --- a/lib/src/internal/global.ts +++ b/lib/src/internal/global.ts @@ -6,6 +6,7 @@ * Licensed under the MIT license. */ +import { safe } from "../helpers/safe"; import { UNDEFINED } from "./constants"; const GLOBAL_CONFIG_KEY = "__tsUtils$gblCfg"; @@ -45,9 +46,9 @@ let _globalCfg: { [key: string ]: any }; * Helper to get the current global value * @returns */ -/*#__NO_SIDE_EFFECTS__*/ export function _getGlobalValue(): Window { - let result: Window; + var result: Window; + if (typeof globalThis !== UNDEFINED) { result = globalThis; } @@ -77,7 +78,7 @@ export function _getGlobalValue(): Window { /*#__NO_SIDE_EFFECTS__*/ export function _getGlobalConfig(): TsUtilsGlobalConfig { if (!_globalCfg) { - let gbl = _getGlobalValue() || {}; + let gbl = safe(_getGlobalValue).v || {}; _globalCfg = gbl[GLOBAL_CONFIG_KEY] = gbl[GLOBAL_CONFIG_KEY] || {}; } diff --git a/lib/src/internal/unwrapFunction.ts b/lib/src/internal/unwrapFunction.ts index 86b93c64..68661f09 100644 --- a/lib/src/internal/unwrapFunction.ts +++ b/lib/src/internal/unwrapFunction.ts @@ -13,10 +13,6 @@ import { ArrProto, CALL, SLICE } from "./constants"; let _slice: typeof ArrProto.slice; -function _throwMissingFunction(funcName: keyof T, thisArg: T): never { - throwTypeError("'" + asString(funcName) + "' not defined for " + dumpObj(thisArg)); -} - /** * @internal * @ignore @@ -27,13 +23,7 @@ function _throwMissingFunction(funcName: keyof T, thisArg: T): never { * @param funcName - The function name to call on the first argument passed to the wrapped function * @returns A function which will call the funcName against the first passed argument and pass on the remaining arguments */ -/*#__NO_SIDE_EFFECTS__*/ -export function _unwrapInstFunction(funcName: keyof T) { - _slice = _slice || ArrProto[SLICE]; - return function(thisArg: any): R { - return (thisArg[funcName] as Function).apply(thisArg, _slice[CALL](arguments, 1)); - }; -} +export const _unwrapInstFunction:(funcName: keyof T) => (this: T, ..._args:any) => R = /*__PURE__*/_unwrapFunctionWithPoly; /** * @internal @@ -43,20 +33,7 @@ export function _unwrapInstFunction(funcName: keyof T) { * @param clsProto - The Class or class prototype to fallback to if the instance doesn't have the function. * @returns A function which will call the funcName against the first passed argument and pass on the remaining arguments */ -/*#__NO_SIDE_EFFECTS__*/ -export function _unwrapFunction(funcName: keyof T, clsProto?: T) { - _slice = _slice || ArrProto[SLICE]; - let clsFn = clsProto && clsProto[funcName]; - - return function(thisArg: any): R { - let theFunc = (thisArg && thisArg[funcName]) || clsFn; - if (theFunc) { - return (theFunc as Function).apply(thisArg, _slice[CALL](arguments, 1)); - } - - _throwMissingFunction(funcName, thisArg); - }; -} +export const _unwrapFunction:(funcName: keyof T, clsProto: T) => (this: T, ..._args:any) => R = /*__PURE__*/_unwrapFunctionWithPoly; /** * @internal @@ -68,7 +45,7 @@ export function _unwrapFunction(funcName: keyof T, clsProto?: T) { * @returns A function which will call the funcName against the first passed argument and pass on the remaining arguments */ /*#__NO_SIDE_EFFECTS__*/ -export function _unwrapFunctionWithPoly any>(funcName: keyof T, clsProto: T, polyFunc: P) { +export function _unwrapFunctionWithPoly any>(funcName: keyof T, clsProto?: T, polyFunc?: P) { _slice = _slice || ArrProto[SLICE]; let clsFn = clsProto && clsProto[funcName]; @@ -79,7 +56,7 @@ export function _unwrapFunctionWithPoly any>(func return ((theFunc || polyFunc) as Function).apply(thisArg, theFunc ? _slice[CALL](theArgs, 1) : theArgs); } - _throwMissingFunction(funcName, thisArg); + throwTypeError("\"" + asString(funcName) + "\" not defined for " + dumpObj(thisArg)); }; } diff --git a/lib/src/iterator/forOf.ts b/lib/src/iterator/forOf.ts index e41a5bbb..21560035 100644 --- a/lib/src/iterator/forOf.ts +++ b/lib/src/iterator/forOf.ts @@ -6,13 +6,13 @@ * Licensed under the MIT license. */ -import { ILazyValue, getLazy } from "../helpers/lazy"; +import { ICachedValue, createCachedValue } from "../helpers/cache"; import { CALL, DONE, VALUE } from "../internal/constants"; import { getKnownSymbol } from "../symbol/symbol"; import { WellKnownSymbols } from "../symbol/well_known"; import { isIterator } from "./iterator"; -let _iterSymbol: ILazyValue; +let _iterSymbol: ICachedValue; /** * Calls the provided `callbackFn` function once for each element in the iterator or iterator returned by @@ -58,7 +58,7 @@ let _iterSymbol: ILazyValue; export function iterForOf(iter: Iterator | Iterable, callbackfn: (value: T, count?: number, iter?: Iterator) => void | number, thisArg?: any): void { if (iter) { if (!isIterator(iter)) { - !_iterSymbol && (_iterSymbol = getLazy(() => getKnownSymbol(WellKnownSymbols.iterator))); + !_iterSymbol && (_iterSymbol = createCachedValue(getKnownSymbol(WellKnownSymbols.iterator))); iter = iter[_iterSymbol.v] ? iter[_iterSymbol.v]() : null; } diff --git a/lib/src/object/create.ts b/lib/src/object/create.ts index 5bee33a8..d8697386 100644 --- a/lib/src/object/create.ts +++ b/lib/src/object/create.ts @@ -8,6 +8,7 @@ import { FUNCTION, ObjClass, OBJECT, PROTOTYPE } from "../internal/constants"; import { dumpObj } from "../helpers/diagnostics"; +import { throwTypeError } from "../helpers/throw"; /** * Creates an object that has the specified prototype, and that optionally contains specified properties. This helper exists to avoid adding a polyfil @@ -34,7 +35,7 @@ export function polyObjCreate(obj: any): any { let type = typeof obj; if (type !== OBJECT && type !== FUNCTION) { - throw new TypeError("Prototype must be an Object or function: " + dumpObj(obj)); + throwTypeError("Prototype must be an Object or function: " + dumpObj(obj)); } function tempFunc() {} diff --git a/lib/src/object/define.ts b/lib/src/object/define.ts index 5c2ae2f3..d750c40d 100644 --- a/lib/src/object/define.ts +++ b/lib/src/object/define.ts @@ -151,6 +151,7 @@ function _createProp(value: ObjDefinePropDescriptor): PropertyDescriptor { * @param target - The object on which to define the property. * @param key - The name or Symbol of the property to be defined or modified. * @param descriptor - The descriptor for the property being defined or modified. + * @returns The object that was passed to the function with the new or updated property. */ export const objDefineProp: (target: T, key: PropertyKey, descriptor: PropertyDescriptor & ThisType) => T = ObjClass["defineProperty"]; diff --git a/lib/src/object/has_own_prop.ts b/lib/src/object/has_own_prop.ts index 19f1cb1b..2386d4c7 100644 --- a/lib/src/object/has_own_prop.ts +++ b/lib/src/object/has_own_prop.ts @@ -42,5 +42,5 @@ import { CALL, ObjProto } from "../internal/constants"; */ /*#__NO_SIDE_EFFECTS__*/ export function objHasOwnProperty(obj: T, prop: PropertyKey): boolean { - return obj && ObjProto.hasOwnProperty[CALL](obj, prop); + return !!obj && ObjProto.hasOwnProperty[CALL](obj, prop); } diff --git a/lib/src/object/is_plain_object.ts b/lib/src/object/is_plain_object.ts index f4032c2b..25bf136a 100644 --- a/lib/src/object/is_plain_object.ts +++ b/lib/src/object/is_plain_object.ts @@ -7,7 +7,7 @@ */ import { getWindow, hasWindow } from "../helpers/environment"; -import { CALL, CONSTRUCTOR, FUNCTION, ObjClass, OBJECT, PROTOTYPE } from "../internal/constants"; +import { CALL, CONSTRUCTOR, FUNCTION, ObjClass, OBJECT, PROTOTYPE, TO_STRING } from "../internal/constants"; import { objHasOwnProperty } from "./has_own_prop"; import { objGetPrototypeOf } from "./object"; @@ -67,7 +67,7 @@ export function isPlainObject(value: any): value is object { if (!_objCtrFnString) { // Lazily caching what the runtime reports as the object function constructor (as a string) // Using an current function lookup to find what this runtime calls a "native" function - _fnToString = Function[PROTOTYPE].toString; + _fnToString = Function[PROTOTYPE][TO_STRING]; _objCtrFnString = _fnToString[CALL](ObjClass); } diff --git a/lib/src/object/object.ts b/lib/src/object/object.ts index 2dceee98..b8b726a6 100644 --- a/lib/src/object/object.ts +++ b/lib/src/object/object.ts @@ -8,7 +8,6 @@ import { NULL_VALUE, ObjClass, __PROTO__ } from "../internal/constants"; import { isArray, isObject } from "../helpers/base"; -import { throwTypeError } from "../helpers/throw"; import { objForEachKey } from "./for_each_key"; import { polyObjEntries, polyObjValues } from "../polyfills/object"; @@ -96,14 +95,7 @@ export const objAssign = ObjClass["assign"]; * console.log(objKeys(myObj)); // console: ['foo'] * ``` */ -/*#__NO_SIDE_EFFECTS__*/ -export function objKeys(value: any): string[] { - if (!isObject(value) || value === NULL_VALUE) { - throwTypeError("objKeys called on non-object"); - } - - return ObjClass.keys(value); -} +export const objKeys: (value: any) => string[] = ObjClass.keys; /** * Perform a deep freeze on the object and all of it's contained values / properties by recursively calling diff --git a/lib/src/polyfills/object.ts b/lib/src/polyfills/object.ts index d54fbcf5..e06b9a78 100644 --- a/lib/src/polyfills/object.ts +++ b/lib/src/polyfills/object.ts @@ -7,6 +7,7 @@ */ import { isObject } from "../helpers/base"; +import { throwTypeError } from "../helpers/throw"; import { NULL_VALUE } from "../internal/constants"; import { objForEachKey } from "../object/for_each_key"; import { objHasOwn } from "../object/has_own"; @@ -22,7 +23,7 @@ import { objHasOwn } from "../object/has_own"; /*#__NO_SIDE_EFFECTS__*/ export function polyObjKeys(obj: any): string[] { if (!isObject(obj) || obj === NULL_VALUE) { - throw new TypeError("polyObjKeys called on non-object"); + throwTypeError("polyObjKeys called on non-object"); } const result: string[] = []; diff --git a/lib/src/polyfills/symbol.ts b/lib/src/polyfills/symbol.ts index 49686577..118c2b39 100644 --- a/lib/src/polyfills/symbol.ts +++ b/lib/src/polyfills/symbol.ts @@ -8,11 +8,11 @@ import { WellKnownSymbols, _wellKnownSymbolMap } from "../symbol/well_known"; import { throwTypeError } from "../helpers/throw"; -import { POLYFILL_TAG, SYMBOL } from "../internal/constants"; +import { POLYFILL_TAG, SYMBOL, TO_STRING } from "../internal/constants"; import { objHasOwn } from "../object/has_own"; import { asString } from "../string/as_string"; import { _GlobalPolySymbols, _getGlobalConfig } from "../internal/global"; -import { strStartsWith } from "../string/starts_with"; +import { strSubstring } from "../string/substring"; import { objKeys } from "../object/object"; const UNIQUE_REGISTRY_ID = "_urid"; @@ -67,7 +67,7 @@ export function polySymbolFor(key: string): symbol { if (!objHasOwn(registry.k, key)) { let newSymbol = polyNewSymbol(key); let regId = objKeys(registry.s).length; - newSymbol[UNIQUE_REGISTRY_ID] = () => regId + "_" + newSymbol.toString(); + newSymbol[UNIQUE_REGISTRY_ID] = () => regId + "_" + newSymbol[TO_STRING](); registry.k[key] = newSymbol; registry.s[newSymbol[UNIQUE_REGISTRY_ID]()] = asString(key); } @@ -84,7 +84,7 @@ export function polySymbolFor(key: string): symbol { */ /*#__NO_SIDE_EFFECTS__*/ export function polySymbolKeyFor(sym: symbol): string | undefined { - if (!sym || !sym.toString || !strStartsWith(sym.toString(), SYMBOL)) { + if (!sym || !sym[TO_STRING] || strSubstring(sym[TO_STRING](), 0, 6) != SYMBOL) { throwTypeError((sym as any) + " is not a symbol"); } @@ -117,7 +117,7 @@ export function polySymbolKeyFor(sym: symbol): string | undefined { export function polyGetKnownSymbol(name: string | WellKnownSymbols): symbol { !_wellKnownSymbolCache && (_wellKnownSymbolCache = {} as any); let result: symbol; - let knownName = _wellKnownSymbolMap[name]; + let knownName: WellKnownSymbols = _wellKnownSymbolMap[name]; if (knownName) { result = _wellKnownSymbolCache[knownName] = _wellKnownSymbolCache[knownName] || polyNewSymbol(SYMBOL + "." + knownName); } diff --git a/lib/src/string/ends_with.ts b/lib/src/string/ends_with.ts index 7f64e979..b3106768 100644 --- a/lib/src/string/ends_with.ts +++ b/lib/src/string/ends_with.ts @@ -38,9 +38,7 @@ export function polyStrEndsWith(value: string, searchString: string, length?: nu } let searchValue = isString(searchString) ? searchString : asString(searchString); - let chkLen = searchValue[LENGTH]; - let len = value[LENGTH]; - let end = !isUndefined(length) && length < len ? length : len; + let end = !isUndefined(length) && length < value[LENGTH] ? length : value[LENGTH]; - return strSubstring(value, end - chkLen, end) === searchValue; + return strSubstring(value, end - searchValue[LENGTH], end) === searchValue; } diff --git a/lib/src/string/starts_with.ts b/lib/src/string/starts_with.ts index 9f74dee0..b3fff79a 100644 --- a/lib/src/string/starts_with.ts +++ b/lib/src/string/starts_with.ts @@ -41,8 +41,7 @@ export function polyStrStartsWith(value: string, searchString: string, position? throwTypeError("'" + dumpObj(value) + "' is not a string"); } let searchValue = isString(searchString) ? searchString : asString(searchString); - let chkLen = searchValue[LENGTH]; let pos = position > 0 ? position : 0; - return strSubstring(value, pos, pos + chkLen) === searchValue; + return strSubstring(value, pos, pos + searchValue[LENGTH]) === searchValue; } diff --git a/lib/src/string/substring.ts b/lib/src/string/substring.ts index ec988eb8..99306b87 100644 --- a/lib/src/string/substring.ts +++ b/lib/src/string/substring.ts @@ -162,10 +162,5 @@ export function strLeft(value: string, count: number): string { */ /*#__NO_SIDE_EFFECTS__*/ export function strRight(value: string, count: number): string { - let len = value[LENGTH]; - if (count <= 0) { - return EMPTY; - } - - return len > count ? strSubstring(value, len - count) : value; + return count <= 0 ? EMPTY : (value[LENGTH] > count ? strSlice(value, -count) : value); } \ No newline at end of file diff --git a/lib/src/symbol/symbol.ts b/lib/src/symbol/symbol.ts index 57f521d9..9ea818d9 100644 --- a/lib/src/symbol/symbol.ts +++ b/lib/src/symbol/symbol.ts @@ -10,7 +10,7 @@ import { NULL_VALUE, SYMBOL, UNDEF_VALUE } from "../internal/constants"; import { polyGetKnownSymbol, polyNewSymbol, polySymbolFor, polySymbolKeyFor } from "../polyfills/symbol"; import { WellKnownSymbols, _wellKnownSymbolMap } from "./well_known"; import { _createIs } from "../helpers/base"; -import { ILazyValue, _globalLazyTestHooks } from "../helpers/lazy"; +import { ILazyValue, _globalLazyTestHooks, _initTestHooks } from "../helpers/lazy"; import { safeGetLazy } from "../helpers/safe_lazy"; import { lazySafeGetInst } from "../helpers/environment"; @@ -18,11 +18,12 @@ let _symbol: ILazyValue; let _symbolFor: ILazyValue<(key: string) => symbol>; let _symbolKeyFor: ILazyValue<(sym: symbol) => string | undefined>; -/*#__NO_SIDE_EFFECTS__*/ -function _getSymbolValue(name: string): ILazyValue { - return safeGetLazy(function() { - return (_symbol.v ? _symbol.v[name] : UNDEF_VALUE) as T; - }, UNDEF_VALUE); +export function _initSymbol() { + if (!_symbol || !_symbol.b) { + _symbol = lazySafeGetInst(SYMBOL); + _symbolFor = safeGetLazy(() => (_symbol.v ? _symbol.v["for"] : UNDEF_VALUE), UNDEF_VALUE); + _symbolKeyFor = safeGetLazy(() => (_symbol.v ? _symbol.v["keyFor"] : UNDEF_VALUE), UNDEF_VALUE); + } } /** @@ -48,11 +49,10 @@ export function hasSymbol(): boolean { * @group Symbol * @returns The value of the named Symbol (if available) */ +/*#__NO_SIDE_EFFECTS__*/ export function getSymbol(): Symbol { - let resetCache = !_symbol || (_globalLazyTestHooks && _globalLazyTestHooks.lzy && !_symbol.b); - resetCache && (_symbol = lazySafeGetInst(SYMBOL)); - (!_symbolFor || resetCache) && (_symbolFor = _getSymbolValue("for")); - (!_symbolKeyFor || resetCache) && (_symbolKeyFor = _getSymbolValue("keyFor")); + !_globalLazyTestHooks && _initTestHooks(); + (!_symbol || _globalLazyTestHooks.lzy) && _initSymbol(); return _symbol.v; } @@ -74,8 +74,9 @@ export function getSymbol(): Symbol { /*#__NO_SIDE_EFFECTS__*/ export function getKnownSymbol(name: string | WellKnownSymbols, noPoly?: boolean): T { let knownName = _wellKnownSymbolMap[name]; + !_globalLazyTestHooks && _initTestHooks(); // Cause lazy symbol to get initialized - (!_symbol || (_globalLazyTestHooks.lzy && !_symbol.b)) && getSymbol(); + (!_symbol || _globalLazyTestHooks.lzy) && _initSymbol(); return _symbol.v ? _symbol.v[knownName || name] : (!noPoly ? polyGetKnownSymbol(name) : UNDEF_VALUE); } @@ -90,8 +91,9 @@ export function getKnownSymbol(name: string | WellKnownSymbols, noPo */ /*#__NO_SIDE_EFFECTS__*/ export function newSymbol(description?: string | number, noPoly?: boolean): symbol { + !_globalLazyTestHooks && _initTestHooks(); // Cause lazy _symbol to get initialized - (!_symbol || (_globalLazyTestHooks.lzy && !_symbol.b)) && getSymbol(); + (!_symbol || _globalLazyTestHooks.lzy) && _initSymbol(); return _symbol.v ? (_symbol.v as any)(description) : (!noPoly ? polyNewSymbol(description) : NULL_VALUE); } @@ -105,8 +107,9 @@ export function newSymbol(description?: string | number, noPoly?: boolean): symb */ /*#__NO_SIDE_EFFECTS__*/ export function symbolFor(key: string): symbol { + !_globalLazyTestHooks && _initTestHooks(); // Cause lazy symbol to get initialized - (!_symbolFor || (_globalLazyTestHooks.lzy && !_symbol.b)) && getSymbol(); + (!_symbolFor || !_symbol || _globalLazyTestHooks.lzy) && _initSymbol(); return (_symbolFor.v || polySymbolFor)(key); } @@ -120,8 +123,9 @@ export function symbolFor(key: string): symbol { */ /*#__NO_SIDE_EFFECTS__*/ export function symbolKeyFor(sym: symbol): string | undefined { + !_globalLazyTestHooks && _initTestHooks(); // Cause lazy symbol to get initialized - (!_symbolKeyFor || (_globalLazyTestHooks.lzy && !_symbol.b)) && getSymbol(); + (!_symbolKeyFor || !_symbol || _globalLazyTestHooks.lzy) && _initSymbol(); return (_symbolKeyFor.v || polySymbolKeyFor)(sym); } diff --git a/lib/src/timer/interval.ts b/lib/src/timer/interval.ts index 7a8562e7..84190f49 100644 --- a/lib/src/timer/interval.ts +++ b/lib/src/timer/interval.ts @@ -6,8 +6,8 @@ * Licensed under the MIT license. */ -import { arrSlice } from "../array/slice"; import { fnApply } from "../funcs/fnApply"; +import { ArrProto, CALL, SLICE } from "../internal/constants"; import { ITimerHandler, _createTimerHandler } from "./handler"; /** @@ -78,7 +78,7 @@ export function scheduleInterval(callback: (...args: A) => void */ export function scheduleInterval(callback: (...args: A) => void, timeout: number): ITimerHandler { let self = this; - let theArguments = arrSlice(arguments); + let theArguments = ArrProto[SLICE][CALL](arguments); let handler = _createTimerHandler(true, (intervalId: any) => { intervalId && clearInterval(intervalId); diff --git a/lib/src/timer/timeout.ts b/lib/src/timer/timeout.ts index 60f33067..ed632390 100644 --- a/lib/src/timer/timeout.ts +++ b/lib/src/timer/timeout.ts @@ -6,10 +6,9 @@ * Licensed under the MIT license. */ -import { arrSlice } from "../array/slice"; import { fnApply } from "../funcs/fnApply"; import { isArray } from "../helpers/base"; -import { UNDEF_VALUE } from "../internal/constants"; +import { ArrProto, CALL, SLICE, UNDEF_VALUE } from "../internal/constants"; import { ITimerHandler, _createTimerHandler } from "./handler"; function _createTimeoutWith(self: any, startTimer: boolean, overrideFn: TimeoutOverrideFn | TimeoutOverrideFuncs, theArgs: any[]): ITimerHandler { @@ -21,7 +20,7 @@ function _createTimeoutWith(self: any, startTimer: boolean, overrideFn: TimeoutO let timerFn = theArgs[0]; theArgs[0] = function () { handler.dn(); - fnApply(timerFn, self, arrSlice(arguments)); + fnApply(timerFn, self, ArrProto[SLICE][CALL](arguments)); }; let handler = _createTimerHandler(startTimer, (timerId?: any) => { @@ -150,7 +149,7 @@ export function scheduleTimeout(callback: (...args: A) => void, * ``` */ export function scheduleTimeout(callback: (...args: A) => void, timeout: number): ITimerHandler { - return _createTimeoutWith(this, true, UNDEF_VALUE, arrSlice(arguments)); + return _createTimeoutWith(this, true, UNDEF_VALUE, ArrProto[SLICE][CALL](arguments)); } /** @@ -318,7 +317,7 @@ export function scheduleTimeoutWith(overrideFn: TimeoutOverride * ``` */ export function scheduleTimeoutWith(overrideFn: TimeoutOverrideFn | TimeoutOverrideFuncs, callback: (...args: A) => void, timeout: number): ITimerHandler { - return _createTimeoutWith(this, true, overrideFn, arrSlice(arguments, 1)); + return _createTimeoutWith(this, true, overrideFn, ArrProto[SLICE][CALL](arguments, 1)); } /** @@ -388,7 +387,7 @@ export function createTimeout(callback: (...args: A) => void, t * ``` */ export function createTimeout(callback: (...args: A) => void, timeout: number): ITimerHandler { - return _createTimeoutWith(this, false, UNDEF_VALUE, arrSlice(arguments)); + return _createTimeoutWith(this, false, UNDEF_VALUE, ArrProto[SLICE][CALL](arguments)); } /** @@ -536,5 +535,5 @@ export function createTimeoutWith(overrideFn: TimeoutOverrideFn * ``` */ export function createTimeoutWith(overrideFn: TimeoutOverrideFn | TimeoutOverrideFuncs, callback: (...args: A) => void, timeout: number): ITimerHandler { - return _createTimeoutWith(this, false, overrideFn, arrSlice(arguments, 1)); + return _createTimeoutWith(this, false, overrideFn, ArrProto[SLICE][CALL](arguments, 1)); } diff --git a/lib/test/src/common/helpers/args.test.ts b/lib/test/src/common/helpers/args.test.ts index 9542c69e..9dbb2eb7 100644 --- a/lib/test/src/common/helpers/args.test.ts +++ b/lib/test/src/common/helpers/args.test.ts @@ -8,10 +8,22 @@ import { assert } from "chai"; import { readArgs } from "../../../../src/funcs/readArgs"; +import { _initTestHooks } from "../../../../src/helpers/lazy"; describe("args helpers", () => { describe("readArgs", () => { + let _orgSymbol = Symbol; + beforeEach(() => { + _orgSymbol = Symbol; + _initTestHooks(); + }); + + afterEach(() => { + // eslint-disable-next-line no-global-assign + Symbol = _orgSymbol; + }); + function* myGenerator() { yield "Hello"; yield "Darkness"; diff --git a/lib/test/src/common/helpers/cache.test.ts b/lib/test/src/common/helpers/cache.test.ts new file mode 100644 index 00000000..67e6adc5 --- /dev/null +++ b/lib/test/src/common/helpers/cache.test.ts @@ -0,0 +1,181 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2024 Nevware21 + * Licensed under the MIT license. + */ + +import { assert } from "chai"; +import { createCachedValue, createDeferredCachedValue } from "../../../../src/helpers/cache"; + +describe("cache helpers", () => { + describe("createCachedValue", () => { + it("validate value", () => { + let cachedValue = createCachedValue("hello darkness"); + assert.equal(cachedValue.v, "hello darkness"); + }); + + it("validate readonly", () => { + let cachedValue = createCachedValue("hello darkness"); + assert.throws(() => { + (cachedValue as any).v = "my old friend"; + }); + }); + + it("validate json", () => { + let cachedValue = createCachedValue("hello darkness"); + assert.equal(JSON.stringify(cachedValue), "\"hello darkness\""); + + let cachedValue2 = createCachedValue({ hello: "darkness" }); + assert.equal(JSON.stringify(cachedValue2), "{\"hello\":\"darkness\"}"); + }); + + it("validate numeric value", () => { + let cachedValue = createCachedValue(42); + assert.equal(cachedValue.v, 42); + }); + + it("validate object value", () => { + let cachedValue = createCachedValue({ hello: "darkness" }); + assert.deepEqual(cachedValue.v, { hello: "darkness" }); + }); + + it("validate array value", () => { + let cachedValue = createCachedValue([1, 2, 3]); + assert.deepEqual(cachedValue.v, [1, 2, 3]); + }); + + it("validate boolean value", () => { + let cachedValue = createCachedValue(true); + assert.equal(cachedValue.v, true); + }); + + it("validate null value", () => { + let cachedValue = createCachedValue(null); + assert.equal(cachedValue.v, null); + }); + + it("validate undefined value", () => { + let cachedValue = createCachedValue(undefined); + assert.equal(cachedValue.v, undefined); + }); + + it("validate date value", () => { + let cachedValue = createCachedValue(new Date()); + assert.equal(cachedValue.v instanceof Date, true); + }); + + it("validate function value", () => { + let cachedValue = createCachedValue(() => { }); + assert.equal(typeof cachedValue.v, "function"); + }); + + it("validate symbol value", () => { + let cachedValue = createCachedValue(Symbol("hello darkness")); + assert.equal(typeof cachedValue.v, "symbol"); + }); + + it("validate bigint value", () => { + let cachedValue = createCachedValue(BigInt(42)); + assert.equal(typeof cachedValue.v, "bigint"); + }); + }); + + describe("createDeferredCacheValue", () => { + it("validate value", () => { + let deferredCacheValue = createDeferredCachedValue(() => "hello darkness"); + assert.equal(deferredCacheValue.v, "hello darkness"); + }); + + it("validate readonly", () => { + let deferredCacheValue = createDeferredCachedValue(() => "hello darkness"); + assert.throws(() => { + (deferredCacheValue as any).v = "my old frield"; + }); + }); + + it("validate json", () => { + let deferredCacheValue = createDeferredCachedValue(() => "hello darkness"); + assert.equal(JSON.stringify(deferredCacheValue), "{\"v\":\"hello darkness\"}"); + }); + + it("validate numeric value", () => { + let deferredCacheValue = createDeferredCachedValue(() => 42); + assert.equal(deferredCacheValue.v, 42); + }); + + it("validate object value", () => { + let deferredCacheValue = createDeferredCachedValue(() => ({ hello: "darkness" })); + assert.deepEqual(deferredCacheValue.v, { hello: "darkness" }); + }); + + it("validate array value", () => { + let deferredCacheValue = createDeferredCachedValue(() => [1, 2, 3]); + assert.deepEqual(deferredCacheValue.v, [1, 2, 3]); + }); + + it("validate boolean value", () => { + let deferredCacheValue = createDeferredCachedValue(() => true); + assert.equal(deferredCacheValue.v, true); + }); + + it("validate null value", () => { + let deferredCacheValue = createDeferredCachedValue(() => null); + assert.equal(deferredCacheValue.v, null); + }); + + it("validate undefined value", () => { + let deferredCacheValue = createDeferredCachedValue(() => undefined); + assert.equal(deferredCacheValue.v, undefined); + }); + + it("validate date value", () => { + let deferredCacheValue = createDeferredCachedValue(() => new Date()); + assert.equal(deferredCacheValue.v instanceof Date, true); + }); + + it("validate function value", () => { + let deferredCacheValue = createDeferredCachedValue(() => () => { }); + assert.equal(typeof deferredCacheValue.v, "function"); + }); + + it("validate symbol value", () => { + let deferredCacheValue = createDeferredCachedValue(() => Symbol("hello darkness")); + assert.equal(typeof deferredCacheValue.v, "symbol"); + }); + + it("validate bigint value", () => { + let deferredCacheValue = createDeferredCachedValue(() => BigInt(42)); + assert.equal(typeof deferredCacheValue.v, "bigint"); + }); + + it("validate callback is not called until accessed", () => { + let called = 0; + let deferredCacheValue = createDeferredCachedValue(() => { + called++; + return "hello darkness"; + }); + + assert.equal(called, 0); + assert.equal(deferredCacheValue.v, "hello darkness"); + assert.equal(called, 1); + assert.equal(deferredCacheValue.v, "hello darkness"); + assert.equal(called, 1); + }); + + it("validate callback is not called until accessed - with error", () => { + let called = 0; + let deferredCacheValue = createDeferredCachedValue(() => { + called++; + throw new Error("hello darkness"); + }); + + assert.equal(called, 0); + assert.throws(() => deferredCacheValue.v); + assert.equal(called, 1); + assert.throws(() => deferredCacheValue.v); + assert.equal(called, 2); + }); + }); +}); \ No newline at end of file diff --git a/lib/test/src/common/helpers/diagnostics.test.ts b/lib/test/src/common/helpers/diagnostics.test.ts index d72b2b85..65044e46 100644 --- a/lib/test/src/common/helpers/diagnostics.test.ts +++ b/lib/test/src/common/helpers/diagnostics.test.ts @@ -18,6 +18,7 @@ describe("diagnostic helpers", () => { assert.equal(dumpObj(null), "[object Null]: null"); assert.equal(dumpObj(null, true), "[object Null]: null"); assert.equal(dumpObj(null, 2), "[object Null]: null"); + assert.equal(dumpObj(null, 2), "[object Null]: null"); }); it("validate type numbers", () => { @@ -27,12 +28,16 @@ describe("diagnostic helpers", () => { }); it("validate object", () => { - assert.equal(dumpObj({ hello: "world" }), "[object Object]: {\"hello\":\"world\"}"); - assert.equal(dumpObj({ hello: "world" }, true), "[object Object]: {\n \"hello\": \"world\"\n}"); - assert.equal(dumpObj({ hello: "world" }, 2), "[object Object]: {\n \"hello\": \"world\"\n}"); + let value = { + hello: "darkness" + }; + + assert.equal(dumpObj(value), "[object Object]: {hello: \"darkness\"}"); + assert.equal(dumpObj(value, true), "[object Object]: {\n hello: \"darkness\"\n}"); + assert.equal(dumpObj(value, 2), "[object Object]: {\n hello: \"darkness\"\n}"); }); - it("Validate circular dump", () => { + it("Validate circular dump - which throws", () => { var ug: any = {}; ug.ug = {}; ug.ug.ug = ug; @@ -42,6 +47,119 @@ describe("diagnostic helpers", () => { assert.ok(dumpObj(ug, true).indexOf("[object Object]: ") === 0); }); + it("validate array", () => { + let value = [1, 2, 3]; + + assert.equal(dumpObj(value), "[object Array]: [1,2,3]"); + assert.equal(dumpObj(value, true), "[object Array]: [\n 1,\n 2,\n 3\n]"); + assert.equal(dumpObj(value, 2), "[object Array]: [\n 1,\n 2,\n 3\n]"); + }); + + it("validate string", () => { + let value = "Hello Darkness"; + let result = JSON.stringify(value); + + assert.equal(dumpObj(value), "[object String]: " + result); + assert.equal(dumpObj(value, true), "[object String]: " + result); + assert.equal(dumpObj(value, 2), "[object String]: " + result); + }); + + it("validate boolean", () => { + let value = true; + + assert.equal(dumpObj(value), "[object Boolean]: true"); + assert.equal(dumpObj(value, true), "[object Boolean]: true"); + assert.equal(dumpObj(value, 2), "[object Boolean]: true"); + }); + + it("validate date", () => { + let value = new Date(); + let result = JSON.stringify(value); + + assert.equal(dumpObj(value), "[object Date]: " + result); + assert.equal(dumpObj(value, true), "[object Date]: " + result); + assert.equal(dumpObj(value, 2), "[object Date]: " + result); + }); + + it("validate function", () => { + let value = () => { }; + let result = String(value); + + assert.equal(dumpObj(value), "[object Function]: " + result); + assert.equal(dumpObj(value, true), "[object Function]: " + result); + assert.equal(dumpObj(value, 2), "[object Function]: " + result); + }); + + it("validate symbol", () => { + let value = Symbol("hello darkness"); + + assert.equal(dumpObj(value), "[object Symbol]: Symbol(hello darkness)"); + assert.equal(dumpObj(value, true), "[object Symbol]: Symbol(hello darkness)"); + assert.equal(dumpObj(value, 2), "[object Symbol]: Symbol(hello darkness)"); + }); + + it("validate bigint", () => { + let value = BigInt(42); + + assert.ok(dumpObj(value).startsWith("[object BigInt]: "), dumpObj(value)); + assert.ok(dumpObj(value, true).startsWith("[object BigInt]: "), dumpObj(value, true)); + assert.ok(dumpObj(value, 2).startsWith("[object BigInt]: "), dumpObj(value, 2)); + }); + + it("validate error", () => { + let value = new Error("Hello Darkness"); + + let dumpValue = dumpObj(value); + assert.equal(dumpValue.indexOf("[object Error]: {stack: \"Error: Hello Darkness") === 0, true, dumpObj(value)); + assert.equal(dumpValue.indexOf("message: \"Hello Darkness\"") > 0, true, dumpObj(value)); + assert.equal(dumpValue.indexOf("name: \"Error\"") > 0, true, dumpObj(value)); + + dumpValue = dumpObj(value, true); + assert.equal(dumpValue.indexOf("[object Error]: {\n stack: \"Error: Hello Darkness") === 0, true, dumpObj(value, true)); + assert.equal(dumpValue.indexOf("message: \"Hello Darkness\"") > 0, true, dumpObj(value, true)); + assert.equal(dumpValue.indexOf("name: \"Error\"") > 0, true, dumpObj(value, true)); + + dumpValue = dumpObj(value, 2); + assert.equal(dumpValue.indexOf("[object Error]: {\n stack: \"Error: Hello Darkness") === 0, true, dumpObj(value, 2)); + assert.equal(dumpValue.indexOf("message: \"Hello Darkness\"") > 0, true, dumpObj(value, 2)); + assert.equal(dumpValue.indexOf("name: \"Error\"") > 0, true, dumpObj(value, 2)); + }); + + it("validate error - with circular reference", () => { + let value = new Error("Hello Darkness"); + (value as any).stack = value; + + let stackValue = String(value.stack); + + assert.equal(dumpObj(value), "[object Error]: {stack: \"" + stackValue + "\",message: \"Hello Darkness\",name: \"Error\"}"); + assert.equal(dumpObj(value, true), "[object Error]: {\n stack: \"" + stackValue + "\",\n message: \"Hello Darkness\",\n name: \"Error\"\n}"); + assert.equal(dumpObj(value, 2), "[object Error]: {\n stack: \"" + stackValue + "\",\n message: \"Hello Darkness\",\n name: \"Error\"\n}"); + }); + + it("validate error - with circular reference - with circular reference", () => { + let value = new Error("Hello Darkness"); + (value as any).stack = value; + (value as any).circular = { value }; + + let stackValue = String(value.stack); + + assert.equal(dumpObj(value), "[object Error]: {stack: \"" + stackValue + "\",message: \"Hello Darkness\",name: \"Error\"}"); + assert.equal(dumpObj(value, true), "[object Error]: {\n stack: \"" + stackValue + "\",\n message: \"Hello Darkness\",\n name: \"Error\"\n}"); + assert.equal(dumpObj(value, 2), "[object Error]: {\n stack: \"" + stackValue + "\",\n message: \"Hello Darkness\",\n name: \"Error\"\n}"); + }); + + it("validate object - with circular reference - with circular reference", () => { + let value: any = { name: "Hello Darkness" }; + (value as any).stack = value; + (value as any).circular = { value }; + + assert.ok(dumpObj(value).startsWith("[object Object]: "), dumpObj(value)); + assert.ok(dumpObj(value).indexOf("Converting circular structure to JSON") != -1, dumpObj(value)); + assert.ok(dumpObj(value, true).startsWith("[object Object]: "), dumpObj(value, true)); + assert.ok(dumpObj(value, true).indexOf("Converting circular structure to JSON") != -1, dumpObj(value)); + assert.ok(dumpObj(value, 2).startsWith("[object Object]: "), dumpObj(value, 2)); + assert.ok(dumpObj(value, 2).indexOf("Converting circular structure to JSON") != -1, dumpObj(value)); + }); }); }); diff --git a/lib/test/src/common/helpers/symbol.test.ts b/lib/test/src/common/helpers/symbol.test.ts index 6ce13470..2493a0cb 100644 --- a/lib/test/src/common/helpers/symbol.test.ts +++ b/lib/test/src/common/helpers/symbol.test.ts @@ -10,8 +10,8 @@ import { assert } from "chai"; import { isUndefined } from "../../../../src/helpers/base"; import { dumpObj } from "../../../../src/helpers/diagnostics"; import { getInst } from "../../../../src/helpers/environment"; -import { setBypassLazyCache } from "../../../../src/helpers/lazy"; -import { hasSymbol, getSymbol, getKnownSymbol, symbolKeyFor, symbolFor, newSymbol} from "../../../../src/symbol/symbol"; +import { _initTestHooks, setBypassLazyCache } from "../../../../src/helpers/lazy"; +import { hasSymbol, getSymbol, getKnownSymbol, symbolKeyFor, symbolFor, newSymbol } from "../../../../src/symbol/symbol"; import { WellKnownSymbols } from "../../../../src/symbol/well_known"; declare var Symbol: any; @@ -128,6 +128,15 @@ describe("symbol helpers", () => { }); describe("Remove Native", () => { + let _orgSymbol = Symbol; + beforeEach(() => { + _orgSymbol = Symbol; + _initTestHooks(); + }); + + afterEach(() => { + Symbol = _orgSymbol; + }); it("getSymbol with noPoly", () => { // Disable lazy caching so we get the true global version @@ -139,11 +148,9 @@ describe("symbol helpers", () => { // Re-enabling caching and force the symbol to be cached setBypassLazyCache(false); - getSymbol(); try { - - + assert.equal(getSymbol(), _orgSymbol, "Check that the Symbol is returned"); Symbol = undefined; assert.equal(getSymbol(), theSymbol, "Check that the Symbol is returned"); assert.equal(getKnownSymbol("toStringTag"), toStringTag, "Check that the expected symbol is returned"); @@ -171,10 +178,9 @@ describe("symbol helpers", () => { // Re-enabling caching and force the symbol to be cached setBypassLazyCache(false); - getSymbol(); try { - + assert.equal(getSymbol(), _orgSymbol, "Check that the Symbol is returned"); Symbol = undefined; assert.equal(getSymbol(), theSymbol, "Check that the Symbol is returned"); assert.equal(getKnownSymbol("toStringTag"), toStringTag, "Check that the expected symbol is returned"); @@ -203,10 +209,9 @@ describe("symbol helpers", () => { // Re-enabling caching and force the symbol to be cached setBypassLazyCache(false); - getSymbol(); try { - + assert.equal(getSymbol(), _orgSymbol, "Check that the Symbol is returned"); Symbol = undefined; assert.equal(getSymbol(), theSymbol, "Check that the Symbol is returned"); assert.equal(getKnownSymbol("toStringTag"), toStringTag, "Check that the expected symbol is returned"); @@ -233,9 +238,9 @@ describe("symbol helpers", () => { // Re-enabling caching and force the symbol to be cached setBypassLazyCache(false); - getSymbol(); try { + assert.equal(getSymbol(), _orgSymbol, "Check that the Symbol is returned"); assert.notEqual(newSymbol("Hello"), newSymbol("Hello"), "Always creates a new symbol"); assert.equal(newSymbol("Hello").toString(), newSymbol("Hello").toString(), "While different they will look the same"); } finally { diff --git a/lib/test/src/common/internal/unwrapFunction.test.ts b/lib/test/src/common/internal/unwrapFunction.test.ts index 09bf5a4e..3b8c0b80 100644 --- a/lib/test/src/common/internal/unwrapFunction.test.ts +++ b/lib/test/src/common/internal/unwrapFunction.test.ts @@ -398,7 +398,7 @@ describe("unwrapInstFunction", () => { assert.equal(testObj1Called, false, "The testObj1 test function has not been called"); assert.ok(_expectThrow(() => { wrappedFn(testObj1); - }, "read properties of undefined")); + }, "\"test\" not defined for")); }); it("test no matching function name for the object", () => { @@ -406,20 +406,20 @@ describe("unwrapInstFunction", () => { assert.ok(_expectThrow(() => { wrappedFn(null); - }, "read properties of null")); + }, "\"test\" not defined for [object Null]")); assert.ok(_expectThrow(() => { wrappedFn({}); - }, "read properties of undefined")); + }, "\"test\" not defined for [object Object]: {}")); wrappedFn = _unwrapInstFunction("test"); assert.ok(_expectThrow(() => { wrappedFn(null); - }, "read properties of null")); + }, "\"test\" not defined for [object Null]")); assert.ok(_expectThrow(() => { wrappedFn({}); - }, "read properties of undefined")); + }, "\"test\" not defined for [object Object]: {}")); }); }); diff --git a/lib/test/src/common/object/object.test.ts b/lib/test/src/common/object/object.test.ts index 819a32a5..c800fff6 100644 --- a/lib/test/src/common/object/object.test.ts +++ b/lib/test/src/common/object/object.test.ts @@ -64,6 +64,7 @@ describe("object helpers", () => { my: "Old", "friend": "." }); + _checkObjKeys(0); }); it("objForEachKey", () => { @@ -1242,19 +1243,17 @@ describe("object helpers", () => { nativeThrew = e; } - if (isObject(value)) { + if (objKeysThrew) { + assert.equal(true, !!nativeThrew || isUndefined(nativeResult) || !!nativeResult, + "Checking whether the Native and objKeys threw or returned undefined [" + dumpObj(objKeysThrew || objKeysResult) + "] - [" + dumpObj(nativeThrew || nativeResult) + "] for [" + dumpObj(value) + "]"); + } else if(nativeThrew) { + assert.ok(false, + "Native threw but objKeys didn't [" + dumpObj(objKeysThrew) + "] - [" + dumpObj(nativeThrew || nativeResult) + "] for [" + dumpObj(value) + "]"); + } else if (nativeResult) { assert.equal(objKeysResult.length, nativeResult.length, "Checking Native and objKeys result for [" + dumpObj(value) + "]"); } else { - if (objKeysThrew) { - assert.equal(true, !!nativeThrew || isUndefined(nativeResult) || !!nativeResult, - "Checking whether the Native and objKeys threw or returned undefined [" + dumpObj(objKeysThrew || objKeysResult) + "] - [" + dumpObj(nativeThrew || nativeResult) + "] for [" + dumpObj(value) + "]"); - } else if(nativeThrew) { - assert.ok(false, - "Native threw but objKeys didn't [" + dumpObj(objKeysThrew) + "] - [" + dumpObj(nativeThrew || nativeResult) + "] for [" + dumpObj(value) + "]"); - } else { - assert.equal(isUndefined(objKeysResult), !!nativeThrew || isUndefined(nativeResult) || !!nativeResult, - "Checking whether the Native and objKeys threw or returned undefined [" + dumpObj(objKeysThrew || objKeysResult) + "] - [" + dumpObj(nativeThrew || nativeResult) + "] for [" + dumpObj(value) + "]"); - } + assert.equal(isUndefined(objKeysResult), !!nativeThrew || isUndefined(nativeResult) || !!nativeResult, + "Checking whether the Native and objKeys threw or returned undefined [" + dumpObj(objKeysThrew || objKeysResult) + "] - [" + dumpObj(nativeThrew || nativeResult) + "] for [" + dumpObj(value) + "]"); } } diff --git a/lib/test/src/common/polyfills/split.test.ts b/lib/test/src/common/polyfills/split.test.ts index 0a54b40d..ef3d6437 100644 --- a/lib/test/src/common/polyfills/split.test.ts +++ b/lib/test/src/common/polyfills/split.test.ts @@ -14,10 +14,23 @@ import { strSplit } from "../../../../src/string/split"; import { strSubstring } from "../../../../src/string/substring"; import { getKnownSymbol } from "../../../../src/symbol/symbol"; import { WellKnownSymbols } from "../../../../src/symbol/well_known"; +import { _initTestHooks } from "../../../../src/helpers/lazy"; describe("polyfill string split helpers", () => { - + describe("strSymSplit", () => { + + let _orgSymbol = Symbol; + beforeEach(() => { + _orgSymbol = Symbol; + _initTestHooks(); + }); + + afterEach(() => { + // eslint-disable-next-line no-global-assign + Symbol = _orgSymbol; + }); + it("null/ undefined", () => { _expectThrow(() => { assert.deepEqual(polyStrSymSplit(null as any, null as any), [""]); diff --git a/package.json b/package.json index a798e4a9..ca94aef0 100644 --- a/package.json +++ b/package.json @@ -251,12 +251,18 @@ "name": "es5-env", "path": "lib/dist-es5/index.js", "limit": "2 kb", - "import": "{ getGlobal, getInst, lazySafeGetInst, hasDocument, getDocument, hasWindow, getWindow, hasNavigator, getNavigator, hasHistory, getHistory, isNode, isWebWorker }", + "import": "{ getGlobal, getInst, hasDocument, getDocument, hasWindow, getWindow, hasNavigator, getNavigator, hasHistory, getHistory, isNode, isWebWorker }", "brotli": false, "ignore": [ "lib/dist-es5/polyfills.js", "lib/dist-es5/polyfills.*.js" ] + }, + { + "name": "es5-poly", + "path": "lib/dist-es5/polyfills.js", + "limit": "7 kb", + "brotli": false } ] }