0

I have created an Analyzer rule to throw a Warning when a class has not been inherited but is not marked as sealed. The analyzer itself is working as expected, but I am unable to get the CodeFix to work. I am not even presented with the option to apply the fix when I hover over the warning.

SealedClassAnalyzer.cs:

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;

namespace SampleProject.CodeAnalysis {
    [DiagnosticAnalyzer(LanguageNames.CSharp)]
    public class SealedClassAnalyzer : DiagnosticAnalyzer {
        public const string DiagnosticId = "SealedClassAnalyzer";

        private static readonly string Title = "Uninherited class must be sealed";
        private static readonly string MessageFormat = "Class '{0}' is not inherited but not marked as sealed";
        private static readonly string Description = "Classes that are not inherited should be marked as sealed";
        private static readonly string Category = "Test"; 

        private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description);

        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } }

        public override void Initialize(AnalysisContext context) {
            context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
            context.EnableConcurrentExecution();

            context.RegisterSyntaxNodeAction(AnalyzeClassDeclaration, SyntaxKind.ClassDeclaration);

        }

        private void AnalyzeClassDeclaration(SyntaxNodeAnalysisContext context) {
            var classDeclaration = (ClassDeclarationSyntax)context.Node;

            if (!classDeclaration.Modifiers.Any(SyntaxKind.AbstractKeyword) 
                && !classDeclaration.Modifiers.Any(SyntaxKind.SealedKeyword)
                && !IsClassInherited(context, classDeclaration)) {
                var diagnostic = Diagnostic.Create(Rule, classDeclaration.GetLocation(), classDeclaration.Identifier.ValueText);
                context.ReportDiagnostic(diagnostic);
            }
        }

        private bool IsClassInherited(SyntaxNodeAnalysisContext context, ClassDeclarationSyntax classDeclaration) {
            foreach (var syntaxTree in context.Compilation.SyntaxTrees) {
                var root = syntaxTree.GetRoot();
                var descendantNodes = root.DescendantNodes().OfType<SimpleBaseTypeSyntax>();
                if (descendantNodes.Any(node => node.Type.ToString() == classDeclaration.Identifier.ValueText)) {
                    return true;
                }
            }

            return false;
        }
    }
}

SealedClassCodeFixProvider.cs:

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Rename;
using Microsoft.CodeAnalysis.Text;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace SampleProject.CodeAnalysis {
    [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(SealedClassCodeFixProvider)), Shared]
    public class SealedClassCodeFixProvider : CodeFixProvider {
        private const string Title = "Seal class";

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

        public sealed override FixAllProvider GetFixAllProvider() {
            // See https://github.com/dotnet/roslyn/blob/main/docs/analyzers/FixAllProvider.md for more information on Fix All Providers
            return WellKnownFixAllProviders.BatchFixer;
        }

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

            // Get the diagnostic that triggered this fix.
            var diagnostic = context.Diagnostics.First();

            // Get the syntax node for the class declaration that should be sealed.
            var classDeclaration = root.FindNode(diagnostic.Location.SourceSpan).FirstAncestorOrSelf<ClassDeclarationSyntax>();

            // Register a code action to seal the class.
            context.RegisterCodeFix(
                CodeAction.Create(
                    Title,
                    ct => SealClassAsync(context.Document, classDeclaration, ct),
                    Title),
                diagnostic);
        }

        private async Task<Document> SealClassAsync(Document document, ClassDeclarationSyntax classDeclaration, CancellationToken cancellationToken) {
            // Add the sealed keyword to the class declaration.
            var newClassDeclaration = classDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.SealedKeyword));

            // Replace the old class declaration with the new one.
            var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
            var newRoot = root.ReplaceNode(classDeclaration, newClassDeclaration);

            // Return the updated document.
            return document.WithSyntaxRoot(newRoot);
        }
    }
}
user3036519
  • 105
  • 1
  • 11

0 Answers0