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;
}