diff --git a/linter-4.2/cookbook_convertor/res/recipes.rst b/linter-4.2/cookbook_convertor/res/recipes.rst index c069dd211eec6b750a4ee98a488bf23e88f7b19d..8f4ec5447ef15172536deb45549a3e7541b5284e 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 ea2a978fd7cde368fc79fecc7e143475ac6da981..48e562fba3b7c87262fb32217820ba6fb31fc4cd 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 2f5c55af1e923e29d8f4e57a9c47b67c3ad23db6..43fe9f521dca156dba1ed682f8ad3509f32a8952 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 cf7f65952b0ea4785e75d61b026e46ed4a47c43f..68440297976e0446aebc4e3d548a6d0f0a896b9c 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 0eb571450fb4f9ca62fb5e4b518b910b9c0102a0..415ef9ba3e36ccf45d110ede4234dd36caa0aadb 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 489a634005f9d3eee4fb2b338962232c8c732ed1..6ac4c6fe6f0131338db1812e6dbc1b26fecfdbaa 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 908925e45396f8b210a2d365d9f0aab63a4fe063..c51b1971e39888dabf4fd2e749748c9585f8093a 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 2af5337bc741c137af00949c0ff2c52df57a72ca..72d428ef063b51b28fb30f16d3542a2fd14be6c8 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 aa47fe476cb1a9f1b0c521dfc3cf00745633647c..13f13363f579325755e8954b4011963971667481 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 a01df14e18fdc3a31a3ac71ec5936087291cb477..13f13363f579325755e8954b4011963971667481 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 76137f30e856f3fb8a972b33ac7f50141317c5b8..63f189fcda4481bf7d4d79604595813e032fa10a 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 731c598239e2e149caf884e25a56b7a8f5912c1f..ded6c64f1d768e0cf9e4db590d598ce8609431c4 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 22bc2c6a6d768e1ca89c8138a318e1d8935421c5..1297159725633a8cd0bcc284db41b325f20f809d 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 ea2a978fd7cde368fc79fecc7e143475ac6da981..48e562fba3b7c87262fb32217820ba6fb31fc4cd 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 2f5c55af1e923e29d8f4e57a9c47b67c3ad23db6..43fe9f521dca156dba1ed682f8ad3509f32a8952 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 cf7f65952b0ea4785e75d61b026e46ed4a47c43f..68440297976e0446aebc4e3d548a6d0f0a896b9c 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 f37ba9bcedbb0443e567b8bbede73bfec4c1bdb1..c274284717e5a7c5ee68d0a0e77980deb0230799 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 a68c466367a68e883a22f0d5c701d04727193866..0f964310cdf882719c1031cb9e2d8f127dfa45bb 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 908925e45396f8b210a2d365d9f0aab63a4fe063..c51b1971e39888dabf4fd2e749748c9585f8093a 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 2af5337bc741c137af00949c0ff2c52df57a72ca..72d428ef063b51b28fb30f16d3542a2fd14be6c8 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 aa47fe476cb1a9f1b0c521dfc3cf00745633647c..13f13363f579325755e8954b4011963971667481 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 a01df14e18fdc3a31a3ac71ec5936087291cb477..13f13363f579325755e8954b4011963971667481 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 76137f30e856f3fb8a972b33ac7f50141317c5b8..63f189fcda4481bf7d4d79604595813e032fa10a 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 731c598239e2e149caf884e25a56b7a8f5912c1f..ded6c64f1d768e0cf9e4db590d598ce8609431c4 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