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