• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/* @internal */
2namespace ts.codefix {
3    type ContextualTrackChangesFunction = (cb: (changeTracker: textChanges.ChangeTracker) => void) => FileTextChanges[];
4    const fixId = "addMissingAsync";
5    const errorCodes = [
6        Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1.code,
7        Diagnostics.Type_0_is_not_assignable_to_type_1.code,
8        Diagnostics.Type_0_is_not_comparable_to_type_1.code
9    ];
10
11    registerCodeFix({
12        fixIds: [fixId],
13        errorCodes,
14        getCodeActions: function getCodeActionsToAddMissingAsync(context) {
15            const { sourceFile, errorCode, cancellationToken, program, span } = context;
16            const diagnostic = find(program.getTypeChecker().getDiagnostics(sourceFile, cancellationToken), getIsMatchingAsyncError(span, errorCode));
17            const directSpan = diagnostic && diagnostic.relatedInformation && find(diagnostic.relatedInformation, r => r.code === Diagnostics.Did_you_mean_to_mark_this_function_as_async.code) as TextSpan | undefined;
18
19            const decl = getFixableErrorSpanDeclaration(sourceFile, directSpan);
20            if (!decl) {
21                return;
22            }
23
24            const trackChanges: ContextualTrackChangesFunction = cb => textChanges.ChangeTracker.with(context, cb);
25            return [getFix(context, decl, trackChanges)];
26        },
27        getAllCodeActions: context => {
28            const { sourceFile } = context;
29            const fixedDeclarations = new Set<number>();
30            return codeFixAll(context, errorCodes, (t, diagnostic) => {
31                const span = diagnostic.relatedInformation && find(diagnostic.relatedInformation, r => r.code === Diagnostics.Did_you_mean_to_mark_this_function_as_async.code) as TextSpan | undefined;
32                const decl = getFixableErrorSpanDeclaration(sourceFile, span);
33                if (!decl) {
34                    return;
35                }
36                const trackChanges: ContextualTrackChangesFunction = cb => (cb(t), []);
37                return getFix(context, decl, trackChanges, fixedDeclarations);
38            });
39        },
40    });
41
42    type FixableDeclaration = ArrowFunction | FunctionDeclaration | FunctionExpression | MethodDeclaration;
43    function getFix(context: CodeFixContext | CodeFixAllContext, decl: FixableDeclaration, trackChanges: ContextualTrackChangesFunction, fixedDeclarations?: Set<number>) {
44        const changes = trackChanges(t => makeChange(t, context.sourceFile, decl, fixedDeclarations));
45        return createCodeFixAction(fixId, changes, Diagnostics.Add_async_modifier_to_containing_function, fixId, Diagnostics.Add_all_missing_async_modifiers);
46    }
47
48    function makeChange(changeTracker: textChanges.ChangeTracker, sourceFile: SourceFile, insertionSite: FixableDeclaration, fixedDeclarations?: Set<number>) {
49        if (fixedDeclarations) {
50            if (fixedDeclarations.has(getNodeId(insertionSite))) {
51                return;
52            }
53        }
54        fixedDeclarations?.add(getNodeId(insertionSite));
55        const cloneWithModifier = factory.updateModifiers(
56            getSynthesizedDeepClone(insertionSite, /*includeTrivia*/ true),
57            factory.createNodeArray(factory.createModifiersFromModifierFlags(getSyntacticModifierFlags(insertionSite) | ModifierFlags.Async)));
58        changeTracker.replaceNode(
59            sourceFile,
60            insertionSite,
61            cloneWithModifier);
62    }
63
64    function getFixableErrorSpanDeclaration(sourceFile: SourceFile, span: TextSpan | undefined): FixableDeclaration | undefined {
65        if (!span) return undefined;
66        const token = getTokenAtPosition(sourceFile, span.start);
67        // Checker has already done work to determine that async might be possible, and has attached
68        // related info to the node, so start by finding the signature that exactly matches up
69        // with the diagnostic range.
70        const decl = findAncestor(token, node => {
71            if (node.getStart(sourceFile) < span.start || node.getEnd() > textSpanEnd(span)) {
72                return "quit";
73            }
74            return (isArrowFunction(node) || isMethodDeclaration(node) || isFunctionExpression(node) || isFunctionDeclaration(node)) && textSpansEqual(span, createTextSpanFromNode(node, sourceFile));
75        }) as FixableDeclaration | undefined;
76
77        return decl;
78    }
79
80    function getIsMatchingAsyncError(span: TextSpan, errorCode: number) {
81        return ({ start, length, relatedInformation, code }: Diagnostic) =>
82            isNumber(start) && isNumber(length) && textSpansEqual({ start, length }, span) &&
83            code === errorCode &&
84            !!relatedInformation &&
85            some(relatedInformation, related => related.code === Diagnostics.Did_you_mean_to_mark_this_function_as_async.code);
86    }
87}
88