mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-06-09 10:06:34 +02:00
8508801670
* Add exclusivity check for virtual + analyzer test * Cleanup --------- Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com>
200 lines
7.5 KiB
C#
200 lines
7.5 KiB
C#
using System.Collections.Immutable;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using Microsoft.CodeAnalysis;
|
|
using Microsoft.CodeAnalysis.CSharp;
|
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
|
using Microsoft.CodeAnalysis.Diagnostics;
|
|
using Robust.Roslyn.Shared;
|
|
|
|
namespace Robust.Analyzers;
|
|
|
|
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
|
public sealed class ExplicitVirtualAnalyzer : DiagnosticAnalyzer
|
|
{
|
|
internal const string Attribute = "Robust.Shared.Analyzers.VirtualAttribute";
|
|
|
|
[SuppressMessage("ReSharper", "RS2008")]
|
|
public static readonly DiagnosticDescriptor ExplicitVirtualRule = new(
|
|
Diagnostics.IdExplicitVirtual,
|
|
"Class must be explicitly marked as [Virtual], abstract, static, or sealed",
|
|
"Class must be explicitly marked as [Virtual], abstract, static, or sealed",
|
|
"Usage",
|
|
DiagnosticSeverity.Warning,
|
|
isEnabledByDefault: true,
|
|
description: "Class must be explicitly marked as [Virtual], abstract, static, or sealed.");
|
|
|
|
public static readonly DiagnosticDescriptor ExclusiveRule = new(
|
|
Diagnostics.IdExclusiveVirtual,
|
|
"A class marked as [Virtual] cannot be abstract, static, or sealed",
|
|
"A class marked as [Virtual] cannot be abstract, static, or sealed",
|
|
"Usage",
|
|
DiagnosticSeverity.Error,
|
|
isEnabledByDefault: true,
|
|
description: "A class marked as [Virtual] cannot be abstract, static, or sealed.");
|
|
|
|
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
|
|
[
|
|
ExplicitVirtualRule,
|
|
ExclusiveRule,
|
|
];
|
|
|
|
public override void Initialize(AnalysisContext context)
|
|
{
|
|
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
|
|
context.EnableConcurrentExecution();
|
|
context.RegisterCompilationStartAction(ctx =>
|
|
{
|
|
if (ctx.Compilation.GetTypeByMetadataName(Attribute) is not INamedTypeSymbol attrSymbol)
|
|
return;
|
|
|
|
ctx.RegisterSyntaxNodeAction(nodeContext => AnalyzeNode(nodeContext, attrSymbol), SyntaxKind.ClassDeclaration);
|
|
});
|
|
}
|
|
|
|
private static void AnalyzeNode(SyntaxNodeAnalysisContext context, INamedTypeSymbol attrSymbol)
|
|
{
|
|
var classDecl = (ClassDeclarationSyntax)context.Node;
|
|
var classSymbol = context.SemanticModel.GetDeclaredSymbol(classDecl);
|
|
if (classSymbol == null)
|
|
return;
|
|
|
|
var hasKeyword = classSymbol.IsSealed || classSymbol.IsAbstract || classSymbol.IsStatic;
|
|
var hasAttribute = AttributeHelper.HasAttribute(classSymbol, attrSymbol, out _);
|
|
|
|
if (hasAttribute && hasKeyword)
|
|
{
|
|
// Having both [Virtual] and sealed/abstract/static doesn't make sense.
|
|
context.ReportDiagnostic(Diagnostic.Create(
|
|
ExclusiveRule,
|
|
classDecl.Keyword.GetLocation()
|
|
));
|
|
}
|
|
|
|
// Having just [Virtual] or sealed/abstract/static is fine.
|
|
if (hasKeyword || hasAttribute)
|
|
return;
|
|
|
|
// Having neither is bad.
|
|
var diag = Diagnostic.Create(ExplicitVirtualRule, classDecl.Keyword.GetLocation());
|
|
context.ReportDiagnostic(diag);
|
|
}
|
|
}
|
|
|
|
// Doesn't work as I'd hoped: Roslyn doesn't provide an API for global usings and I can't get batch changes to work.
|
|
/*
|
|
[ExportCodeFixProvider(LanguageNames.CSharp)]
|
|
public sealed class ExplicitVirtualCodeFixProvider : CodeFixProvider
|
|
{
|
|
private const string TitleSealed = "Annotate class as sealed.";
|
|
private const string TitleVirtual = "Annotate class as [Virtual].";
|
|
private const string TitleAbstract = "Annotate class as abstract.";
|
|
private const string TitleStatic = "Annotate class as static.";
|
|
|
|
public override async Task RegisterCodeFixesAsync(CodeFixContext context)
|
|
{
|
|
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken);
|
|
|
|
foreach (var diagnostic in context.Diagnostics)
|
|
{
|
|
var span = diagnostic.Location.SourceSpan;
|
|
var classDecl = root.FindToken(span.Start).Parent.AncestorsAndSelf().OfType<ClassDeclarationSyntax>()
|
|
.First();
|
|
|
|
context.RegisterCodeFix(
|
|
CodeAction.Create(
|
|
TitleVirtual,
|
|
c => FixVirtualAsync(context.Document, classDecl, c),
|
|
TitleVirtual),
|
|
diagnostic);
|
|
|
|
context.RegisterCodeFix(
|
|
CodeAction.Create(
|
|
TitleStatic,
|
|
c => FixStaticAsync(context.Document, classDecl, c),
|
|
TitleStatic),
|
|
diagnostic);
|
|
|
|
context.RegisterCodeFix(
|
|
CodeAction.Create(
|
|
TitleSealed,
|
|
c => FixSealedAsync(context.Document, classDecl, c),
|
|
TitleSealed),
|
|
diagnostic);
|
|
|
|
context.RegisterCodeFix(
|
|
CodeAction.Create(
|
|
TitleAbstract,
|
|
c => FixAbstractAsync(context.Document, classDecl, c),
|
|
TitleAbstract),
|
|
diagnostic);
|
|
}
|
|
}
|
|
|
|
private async Task<Document> FixVirtualAsync(
|
|
Document document,
|
|
ClassDeclarationSyntax classDecl,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var ns = "Robust.Shared.Analyzers";
|
|
var attrib = SyntaxFactory.Attribute(SyntaxFactory.ParseName("Virtual"));
|
|
|
|
var newClassDecl = classDecl.AddAttributeLists(
|
|
SyntaxFactory.AttributeList(SyntaxFactory.SeparatedList(new[] { attrib })));
|
|
|
|
var root = (CompilationUnitSyntax)await document.GetSyntaxRootAsync(cancellationToken);
|
|
root = root.ReplaceNode(classDecl, newClassDecl);
|
|
|
|
var options = await document.GetOptionsAsync(cancellationToken);
|
|
|
|
if (root.Usings.All(u => u.Name.ToString() != ns))
|
|
{
|
|
root = root.AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(ns)));
|
|
}
|
|
|
|
return document.WithSyntaxRoot(root);
|
|
}
|
|
|
|
private async Task<Document> FixStaticAsync(
|
|
Document document,
|
|
ClassDeclarationSyntax classDecl,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var newClassDecl = classDecl.AddModifiers(SyntaxFactory.Token(SyntaxKind.StaticKeyword));
|
|
|
|
var root = (CompilationUnitSyntax)await document.GetSyntaxRootAsync(cancellationToken);
|
|
root = root.ReplaceNode(classDecl, newClassDecl);
|
|
|
|
return document.WithSyntaxRoot(root);
|
|
}
|
|
|
|
private async Task<Document> FixAbstractAsync(
|
|
Document document,
|
|
ClassDeclarationSyntax classDecl,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var newClassDecl = classDecl.AddModifiers(SyntaxFactory.Token(SyntaxKind.AbstractKeyword));
|
|
|
|
var root = (CompilationUnitSyntax)await document.GetSyntaxRootAsync(cancellationToken);
|
|
root = root.ReplaceNode(classDecl, newClassDecl);
|
|
|
|
return document.WithSyntaxRoot(root);
|
|
}
|
|
|
|
private async Task<Document> FixSealedAsync(
|
|
Document document,
|
|
ClassDeclarationSyntax classDecl,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var newClassDecl = classDecl.AddModifiers(SyntaxFactory.Token(SyntaxKind.SealedKeyword));
|
|
|
|
var root = (CompilationUnitSyntax)await document.GetSyntaxRootAsync(cancellationToken);
|
|
root = root.ReplaceNode(classDecl, newClassDecl);
|
|
|
|
return document.WithSyntaxRoot(root);
|
|
}
|
|
|
|
public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;
|
|
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(Diagnostics.IdExplicitVirtual);
|
|
}
|
|
*/
|