using System.Linq; using System.Text; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; namespace GFramework.Generator.generator.logging { /// /// 日志生成器,用于为标记了LogAttribute的类自动生成日志字段 /// [Generator] public sealed class LoggerGenerator : IIncrementalGenerator { private const string AttributeMetadataName = "GFramework.Generator.Attributes.Logging.LogAttribute"; /// /// 初始化增量生成器 /// /// 增量生成器初始化上下文 public void Initialize(IncrementalGeneratorInitializationContext context) { // 1. 拿到 LogAttribute Symbol var logAttributeSymbol = context.CompilationProvider.Select((compilation, _) => compilation.GetTypeByMetadataName(AttributeMetadataName)); // 2. 在 SyntaxProvider 阶段就拿到 SemanticModel var candidates = context.SyntaxProvider.CreateSyntaxProvider( static (node, _) => node is ClassDeclarationSyntax, static (ctx, _) => { var classDecl = (ClassDeclarationSyntax)ctx.Node; var symbol = ctx.SemanticModel.GetDeclaredSymbol(classDecl); return (ClassDecl: classDecl, Symbol: symbol); }) .Where(x => x.Symbol is not null); // 3. 合并 Attribute Symbol 并筛选 var targets = candidates.Combine(logAttributeSymbol) .Where(pair => { var symbol = pair.Left.Symbol!; var attrSymbol = pair.Right; if (attrSymbol is null) return false; return symbol.GetAttributes().Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, attrSymbol)); }); // 4. 输出代码 context.RegisterSourceOutput(targets, (spc, pair) => { var classDecl = pair.Left.ClassDecl; var classSymbol = pair.Left.Symbol!; // 必须是 partial if (!classDecl.Modifiers.Any(SyntaxKind.PartialKeyword)) { spc.ReportDiagnostic( Diagnostic.Create( Diagnostics.MustBePartial, classDecl.Identifier.GetLocation(), classSymbol.Name )); return; } var source = Generate(classSymbol); spc.AddSource( $"{classSymbol.Name}.Logger.g.cs", SourceText.From(source, Encoding.UTF8)); }); } /// /// 生成日志字段代码 /// /// 类符号 /// 生成的代码字符串 private static string Generate(INamedTypeSymbol classSymbol) { var ns = classSymbol.ContainingNamespace.IsGlobalNamespace ? null : classSymbol.ContainingNamespace.ToDisplayString(); var className = classSymbol.Name; var attr = classSymbol.GetAttributes() .First(a => a.AttributeClass!.ToDisplayString() == AttributeMetadataName); string categoryExpr; if (attr.ConstructorArguments.Length > 0 && attr.ConstructorArguments[0].Value is string s && !string.IsNullOrWhiteSpace(s)) { // 用户显式指定字符串 categoryExpr = $"\"{s}\""; } else { // 默认使用 nameof(Class) categoryExpr = $"nameof({className})"; } var fieldName = GetNamedArg(attr, "FieldName", "_log"); var access = GetNamedArg(attr, "AccessModifier", "private"); var isStatic = GetNamedArg(attr, "IsStatic", true); var staticKeyword = isStatic ? "static " : ""; var sb = new StringBuilder(); sb.AppendLine("// "); sb.AppendLine("using GFramework.Core.logging;"); if (ns is not null) { sb.AppendLine($"namespace {ns}"); sb.AppendLine("{"); } sb.AppendLine($" public partial class {className}"); sb.AppendLine(" {"); sb.AppendLine( $" {access} {staticKeyword}readonly ILog {fieldName} =" + $" Log.CreateLogger(\"{categoryExpr}\");"); sb.AppendLine(" }"); if (ns is not null) sb.AppendLine("}"); return sb.ToString(); } /// /// 获取属性参数的值 /// /// 参数类型 /// 属性数据 /// 参数名称 /// 默认值 /// 参数值或默认值 private static T GetNamedArg(AttributeData attr, string name, T defaultValue) { foreach (var kv in attr.NamedArguments) { if (kv.Key == name && kv.Value.Value is T v) return v; } return defaultValue; } } }