-
Notifications
You must be signed in to change notification settings - Fork 116
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
546 additions
and
1 deletion.
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
33 changes: 33 additions & 0 deletions
33
src/AspectInjector.Analyzer/Refactorings/AdviceAttributeCodeRefactoringProvider.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,33 @@ | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CodeRefactorings; | ||
using Microsoft.CodeAnalysis.CSharp; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using System.Linq; | ||
using System.Threading.Tasks; | ||
|
||
namespace AspectInjector.Analyzer.Refactorings | ||
{ | ||
[ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = nameof(AdviceAttributeCodeRefactoringProvider))] | ||
public class AdviceAttributeCodeRefactoringProvider : AdviceCodeRefactoringProvider | ||
{ | ||
public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) | ||
{ | ||
var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); | ||
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); | ||
var node = root.FindNode(context.Span); | ||
|
||
var attrSyntax = (node as AttributeSyntax) ?? node.Parent as AttributeSyntax; | ||
|
||
if (attrSyntax != null) | ||
{ | ||
var attr = semanticModel.GetSymbolInfo(attrSyntax, context.CancellationToken); | ||
if (attr.Symbol?.ContainingSymbol is INamedTypeSymbol named && named.ToDisplayString() == WellKnown.AdviceType) | ||
{ | ||
var method = attrSyntax.AncestorsAndSelf().OfType<MethodDeclarationSyntax>().FirstOrDefault(); | ||
if (method != null) | ||
await RegisterRefactorings(context, method); | ||
} | ||
} | ||
} | ||
} | ||
} |
103 changes: 103 additions & 0 deletions
103
src/AspectInjector.Analyzer/Refactorings/AdviceCodeRefactoringProvider.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,103 @@ | ||
using AspectInjector.Broker; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CodeActions; | ||
using Microsoft.CodeAnalysis.CodeRefactorings; | ||
using Microsoft.CodeAnalysis.CSharp; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using System.Collections.Generic; | ||
using System.Collections.Immutable; | ||
using System.Linq; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace AspectInjector.Analyzer.Refactorings | ||
{ | ||
[ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = nameof(AdviceCodeRefactoringProvider))] | ||
public class AdviceCodeRefactoringProvider : CodeRefactoringProvider | ||
{ | ||
private static readonly ImmutableArray<ParameterSample> _shared = new List<ParameterSample> | ||
{ | ||
Samples.Parameters.Arguments, | ||
Samples.Parameters.Instance, | ||
Samples.Parameters.Name, | ||
Samples.Parameters.Type, | ||
Samples.Parameters.Metadata, | ||
Samples.Parameters.ReturnType, | ||
Samples.Parameters.Triggers, | ||
}.ToImmutableArray(); | ||
|
||
private static readonly ImmutableArray<ParameterSample> _after = _shared.AddRange(new List<ParameterSample> | ||
{ | ||
Samples.Parameters.ReturnValue, | ||
}); | ||
|
||
private static readonly ImmutableArray<ParameterSample> _around = _shared.AddRange(new List<ParameterSample> | ||
{ | ||
Samples.Parameters.Target, | ||
}); | ||
|
||
public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) | ||
{ | ||
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); | ||
|
||
var node = root.FindNode(context.Span); | ||
var method = node as MethodDeclarationSyntax; | ||
if (method != null) | ||
{ | ||
await RegisterRefactorings(context, method); | ||
} | ||
} | ||
|
||
protected async Task RegisterRefactorings(CodeRefactoringContext context, MethodDeclarationSyntax method) | ||
{ | ||
var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); | ||
var methodSymbol = semanticModel.GetDeclaredSymbol(method); | ||
var attr = methodSymbol.GetAdviceAttribute(); | ||
|
||
if (attr == null) | ||
return; | ||
|
||
var kindArg = attr.ConstructorArguments.FirstOrDefault(); | ||
|
||
if (kindArg.IsNull) | ||
return; | ||
|
||
var kind = (Kind)kindArg.Value; | ||
|
||
var list = _shared; | ||
if (kind == Kind.After) | ||
list = _after; | ||
if (kind == Kind.Around) | ||
list = _around; | ||
|
||
var currentAttrs = methodSymbol.Parameters.SelectMany(p => p.GetAttributes()).ToImmutableArray(); | ||
|
||
foreach (var entry in list) | ||
{ | ||
if (HasParameter(currentAttrs, entry.Source)) | ||
continue; | ||
|
||
context.RegisterRefactoring(CodeAction.Create( | ||
$"Add '{entry.Source}' parameter to this advice.", | ||
ct => AddParameter(context, method, entry, ct) | ||
)); | ||
} | ||
} | ||
|
||
private async Task<Document> AddParameter(CodeRefactoringContext context, MethodDeclarationSyntax method, ParameterSample parameter, CancellationToken ct) | ||
{ | ||
var root = await context.Document.GetSyntaxRootAsync(ct).ConfigureAwait(false); | ||
var newMethod = method.WithParameterList(method.ParameterList.AddParameters(parameter.ParameterSyntax)/*.WithAdditionalAnnotations(Formatter.Annotation)*/); | ||
|
||
root = root.ReplaceNode(method, newMethod); | ||
root = root.WithUpdatedUsings(new[] { parameter.UsingSyntax }); | ||
|
||
return context.Document.WithSyntaxRoot(root); | ||
} | ||
|
||
private bool HasParameter(ImmutableArray<AttributeData> attributes, string source) | ||
{ | ||
return attributes.Any(a => a.ConstructorArguments.Any(ca => ((Source)ca.Value).ToString() == source)); | ||
} | ||
} | ||
} |
36 changes: 36 additions & 0 deletions
36
src/AspectInjector.Analyzer/Refactorings/AspectAttributeCodeRefactoringProvider.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,36 @@ | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CodeRefactorings; | ||
using Microsoft.CodeAnalysis.CSharp; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using System.Linq; | ||
using System.Threading.Tasks; | ||
|
||
namespace AspectInjector.Analyzer.Refactorings | ||
{ | ||
[ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = nameof(AspectAttributeCodeRefactoringProvider))] | ||
public class AspectAttributeCodeRefactoringProvider : AspectCodeRefactoringProvider | ||
{ | ||
public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) | ||
{ | ||
var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); | ||
var root = await context.Document | ||
.GetSyntaxRootAsync(context.CancellationToken) | ||
.ConfigureAwait(false); | ||
var node = root.FindNode(context.Span); | ||
|
||
var attrSyntax = (node as AttributeSyntax) ?? node.Parent as AttributeSyntax; | ||
|
||
|
||
if (attrSyntax != null) | ||
{ | ||
var attr = semanticModel.GetSymbolInfo(attrSyntax, context.CancellationToken); | ||
if (attr.Symbol?.ContainingSymbol is INamedTypeSymbol named && named.ToDisplayString() == WellKnown.AspectType) | ||
{ | ||
var @class = attrSyntax.AncestorsAndSelf().OfType<ClassDeclarationSyntax>().FirstOrDefault(); | ||
if (@class != null) | ||
RegisterRefactoring(context, @class); | ||
} | ||
} | ||
} | ||
} | ||
} |
61 changes: 61 additions & 0 deletions
61
src/AspectInjector.Analyzer/Refactorings/AspectCodeRefactoringProvider.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,61 @@ | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CodeActions; | ||
using Microsoft.CodeAnalysis.CodeRefactorings; | ||
using Microsoft.CodeAnalysis.CSharp; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace AspectInjector.Analyzer.Refactorings | ||
{ | ||
[ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = nameof(AspectCodeRefactoringProvider))] | ||
public class AspectCodeRefactoringProvider : CodeRefactoringProvider | ||
{ | ||
public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) | ||
{ | ||
var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); | ||
var root = await context.Document | ||
.GetSyntaxRootAsync(context.CancellationToken) | ||
.ConfigureAwait(false); | ||
var node = root.FindNode(context.Span); | ||
var @class = node as ClassDeclarationSyntax; | ||
if (@class == null) | ||
return; | ||
|
||
var attr = semanticModel.GetDeclaredSymbol(@class).GetAspectAttribute(); | ||
|
||
if (attr == null) | ||
return; | ||
|
||
RegisterRefactoring(context, @class); | ||
} | ||
|
||
protected void RegisterRefactoring(CodeRefactoringContext context, ClassDeclarationSyntax @class) | ||
{ | ||
context.RegisterRefactoring(CodeAction.Create( | ||
"Add 'Before' advice to this aspect.", | ||
ct => | ||
CreateAdvice(context, @class, Samples.Advices.Before, ct))); | ||
context.RegisterRefactoring(CodeAction.Create( | ||
"Add 'After' advice to this aspect.", | ||
ct => | ||
CreateAdvice(context, @class, Samples.Advices.After, ct))); | ||
context.RegisterRefactoring(CodeAction.Create( | ||
"Add 'Around' advice to this aspect.", | ||
ct => | ||
CreateAdvice(context, @class, Samples.Advices.Around, ct))); | ||
} | ||
|
||
private async Task<Document> CreateAdvice(CodeRefactoringContext context, ClassDeclarationSyntax @class, (MethodDeclarationSyntax Method, UsingDirectiveSyntax[] Usings) advice, CancellationToken cancellationToken) | ||
{ | ||
var root = await context.Document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); | ||
|
||
var newClass = @class.AddMembers(advice.Method); | ||
|
||
root = root.ReplaceNode(@class, newClass); | ||
root = root.WithUpdatedUsings(advice.Usings); | ||
|
||
return context.Document.WithSyntaxRoot(root); | ||
} | ||
} | ||
} |
70 changes: 70 additions & 0 deletions
70
src/AspectInjector.Analyzer/Refactorings/PossibleMixinCodeRefactoringProvider.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,70 @@ | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CodeActions; | ||
using Microsoft.CodeAnalysis.CodeRefactorings; | ||
using Microsoft.CodeAnalysis.CSharp; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; | ||
using System.Linq; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.CodeAnalysis.Formatting; | ||
using AspectInjector.Broker; | ||
|
||
namespace AspectInjector.Analyzer.Refactorings | ||
{ | ||
[ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = nameof(PossibleMixinCodeRefactoringProvider))] | ||
public class PossibleMixinCodeRefactoringProvider : CodeRefactoringProvider | ||
{ | ||
public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) | ||
{ | ||
var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); | ||
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); | ||
var node = root.FindNode(context.Span); | ||
var @base = node as BaseTypeSyntax; | ||
if (@base != null) | ||
{ | ||
var baseType = semanticModel.GetSymbolInfo(@base.Type).Symbol as ITypeSymbol; | ||
if (baseType != null && baseType.TypeKind == TypeKind.Interface) | ||
{ | ||
var @class = @base.AncestorsAndSelf().OfType<ClassDeclarationSyntax>().FirstOrDefault(); | ||
if (@class != null) | ||
{ | ||
var classSymbol = semanticModel.GetDeclaredSymbol(@class); | ||
var attr = classSymbol?.GetAspectAttribute(); | ||
if (attr != null) | ||
{ | ||
var currentMixins = classSymbol.GetMixinAttributes(); | ||
if (!currentMixins.Any(m => m.ConstructorArguments.Length > 0 && | ||
m.ConstructorArguments[0].Value is ITypeSymbol ts && | ||
ts == baseType)) | ||
{ | ||
context.RegisterRefactoring(CodeAction.Create( | ||
"Register this interface with Aspect as Mixin.", | ||
ct => AddMixinAttribute(context, @class, @base.Type, ct))); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
private async Task<Document> AddMixinAttribute(CodeRefactoringContext context, ClassDeclarationSyntax @class, TypeSyntax baseType, CancellationToken ct) | ||
{ | ||
var root = await context.Document.GetSyntaxRootAsync(ct).ConfigureAwait(false); | ||
|
||
var newClass = @class.WithAttributeLists(@class.AttributeLists.Add( | ||
AttributeList( | ||
SeparatedList(new[]{ | ||
Attribute(IdentifierName(nameof(Mixin)), | ||
AttributeArgumentList(SeparatedList(new[]{ AttributeArgument(TypeOfExpression(baseType.WithoutTrivia())) })) | ||
) | ||
}) | ||
).WithAdditionalAnnotations(Formatter.Annotation) | ||
)); | ||
|
||
root = root.ReplaceNode(@class, newClass); | ||
|
||
return context.Document.WithSyntaxRoot(root); | ||
} | ||
} | ||
} |
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,68 @@ | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CSharp; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using Microsoft.CodeAnalysis.Formatting; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; | ||
|
||
namespace AspectInjector.Analyzer.Refactorings | ||
{ | ||
public static partial class Samples | ||
{ | ||
public static class Advices | ||
{ | ||
public static readonly (MethodDeclarationSyntax Method, UsingDirectiveSyntax[] Usings) Before = CreateAdviceSample( | ||
Broker.Kind.Before, "Before", PredefinedType(Token(SyntaxKind.VoidKeyword)), | ||
new[] { Parameters.Name, Parameters.Arguments, Parameters.Type }, | ||
@"{ | ||
//Don't forget to remove unused parameters as it improves performance! | ||
//Alt+Enter or Ctrl+. on Method name or Advice attribute to add more Arguments | ||
Console.WriteLine($""Calling {name} from {hostType.Name} with args: {string.Join(',', args.Select(a => ToString()))}""); | ||
}", | ||
new[] { SyntaxBasicBlocks.Namespaces.SystemLinq, SyntaxBasicBlocks.Namespaces.System }); | ||
|
||
|
||
public static readonly (MethodDeclarationSyntax Method, UsingDirectiveSyntax[] Usings) After = CreateAdviceSample( | ||
Broker.Kind.After, "After", PredefinedType(Token(SyntaxKind.VoidKeyword)), | ||
new[] { Parameters.Name, Parameters.ReturnValue, Parameters.Type }, | ||
@"{ | ||
//Don't forget to remove unused parameters as it improves performance! | ||
//Alt+Enter or Ctrl+. on Method name or Advice attribute to add more Arguments | ||
Console.WriteLine($""Finished {name} from {hostType.Name} with result: {retValue}""); | ||
}", | ||
new[] { SyntaxBasicBlocks.Namespaces.System }); | ||
|
||
public static readonly (MethodDeclarationSyntax Method, UsingDirectiveSyntax[] Usings) Around = CreateAdviceSample( | ||
Broker.Kind.Around, "Around", PredefinedType(Token(SyntaxKind.ObjectKeyword)), | ||
new[] { Parameters.Name, Parameters.Arguments, Parameters.Type, Parameters.Target }, | ||
@"{ | ||
//Don't forget to remove unused parameters as it improves performance! | ||
//Alt+Enter or Ctrl+. on Method name or Advice attribute to add more Arguments | ||
Console.WriteLine($""Entering {name} from {hostType.Name}""); | ||
var result = target(args); | ||
Console.WriteLine($""Leaving {name} from {hostType.Name}""); | ||
return result; | ||
}", | ||
new[] { SyntaxBasicBlocks.Namespaces.System }); | ||
|
||
private static (MethodDeclarationSyntax Method, UsingDirectiveSyntax[] Usings) CreateAdviceSample(Broker.Kind kind, string name, TypeSyntax returnType, IReadOnlyList<ParameterSample> parameters, string body, UsingDirectiveSyntax[] usings = null) | ||
{ | ||
usings = usings ?? new UsingDirectiveSyntax[0]; | ||
usings = usings.Concat(parameters.Select(p => p.UsingSyntax)).Where(s => s != null).ToArray(); | ||
|
||
var method = MethodDeclaration(returnType, Identifier(name)) | ||
.WithAdviceAttribute(kind) | ||
.WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword))) | ||
.WithParameterList(ParameterList(SeparatedList(parameters.Select(p => p.ParameterSyntax)))) | ||
.WithBody(ParseCompilationUnit(body, options: new CSharpParseOptions(kind: SourceCodeKind.Script)).ChildNodes().First().ChildNodes().OfType<BlockSyntax>().First()) | ||
.WithAdditionalAnnotations(Formatter.Annotation); | ||
|
||
return (method, usings); | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.