GwWuYou eed07a1a4b refactor(constants): 将路径常量类移动到通用模块并更新引用
- 将 PathContests 类从 GFramework.SourceGenerators.constants 移动到 GFramework.SourceGenerators.Common.constants
- 删除旧的 GFramework.Godot.SourceGenerators.constants.PathContests 文件
- 更新 GodotLoggerGenerator 中的命名空间引用
- 更新 LoggerGenerator 和 ContextAwareGenerator 的常量引用
- 为 PathContests 类添加详细的 XML 文档注释
- 扩展 PathContests 类,添加多个模块的命名空间常量定义
2025-12-28 13:45:37 +08:00

154 lines
6.0 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.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;
/// <summary>
/// 日志生成器用于为标记了GodotLogAttribute的类自动生成日志字段
/// </summary>
[Generator]
public sealed class GodotLoggerGenerator : IIncrementalGenerator
{
private const string AttributeMetadataName =
$"{PathContests.GodotSourceGeneratorsAbstractionsPath}.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 {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(" /// <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;
}