diff --git a/Biwen.AutoClassGen.Attributes/AutoDtoAttribute.cs b/Biwen.AutoClassGen.Attributes/AutoDtoAttribute.cs index fae4f49..74346f2 100644 --- a/Biwen.AutoClassGen.Attributes/AutoDtoAttribute.cs +++ b/Biwen.AutoClassGen.Attributes/AutoDtoAttribute.cs @@ -21,4 +21,22 @@ public AutoDtoAttribute(Type fromType, params string[] excludeProps) ExcludeProps = excludeProps; } } + +#if NET7_0_OR_GREATER + + /// + /// 自动创建Dto + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] +#pragma warning disable SA1402 // File may only contain a single type + public class AutoDtoAttribute : AutoDtoAttribute +#pragma warning restore SA1402 // File may only contain a single type + { + public AutoDtoAttribute(params string[] excludeProps) : base(typeof(T), excludeProps) + { + } + } + +#endif + } \ No newline at end of file diff --git a/Biwen.AutoClassGen.Attributes/Biwen.AutoClassGen.Attributes.csproj b/Biwen.AutoClassGen.Attributes/Biwen.AutoClassGen.Attributes.csproj index e5987e1..971d645 100644 --- a/Biwen.AutoClassGen.Attributes/Biwen.AutoClassGen.Attributes.csproj +++ b/Biwen.AutoClassGen.Attributes/Biwen.AutoClassGen.Attributes.csproj @@ -1,7 +1,7 @@  - netstandard2.0 + netstandard2.0;net7.0 1.1.0.3 https://github.com/vipwan/Biwen.AutoClassGen 万雅虎 @@ -32,7 +32,7 @@ - + all diff --git a/Biwen.AutoClassGen.Gen/SourceGenAnalyzer.cs b/Biwen.AutoClassGen.Gen/SourceGenAnalyzer.cs index 7a66ef6..22c3e2f 100644 --- a/Biwen.AutoClassGen.Gen/SourceGenAnalyzer.cs +++ b/Biwen.AutoClassGen.Gen/SourceGenAnalyzer.cs @@ -1,5 +1,6 @@ namespace Biwen.AutoClassGen { + using System; using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis; @@ -18,6 +19,7 @@ public class SourceGenAnalyzer : DiagnosticAnalyzer public const string GEN011 = "GEN011"; public const string GEN021 = "GEN021"; public const string GEN031 = "GEN031"; // 推荐生成 + public const string GEN041 = "GEN041"; // 重复标注 /// /// 无法生成类的错误 @@ -53,7 +55,6 @@ public class SourceGenAnalyzer : DiagnosticAnalyzer helpLinkUri: Helplink, isEnabledByDefault: true); - /// /// 推荐使用自动生成 /// @@ -66,15 +67,35 @@ public class SourceGenAnalyzer : DiagnosticAnalyzer isEnabledByDefault: true); + /// + /// Dto特性重复标注 + /// + private static readonly DiagnosticDescriptor MutiMarkedAutoDtoError = new(id: GEN041, + title: "重复标注[AutoDto]", + messageFormat: "重复标注了[AutoDto],请删除多余的标注", + category: typeof(SourceGenerator).Assembly.GetName().Name, + DiagnosticSeverity.Error, + helpLinkUri: Helplink, + isEnabledByDefault: true); + + + + #endregion public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create( InvalidDeclareError, InvalidDeclareNameError, SuggestDeclareNameWarning, - SuggestAutoGen); + SuggestAutoGen, + MutiMarkedAutoDtoError); private const string AttributeValueMetadataName = "AutoGen"; + /// + /// Dto特性名称,注意存在泛型的情况 + /// + private const string AttributeValueMetadataNameDto = "AutoDto"; + public override void Initialize(AnalysisContext context) { @@ -83,67 +104,92 @@ public override void Initialize(AnalysisContext context) context.EnableConcurrentExecution(); context.RegisterSyntaxNodeAction(ctx => { - // Find implicitly typed interface declarations. - var declaration = (InterfaceDeclarationSyntax)ctx.Node; - if (declaration == null) return; - - if (declaration.AttributeLists.Count > 0) + var kind = ctx.Node.Kind(); + // InterfaceDeclarationSyntax + if (kind == SyntaxKind.InterfaceDeclaration) { - foreach (var attr in declaration.AttributeLists.AsEnumerable()) + var declaration = (InterfaceDeclarationSyntax)ctx.Node; + + if (declaration.AttributeLists.Count > 0) { - if (attr.Attributes.Any(x => x.Name.ToString() == AttributeValueMetadataName)) + foreach (var attr in declaration.AttributeLists.AsEnumerable()) { - if (declaration.BaseList == null || !declaration.BaseList.Types.Any()) + if (attr.Attributes.Any(x => x.Name.ToString() == AttributeValueMetadataName)) { - // issue error - ctx.ReportDiagnostic(Diagnostic.Create(InvalidDeclareError, attr.GetLocation())); - } - - var arg0 = attr.Attributes.First(x => x.Name.ToString() == AttributeValueMetadataName) - .ArgumentList!.Arguments[0]; - - var arg1 = attr.Attributes.First(x => x.Name.ToString() == AttributeValueMetadataName) - .ArgumentList!.Arguments[1]; - - if (declaration.Identifier.Text == arg0.GetText().ToString().Replace("\"", "")) - { - var location = arg0?.GetLocation(); - // issue error - ctx.ReportDiagnostic(Diagnostic.Create(InvalidDeclareNameError, location)); - } - - // NamespaceDeclarationSyntax - if (declaration.Parent is NamespaceDeclarationSyntax @namespace && - @namespace?.Name.ToString() != arg1?.GetText().ToString().Replace("\"", "")) - { - var location = arg1?.GetLocation(); - // issue warning - ctx.ReportDiagnostic(Diagnostic.Create(SuggestDeclareNameWarning, location)); - } - // FileScopedNamespaceDeclaration - if (declaration.Parent is FileScopedNamespaceDeclarationSyntax @namespace2 && - @namespace2?.Name.ToString() != arg1?.GetText().ToString().Replace("\"", "")) - { - var location = arg1?.GetLocation(); - // issue warning - ctx.ReportDiagnostic(Diagnostic.Create(SuggestDeclareNameWarning, location)); + if (declaration.BaseList == null || !declaration.BaseList.Types.Any()) + { + // issue error + ctx.ReportDiagnostic(Diagnostic.Create(InvalidDeclareError, attr.GetLocation())); + } + + var arg0 = attr.Attributes.First(x => x.Name.ToString() == AttributeValueMetadataName) + .ArgumentList!.Arguments[0]; + + var arg1 = attr.Attributes.First(x => x.Name.ToString() == AttributeValueMetadataName) + .ArgumentList!.Arguments[1]; + + if (declaration.Identifier.Text == arg0.GetText().ToString().Replace("\"", "")) + { + var location = arg0?.GetLocation(); + // issue error + ctx.ReportDiagnostic(Diagnostic.Create(InvalidDeclareNameError, location)); + } + + // NamespaceDeclarationSyntax + if (declaration.Parent is NamespaceDeclarationSyntax @namespace && + @namespace?.Name.ToString() != arg1?.GetText().ToString().Replace("\"", "")) + { + var location = arg1?.GetLocation(); + // issue warning + ctx.ReportDiagnostic(Diagnostic.Create(SuggestDeclareNameWarning, location)); + } + // FileScopedNamespaceDeclaration + if (declaration.Parent is FileScopedNamespaceDeclarationSyntax @namespace2 && + @namespace2?.Name.ToString() != arg1?.GetText().ToString().Replace("\"", "")) + { + var location = arg1?.GetLocation(); + // issue warning + ctx.ReportDiagnostic(Diagnostic.Create(SuggestDeclareNameWarning, location)); + } } } } + // suggest + if (declaration.BaseList != null && declaration.BaseList.Types.Any(x => x.IsKind(SyntaxKind.SimpleBaseType))) + { + var haveAttr = declaration.AttributeLists.Any(x => x.Attributes.Any(x => x.Name.ToString() == AttributeValueMetadataName)); + if (!haveAttr) + { + var location = declaration.GetLocation(); + // issue suggest + ctx.ReportDiagnostic(Diagnostic.Create(SuggestAutoGen, location)); + } + } } - - // suggest - if (declaration.BaseList != null && declaration.BaseList.Types.Any(x => x.IsKind(SyntaxKind.SimpleBaseType))) + // ClassDeclarationSyntax + if (kind == SyntaxKind.ClassDeclaration) { - var haveAttr = declaration.AttributeLists.Any(x => x.Attributes.Any(x => x.Name.ToString() == AttributeValueMetadataName)); - if (!haveAttr) + var declaration = (ClassDeclarationSyntax)ctx.Node; + if (declaration.AttributeLists.Count > 0) { - var location = declaration.GetLocation(); - // issue suggest - ctx.ReportDiagnostic(Diagnostic.Create(SuggestAutoGen, location)); + foreach (var attr in declaration.AttributeLists.AsEnumerable()) + { + if (attr.Attributes.Where(x => x.Name.ToString().IndexOf( + AttributeValueMetadataNameDto, StringComparison.Ordinal) == 0).Count() > 1) + { + var location = declaration.GetLocation(); + + // issue error + ctx.ReportDiagnostic(Diagnostic.Create(MutiMarkedAutoDtoError, location)); + } + } } } - }, SyntaxKind.InterfaceDeclaration); + + }, + SyntaxKind.InterfaceDeclaration, + SyntaxKind.ClassDeclaration); + } } } \ No newline at end of file diff --git a/Biwen.AutoClassGen.Gen/SourceGenerator.cs b/Biwen.AutoClassGen.Gen/SourceGenerator.cs index ddcc731..2c7fe64 100644 --- a/Biwen.AutoClassGen.Gen/SourceGenerator.cs +++ b/Biwen.AutoClassGen.Gen/SourceGenerator.cs @@ -8,6 +8,7 @@ namespace Biwen.AutoClassGen using System.Collections.Immutable; using System.Linq; using System.Text; + using System.Text.RegularExpressions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -22,6 +23,13 @@ public class SourceGenerator : IIncrementalGenerator private const string AttributeMetadataNameDto = "Biwen.AutoClassGen.Attributes.AutoDtoAttribute"; private const string AttributeValueMetadataNameDto = "AutoDto"; + + /// + /// 泛型AutoDtoAttribute + /// + private const string AttributeMetadataNameDtoG = "Biwen.AutoClassGen.Attributes.AutoDtoAttribute`1"; + + public void Initialize(IncrementalGeneratorInitializationContext context) { @@ -54,6 +62,22 @@ public void Initialize(IncrementalGeneratorInitializationContext context) context.RegisterSourceOutput(compilationAndTypesDto, static (spc, source) => HandleAnnotatedNodesDto(source.Item1, source.Item2, spc)); #endregion + + + #region AutoDtoAttributeG + + var nodesDtoG = context.SyntaxProvider.ForAttributeWithMetadataName( + AttributeMetadataNameDtoG, + (context, attributeSyntax) => true, + (syntaxContext, _) => syntaxContext.TargetNode).Collect(); + + IncrementalValueProvider<(Compilation, ImmutableArray)> compilationAndTypesDtoG = + context.CompilationProvider.Combine(nodesDtoG); + + context.RegisterSourceOutput(compilationAndTypesDtoG, static (spc, source) => HandleAnnotatedNodesDtoG(source.Item1, source.Item2, spc)); + + #endregion + } @@ -65,9 +89,9 @@ public void Initialize(IncrementalGeneratorInitializationContext context) /// private static void HandleAnnotatedNodes(Compilation compilation, ImmutableArray nodes, SourceProductionContext context) { - StringBuilder envStringBuilder = new(); - + if (nodes.Length == 0) return; + StringBuilder envStringBuilder = new(); envStringBuilder.AppendLine("// "); envStringBuilder.AppendLine("// author:vipwan@outlook.com 万雅虎"); @@ -217,6 +241,7 @@ void GenProperty(TypeSyntax @interfaceType) /// private static void HandleAnnotatedNodesDto(Compilation compilation, ImmutableArray nodes, SourceProductionContext context) { + if (nodes.Length == 0) return; StringBuilder envStringBuilder = new(); @@ -385,6 +410,179 @@ void GenProperty(TypeSyntax @type) context.AddSource($"Biwen.AutoClassGenDto.g.cs", SourceText.From(envSource, Encoding.UTF8)); } + + /// + /// Gen AutoDtoAttribute G + /// + /// + /// + /// + private static void HandleAnnotatedNodesDtoG(Compilation compilation, ImmutableArray nodes, SourceProductionContext context) + { + if(nodes.Length == 0) return; + + StringBuilder envStringBuilder = new(); + + envStringBuilder.AppendLine("// "); + envStringBuilder.AppendLine("// author:vipwan@outlook.com 万雅虎"); + 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("using System;"); + envStringBuilder.AppendLine("using System.Collections.Generic;"); + envStringBuilder.AppendLine("using System.Text;"); + envStringBuilder.AppendLine("using System.Threading.Tasks;"); + envStringBuilder.AppendLine("#pragma warning disable"); + + 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?.IndexOf(AttributeValueMetadataNameDto, System.StringComparison.Ordinal) == 0) + { + attributeSyntax = attr.Attributes.First(x => x.Name.ToString().IndexOf(AttributeValueMetadataNameDto, System.StringComparison.Ordinal) == 0); + break; + } + } + if (attributeSyntax == null) + { + continue; + } + + + //转译的Entity类名 + var entityName = string.Empty; + + string pattern = @"(?<=<)(?\w+)(?=>)"; + var match = Regex.Match(attributeSyntax.ToString(), pattern); + if (match.Success) + { + entityName = match.Groups["type"].Value.Split(['.']).Last(); + } + else + { + continue; + } + + var sb = new StringBuilder(); + sb.AppendLine(); + sb.AppendLine($"//generate {entityName}-{node.Identifier.ValueText}"); + sb.AppendLine(); + sb.AppendLine("namespace $ni"); + sb.AppendLine("{"); + sb.AppendLine("$namespace"); + sb.AppendLine("$classes"); + sb.AppendLine("}"); + // sb.AppendLine("#pragma warning restore"); + string classTemp = $"partial class $className {{ $body }}"; + + { + // 排除的属性 + List excapes = []; + for (var i = 0; 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(); + StringBuilder mapperBodyBuilder = new(); + + bodyInnerBuilder.AppendLine(); + + // 生成属性 + 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}"); + + // mapper: + mapperBodyBuilder.AppendLine($"{prop.Name} = model.{prop.Name},"); + } + }); + } + } + + // 生成属性: + 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); + + // 生成Mapper + var mapperSource = MapperTemplate.Replace("$namespace", namespaces.First()); + mapperSource = mapperSource.Replace("$ns", rootNamespace); + mapperSource = mapperSource.Replace("$baseclass", entityName); + mapperSource = mapperSource.Replace("$dtoclass", className); + mapperSource = mapperSource.Replace("$body", mapperBodyBuilder.ToString()); + + // 合并 + source = $"{source}\r\n{mapperSource}"; + envStringBuilder.AppendLine(source); + } + } + + envStringBuilder.AppendLine("#pragma warning restore"); + var envSource = envStringBuilder.ToString(); + // format: + envSource = FormatContent(envSource); + context.AddSource($"Biwen.AutoClassGenDtoG.g.cs", SourceText.From(envSource, Encoding.UTF8)); + } + + #region Template public static readonly string MapperTemplate = $@" diff --git a/Biwen.AutoClassGen.TestConsole/Biwen.AutoClassGen.TestConsole.csproj b/Biwen.AutoClassGen.TestConsole/Biwen.AutoClassGen.TestConsole.csproj index 7f8d93a..3935225 100644 --- a/Biwen.AutoClassGen.TestConsole/Biwen.AutoClassGen.TestConsole.csproj +++ b/Biwen.AutoClassGen.TestConsole/Biwen.AutoClassGen.TestConsole.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + net7.0 enable enable diff --git a/Biwen.AutoClassGen.TestConsole/Dtos/UserDto.cs b/Biwen.AutoClassGen.TestConsole/Dtos/UserDto.cs index 4ce79df..e19ee95 100644 --- a/Biwen.AutoClassGen.TestConsole/Dtos/UserDto.cs +++ b/Biwen.AutoClassGen.TestConsole/Dtos/UserDto.cs @@ -18,9 +18,19 @@ public partial class UserDto /// to be generated more than one /// [AutoDto(typeof(User), nameof(User.Email))] + public partial class User2Dto { } + + /// + /// another way to be generated + /// + [AutoDto(nameof(User.Email), "TestCol")] + public partial class User3Dto + { + } + } namespace Biwen.AutoClassGen.TestConsole.Entitys diff --git a/Biwen.AutoClassGen.TestConsole/Program.cs b/Biwen.AutoClassGen.TestConsole/Program.cs index 494a136..c0092d2 100644 --- a/Biwen.AutoClassGen.TestConsole/Program.cs +++ b/Biwen.AutoClassGen.TestConsole/Program.cs @@ -1,4 +1,5 @@ using Biwen.AutoClassGen; +using Biwen.AutoClassGen.TestConsole.Dtos; using Biwen.AutoClassGen.TestConsole.Entitys; @@ -21,6 +22,7 @@ Age = 18, }; + var user = new User { Age = 18, @@ -36,7 +38,15 @@ //mapper to User2Dto var user2Dto = user.MapperToUser2Dto(); +//from [AutoDto(params string?[])] +var user3Dto = user.MapperToUser3Dto(); + + Console.WriteLine($"{queryRequest.KeyWord}"); -Console.WriteLine($"I`m {userDto.FirstName} {userDto.LastName} I`m {userDto.Age} years old"); +Console.WriteLine($"I`m {nameof(userDto)} {userDto.FirstName} {userDto.LastName} I`m {userDto.Age} years old"); +Console.WriteLine($"I`m {nameof(user2Dto)} {user2Dto.FirstName} {user2Dto.LastName} I`m {user2Dto.Age} years old"); +Console.WriteLine($"I`m {nameof(user3Dto)} {user3Dto.FirstName} {user3Dto.LastName} I`m {user3Dto.Age} years old"); + + Console.ReadLine(); \ No newline at end of file