Skip to content

Commit

Permalink
Support Auto Decoration #5
Browse files Browse the repository at this point in the history
  • Loading branch information
vipwan committed Nov 11, 2023
1 parent 5610212 commit 838526b
Show file tree
Hide file tree
Showing 10 changed files with 442 additions and 7 deletions.
32 changes: 32 additions & 0 deletions Biwen.AutoClassGen.Attributes/AutoDecorAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;
namespace Biwen.AutoClassGen.Attributes
{

/// <summary>
/// Auto Decoration Attribute
/// </summary>
[AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
public class AutoDecorAttribute : Attribute
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:删除未使用的参数", Justification = "<挂起>")]
public AutoDecorAttribute(Type implement) { }
}

#if NET7_0_OR_GREATER

/// <summary>
/// Auto Decoration Attribute
/// </summary>
[AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
#pragma warning disable SA1402 // File may only contain a single type
public class AutoDecorAttribute<T> : AutoDecorAttribute where T : class
#pragma warning restore SA1402 // File may only contain a single type
{
public AutoDecorAttribute() : base(typeof(T))
{
}
}

#endif

}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Scrutor" Version="4.2.2" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.507">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down
15 changes: 15 additions & 0 deletions Biwen.AutoClassGen.Gen/DiagnosticDescriptors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ internal class DiagnosticDescriptors
public const string GEN031 = "GEN031"; // 推荐生成
public const string GEN041 = "GEN041"; // 重复标注
public const string GEN042 = "GEN042"; // 不可用于abstract基类
public const string GEN043 = "GEN043"; // 标记[AutoDecor]的类型,装饰器必须是它的实现类或者子类

/// <summary>
/// 无法生成类的错误
Expand Down Expand Up @@ -83,5 +84,19 @@ internal class DiagnosticDescriptors
helpLinkUri: Helplink,
isEnabledByDefault: true);




/// <summary>
/// Decor错误标注
/// </summary>
public static readonly DiagnosticDescriptor MarkedAutoDecorError = new(id: GEN043,
title: "标记[AutoDecor]的类型,装饰器必须是它的实现类或者子类",
messageFormat: "标记[AutoDecor]的类型,装饰器必须是它的实现类或者子类",
category: typeof(SourceGenerator).Assembly.GetName().Name,
DiagnosticSeverity.Error,
helpLinkUri: Helplink,
isEnabledByDefault: true);

}
}
60 changes: 56 additions & 4 deletions Biwen.AutoClassGen.Gen/SourceGenAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,28 @@
public class SourceGenAnalyzer : DiagnosticAnalyzer
{


public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(
Desc.InvalidDeclareError,
Desc.InvalidDeclareNameError,
Desc.SuggestDeclareNameWarning,
Desc.SuggestAutoGen,
Desc.MutiMarkedAutoDtoError,
Desc.MarkedAbstractAutoDtoError);
Desc.MarkedAbstractAutoDtoError,
Desc.MarkedAutoDecorError);

private const string AttributeValueMetadataName = "AutoGen";
/// <summary>
/// Dto特性名称,注意存在泛型的情况
/// </summary>
private const string AttributeValueMetadataNameDto = "AutoDto";

/// <summary>
/// AutoDecor,注意存在泛型的情况
/// </summary>

private const string AttributeValueMetadataNameDecor = "AutoDecor";



public override void Initialize(AnalysisContext context)
{
Expand Down Expand Up @@ -129,13 +136,58 @@ public override void Initialize(AnalysisContext context)
ctx.ReportDiagnostic(Diagnostic.Create(Desc.MarkedAbstractAutoDtoError, location));
}
}

if (attr.Attributes.Where(x => x.Name.ToString().IndexOf(
AttributeValueMetadataNameDecor, StringComparison.Ordinal) == 0).Any())
{
foreach (var at in attr.Attributes)
{
if (at.ArgumentList != null && at.ArgumentList.Arguments.Any())
{
var arg0 = at.ArgumentList.Arguments[0];
if (arg0.Expression is TypeOfExpressionSyntax express)
{
var implNameStr = express.Type.ToString();
var symbol = ctx.Compilation.GetSymbolsWithName(implNameStr, SymbolFilter.Type);
if (symbol.Any())
{
var implName = symbol.First();
if (declaration.BaseList?.Types.Any(x => x.Type.ToString() == implName.Name) is not true
&& declaration.Identifier.Text != (implName as ITypeSymbol)?.BaseType?.Name)
{
var location = arg0?.GetLocation();
// issue error
ctx.ReportDiagnostic(Diagnostic.Create(Desc.MarkedAutoDecorError, location));
}
}
}
}
if (at.Name is GenericNameSyntax genericNameSyntax)
{
var implNameStr = genericNameSyntax.TypeArgumentList.Arguments[0].ToString();
var symbol = ctx.Compilation.GetSymbolsWithName(genericNameSyntax.TypeArgumentList.Arguments[0].ToString(), SymbolFilter.Type);

if (symbol.Any())
{
var implName = symbol.First();
if (declaration.BaseList?.Types.Any(x => x.Type.ToString() == implName.Name) is not true
&& declaration.Identifier.Text != (implName as ITypeSymbol)?.BaseType?.Name)
{
var location = at?.GetLocation();
// issue error
ctx.ReportDiagnostic(Diagnostic.Create(Desc.MarkedAutoDecorError, location));
}
}
}
}
}
}
}
}
},
SyntaxKind.InterfaceDeclaration,
SyntaxKind.ClassDeclaration);

