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.LogAttribute"; /// /// 初始化增量生成器 /// /// 增量生成器初始化上下文 public void Initialize(IncrementalGeneratorInitializationContext context) { // 查找所有类声明语法节点,并获取对应的符号信息 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); // 筛选出带有指定属性标记的类 var targets = candidates.Where(x => x.Symbol!.GetAttributes().Any(a => { var c = a.AttributeClass; if (c is null) return false; return c.ToDisplayString() == AttributeMetadataName || c.Name == "LogAttribute"; })); // 注册源代码输出,为符合条件的类生成日志相关的源代码 context.RegisterSourceOutput(targets, (spc, pair) => { var classDecl = pair.ClassDecl; var classSymbol = pair.Symbol!; if (!classDecl.Modifiers.Any(SyntaxKind.PartialKeyword)) { spc.ReportDiagnostic( Diagnostic.Create( Diagnostics.MustBePartial, classDecl.Identifier.GetLocation(), classSymbol.Name)); return; } var source = Generate(classSymbol, spc); spc.AddSource($"{classSymbol.Name}.Logger.g.cs", SourceText.From(source, Encoding.UTF8)); }); } /// /// 生成日志字段代码 /// /// 类符号 /// 源代码生成上下文 /// 生成的代码字符串 private static string Generate(INamedTypeSymbol classSymbol, SourceProductionContext spc) { var ns = classSymbol.ContainingNamespace.IsGlobalNamespace ? null : classSymbol.ContainingNamespace.ToDisplayString(); var className = classSymbol.Name; var attr = classSymbol.GetAttributes() .FirstOrDefault(a => a.AttributeClass?.ToDisplayString() == AttributeMetadataName); if (attr is null) { // 理论上不会发生,但防御式处理 spc.ReportDiagnostic( Diagnostic.Create( Diagnostics.LogAttributeInvalid, classSymbol.Locations.FirstOrDefault(), className, "未找到 LogAttribute" )); return string.Empty; } // === 解析 category === string category; if (attr.ConstructorArguments.Length == 0) { // 默认:类名 category = className; } else if (attr.ConstructorArguments[0].Value is string s && !string.IsNullOrWhiteSpace(s)) { category = s; } else { spc.ReportDiagnostic( Diagnostic.Create( Diagnostics.LogAttributeInvalid, classSymbol.Locations.FirstOrDefault(), className, "LogAttribute 的构造参数不是有效的 string" )); return string.Empty; } 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(\"{category}\");"); 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; } } }