• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/* @internal */
2namespace ts.codefix {
3    const errorCodeToFixes = createMultiMap<CodeFixRegistration>();
4    const fixIdToRegistration = new Map<string, CodeFixRegistration>();
5
6    export function createCodeFixActionWithoutFixAll(fixName: string, changes: FileTextChanges[], description: DiagnosticAndArguments) {
7        return createCodeFixActionWorker(fixName, diagnosticToString(description), changes, /*fixId*/ undefined, /*fixAllDescription*/ undefined);
8    }
9
10    export function createCodeFixAction(fixName: string, changes: FileTextChanges[], description: DiagnosticAndArguments, fixId: {}, fixAllDescription: DiagnosticAndArguments, command?: CodeActionCommand): CodeFixAction {
11        return createCodeFixActionWorker(fixName, diagnosticToString(description), changes, fixId, diagnosticToString(fixAllDescription), command);
12    }
13
14    export function createCodeFixActionMaybeFixAll(fixName: string, changes: FileTextChanges[], description: DiagnosticAndArguments, fixId?: {}, fixAllDescription?: DiagnosticAndArguments, command?: CodeActionCommand) {
15        return createCodeFixActionWorker(fixName, diagnosticToString(description), changes, fixId, fixAllDescription && diagnosticToString(fixAllDescription), command);
16    }
17
18    function createCodeFixActionWorker(fixName: string, description: string, changes: FileTextChanges[], fixId?: {}, fixAllDescription?: string, command?: CodeActionCommand): CodeFixAction {
19        return { fixName, description, changes, fixId, fixAllDescription, commands: command ? [command] : undefined };
20    }
21
22    export function registerCodeFix(reg: CodeFixRegistration) {
23        for (const error of reg.errorCodes) {
24            errorCodeToFixes.add(String(error), reg);
25        }
26        if (reg.fixIds) {
27            for (const fixId of reg.fixIds) {
28                Debug.assert(!fixIdToRegistration.has(fixId));
29                fixIdToRegistration.set(fixId, reg);
30            }
31        }
32    }
33
34    export function getSupportedErrorCodes(): string[] {
35        return arrayFrom(errorCodeToFixes.keys());
36    }
37
38    function removeFixIdIfFixAllUnavailable(registration: CodeFixRegistration, diagnostics: Diagnostic[]) {
39        const { errorCodes } = registration;
40        let maybeFixableDiagnostics = 0;
41        for (const diag of diagnostics) {
42            if (contains(errorCodes, diag.code)) maybeFixableDiagnostics++;
43            if (maybeFixableDiagnostics > 1) break;
44        }
45
46        const fixAllUnavailable = maybeFixableDiagnostics < 2;
47        return ({ fixId, fixAllDescription, ...action }: CodeFixAction): CodeFixAction => {
48            return fixAllUnavailable ? action : { ...action, fixId, fixAllDescription };
49        };
50    }
51
52    export function getFixes(context: CodeFixContext): readonly CodeFixAction[] {
53        const diagnostics = getDiagnostics(context);
54        const registrations = errorCodeToFixes.get(String(context.errorCode));
55        return flatMap(registrations, f => map(f.getCodeActions(context), removeFixIdIfFixAllUnavailable(f, diagnostics)));
56    }
57
58    export function getAllFixes(context: CodeFixAllContext): CombinedCodeActions {
59        // Currently fixId is always a string.
60        return fixIdToRegistration.get(cast(context.fixId, isString))!.getAllCodeActions!(context);
61    }
62
63    export function createCombinedCodeActions(changes: FileTextChanges[], commands?: CodeActionCommand[]): CombinedCodeActions {
64        return { changes, commands };
65    }
66
67    export function createFileTextChanges(fileName: string, textChanges: TextChange[]): FileTextChanges {
68        return { fileName, textChanges };
69    }
70
71    export function codeFixAll(
72        context: CodeFixAllContext,
73        errorCodes: number[],
74        use: (changes: textChanges.ChangeTracker, error: DiagnosticWithLocation, commands: Push<CodeActionCommand>) => void,
75    ): CombinedCodeActions {
76        const commands: CodeActionCommand[] = [];
77        const changes = textChanges.ChangeTracker.with(context, t => eachDiagnostic(context, errorCodes, diag => use(t, diag, commands)));
78        return createCombinedCodeActions(changes, commands.length === 0 ? undefined : commands);
79    }
80
81    export function eachDiagnostic(context: CodeFixAllContext, errorCodes: readonly number[], cb: (diag: DiagnosticWithLocation) => void): void {
82        for (const diag of getDiagnostics(context)) {
83            if (contains(errorCodes, diag.code)) {
84                cb(diag as DiagnosticWithLocation);
85            }
86        }
87    }
88
89    function getDiagnostics({ program, sourceFile, cancellationToken }: CodeFixContextBase) {
90        return [
91            ...program.getSemanticDiagnostics(sourceFile, cancellationToken),
92            ...program.getSyntacticDiagnostics(sourceFile, cancellationToken),
93            ...computeSuggestionDiagnostics(sourceFile, program, cancellationToken)
94        ];
95    }
96}
97