This page documents the code fix framework in the TypeScript language service: how fixes are registered, how ChangeTracker accumulates edits, how suggestion diagnostics identify fixable patterns, and how several canonical fix implementations work. For the broader language service architecture this framework lives inside, see Language Service. For refactoring support (a related but separate concept), see Refactorings.
The code fix system lets the language service respond to compiler diagnostics with one or more automated edits. When an editor asks for code actions at a diagnostic location, the service looks up all registered fixes for that error code, invokes their handlers, and returns a list of FileTextChanges describing the edits to apply.
A parallel mechanism — suggestion diagnostics — generates advisory diagnostics that are not compiler errors but that signal an available automated transformation (e.g., "this function may be converted to async").
Sources: src/services/codefixes/fixAddMissingMember.ts158-251 src/services/textChanges.ts493-507
Every code fix is registered with registerCodeFix, exported from the ts.codefix namespace. The registration object has three required fields and one optional one:
| Field | Type | Purpose |
|---|---|---|
errorCodes | number[] | Diagnostic codes this fix handles |
getCodeActions | (context: CodeFixContext) → CodeFixAction[] | undefined | Produces fixes for a single diagnostic |
fixIds | string[] | Stable IDs used for "fix all" grouping |
getAllCodeActions | (context: CodeFixAllContext) → CombinedCodeActions | Optional; applies fix to every matching diagnostic in the file |
Fix ID conventions. Each fix has a short string identifier such as "fixMissingMember", "inferFromUsage", or "convertToAsyncFunction". The fixId connects individual fix actions to their "fix all" counterpart. The getAllCodeActions handler typically uses the helper codeFixAll, which iterates over all diagnostics matching errorCodes and applies the same transformation to each.
Example registration pattern (from fixAddMissingMember):
registerCodeFix({
errorCodes, // Diagnostics.Property_0_does_not_exist_on_type_1.code, ...
getCodeActions(context) { ... },
fixIds: [fixMissingMember, fixMissingFunctionDeclaration, ...],
getAllCodeActions: context => createCombinedCodeActions(ChangeTracker.with(context, changes => {
eachDiagnostic(context, errorCodes, diag => { ... });
})),
});
Sources: src/services/codefixes/fixAddMissingMember.ts133-251 src/services/codefixes/fixExtendsInterfaceBecomesImplements.ts19-37 src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts26-51
ChangeTracker is the primary interface for building edits. It accumulates changes in memory and converts them to FileTextChanges[] at the end. It lives in src/services/textChanges.ts.
Sources: src/services/textChanges.ts499-507
Internally, ChangeTracker stores a list of Change objects, each one a discriminated union:
ChangeKind | Interface | Description |
|---|---|---|
Remove | RemoveNode | Delete a node or range |
ReplaceWithSingleNode | ReplaceWithSingleNode | Swap a range for one node |
ReplaceWithMultipleNodes | ReplaceWithMultipleNodes | Swap a range for several nodes |
Text | ChangeText | Insert/replace raw text |
Sources: src/services/textChanges.ts307-344
| Method | Purpose |
|---|---|
deleteNode(sourceFile, node) | Remove a node, adjusting surrounding trivia |
deleteRange(sourceFile, range) | Remove an exact text range |
deleteModifier(sourceFile, modifier) | Remove a modifier keyword |
replaceNode(sourceFile, oldNode, newNode) | Swap one node for another |
replaceNodeWithNodes(sourceFile, oldNode, newNodes) | Expand a node into multiple |
replaceRangeWithText(sourceFile, range, text) | Insert raw text at a range |
insertNodeAt(sourceFile, pos, newNode) | Insert at a character offset |
insertNodeBefore(sourceFile, before, newNode) | Insert before an existing node |
insertNodeAfter(sourceFile, after, newNode) | Insert after an existing node |
insertMemberAtStart(sourceFile, node, newElement) | Insert class/interface member at top |
insertNodeAtConstructorStart(sourceFile, ctr, stmt) | Prepend to constructor body |
tryInsertTypeAnnotation(sourceFile, node, type) | Add : Type annotation |
addJSDocTags(sourceFile, parent, newTags) | Add or merge JSDoc tags |
insertModifierBefore(sourceFile, modifier, before) | Add a keyword modifier |
Sources: src/services/textChanges.ts524-790
Deletion and replacement of nodes needs to account for leading and trailing whitespace, comments, and newlines. ChangeTracker uses two enums to configure this:
LeadingTriviaOption — controls where the start of the replaced range begins:
| Value | Behavior |
|---|---|
Exclude | Start at getStart(), skip all trivia |
IncludeAll | Include all leading trivia up to the previous token's end |
JSDoc | Include attached JSDoc comment block |
StartLine | Include trivia only from the start of the current line |
TrailingTriviaOption — controls where the end of the replaced range falls:
| Value | Behavior |
|---|---|
Exclude | End at getEnd(), no trivia |
ExcludeWhitespace | Strip comments but not whitespace |
Include | Extend past trailing trivia and line breaks |
Sources: src/services/textChanges.ts213-241 src/services/textChanges.ts351-465
Suggestion diagnostics are a second category of diagnostics, separate from errors and warnings, that signal automated transformations without flagging a problem. Editors display them as lightbulbs rather than squiggles.
The entry point is computeSuggestionDiagnostics in src/services/suggestionDiagnostics.ts:
Sources: src/services/suggestionDiagnostics.ts68-141
| Diagnostic | Condition |
|---|---|
This_may_be_converted_to_an_async_function | Non-async function returns a Promise with .then/.catch chains |
File_is_a_CommonJS_module_it_may_be_converted_to_an_ES_module | CommonJS file in an ESM-enabled program |
require_call_may_be_converted_to_an_import | Top-level const x = require(...) |
JSDoc_typedef_may_be_converted_to_TypeScript_type | JSDoc @typedef that can become a TS type alias |
Import_may_be_converted_to_a_default_import | Namespace import of a CJS-export-equals module |
This_constructor_function_may_be_converted_to_a_class_declaration | JS constructor function with prototype members |
The function isConvertibleFunction src/services/suggestionDiagnostics.ts189-195 checks three conditions before emitting the async suggestion:
async.Block.The function isFixablePromiseHandler src/services/suggestionDiagnostics.ts219-239 validates that the entire .then/.catch/.finally chain can actually be transformed.
Sources: src/services/suggestionDiagnostics.ts68-320
Every fix file in src/services/codefixes/ follows the same pattern:
Sources: src/services/codefixes/fixExtendsInterfaceBecomesImplements.ts19-37 src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts26-51
File: src/services/codefixes/fixAddMissingMember.ts
Error codes handled: Property_0_does_not_exist_on_type_1, Property_0_is_missing_in_type_1_but_required_in_type_2, Type_0_is_missing_the_following_properties_from_type_1, and several others.
The fix identifies the kind of missing member via the Info discriminated union:
InfoKind | Scenario | Action |
|---|---|---|
TypeLikeDeclaration | Missing property/method on class or interface | Add property or method declaration |
Enum | Missing enum member | Add enum member |
Function | Calling an undefined function | Add function declaration |
ObjectLiteral | Object literal missing required properties | Add property assignments |
JsxAttributes | JSX element missing required attributes | Add JSX attributes |
Signature | Identifier typed as a call signature | Add matching function |
The getInfo function src/services/codefixes/fixAddMissingMember.ts304-413 inspects the token at the diagnostic position and determines which InfoKind applies.
For TypeScript files, addPropertyDeclaration uses checker.typeToTypeNode to synthesize a typed property. For JS files, addMissingMemberInJs initializes the property in the constructor body or as a static assignment.
The fix reuses createMissingMemberNodes from src/services/codefixes/helpers.ts to generate class member nodes for all missing symbols, which is also used by fixClassIncorrectlyImplementsInterface and fixClassDoesntImplementInheritedAbstractMember.
Sources: src/services/codefixes/fixAddMissingMember.ts133-410 src/services/codefixes/helpers.ts129-144
File: src/services/codefixes/inferFromUsage.ts
Fix ID: "inferFromUsage"
Error codes handled: All implicit any diagnostics — for variables, parameters, properties, get/set accessors, and this.
The fix works in two steps:
FindAllReferences.getReferenceEntriesForNode to find every usage site of the identifier.For JS files, the fix writes JSDoc tags (@type, @param, @returns) instead of inline TypeScript annotations.
The annotate function src/services/codefixes/inferFromUsage.ts414-430 calls tryInsertTypeAnnotation on the ChangeTracker. If the inferred type contains an ImportType node that needs auto-import, tryReplaceImportTypeNodeWithAutoImport queues the import via an ImportAdder.
Sources: src/services/codefixes/inferFromUsage.ts114-445
File: src/services/codefixes/convertToAsyncFunction.ts
Fix ID: "convertToAsyncFunction"
Triggered by: Suggestion diagnostic This_may_be_converted_to_an_async_function
This fix rewrites a Promise-chained function into an async/await function. The high-level steps are:
The Transformer object src/services/codefixes/convertToAsyncFunction.ts124-129 carries:
checker — the type checkersynthNamesMap — maps symbol IDs to synthesized SynthIdentifier namessetOfExpressionsToReturn — promise expressions that should not be saved to a variableisInJSFile — whether to omit type annotationsSynthIdentifier and SynthBindingPattern src/services/codefixes/convertToAsyncFunction.ts101-122 are synthetic name representations used to track which variable names have been declared during the transformation and to handle destructured callback arguments.
The transformExpression function src/services/codefixes/convertToAsyncFunction.ts387-408 dispatches to transformThen, transformCatch, or transformFinally depending on the call shape. .catch and .finally continuations are wrapped in try/catch blocks.
Sources: src/services/codefixes/convertToAsyncFunction.ts87-194 src/services/codefixes/convertToAsyncFunction.ts387-408
While the source for this fix is not listed in the provided files, it follows the standard pattern: registered for the error TS2304 and related "is declared but never read" codes, it either removes the identifier, prefixes it with _, or removes the entire declaration depending on context. It uses ChangeTracker.deleteNode or ChangeTracker.replaceNodeWithText.
File: src/services/codefixes/helpers.ts
createMissingMemberNodes src/services/codefixes/helpers.ts129-144 filters a list of symbols to those absent from a class's own members, then calls addNewNodeForMemberSymbol for each. addNewNodeForMemberSymbol src/services/codefixes/helpers.ts176-373 generates the appropriate AST node based on the symbol's declaration kind:
| Declaration Kind | Generated Node |
|---|---|
PropertySignature / PropertyDeclaration | factory.createPropertyDeclaration |
GetAccessor / SetAccessor | factory.createGetAccessorDeclaration / factory.createSetAccessorDeclaration |
MethodSignature / MethodDeclaration | Method declaration(s) via createMethodImplementingSignatures or createSignatureDeclarationFromSignature |
If an ImportAdder is provided, types containing ImportType nodes are rewritten to use identifier references and the required imports are queued.
Sources: src/services/codefixes/helpers.ts129-374
createImportAdder produces an object that queues import statements needed by generated type annotations. After all edits are enqueued on the ChangeTracker, importAdder.writeFixes(changes) emits the import declarations as additional changes.
Sources: src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts104-106
| Function | Purpose |
|---|---|
createCodeFixAction(fixName, changes, description, fixId, fixAllDescription) | Creates an action with a "fix all" variant |
createCodeFixActionWithoutFixAll(fixName, changes, description) | Creates a one-off action with no "fix all" |
Sources: src/services/codefixes/fixAddMissingMember.ts168-172 src/services/codefixes/disableJsDiagnostics.ts43-55
The diagram below maps the major code fix framework concepts to their concrete code locations:
Sources: src/services/textChanges.ts493-510 src/services/codefixes/fixAddMissingMember.ts133-251 src/services/suggestionDiagnostics.ts68-320
| Fix ID | File | Error Codes |
|---|---|---|
fixMissingMember | fixAddMissingMember.ts | 2339, 2345, 2741, 2739, 2740, ... |
inferFromUsage | inferFromUsage.ts | 7005, 7006, 7008, 7010, 7019, ... |
convertToAsyncFunction | convertToAsyncFunction.ts | 80006 (suggestion) |
fixClassIncorrectlyImplementsInterface | fixClassIncorrectlyImplementsInterface.ts | 2420, 2424 |
fixClassDoesntImplementInheritedAbstractMember | fixClassDoesntImplementInheritedAbstractMember.ts | 2515, 2653, ... |
extendsInterfaceBecomesImplements | fixExtendsInterfaceBecomesImplements.ts | 2689 |
classSuperMustPrecedeThisAccess | fixClassSuperMustPrecedeThisAccess.ts | 17009 |
fixAwaitInSyncFunction | fixAwaitInSyncFunction.ts | 1308, 1329, 1338, 2727 |
forgottenThisPropertyAccess | fixForgottenThisPropertyAccess.ts | 2304, 18004, ... |
constructorForDerivedNeedSuperCall | fixConstructorForDerivedNeedSuperCall.ts | 2377 |
disableJsDiagnostics | disableJsDiagnostics.ts | (all error codes, JS-only) |
useDefaultImport | useDefaultImport.ts | 80003 (suggestion) |
convertToTypeOnlyExport | convertToTypeOnlyExport.ts | 1205 |
fixInvalidImportSyntax | fixInvalidImportSyntax.ts | 2349, 2351, 2322, ... |
jdocTypes | fixJSDocTypes.ts | 8020, 17019 |
Sources: src/services/codefixes/fixAddMissingMember.ts138-147 src/services/codefixes/inferFromUsage.ts115-159 src/services/codefixes/convertToAsyncFunction.ts87-88 src/services/codefixes/disableJsDiagnostics.ts25-31
Code fix tests are written as Fourslash tests in tests/cases/fourslash/. They use:
verify.codeFix({ description, newFileContent }) — assert a single fix produces the expected output.verify.codeFixAll({ fixId, fixAllDescription, newFileContent }) — assert the "fix all" variant.verify.getSuggestionDiagnostics([{ message, code, range }]) — assert suggestion diagnostics at a location.Baseline output for complex transformations (like convertToAsyncFunction) is stored under tests/baselines/reference/convertToAsyncFunction/, with // ==ORIGINAL== and // ==ASYNC FUNCTION::...== sections showing before and after.
Sources: tests/cases/fourslash/codeFixUseDefaultImport.ts30-48 tests/cases/fourslash/codeFixUseDefaultImport_all.ts13-21 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_basicWithComments.ts1-18
Refresh this wiki
This wiki was recently refreshed. Please wait 4 days to refresh again.