Deepstaging.Roslyn.Workspace¶
Code fix infrastructure for Roslyn analyzers using the Workspace API.
What is this?¶
This library provides base classes and utilities for building Roslyn code fix providers. It simplifies the boilerplate of registering code fixes and working with syntax nodes.
Installation¶
Quick Start¶
Define a code fix with attributes¶
[ExportCodeFixProvider(LanguageNames.CSharp)]
[CodeFix("MY001")]
[CodeFix("MY002")] // Can fix multiple diagnostics
public class AddPartialModifierCodeFix : SyntaxCodeFix<TypeDeclarationSyntax>
{
protected override CodeAction? CreateFix(Document document, ValidSyntax<TypeDeclarationSyntax> syntax)
{
return CodeAction.Create(
title: "Add partial modifier",
createChangedDocument: ct => AddPartialModifier(document, syntax.Node, ct),
equivalenceKey: "AddPartial");
}
private async Task<Document> AddPartialModifier(
Document document,
TypeDeclarationSyntax declaration,
CancellationToken ct)
{
var newDeclaration = declaration
.AddModifiers(SyntaxFactory.Token(SyntaxKind.PartialKeyword));
return await document.ReplaceNode(declaration, newDeclaration, ct);
}
}
The base class handles:
- Reading
[CodeFix]attributes to populateFixableDiagnosticIds - Finding the syntax node at the diagnostic location
- Registering the code action with proper error handling
API Reference¶
SyntaxCodeFix\<TSyntax>¶
Abstract base class for code fix providers:
public abstract class SyntaxCodeFix<TSyntax> : CodeFixProvider
where TSyntax : SyntaxNode
{
// Automatically populated from [CodeFix] attributes
public sealed override ImmutableArray<string> FixableDiagnosticIds { get; }
// Override to create your fix
protected abstract CodeAction? CreateFix(
Document document,
ValidSyntax<TSyntax> syntax);
}
CodeFixAttribute¶
Declarative configuration for code fixes:
[CodeFix("MY001")] // The diagnostic ID this code fix handles
[CodeFix("MY002")] // Add multiple attributes for multiple diagnostics
Specialized Base Classes¶
Pre-defined base classes for common syntax types:
// Type declarations
public abstract class ClassCodeFix : SyntaxCodeFix<ClassDeclarationSyntax>;
public abstract class StructCodeFix : SyntaxCodeFix<StructDeclarationSyntax>;
public abstract class InterfaceCodeFix : SyntaxCodeFix<InterfaceDeclarationSyntax>;
public abstract class RecordCodeFix : SyntaxCodeFix<RecordDeclarationSyntax>;
public abstract class EnumCodeFix : SyntaxCodeFix<EnumDeclarationSyntax>;
// Member declarations
public abstract class MethodCodeFix : SyntaxCodeFix<MethodDeclarationSyntax>;
public abstract class PropertyCodeFix : SyntaxCodeFix<PropertyDeclarationSyntax>;
public abstract class FieldCodeFix : SyntaxCodeFix<FieldDeclarationSyntax>;
public abstract class ConstructorCodeFix : SyntaxCodeFix<ConstructorDeclarationSyntax>;
public abstract class EventCodeFix : SyntaxCodeFix<EventDeclarationSyntax>;
public abstract class ParameterCodeFix : SyntaxCodeFix<ParameterSyntax>;
Document Extensions¶
Convenient methods for document manipulation:
// Replace a syntax node (async)
Document newDoc = await document.ReplaceNode(oldNode, newNode, cancellationToken);
// Replace a syntax node (sync, when root is already available)
Document newDoc = document.ReplaceNode(root, oldNode, newNode);
CodeFixContext Extensions¶
Helper methods for finding syntax nodes:
// Generic find
OptionalSyntax<TSyntax> result = await context.FindDeclaration<TypeDeclarationSyntax>();
// Type-specific helpers
OptionalSyntax<ClassDeclarationSyntax> classDecl = await context.FindClass();
OptionalSyntax<MethodDeclarationSyntax> methodDecl = await context.FindMethod();
OptionalSyntax<PropertyDeclarationSyntax> propDecl = await context.FindProperty();
// Also: FindStruct(), FindInterface(), FindRecord(), FindEnum(),
// FindField(), FindConstructor(), FindEvent(), FindParameter()
Projections¶
ValidSyntax\<T>¶
A wrapper guaranteeing a non-null syntax node:
protected override CodeAction? CreateFix(Document document, ValidSyntax<TypeDeclarationSyntax> syntax)
{
// syntax.Node is guaranteed non-null
string name = syntax.Node.Identifier.Text;
// Access the underlying node
TypeDeclarationSyntax node = syntax.Node;
}
Comparison¶
Without this library:
public class MyCodeFix : CodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds =>
ImmutableArray.Create("MY001", "MY002");
public override FixAllProvider GetFixAllProvider() =>
WellKnownFixAllProviders.BatchFixer;
public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken);
if (root == null) return;
var diagnostic = context.Diagnostics.First();
var node = root.FindNode(diagnostic.Location.SourceSpan);
if (node.FirstAncestorOrSelf<TypeDeclarationSyntax>() is not { } typeDecl)
return;
var codeAction = CodeAction.Create(...);
context.RegisterCodeFix(codeAction, diagnostic);
}
}
With this library: