Skip to content

Commit

Permalink
Analyzer to enforce classes to be either [Virtual], abstract, or seal…
Browse files Browse the repository at this point in the history
…ed. (space-wizards#2469)

Co-authored-by: metalgearsloth <[email protected]>
Co-authored-by: metalgearsloth <[email protected]>
  • Loading branch information
3 people authored Feb 5, 2022
1 parent ec768c5 commit de4d255
Show file tree
Hide file tree
Showing 593 changed files with 1,165 additions and 829 deletions.
16 changes: 10 additions & 6 deletions Robust.Analyzers/Diagnostics.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
using Microsoft.CodeAnalysis;

namespace Robust.Generators
namespace Robust.Analyzers;

public static class Diagnostics
{
public static class Diagnostics
{
public static SuppressionDescriptor MeansImplicitAssignment =>
new SuppressionDescriptor("RADC1000", "CS0649", "Marked as implicitly assigned.");
}
public const string IdExplicitInterface = "RA0000";
public const string IdSerializable = "RA0001";
public const string IdFriend = "RA0002";
public const string IdExplicitVirtual = "RA0003";

public static SuppressionDescriptor MeansImplicitAssignment =>
new SuppressionDescriptor("RADC1000", "CS0649", "Marked as implicitly assigned.");
}
16 changes: 8 additions & 8 deletions Robust.Analyzers/ExplicitInterfaceAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ public class ExplicitInterfaceAnalyzer : DiagnosticAnalyzer
SyntaxKind.OverrideKeyword
};

public const string DiagnosticId = "RA0000";

private const string Title = "No explicit interface specified";
private const string MessageFormat = "No explicit interface specified";
private const string Description = "Make sure to specify the interface in your method-declaration.";
private const string Category = "Usage";

[SuppressMessage("ReSharper", "RS2008")] private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description);
[SuppressMessage("ReSharper", "RS2008")] private static readonly DiagnosticDescriptor Rule = new(
Diagnostics.IdExplicitInterface,
"No explicit interface specified",
"No explicit interface specified",
"Usage",
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
description: "Make sure to specify the interface in your method-declaration.");

private const string RequiresExplicitImplementationAttributeMetadataName =
"Robust.Shared.Analyzers.RequiresExplicitImplementationAttribute";
Expand Down
176 changes: 176 additions & 0 deletions Robust.Analyzers/ExplicitVirtualAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;

namespace Robust.Analyzers;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class ExplicitVirtualAnalyzer : DiagnosticAnalyzer
{
internal const string Attribute = "Robust.Shared.Analyzers.VirtualAttribute";

[SuppressMessage("ReSharper", "RS2008")]
private static readonly DiagnosticDescriptor Rule = 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 override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);

public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.ClassDeclaration);
}

private static bool HasAttribute(INamedTypeSymbol namedTypeSymbol, INamedTypeSymbol attrSymbol)
{
return namedTypeSymbol.GetAttributes()
.Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, attrSymbol));
}

private static void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
var attrSymbol = context.Compilation.GetTypeByMetadataName(Attribute);
var classDecl = (ClassDeclarationSyntax)context.Node;
var classSymbol = context.SemanticModel.GetDeclaredSymbol(classDecl);
if (classSymbol == null)
return;

if (classSymbol.IsSealed || classSymbol.IsAbstract || classSymbol.IsStatic)
return;

if (HasAttribute(classSymbol, attrSymbol))
return;

