From 945a6fe93eab6a0e4955de83c1fed8d83c4772d5 Mon Sep 17 00:00:00 2001 From: Vsevolod Pukhov Date: Tue, 12 Sep 2023 21:57:07 +0300 Subject: [PATCH] [ArkTS Linter] Handle recursion in aliases and inheritance Signed-off-by: Vsevolod Pukhov --- linter-4.2/src/TypeScriptLinter.ts | 30 ++++++++--- linter-4.2/src/Utils.ts | 29 +++++++++++ linter-4.2/test/recursive_types.ts | 52 +++++++++++++++++++ .../test/recursive_types.ts.autofix.skip | 0 linter-4.2/test/recursive_types.ts.relax.json | 48 +++++++++++++++++ .../test/recursive_types.ts.strict.json | 48 +++++++++++++++++ linter/src/TypeScriptLinter.ts | 27 ++++++++-- linter/src/utils/TsUtils.ts | 29 +++++++++++ linter/test/recursive_types.ts | 52 +++++++++++++++++++ linter/test/recursive_types.ts.autofix.skip | 0 linter/test/recursive_types.ts.relax.json | 48 +++++++++++++++++ linter/test/recursive_types.ts.strict.json | 48 +++++++++++++++++ 12 files changed, 398 insertions(+), 13 deletions(-) create mode 100644 linter-4.2/test/recursive_types.ts create mode 100644 linter-4.2/test/recursive_types.ts.autofix.skip create mode 100644 linter-4.2/test/recursive_types.ts.relax.json create mode 100644 linter-4.2/test/recursive_types.ts.strict.json create mode 100644 linter/test/recursive_types.ts create mode 100644 linter/test/recursive_types.ts.autofix.skip create mode 100644 linter/test/recursive_types.ts.relax.json create mode 100644 linter/test/recursive_types.ts.strict.json diff --git a/linter-4.2/src/TypeScriptLinter.ts b/linter-4.2/src/TypeScriptLinter.ts index 7a4e4b59d..958754e43 100644 --- a/linter-4.2/src/TypeScriptLinter.ts +++ b/linter-4.2/src/TypeScriptLinter.ts @@ -1405,17 +1405,25 @@ 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); + } + } + }; + if (tsClassDecl.heritageClauses) { for (const hClause of tsClassDecl.heritageClauses) { - if (hClause && hClause.token === ts.SyntaxKind.ImplementsKeyword) { - for (const tsTypeExpr of hClause.types) { - const tsExprType = this.tsTypeChecker.getTypeAtLocation( - tsTypeExpr.expression - ); - if (tsExprType.isClass()) - this.incrementCounters(tsTypeExpr, FaultID.ImplementsClass); - } + if (!hClause) { + continue; } + visitHClause(hClause); } } @@ -1478,6 +1486,9 @@ 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) { @@ -2270,6 +2281,9 @@ export class TypeScriptLinter { | ts.PropertyDeclaration | ts.ParameterDeclaration ): void { + if (type.aliasSymbol != undefined) { + return; + } if (type.isUnion()) { 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 f54da61bf..7dd925729 100644 --- a/linter-4.2/src/Utils.ts +++ b/linter-4.2/src/Utils.ts @@ -1299,4 +1299,33 @@ export class TsUtils { } return undefined; } + + public typeIsRecursive(topType: ts.Type, type: ts.Type | undefined = undefined): boolean { + if (type == undefined) { + type = topType; + } else if (type == topType) { + return true; + } else if (type.aliasSymbol) { + return false; + } + + if (type.isUnion()) { + for (let unionElem of type.types) { + if (this.typeIsRecursive(topType, unionElem)) { + return true; + } + } + } + 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) { + if (this.typeIsRecursive(topType, typeArg)) { + return true; + } + } + } + } + return false; + } } diff --git a/linter-4.2/test/recursive_types.ts b/linter-4.2/test/recursive_types.ts new file mode 100644 index 000000000..40647e45f --- /dev/null +++ b/linter-4.2/test/recursive_types.ts @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2022-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +class ID { } + +type A = ID; +(fn: () => A) => { let x = fn() } +(fn: () => ID | A> | null> | ID) => { let x = fn() } + +type B = ID>>; +(fn: () => B) => { let x = fn() } + +type C = ID>>; +(fn: () => C) => { let x = fn() } + +class X1 extends ID { }; +(fn: () => X1) => { let x = fn() } + +class X2 extends ID | null> { }; +(fn: () => X2) => { let x = fn() } + +namespace test1 { + type A = string | Array | Map>; + + class C { + private f(): A { + let t = this.f(); + return ""; + }; + } +} + +namespace test2 { + type A = string | Array | Map>; + + function f(): A { + return "" + } + let b = f(); +} \ No newline at end of file diff --git a/linter-4.2/test/recursive_types.ts.autofix.skip b/linter-4.2/test/recursive_types.ts.autofix.skip new file mode 100644 index 000000000..e69de29bb diff --git a/linter-4.2/test/recursive_types.ts.relax.json b/linter-4.2/test/recursive_types.ts.relax.json new file mode 100644 index 000000000..6def1084c --- /dev/null +++ b/linter-4.2/test/recursive_types.ts.relax.json @@ -0,0 +1,48 @@ +{ + "copyright": [ + "Copyright (c) 2022-2023 Huawei Device Co., Ltd.", + "Licensed under the Apache License, Version 2.0 (the 'License');", + "you may not use this file except in compliance with the License.", + "You may obtain a copy of the License at", + "", + "http://www.apache.org/licenses/LICENSE-2.0", + "", + "Unless required by applicable law or agreed to in writing, software", + "distributed under the License is distributed on an 'AS IS' BASIS,", + "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.", + "See the License for the specific language governing permissions and", + "limitations under the License." + ], + "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" + } + ] +} \ 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 new file mode 100644 index 000000000..6def1084c --- /dev/null +++ b/linter-4.2/test/recursive_types.ts.strict.json @@ -0,0 +1,48 @@ +{ + "copyright": [ + "Copyright (c) 2022-2023 Huawei Device Co., Ltd.", + "Licensed under the Apache License, Version 2.0 (the 'License');", + "you may not use this file except in compliance with the License.", + "You may obtain a copy of the License at", + "", + "http://www.apache.org/licenses/LICENSE-2.0", + "", + "Unless required by applicable law or agreed to in writing, software", + "distributed under the License is distributed on an 'AS IS' BASIS,", + "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.", + "See the License for the specific language governing permissions and", + "limitations under the License." + ], + "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" + } + ] +} \ No newline at end of file diff --git a/linter/src/TypeScriptLinter.ts b/linter/src/TypeScriptLinter.ts index f219c9e42..af118fc1f 100644 --- a/linter/src/TypeScriptLinter.ts +++ b/linter/src/TypeScriptLinter.ts @@ -1108,15 +1108,26 @@ 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); + } + } + }; + if (tsClassDecl.heritageClauses) { for (const hClause of tsClassDecl.heritageClauses) { - if (!hClause || hClause.token !== ts.SyntaxKind.ImplementsKeyword) { + if (!hClause) { continue; } - for (const tsTypeExpr of hClause.types) { - const tsExprType = this.tsTypeChecker.getTypeAtLocation(tsTypeExpr.expression); - if (tsExprType.isClass()) this.incrementCounters(tsTypeExpr, FaultID.ImplementsClass); - } + visitHClause(hClause); } } this.handleDecorators(ts.getDecorators(tsClassDecl)); @@ -1172,6 +1183,9 @@ 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) { @@ -1798,6 +1812,9 @@ export class TypeScriptLinter { type: ts.Type, decl: ts.VariableDeclaration | ts.PropertyDeclaration | ts.ParameterDeclaration ): void { + if (type.aliasSymbol != undefined) { + return; + } if (type.isUnion()) { for (let unionElem of type.types) this.validateDeclInferredType(unionElem, decl) diff --git a/linter/src/utils/TsUtils.ts b/linter/src/utils/TsUtils.ts index d72d8158d..9f2a3bd80 100644 --- a/linter/src/utils/TsUtils.ts +++ b/linter/src/utils/TsUtils.ts @@ -1079,4 +1079,33 @@ export class TsUtils { } return undefined; } + + public typeIsRecursive(topType: ts.Type, type: ts.Type | undefined = undefined): boolean { + if (type == undefined) { + type = topType; + } else if (type == topType) { + return true; + } else if (type.aliasSymbol) { + return false; + } + + if (type.isUnion()) { + for (let unionElem of type.types) { + if (this.typeIsRecursive(topType, unionElem)) { + return true; + } + } + } + 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) { + if (this.typeIsRecursive(topType, typeArg)) { + return true; + } + } + } + } + return false; + } } diff --git a/linter/test/recursive_types.ts b/linter/test/recursive_types.ts new file mode 100644 index 000000000..40647e45f --- /dev/null +++ b/linter/test/recursive_types.ts @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2022-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +class ID { } + +type A = ID; +(fn: () => A) => { let x = fn() } +(fn: () => ID | A> | null> | ID) => { let x = fn() } + +type B = ID>>; +(fn: () => B) => { let x = fn() } + +type C = ID>>; +(fn: () => C) => { let x = fn() } + +class X1 extends ID { }; +(fn: () => X1) => { let x = fn() } + +class X2 extends ID | null> { }; +(fn: () => X2) => { let x = fn() } + +namespace test1 { + type A = string | Array | Map>; + + class C { + private f(): A { + let t = this.f(); + return ""; + }; + } +} + +namespace test2 { + type A = string | Array | Map>; + + function f(): A { + return "" + } + let b = f(); +} \ No newline at end of file diff --git a/linter/test/recursive_types.ts.autofix.skip b/linter/test/recursive_types.ts.autofix.skip new file mode 100644 index 000000000..e69de29bb diff --git a/linter/test/recursive_types.ts.relax.json b/linter/test/recursive_types.ts.relax.json new file mode 100644 index 000000000..6def1084c --- /dev/null +++ b/linter/test/recursive_types.ts.relax.json @@ -0,0 +1,48 @@ +{ + "copyright": [ + "Copyright (c) 2022-2023 Huawei Device Co., Ltd.", + "Licensed under the Apache License, Version 2.0 (the 'License');", + "you may not use this file except in compliance with the License.", + "You may obtain a copy of the License at", + "", + "http://www.apache.org/licenses/LICENSE-2.0", + "", + "Unless required by applicable law or agreed to in writing, software", + "distributed under the License is distributed on an 'AS IS' BASIS,", + "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.", + "See the License for the specific language governing permissions and", + "limitations under the License." + ], + "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" + } + ] +} \ No newline at end of file diff --git a/linter/test/recursive_types.ts.strict.json b/linter/test/recursive_types.ts.strict.json new file mode 100644 index 000000000..6def1084c --- /dev/null +++ b/linter/test/recursive_types.ts.strict.json @@ -0,0 +1,48 @@ +{ + "copyright": [ + "Copyright (c) 2022-2023 Huawei Device Co., Ltd.", + "Licensed under the Apache License, Version 2.0 (the 'License');", + "you may not use this file except in compliance with the License.", + "You may obtain a copy of the License at", + "", + "http://www.apache.org/licenses/LICENSE-2.0", + "", + "Unless required by applicable law or agreed to in writing, software", + "distributed under the License is distributed on an 'AS IS' BASIS,", + "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.", + "See the License for the specific language governing permissions and", + "limitations under the License." + ], + "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" + } + ] +} \ No newline at end of file -- Gitee