You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@echarts.apache.org by su...@apache.org on 2020/09/07 16:00:00 UTC

[incubator-echarts] branch next updated: ts: (1) add more strict type check for id, name from option. (2) add more strict conversion for id, name from option.

This is an automated email from the ASF dual-hosted git repository.

sushuang pushed a commit to branch next
in repository https://gitbox.apache.org/repos/asf/incubator-echarts.git


The following commit(s) were added to refs/heads/next by this push:
     new 99ab1d7  ts: (1) add more strict type check for id, name from option. (2) add more strict conversion for id, name from option.
99ab1d7 is described below

commit 99ab1d7a2f1413ce38dd55d3808c4e434fe49ba8
Author: 100pah <su...@gmail.com>
AuthorDate: Mon Sep 7 23:59:12 2020 +0800

    ts: (1) add more strict type check for id, name from option. (2) add more strict conversion for id, name from option.
---
 src/chart/helper/createGraphFromNodeEdge.ts |  3 +-
 src/chart/helper/whiskerBoxCommon.ts        |  1 -
 src/chart/sankey/SankeySeries.ts            |  2 +-
 src/component/timeline/TimelineModel.ts     | 10 ++--
 src/data/List.ts                            | 75 ++++++++++++++++++++++-------
 src/data/Tree.ts                            | 20 ++++----
 src/data/helper/dataStackHelper.ts          | 18 +++----
 src/util/model.ts                           | 31 +++++++++---
 src/util/types.ts                           | 12 +++--
 9 files changed, 115 insertions(+), 57 deletions(-)

diff --git a/src/chart/helper/createGraphFromNodeEdge.ts b/src/chart/helper/createGraphFromNodeEdge.ts
index 16206de..6e07d27 100644
--- a/src/chart/helper/createGraphFromNodeEdge.ts
+++ b/src/chart/helper/createGraphFromNodeEdge.ts
@@ -30,6 +30,7 @@ import {
     OptionDataItemObject
 } from '../../util/types';
 import SeriesModel from '../../model/Series';
