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