-
Notifications
You must be signed in to change notification settings - Fork 2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Interface & interface methods analyzer working so far * changed logic for attribute finding to be consistent with the other analyzers * Analyzer working correctly * Code fixer working * removed sandbox for testing analyzers * Add generic arity and namespace/nesting --------- Co-authored-by: Ledjon Behluli <[email protected]> Co-authored-by: ReubenBond <[email protected]>
- Loading branch information
1 parent
8f73d43
commit f29d7e4
Showing
9 changed files
with
475 additions
and
3 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
156 changes: 156 additions & 0 deletions
156
src/Orleans.Analyzers/GenerateAliasAttributesAnalyzer.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,156 @@ | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CSharp; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
using System.Collections.Generic; | ||
using System.Collections.Immutable; | ||
using System.Linq; | ||
using System.Net.NetworkInformation; | ||
using System.Text; | ||
|
||
namespace Orleans.Analyzers; | ||
|
||
[DiagnosticAnalyzer(LanguageNames.CSharp)] | ||
public class GenerateAliasAttributesAnalyzer : DiagnosticAnalyzer | ||
{ | ||
public const string RuleId = "ORLEANS0010"; | ||
private const string Category = "Usage"; | ||
private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.AddAliasAttributesTitle), Resources.ResourceManager, typeof(Resources)); | ||
private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.AddAliasMessageFormat), Resources.ResourceManager, typeof(Resources)); | ||
private static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.AddAliasAttributesDescription), Resources.ResourceManager, typeof(Resources)); | ||
|
||
private static readonly DiagnosticDescriptor Rule = new(RuleId, Title, MessageFormat, Category, DiagnosticSeverity.Info, isEnabledByDefault: true, description: Description); | ||
|
||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule); | ||
|
||
public override void Initialize(AnalysisContext context) | ||
{ | ||
context.EnableConcurrentExecution(); | ||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze); | ||
context.RegisterSyntaxNodeAction(CheckSyntaxNode, | ||
SyntaxKind.InterfaceDeclaration, | ||
SyntaxKind.ClassDeclaration, | ||
SyntaxKind.StructDeclaration, | ||
SyntaxKind.RecordDeclaration, | ||
SyntaxKind.RecordStructDeclaration); | ||
} | ||
|
||
private void CheckSyntaxNode(SyntaxNodeAnalysisContext context) | ||
{ | ||
// Interface types and their methods | ||
if (context.Node is InterfaceDeclarationSyntax { } interfaceDeclaration) | ||
{ | ||
if (!context.SemanticModel | ||
.GetDeclaredSymbol(interfaceDeclaration, context.CancellationToken) | ||
.ExtendsGrainInterface()) | ||
{ | ||
return; | ||
} | ||
|
||
if (!interfaceDeclaration.HasAttribute(Constants.AliasAttributeName)) | ||
{ | ||
ReportFor( | ||
context, | ||
interfaceDeclaration.GetLocation(), | ||
interfaceDeclaration.Identifier.ToString(), | ||
GetArity(interfaceDeclaration), | ||
GetNamespaceAndNesting(interfaceDeclaration)); | ||
} | ||
|
||
foreach (var methodDeclaration in interfaceDeclaration.Members.OfType<MethodDeclarationSyntax>()) | ||
{ | ||
if (methodDeclaration.IsStatic()) | ||
{ | ||
continue; | ||
} | ||
|
||
if (!methodDeclaration.HasAttribute(Constants.AliasAttributeName)) | ||
{ | ||
ReportFor(context, methodDeclaration.GetLocation(), methodDeclaration.Identifier.ToString(), arity: 0, namespaceAndNesting: null); | ||
} | ||
} | ||
|
||
return; | ||
} | ||
|
||
// Rest of types: class, struct, record | ||
if (context.Node is TypeDeclarationSyntax { } typeDeclaration) | ||
{ | ||
if (!typeDeclaration.HasAttribute(Constants.GenerateSerializerAttributeName)) | ||
{ | ||
return; | ||
} | ||
|
||
if (typeDeclaration.HasAttribute(Constants.AliasAttributeName)) | ||
{ | ||
return; | ||
} | ||
|
||
ReportFor( | ||
context, | ||
typeDeclaration.GetLocation(), | ||
typeDeclaration.Identifier.ToString(), | ||
GetArity(typeDeclaration), | ||
GetNamespaceAndNesting(typeDeclaration)); | ||
} | ||
} | ||
|
||
private static int GetArity(TypeDeclarationSyntax typeDeclarationSyntax) | ||
{ | ||
var node = typeDeclarationSyntax; | ||
int arity = 0; | ||
while (node is TypeDeclarationSyntax type) | ||
{ | ||
arity += type.Arity; | ||
node = type.Parent as TypeDeclarationSyntax; | ||
} | ||
|
||
return arity; | ||
} | ||
|
||
private static string GetNamespaceAndNesting(TypeDeclarationSyntax typeDeclarationSyntax) | ||
{ | ||
SyntaxNode node = typeDeclarationSyntax.Parent; | ||
StringBuilder sb = new(); | ||
Stack<string> segments = new(); | ||
while (node is not null) | ||
{ | ||
if (node is TypeDeclarationSyntax type) | ||
{ | ||
segments.Push(type.Identifier.ToString()); | ||
} | ||
else if (node is NamespaceDeclarationSyntax ns) | ||
{ | ||
segments.Push(ns.Name.ToString()); | ||
} | ||
|
||
node = node.Parent; | ||
} | ||
|
||
foreach (var segment in segments) | ||
{ | ||
if (sb.Length > 0) | ||
{ | ||
sb.Append('.'); | ||
} | ||
|
||
sb.Append(segment); | ||
} | ||
|
||
return sb.ToString(); | ||
} | ||
|
||
private static void ReportFor(SyntaxNodeAnalysisContext context, Location location, string typeName, int arity, string namespaceAndNesting) | ||
{ | ||
var builder = ImmutableDictionary.CreateBuilder<string, string>(); | ||
|
||
builder.Add("TypeName", typeName); | ||
builder.Add("NamespaceAndNesting", namespaceAndNesting); | ||
builder.Add("Arity", arity.ToString(System.Globalization.CultureInfo.InvariantCulture)); | ||
|
||
context.ReportDiagnostic(Diagnostic.Create( | ||
descriptor: Rule, | ||
location: location, | ||
properties: builder.ToImmutable())); | ||
} | ||
} |
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,81 @@ | ||
using System.Collections.Immutable; | ||
using System.Threading.Tasks; | ||
using Microsoft.CodeAnalysis.CodeActions; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CodeFixes; | ||
using System.Composition; | ||
using Microsoft.CodeAnalysis.Editing; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using Microsoft.CodeAnalysis.Simplification; | ||
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; | ||
|
||
namespace Orleans.Analyzers; | ||
|
||
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(GenerateAliasAttributesCodeFix)), Shared] | ||
public class GenerateAliasAttributesCodeFix : CodeFixProvider | ||
{ | ||
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(GenerateAliasAttributesAnalyzer.RuleId); | ||
public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; | ||
|
||
public override async Task RegisterCodeFixesAsync(CodeFixContext context) | ||
{ | ||
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); | ||
|
||
foreach (var diagnostic in context.Diagnostics) | ||
{ | ||
var node = root.FindNode(diagnostic.Location.SourceSpan); | ||
if (node != null) | ||
{ | ||
// Check if its an interface method | ||
var methodDeclaration = node.FirstAncestorOrSelf<MethodDeclarationSyntax>(); | ||
if (methodDeclaration != null) | ||
{ | ||
await FixFor(context, diagnostic, methodDeclaration); | ||
continue; | ||
} | ||
|
||
// Check if its a type declaration (interface itself, class, struct, record) | ||
var typeDeclaration = node.FirstAncestorOrSelf<TypeDeclarationSyntax>(); | ||
if (typeDeclaration != null) | ||
{ | ||
await FixFor(context, diagnostic, typeDeclaration); | ||
continue; | ||
} | ||
} | ||
} | ||
} | ||
|
||
private static async Task FixFor(CodeFixContext context, Diagnostic diagnostic, SyntaxNode declaration) | ||
{ | ||
var documentEditor = await DocumentEditor.CreateAsync(context.Document, context.CancellationToken); | ||
|
||
var arityString = diagnostic.Properties["Arity"] switch | ||
{ | ||
null or "0" => "", | ||
string value => $"`{value}" | ||
}; | ||
var typeName = diagnostic.Properties["TypeName"]; | ||
var ns = diagnostic.Properties["NamespaceAndNesting"] switch | ||
{ | ||
{ Length: > 0 } value => $"{value}.", | ||
_ => "" | ||
}; | ||
|
||
var aliasAttribute = | ||
Attribute( | ||
ParseName(Constants.AliasAttributeFullyQualifiedName)) | ||
.WithArgumentList( | ||
ParseAttributeArgumentList($"(\"{ns}{typeName}{arityString}\")")) | ||
.WithAdditionalAnnotations(Simplifier.Annotation); | ||
|
||
documentEditor.AddAttribute(declaration, aliasAttribute); | ||
var updatedDocument = documentEditor.GetChangedDocument(); | ||
|
||
context.RegisterCodeFix( | ||
action: CodeAction.Create( | ||
Resources.AddAliasAttributesTitle, | ||
createChangedDocument: ct => Task.FromResult(updatedDocument), | ||
equivalenceKey: GenerateAliasAttributesAnalyzer.RuleId), | ||
diagnostic: diagnostic); | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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.