diff --git a/Biwen.AutoClassGen.Attributes/AutoDtoAttribute.cs b/Biwen.AutoClassGen.Attributes/AutoDtoAttribute.cs new file mode 100644 index 0000000..fae4f49 --- /dev/null +++ b/Biwen.AutoClassGen.Attributes/AutoDtoAttribute.cs @@ -0,0 +1,24 @@ +using System; + +namespace Biwen.AutoClassGen.Attributes +{ + /// + /// 自动创建Dto + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] + public class AutoDtoAttribute : Attribute + { + /// + /// 从指定类型创建 + /// + public Type FromType { get; private set; } + + public string[] ExcludeProps { get; private set; } + + public AutoDtoAttribute(Type fromType, params string[] excludeProps) + { + FromType = fromType; + ExcludeProps = excludeProps; + } + } +} \ No newline at end of file diff --git a/Biwen.AutoClassGen.Gen/SourceGenerator.cs b/Biwen.AutoClassGen.Gen/SourceGenerator.cs index 7cb83f0..88c06a3 100644 --- a/Biwen.AutoClassGen.Gen/SourceGenerator.cs +++ b/Biwen.AutoClassGen.Gen/SourceGenerator.cs @@ -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; @@ -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, @@ -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)> compilationAndTypesDto = + context.CompilationProvider.Combine(nodesDto); + + context.RegisterSourceOutput(compilationAndTypesDto, static (spc, source) => HandleAnnotatedNodesDto(source.Item1, source.Item2, spc)); + + #endregion } + + /// + /// Gen AutoGenAttribute + /// + /// + /// + /// private static void HandleAnnotatedNodes(Compilation compilation, ImmutableArray nodes, SourceProductionContext context) { var sb = new StringBuilder(); @@ -174,6 +205,160 @@ void genProperty(TypeSyntax @interfaceType) } } + /// + /// Gen AutoDtoAttribute + /// + /// + /// + /// + private static void HandleAnnotatedNodesDto(Compilation compilation, ImmutableArray nodes, SourceProductionContext context) + { + var sb = new StringBuilder(); + sb.AppendLine("// "); + sb.AppendLine("// author:vipwan@outlook.com 万雅虎"); + 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()) + { + + + 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 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().Single().Name.ToString(); + + StringBuilder bodyBuilder = new(); + List namespaces = []; + StringBuilder bodyInnerBuilder = new(); + + //生成属性 + void genProperty(TypeSyntax @type) + { + var symbols = compilation.GetSymbolsWithName(type.ToString()); + foreach (ITypeSymbol symbol in symbols.Cast()) + { + var fullNameSpace = symbol.ContainingNamespace.ToDisplayString(); + //命名空间 + if (!namespaces.Contains(fullNameSpace)) + { + namespaces.Add(fullNameSpace); + } + symbol.GetMembers().OfType().ToList().ForEach(prop => + { + if (!Excapes.Contains(prop.Name)) + { + //prop: + var raw = $"public {prop.Type.ToDisplayString()} {prop.Name} {{get;set;}}"; + //body: + bodyInnerBuilder.AppendLine($"/// "); + bodyInnerBuilder.AppendLine($"{raw}"); + } + }); + } + } + + //生成属性: + var symbols = compilation.GetSymbolsWithName(entityName, SymbolFilter.Type); + var symbol = symbols.Cast().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)); + } + } + } + /// /// 格式化代码 /// diff --git a/Biwen.AutoClassGen.TestConsole/Dtos/UserDto.cs b/Biwen.AutoClassGen.TestConsole/Dtos/UserDto.cs new file mode 100644 index 0000000..9f99b0a --- /dev/null +++ b/Biwen.AutoClassGen.TestConsole/Dtos/UserDto.cs @@ -0,0 +1,17 @@ +using Biwen.AutoClassGen.TestConsole.Entitys; + + +#pragma warning disable +namespace Biwen.AutoClassGen.TestConsole.Dtos +{ + + /// + /// to be generated + /// + [AutoDto(typeof(User), nameof(User.Id), "TestCol")] + public partial class UserDto + { + + } +} +#pragma warning restore \ No newline at end of file diff --git a/Biwen.AutoClassGen.TestConsole/Entitys/User.cs b/Biwen.AutoClassGen.TestConsole/Entitys/User.cs new file mode 100644 index 0000000..096b96c --- /dev/null +++ b/Biwen.AutoClassGen.TestConsole/Entitys/User.cs @@ -0,0 +1,47 @@ +namespace Biwen.AutoClassGen.TestConsole.Entitys +{ + public class User : Info + { + /// + /// Id + /// + public string Id { get; set; } = null!; + /// + /// first name + /// + public string FirstName { get; set; } = null!; + + /// + /// last name + /// + public string LastName { get; set; } = null!; + + /// + /// age + /// + public int? Age { get; set; } + + /// + /// fullname + /// + public string? FullName => $"{FirstName} {LastName}"; + + } + + public abstract class Info : Other + { + /// + /// email + /// + public string? Email { get; set; } + } + + public abstract class Other + { + /// + /// remark + /// + public string? Remark { get; set; } + } + +} \ No newline at end of file diff --git a/Biwen.AutoClassGen.TestConsole/Program.cs b/Biwen.AutoClassGen.TestConsole/Program.cs index d118115..3ca679f 100644 --- a/Biwen.AutoClassGen.TestConsole/Program.cs +++ b/Biwen.AutoClassGen.TestConsole/Program.cs @@ -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(); \ No newline at end of file