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