From 5799b5af3c42f580cb778509b0e670ee04bfea37 Mon Sep 17 00:00:00 2001 From: Vsevolod Pukhov Date: Wed, 13 Sep 2023 18:01:42 +0300 Subject: [PATCH] [ArkTS Linter] Permit recursive type aliases Signed-off-by: Vsevolod Pukhov --- linter-4.2/src/TypeScriptLinter.ts | 36 +++++++------- linter-4.2/src/Utils.ts | 1 + linter-4.2/test/recursive_types.ts | 48 +++++++++++++++---- linter-4.2/test/recursive_types.ts.relax.json | 31 ++---------- .../test/recursive_types.ts.strict.json | 31 ++---------- linter/src/TypeScriptLinter.ts | 26 +++++----- linter/src/utils/TsUtils.ts | 1 + linter/test/recursive_types.ts | 48 +++++++++++++++---- linter/test/recursive_types.ts.relax.json | 31 ++---------- linter/test/recursive_types.ts.strict.json | 31 ++---------- 10 files changed, 124 insertions(+), 160 deletions(-) diff --git a/linter-4.2/src/TypeScriptLinter.ts b/linter-4.2/src/TypeScriptLinter.ts index d2852f46a..8fbf03927 100644 --- a/linter-4.2/src/TypeScriptLinter.ts +++ b/linter-4.2/src/TypeScriptLinter.ts @@ -1391,15 +1391,11 @@ export class TypeScriptLinter { this.countClassMembersWithDuplicateName(tsClassDecl); - let tsClassDeclType = this.tsTypeChecker.getTypeAtLocation(tsClassDecl); - const visitHClause = (hClause: ts.HeritageClause) => { for (const tsTypeExpr of hClause.types) { const tsExprType = this.tsTypeChecker.getTypeAtLocation(tsTypeExpr.expression); if (tsExprType.isClass() && hClause.token == ts.SyntaxKind.ImplementsKeyword) { this.incrementCounters(tsTypeExpr, FaultID.ImplementsClass); - } else if (this.tsUtils.typeIsRecursive(tsClassDeclType, this.tsTypeChecker.getTypeAtLocation(tsTypeExpr))) { - this.incrementCounters(tsTypeExpr, FaultID.ClassAsObject); } } }; @@ -1472,9 +1468,6 @@ export class TypeScriptLinter { this.tsTypeChecker.getSymbolAtLocation(tsTypeAlias.name), tsTypeAlias ); - if (this.tsUtils.typeIsRecursive(this.tsTypeChecker.getTypeAtLocation(node))) { - this.incrementCounters(tsTypeAlias, FaultID.ClassAsObject); - } } private handleImportClause(node: ts.Node) { @@ -2262,6 +2255,8 @@ export class TypeScriptLinter { } } + private validatedTypesSet = new Set(); + private validateDeclInferredType( type: ts.Type, decl: @@ -2272,21 +2267,22 @@ export class TypeScriptLinter { if (type.aliasSymbol != undefined) { return; } - if (type.isUnion()) { - for (let unionElem of type.types) - this.validateDeclInferredType(unionElem, decl); - } - - if ( - type.flags & ts.TypeFlags.Object && - (type as ts.ObjectType).objectFlags & ts.ObjectFlags.Reference - ) { - const typeArgs = this.tsTypeChecker.getTypeArguments( - type as ts.TypeReference - ); + if (type.flags & ts.TypeFlags.Object && (type as ts.ObjectType).objectFlags & ts.ObjectFlags.Reference) { + const typeArgs = this.tsTypeChecker.getTypeArguments(type as ts.TypeReference); if (typeArgs) { - for (const typeArg of typeArgs) + for (const typeArg of typeArgs) { this.validateDeclInferredType(typeArg, decl); + } + } + return; + } + if (this.validatedTypesSet.has(type)) { + return; + } + if (type.isUnion()) { + this.validatedTypesSet.add(type); + for (let unionElem of type.types) { + this.validateDeclInferredType(unionElem, decl) } } diff --git a/linter-4.2/src/Utils.ts b/linter-4.2/src/Utils.ts index 1fc3ed1ab..d688bada1 100644 --- a/linter-4.2/src/Utils.ts +++ b/linter-4.2/src/Utils.ts @@ -1325,6 +1325,7 @@ export class TsUtils { return undefined; } + // has to be re-implemented with local loop detection public typeIsRecursive(topType: ts.Type, type: ts.Type | undefined = undefined): boolean { if (type == undefined) { type = topType; diff --git a/linter-4.2/test/recursive_types.ts b/linter-4.2/test/recursive_types.ts index 40647e45f..52402491b 100644 --- a/linter-4.2/test/recursive_types.ts +++ b/linter-4.2/test/recursive_types.ts @@ -13,24 +13,27 @@ * limitations under the License. */ -class ID { } +class I { } -type A = ID; +type A = I; (fn: () => A) => { let x = fn() } -(fn: () => ID | A> | null> | ID) => { let x = fn() } +(fn: () => I | A> | null> | I) => { let x = fn() } -type B = ID>>; +type B = I>>; (fn: () => B) => { let x = fn() } -type C = ID>>; +type C = I>>; (fn: () => C) => { let x = fn() } -class X1 extends ID { }; +class X1 extends I { }; (fn: () => X1) => { let x = fn() } -class X2 extends ID | null> { }; +class X2 extends I | null> { }; (fn: () => X2) => { let x = fn() } +type D = string | I; +(fn: () => D) => { let x = fn() } + namespace test1 { type A = string | Array | Map>; @@ -49,4 +52,33 @@ namespace test2 { return "" } let b = f(); -} \ No newline at end of file +} + +namespace test3 { + export type NVal = string | Array | Map>; + + class NParser { + private upd(name: string, value: NVal, parent: Map) { + if (parent.has(name)) { + let cur = parent.get(name); + if (cur instanceof Array) { + (cur as Array).push(value); + } else if (cur != undefined) { + let array = [cur, value]; + parent.set(name, array); + } + } else { + parent.set(name, value); + } + } + } +} + +type E = [E]; +(fn: () => E) => { let x = fn() } + +type F1 = () => F1; +(fn: () => F1) => { let x = fn() } + +type F2 = (a: F2 | number | F2[]) => F2 | Array | string>; +(fn: () => F2) => { let x = fn() } diff --git a/linter-4.2/test/recursive_types.ts.relax.json b/linter-4.2/test/recursive_types.ts.relax.json index 6def1084c..9f63e9379 100644 --- a/linter-4.2/test/recursive_types.ts.relax.json +++ b/linter-4.2/test/recursive_types.ts.relax.json @@ -15,34 +15,9 @@ ], "nodes": [ { - "line": 18, - "column": 1, - "problem": "ClassAsObject" - }, - { - "line": 25, - "column": 1, - "problem": "ClassAsObject" - }, - { - "line": 28, - "column": 18, - "problem": "ClassAsObject" - }, - { - "line": 31, - "column": 18, - "problem": "ClassAsObject" - }, - { - "line": 35, - "column": 5, - "problem": "ClassAsObject" - }, - { - "line": 46, - "column": 5, - "problem": "ClassAsObject" + "line": 77, + "column": 10, + "problem": "TupleType" } ] } \ No newline at end of file diff --git a/linter-4.2/test/recursive_types.ts.strict.json b/linter-4.2/test/recursive_types.ts.strict.json index 6def1084c..9f63e9379 100644 --- a/linter-4.2/test/recursive_types.ts.strict.json +++ b/linter-4.2/test/recursive_types.ts.strict.json @@ -15,34 +15,9 @@ ], "nodes": [ { - "line": 18, - "column": 1, - "problem": "ClassAsObject" - }, - { - "line": 25, - "column": 1, - "problem": "ClassAsObject" - }, - { - "line": 28, - "column": 18, - "problem": "ClassAsObject" - }, - { - "line": 31, - "column": 18, - "problem": "ClassAsObject" - }, - { - "line": 35, - "column": 5, - "problem": "ClassAsObject" - }, - { - "line": 46, - "column": 5, - "problem": "ClassAsObject" + "line": 77, + "column": 10, + "problem": "TupleType" } ] } \ No newline at end of file diff --git a/linter/src/TypeScriptLinter.ts b/linter/src/TypeScriptLinter.ts index dc31177e0..41c37ca5d 100644 --- a/linter/src/TypeScriptLinter.ts +++ b/linter/src/TypeScriptLinter.ts @@ -1102,15 +1102,11 @@ export class TypeScriptLinter { } this.countClassMembersWithDuplicateName(tsClassDecl); - let tsClassDeclType = this.tsTypeChecker.getTypeAtLocation(tsClassDecl); - const visitHClause = (hClause: ts.HeritageClause) => { for (const tsTypeExpr of hClause.types) { const tsExprType = this.tsTypeChecker.getTypeAtLocation(tsTypeExpr.expression); if (tsExprType.isClass() && hClause.token == ts.SyntaxKind.ImplementsKeyword) { this.incrementCounters(tsTypeExpr, FaultID.ImplementsClass); - } else if (this.tsUtils.typeIsRecursive(tsClassDeclType, this.tsTypeChecker.getTypeAtLocation(tsTypeExpr))) { - this.incrementCounters(tsTypeExpr, FaultID.ClassAsObject); } } }; @@ -1176,9 +1172,6 @@ export class TypeScriptLinter { this.countDeclarationsWithDuplicateName( this.tsTypeChecker.getSymbolAtLocation(tsTypeAlias.name), tsTypeAlias ); - if (this.tsUtils.typeIsRecursive(this.tsTypeChecker.getTypeAtLocation(node))) { - this.incrementCounters(tsTypeAlias, FaultID.ClassAsObject); - } } private handleImportClause(node: ts.Node) { @@ -1803,6 +1796,8 @@ export class TypeScriptLinter { } } + private validatedTypesSet = new Set(); + private validateDeclInferredType( type: ts.Type, decl: ts.VariableDeclaration | ts.PropertyDeclaration | ts.ParameterDeclaration @@ -1810,15 +1805,22 @@ export class TypeScriptLinter { if (type.aliasSymbol != undefined) { return; } - if (type.isUnion()) { - for (let unionElem of type.types) - this.validateDeclInferredType(unionElem, decl) - } if (type.flags & ts.TypeFlags.Object && (type as ts.ObjectType).objectFlags & ts.ObjectFlags.Reference) { const typeArgs = this.tsTypeChecker.getTypeArguments(type as ts.TypeReference); if (typeArgs) { - for (const typeArg of typeArgs) + for (const typeArg of typeArgs) { this.validateDeclInferredType(typeArg, decl); + } + } + return; + } + if (this.validatedTypesSet.has(type)) { + return; + } + if (type.isUnion()) { + this.validatedTypesSet.add(type); + for (let unionElem of type.types) { + this.validateDeclInferredType(unionElem, decl) } } if (this.tsUtils.isAnyType(type)) { diff --git a/linter/src/utils/TsUtils.ts b/linter/src/utils/TsUtils.ts index 6aa0e7814..48589f0c6 100644 --- a/linter/src/utils/TsUtils.ts +++ b/linter/src/utils/TsUtils.ts @@ -1094,6 +1094,7 @@ export class TsUtils { return undefined; } + // has to be re-implemented with local loop detection public typeIsRecursive(topType: ts.Type, type: ts.Type | undefined = undefined): boolean { if (type == undefined) { type = topType; diff --git a/linter/test/recursive_types.ts b/linter/test/recursive_types.ts index 40647e45f..52402491b 100644 --- a/linter/test/recursive_types.ts +++ b/linter/test/recursive_types.ts @@ -13,24 +13,27 @@ * limitations under the License. */ -class ID { } +class I { } -type A = ID; +type A = I; (fn: () => A) => { let x = fn() } -(fn: () => ID | A> | null> | ID) => { let x = fn() } +(fn: () => I | A> | null> | I) => { let x = fn() } -type B = ID>>; +type B = I>>; (fn: () => B) => { let x = fn() } -type C = ID>>; +type C = I>>; (fn: () => C) => { let x = fn() } -class X1 extends ID { }; +class X1 extends I { }; (fn: () => X1) => { let x = fn() } -class X2 extends ID | null> { }; +class X2 extends I | null> { }; (fn: () => X2) => { let x = fn() } +type D = string | I; +(fn: () => D) => { let x = fn() } + namespace test1 { type A = string | Array | Map>; @@ -49,4 +52,33 @@ namespace test2 { return "" } let b = f(); -} \ No newline at end of file +} + +namespace test3 { + export type NVal = string | Array | Map>; + + class NParser { + private upd(name: string, value: NVal, parent: Map) { + if (parent.has(name)) { + let cur = parent.get(name); + if (cur instanceof Array) { + (cur as Array).push(value); + } else if (cur != undefined) { + let array = [cur, value]; + parent.set(name, array); + } + } else { + parent.set(name, value); + } + } + } +} + +type E = [E]; +(fn: () => E) => { let x = fn() } + +type F1 = () => F1; +(fn: () => F1) => { let x = fn() } + +type F2 = (a: F2 | number | F2[]) => F2 | Array | string>; +(fn: () => F2) => { let x = fn() } diff --git a/linter/test/recursive_types.ts.relax.json b/linter/test/recursive_types.ts.relax.json index 6def1084c..9f63e9379 100644 --- a/linter/test/recursive_types.ts.relax.json +++ b/linter/test/recursive_types.ts.relax.json @@ -15,34 +15,9 @@ ], "nodes": [ { - "line": 18, - "column": 1, - "problem": "ClassAsObject" - }, - { - "line": 25, - "column": 1, - "problem": "ClassAsObject" - }, - { - "line": 28, - "column": 18, - "problem": "ClassAsObject" - }, - { - "line": 31, - "column": 18, - "problem": "ClassAsObject" - }, - { - "line": 35, - "column": 5, - "problem": "ClassAsObject" - }, - { - "line": 46, - "column": 5, - "problem": "ClassAsObject" + "line": 77, + "column": 10, + "problem": "TupleType" } ] } \ No newline at end of file diff --git a/linter/test/recursive_types.ts.strict.json b/linter/test/recursive_types.ts.strict.json index 6def1084c..9f63e9379 100644 --- a/linter/test/recursive_types.ts.strict.json +++ b/linter/test/recursive_types.ts.strict.json @@ -15,34 +15,9 @@ ], "nodes": [ { - "line": 18, - "column": 1, - "problem": "ClassAsObject" - }, - { - "line": 25, - "column": 1, - "problem": "ClassAsObject" - }, - { - "line": 28, - "column": 18, - "problem": "ClassAsObject" - }, - { - "line": 31, - "column": 18, - "problem": "ClassAsObject" - }, - { - "line": 35, - "column": 5, - "problem": "ClassAsObject" - }, - { - "line": 46, - "column": 5, - "problem": "ClassAsObject" + "line": 77, + "column": 10, + "problem": "TupleType" } ] } \ No newline at end of file -- Gitee