Skip to content

Commit

Permalink
提供对DTO生成的Gen支持 #4
Browse files Browse the repository at this point in the history
  • Loading branch information
vipwan committed Nov 4, 2023
1 parent 1a88a90 commit aff03d3
Show file tree
Hide file tree
Showing 5 changed files with 282 additions and 1 deletion.
24 changes: 24 additions & 0 deletions Biwen.AutoClassGen.Attributes/AutoDtoAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;

namespace Biwen.AutoClassGen.Attributes
{
/// <summary>
/// 自动创建Dto
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public class AutoDtoAttribute : Attribute
{
/// <summary>
/// 从指定类型创建
/// </summary>
public Type FromType { get; private set; }

public string[] ExcludeProps { get; private set; }

public AutoDtoAttribute(Type fromType, params string[] excludeProps)
{
FromType = fromType;
ExcludeProps = excludeProps;
}
}
}
185 changes: 185 additions & 0 deletions Biwen.AutoClassGen.Gen/SourceGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
Expand All @@ -19,10 +20,18 @@ public class SourceGenerator : IIncrementalGenerator
const string AttributeMetadataName = "Biwen.AutoClassGen.Attributes.AutoGenAttribute";
const string AttributeValueMetadataName = "AutoGen";

const string AttributeMetadataName_Dto = "Biwen.AutoClassGen.Attributes.AutoDtoAttribute";
const string AttributeValueMetadataName_Dto = "AutoDto";



public void Initialize(IncrementalGeneratorInitializationContext context)
{


#region AutoGenAttribute


var nodes = context.SyntaxProvider.ForAttributeWithMetadataName(
AttributeMetadataName,
(context, attributeSyntax) => true,
Expand All @@ -33,8 +42,30 @@ public void Initialize(IncrementalGeneratorInitializationContext context)

context.RegisterSourceOutput(compilationAndTypes, static (spc, source) => HandleAnnotatedNodes(source.Item1, source.Item2, spc));

#endregion

#region AutoDtoAttribute

var nodesDto = context.SyntaxProvider.ForAttributeWithMetadataName(
AttributeMetadataName_Dto,
(context, attributeSyntax) => true,
(syntaxContext, _) => syntaxContext.TargetNode).Collect();

IncrementalValueProvider<(Compilation, ImmutableArray<SyntaxNode>)> compilationAndTypesDto =
context.CompilationProvider.Combine(nodesDto);

context.RegisterSourceOutput(compilationAndTypesDto, static (spc, source) => HandleAnnotatedNodesDto(source.Item1, source.Item2, spc));

#endregion
}


/// <summary>
/// Gen AutoGenAttribute
/// </summary>
/// <param name="compilation"></param>
/// <param name="nodes"></param>
/// <param name="context"></param>
private static void HandleAnnotatedNodes(Compilation compilation, ImmutableArray<SyntaxNode> nodes, SourceProductionContext context)
{
var sb = new StringBuilder();
Expand Down Expand Up @@ -174,6 +205,160 @@ void genProperty(TypeSyntax @interfaceType)
}
}