var diag = Diagnostic.Create(Rule, 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);
}
*/
16 changes: 8 additions & 8 deletions Robust.Analyzers/FriendAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ public class FriendAnalyzer : DiagnosticAnalyzer
{
const string FriendAttribute = "Robust.Shared.Analyzers.FriendAttribute";

public const string DiagnosticId = "RA0002";

private const string Title = "Tried to access friend-only member";
private const string MessageFormat = "Tried to access member \"{0}\" in class \"{1}\" which can only be accessed by friend classes";
private const string Description = "Make sure to specify the accessing class in the friends attribute.";
private const string Category = "Usage";

[SuppressMessage("ReSharper", "RS2008")]
private static readonly DiagnosticDescriptor Rule = new (DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Error, true, Description);
private static readonly DiagnosticDescriptor Rule = new (
Diagnostics.IdFriend,
"Tried to access friend-only member",
"Tried to access member \"{0}\" in class \"{1}\" which can only be accessed by friend classes",
"Usage",
DiagnosticSeverity.Error,
true,
"Make sure to specify the accessing class in the friends attribute.");

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

Expand Down
2 changes: 0 additions & 2 deletions Robust.Analyzers/MeansImplicitAssigmentSuppressor.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
using System;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Robust.Generators;

namespace Robust.Analyzers
{
Expand Down
2 changes: 1 addition & 1 deletion Robust.Analyzers/Robust.Analyzers.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>9</LangVersion>
<LangVersion>10</LangVersion>
</PropertyGroup>

<ItemGroup>
Expand Down
17 changes: 10 additions & 7 deletions Robust.Analyzers/SerializableAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,21 @@ namespace Robust.Analyzers
public class SerializableAnalyzer : DiagnosticAnalyzer
{
// Metadata of the analyzer
public const string DiagnosticId = "RA0001";

// You could use LocalizedString but it's a little more complicated for this sample
private const string Title = "Class not marked as (Net)Serializable";
private const string MessageFormat = "Class not marked as (Net)Serializable";
private const string Description = "The class should be marked as (Net)Serializable.";
private const string Category = "Usage";

private const string RequiresSerializableAttributeMetadataName = "Robust.Shared.Analyzers.RequiresSerializableAttribute";
private const string SerializableAttributeMetadataName = "System.SerializableAttribute";
private const string NetSerializableAttributeMetadataName = "Robust.Shared.Serialization.NetSerializableAttribute";

[SuppressMessage("ReSharper", "RS2008")] private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description);
[SuppressMessage("ReSharper", "RS2008")] private static readonly DiagnosticDescriptor Rule = new(
Diagnostics.IdSerializable,
"Class not marked as (Net)Serializable",
"Class not marked as (Net)Serializable",
"Usage",
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
description: "The class should be marked as (Net)Serializable.");

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

Expand Down Expand Up @@ -139,7 +141,8 @@ private async Task<Document> FixAsync(Document document, ClassDeclarationSyntax
return document.WithSyntaxRoot(root);
}

public sealed override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(SerializableAnalyzer.DiagnosticId);
public sealed override ImmutableArray<string> FixableDiagnosticIds
=> ImmutableArray.Create(Diagnostics.IdSerializable);

public override FixAllProvider GetFixAllProvider()
{
Expand Down
2 changes: 2 additions & 0 deletions Robust.Benchmarks/NumericsHelpers/AddBenchmark.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using BenchmarkDotNet.Attributes;
using Robust.Shared.Analyzers;

namespace Robust.Benchmarks.NumericsHelpers
{
[Virtual]
public class AddBenchmark
{
[Params(32, 128)]
Expand Down
2 changes: 1 addition & 1 deletion Robust.Benchmarks/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Robust.Benchmarks
{
internal class Program
internal static class Program
{
// --allCategories=ctg1,ctg2
// --anyCategories=ctg1,ctg2
Expand Down
2 changes: 1 addition & 1 deletion Robust.Benchmarks/Serialization/BenchmarkIntSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

namespace Robust.Benchmarks.Serialization
{
public class BenchmarkIntSerializer : ITypeSerializer<int, ValueDataNode>
public sealed class BenchmarkIntSerializer : ITypeSerializer<int, ValueDataNode>
{
public ValidationNode Validate(ISerializationManager serializationManager, ValueDataNode node,
IDependencyCollection dependencies, ISerializationContext? context = null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Linq;
using BenchmarkDotNet.Attributes;
using Robust.Benchmarks.Serialization.Definitions;
using Robust.Shared.Analyzers;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Mapping;
Expand All @@ -13,6 +14,7 @@
namespace Robust.Benchmarks.Serialization.Copy
{
[MemoryDiagnoser]
[Virtual]
public class SerializationCopyBenchmark : SerializationBenchmark
{
public SerializationCopyBenchmark()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace Robust.Benchmarks.Serialization.Definitions
{
public class BenchmarkFlags
public sealed class BenchmarkFlags
{
public const int Zero = 1 << 0;
public const int ThirtyOne = 1 << 31;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Analyzers;
using Robust.Shared.Serialization.Manager.Attributes;

namespace Robust.Benchmarks.Serialization.Definitions
{
[DataDefinition]
[Virtual]
public class DataDefinitionWithString
{
[DataField("string")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace Robust.Benchmarks.Serialization.Definitions
/// Taken from content.
/// </summary>
[Prototype("seed")]
public class SeedDataDefinition : IPrototype
public sealed class SeedDataDefinition : IPrototype
{
public const string Prototype = @"
- type: seed
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using BenchmarkDotNet.Attributes;
using Robust.Shared.Analyzers;
using Robust.Shared.Serialization.Manager;

namespace Robust.Benchmarks.Serialization.Initialize
{
[MemoryDiagnoser]
[Virtual]
public class SerializationInitializeBenchmark : SerializationBenchmark
{
[IterationCleanup]
Expand Down
Loading

0 comments on commit de4d255

Please sign in to comment.