+import { convertOptionIdName } from '../../util/model';
 
 export default function (
     nodes: OptionSourceDataOriginal<OptionDataValue, OptionDataItemObject<OptionDataValue>>,
@@ -59,7 +60,7 @@ export default function (
         if (graph.addEdge(source, target, linkCount)) {
             validEdges.push(link);
             linkNameList.push(zrUtil.retrieve(
-                link.id != null ? link.id + '' : null,
+                convertOptionIdName(link.id, null),
                 source + ' > ' + target
             ));
             linkCount++;
diff --git a/src/chart/helper/whiskerBoxCommon.ts b/src/chart/helper/whiskerBoxCommon.ts
index d1f6976..679360d 100644
--- a/src/chart/helper/whiskerBoxCommon.ts
+++ b/src/chart/helper/whiskerBoxCommon.ts
@@ -25,7 +25,6 @@ import type { SeriesOption, SeriesOnCartesianOptionMixin, LayoutOrient } from '.
 import type GlobalModel from '../../model/Global';
 import type SeriesModel from '../../model/Series';
 import type CartesianAxisModel from '../../coord/cartesian/AxisModel';
-import type DataDimensionInfo from '../../data/DataDimensionInfo';
 import type List from '../../data/List';
 import type Axis2D from '../../coord/cartesian/Axis2D';
 import { CoordDimensionDefinition } from '../../data/helper/createDimensions';
diff --git a/src/chart/sankey/SankeySeries.ts b/src/chart/sankey/SankeySeries.ts
index 28d5e6f..3b6e361 100644
--- a/src/chart/sankey/SankeySeries.ts
+++ b/src/chart/sankey/SankeySeries.ts
@@ -38,7 +38,7 @@ import GlobalModel from '../../model/Global';
 import List from '../../data/List';
 import { LayoutRect } from '../../util/layout';
 import { createTooltipMarkup } from '../../component/tooltip/tooltipMarkup';
-import { defaultSeriesFormatTooltip } from '../../component/tooltip/seriesFormatTooltip';
+
 
 type FocusNodeAdjacency = boolean | 'inEdges' | 'outEdges' | 'allEdges';
 
diff --git a/src/component/timeline/TimelineModel.ts b/src/component/timeline/TimelineModel.ts
index 4598abd..7160d14 100644
--- a/src/component/timeline/TimelineModel.ts
+++ b/src/component/timeline/TimelineModel.ts
@@ -19,7 +19,6 @@
 
 import ComponentModel from '../../model/Component';
 import List from '../../data/List';
-import * as modelUtil from '../../util/model';
 import {
     ComponentOption,
     BoxLayoutOptionMixin,
@@ -38,6 +37,7 @@ import {
 import Model from '../../model/Model';
 import GlobalModel, { GlobalModelSetOptionOpts } from '../../model/Global';
 import { each, isObject, clone, isString } from 'zrender/src/core/util';
+import { convertOptionIdName, getDataItemValue } from '../../util/model';
 
 
 export interface TimelineControlStyle extends ItemStyleOption {
@@ -247,7 +247,7 @@ class TimelineModel extends ComponentModel<TimelineOption> {
         if (axisType === 'category') {
             processedDataArr = [];
             each(dataArr, function (item, index) {
-                let value = modelUtil.getDataItemValue(item);
+                const value = convertOptionIdName(getDataItemValue(item), '');
                 let newItem;
 
                 if (isObject(item)) {
@@ -260,11 +260,7 @@ class TimelineModel extends ComponentModel<TimelineOption> {
 
                 processedDataArr.push(newItem);
 
-                if (!isString(value) && (value == null || isNaN(value as number))) {
-                    value = '';
-                }
-
-                names.push(value + '');
+                names.push(value);
             });
         }
         else {
diff --git a/src/data/List.ts b/src/data/List.ts
index e1e5dba..1f0d0a8 100644
--- a/src/data/List.ts
+++ b/src/data/List.ts
@@ -33,9 +33,10 @@ import {ArrayLike, Dictionary, FunctionPropertyNames} from 'zrender/src/core/typ
 import Element from 'zrender/src/Element';
 import {
     DimensionIndex, DimensionName, DimensionLoose, OptionDataItem,
-    ParsedValue, ParsedValueNumeric, OrdinalNumber, DimensionUserOuput, ModelOption, SeriesDataType
+    ParsedValue, ParsedValueNumeric, OrdinalNumber, DimensionUserOuput,
+    ModelOption, SeriesDataType, OrdinalRawValue
 } from '../util/types';
-import {isDataItemOption} from '../util/model';
+import {isDataItemOption, convertOptionIdName} from '../util/model';
 import { getECData } from '../util/ecData';
 import { PathStyleProps } from 'zrender/src/graphic/Path';
 import type Graph from './Graph';
@@ -144,12 +145,21 @@ export interface DefaultDataVisual {
     colorFromPalette?: boolean
 }
 
+export interface DataCalculationInfo<SERIES_MODEL> {
+    stackedDimension: string;
+    stackedByDimension: string;
+    isStackedByIndex: boolean;
+    stackedOverDimension: string;
+    stackResultDimension: string;
+    stackedOnSeries?: SERIES_MODEL;
+}
+
 // -----------------------------
 // Internal method declarations:
 // -----------------------------
 let defaultDimValueGetters: {[sourceFormat: string]: DimValueGetter};
 let prepareInvertedIndex: (list: List) => void;
-let getRawValueFromStore: (list: List, dimIndex: number, rawIndex: number) => any;
+let getRawValueFromStore: (list: List, dimIndex: number, rawIndex: number) => ParsedValue | OrdinalRawValue;
 let getIndicesCtor: (list: List) => DataArrayLikeConstructor;
 let prepareChunks: (
     storage: DataStorage, dimInfo: DataDimensionInfo, chunkSize: number, chunkCount: number, end: number
@@ -245,7 +255,7 @@ class List<
 
     private _invertedIndicesMap: {[dimName: string]: ArrayLike<number>};
 
-    private _calculationInfo: {[key: string]: any} = {};
+    private _calculationInfo: DataCalculationInfo<HostModel> = {} as DataCalculationInfo<HostModel>;
 
     // User output info of this data.
     // DO NOT use it in other places!
@@ -628,7 +638,7 @@ class List<
             // ??? FIXME not check by pure but sourceFormat?
             // TODO refactor these logic.
             if (!rawData.pure) {
-                let name: any = nameList[idx];
+                let name: string = nameList[idx];
 
                 if (dataItem && name == null) {
                     // If dataItem is {name: ...}, it has highest priority.
@@ -636,24 +646,26 @@ class List<
                     if ((dataItem as any).name != null) {
                         // There is no other place to persistent dataItem.name,
                         // so save it to nameList.
-                        nameList[idx] = name = (dataItem as any).name;
+                        nameList[idx] = name = convertOptionIdName((dataItem as any).name, null);
                     }
                     else if (nameDimIdx != null) {
                         const nameDim = dimensions[nameDimIdx];
                         const nameDimChunk = storage[nameDim][chunkIndex];
                         if (nameDimChunk) {
-                            name = nameDimChunk[chunkOffset];
                             const ordinalMeta = dimensionInfoMap[nameDim].ordinalMeta;
-                            if (ordinalMeta && ordinalMeta.categories.length) {
-                                name = ordinalMeta.categories[name];
-                            }
+                            name = convertOptionIdName(
+                                (ordinalMeta && ordinalMeta.categories.length)
+                                    ? ordinalMeta.categories[nameDimChunk[chunkOffset] as number]
+                                    : nameDimChunk[chunkOffset],
+                                null
+                            );
                         }
                     }
                 }
 
                 // Try using the id in option
                 // id or name is used on dynamical data, mapping old and new items.
-                let id = dataItem == null ? null : (dataItem as any).id;
+                let id: string = dataItem == null ? null : convertOptionIdName((dataItem as any).id, null);
 
                 if (id == null && name != null) {
                     // Use name as id and add counter to avoid same name
@@ -908,17 +920,29 @@ class List<
         this._approximateExtent[dim] = extent.slice() as [number, number];
     }
 
-    getCalculationInfo(key: string): any {
+    getCalculationInfo<CALC_INFO_KEY extends keyof DataCalculationInfo<HostModel>>(
+        key: CALC_INFO_KEY
+    ): DataCalculationInfo<HostModel>[CALC_INFO_KEY] {
         return this._calculationInfo[key];
     }
 
     /**
      * @param key or k-v object
      */
-    setCalculationInfo(key: string | object, value?: any) {
+    setCalculationInfo(
+        key: DataCalculationInfo<HostModel>
+    ): void;
+    setCalculationInfo<CALC_INFO_KEY extends keyof DataCalculationInfo<HostModel>>(
+        key: CALC_INFO_KEY,
+        value: DataCalculationInfo<HostModel>[CALC_INFO_KEY]
+    ): void;
+    setCalculationInfo(
+        key: (keyof DataCalculationInfo<HostModel>) | DataCalculationInfo<HostModel>,
+        value?: DataCalculationInfo<HostModel>[keyof DataCalculationInfo<HostModel>]
+    ): void {
         isObject(key)
             ? zrUtil.extend(this._calculationInfo, key as object)
-            : (this._calculationInfo[key] = value);
+            : ((this._calculationInfo as any)[key] = value);
     }
 
     /**
@@ -1140,13 +1164,25 @@ class List<
         }
     }
 
+    /**
+     * @return Never be null/undefined. `number` will be converted to string. Becuase:
+     * In most cases, name is used in display, where returning a string is more convenient.
+     * In other cases, name is used in query (see `indexOfName`), where we can keep the
+     * rule that name `2` equals to name `'2'`.
+     */
     getName(idx: number): string {
         const rawIndex = this.getRawIndex(idx);
         return this._nameList[rawIndex]
-            || getRawValueFromStore(this, this._nameDimIdx, rawIndex)
+            || convertOptionIdName(getRawValueFromStore(this, this._nameDimIdx, rawIndex), '')
             || '';
     }
 
+    /**
+     * @return Never null/undefined. `number` will be converted to string. Becuase:
+     * In all cases having encountered at present, id is used in making diff comparison, which
+     * are usually based on hash map. We can keep the rule that the internal id are always string
+     * (treat `2` is the same as `'2'`) to make the related logic simple.
+     */
     getId(idx: number): string {
         return getId(this, this.getRawIndex(idx));
     }
@@ -1981,7 +2017,9 @@ class List<
             });
         };
 
-        getRawValueFromStore = function (list: List, dimIndex: number, rawIndex: number): any {
+        getRawValueFromStore = function (
+            list: List, dimIndex: number, rawIndex: number
+        ): ParsedValue | OrdinalRawValue {
             let val;
             if (dimIndex != null) {
                 const chunkSize = list._chunkSize;
@@ -2043,10 +2081,13 @@ class List<
             return -1;
         };
 
+        /**
+         * @see the comment of `List['getId']`.
+         */
         getId = function (list: List, rawIndex: number): string {
             let id = list._idList[rawIndex];
             if (id == null) {
-                id = getRawValueFromStore(list, list._idDimIdx, rawIndex);
+                id = convertOptionIdName(getRawValueFromStore(list, list._idDimIdx, rawIndex), null);
             }
             if (id == null) {
                 // FIXME Check the usage in graph, should not use prefix.
diff --git a/src/data/Tree.ts b/src/data/Tree.ts
index bf5b8fb..a42591a 100644
--- a/src/data/Tree.ts
+++ b/src/data/Tree.ts
@@ -26,8 +26,12 @@ import Model from '../model/Model';
 import linkList from './helper/linkList';
 import List from './List';
 import createDimensions from './helper/createDimensions';
-import { DimensionLoose, ParsedValue } from '../util/types';
+import {
+    DimensionLoose, ParsedValue, OptionId, OptionDataValue,
+    OptionDataItemObject
+} from '../util/types';
 import { Dictionary } from 'zrender/src/core/types';
+import { convertOptionIdName } from '../util/model';
 
 type TreeTraverseOrder = 'preorder' | 'postorder';
 type TreeTraverseCallback<Ctx> = (this: Ctx, node: TreeNode) => boolean | void;
@@ -36,10 +40,8 @@ type TreeTraverseOption = {
     attr?: 'children' | 'viewChildren'
 };
 
-interface TreeNodeData {
-    name?: string
-    value?: any
-    children?: TreeNodeData[]
+interface TreeNodeOption extends Pick<OptionDataItemObject<OptionDataValue>, 'name' | 'value'> {
+    children?: TreeNodeOption[];
 }
 
 export class TreeNode {
@@ -405,7 +407,7 @@ class Tree<HostModel extends Model = Model, LevelOption = any, LeavesOption = an
      *     ]
      * }
      */
-    static createTree<T extends TreeNodeData, HostModel extends Model, LevelOption>(
+    static createTree<T extends TreeNodeOption, HostModel extends Model, LevelOption>(
         dataRoot: T,
         hostModel: HostModel,
         treeOptions?: {
@@ -415,18 +417,18 @@ class Tree<HostModel extends Model = Model, LevelOption = any, LeavesOption = an
     ) {
 
         const tree = new Tree(hostModel, treeOptions && treeOptions.levels);
-        const listData: TreeNodeData[] = [];
+        const listData: TreeNodeOption[] = [];
         let dimMax = 1;
 
         buildHierarchy(dataRoot);
 
-        function buildHierarchy(dataNode: TreeNodeData, parentNode?: TreeNode) {
+        function buildHierarchy(dataNode: TreeNodeOption, parentNode?: TreeNode) {
             const value = dataNode.value;
             dimMax = Math.max(dimMax, zrUtil.isArray(value) ? value.length : 1);
 
             listData.push(dataNode);
 
-            const node = new TreeNode(dataNode.name, tree);
+            const node = new TreeNode(convertOptionIdName(dataNode.name, ''), tree);
             parentNode
                 ? addChild(node, parentNode)
                 : (tree.root = node);
diff --git a/src/data/helper/dataStackHelper.ts b/src/data/helper/dataStackHelper.ts
index 5682435..cef669c 100644
--- a/src/data/helper/dataStackHelper.ts
+++ b/src/data/helper/dataStackHelper.ts
@@ -20,16 +20,9 @@
 import {each, isString} from 'zrender/src/core/util';
 import DataDimensionInfo from '../DataDimensionInfo';
 import SeriesModel from '../../model/Series';
-import List from '../List';
+import List, { DataCalculationInfo } from '../List';
 import type { SeriesOption, SeriesStackOptionMixin, DimensionName } from '../../util/types';
 
-interface DataStackResult {
-    stackedDimension: string
-    stackedByDimension: string
-    isStackedByIndex: boolean
-    stackedOverDimension: string
-    stackResultDimension: string
-}
 
 /**
  * Note that it is too complicated to support 3d stack by value
@@ -58,7 +51,14 @@ export function enableDataStack(
         stackedCoordDimension?: string
         byIndex?: boolean
     }
-): DataStackResult {
+): Pick<
+    DataCalculationInfo<unknown>,
+    'stackedDimension'
+    | 'stackedByDimension'
+    | 'isStackedByIndex'
+    | 'stackedOverDimension'
+    | 'stackResultDimension'
+> {
     opt = opt || {};
     let byIndex = opt.byIndex;
     const stackedCoordDimension = opt.stackedCoordDimension;
diff --git a/src/util/model.ts b/src/util/model.ts
index dffc341..c09a154 100644
--- a/src/util/model.ts
+++ b/src/util/model.ts
@@ -41,7 +41,9 @@ import {
     OptionDataItem,
     OptionDataValue,
     TooltipRenderMode,
-    Payload
+    Payload,
+    OptionId,
+    OptionName
 } from './types';
 import { Dictionary } from 'zrender/src/core/types';
 import SeriesModel from '../model/Series';
@@ -150,7 +152,7 @@ export function isDataItemOption(dataItem: OptionDataItem): boolean {
 // number id will not be converted to string in option.
 // number id will be converted to string in component instance id.
 export interface MappingExistingItem {
-    id?: string | number;
+    id?: OptionId;
     name?: string;
 };
 /**
@@ -499,7 +501,11 @@ function makeIdAndName(
     });
 }
 
-function keyExistAndEqual(attr: 'id' | 'name', obj1: MappingExistingItem, obj2: MappingExistingItem): boolean {
+function keyExistAndEqual(
+    attr: 'id' | 'name',
+    obj1: { id?: OptionId, name?: OptionName },
+    obj2: { id?: OptionId, name?: OptionName }
+): boolean {
     const key1 = obj1[attr];
     const key2 = obj2[attr];
     // See `MappingExistingItem`. `id` and `name` trade string equals to number.
@@ -509,13 +515,22 @@ function keyExistAndEqual(attr: 'id' | 'name', obj1: MappingExistingItem, obj2:
 /**
  * @return return null if not exist.
  */
-function makeComparableKey(val: string | number): string {
+function makeComparableKey(val: unknown): string {
     if (__DEV__) {
         if (val == null) {
             throw new Error();
         }
     }
-    return val + '';
+    return convertOptionIdName(val, '');
+}
+
+export function convertOptionIdName(idOrName: unknown, defaultValue: string): string {
+    const type = typeof idOrName;
+    return type === 'string'
+        ? idOrName as string
+        : (type === 'number' || isStringSafe(idOrName))
+        ? idOrName + ''
+        : defaultValue;
 }
 
 export function validateIdOrName(idOrName: unknown) {
@@ -542,7 +557,7 @@ export function isNameSpecified(componentModel: ComponentModel): boolean {
  * @param {Object} cmptOption
  * @return {boolean}
  */
-export function isComponentIdInternal(cmptOption: MappingExistingItem): boolean {
+export function isComponentIdInternal(cmptOption: { id?: MappingExistingItem['id'] }): boolean {
     return cmptOption
         && cmptOption.id != null
         && makeComparableKey(cmptOption.id).indexOf(INTERNAL_COMPONENT_ID_PREFIX) === 0;
@@ -733,8 +748,8 @@ let innerUniqueIndex = getRandomIdBase();
  * The priority is: index > id > name, the same with `ecModel.queryComponents`.
  */
 export type ModelFinderIndexQuery = number | number[] | 'all' | 'none' | false;
-export type ModelFinderIdQuery = number | number[] | string | string[];
-export type ModelFinderNameQuery = number | number[] | string | string[];
+export type ModelFinderIdQuery = OptionId | OptionId[];
+export type ModelFinderNameQuery = OptionId | OptionId[];
 export type ModelFinder = string | ModelFinderObject;
 export type ModelFinderObject = {
     seriesIndex?: ModelFinderIndexQuery, seriesId?: ModelFinderIdQuery, seriesName?: ModelFinderNameQuery
diff --git a/src/util/types.ts b/src/util/types.ts
index c33f0c7..d57e6b3 100644
--- a/src/util/types.ts
+++ b/src/util/types.ts
@@ -517,11 +517,15 @@ export type OptionDataItem =
     | OptionDataItemObject<OptionDataValue>;
 // Only for `SOURCE_FORMAT_KEYED_ORIGINAL`
 export type OptionDataItemObject<T> = {
-    id?: string | number;
-    name?: string;
+    id?: OptionId;
+    name?: OptionName;
     value?: T[] | T;
     selected?: boolean;
 };
+// Compat number because it is usually used and not easy to
+// restrict it in practise.
+export type OptionId = string | number;
+export type OptionName = string | number;
 export interface GraphEdgeItemObject<
     VAL extends OptionDataValue
 > extends OptionDataItemObject<VAL> {
@@ -1247,8 +1251,8 @@ export interface CommonAxisPointerOption {
 export interface ComponentOption {
     type?: string;
 
-    id?: string;
-    name?: string;
+    id?: OptionId;
+    name?: OptionName;
 
     z?: number;
     zlevel?: number;


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org
For additional commands, e-mail: commits-help@echarts.apache.org