diff --git a/linter-4.2/src/TypeScriptLinter.ts b/linter-4.2/src/TypeScriptLinter.ts index 7a4e4b59dffd7f11a7145b0dacea907343f19eb7..958754e439e6895123edae224a1178b0c9a6a019 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 f54da61bf62c043dcf9b0bfed7a824634a398ba1..7dd92572995a369490bbd9e2aff603a533fecfff 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 0000000000000000000000000000000000000000..40647e45fa3aaf84e7c1a0e7e98b70824f04a3bb --- /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 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 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 0000000000000000000000000000000000000000..6def1084c2fa21d5c7d6230283c00d4013e534af --- /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 0000000000000000000000000000000000000000..6def1084c2fa21d5c7d6230283c00d4013e534af --- /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 f219c9e425aedcfc91e379aafcae7e745f26f3dd..af118fc1f5defadfb350a536d4919312670e1849 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 d72d8158dacd9a79a76943f5f717fee7a162d6e7..9f2a3bd80a890303a5c1373c02d0ad746786c29d 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 0000000000000000000000000000000000000000..40647e45fa3aaf84e7c1a0e7e98b70824f04a3bb --- /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 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/linter/test/recursive_types.ts.relax.json b/linter/test/recursive_types.ts.relax.json new file mode 100644 index 0000000000000000000000000000000000000000..6def1084c2fa21d5c7d6230283c00d4013e534af --- /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 0000000000000000000000000000000000000000..6def1084c2fa21d5c7d6230283c00d4013e534af --- /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