/// <summary>
/// Gen AutoDtoAttribute
/// </summary>
/// <param name="compilation"></param>
/// <param name="nodes"></param>
/// <param name="context"></param>
private static void HandleAnnotatedNodesDto(Compilation compilation, ImmutableArray<SyntaxNode> nodes, SourceProductionContext context)
{
var sb = new StringBuilder();
sb.AppendLine("// <auto-generated />");
sb.AppendLine("// author:[email protected] 万雅虎");
sb.AppendLine("// issue:https://github.com/vipwan/Biwen.AutoClassGen/issues");
sb.AppendLine("// 如果你在使用中遇到问题,请第一时间issue,谢谢!");
sb.AppendLine("// This file is generated by Biwen.AutoClassGen.SourceGenerator");
sb.AppendLine();
sb.AppendLine("using System;");
sb.AppendLine("using System.Collections.Generic;");
sb.AppendLine("using System.Text;");
sb.AppendLine("using System.Threading.Tasks;");

sb.AppendLine();
sb.AppendLine("$namespace");
sb.AppendLine();

sb.AppendLine("#pragma warning disable");
sb.AppendLine("namespace $ni");
sb.AppendLine("{");
sb.AppendLine("$classes");
sb.AppendLine("}");
sb.AppendLine("#pragma warning restore");

string classTemp = $"public partial class $className {{ $body }}";

foreach (ClassDeclarationSyntax node in nodes.AsEnumerable().Cast<ClassDeclarationSyntax>())
{


AttributeSyntax? attributeSyntax = null;
foreach (var attr in node.AttributeLists.AsEnumerable())
{
var attrName = attr.Attributes.FirstOrDefault()?.Name.ToString();
if (attrName == AttributeValueMetadataName_Dto)
{
attributeSyntax = attr.Attributes.First(x => x.Name.ToString() == AttributeValueMetadataName_Dto);
break;
}
}
if (attributeSyntax == null)
{
continue;
}

{
//转译的Entity类名
var entityName = string.Empty;
var eType = (attributeSyntax.ArgumentList!.Arguments[0].Expression as TypeOfExpressionSyntax)!.Type;
if (eType.IsKind(SyntaxKind.IdentifierName))
{
entityName = (eType as IdentifierNameSyntax)!.Identifier.ValueText;
}
else if (eType.IsKind(SyntaxKind.QualifiedName))
{
entityName = (eType as QualifiedNameSyntax)!.ToString().Split(['.']).Last();
}
else if (eType.IsKind(SyntaxKind.AliasQualifiedName))
{
entityName = (eType as AliasQualifiedNameSyntax)!.ToString().Split(['.']).Last();
}
if (string.IsNullOrEmpty(entityName))
{
continue;
}
//排除的属性
List<string> Excapes = [];
for (var i = 1; i < attributeSyntax.ArgumentList.Arguments.Count; i++)
{
var expressionSyntax = attributeSyntax.ArgumentList.Arguments[i].Expression;
if (expressionSyntax.IsKind(SyntaxKind.InvocationExpression))
{
var name = (expressionSyntax as InvocationExpressionSyntax)!.ArgumentList.DescendantNodes().First().ToString();
Excapes.Add(name.Split(['.']).Last());
}
else if ((expressionSyntax.IsKind(SyntaxKind.StringLiteralExpression)))
{
var name = (expressionSyntax as LiteralExpressionSyntax)!.Token.ValueText;
Excapes.Add(name);
}
}

var className = node.Identifier.ValueText;
var rootNamespace = node.AncestorsAndSelf().OfType<NamespaceDeclarationSyntax>().Single().Name.ToString();

StringBuilder bodyBuilder = new();
List<string> namespaces = [];
StringBuilder bodyInnerBuilder = new();

//生成属性
void genProperty(TypeSyntax @type)
{
var symbols = compilation.GetSymbolsWithName(type.ToString());
foreach (ITypeSymbol symbol in symbols.Cast<ITypeSymbol>())
{
var fullNameSpace = symbol.ContainingNamespace.ToDisplayString();
//命名空间
if (!namespaces.Contains(fullNameSpace))
{
namespaces.Add(fullNameSpace);
}
symbol.GetMembers().OfType<IPropertySymbol>().ToList().ForEach(prop =>
{
if (!Excapes.Contains(prop.Name))
{
//prop:
var raw = $"public {prop.Type.ToDisplayString()} {prop.Name} {{get;set;}}";
//body:
bodyInnerBuilder.AppendLine($"/// <inheritdoc cref=\"{@type}.{prop.Name}\" />");
bodyInnerBuilder.AppendLine($"{raw}");
}
});
}
}

//生成属性:
var symbols = compilation.GetSymbolsWithName(entityName, SymbolFilter.Type);
var symbol = symbols.Cast<ITypeSymbol>().FirstOrDefault();
genProperty(SyntaxFactory.ParseTypeName(symbol.MetadataName));

//生成父类的属性:
INamedTypeSymbol? baseType = symbol.BaseType;
while (baseType != null)
{
genProperty(SyntaxFactory.ParseTypeName(baseType.MetadataName));
baseType = baseType.BaseType;
}

var rawClass = classTemp.Replace("$className", className);
rawClass = rawClass.Replace("$body", bodyInnerBuilder.ToString());
//append:
bodyBuilder.AppendLine(rawClass);

string rawNamespace = string.Empty;
namespaces.ToList().ForEach(ns => rawNamespace += $"using {ns};\r\n");

var source = sb.ToString();
source = source.Replace("$namespace", rawNamespace);
source = source.Replace("$classes", bodyBuilder.ToString());
source = source.Replace("$ni", rootNamespace);
//format:
source = FormatContent(source);
context.AddSource($"Biwen.AutoClassGenDto.{className}.{node.Identifier.Text}.g.cs", SourceText.From(source, Encoding.UTF8));
}
}
}

/// <summary>
/// 格式化代码
/// </summary>
Expand Down
17 changes: 17 additions & 0 deletions Biwen.AutoClassGen.TestConsole/Dtos/UserDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Biwen.AutoClassGen.TestConsole.Entitys;


#pragma warning disable
namespace Biwen.AutoClassGen.TestConsole.Dtos
{

/// <summary>
/// to be generated
/// </summary>
[AutoDto(typeof(User), nameof(User.Id), "TestCol")]
public partial class UserDto
{

}
}
#pragma warning restore
47 changes: 47 additions & 0 deletions Biwen.AutoClassGen.TestConsole/Entitys/User.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
namespace Biwen.AutoClassGen.TestConsole.Entitys
{
public class User : Info
{
/// <summary>
/// Id
/// </summary>
public string Id { get; set; } = null!;
/// <summary>
/// first name
/// </summary>
public string FirstName { get; set; } = null!;

/// <summary>
/// last name
/// </summary>
public string LastName { get; set; } = null!;

/// <summary>
/// age
/// </summary>
public int? Age { get; set; }

/// <summary>
/// fullname
/// </summary>
public string? FullName => $"{FirstName} {LastName}";

}

public abstract class Info : Other
{
/// <summary>
/// email
/// </summary>
public string? Email { get; set; }
}

public abstract class Other
{
/// <summary>
/// remark
/// </summary>
public string? Remark { get; set; }
}

}
10 changes: 9 additions & 1 deletion Biwen.AutoClassGen.TestConsole/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,15 @@
KeyWord = "biwen"
};

Console.WriteLine($"{queryRequest.KeyWord}");
Biwen.AutoClassGen.TestConsole.Dtos.UserDto userDto = new()
{
FirstName = "biwen",
LastName = "wan",
Age = 18,
};


Console.WriteLine($"{queryRequest.KeyWord}");
Console.WriteLine($"I`m {userDto.FirstName} {userDto.LastName} I`m {userDto.Age} years old");

Console.ReadLine();

0 comments on commit aff03d3

Please sign in to comment.