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