mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-22 19:03:29 +08:00
- 将 PathContests 类从 GFramework.SourceGenerators.constants 移动到 GFramework.SourceGenerators.Common.constants - 删除旧的 GFramework.Godot.SourceGenerators.constants.PathContests 文件 - 更新 GodotLoggerGenerator 中的命名空间引用 - 更新 LoggerGenerator 和 ContextAwareGenerator 的常量引用 - 为 PathContests 类添加详细的 XML 文档注释 - 扩展 PathContests 类,添加多个模块的命名空间常量定义
154 lines
6.0 KiB
C#
154 lines
6.0 KiB
C#
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;
|
||
} |