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

154 lines
5.9 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Linq;
using System.Text;
using GFramework.Godot.SourceGenerators.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;
/// <summary>
/// 日志生成器用于为标记了GodotLogAttribute的类自动生成日志字段
/// </summary>
[Generator]
public sealed class GodotLoggerGenerator : IIncrementalGenerator
{
private const string AttributeMetadataName =
$"{PathContests.RootAbstractionsPath}.logging.GodotLogAttribute";
private const string AttributeShortNameWithoutSuffix = "GodotLog";
/// <summary>
/// 初始化源生成器,设置语法提供程序和源输出注册
/// </summary>
/// <param name="context">增量生成器初始化上下文</param>
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));
}
});
}
/// <summary>
/// 获取类符号上的GodotLogAttribute属性数据
/// </summary>
/// <param name="classSymbol">类符号</param>
/// <returns>GodotLogAttribute属性数据如果未找到则返回null</returns>
private static AttributeData? GetAttribute(INamedTypeSymbol classSymbol)
=> classSymbol.GetAttributes().FirstOrDefault(a =>
{
var cls = a.AttributeClass;
return cls != null &&
(cls.ToDisplayString() == AttributeMetadataName ||
cls.Name.StartsWith(AttributeShortNameWithoutSuffix));
});
/// <summary>
/// 生成日志字段的源代码
/// </summary>
/// <param name="classSymbol">类符号</param>
/// <param name="attr">GodotLogAttribute属性数据</param>
/// <returns>生成的源代码字符串</returns>
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("// <auto-generated />");
sb.AppendLine("using GFramework.Core.logging;");
sb.AppendLine("using GFramework.Godot.logging;");
sb.AppendLine();
if (ns is not null)
{
sb.AppendLine($"namespace {ns}");
sb.AppendLine("{");
}
sb.AppendLine($" public partial class {className}");
sb.AppendLine(" {");
sb.AppendLine(" /// <summary>Auto-generated logger</summary>");
sb.AppendLine(
$" {access} {staticKeyword}readonly ILogger {fieldName} = new GodotLoggerFactory().GetLogger(\"{name}\");");
sb.AppendLine(" }");
if (ns is not null)
sb.AppendLine("}");
return sb.ToString();
}
/// <summary>
/// 从属性数据中获取指定名称的命名参数值
/// </summary>
/// <param name="attr">属性数据</param>
/// <param name="name">参数名称</param>
/// <returns>参数值如果未找到则返回null</returns>
private static object? GetNamedArg(AttributeData attr, string name)
=> attr.NamedArguments.FirstOrDefault(kv => kv.Key == name).Value.Value;
}