diff --git a/src/services/codefixes/annotateWithTypeFromJSDoc.ts b/src/services/codefixes/annotateWithTypeFromJSDoc.ts index 5edbab01450..c8a3977eb3b 100644 --- a/src/services/codefixes/annotateWithTypeFromJSDoc.ts +++ b/src/services/codefixes/annotateWithTypeFromJSDoc.ts @@ -50,19 +50,19 @@ namespace ts.codefix { for (const param of decl.parameters) { if (!param.type) { const paramType = getJSDocType(param); - if (paramType) changes.insertTypeAnnotation(sourceFile, param, transformJSDocType(paramType)); + if (paramType) changes.tryInsertTypeAnnotation(sourceFile, param, transformJSDocType(paramType)); } } if (needParens) changes.insertNodeAfter(sourceFile, last(decl.parameters), createToken(SyntaxKind.CloseParenToken)); if (!decl.type) { const returnType = getJSDocReturnType(decl); - if (returnType) changes.insertTypeAnnotation(sourceFile, decl, transformJSDocType(returnType)); + if (returnType) changes.tryInsertTypeAnnotation(sourceFile, decl, transformJSDocType(returnType)); } } else { const jsdocType = Debug.assertDefined(getJSDocType(decl)); // If not defined, shouldn't have been an error to fix Debug.assert(!decl.type); // If defined, shouldn't have been an error to fix. - changes.insertTypeAnnotation(sourceFile, decl, transformJSDocType(jsdocType)); + changes.tryInsertTypeAnnotation(sourceFile, decl, transformJSDocType(jsdocType)); } } diff --git a/src/services/codefixes/inferFromUsage.ts b/src/services/codefixes/inferFromUsage.ts index 8398bc7c6d5..99556fc84de 100644 --- a/src/services/codefixes/inferFromUsage.ts +++ b/src/services/codefixes/inferFromUsage.ts @@ -173,7 +173,7 @@ namespace ts.codefix { function annotate(changes: textChanges.ChangeTracker, sourceFile: SourceFile, declaration: textChanges.TypeAnnotatable, type: Type | undefined, program: Program): void { const typeNode = type && getTypeNodeIfAccessible(type, declaration, program.getTypeChecker()); - if (typeNode) changes.insertTypeAnnotation(sourceFile, declaration, typeNode); + if (typeNode) changes.tryInsertTypeAnnotation(sourceFile, declaration, typeNode); } function getTypeNodeIfAccessible(type: Type, enclosingScope: Node, checker: TypeChecker): TypeNode | undefined { diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index 0b29cdc438b..0a48bcf6228 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -360,12 +360,21 @@ namespace ts.textChanges { } /** Prefer this over replacing a node with another that has a type annotation, as it avoids reformatting the other parts of the node. */ - public insertTypeAnnotation(sourceFile: SourceFile, node: TypeAnnotatable, type: TypeNode): void { - const end = (isFunctionLike(node) - // If no `)`, is an arrow function `x => x`, so use the end of the first parameter - ? findChildOfKind(node, SyntaxKind.CloseParenToken, sourceFile) || first(node.parameters) - : node.kind !== SyntaxKind.VariableDeclaration && node.questionToken ? node.questionToken : node.name).end; - this.insertNodeAt(sourceFile, end, type, { prefix: ": " }); + public tryInsertTypeAnnotation(sourceFile: SourceFile, node: TypeAnnotatable, type: TypeNode): void { + let endNode: Node; + if (isFunctionLike(node)) { + endNode = findChildOfKind(node, SyntaxKind.CloseParenToken, sourceFile); + if (!endNode) { + if (!isArrowFunction(node)) return; // Function missing parentheses, give up + // If no `)`, is an arrow function `x => x`, so use the end of the first parameter + endNode = first(node.parameters); + } + } + else { + endNode = node.kind !== SyntaxKind.VariableDeclaration && node.questionToken ? node.questionToken : node.name; + } + + this.insertNodeAt(sourceFile, endNode.end, type, { prefix: ": " }); } public insertTypeParameters(sourceFile: SourceFile, node: SignatureDeclaration, typeParameters: ReadonlyArray): void { diff --git a/tests/cases/fourslash/codeFixInferFromUsage_noCrashOnMissingParens.ts b/tests/cases/fourslash/codeFixInferFromUsage_noCrashOnMissingParens.ts new file mode 100644 index 00000000000..ba320280e87 --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromUsage_noCrashOnMissingParens.ts @@ -0,0 +1,12 @@ +/// + +// @noImplicitAny: true +// @target: esnext + +////class C { +//// m() { this.x * 2; } +//// get x { return null; } +////} + +// Just testing that we don't crash in `insertTypeAnnotation` from inferFromUsage +verify.not.codeFixAvailable();