refactor(logging): 重构Godot日志生成器实现

- 将日志生成器注释从LogAttribute更新为GodotLogAttribute
- 移除冗余的常量定义AttributeShortName
- 优化语法提供程序的查询逻辑,简化LINQ表达式
- 更新方法注释,明确源生成器初始化功能
- 简化属性数据获取逻辑,使用更直接的LINQ查询
- 优化代码生成部分的变量命名,提高可读性
- 改进错误处理的代码生成格式,统一命名规范
- 移除不必要的空行和注释,精简代码结构
This commit is contained in:
GwWuYou 2025-12-28 13:39:38 +08:00
parent 00704b7ec2
commit bc462987af

View File

@ -11,7 +11,7 @@ using Microsoft.CodeAnalysis.Text;
namespace GFramework.Godot.SourceGenerators.logging; namespace GFramework.Godot.SourceGenerators.logging;
/// <summary> /// <summary>
/// 日志生成器用于为标记了LogAttribute的类自动生成日志字段 /// 日志生成器,用于为标记了GodotLogAttribute的类自动生成日志字段
/// </summary> /// </summary>
[Generator] [Generator]
public sealed class GodotLoggerGenerator : IIncrementalGenerator public sealed class GodotLoggerGenerator : IIncrementalGenerator
@ -19,37 +19,33 @@ public sealed class GodotLoggerGenerator : IIncrementalGenerator
private const string AttributeMetadataName = private const string AttributeMetadataName =
$"{PathContests.RootAbstractionsPath}.logging.GodotLogAttribute"; $"{PathContests.RootAbstractionsPath}.logging.GodotLogAttribute";
private const string AttributeShortName = "GodotLogAttribute";
private const string AttributeShortNameWithoutSuffix = "GodotLog"; private const string AttributeShortNameWithoutSuffix = "GodotLog";
/// <summary> /// <summary>
/// 初始化生成器,设置语法过滤和代码生成逻辑 /// 初始化源生成器,设置语法提供程序和源输出注册
/// </summary> /// </summary>
/// <param name="context">增量生成器初始化上下文</param> /// <param name="context">增量生成器初始化上下文</param>
public void Initialize(IncrementalGeneratorInitializationContext context) public void Initialize(IncrementalGeneratorInitializationContext context)
{ {
// 1. 语法过滤:快速筛选候选类 // 创建语法提供程序用于查找标记了GodotLogAttribute的类声明
var targets = context.SyntaxProvider.CreateSyntaxProvider( var targets = context.SyntaxProvider
.CreateSyntaxProvider(
static (node, _) => static (node, _) =>
{ {
if (node is not ClassDeclarationSyntax cls) return false; if (node is not ClassDeclarationSyntax cls) return false;
// 只要包含 Log 字眼的 Attribute 就先放行 return cls.AttributeLists
return cls.AttributeLists.SelectMany(a => a.Attributes).Any(a => .SelectMany(a => a.Attributes)
{ .Any(a => a.Name.ToString().Contains(AttributeShortNameWithoutSuffix));
var name = a.Name.ToString();
// 简单的字符串匹配,防止错过别名情况
return name.Contains(AttributeShortNameWithoutSuffix);
});
}, },
static (ctx, _) => static (ctx, _) =>
{ {
var classDecl = (ClassDeclarationSyntax)ctx.Node; var cls = (ClassDeclarationSyntax)ctx.Node;
var symbol = ctx.SemanticModel.GetDeclaredSymbol(classDecl); var sym = ctx.SemanticModel.GetDeclaredSymbol(cls);
return (ClassDecl: classDecl, Symbol: symbol); return (ClassDecl: cls, Symbol: sym);
}) })
.Where(x => x.Symbol is not null); .Where(x => x.Symbol is not null);
// 2. 生成代码 // 注册源输出,为符合条件的类生成日志字段代码
context.RegisterSourceOutput(targets, (spc, pair) => context.RegisterSourceOutput(targets, (spc, pair) =>
{ {
try try
@ -57,102 +53,76 @@ public sealed class GodotLoggerGenerator : IIncrementalGenerator
var classDecl = pair.ClassDecl; var classDecl = pair.ClassDecl;
var classSymbol = pair.Symbol!; var classSymbol = pair.Symbol!;
// 再次确认是否真的含有目标 Attribute (语义检查)
var attr = GetAttribute(classSymbol); var attr = GetAttribute(classSymbol);
if (attr == null) return; // 可能是名字相似但不是我们要的 Attribute if (attr is null) return;
// 检查 partial
if (!classDecl.Modifiers.Any(SyntaxKind.PartialKeyword)) if (!classDecl.Modifiers.Any(SyntaxKind.PartialKeyword))
{ {
spc.ReportDiagnostic(Diagnostic.Create( spc.ReportDiagnostic(Diagnostic.Create(
CommonDiagnostics.ClassMustBePartial, CommonDiagnostics.ClassMustBePartial,
classDecl.Identifier.GetLocation(), classDecl.Identifier.GetLocation(),
classSymbol.Name)); classSymbol.Name));
return; return;
} }
var source = Generate(classSymbol, attr); var source = Generate(classSymbol, attr);
var hintName = $"{classSymbol.Name}.Logger.g.cs"; spc.AddSource($"{classSymbol.Name}.Logger.g.cs", SourceText.From(source, Encoding.UTF8));
spc.AddSource(hintName, SourceText.From(source, Encoding.UTF8));
} }
catch (Exception ex) catch (Exception ex)
{ {
// === 关键修复:生成错误报告文件 ===
var errorSource = $"// source generator error: {ex.Message}\n// StackTrace:\n// {ex.StackTrace}";
// 替换非法字符以防文件名报错
var safeName = pair.Symbol?.Name ?? "Unknown"; 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)); spc.AddSource($"{safeName}.Logger.Error.g.cs", SourceText.From(errorSource, Encoding.UTF8));
} }
}); });
} }
/// <summary> /// <summary>
/// 获取类符号上的LogAttribute特性 /// 获取类符号上的GodotLogAttribute属性数据
/// </summary> /// </summary>
/// <param name="classSymbol">类符号</param> /// <param name="classSymbol">类符号</param>
/// <returns>LogAttribute特性数据如果不存在则返回null</returns> /// <returns>GodotLogAttribute属性数据如果未找到则返回null</returns>
private static AttributeData? GetAttribute(INamedTypeSymbol classSymbol) private static AttributeData? GetAttribute(INamedTypeSymbol classSymbol)
{ => classSymbol.GetAttributes().FirstOrDefault(a =>
return classSymbol.GetAttributes().FirstOrDefault(a =>
{ {
var cls = a.AttributeClass; var cls = a.AttributeClass;
if (cls == null) return false; return cls != null &&
(cls.ToDisplayString() == AttributeMetadataName ||
// 宽松匹配:全名匹配 OR 名字匹配 cls.Name.StartsWith(AttributeShortNameWithoutSuffix));
return cls.ToDisplayString() == AttributeMetadataName ||
cls.Name == AttributeShortName;
}); });
}
/// <summary> /// <summary>
/// 生成日志字段代码 /// 生成日志字段的源代码
/// </summary> /// </summary>
/// <param name="classSymbol">类符号</param> /// <param name="classSymbol">类符号</param>
/// <param name="attr">LogAttribute特性数据</param> /// <param name="attr">GodotLogAttribute属性数据</param>
/// <returns>生成的C#代码字符串</returns> /// <returns>生成的代码字符串</returns>
private static string Generate(INamedTypeSymbol classSymbol, AttributeData attr) private static string Generate(INamedTypeSymbol classSymbol, AttributeData attr)
{ {
var ns = classSymbol.ContainingNamespace.IsGlobalNamespace var ns = classSymbol.ContainingNamespace.IsGlobalNamespace
? null ? null
: classSymbol.ContainingNamespace.ToDisplayString(); : classSymbol.ContainingNamespace.ToDisplayString();
var className = classSymbol.Name; var className = classSymbol.Name;
// === 解析 Name === // 解析构造函数参数
var name = className; // 默认使用类名 var name = className;
if (attr.ConstructorArguments.Length > 0 && attr.ConstructorArguments[0].Value is string s &&
// 检查是否有构造函数参数 !string.IsNullOrWhiteSpace(s))
if (attr.ConstructorArguments.Length > 0)
{ {
var argValue = attr.ConstructorArguments[0].Value; name = s;
name = argValue switch
{
// 情况 1: 参数存在,但值为 null (例如 [GodotLog] 且构造函数有默认值 null)
null => className,
// 情况 2: 参数存在,且是有效的字符串 (例如 [GodotLog("MyCategory")])
string s when !string.IsNullOrWhiteSpace(s) => s,
_ => $"{className}_InvalidArg"
};
} }
// === 解析 Named Arguments (更加安全的获取方式) === // 解析命名参数
var fieldName = GetNamedArg(attr, "FieldName")?.ToString() ?? "_log"; var fieldName = GetNamedArg(attr, "FieldName")?.ToString() ?? "_log";
var access = var access = GetNamedArg(attr, "AccessModifier")?.ToString() ?? "private";
GetNamedArg(attr, "AccessModifier")?.ToString() ?? var isStatic = GetNamedArg(attr, "IsStatic") is not bool b || b;
"private"; // 注意:如果你的 AccessModifier 是枚举,这里得到的可能是 int 或枚举名
// 处理 bool 类型
var isStaticObj = GetNamedArg(attr, "IsStatic");
var isStatic = isStaticObj is not bool b || b; // 默认为 true
var staticKeyword = isStatic ? "static " : ""; var staticKeyword = isStatic ? "static " : "";
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.AppendLine("// <auto-generated />"); sb.AppendLine("// <auto-generated />");
sb.AppendLine("using GFramework.Core.logging;"); // 确保这里引用了 ILogger sb.AppendLine("using GFramework.Core.logging;");
sb.AppendLine("using GFramework.Godot.logging;"); // 确保这里引用了 GodotLoggerFactory sb.AppendLine("using GFramework.Godot.logging;");
sb.AppendLine();
if (ns is not null) if (ns is not null)
{ {
@ -164,8 +134,7 @@ public sealed class GodotLoggerGenerator : IIncrementalGenerator
sb.AppendLine(" {"); sb.AppendLine(" {");
sb.AppendLine(" /// <summary>Auto-generated logger</summary>"); sb.AppendLine(" /// <summary>Auto-generated logger</summary>");
sb.AppendLine( sb.AppendLine(
$" {access} {staticKeyword}readonly ILogger {fieldName} = " + $" {access} {staticKeyword}readonly ILogger {fieldName} = new GodotLoggerFactory().GetLogger(\"{name}\");");
$"new GodotLoggerFactory().GetLogger(\"{name}\");");
sb.AppendLine(" }"); sb.AppendLine(" }");
if (ns is not null) if (ns is not null)
@ -175,13 +144,11 @@ public sealed class GodotLoggerGenerator : IIncrementalGenerator
} }
/// <summary> /// <summary>
/// 从特性数据中获取命名参数的 /// 从属性数据中获取指定名称的命名参数
/// </summary> /// </summary>
/// <param name="attr">性数据</param> /// <param name="attr">性数据</param>
/// <param name="name">参数名称</param> /// <param name="name">参数名称</param>
/// <returns>参数值,如果不存在则返回null</returns> /// <returns>参数值,如果未找到则返回null</returns>
private static object? GetNamedArg(AttributeData attr, string name) private static object? GetNamedArg(AttributeData attr, string name)
{ => attr.NamedArguments.FirstOrDefault(kv => kv.Key == name).Value.Value;
return (from kv in attr.NamedArguments where kv.Key == name select kv.Value.Value).FirstOrDefault();
}
} }