Code Fix Pattern
An analyzer identifies a problem. A code fix repairs it. They ship as a pair — same diagnostic ID connects them. With the base classes and pre-built actions, most code fixes are 5-10 lines.
Basic Structure
[CodeFix(AutoNotifyMustBePartialAnalyzer.DiagnosticId)]
public sealed class MakePartialClassCodeFixProvider : ClassCodeFix
{
protected override CodeAction CreateFix(
Document document,
ValidSyntax<ClassDeclarationSyntax> syntax)
{
return document.AddPartialModifierAction(syntax);
}
}
Available Base Classes
| Base Class |
Syntax Type |
Use Case |
ClassCodeFix |
ClassDeclarationSyntax |
Fix class declarations |
StructCodeFix |
StructDeclarationSyntax |
Fix struct declarations |
InterfaceCodeFix |
InterfaceDeclarationSyntax |
Fix interface declarations |
RecordCodeFix |
RecordDeclarationSyntax |
Fix record declarations |
EnumCodeFix |
EnumDeclarationSyntax |
Fix enum declarations |
MethodCodeFix |
MethodDeclarationSyntax |
Fix method declarations |
PropertyCodeFix |
PropertyDeclarationSyntax |
Fix property declarations |
FieldCodeFix |
FieldDeclarationSyntax |
Fix field declarations |
ConstructorCodeFix |
ConstructorDeclarationSyntax |
Fix constructor declarations |
EventCodeFix |
EventDeclarationSyntax |
Fix event declarations |
ParameterCodeFix |
ParameterSyntax |
Fix parameter declarations |
Common Helpers
These extension methods on Document create ready-to-use CodeAction objects:
Type Modifiers
| Helper |
Description |
AddPartialModifierAction |
Add partial modifier to a type |
AddSealedModifierAction |
Add sealed modifier to a type |
AddStaticModifierAction |
Add static modifier to a type |
AddAbstractModifierAction |
Add abstract modifier to a type |
AddReadonlyModifierAction |
Add readonly modifier to a struct |
AddModifierAction |
Add any modifier (generic) |
RemoveModifierAction |
Remove a modifier from a type |
Method Modifiers
| Helper |
Description |
AddAsyncModifierAction |
Add async modifier to a method |
AddVirtualModifierAction |
Add virtual modifier to a method |
AddOverrideModifierAction |
Add override modifier to a method |
AddStaticMethodModifierAction |
Add static modifier to a method |
AddMethodModifierAction |
Add any modifier to a method |
RemoveMethodModifierAction |
Remove a modifier from a method |
Rename Helpers
| Helper |
Description |
RenameMethodAction |
Rename a method |
AddAsyncSuffixAction |
Add 'Async' suffix to method name |
RemoveAsyncSuffixAction |
Remove 'Async' suffix from method name |
RenamePropertyAction |
Rename a property |
RenameFieldAction |
Rename a field |
RenameTypeAction |
Rename a type (class, struct, etc.) |
Field & Property Modifiers
| Helper |
Description |
MakeFieldPrivateAction |
Change field accessibility to private |
AddFieldReadonlyModifierAction |
Add readonly modifier to a field |
AddRequiredModifierAction |
Add required modifier to a property |
MakePropertyInitOnlyAction |
Replace set with init accessor |
Return Type Helpers
| Helper |
Description |
ChangeReturnTypeAction |
Change the return type of a method |
WrapReturnTypeInTaskAction |
Wrap return type in Task<T> |
Structure Helpers
| Helper |
Description |
AddUsingAction |
Add a using directive |
AddBaseTypeAction |
Add a base type to a type declaration |
AddInterfaceAction |
Add an interface implementation |
AddAttributeAction |
Add an attribute to a member |
RemoveAttributeAction |
Remove an attribute from a member |
ReplaceAttributeAction |
Replace one attribute with another (preserves namespace prefix) |
SuppressWithPragmaAction |
Suppress a diagnostic with #pragma warning disable |
Examples
Make Field Private
[CodeFix(FieldMustBePrivateAnalyzer.DiagnosticId)]
public sealed class MakeFieldPrivateCodeFix : FieldCodeFix
{
protected override CodeAction CreateFix(
Document document,
ValidSyntax<FieldDeclarationSyntax> syntax)
{
return document.MakeFieldPrivateAction(syntax);
}
}
Add Sealed Modifier
[CodeFix(EffectsModuleShouldBeSealedAnalyzer.DiagnosticId)]
public sealed class MakeClassSealedCodeFix : ClassCodeFix
{
protected override CodeAction? CreateFix(
Document document,
ValidSyntax<ClassDeclarationSyntax> syntax)
{
return document.AddSealedModifierAction(syntax);
}
}
Make Struct Readonly
[CodeFix(StrongIdShouldBeReadonlyAnalyzer.DiagnosticId)]
public sealed class MakeStructReadonlyCodeFix : StructCodeFix
{
protected override CodeAction? CreateFix(
Document document,
ValidSyntax<StructDeclarationSyntax> syntax)
{
return document.AddModifierAction(syntax, SyntaxKind.ReadOnlyKeyword, "Make struct readonly");
}
}
Add Interface Implementation
[CodeFix(MustImplementDisposableAnalyzer.DiagnosticId)]
public sealed class ImplementDisposableCodeFix : ClassCodeFix
{
protected override CodeAction? CreateFix(
Document document,
ValidSyntax<ClassDeclarationSyntax> syntax)
{
return document.AddInterfaceAction(syntax, "IDisposable", "Implement IDisposable");
}
}
Add Missing Attribute
[CodeFix(MissingSerializableAnalyzer.DiagnosticId)]
public sealed class AddSerializableAttributeCodeFix : ClassCodeFix
{
protected override CodeAction? CreateFix(
Document document,
ValidSyntax<ClassDeclarationSyntax> syntax)
{
return document.AddAttributeAction(syntax, "Serializable", "Add [Serializable] attribute");
}
}
Remove Modifier
[CodeFix(StaticClassCannotHaveInstanceMembersAnalyzer.DiagnosticId)]
public sealed class RemoveStaticModifierCodeFix : ClassCodeFix
{
protected override CodeAction? CreateFix(
Document document,
ValidSyntax<ClassDeclarationSyntax> syntax)
{
return document.RemoveModifierAction(syntax, SyntaxKind.StaticKeyword, "Remove 'static' modifier");
}
}
Add Async Suffix (Method Rename)
[CodeFix(AsyncMethodNamingAnalyzer.DiagnosticId)]
public sealed class AddAsyncSuffixCodeFix : MethodCodeFix
{
protected override CodeAction? CreateFix(
Document document,
ValidSyntax<MethodDeclarationSyntax> syntax)
{
return document.AddAsyncSuffixAction(syntax);
}
}
Rename Method
[CodeFix(MethodNamingConventionAnalyzer.DiagnosticId)]
public sealed class RenameMethodCodeFix : MethodCodeFix
{
protected override CodeAction? CreateFix(
Document document,
ValidSyntax<MethodDeclarationSyntax> syntax)
{
var currentName = syntax.Node.Identifier.Text;
var newName = currentName.ToPascalCase(); // Your naming logic
return document.RenameMethodAction(syntax, newName);
}
}
Change Return Type to Task
[CodeFix(AsyncMethodReturnTypeAnalyzer.DiagnosticId)]
public sealed class WrapReturnTypeInTaskCodeFix : MethodCodeFix
{
protected override CodeAction? CreateFix(
Document document,
ValidSyntax<MethodDeclarationSyntax> syntax)
{
return document.WrapReturnTypeInTaskAction(syntax);
}
}
Make Property Init-Only
[CodeFix(ImmutablePropertyAnalyzer.DiagnosticId)]
public sealed class MakePropertyInitOnlyCodeFix : PropertyCodeFix
{
protected override CodeAction? CreateFix(
Document document,
ValidSyntax<PropertyDeclarationSyntax> syntax)
{
return document.MakePropertyInitOnlyAction(syntax);
}
}
Add Required Modifier
[CodeFix(RequiredPropertyAnalyzer.DiagnosticId)]
public sealed class AddRequiredModifierCodeFix : PropertyCodeFix
{
protected override CodeAction? CreateFix(
Document document,
ValidSyntax<PropertyDeclarationSyntax> syntax)
{
return document.AddRequiredModifierAction(syntax);
}
}
Remove Attribute
[CodeFix(ObsoleteAttributeAnalyzer.DiagnosticId)]
public sealed class RemoveObsoleteAttributeCodeFix : ClassCodeFix
{
protected override CodeAction? CreateFix(
Document document,
ValidSyntax<ClassDeclarationSyntax> syntax)
{
return document.RemoveAttributeAction(syntax, "Obsolete");
}
}
Suppress with Pragma
[CodeFix(LegacyApiUsageAnalyzer.DiagnosticId)]
public sealed class SuppressLegacyApiWarningCodeFix : ClassCodeFix
{
protected override CodeAction? CreateFix(
Document document,
ValidSyntax<ClassDeclarationSyntax> syntax)
{
// Offer suppression as an alternative to fixing
return document.SuppressWithPragmaAction(
Diagnostic.Create(LegacyApiUsageAnalyzer.Rule, syntax.Value.GetLocation()));
}
}
Replace Attribute
[CodeFix(DeprecatedAttributeAnalyzer.DiagnosticId)]
public sealed class ReplaceDeprecatedAttributeCodeFix : ClassCodeFix
{
protected override CodeAction? CreateFix(
Document document,
ValidSyntax<ClassDeclarationSyntax> syntax)
{
return document.ReplaceAttributeAction(
syntax, "EffectsModule", "Capability");
}
}
Project-Level Code Fixes
For fixes that modify the project file (e.g., adding MSBuild properties) or write files to the project directory, use ProjectCodeFix:
[CodeFix(MissingUserSecretsIdAnalyzer.DiagnosticId)]
[ExportCodeFixProvider(LanguageNames.CSharp)]
public sealed class AddUserSecretsIdCodeFix : ProjectCodeFix<INamedTypeSymbol>
{
protected override CodeAction? CreateFix(
Project project,
ValidSymbol<INamedTypeSymbol> symbol,
Diagnostic diagnostic) =>
project.AddProjectPropertyAction("UserSecretsId", Guid.NewGuid().ToString());
}
Available Base Classes
| Base Class |
Use Case |
ProjectCodeFix |
Direct access to Project and Diagnostic |
ProjectCodeFix<TSymbol> |
Automatic symbol resolution at diagnostic location |
Project-Level Actions
| Helper |
Description |
project.AddProjectPropertyAction(name, value) |
Add MSBuild property to first <PropertyGroup> |
project.WriteFileAction(path, content) |
Write a file to the project directory |
project.WriteFilesAction(title, files) |
Write multiple files in a single operation |
project.ModifyXmlFileAction(title, path, action) |
Modify (or create) an XML file |
project.FileActions(title) |
Fluent builder for composing multiple file operations |
Composing Multiple File Operations
Use FileActions to batch writes, conditional writes, JSON merges, and XML modifications into a single code action:
[CodeFix(SchemaAnalyzer.DiagnosticId)]
[ExportCodeFixProvider(LanguageNames.CSharp)]
public sealed class GenerateSchemaCodeFix : ProjectCodeFix<INamedTypeSymbol>
{
protected override CodeAction? CreateFix(
Project project,
ValidSymbol<INamedTypeSymbol> symbol,
Diagnostic diagnostic) =>
project.FileActions("Generate schema files")
.Write($"{symbol.Name}.schema.json", SchemaGenerator.Generate(symbol))
.WriteIfNotExists("appsettings.json", "{}")
.MergeJsonFile("appsettings.json", DefaultSettings.Template)
.AppendLine(".gitignore", "*.local.json")
.ToCodeAction();
}
Managed Props Files
For generators that need a local .props file with predictable defaults, use ManagedPropsFile:
public sealed class MyGeneratorProps : ManagedPropsFile
{
public override string FileName => "mygenerator.props";
protected override void ConfigureDefaults(PropsBuilder builder) =>
builder
.Property("CompilerGeneratedFilesOutputPath", "!generated")
.ItemGroup(items =>
{
items.Remove("Compile", "!generated/**");
items.Include("None", "!generated/**");
});
}
Then use it in a code fix — defaults are ensured automatically:
project.FileActions("Configure generator")
.ModifyPropsFile<MyGeneratorProps>(doc =>
doc.SetPropertyGroup("Generator", pg => pg
.Property("EnableFeature", "true")))
.Write("template.json", content)
.ToCodeAction();
See the Code Fixes docs for full API details.
Custom CodeFix Without Base Class
For more complex scenarios, extend SyntaxCodeFix<T> directly:
[CodeFix(MultipleIssuesAnalyzer.DiagnosticId)]
public sealed class FixMultipleIssuesCodeFix : SyntaxCodeFix<TypeDeclarationSyntax>
{
protected override CodeAction? CreateFix(
Document document,
ValidSyntax<TypeDeclarationSyntax> syntax)
{
return CodeAction.Create(
"Fix all issues",
async ct =>
{
var root = await document.GetSyntaxRootAsync(ct);
// Apply multiple transformations
var newType = syntax.Value
.AddModifiers(SyntaxFactory.Token(SyntaxKind.PartialKeyword))
.AddModifiers(SyntaxFactory.Token(SyntaxKind.SealedKeyword));
var newRoot = root!.ReplaceNode(syntax.Value, newType);
return document.WithSyntaxRoot(newRoot);
});
}
}