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