using System; using System.Linq; using System.Text; using GFramework.SourceGenerators.Common.constants; using GFramework.SourceGenerators.Common.diagnostics; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; namespace GFramework.Godot.SourceGenerators.logging; /// /// 日志生成器,用于为标记了GodotLogAttribute的类自动生成日志字段 /// [Generator] public sealed class GodotLoggerGenerator : IIncrementalGenerator { private const string AttributeMetadataName = $"{PathContests.GodotSourceGeneratorsAbstractionsPath}.logging.GodotLogAttribute"; private const string AttributeShortNameWithoutSuffix = "GodotLog"; /// /// 初始化源生成器,设置语法提供程序和源输出注册 /// /// 增量生成器初始化上下文 public void Initialize(IncrementalGeneratorInitializationContext context) { // 创建语法提供程序,用于查找标记了GodotLogAttribute的类声明 var targets = context.SyntaxProvider .CreateSyntaxProvider( static (node, _) => { if (node is not ClassDeclarationSyntax cls) return false; return cls.AttributeLists .SelectMany(a => a.Attributes) .Any(a => a.Name.ToString().Contains(AttributeShortNameWithoutSuffix)); }, static (ctx, _) => { var cls = (ClassDeclarationSyntax)ctx.Node; var sym = ctx.SemanticModel.GetDeclaredSymbol(cls); return (ClassDecl: cls, Symbol: sym); }) .Where(x => x.Symbol is not null); // 注册源输出,为符合条件的类生成日志字段代码 context.RegisterSourceOutput(targets, (spc, pair) => { try { var classDecl = pair.ClassDecl; var classSymbol = pair.Symbol!; var attr = GetAttribute(classSymbol); if (attr is null) return; if (!classDecl.Modifiers.Any(SyntaxKind.PartialKeyword)) { spc.ReportDiagnostic(Diagnostic.Create( CommonDiagnostics.ClassMustBePartial, classDecl.Identifier.GetLocation(), classSymbol.Name)); return; } var source = Generate(classSymbol, attr); spc.AddSource($"{classSymbol.Name}.Logger.g.cs", SourceText.From(source, Encoding.UTF8)); } catch (Exception ex) { var safeName = pair.Symbol?.Name ?? "Unknown"; var errorSource = $"// Source generator error: {ex.Message}\n// StackTrace:\n// {ex.StackTrace}"; spc.AddSource($"{safeName}.Logger.Error.g.cs", SourceText.From(errorSource, Encoding.UTF8)); } }); } /// /// 获取类符号上的GodotLogAttribute属性数据 /// /// 类符号 /// GodotLogAttribute属性数据,如果未找到则返回null private static AttributeData? GetAttribute(INamedTypeSymbol classSymbol) => classSymbol.GetAttributes().FirstOrDefault(a => { var cls = a.AttributeClass; return cls != null && (cls.ToDisplayString() == AttributeMetadataName || cls.Name.StartsWith(AttributeShortNameWithoutSuffix)); }); /// /// 生成日志字段的源代码 /// /// 类符号 /// GodotLogAttribute属性数据 /// 生成的源代码字符串 private static string Generate(INamedTypeSymbol classSymbol, AttributeData attr) { var ns = classSymbol.ContainingNamespace.IsGlobalNamespace ? null : classSymbol.ContainingNamespace.ToDisplayString(); var className = classSymbol.Name; // 解析构造函数参数 var name = className; if (attr.ConstructorArguments.Length > 0 && attr.ConstructorArguments[0].Value is string s && !string.IsNullOrWhiteSpace(s)) { name = s; } // 解析命名参数 var fieldName = GetNamedArg(attr, "FieldName")?.ToString() ?? "_log"; var access = GetNamedArg(attr, "AccessModifier")?.ToString() ?? "private"; var isStatic = GetNamedArg(attr, "IsStatic") is not bool b || b; var staticKeyword = isStatic ? "static " : ""; var sb = new StringBuilder(); sb.AppendLine("// "); sb.AppendLine($"using {PathContests.CoreAbstractionsNamespace}.logging;"); sb.AppendLine($"using {PathContests.GodotNamespace}.logging;"); sb.AppendLine(); if (ns is not null) { sb.AppendLine($"namespace {ns}"); sb.AppendLine("{"); } sb.AppendLine($" public partial class {className}"); sb.AppendLine(" {"); sb.AppendLine(" /// Auto-generated logger"); sb.AppendLine( $" {access} {staticKeyword}readonly ILogger {fieldName} = new GodotLoggerFactory().GetLogger(\"{name}\");"); sb.AppendLine(" }"); if (ns is not null) sb.AppendLine("}"); return sb.ToString(); } /// /// 从属性数据中获取指定名称的命名参数值 /// /// 属性数据 /// 参数名称 /// 参数值,如果未找到则返回null private static object? GetNamedArg(AttributeData attr, string name) => attr.NamedArguments.FirstOrDefault(kv => kv.Key == name).Value.Value; }