SyntaxKind.ClassDeclaration
);
}
}
}
143 changes: 142 additions & 1 deletion Biwen.AutoClassGen.Gen/SourceGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@ public class SourceGenerator : IIncrementalGenerator
private const string AttributeMetadataNameDtoG = "Biwen.AutoClassGen.Attributes.AutoDtoAttribute`1";


private const string AttributeMetadataNameDecor = "Biwen.AutoClassGen.Attributes.AutoDecorAttribute";

//private const string AttributeValueMetadataNameDecor = "AutoDecor";
/// <summary>
/// 泛型AutoDecorAttribute
/// </summary>
private const string AttributeMetadataNameDecorG = "Biwen.AutoClassGen.Attributes.AutoDecorAttribute`1";




public void Initialize(IncrementalGeneratorInitializationContext context)
{

Expand Down Expand Up @@ -79,6 +90,26 @@ public void Initialize(IncrementalGeneratorInitializationContext context)

#endregion


#region AutoDecorAttribute

var nodesDecor = context.SyntaxProvider.ForAttributeWithMetadataName(
AttributeMetadataNameDecor,
(context, attributeSyntax) => true,
(syntaxContext, _) => syntaxContext.TargetSymbol).Collect();

var nodesDecorG = context.SyntaxProvider.ForAttributeWithMetadataName(
AttributeMetadataNameDecorG,
(context, attributeSyntax) => true,
(syntaxContext, _) => syntaxContext.TargetSymbol).Collect();

IncrementalValueProvider<((Compilation, ImmutableArray<ISymbol>), ImmutableArray<ISymbol>)> compilationAndTypesDecor =
context.CompilationProvider.Combine(nodesDecor).Combine(nodesDecorG);

context.RegisterSourceOutput(compilationAndTypesDecor, static (spc, source) =>
HandleAnnotatedNodesDecor(source.Item1.Item1, source.Item1.Item2, source.Item2, spc));

#endregion
}


Expand Down Expand Up @@ -428,7 +459,7 @@ void GenProperty(TypeSyntax @type)
/// <param name="context"></param>
private static void HandleAnnotatedNodesDtoG(Compilation compilation, ImmutableArray<SyntaxNode> nodes, SourceProductionContext context)
{
if(nodes.Length == 0) return;
if (nodes.Length == 0) return;

StringBuilder envStringBuilder = new();

Expand Down Expand Up @@ -592,6 +623,116 @@ void GenProperty(TypeSyntax @type)
}


