C# Codefixes: Minimum Requirement That Codefix Is Displayed

C# Codefixes: Minimum Requirement That Codefix Is Displayed

I recently started with CodeAnalyzers and CodeFixes.

Of course the template coming with the sdk () works. But when I move on, the codefix does not show up anymore on debugging.

When walking through the diagnostic is created as it is supposed to be. BUT: The codefix (Strg+.) isnt shown anymore. I took care of Diagnostic-Id, equivalence-key, ... but cannot figure out, why I dont get my codefix shown anymore. So what are the minimum "requirements" for a codefix to be shown for a diagnostic?


Here is the code of the analyzer:

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class VirtualMemberAnalyzer : DiagnosticAnalyzer
{
    public const string PublicVirtualMethod_DiagnosticId = "PublicVirtualMethod";

    private const string Category = "Naming";

    private static readonly DiagnosticDescriptor PublicMethodVirtualRule = new DiagnosticDescriptor(
        PublicVirtualMethod_DiagnosticId,
        "public methode not virtual",
        "every public methode must be virtual",
        Category,
        DiagnosticSeverity.Error,
        true);

    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
        ImmutableArray.Create(PublicMethodVirtualRule);

    public override void Initialize(AnalysisContext context)
    {
        context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType);
    }

    private static void AnalyzeSymbol(SymbolAnalysisContext context)
    {
        var namedTypeSymbol = (INamedTypeSymbol)context.Symbol;

        foreach (var methodSymbol in namedTypeSymbol.GetMembers().OfType<IMethodSymbol>())
        {
            if (methodSymbol.MethodKind == MethodKind.Ordinary && !methodSymbol.IsStatic && !methodSymbol.IsVirtual && methodSymbol.DeclaredAccessibility == Accessibility.Public)
            {
                var diagnostic = Diagnostic.Create(
                    PublicMethodVirtualRule,
                    methodSymbol.Locations[0],
                    methodSymbol.Name);
                context.ReportDiagnostic(diagnostic);
            }
        }
    }
}

And of the Codefix-Provider:

[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(VirtualMemberAnalyzersCodeFixProvider)), Shared]
public class VirtualMemberAnalyzersCodeFixProvider : CodeFixProvider {
    private const string title_property = "Make property virtual";
    private const string title_method = "Make method virtual";

    public sealed override ImmutableArray<string> FixableDiagnosticIds {
        get {
            return ImmutableArray.Create(VirtualMemberAnalyzer.PublicVirtualMethod_DiagnosticId);
        }
    }

    public sealed override FixAllProvider GetFixAllProvider() {
        return WellKnownFixAllProviders.BatchFixer;
    }

    public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) {
        var root = await context.Document.GetSyntaxRootAsync();

        var diagnostic = context.Diagnostics.First();
        var diagnosticSpan = diagnostic.Location.SourceSpan;

        var methodDeclarations = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType<MethodDeclarationSyntax>().FirstOrDefault();

        if (methodDeclarations != null) {
            CodeAction codeAction = CodeAction.Create(title_method, c => MakeVirtual(context.Document, methodDeclarations, c), equivalenceKey: title_method);
            context.RegisterCodeFix(codeAction, diagnostic);
        }
    }

    private async Task<Document> MakeVirtual(Document document, MethodDeclarationSyntax memberDeclaration, CancellationToken cancellationToken)
    {
        SyntaxTokenList memberDeclarationModifiers = memberDeclaration.Modifiers;
        memberDeclarationModifiers.Add(SyntaxFactory.Token(SyntaxKind.VirtualKeyword));
        MethodDeclarationSyntax methodDeclarationSyntax = memberDeclaration.WithModifiers(memberDeclarationModifiers);

        var oldRoot = await document.GetSyntaxRootAsync(cancellationToken);
        var newRoot = oldRoot.ReplaceNode(memberDeclaration, methodDeclarationSyntax);

        return document.WithSyntaxRoot(newRoot);
    }
}

1 Answer

The memberDeclarationModifiers.Add(SyntaxFactory.Token(SyntaxKind.VirtualKeyword)); returns a new SyntaxTokenList. You're not using the new created list. Remember that SyntaxTrees are Immutable in Roslyn . Every change in the tree will create a new tree. Here is your codefix for your roslyn codefix :)

private async Task<Document> MakeVirtual(Document document, MethodDeclarationSyntax memberDeclaration, CancellationToken cancellationToken)
{
    var methodDeclarationSyntax =
        memberDeclaration.WithModifiers(
            memberDeclaration.Modifiers.Add( SyntaxFactory.Token( SyntaxKind.VirtualKeyword ) ) );

    var oldRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait( false );
    var newRoot = oldRoot.ReplaceNode(memberDeclaration, methodDeclarationSyntax);

    return document.WithSyntaxRoot(newRoot);
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

David Miller
Author

David Miller

David Miller brings 15 years of experience in global economics, personal finance strategy, and market dynamics. He specializes in turning complex economic trends into actionable insights for everyday readers.