Tobias
Tobias

Reputation: 2985

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);
    }
}

Upvotes: 0

Views: 72

Answers (1)

Sievajet
Sievajet

Reputation: 3513

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);
}

Upvotes: 0

Related Questions