-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
36 changed files
with
1,092 additions
and
60 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,4 @@ | ||
[*.{cs,vb}] | ||
|
||
# IDE0130: 命名空间与文件夹结构不匹配 | ||
dotnet_diagnostic.IDE0130.severity = none |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,251 @@ | ||
using Generators.Shared; | ||
using Generators.Shared.Builder; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
|
||
namespace AutoAopProxyGenerator; | ||
|
||
[Generator(LanguageNames.CSharp)] | ||
public class AutoAopProxyClassGenerator : IIncrementalGenerator | ||
{ | ||
public const string Aspectable = "AutoAopProxyGenerator.GenAspectProxyAttribute"; | ||
public const string AspectHandler = "AutoAopProxyGenerator.AddAspectHandlerAttribute"; | ||
public const string IgnoreAspect = "AutoAopProxyGenerator.IgnoreAspectAttribute"; | ||
public void Initialize(IncrementalGeneratorInitializationContext context) | ||
{ | ||
var source = context.SyntaxProvider.ForAttributeWithMetadataName( | ||
Aspectable | ||
, static (node, _) => node is ClassDeclarationSyntax | ||
, static (ctx, _) => ctx); | ||
context.RegisterSourceOutput(source, static (context, source) => | ||
{ | ||
var targetSymbol = (INamedTypeSymbol)source.TargetSymbol; | ||
//INamedTypeSymbol[] needToCheckSymbol = [targetSymbol, .. targetSymbol.AllInterfaces]; | ||
|
||
|
||
var allInterfaces = targetSymbol.AllInterfaces.Where(a => a.HasAttribute(AspectHandler)).ToArray(); | ||
|
||
#region 检查Attribute标注是否合法 | ||
bool pass = true; | ||
foreach (var i in allInterfaces) | ||
{ | ||
if (!pass) | ||
{ | ||
break; | ||
} | ||
var all = i.GetAttributes(AspectHandler); | ||
foreach (var a in all) | ||
{ | ||
var at = a.GetNamedValue("AspectType"); | ||
if (at == null) | ||
{ | ||
context.ReportDiagnostic(DiagnosticDefinitions.AAPG00001(source.TargetNode.GetLocation())); | ||
pass = false; | ||
break; | ||
} | ||
var att = (INamedTypeSymbol)at; | ||
if (!att.HasInterface("AutoAopProxyGenerator.IAspectHandler")) | ||
{ | ||
context.ReportDiagnostic(DiagnosticDefinitions.AAPG00002(source.TargetNode.GetLocation())); | ||
pass = false; | ||
break; | ||
} | ||
} | ||
} | ||
if (!pass) | ||
{ | ||
return; | ||
} | ||
#endregion | ||
|
||
#region 获取所有的AspectHandler | ||
var allHandlers = allInterfaces.SelectMany(x => | ||
{ | ||
var handlers = x.GetAttributes(AspectHandler).Select(a => | ||
{ | ||
var at = a.GetNamedValue("AspectType"); | ||
if (at == null) | ||
{ | ||
context.ReportDiagnostic(DiagnosticDefinitions.AAPG00001(source.TargetNode.GetLocation())); | ||
return null; | ||
} | ||
var att = (INamedTypeSymbol)at; | ||
if (!att.HasInterface("AutoAopProxyGenerator.IAspectHandler")) | ||
{ | ||
context.ReportDiagnostic(DiagnosticDefinitions.AAPG00002(source.TargetNode.GetLocation())); | ||
return null; | ||
} | ||
return att; | ||
}); | ||
return handlers.Where(i => i != null).Cast<INamedTypeSymbol>(); | ||
}).Distinct(EqualityComparer<INamedTypeSymbol>.Default).ToArray(); | ||
#endregion | ||
|
||
var file = CreateGeneratedProxyClassFile(targetSymbol, allInterfaces, allHandlers); | ||
if (file != null) | ||
{ | ||
//var ss = file.ToString(); | ||
context.AddSource(file); | ||
} | ||
}); | ||
} | ||
|
||
private static CodeFile? CreateGeneratedProxyClassFile(INamedTypeSymbol classSymbol, INamedTypeSymbol[] interfaces, INamedTypeSymbol[] allHandlers) | ||
{ | ||
// 代理字段和aspect handler 字段 | ||
List<Node> members = [ | ||
FieldBuilder.Default.MemberType(classSymbol.ToDisplayString()).FieldName("proxy") | ||
, .. allHandlers.Select(n =>FieldBuilder.Default.MemberType(n.ToDisplayString()).FieldName(n.MetadataName)) | ||
]; | ||
// 构造函数 | ||
List<Statement> ctorbody = [ | ||
"this.proxy = proxy" | ||
, ..allHandlers.Select(n => $"this.{n.MetadataName} = {n.MetadataName}") | ||
]; | ||
var ctor = ConstructorBuilder.Default.MethodName($"{classSymbol.FormatClassName()}GeneratedProxy") | ||
.AddParameter([$"{classSymbol.ToDisplayString()} proxy", .. allHandlers.Select(n => $"{n.ToDisplayString()} {n.MetadataName}")]).AddBody([.. ctorbody]); | ||
members.Add(ctor); | ||
// 接口方法 | ||
foreach (var iface in interfaces) | ||
{ | ||
members.AddRange(CreateProxyMethod(iface, classSymbol)); | ||
} | ||
|
||
var proxyClass = ClassBuilder.Default | ||
.ClassName($"{classSymbol.FormatClassName()}GeneratedProxy") | ||
.AddMembers([.. members]) | ||
.Interface([.. interfaces.Select(i => i.ToDisplayString())]) | ||
.AddGeneratedCodeAttribute(typeof(AutoAopProxyClassGenerator)); | ||
|
||
return CodeFile.New($"{classSymbol.FormatFileName()}GeneratedProxyClass.g.cs") | ||
.AddUsings("using AutoAopProxyGenerator;") | ||
.AddMembers(NamespaceBuilder.Default.Namespace(classSymbol.ContainingNamespace.ToDisplayString()).AddMembers(proxyClass)); | ||
} | ||
|
||
private static IEnumerable<MethodBuilder> CreateProxyMethod(INamedTypeSymbol iface, INamedTypeSymbol classSymbol) | ||
{ | ||
var handlers = iface.GetAttributes(AspectHandler).Select(a => a.GetNamedValue("AspectType")).OfType<INamedTypeSymbol>().ToArray(); | ||
|
||
foreach (var m in iface.GetMethods()) | ||
{ | ||
MethodBuilder methodBuilder; | ||
if (m.HasAttribute(IgnoreAspect)) | ||
{ | ||
methodBuilder = CreateDirectInvokeMethod(); | ||
} | ||
else | ||
{ | ||
methodBuilder = CreateProxyMethod(); | ||
} | ||
|
||
yield return methodBuilder; | ||
|
||
MethodBuilder CreateDirectInvokeMethod() | ||
{ | ||
var builder = MethodBuilder.Default | ||
.MethodName(m.Name) | ||
.Generic([.. m.GetTypeParameters()]) | ||
.AddParameter([.. m.Parameters.Select(p => $"{p.Type.ToDisplayString()} {p.Name}")]) | ||
.ReturnType(m.ReturnType.ToDisplayString()) | ||
.AddGeneratedCodeAttribute(typeof(AutoAopProxyClassGenerator)); | ||
builder = builder | ||
.Lambda($"proxy.{builder.ConstructedMethodName}({string.Join(", ", m.Parameters.Select(p => p.Name))})"); | ||
|
||
return builder; | ||
} | ||
|
||
MethodBuilder CreateProxyMethod() | ||
{ | ||
var method = m.IsGenericMethod ? m.ConstructedFrom : m; | ||
var returnType = method.ReturnType.GetGenericTypes().FirstOrDefault() ?? method.ReturnType; | ||
var isAsync = method.IsAsync || method.ReturnType.ToDisplayString().StartsWith("System.Threading.Tasks.Task"); | ||
var builder = MethodBuilder.Default | ||
.MethodName(method.Name) | ||
.Async(isAsync) | ||
.Generic([.. method.GetTypeParameters()]) | ||
.AddParameter([.. method.Parameters.Select(p => $"{p.Type.ToDisplayString()} {p.Name}")]) | ||
.ReturnType(method.ReturnType.ToDisplayString()) | ||
.AddGeneratedCodeAttribute(typeof(AutoAopProxyClassGenerator)); | ||
List<Statement> statements = []; | ||
var hasReturn = !method.ReturnsVoid && returnType.ToDisplayString() != "System.Threading.Tasks.Task"; | ||
if (hasReturn) | ||
{ | ||
statements.Add($"{returnType.ToDisplayString()} returnValue = default;"); | ||
} | ||
var done = LocalFunction.Default | ||
.MethodName("Done") | ||
.AddParameters("ProxyContext ctx") | ||
.Async(isAsync) | ||
.Return("System.Threading.Tasks.Task") | ||
.AddBody([.. CreateLocalFunctionBody(method, builder.ConstructedMethodName, isAsync, hasReturn)]); | ||
|
||
statements.Add(done); | ||
statements.Add("var builder = AsyncPipelineBuilder<ProxyContext>.Create(Done)"); | ||
foreach (var handler in handlers) | ||
{ | ||
statements.Add($"builder.Use({handler.MetadataName}.Invoke)"); | ||
} | ||
statements.Add("var job = builder.Build()"); | ||
var ptypes = method.Parameters.Length > 0 ? $"[{string.Join(", ", method.Parameters.Select(p => $"typeof({p.Type.ToDisplayString()})"))}]" : "Type.EmptyTypes"; | ||
statements.Add($"var context = ContextHelper<{iface.ToDisplayString()}, {classSymbol.ToDisplayString()}>.GetOrCreate(nameof({method.Name}), {ptypes})"); | ||
if (hasReturn) | ||
{ | ||
statements.Add("context.HasReturnValue = true"); | ||
} | ||
statements.Add($"context.Parameters = new object?[] {{{string.Join(", ", method.Parameters.Select(p => p.Name))}}};"); | ||
if (isAsync) | ||
{ | ||
statements.Add("await job.Invoke(context)"); | ||
} | ||
else | ||
{ | ||
statements.Add("job.Invoke(context).GetAwaiter().GetResult()"); | ||
} | ||
if (hasReturn) | ||
statements.Add("return returnValue"); | ||
|
||
builder.AddBody([.. statements]); | ||
|
||
return builder; | ||
} | ||
} | ||
} | ||
|
||
private static IEnumerable<Statement> CreateLocalFunctionBody(IMethodSymbol method, string proxyName, bool isAsync, bool hasReturn) | ||
{ | ||
if (isAsync) | ||
{ | ||
if (hasReturn) | ||
{ | ||
yield return $"returnValue = await proxy.{proxyName}({string.Join(", ", method.Parameters.Select(p => p.Name))})"; | ||
yield return "ctx.Executed = true"; | ||
yield return "ctx.ReturnValue = returnValue"; | ||
} | ||
else | ||
{ | ||
yield return $"await proxy.{proxyName}({string.Join(", ", method.Parameters.Select(p => p.Name))})"; | ||
yield return "ctx.Executed = true"; | ||
} | ||
} | ||
else | ||
{ | ||
if (hasReturn) | ||
{ | ||
yield return $"returnValue = proxy.{proxyName}({string.Join(", ", method.Parameters.Select(p => p.Name))})"; | ||
yield return "ctx.Executed = true"; | ||
yield return "ctx.ReturnValue = returnValue"; | ||
yield return "return global::System.Threading.Tasks.Task.CompletedTask"; | ||
} | ||
else | ||
{ | ||
yield return $"proxy.{proxyName}({string.Join(", ", method.Parameters.Select(p => p.Name))})"; | ||
yield return "ctx.Executed = true"; | ||
yield return "return global::System.Threading.Tasks.Task.CompletedTask"; | ||
} | ||
} | ||
} | ||
} |
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 System; | ||
using System.Collections.Generic; | ||
using System.Text; | ||
|
||
namespace AutoAopProxyGenerator | ||
{ | ||
internal class DiagnosticDefinitions | ||
{ | ||
/// <summary> | ||
/// AutoAopProxyGenerator.AddAspectHandlerAttribute.AspectType不能为null | ||
/// </summary> | ||
/// <param name="location"></param> | ||
/// <returns></returns> | ||
public static Diagnostic AAPG00001(Location? location) => Diagnostic.Create(new DiagnosticDescriptor( | ||
id: "AAPG00001", | ||
title: "AutoAopProxyGenerator.AddAspectHandlerAttribute.AspectType不能为null", | ||
messageFormat: "AutoAopProxyGenerator.AddAspectHandlerAttribute.AspectType不能为null", | ||
category: typeof(AutoAopProxyClassGenerator).FullName!, | ||
defaultSeverity: DiagnosticSeverity.Error, | ||
isEnabledByDefault: true), location); | ||
|
||
/// <summary> | ||
/// AutoAopProxyGenerator.AddAspectHandlerAttribute.AspectType不实现IAspectHandler | ||
/// </summary> | ||
/// <param name="location"></param> | ||
/// <returns></returns> | ||
public static Diagnostic AAPG00002(Location? location) => Diagnostic.Create(new DiagnosticDescriptor( | ||
id: "AAPG00002", | ||
title: "AutoAopProxyGenerator.AddAspectHandlerAttribute.AspectType不实现IAspectHandler", | ||
messageFormat: "AutoAopProxyGenerator.AddAspectHandlerAttribute.AspectType不实现IAspectHandler", | ||
category: typeof(AutoAopProxyClassGenerator).FullName!, | ||
defaultSeverity: DiagnosticSeverity.Error, | ||
isEnabledByDefault: true), location); | ||
} | ||
} |
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,8 @@ | ||
{ | ||
"profiles": { | ||
"aop": { | ||
"commandName": "DebugRoslynComponent", | ||
"targetProject": "..\\TestProject1\\TestProject1.csproj" | ||
} | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
src/AutoAopProxyGenerator/Attributes/AddAspectHandlerAttribute.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,16 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
namespace AutoAopProxyGenerator; | ||
|
||
/// <summary> | ||
/// 在类或者接口上配置切面处理 | ||
/// </summary> | ||
[AttributeUsage(AttributeTargets.Interface, AllowMultiple = true)] | ||
public sealed class AddAspectHandlerAttribute : Attribute | ||
{ | ||
public Type? AspectType { get; set; } | ||
} |
12 changes: 0 additions & 12 deletions
12
src/AutoAopProxyGenerator/Attributes/AspectInterceptorAttribute.cs
This file was deleted.
Oops, something went wrong.
12 changes: 12 additions & 0 deletions
12
src/AutoAopProxyGenerator/Attributes/GenAspectProxyAttribute.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,12 @@ | ||
using System; | ||
|
||
namespace AutoAopProxyGenerator; | ||
|
||
/// <summary> | ||
/// 标记需要生成代理类的类型 | ||
/// </summary> | ||
[AttributeUsage(AttributeTargets.Class)] | ||
public sealed class GenAspectProxyAttribute : Attribute | ||
{ | ||
|
||
} |
12 changes: 12 additions & 0 deletions
12
src/AutoAopProxyGenerator/Attributes/IgnoreAspectAttribute.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,12 @@ | ||
using System; | ||
|
||
namespace AutoAopProxyGenerator; | ||
|
||
/// <summary> | ||
/// 配置不需要切面的方法 | ||
/// </summary> | ||
[AttributeUsage(AttributeTargets.Method)] | ||
public sealed class IgnoreAspectAttribute : Attribute | ||
{ | ||
|
||
} |
Oops, something went wrong.