From d6795c2f29ccfc798c0be9814c710e506b1a296f Mon Sep 17 00:00:00 2001 From: Mikhail Velikanov Date: Mon, 26 Jun 2023 13:26:27 +0300 Subject: [PATCH] TS Linter. Autofix catch clause and throw statement. Change-Id: Iff2304eabe7eeeab68a3c27dddbb36eeceb8e03b Signed-off-by: Mikhail Velikanov --- linter/src/Autofixer.ts | 13 ++ linter/src/TypeScriptLinter.ts | 22 ++- linter/test/catch_clause.ts.autofix.json | 95 ++++++++++++ linter/test/catch_clause.ts.autofix.skip | 0 linter/test/conditional_types.ts | 2 +- linter/test/conditional_types.ts.autofix.json | 139 ++++++++++++++++++ linter/test/conditional_types.ts.autofix.skip | 0 linter/test/types.ts | 2 +- linter/test/types.ts.strict.json | 5 - 9 files changed, 264 insertions(+), 14 deletions(-) create mode 100755 linter/test/catch_clause.ts.autofix.json delete mode 100644 linter/test/catch_clause.ts.autofix.skip create mode 100755 linter/test/conditional_types.ts.autofix.json delete mode 100644 linter/test/conditional_types.ts.autofix.skip diff --git a/linter/src/Autofixer.ts b/linter/src/Autofixer.ts index 58c574aa4..b5b3155f9 100644 --- a/linter/src/Autofixer.ts +++ b/linter/src/Autofixer.ts @@ -135,4 +135,17 @@ export function fixFunctionExpression(funcExpr: ts.FunctionExpression, ); let text = printer.printNode(ts.EmitHint.Unspecified, arrowFunc, funcExpr.getSourceFile()); return { start: funcExpr.getStart(), end: funcExpr.getEnd(), replacementText: text }; +} + +export function dropTypeOnVarDecl(varDecl: ts.VariableDeclaration): Autofix { + let newVarDecl = ts.factory.createVariableDeclaration(varDecl.name, undefined, undefined, undefined); + let text = printer.printNode(ts.EmitHint.Unspecified, newVarDecl, varDecl.getSourceFile()); + return { start: varDecl.getStart(), end: varDecl.getEnd(), replacementText: text}; +} + +export function wrapExpressionInError(expr: ts.Expression, isString: boolean): Autofix { + let ctorArgs = isString ? [ expr ] : [ ts.factory.createStringLiteral('', true), expr]; + let newExpr = ts.factory.createNewExpression(ts.factory.createIdentifier('Error'), undefined, ctorArgs); + let text = printer.printNode(ts.EmitHint.Unspecified, newExpr, expr.getSourceFile()); + return { start: expr.getStart(), end: expr.getEnd(), replacementText: text }; } \ No newline at end of file diff --git a/linter/src/TypeScriptLinter.ts b/linter/src/TypeScriptLinter.ts index 8c002520d..f4e68c905 100644 --- a/linter/src/TypeScriptLinter.ts +++ b/linter/src/TypeScriptLinter.ts @@ -604,8 +604,12 @@ export class TypeScriptLinter { private handleThrowStatement(node: ts.Node) { let throwStmt = node as ts.ThrowStatement; let throwExprType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(throwStmt.expression); - if (!throwExprType.isClassOrInterface() || !this.typeHierarchyHasTypeError(throwExprType)) - this.incrementCounters(node, FaultID.ThrowStatement); + if (!throwExprType.isClassOrInterface() || !this.typeHierarchyHasTypeError(throwExprType)) { + let autofix: Autofix[] | undefined = undefined; + if (Autofixer.shouldAutofix(throwStmt, FaultID.ThrowStatement)) + autofix = [ Autofixer.wrapExpressionInError(throwStmt.expression, Utils.isStringType(throwExprType)) ]; + this.incrementCounters(node, FaultID.ThrowStatement, true, autofix); + } } private handleForStatement(node: ts.Node) { @@ -942,11 +946,15 @@ export class TypeScriptLinter { private handleCatchClause(node: ts.Node) { let tsCatch = node as ts.CatchClause; // In TS catch clause doesn't permit specification of the exception varible type except 'any' or 'unknown'. - // It is not compatible with STS 'catch' where the exception varilab has to be of type - // 'Exception' or derived from it. - // So each 'catch' which has explicite type for the exception object goes to problems in strict mode. - if (tsCatch.variableDeclaration && tsCatch.variableDeclaration.type) - this.incrementCounters(node, FaultID.CatchWithUnsupportedType); + // It is not compatible with STS 'catch' where the exception variable has to be of type + // Error or derived from it. + // So each 'catch' which has explicit type for the exception object goes to problems in strict mode. + if (tsCatch.variableDeclaration && tsCatch.variableDeclaration.type) { + let autofix: Autofix[] | undefined = undefined; + if (Autofixer.shouldAutofix(tsCatch, FaultID.CatchWithUnsupportedType)) + autofix = [ Autofixer.dropTypeOnVarDecl(tsCatch.variableDeclaration) ]; + this.incrementCounters(node, FaultID.CatchWithUnsupportedType, true, autofix); + } } private handleClassDeclaration(node: ts.Node) { diff --git a/linter/test/catch_clause.ts.autofix.json b/linter/test/catch_clause.ts.autofix.json new file mode 100755 index 000000000..c3db7e9f3 --- /dev/null +++ b/linter/test/catch_clause.ts.autofix.json @@ -0,0 +1,95 @@ +{ + "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": 17, + "column": 13, + "problem": "ThrowStatement", + "autofixable": true, + "autofix": [ + { + "start": 634, + "end": 657, + "replacementText": "new Error(\"Catch with 'any' type\")" + } + ] + }, + { + "line": 18, + "column": 3, + "problem": "CatchWithUnsupportedType", + "autofixable": true, + "autofix": [ + { + "start": 668, + "end": 674, + "replacementText": "e" + } + ] + }, + { + "line": 18, + "column": 13, + "problem": "AnyType", + "autofixable": false + }, + { + "line": 23, + "column": 13, + "problem": "ThrowStatement", + "autofixable": true, + "autofix": [ + { + "start": 723, + "end": 750, + "replacementText": "new Error(\"Catch with 'unknown' type\")" + } + ] + }, + { + "line": 24, + "column": 3, + "problem": "CatchWithUnsupportedType", + "autofixable": true, + "autofix": [ + { + "start": 761, + "end": 771, + "replacementText": "e" + } + ] + }, + { + "line": 24, + "column": 13, + "problem": "UnknownType", + "autofixable": false + }, + { + "line": 29, + "column": 13, + "problem": "ThrowStatement", + "autofixable": true, + "autofix": [ + { + "start": 820, + "end": 849, + "replacementText": "new Error('Catch without explicit type')" + } + ] + } + ] +} \ No newline at end of file diff --git a/linter/test/catch_clause.ts.autofix.skip b/linter/test/catch_clause.ts.autofix.skip deleted file mode 100644 index e69de29bb..000000000 diff --git a/linter/test/conditional_types.ts b/linter/test/conditional_types.ts index 8d46cc39a..af657f550 100644 --- a/linter/test/conditional_types.ts +++ b/linter/test/conditional_types.ts @@ -32,7 +32,7 @@ type NameOrId = T extends number ? IdLabel : NameLabel; function createLabel(idOrName: T): NameOrId { - throw 'unimplemented'; + throw 1; } const a = createLabel('typescript'); // let a: NameLabel const b = createLabel(2.8); // let b: IdLabel diff --git a/linter/test/conditional_types.ts.autofix.json b/linter/test/conditional_types.ts.autofix.json new file mode 100755 index 000000000..a27d015a5 --- /dev/null +++ b/linter/test/conditional_types.ts.autofix.json @@ -0,0 +1,139 @@ +{ + "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": 19, + "column": 1, + "problem": "InterfaceOrEnumMerging", + "autofixable": false + }, + { + "line": 22, + "column": 17, + "problem": "ConditionalType", + "autofixable": false + }, + { + "line": 23, + "column": 17, + "problem": "ConditionalType", + "autofixable": false + }, + { + "line": 31, + "column": 25, + "problem": "UnionType", + "autofixable": false + }, + { + "line": 31, + "column": 44, + "problem": "ConditionalType", + "autofixable": false + }, + { + "line": 34, + "column": 32, + "problem": "UnionType", + "autofixable": false + }, + { + "line": 35, + "column": 3, + "problem": "ThrowStatement", + "autofixable": true, + "autofix": [ + { + "start": 1144, + "end": 1145, + "replacementText": "new Error('', 1)" + } + ] + }, + { + "line": 37, + "column": 11, + "problem": "GenericCallNoTypeArgs", + "autofixable": false + }, + { + "line": 38, + "column": 11, + "problem": "GenericCallNoTypeArgs", + "autofixable": false + }, + { + "line": 39, + "column": 11, + "problem": "GenericCallNoTypeArgs", + "autofixable": false + }, + { + "line": 41, + "column": 21, + "problem": "ConditionalType", + "autofixable": false + }, + { + "line": 41, + "column": 31, + "problem": "ObjectTypeLiteral", + "autofixable": false + }, + { + "line": 41, + "column": 42, + "problem": "UnknownType", + "autofixable": false + }, + { + "line": 41, + "column": 54, + "problem": "IndexedAccessType", + "autofixable": false + }, + { + "line": 41, + "column": 56, + "problem": "StringLiteralType", + "autofixable": false + }, + { + "line": 45, + "column": 1, + "problem": "InterfaceOrEnumMerging", + "autofixable": false + }, + { + "line": 51, + "column": 19, + "problem": "ConditionalType", + "autofixable": false + }, + { + "line": 51, + "column": 29, + "problem": "AnyType", + "autofixable": false + }, + { + "line": 51, + "column": 37, + "problem": "IndexedAccessType", + "autofixable": false + } + ] +} diff --git a/linter/test/conditional_types.ts.autofix.skip b/linter/test/conditional_types.ts.autofix.skip deleted file mode 100644 index e69de29bb..000000000 diff --git a/linter/test/types.ts b/linter/test/types.ts index 290453460..4230ef66d 100644 --- a/linter/test/types.ts +++ b/linter/test/types.ts @@ -46,7 +46,7 @@ export function animations() { const regex = /go*d/; - throw 'labuda'; + throw new TypeError('labuda'); } const c = 'c'; diff --git a/linter/test/types.ts.strict.json b/linter/test/types.ts.strict.json index 4316cc603..687c45fd7 100644 --- a/linter/test/types.ts.strict.json +++ b/linter/test/types.ts.strict.json @@ -129,11 +129,6 @@ "column": 17, "problem": "RegexLiteral" }, - { - "line": 49, - "column": 3, - "problem": "ThrowStatement" - }, { "line": 54, "column": 26, -- Gitee