• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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}