From 97813176a0d62e8cf0238279632bb8387ef03346 Mon Sep 17 00:00:00 2001 From: Ilya Trubachev Date: Wed, 20 Sep 2023 18:37:13 +0300 Subject: [PATCH] relax recipe 82 Signed-off-by: Ilya Trubachev --- linter-4.2/cookbook_convertor/res/recipes.rst | 31 ++++++++------ linter-4.2/docs/rules/recipe80.md | 2 +- linter-4.2/docs/rules/recipe82.md | 25 ++++++----- linter-4.2/src/CookBookMsg.ts | 2 +- linter-4.2/src/TypeScriptLinter.ts | 28 ++++++++----- linter-4.2/src/Utils.ts | 41 +++++++++++++++---- linter-4.2/test/for_stmts.ts | 37 ++++++++++++++++- linter-4.2/test/indexed_derived_from_array.ts | 7 ++++ linter-4.2/test_rules/rule82.ts.autofix.json | 11 +---- linter-4.2/test_rules/rule82.ts.strict.json | 10 +---- linter-4.2/test_rules/rule94.ts.autofix.json | 2 +- linter-4.2/test_rules/rule94.ts.strict.json | 2 +- linter/cookbook_convertor/res/recipes.rst | 32 ++++++++------- linter/docs/rules/recipe80.md | 2 +- linter/docs/rules/recipe82.md | 25 ++++++----- linter/src/CookBookMsg.ts | 2 +- linter/src/TypeScriptLinter.ts | 19 +++++---- linter/src/utils/TsUtils.ts | 40 ++++++++++++++---- linter/test/for_stmts.ts | 37 ++++++++++++++++- linter/test/indexed_derived_from_array.ts | 7 ++++ linter/test_rules/rule82.ts.autofix.json | 11 +---- linter/test_rules/rule82.ts.strict.json | 10 +---- linter/test_rules/rule94.ts.autofix.json | 2 +- linter/test_rules/rule94.ts.strict.json | 2 +- 24 files changed, 256 insertions(+), 131 deletions(-) diff --git a/linter-4.2/cookbook_convertor/res/recipes.rst b/linter-4.2/cookbook_convertor/res/recipes.rst index c069dd211..8f4ec5447 100644 --- a/linter-4.2/cookbook_convertor/res/recipes.rst +++ b/linter-4.2/cookbook_convertor/res/recipes.rst @@ -2781,33 +2781,38 @@ iterate over data. .. _R082: -|CB_R| #82: ``for-of`` is supported only for arrays ------------------------------------------------------- +|CB_R| ``for-of`` is supported only for arrays, strings, sets, maps and classes derived from them +------------------------------------------------------------------------------------------------- |CB_RULE| ~~~~~~~~~ -|LANG| supports the iteration over arrays by the ``for .. of`` loop, -but does not support the iteration of objects content. +|LANG| supports the iteration over arrays, strings, sets, maps and classes +derived from them by the ``for .. of`` loop, but does not support the +iteration of objects content. All typed arrays from the standard +library (for example, ``Int32Array``) are also supported. |CB_BAD| ~~~~~~~~ -.. code:: typescript - - let a: string[] = ["a", "b", "c"] - for (let s of a) { - console.log(s) +.. code-block:: typescript + class A { + prop1: number; + prop2: number; + } + let a = new A() + for (let prop of a) { + console.log(prop) } |CB_OK| ~~~~~~~ -.. code:: typescript +.. code-block:: typescript - let a: string[] = ["a", "b", "c"] - for (let s of a) { - console.log(s) + let a = new Set([1, 2, 3]) + for (let n of a) { + console.log(n) } |CB_SEE| diff --git a/linter-4.2/docs/rules/recipe80.md b/linter-4.2/docs/rules/recipe80.md index ea2a978fd..48e562fba 100644 --- a/linter-4.2/docs/rules/recipe80.md +++ b/linter-4.2/docs/rules/recipe80.md @@ -36,6 +36,6 @@ cannot change at runtime. For arrays, iterate with the regular ``for`` loop. ## See also -- Recipe 082: ``for-of`` is supported only for arrays and strings (``arkts-for-of-str-arr``) +- Recipe 082: ``for-of`` is supported only for arrays, strings, sets, maps and classes derived from them (``arkts-for-of-str-arr``) diff --git a/linter-4.2/docs/rules/recipe82.md b/linter-4.2/docs/rules/recipe82.md index 2f5c55af1..43fe9f521 100644 --- a/linter-4.2/docs/rules/recipe82.md +++ b/linter-4.2/docs/rules/recipe82.md @@ -1,22 +1,26 @@ -# ``for-of`` is supported only for arrays and strings +# ``for-of`` is supported only for arrays, strings, sets, maps and classes derived from them Rule ``arkts-for-of-str-arr`` **Severity: error** -ArkTS supports the iteration over arrays and strings by the ``for .. of`` loop, -but does not support the iteration of objects content. All typed arrays from -the standard library (for example, ``Int32Array``) are also supported. - +|LANG| supports the iteration over arrays, strings, sets, maps and classes +derived from them by the ``for .. of`` loop, but does not support the +iteration of objects content. All typed arrays from the standard +library (for example, ``Int32Array``) are also supported. ## TypeScript ``` - let a: Set = new Set([1, 2, 3]) - for (let s of a) { - console.log(s) + class A { + prop1: number; + prop2: number; + } + let a = new A() + for (let prop of a) { + console.log(prop) } ``` @@ -26,9 +30,8 @@ the standard library (for example, ``Int32Array``) are also supported. ``` - let a: Set = new Set([1, 2, 3]) - let numbers = Array.from(a.values()) - for (let n of numbers) { + let a = new Set([1, 2, 3]) + for (let n of a) { console.log(n) } diff --git a/linter-4.2/src/CookBookMsg.ts b/linter-4.2/src/CookBookMsg.ts index cf7f65952..684402979 100644 --- a/linter-4.2/src/CookBookMsg.ts +++ b/linter-4.2/src/CookBookMsg.ts @@ -101,7 +101,7 @@ cookBookTag[78] = ''; cookBookTag[79] = 'Type annotation in catch clause is not supported (arkts-no-types-in-catch)'; cookBookTag[80] = '"for .. in" is not supported (arkts-no-for-in)'; cookBookTag[81] = ''; -cookBookTag[82] = '"for-of" is supported only for arrays and strings (arkts-for-of-str-arr)'; +cookBookTag[82] = '"for-of" is supported only for arrays, strings, sets, maps and classes derived from them (arkts-for-of-str-arr)'; cookBookTag[83] = 'Mapped type expression is not supported (arkts-no-mapped-types)'; cookBookTag[84] = '"with" statement is not supported (arkts-no-with)'; cookBookTag[85] = ''; diff --git a/linter-4.2/src/TypeScriptLinter.ts b/linter-4.2/src/TypeScriptLinter.ts index 0eb571450..415ef9ba3 100644 --- a/linter-4.2/src/TypeScriptLinter.ts +++ b/linter-4.2/src/TypeScriptLinter.ts @@ -15,7 +15,7 @@ import * as ts from "typescript"; import * as path from "node:path"; -import { TsUtils, getNodeOrLineEnd } from "./Utils"; +import { TsUtils, getNodeOrLineEnd, CheckType } from "./Utils"; import { FaultID, faultsAttrs } from "./Problems"; import { cookBookMsg, cookBookTag } from "./CookBookMsg"; import { LinterConfig } from "./TypeScriptLinterConfig"; @@ -718,13 +718,19 @@ export class TypeScriptLinter { undefined, ts.NodeBuilderFlags.None ); - - if (!(ts.isArrayLiteralExpression(expr) || - (exprTypeNode && ts.isArrayTypeNode(exprTypeNode)) || - this.tsUtils.isTypedArray(exprTypeNode) || - exprType.isStringLiteral() || - this.tsUtils.isStringType(exprType) || - this.tsUtils.isDerivedFromArray(exprType))) { + + const isArrayLike = + ts.isArrayLiteralExpression(expr) || + (exprTypeNode && ts.isArrayTypeNode(exprTypeNode)) || + this.tsUtils.isTypedArray(exprTypeNode) || + this.tsUtils.isDerivedFrom(exprType, CheckType.Array); + const isStringLike = exprType.isStringLiteral() || this.tsUtils.isStringType(exprType) || + this.tsUtils.isDerivedFrom(exprType, CheckType.String) + const isSetLike = this.tsUtils.isType(exprTypeNode, "Set") || + this.tsUtils.isDerivedFrom(exprType, CheckType.Set) + const isMapLike = this.tsUtils.isType(exprTypeNode, "Map") || + this.tsUtils.isDerivedFrom(exprType, CheckType.Map) + if (!isArrayLike && !isStringLike && !isSetLike && !isMapLike) { this.incrementCounters(node, FaultID.ForOfNonArray); } } @@ -1645,9 +1651,9 @@ export class TypeScriptLinter { ); const checkClassOrInterface = tsElemAccessBaseExprType.isClassOrInterface() && !this.tsUtils.isGenericArrayType(tsElemAccessBaseExprType) && - !this.tsUtils.isDerivedFromArray(tsElemAccessBaseExprType); + !this.tsUtils.isDerivedFrom(tsElemAccessBaseExprType, CheckType.Array); const checkThisOrSuper = this.tsUtils.isThisOrSuperExpr(tsElementAccessExpr.expression) && - !this.tsUtils.isDerivedFromArray(tsElemAccessBaseExprType); + !this.tsUtils.isDerivedFrom(tsElemAccessBaseExprType, CheckType.Array); // if (this.tsUtils.isEnumType(tsElemAccessBaseExprType)) { // implement argument expression type check @@ -2051,7 +2057,7 @@ export class TypeScriptLinter { if ( ts.isArrayTypeNode(spreadExprTypeNode) || this.tsUtils.isTypedArray(spreadExprTypeNode) || - this.tsUtils.isDerivedFromArray(spreadExprType) + this.tsUtils.isDerivedFrom(spreadExprType, CheckType.Array) ) { return; } diff --git a/linter-4.2/src/Utils.ts b/linter-4.2/src/Utils.ts index 489a63400..6ac4c6fe6 100644 --- a/linter-4.2/src/Utils.ts +++ b/linter-4.2/src/Utils.ts @@ -90,6 +90,13 @@ export function pathContainsDirectory(targetPath: string, dir: string): boolean return false; } +export enum CheckType { + Array, + String = "String", + Set = "Set", + Map = "Map", +}; + export class TsUtils { static ES_OBJECT = 'ESObject' @@ -204,6 +211,13 @@ export class TsUtils { return TsUtils.TYPED_ARRAYS.includes(this.entityNameToString(tsType.typeName)); } + public isType(tsType: ts.TypeNode | undefined, checkType: string): boolean { + if (tsType === undefined || !ts.isTypeReferenceNode(tsType)) { + return false; + } + return this.entityNameToString(tsType.typeName) == checkType; + } + public entityNameToString(name: ts.EntityName): string { if (ts.isIdentifier(name)) { return name.escapedText.toString(); @@ -410,24 +424,26 @@ export class TsUtils { } // does something similar to relatedByInheritanceOrIdentical function - public isDerivedFromArray(tsType: ts.Type): tsType is ts.TypeReference { + public isDerivedFrom(tsType: ts.Type, checkType: CheckType): tsType is ts.TypeReference { if (this.isTypeReference(tsType) && tsType.target !== tsType) tsType = tsType.target; + + const tsTypeNode = this.tsTypeChecker.typeToTypeNode(tsType, undefined, ts.NodeBuilderFlags.None); + if (checkType == CheckType.Array && (this.isGenericArrayType(tsType) || this.isTypedArray(tsTypeNode))) + return true; + if (checkType != CheckType.Array && this.isType(tsTypeNode, checkType.toString())) + return true; if (!tsType.symbol || !tsType.symbol.declarations) return false; for (let tsTypeDecl of tsType.symbol.declarations) { if ( (!ts.isClassDeclaration(tsTypeDecl) && !ts.isInterfaceDeclaration(tsTypeDecl)) || !tsTypeDecl.heritageClauses - ) break; + ) continue; for (let heritageClause of tsTypeDecl.heritageClauses) { - for (let baseTypeExpr of heritageClause.types) { - let baseType = this.tsTypeChecker.getTypeAtLocation(baseTypeExpr); - if (this.isTypeReference(baseType) && baseType.target !== baseType) baseType = baseType.target; - const baseTypeNode = this.tsTypeChecker.typeToTypeNode(baseType, undefined, ts.NodeBuilderFlags.None); - return this.isGenericArrayType(baseType) || this.isTypedArray(baseTypeNode); - } + if (this.processParentTypesCheck(heritageClause.types, checkType)) return true; } } + return false; } @@ -697,6 +713,15 @@ export class TsUtils { return false; } + private processParentTypesCheck(parentTypes: ts.NodeArray, checkType: CheckType): boolean { + for (let baseTypeExpr of parentTypes) { + let baseType = this.tsTypeChecker.getTypeAtLocation(baseTypeExpr); + if (this.isTypeReference(baseType) && baseType.target !== baseType) baseType = baseType.target; + if (baseType && this.isDerivedFrom(baseType, checkType)) return true; + } + return false; + } + public isObjectType(tsType: ts.Type): boolean { return ( tsType && tsType.isClassOrInterface() && tsType.symbol && diff --git a/linter-4.2/test/for_stmts.ts b/linter-4.2/test/for_stmts.ts index 908925e45..c51b1971e 100644 --- a/linter-4.2/test/for_stmts.ts +++ b/linter-4.2/test/for_stmts.ts @@ -248,4 +248,39 @@ for (const i of a29) { } for (const i of a30) { console.log(i); -} \ No newline at end of file +} + +class A extends Map {} +class B extends Set {} +class C extends String {} + +let map = new Map([["a", 1], ["b", 2], ["c", 3]]); +let derivedFromMap = new A([["d", 4], ["e", 5], ["f", 6]]); +let set = new Set([1, 2, 3]); +let derivedFromSet = new B([4, 5, 6]); +let str = "hello"; +let derivedFromString = new C("world") + +for (let i of map) { + console.log(i); +} + +for (let i of derivedFromMap) { + console.log(i); +} + +for (let i of set) { + console.log(i); +} + +for (let i of derivedFromSet) { + console.log(i); +} + +for (let i of str) { + console.log(i); +} + +for (let i of derivedFromString) { + console.log(i); +} diff --git a/linter-4.2/test/indexed_derived_from_array.ts b/linter-4.2/test/indexed_derived_from_array.ts index 2af5337bc..72d428ef0 100644 --- a/linter-4.2/test/indexed_derived_from_array.ts +++ b/linter-4.2/test/indexed_derived_from_array.ts @@ -30,6 +30,13 @@ let arr3 = new NumberTypedArray([7, 8, 9]) console.log(arr3[0]) console.log(arr3[nextId]) +class A extends Uint32Array {} +class B extends A {} +class C extends B {} +let arr4 = new C([10, 11, 12]) +console.log(arr4[0]) +console.log(arr4[nextId]) + class Point extends Array { constructor(x: T, y: T) { super(x, y) diff --git a/linter-4.2/test_rules/rule82.ts.autofix.json b/linter-4.2/test_rules/rule82.ts.autofix.json index aa47fe476..13f13363f 100644 --- a/linter-4.2/test_rules/rule82.ts.autofix.json +++ b/linter-4.2/test_rules/rule82.ts.autofix.json @@ -1,12 +1,3 @@ { - "nodes": [ - { - "line": 2, - "column": 1, - "problem": "ForOfNonArray", - "autofixable": false, - "suggest": "", - "rule": "\"for-of\" is supported only for arrays and strings (arkts-for-of-str-arr)" - } - ] + "nodes": [] } \ No newline at end of file diff --git a/linter-4.2/test_rules/rule82.ts.strict.json b/linter-4.2/test_rules/rule82.ts.strict.json index a01df14e1..13f13363f 100644 --- a/linter-4.2/test_rules/rule82.ts.strict.json +++ b/linter-4.2/test_rules/rule82.ts.strict.json @@ -1,11 +1,3 @@ { - "nodes": [ - { - "line": 2, - "column": 1, - "problem": "ForOfNonArray", - "suggest": "", - "rule": "\"for-of\" is supported only for arrays and strings (arkts-for-of-str-arr)" - } - ] + "nodes": [] } \ No newline at end of file diff --git a/linter-4.2/test_rules/rule94.ts.autofix.json b/linter-4.2/test_rules/rule94.ts.autofix.json index 76137f30e..63f189fcd 100644 --- a/linter-4.2/test_rules/rule94.ts.autofix.json +++ b/linter-4.2/test_rules/rule94.ts.autofix.json @@ -22,7 +22,7 @@ "problem": "ForOfNonArray", "autofixable": false, "suggest": "", - "rule": "\"for-of\" is supported only for arrays and strings (arkts-for-of-str-arr)" + "rule": "\"for-of\" is supported only for arrays, strings, sets, maps and classes derived from them (arkts-for-of-str-arr)" } ] } \ No newline at end of file diff --git a/linter-4.2/test_rules/rule94.ts.strict.json b/linter-4.2/test_rules/rule94.ts.strict.json index 731c59823..ded6c64f1 100644 --- a/linter-4.2/test_rules/rule94.ts.strict.json +++ b/linter-4.2/test_rules/rule94.ts.strict.json @@ -19,7 +19,7 @@ "column": 1, "problem": "ForOfNonArray", "suggest": "", - "rule": "\"for-of\" is supported only for arrays and strings (arkts-for-of-str-arr)" + "rule": "\"for-of\" is supported only for arrays, strings, sets, maps and classes derived from them (arkts-for-of-str-arr)" } ] } \ No newline at end of file diff --git a/linter/cookbook_convertor/res/recipes.rst b/linter/cookbook_convertor/res/recipes.rst index 22bc2c6a6..129715972 100644 --- a/linter/cookbook_convertor/res/recipes.rst +++ b/linter/cookbook_convertor/res/recipes.rst @@ -2688,35 +2688,39 @@ iterate over data. .. _R082: -|CB_R| #82: ``for-of`` is supported only for arrays ------------------------------------------------------- +|CB_R| ``for-of`` is supported only for arrays, strings, sets, maps and classes derived from them +------------------------------------------------------------------------------------------------- |CB_RULE| ~~~~~~~~~ -|LANG| supports the iteration over arrays by the ``for .. of`` loop, -but does not support the iteration of objects content. +|LANG| supports the iteration over arrays, strings, sets, maps and classes +derived from them by the ``for .. of`` loop, but does not support the +iteration of objects content. All typed arrays from the standard +library (for example, ``Int32Array``) are also supported. |CB_BAD| ~~~~~~~~ -.. code:: typescript - - let a: string[] = ["a", "b", "c"] - for (let s of a) { - console.log(s) +.. code-block:: typescript + class A { + prop1: number; + prop2: number; + } + let a = new A() + for (let prop of a) { + console.log(prop) } |CB_OK| ~~~~~~~ -.. code:: typescript +.. code-block:: typescript - let a: string[] = ["a", "b", "c"] - for (let s of a) { - console.log(s) + let a = new Set([1, 2, 3]) + for (let n of a) { + console.log(n) } - |CB_SEE| ~~~~~~~~ diff --git a/linter/docs/rules/recipe80.md b/linter/docs/rules/recipe80.md index ea2a978fd..48e562fba 100644 --- a/linter/docs/rules/recipe80.md +++ b/linter/docs/rules/recipe80.md @@ -36,6 +36,6 @@ cannot change at runtime. For arrays, iterate with the regular ``for`` loop. ## See also -- Recipe 082: ``for-of`` is supported only for arrays and strings (``arkts-for-of-str-arr``) +- Recipe 082: ``for-of`` is supported only for arrays, strings, sets, maps and classes derived from them (``arkts-for-of-str-arr``) diff --git a/linter/docs/rules/recipe82.md b/linter/docs/rules/recipe82.md index 2f5c55af1..43fe9f521 100644 --- a/linter/docs/rules/recipe82.md +++ b/linter/docs/rules/recipe82.md @@ -1,22 +1,26 @@ -# ``for-of`` is supported only for arrays and strings +# ``for-of`` is supported only for arrays, strings, sets, maps and classes derived from them Rule ``arkts-for-of-str-arr`` **Severity: error** -ArkTS supports the iteration over arrays and strings by the ``for .. of`` loop, -but does not support the iteration of objects content. All typed arrays from -the standard library (for example, ``Int32Array``) are also supported. - +|LANG| supports the iteration over arrays, strings, sets, maps and classes +derived from them by the ``for .. of`` loop, but does not support the +iteration of objects content. All typed arrays from the standard +library (for example, ``Int32Array``) are also supported. ## TypeScript ``` - let a: Set = new Set([1, 2, 3]) - for (let s of a) { - console.log(s) + class A { + prop1: number; + prop2: number; + } + let a = new A() + for (let prop of a) { + console.log(prop) } ``` @@ -26,9 +30,8 @@ the standard library (for example, ``Int32Array``) are also supported. ``` - let a: Set = new Set([1, 2, 3]) - let numbers = Array.from(a.values()) - for (let n of numbers) { + let a = new Set([1, 2, 3]) + for (let n of a) { console.log(n) } diff --git a/linter/src/CookBookMsg.ts b/linter/src/CookBookMsg.ts index cf7f65952..684402979 100644 --- a/linter/src/CookBookMsg.ts +++ b/linter/src/CookBookMsg.ts @@ -101,7 +101,7 @@ cookBookTag[78] = ''; cookBookTag[79] = 'Type annotation in catch clause is not supported (arkts-no-types-in-catch)'; cookBookTag[80] = '"for .. in" is not supported (arkts-no-for-in)'; cookBookTag[81] = ''; -cookBookTag[82] = '"for-of" is supported only for arrays and strings (arkts-for-of-str-arr)'; +cookBookTag[82] = '"for-of" is supported only for arrays, strings, sets, maps and classes derived from them (arkts-for-of-str-arr)'; cookBookTag[83] = 'Mapped type expression is not supported (arkts-no-mapped-types)'; cookBookTag[84] = '"with" statement is not supported (arkts-no-with)'; cookBookTag[85] = ''; diff --git a/linter/src/TypeScriptLinter.ts b/linter/src/TypeScriptLinter.ts index f37ba9bce..c27428471 100644 --- a/linter/src/TypeScriptLinter.ts +++ b/linter/src/TypeScriptLinter.ts @@ -16,7 +16,7 @@ import * as ts from 'typescript'; import * as path from 'node:path'; import { getNodeOrLineEnd } from './utils/functions/GetNodeOrLineEnd'; -import { TsUtils } from './utils/TsUtils'; +import { TsUtils, CheckType } from './utils/TsUtils'; import { FaultID } from './Problems'; import { faultsAttrs } from './FaultAttrs'; import { faultDesc } from './FaultDesc'; @@ -599,9 +599,14 @@ export class TypeScriptLinter { ts.isArrayLiteralExpression(expr) || (exprTypeNode && ts.isArrayTypeNode(exprTypeNode)) || this.tsUtils.isTypedArray(exprTypeNode) || - this.tsUtils.isDerivedFromArray(exprType); - const isStringLike = exprType.isStringLiteral() || this.tsUtils.isStringType(exprType); - if (!isArrayLike && !isStringLike) { + this.tsUtils.isDerivedFrom(exprType, CheckType.Array); + const isStringLike = exprType.isStringLiteral() || this.tsUtils.isStringType(exprType) || + this.tsUtils.isDerivedFrom(exprType, CheckType.String) + const isSetLike = this.tsUtils.isType(exprTypeNode, "Set") || + this.tsUtils.isDerivedFrom(exprType, CheckType.Set) + const isMapLike = this.tsUtils.isType(exprTypeNode, "Map") || + this.tsUtils.isDerivedFrom(exprType, CheckType.Map) + if (!isArrayLike && !isStringLike && !isSetLike && !isMapLike) { this.incrementCounters(node, FaultID.ForOfNonArray); } } @@ -1324,9 +1329,9 @@ export class TypeScriptLinter { const checkClassOrInterface = tsElemAccessBaseExprType.isClassOrInterface() && !this.tsUtils.isGenericArrayType(tsElemAccessBaseExprType) && - !this.tsUtils.isDerivedFromArray(tsElemAccessBaseExprType); + !this.tsUtils.isDerivedFrom(tsElemAccessBaseExprType, CheckType.Array); const checkThisOrSuper = this.tsUtils.isThisOrSuperExpr(tsElementAccessExpr.expression) && - !this.tsUtils.isDerivedFromArray(tsElemAccessBaseExprType); + !this.tsUtils.isDerivedFrom(tsElemAccessBaseExprType, CheckType.Array); // implement check for enum argument expression @@ -1656,7 +1661,7 @@ export class TypeScriptLinter { const spreadExprTypeNode = this.tsTypeChecker.typeToTypeNode(spreadExprType, undefined, ts.NodeBuilderFlags.None); if (spreadExprTypeNode !== undefined && (ts.isCallLikeExpression(node.parent) || ts.isArrayLiteralExpression(node.parent))) { if (ts.isArrayTypeNode(spreadExprTypeNode) || this.tsUtils.isTypedArray(spreadExprTypeNode) || - this.tsUtils.isDerivedFromArray(spreadExprType)) { + this.tsUtils.isDerivedFrom(spreadExprType, CheckType.Array)) { return } } diff --git a/linter/src/utils/TsUtils.ts b/linter/src/utils/TsUtils.ts index a68c46636..0f964310c 100644 --- a/linter/src/utils/TsUtils.ts +++ b/linter/src/utils/TsUtils.ts @@ -24,6 +24,12 @@ import { isStdLibraryType } from './functions/IsStdLibrary'; import { isStructDeclaration } from './functions/IsStruct'; import { pathContainsDirectory } from './functions/PathHelper'; +export enum CheckType { + Array, + String = "String", + Set = "Set", + Map = "Map", +}; export class TsUtils { constructor(private tsTypeChecker: ts.TypeChecker, private testMode: boolean) { } @@ -39,6 +45,13 @@ export class TsUtils { return TYPED_ARRAYS.includes(this.entityNameToString(tsType.typeName)); } + public isType(tsType: ts.TypeNode | undefined, checkType: string): boolean { + if (tsType === undefined || !ts.isTypeReferenceNode(tsType)) { + return false; + } + return this.entityNameToString(tsType.typeName) == checkType; + } + public entityNameToString(name: ts.EntityName): string { if (ts.isIdentifier(name)) { return name.escapedText.toString(); @@ -245,24 +258,26 @@ export class TsUtils { } // does something similar to relatedByInheritanceOrIdentical function - public isDerivedFromArray(tsType: ts.Type): tsType is ts.TypeReference { + public isDerivedFrom(tsType: ts.Type, checkType: CheckType): tsType is ts.TypeReference { if (this.isTypeReference(tsType) && tsType.target !== tsType) tsType = tsType.target; + + const tsTypeNode = this.tsTypeChecker.typeToTypeNode(tsType, undefined, ts.NodeBuilderFlags.None); + if (checkType == CheckType.Array && (this.isGenericArrayType(tsType) || this.isTypedArray(tsTypeNode))) + return true; + if (checkType != CheckType.Array && this.isType(tsTypeNode, checkType.toString())) + return true; if (!tsType.symbol || !tsType.symbol.declarations) return false; for (let tsTypeDecl of tsType.symbol.declarations) { if ( (!ts.isClassDeclaration(tsTypeDecl) && !ts.isInterfaceDeclaration(tsTypeDecl)) || !tsTypeDecl.heritageClauses - ) break; + ) continue; for (let heritageClause of tsTypeDecl.heritageClauses) { - for (let baseTypeExpr of heritageClause.types) { - let baseType = this.tsTypeChecker.getTypeAtLocation(baseTypeExpr); - if (this.isTypeReference(baseType) && baseType.target !== baseType) baseType = baseType.target; - const baseTypeNode = this.tsTypeChecker.typeToTypeNode(baseType, undefined, ts.NodeBuilderFlags.None); - return this.isGenericArrayType(baseType) || this.isTypedArray(baseTypeNode); - } + if (this.processParentTypesCheck(heritageClause.types, checkType)) return true; } } + return false; } @@ -521,6 +536,15 @@ export class TsUtils { return false; } + private processParentTypesCheck(parentTypes: ts.NodeArray, checkType: CheckType): boolean { + for (let baseTypeExpr of parentTypes) { + let baseType = this.tsTypeChecker.getTypeAtLocation(baseTypeExpr); + if (this.isTypeReference(baseType) && baseType.target !== baseType) baseType = baseType.target; + if (baseType && this.isDerivedFrom(baseType, checkType)) return true; + } + return false; + } + public isObjectType(tsType: ts.Type): boolean { return ( tsType && tsType.isClassOrInterface() && tsType.symbol && diff --git a/linter/test/for_stmts.ts b/linter/test/for_stmts.ts index 908925e45..c51b1971e 100644 --- a/linter/test/for_stmts.ts +++ b/linter/test/for_stmts.ts @@ -248,4 +248,39 @@ for (const i of a29) { } for (const i of a30) { console.log(i); -} \ No newline at end of file +} + +class A extends Map {} +class B extends Set {} +class C extends String {} + +let map = new Map([["a", 1], ["b", 2], ["c", 3]]); +let derivedFromMap = new A([["d", 4], ["e", 5], ["f", 6]]); +let set = new Set([1, 2, 3]); +let derivedFromSet = new B([4, 5, 6]); +let str = "hello"; +let derivedFromString = new C("world") + +for (let i of map) { + console.log(i); +} + +for (let i of derivedFromMap) { + console.log(i); +} + +for (let i of set) { + console.log(i); +} + +for (let i of derivedFromSet) { + console.log(i); +} + +for (let i of str) { + console.log(i); +} + +for (let i of derivedFromString) { + console.log(i); +} diff --git a/linter/test/indexed_derived_from_array.ts b/linter/test/indexed_derived_from_array.ts index 2af5337bc..72d428ef0 100644 --- a/linter/test/indexed_derived_from_array.ts +++ b/linter/test/indexed_derived_from_array.ts @@ -30,6 +30,13 @@ let arr3 = new NumberTypedArray([7, 8, 9]) console.log(arr3[0]) console.log(arr3[nextId]) +class A extends Uint32Array {} +class B extends A {} +class C extends B {} +let arr4 = new C([10, 11, 12]) +console.log(arr4[0]) +console.log(arr4[nextId]) + class Point extends Array { constructor(x: T, y: T) { super(x, y) diff --git a/linter/test_rules/rule82.ts.autofix.json b/linter/test_rules/rule82.ts.autofix.json index aa47fe476..13f13363f 100644 --- a/linter/test_rules/rule82.ts.autofix.json +++ b/linter/test_rules/rule82.ts.autofix.json @@ -1,12 +1,3 @@ { - "nodes": [ - { - "line": 2, - "column": 1, - "problem": "ForOfNonArray", - "autofixable": false, - "suggest": "", - "rule": "\"for-of\" is supported only for arrays and strings (arkts-for-of-str-arr)" - } - ] + "nodes": [] } \ No newline at end of file diff --git a/linter/test_rules/rule82.ts.strict.json b/linter/test_rules/rule82.ts.strict.json index a01df14e1..13f13363f 100644 --- a/linter/test_rules/rule82.ts.strict.json +++ b/linter/test_rules/rule82.ts.strict.json @@ -1,11 +1,3 @@ { - "nodes": [ - { - "line": 2, - "column": 1, - "problem": "ForOfNonArray", - "suggest": "", - "rule": "\"for-of\" is supported only for arrays and strings (arkts-for-of-str-arr)" - } - ] + "nodes": [] } \ No newline at end of file diff --git a/linter/test_rules/rule94.ts.autofix.json b/linter/test_rules/rule94.ts.autofix.json index 76137f30e..63f189fcd 100644 --- a/linter/test_rules/rule94.ts.autofix.json +++ b/linter/test_rules/rule94.ts.autofix.json @@ -22,7 +22,7 @@ "problem": "ForOfNonArray", "autofixable": false, "suggest": "", - "rule": "\"for-of\" is supported only for arrays and strings (arkts-for-of-str-arr)" + "rule": "\"for-of\" is supported only for arrays, strings, sets, maps and classes derived from them (arkts-for-of-str-arr)" } ] } \ No newline at end of file diff --git a/linter/test_rules/rule94.ts.strict.json b/linter/test_rules/rule94.ts.strict.json index 731c59823..ded6c64f1 100644 --- a/linter/test_rules/rule94.ts.strict.json +++ b/linter/test_rules/rule94.ts.strict.json @@ -19,7 +19,7 @@ "column": 1, "problem": "ForOfNonArray", "suggest": "", - "rule": "\"for-of\" is supported only for arrays and strings (arkts-for-of-str-arr)" + "rule": "\"for-of\" is supported only for arrays, strings, sets, maps and classes derived from them (arkts-for-of-str-arr)" } ] } \ No newline at end of file -- Gitee