1/* @internal */ 2namespace ts.codefix { 3 const fixName = "addVoidToPromise"; 4 const fixId = "addVoidToPromise"; 5 const errorCodes = [ 6 Diagnostics.Expected_0_arguments_but_got_1_Did_you_forget_to_include_void_in_your_type_argument_to_Promise.code 7 ]; 8 registerCodeFix({ 9 errorCodes, 10 fixIds: [fixId], 11 getCodeActions(context) { 12 const changes = textChanges.ChangeTracker.with(context, t => makeChange(t, context.sourceFile, context.span, context.program)); 13 if (changes.length > 0) { 14 return [createCodeFixAction(fixName, changes, Diagnostics.Add_void_to_Promise_resolved_without_a_value, fixId, Diagnostics.Add_void_to_all_Promises_resolved_without_a_value)]; 15 } 16 }, 17 getAllCodeActions(context: CodeFixAllContext) { 18 return codeFixAll(context, errorCodes, (changes, diag) => makeChange(changes, diag.file, diag, context.program, new Set())); 19 } 20 }); 21 22 function makeChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, span: TextSpan, program: Program, seen?: Set<ParameterDeclaration>) { 23 const node = getTokenAtPosition(sourceFile, span.start); 24 if (!isIdentifier(node) || !isCallExpression(node.parent) || node.parent.expression !== node || node.parent.arguments.length !== 0) return; 25 26 const checker = program.getTypeChecker(); 27 const symbol = checker.getSymbolAtLocation(node); 28 29 // decl should be `new Promise((<decl>) => {})` 30 const decl = symbol?.valueDeclaration; 31 if (!decl || !isParameter(decl) || !isNewExpression(decl.parent.parent)) return; 32 33 // no need to make this change if we have already seen this parameter. 34 if (seen?.has(decl)) return; 35 seen?.add(decl); 36 37 const typeArguments = getEffectiveTypeArguments(decl.parent.parent); 38 if (some(typeArguments)) { 39 // append ` | void` to type argument 40 const typeArgument = typeArguments[0]; 41 const needsParens = !isUnionTypeNode(typeArgument) && !isParenthesizedTypeNode(typeArgument) && 42 isParenthesizedTypeNode(factory.createUnionTypeNode([typeArgument, factory.createKeywordTypeNode(SyntaxKind.VoidKeyword)]).types[0]); 43 if (needsParens) { 44 changes.insertText(sourceFile, typeArgument.pos, "("); 45 } 46 changes.insertText(sourceFile, typeArgument.end, needsParens ? ") | void" : " | void"); 47 } 48 else { 49 // make sure the Promise is type is untyped (i.e., `unknown`) 50 const signature = checker.getResolvedSignature(node.parent); 51 const parameter = signature?.parameters[0]; 52 const parameterType = parameter && checker.getTypeOfSymbolAtLocation(parameter, decl.parent.parent); 53 if (isInJSFile(decl)) { 54 if (!parameterType || parameterType.flags & TypeFlags.AnyOrUnknown) { 55 // give the expression a type 56 changes.insertText(sourceFile, decl.parent.parent.end, `)`); 57 changes.insertText(sourceFile, skipTrivia(sourceFile.text, decl.parent.parent.pos), `/** @type {Promise<void>} */(`); 58 } 59 } 60 else { 61 if (!parameterType || parameterType.flags & TypeFlags.Unknown) { 62 // add `void` type argument 63 changes.insertText(sourceFile, decl.parent.parent.expression.end, "<void>"); 64 } 65 } 66 } 67 } 68 69 function getEffectiveTypeArguments(node: NewExpression) { 70 if (isInJSFile(node)) { 71 if (isParenthesizedExpression(node.parent)) { 72 const jsDocType = getJSDocTypeTag(node.parent)?.typeExpression.type; 73 if (jsDocType && isTypeReferenceNode(jsDocType) && isIdentifier(jsDocType.typeName) && idText(jsDocType.typeName) === "Promise") { 74 return jsDocType.typeArguments; 75 } 76 } 77 } 78 else { 79 return node.typeArguments; 80 } 81 } 82}