diff --git a/linter-4.2/src/TypeScriptLinter.ts b/linter-4.2/src/TypeScriptLinter.ts index d2852f46afd2be6e7ffffb18094a55f580f58e5e..8fbf03927faddd02a47d9e5db97224b40d82ee32 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 1fc3ed1abd7b6ed8c1435be18dafe5ad0036e1d9..d688bada1484054038b6a6973f90814b51b8bdbe 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 40647e45fa3aaf84e7c1a0e7e98b70824f04a3bb..52402491bcdf2a3ada94cbd7a307cd10ecd7e4e9 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 6def1084c2fa21d5c7d6230283c00d4013e534af..9f63e9379e42239afd2f06f34694593e296a573e 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 6def1084c2fa21d5c7d6230283c00d4013e534af..9f63e9379e42239afd2f06f34694593e296a573e 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 dc31177e04be7bda950dba8cb05faa45139b1b1c..41c37ca5d219438387f036e02bb3dcb743b52d9f 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 6aa0e781478a806727e0d66812182e7a81f70c0b..48589f0c6060274b66f0b8d49dec058cdbcb139c 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 40647e45fa3aaf84e7c1a0e7e98b70824f04a3bb..52402491bcdf2a3ada94cbd7a307cd10ecd7e4e9 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 6def1084c2fa21d5c7d6230283c00d4013e534af..9f63e9379e42239afd2f06f34694593e296a573e 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 6def1084c2fa21d5c7d6230283c00d4013e534af..9f63e9379e42239afd2f06f34694593e296a573e 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