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