private static void HandleAnnotatedNodesDecor(Compilation compilation, ImmutableArray<ISymbol> nodes, ImmutableArray<ISymbol> nodes2, SourceProductionContext context)
{
if (nodes.Length == 0) return;

IList<KeyValuePair<string, string>> ofImpls = [];
// (普通特性)获取所有实现类
foreach (var node in nodes)
{
var tName = node.OriginalDefinition.ToDisplayString();
foreach (var item in node.GetAttributes().Where(x => x.AttributeClass?.MetadataName == "AutoDecorAttribute"))
{
var attributeSyntax = item?.ApplicationSyntaxReference?.GetSyntax() as AttributeSyntax;

if (attributeSyntax?.ArgumentList?.Arguments[0].Expression is TypeOfExpressionSyntax implNameSyntax)
{
var implNameStr = implNameSyntax.Type.ToString();
var symbol = compilation.GetSymbolsWithName(implNameStr, SymbolFilter.Type);

if (symbol?.Any() is true)
{
var implName = symbol.First().ToDisplayString();
implNameStr = implName;

ofImpls.Add(new KeyValuePair<string, string>(tName, implName));
}
}
}
}
// (泛型特性)获取所有实现类
foreach (var node in nodes2)
{
var tName = node.OriginalDefinition.ToDisplayString();
foreach (var item in node.GetAttributes().Where(x => x.AttributeClass?.MetadataName == "AutoDecorAttribute`1"))
{
var attributeSyntax = item?.ApplicationSyntaxReference?.GetSyntax() as AttributeSyntax;

if (attributeSyntax?.Name is GenericNameSyntax genericNameSyntax)
{
var implNameStr = genericNameSyntax.TypeArgumentList.Arguments[0].ToString();
var symbol = compilation.GetSymbolsWithName(genericNameSyntax.TypeArgumentList.Arguments[0].ToString(), SymbolFilter.Type);

if (symbol?.Any() is true)
{
var implName = symbol.First().ToDisplayString();
implNameStr = implName;

ofImpls.Add(new KeyValuePair<string, string>(tName, implName));
}
}
}
}

if (ofImpls.Count == 0)
{
return;
}

//namespace Microsoft.Extensions.DependencyInjection
// {
// public static class AutoAopExtensions
// {
// /// <summary>
// /// AddAutoDecor
// /// </summary>
// /// <param name="services"></param>
// /// <returns></returns>
// public static IServiceCollection AddAutoDecor(this IServiceCollection services)
// {
// services.Decorate<IHelloService, IHelloServiceDecorate>();
// services.Decorate<IHelloService, IHelloServiceDecorate2>();
// return services;
// }
// }
// }

StringBuilder envStringBuilder = new();
envStringBuilder.AppendLine("// <auto-generated />");
envStringBuilder.AppendLine("// author:[email protected] 万雅虎");
envStringBuilder.AppendLine("// issue:https://github.com/vipwan/Biwen.AutoClassGen/issues");
envStringBuilder.AppendLine("// 如果你在使用中遇到问题,请第一时间issue,谢谢!");
envStringBuilder.AppendLine("// This file is generated by Biwen.AutoClassGen.SourceGenerator");
envStringBuilder.AppendLine();
envStringBuilder.AppendLine("#pragma warning disable");
envStringBuilder.AppendLine("namespace Microsoft.Extensions.DependencyInjection");
envStringBuilder.AppendLine("{");
envStringBuilder.AppendLine("public static class AutoDecorExtensions");
envStringBuilder.AppendLine("{");
envStringBuilder.AppendLine("/// <summary>");
envStringBuilder.AppendLine("/// AddAutoDecor");
envStringBuilder.AppendLine("/// </summary>");
envStringBuilder.AppendLine("public static IServiceCollection AddAutoDecor(this IServiceCollection services)");
envStringBuilder.AppendLine("{");
// decor
foreach (var item in ofImpls)
{
envStringBuilder.AppendLine($"services.Decorate<{item.Key}, {item.Value}>();");
}
envStringBuilder.AppendLine("return services;");
envStringBuilder.AppendLine("}");
envStringBuilder.AppendLine("}");
envStringBuilder.AppendLine("}");
envStringBuilder.AppendLine("#pragma warning restore");

var envSource = envStringBuilder.ToString();
// format:
envSource = FormatContent(envSource);
context.AddSource($"Biwen.AutoClassGenDecor.g.cs", SourceText.From(envSource, Encoding.UTF8));
}


#region Template

public static readonly string MapperTemplate = $@"
Expand Down
61 changes: 61 additions & 0 deletions Biwen.AutoClassGen.TestConsole/Decors/IHelloService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
namespace Biwen.AutoClassGen.TestConsole.Decors
{

[AutoDecor(typeof(HelloServiceDecor1))]
[AutoDecor<HelloServiceDecor2>]
public interface IHelloService
{
string SayHello(string name);
}

/// <summary>
/// implement IHelloService
/// </summary>
[AutoDecor<HelloServiceDecor1>]
public class HelloService : IHelloService
{
public string SayHello(string name)
{
return $"Hello {name}";
}
}

/// <summary>
/// decor IHelloService
/// </summary>
public class HelloServiceDecor1 : HelloService
{
private readonly HelloService _helloService;

public HelloServiceDecor1(HelloService helloService)
{
_helloService = helloService;
}

public new string SayHello(string name)
{
Console.WriteLine($"Hello {name} from HelloServiceDecor1");
return _helloService.SayHello(name);
}
}
/// <summary>
/// decor IHelloService 2
/// </summary>
public class HelloServiceDecor2 : IHelloService
{
private readonly IHelloService _helloService;

public HelloServiceDecor2(IHelloService helloService)
{
_helloService = helloService;
}

public string SayHello(string name)
{
Console.WriteLine($"Hello {name} from HelloServiceDecor2");
return _helloService.SayHello(name);
}
}


}
Loading

0 comments on commit 838526b

Please sign in to comment.