forked from space-wizards/RobustToolbox
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Analyzer Bongaloo 2: The Return (space-wizards#1512)
Co-authored-by: Paul <[email protected]>
- Loading branch information
1 parent
91759cd
commit b3976eb
Showing
19 changed files
with
303 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="12.0"> | ||
<ItemGroup> | ||
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\Robust.Analyzers\Robust.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/> | ||
</ItemGroup> | ||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="12.0"> | ||
<Import Project="Robust.Analyzers.targets" /> | ||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
using System.Collections.Generic; | ||
using System.Collections.Immutable; | ||
using System.Diagnostics.CodeAnalysis; | ||
using System.Linq; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CodeActions; | ||
using Microsoft.CodeAnalysis.CodeFixes; | ||
using Microsoft.CodeAnalysis.CSharp; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
using Document = Microsoft.CodeAnalysis.Document; | ||
|
||
namespace Robust.Analyzers | ||
{ | ||
[DiagnosticAnalyzer(LanguageNames.CSharp)] | ||
public class ExplicitInterfaceAnalyzer : DiagnosticAnalyzer | ||
{ | ||
public readonly SyntaxKind[] ExcludedModifiers = | ||
{ | ||
SyntaxKind.VirtualKeyword, | ||
SyntaxKind.AbstractKeyword, | ||
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); | ||
|
||
private const string RequiresExplicitImplementationAttributeMetadataName = | ||
"Robust.Shared.Analyzers.RequiresExplicitImplementationAttribute"; | ||
|
||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule); | ||
|
||
public override void Initialize(AnalysisContext context) | ||
{ | ||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.None); | ||
context.EnableConcurrentExecution(); | ||
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.MethodDeclaration); | ||
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.PropertyDeclaration); | ||
} | ||
|
||
private void AnalyzeNode(SyntaxNodeAnalysisContext context) | ||
{ | ||
ISymbol symbol; | ||
Location location; | ||
switch (context.Node) | ||
{ | ||
//we already have a explicit interface specified, no need to check further | ||
case MethodDeclarationSyntax methodDecl when methodDecl.ExplicitInterfaceSpecifier != null || methodDecl.Modifiers.Any(m => ExcludedModifiers.Contains(m.Kind())): | ||
return; | ||
case PropertyDeclarationSyntax propertyDecl when propertyDecl.ExplicitInterfaceSpecifier != null || propertyDecl.Modifiers.Any(m => ExcludedModifiers.Contains(m.Kind())): | ||
return; | ||
|
||
case MethodDeclarationSyntax methodDecl: | ||
symbol = context.SemanticModel.GetDeclaredSymbol(methodDecl); | ||
location = methodDecl.Identifier.GetLocation(); | ||
break; | ||
case PropertyDeclarationSyntax propertyDecl: | ||
symbol = context.SemanticModel.GetDeclaredSymbol(propertyDecl); | ||
location = propertyDecl.Identifier.GetLocation(); | ||
break; | ||
|
||
default: | ||
return; | ||
} | ||
|
||
var attrSymbol = context.Compilation.GetTypeByMetadataName(RequiresExplicitImplementationAttributeMetadataName); | ||
|
||
var isInterfaceMember = symbol?.ContainingType.AllInterfaces.Any( | ||
i => | ||
i.GetMembers().Any(m => SymbolEqualityComparer.Default.Equals(symbol, symbol.ContainingType.FindImplementationForInterfaceMember(m))) | ||
&& i.GetAttributes().Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, attrSymbol)) | ||
) ?? false; | ||
|
||
if (isInterfaceMember) | ||
{ | ||
//we do not have an explicit interface specified. bad! | ||
var diagnostic = Diagnostic.Create( | ||
Rule, | ||
location); | ||
context.ReportDiagnostic(diagnostic); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>netstandard2.0</TargetFramework> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" PrivateAssets="all" /> | ||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.2" PrivateAssets="all" /> | ||
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="3.8.0" /> | ||
</ItemGroup> | ||
|
||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
using System.Collections.Generic; | ||
using System.Collections.Immutable; | ||
using System.Diagnostics.CodeAnalysis; | ||
using System.Linq; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CodeActions; | ||
using Microsoft.CodeAnalysis.CodeFixes; | ||
using Microsoft.CodeAnalysis.CSharp; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
|
||
namespace Robust.Analyzers | ||
{ | ||
[DiagnosticAnalyzer(LanguageNames.CSharp)] | ||
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); | ||
|
||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule); | ||
|
||
public override void Initialize(AnalysisContext context) | ||
{ | ||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.None); | ||
context.EnableConcurrentExecution(); | ||
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.ClassDeclaration); | ||
} | ||
|
||
private bool Marked(INamedTypeSymbol namedTypeSymbol, INamedTypeSymbol attrSymbol) | ||
{ | ||
if (namedTypeSymbol == null) return false; | ||
if (namedTypeSymbol.GetAttributes() | ||
.Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, attrSymbol))) return true; | ||
return Marked(namedTypeSymbol.BaseType, attrSymbol); | ||
} | ||
|
||
private void AnalyzeNode(SyntaxNodeAnalysisContext context) | ||
{ | ||
var attrSymbol = context.Compilation.GetTypeByMetadataName(RequiresSerializableAttributeMetadataName); | ||
var classDecl = (ClassDeclarationSyntax) context.Node; | ||
var classSymbol = context.SemanticModel.GetDeclaredSymbol(classDecl); | ||
if (classSymbol == null) return; | ||
|
||
if (Marked(classSymbol, attrSymbol)) | ||
{ | ||
var attributes = classSymbol.GetAttributes(); | ||
var serAttr = context.Compilation.GetTypeByMetadataName(SerializableAttributeMetadataName); | ||
var netSerAttr = context.Compilation.GetTypeByMetadataName(NetSerializableAttributeMetadataName); | ||
|
||
var hasSerAttr = attributes.Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, serAttr)); | ||
var hasNetSerAttr = | ||
attributes.Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, netSerAttr)); | ||
|
||
if (!hasSerAttr || !hasNetSerAttr) | ||
{ | ||
var requiredAttributes = new List<string>(); | ||
if(!hasSerAttr) requiredAttributes.Add(SerializableAttributeMetadataName); | ||
if(!hasNetSerAttr) requiredAttributes.Add(NetSerializableAttributeMetadataName); | ||
|
||
context.ReportDiagnostic( | ||
Diagnostic.Create( | ||
Rule, | ||
classDecl.Identifier.GetLocation(), | ||
ImmutableDictionary.CreateRange(new Dictionary<string, string>() | ||
{ | ||
{ | ||
"requiredAttributes", string.Join(",", requiredAttributes) | ||
} | ||
}))); | ||
} | ||
} | ||
} | ||
} | ||
|
||
[ExportCodeFixProvider(LanguageNames.CSharp)] | ||
public class SerializableCodeFixProvider : CodeFixProvider | ||
{ | ||
private const string Title = "Annotate class as (Net)Serializable."; | ||
|
||
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(); | ||
|
||
if(!diagnostic.Properties.TryGetValue("requiredAttributes", out var requiredAttributes)) return; | ||
|
||
context.RegisterCodeFix( | ||
CodeAction.Create( | ||
Title, | ||
c => FixAsync(context.Document, classDecl, requiredAttributes, c), | ||
Title), | ||
diagnostic); | ||
} | ||
} | ||
|
||
private async Task<Document> FixAsync(Document document, ClassDeclarationSyntax classDecl, | ||
string requiredAttributes, CancellationToken cancellationToken) | ||
{ | ||
var attributes = new List<AttributeSyntax>(); | ||
var namespaces = new List<string>(); | ||
foreach (var attribute in requiredAttributes.Split(',')) | ||
{ | ||
var tempSplit = attribute.Split('.'); | ||
namespaces.Add(string.Join(".",tempSplit.Take(tempSplit.Length-1))); | ||
var @class = tempSplit.Last(); | ||
@class = @class.Substring(0, @class.Length - 9); //cut out "Attribute" at the end | ||
attributes.Add(SyntaxFactory.Attribute(SyntaxFactory.ParseName(@class))); | ||
} | ||
|
||
var newClassDecl = | ||
classDecl.AddAttributeLists(SyntaxFactory.AttributeList(SyntaxFactory.SeparatedList(attributes))); | ||
|
||
var root = (CompilationUnitSyntax) await document.GetSyntaxRootAsync(cancellationToken); | ||
root = root.ReplaceNode(classDecl, newClassDecl); | ||
|
||
foreach (var ns in namespaces) | ||
{ | ||
if(root.Usings.Any(u => u.Name.ToString() == ns)) continue; | ||
root = root.AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(ns))); | ||
} | ||
return document.WithSyntaxRoot(root); | ||
} | ||
|
||
public sealed override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(SerializableAnalyzer.DiagnosticId); | ||
|
||
public override FixAllProvider GetFixAllProvider() | ||
{ | ||
return WellKnownFixAllProviders.BatchFixer; | ||
} | ||
|
||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
7 changes: 7 additions & 0 deletions
7
Robust.Shared/Analyzers/RequiresExplicitImplementationAttribute.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
using System; | ||
|
||
namespace Robust.Shared.Analyzers | ||
{ | ||
[AttributeUsage(AttributeTargets.Interface)] | ||
public class RequiresExplicitImplementationAttribute : Attribute { } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
using System; | ||
|
||
namespace Robust.Shared.Analyzers | ||
{ | ||
[AttributeUsage(AttributeTargets.Class)] | ||
public class RequiresSerializableAttribute : Attribute { } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.