]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
refactor: rewrite config helpers to ts (#11121)
authorDan Onoshko <danon0404@gmail.com>
Tue, 21 Mar 2023 20:03:15 +0000 (00:03 +0400)
committerGitHub <noreply@github.com>
Tue, 21 Mar 2023 20:03:15 +0000 (16:03 -0400)
* refactor: rewrite config helpers to ts

* refactor: review fixes

src/helpers/helpers.config.ts [moved from src/helpers/helpers.config.js with 61% similarity]
src/helpers/helpers.config.types.ts [new file with mode: 0644]
src/helpers/helpers.core.ts
src/types/utils.d.ts

similarity index 61%
rename from src/helpers/helpers.config.js
rename to src/helpers/helpers.config.ts
index 3820c5ce3251fe503368e76d1cb3f95495bb7242..db0b125e9e396b000a4e5284d50a2a81bed7499c 100644 (file)
@@ -1,33 +1,57 @@
-import {defined, isArray, isFunction, isObject, resolveObjectKey, _capitalize} from './helpers.core.js';
+/* eslint-disable @typescript-eslint/no-use-before-define */
+import type {AnyObject} from '../types/basic.js';
+import type {ChartMeta} from '../types/index.js';
+import type {
+  ResolverObjectKey,
+  ResolverCache,
+  ResolverProxy,
+  DescriptorDefaults,
+  Descriptor,
+  ContextCache,
+  ContextProxy
+} from './helpers.config.types.js';
+import {isArray, isFunction, isObject, resolveObjectKey, _capitalize} from './helpers.core.js';
+
+export * from './helpers.config.types.js';
 
 /**
  * Creates a Proxy for resolving raw values for options.
- * @param {object[]} scopes - The option scopes to look for values, in resolution order
- * @param {string[]} [prefixes] - The prefixes for values, in resolution order.
- * @param {object[]} [rootScopes] - The root option scopes
- * @param {string|boolean} [fallback] - Parent scopes fallback
- * @param {function} [getTarget] - callback for getting the target for changed values
+ * @param scopes - The option scopes to look for values, in resolution order
+ * @param prefixes - The prefixes for values, in resolution order.
+ * @param rootScopes - The root option scopes
+ * @param fallback - Parent scopes fallback
+ * @param getTarget - callback for getting the target for changed values
  * @returns Proxy
  * @private
  */
-export function _createResolver(scopes, prefixes = [''], rootScopes = scopes, fallback, getTarget = () => scopes[0]) {
-  if (!defined(fallback)) {
+export function _createResolver<
+  T extends AnyObject[] = AnyObject[],
+  R extends AnyObject[] = T
+>(
+  scopes: T,
+  prefixes = [''],
+  rootScopes?: R,
+  fallback?: ResolverObjectKey,
+  getTarget = () => scopes[0]
+) {
+  const finalRootScopes = rootScopes || scopes;
+  if (typeof fallback === 'undefined') {
     fallback = _resolve('_fallback', scopes);
   }
-  const cache = {
+  const cache: ResolverCache<T, R> = {
     [Symbol.toStringTag]: 'Object',
     _cacheable: true,
     _scopes: scopes,
-    _rootScopes: rootScopes,
+    _rootScopes: finalRootScopes,
     _fallback: fallback,
     _getTarget: getTarget,
-    override: (scope) => _createResolver([scope, ...scopes], prefixes, rootScopes, fallback),
+    override: (scope: AnyObject) => _createResolver([scope, ...scopes], prefixes, finalRootScopes, fallback),
   };
   return new Proxy(cache, {
     /**
      * A trap for the delete operator.
      */
-    deleteProperty(target, prop) {
+    deleteProperty(target, prop: string) {
       delete target[prop]; // remove from cache
       delete target._keys; // remove cached keys
       delete scopes[0][prop]; // remove from top level scope
@@ -37,7 +61,7 @@ export function _createResolver(scopes, prefixes = [''], rootScopes = scopes, fa
     /**
      * A trap for getting property values.
      */
-    get(target, prop) {
+    get(target, prop: string) {
       return _cached(target, prop,
         () => _resolveWithPrefixes(prop, prefixes, scopes, target));
     },
@@ -60,7 +84,7 @@ export function _createResolver(scopes, prefixes = [''], rootScopes = scopes, fa
     /**
      * A trap for the in operator.
      */
-    has(target, prop) {
+    has(target, prop: string) {
       return getKeysFromAllScopes(target).includes(prop);
     },
 
@@ -74,33 +98,41 @@ export function _createResolver(scopes, prefixes = [''], rootScopes = scopes, fa
     /**
      * A trap for setting property values.
      */
-    set(target, prop, value) {
+    set(target, prop: string, value) {
       const storage = target._storage || (target._storage = getTarget());
       target[prop] = storage[prop] = value; // set to top level scope + cache
       delete target._keys; // remove cached keys
       return true;
     }
-  });
+  }) as ResolverProxy<T, R>;
 }
 
 /**
  * Returns an Proxy for resolving option values with context.
- * @param {object} proxy - The Proxy returned by `_createResolver`
- * @param {object} context - Context object for scriptable/indexable options
- * @param {object} [subProxy] - The proxy provided for scriptable options
- * @param {{scriptable: boolean, indexable: boolean, allKeys?: boolean}} [descriptorDefaults] - Defaults for descriptors
+ * @param proxy - The Proxy returned by `_createResolver`
+ * @param context - Context object for scriptable/indexable options
+ * @param subProxy - The proxy provided for scriptable options
+ * @param descriptorDefaults - Defaults for descriptors
  * @private
  */
-export function _attachContext(proxy, context, subProxy, descriptorDefaults) {
-  const cache = {
+export function _attachContext<
+  T extends AnyObject[] = AnyObject[],
+  R extends AnyObject[] = T
+>(
+  proxy: ResolverProxy<T, R>,
+  context: AnyObject,
+  subProxy?: ResolverProxy<T, R>,
+  descriptorDefaults?: DescriptorDefaults
+) {
+  const cache: ContextCache<T, R> = {
     _cacheable: false,
     _proxy: proxy,
     _context: context,
     _subProxy: subProxy,
     _stack: new Set(),
     _descriptors: _descriptors(proxy, descriptorDefaults),
-    setContext: (ctx) => _attachContext(proxy, ctx, subProxy, descriptorDefaults),
-    override: (scope) => _attachContext(proxy.override(scope), context, subProxy, descriptorDefaults)
+    setContext: (ctx: AnyObject) => _attachContext(proxy, ctx, subProxy, descriptorDefaults),
+    override: (scope: AnyObject) => _attachContext(proxy.override(scope), context, subProxy, descriptorDefaults)
   };
   return new Proxy(cache, {
     /**
@@ -115,7 +147,7 @@ export function _attachContext(proxy, context, subProxy, descriptorDefaults) {
     /**
      * A trap for getting property values.
      */
-    get(target, prop, receiver) {
+    get(target, prop: string, receiver) {
       return _cached(target, prop,
         () => _resolveWithContext(target, prop, receiver));
     },
@@ -159,13 +191,16 @@ export function _attachContext(proxy, context, subProxy, descriptorDefaults) {
       delete target[prop]; // remove from cache
       return true;
     }
-  });
+  }) as ContextProxy<T, R>;
 }
 
 /**
  * @private
  */
-export function _descriptors(proxy, defaults = {scriptable: true, indexable: true}) {
+export function _descriptors(
+  proxy: ResolverCache,
+  defaults: DescriptorDefaults = {scriptable: true, indexable: true}
+): Descriptor {
   const {_scriptable = defaults.scriptable, _indexable = defaults.indexable, _allKeys = defaults.allKeys} = proxy;
   return {
     allKeys: _allKeys,
@@ -176,11 +211,15 @@ export function _descriptors(proxy, defaults = {scriptable: true, indexable: tru
   };
 }
 
-const readKey = (prefix, name) => prefix ? prefix + _capitalize(name) : name;
-const needsSubResolver = (prop, value) => isObject(value) && prop !== 'adapters' &&
+const readKey = (prefix: string, name: string) => prefix ? prefix + _capitalize(name) : name;
+const needsSubResolver = (prop: string, value: unknown) => isObject(value) && prop !== 'adapters' &&
   (Object.getPrototypeOf(value) === null || value.constructor === Object);
 
-function _cached(target, prop, resolve) {
+function _cached(
+  target: AnyObject,
+  prop: string,
+  resolve: () => unknown
+) {
   if (Object.prototype.hasOwnProperty.call(target, prop)) {
     return target[prop];
   }
@@ -191,7 +230,11 @@ function _cached(target, prop, resolve) {
   return value;
 }
 
-function _resolveWithContext(target, prop, receiver) {
+function _resolveWithContext(
+  target: ContextCache,
+  prop: string,
+  receiver: AnyObject
+) {
   const {_proxy, _context, _subProxy, _descriptors: descriptors} = target;
   let value = _proxy[prop]; // resolve from proxy
 
@@ -209,14 +252,18 @@ function _resolveWithContext(target, prop, receiver) {
   return value;
 }
 
-function _resolveScriptable(prop, value, target, receiver) {
+function _resolveScriptable(
+  prop: string,
+  getValue: (ctx: AnyObject, sub: AnyObject) => unknown,
+  target: ContextCache,
+  receiver: AnyObject
+) {
   const {_proxy, _context, _subProxy, _stack} = target;
   if (_stack.has(prop)) {
-    // @ts-ignore
     throw new Error('Recursion detected: ' + Array.from(_stack).join('->') + '->' + prop);
   }
   _stack.add(prop);
-  value = value(_context, _subProxy || receiver);
+  let value = getValue(_context, _subProxy || receiver);
   _stack.delete(prop);
   if (needsSubResolver(prop, value)) {
     // When scriptable option returns an object, create a resolver on that.
@@ -225,11 +272,16 @@ function _resolveScriptable(prop, value, target, receiver) {
   return value;
 }
 
-function _resolveArray(prop, value, target, isIndexable) {
+function _resolveArray(
+  prop: string,
+  value: unknown[],
+  target: ContextCache,
+  isIndexable: (key: string) => boolean
+) {
   const {_proxy, _context, _subProxy, _descriptors: descriptors} = target;
 
-  if (defined(_context.index) && isIndexable(prop)) {
-    value = value[_context.index % value.length];
+  if (typeof _context.index !== 'undefined' && isIndexable(prop)) {
+    return value[_context.index % value.length];
   } else if (isObject(value[0])) {
     // Array of objects, return array or resolvers
     const arr = value;
@@ -243,25 +295,35 @@ function _resolveArray(prop, value, target, isIndexable) {
   return value;
 }
 
-function resolveFallback(fallback, prop, value) {
+function resolveFallback(
+  fallback: ResolverObjectKey | ((prop: ResolverObjectKey, value: unknown) => ResolverObjectKey),
+  prop: ResolverObjectKey,
+  value: unknown
+) {
   return isFunction(fallback) ? fallback(prop, value) : fallback;
 }
 
-const getScope = (key, parent) => key === true ? parent
+const getScope = (key: ResolverObjectKey, parent: AnyObject) => key === true ? parent
   : typeof key === 'string' ? resolveObjectKey(parent, key) : undefined;
 
-function addScopes(set, parentScopes, key, parentFallback, value) {
+function addScopes(
+  set: Set<AnyObject>,
+  parentScopes: AnyObject[],
+  key: ResolverObjectKey,
+  parentFallback: ResolverObjectKey,
+  value: unknown
+) {
   for (const parent of parentScopes) {
     const scope = getScope(key, parent);
     if (scope) {
       set.add(scope);
       const fallback = resolveFallback(scope._fallback, key, value);
-      if (defined(fallback) && fallback !== key && fallback !== parentFallback) {
+      if (typeof fallback !== 'undefined' && fallback !== key && fallback !== parentFallback) {
         // When we reach the descriptor that defines a new _fallback, return that.
         // The fallback will resume to that new scope.
         return fallback;
       }
-    } else if (scope === false && defined(parentFallback) && key !== parentFallback) {
+    } else if (scope === false && typeof parentFallback !== 'undefined' && key !== parentFallback) {
       // Fallback to `false` results to `false`, when falling back to different key.
       // For example `interaction` from `hover` or `plugins.tooltip` and `animation` from `animations`
       return null;
@@ -270,34 +332,49 @@ function addScopes(set, parentScopes, key, parentFallback, value) {
   return false;
 }
 
-function createSubResolver(parentScopes, resolver, prop, value) {
+function createSubResolver(
+  parentScopes: AnyObject[],
+  resolver: ResolverCache,
+  prop: ResolverObjectKey,
+  value: unknown
+) {
   const rootScopes = resolver._rootScopes;
   const fallback = resolveFallback(resolver._fallback, prop, value);
   const allScopes = [...parentScopes, ...rootScopes];
-  const set = new Set();
+  const set = new Set<AnyObject>();
   set.add(value);
   let key = addScopesFromKey(set, allScopes, prop, fallback || prop, value);
   if (key === null) {
     return false;
   }
-  if (defined(fallback) && fallback !== prop) {
+  if (typeof fallback !== 'undefined' && fallback !== prop) {
     key = addScopesFromKey(set, allScopes, fallback, key, value);
     if (key === null) {
       return false;
     }
   }
   return _createResolver(Array.from(set), [''], rootScopes, fallback,
-    () => subGetTarget(resolver, prop, value));
+    () => subGetTarget(resolver, prop as string, value));
 }
 
-function addScopesFromKey(set, allScopes, key, fallback, item) {
+function addScopesFromKey(
+  set: Set<AnyObject>,
+  allScopes: AnyObject[],
+  key: ResolverObjectKey,
+  fallback: ResolverObjectKey,
+  item: unknown
+) {
   while (key) {
     key = addScopes(set, allScopes, key, fallback, item);
   }
   return key;
 }
 
-function subGetTarget(resolver, prop, value) {
+function subGetTarget(
+  resolver: ResolverCache,
+  prop: string,
+  value: unknown
+) {
   const parent = resolver._getTarget();
   if (!(prop in parent)) {
     parent[prop] = {};
@@ -310,11 +387,16 @@ function subGetTarget(resolver, prop, value) {
   return target || {};
 }
 
-function _resolveWithPrefixes(prop, prefixes, scopes, proxy) {
-  let value;
+function _resolveWithPrefixes(
+  prop: string,
+  prefixes: string[],
+  scopes: AnyObject[],
+  proxy: ResolverProxy
+) {
+  let value: unknown;
   for (const prefix of prefixes) {
     value = _resolve(readKey(prefix, prop), scopes);
-    if (defined(value)) {
+    if (typeof value !== 'undefined') {
       return needsSubResolver(prop, value)
         ? createSubResolver(scopes, proxy, prop, value)
         : value;
@@ -322,19 +404,19 @@ function _resolveWithPrefixes(prop, prefixes, scopes, proxy) {
   }
 }
 
-function _resolve(key, scopes) {
+function _resolve(key: string, scopes: AnyObject[]) {
   for (const scope of scopes) {
     if (!scope) {
       continue;
     }
     const value = scope[key];
-    if (defined(value)) {
+    if (typeof value !== 'undefined') {
       return value;
     }
   }
 }
 
-function getKeysFromAllScopes(target) {
+function getKeysFromAllScopes(target: ResolverCache) {
   let keys = target._keys;
   if (!keys) {
     keys = target._keys = resolveKeysFromAllScopes(target._scopes);
@@ -342,8 +424,8 @@ function getKeysFromAllScopes(target) {
   return keys;
 }
 
-function resolveKeysFromAllScopes(scopes) {
-  const set = new Set();
+function resolveKeysFromAllScopes(scopes: AnyObject[]) {
+  const set = new Set<string>();
   for (const scope of scopes) {
     for (const key of Object.keys(scope).filter(k => !k.startsWith('_'))) {
       set.add(key);
@@ -352,11 +434,16 @@ function resolveKeysFromAllScopes(scopes) {
   return Array.from(set);
 }
 
-export function _parseObjectDataRadialScale(meta, data, start, count) {
+export function _parseObjectDataRadialScale(
+  meta: ChartMeta<'line' | 'scatter'>,
+  data: AnyObject[],
+  start: number,
+  count: number
+) {
   const {iScale} = meta;
   const {key = 'r'} = this._parsing;
-  const parsed = new Array(count);
-  let i, ilen, index, item;
+  const parsed = new Array<{r: unknown}>(count);
+  let i: number, ilen: number, index: number, item: AnyObject;
 
   for (i = 0, ilen = count; i < ilen; ++i) {
     index = i + start;
diff --git a/src/helpers/helpers.config.types.ts b/src/helpers/helpers.config.types.ts
new file mode 100644 (file)
index 0000000..64f0e32
--- /dev/null
@@ -0,0 +1,60 @@
+import type {AnyObject} from '../types/basic.js';
+import type {Merge} from '../types/utils.js';
+
+export type ResolverObjectKey = string | boolean;
+
+export interface ResolverCache<
+  T extends AnyObject[] = AnyObject[],
+  R extends AnyObject[] = T
+> {
+  [Symbol.toStringTag]: 'Object';
+  _cacheable: boolean;
+  _scopes: T;
+  _rootScopes: T | R;
+  _fallback: ResolverObjectKey;
+  _keys?: string[];
+  _scriptable?: boolean;
+  _indexable?: boolean;
+  _allKeys?: boolean;
+  _storage?: T[number];
+  _getTarget(): T[number];
+  override<S extends AnyObject>(scope: S): ResolverProxy<(T[number] | S)[], T | R>
+}
+
+export type ResolverProxy<
+  T extends AnyObject[] = AnyObject[],
+  R extends AnyObject[] = T
+> = Merge<T[number]> & ResolverCache<T, R>
+
+export interface DescriptorDefaults {
+  scriptable: boolean;
+  indexable: boolean;
+  allKeys?: boolean
+}
+
+export interface Descriptor {
+  allKeys: boolean;
+  scriptable: boolean;
+  indexable: boolean;
+  isScriptable(key: string): boolean;
+  isIndexable(key: string): boolean;
+}
+
+export interface ContextCache<
+  T extends AnyObject[] = AnyObject[],
+  R extends AnyObject[] = T
+> {
+  _cacheable: boolean;
+  _proxy: ResolverProxy<T, R>;
+  _context: AnyObject;
+  _subProxy: ResolverProxy<T, R>;
+  _stack: Set<string>;
+  _descriptors: Descriptor
+  setContext(ctx: AnyObject): ContextProxy<T, R>
+  override<S extends AnyObject>(scope: S): ContextProxy<(T[number] | S)[], T | R>
+}
+
+export type ContextProxy<
+  T extends AnyObject[] = AnyObject[],
+  R extends AnyObject[] = T
+> = Merge<T[number]> & ContextCache<T, R>;
index 74cd7ab17047058a2bd4462b8294265dc45eaa1c..723495c379947d5c17d70b14f148b8c1078ba904 100644 (file)
@@ -375,7 +375,7 @@ function _getKeyResolver(key: string) {
   };
 }
 
-export function resolveObjectKey(obj: AnyObject, key: string): AnyObject {
+export function resolveObjectKey(obj: AnyObject, key: string): any {
   const resolver = keyResolvers[key] || (keyResolvers[key] = _getKeyResolver(key));
   return resolver(obj);
 }
index a8533f22da8ea99a02e93e5d5f78a1d165300ec1..17b1cbd9f77fbffbca43fc9dc67b25ad34a06e0b 100644 (file)
@@ -18,3 +18,13 @@ export type DistributiveArray<T> = [T] extends [unknown] ? Array<T> : never
 
 // https://stackoverflow.com/a/50375286
 export type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
+
+export type AllKeys<T> = T extends any ? keyof T : never;
+
+export type PickType<T, K extends AllKeys<T>> = T extends { [k in K]?: any }
+  ? T[K]
+  : undefined;
+
+export type Merge<T extends object> = {
+  [k in AllKeys<T>]: PickType<T, k>;
+};