refactor(generators): 重构源代码生成器基类架构

- 将 AttributeClassGeneratorBase 抽象基类拆分为 MetadataAttributeClassGeneratorBase 和 TypeAttributeClassGeneratorBase
- 为 GodotLoggerGenerator 实现 TypeAttributeClassGeneratorBase 基类
- 为 EnumExtensionsGenerator 实现 MetadataAttributeClassGeneratorBase 基类
- 为 LoggerGenerator 实现 TypeAttributeClassGeneratorBase 基类
- 为 ContextAwareGenerator 实现 MetadataAttributeClassGeneratorBase 基类
- 添加 ContextAwareGenerator 中对 IContextAware 接口实现的验证逻辑
- 简化 AttributeClassGeneratorBase 中的语法提供程序实现
- 移除 AttributeClassGeneratorBase 中的异常处理和错误输出逻辑
- 优化属性解析机制,使用元数据名称或类型进行特性查找
This commit is contained in:
GwWuYou 2025-12-28 16:08:24 +08:00
parent af24a64d3b
commit 414e49c413
7 changed files with 155 additions and 177 deletions

View File

@ -1,12 +1,10 @@
using System;
using System.Linq;
using System.Text;
using GFramework.Godot.SourceGenerators.Abstractions.logging;
using GFramework.SourceGenerators.Common.constants;
using GFramework.SourceGenerators.Common.diagnostics;
using GFramework.SourceGenerators.Common.generator;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
namespace GFramework.Godot.SourceGenerators.logging;
@ -14,95 +12,25 @@ namespace GFramework.Godot.SourceGenerators.logging;
/// 日志生成器用于为标记了GodotLogAttribute的类自动生成日志字段
/// </summary>
[Generator]
public sealed class GodotLoggerGenerator : IIncrementalGenerator
public sealed class GodotLoggerGenerator : TypeAttributeClassGeneratorBase
{
private const string AttributeMetadataName =
$"{PathContests.GodotSourceGeneratorsAbstractionsPath}.logging.GodotLogAttribute";
protected override Type AttributeType => typeof(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));
});
protected override string AttributeShortNameWithoutSuffix => "GodotLog";
/// <summary>
/// 生成日志字段的源代码
/// </summary>
/// <param name="classSymbol">类符号</param>
/// <param name="symbol">类符号</param>
/// <param name="attr">GodotLogAttribute属性数据</param>
/// <returns>生成的源代码字符串</returns>
private static string Generate(INamedTypeSymbol classSymbol, AttributeData attr)
protected override string Generate(INamedTypeSymbol symbol, AttributeData attr)
{
var ns = classSymbol.ContainingNamespace.IsGlobalNamespace
var ns = symbol.ContainingNamespace.IsGlobalNamespace
? null
: classSymbol.ContainingNamespace.ToDisplayString();
var className = classSymbol.Name;
: symbol.ContainingNamespace.ToDisplayString();
var className = symbol.Name;
// 解析构造函数参数
var name = className;
@ -144,11 +72,19 @@ public sealed class GodotLoggerGenerator : IIncrementalGenerator
}
/// <summary>
/// 从属性数据中获取指定名称的命名参数值
/// 获取生成文件的提示名称
/// </summary>
/// <param name="symbol">类型符号</param>
/// <returns>生成文件的提示名称</returns>
protected override string GetHintName(INamedTypeSymbol symbol)
=> $"{symbol.Name}.Logger.g.cs";
/// <summary>
/// 获取属性的命名参数值
/// </summary>
/// <param name="attr">属性数据</param>
/// <param name="name">参数名称</param>
/// <returns>参数值如果未找到则返回null</returns>
/// <returns>参数值</returns>
private static object? GetNamedArg(AttributeData attr, string name)
=> attr.NamedArguments.FirstOrDefault(kv => kv.Key == name).Value.Value;
}

View File

@ -1,5 +1,4 @@
using System;
using System.Linq;
using System.Linq;
using GFramework.SourceGenerators.Common.diagnostics;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
@ -13,13 +12,7 @@ namespace GFramework.SourceGenerators.Common.generator;
public abstract class AttributeClassGeneratorBase : IIncrementalGenerator
{
/// <summary>
/// 获取属性的元数据名称
/// </summary>
protected abstract Type AttributeType { get; }
/// <summary>
/// Attribute 的短名称(不含 Attribute 后缀)
/// 仅用于 Syntax 层宽松匹配
/// 获取属性的短名称(不包含后缀)
/// </summary>
protected abstract string AttributeShortNameWithoutSuffix { get; }
@ -29,66 +22,70 @@ public abstract class AttributeClassGeneratorBase : IIncrementalGenerator
/// <param name="context">增量生成器初始化上下文</param>
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var targets = context.SyntaxProvider.CreateSyntaxProvider(
// 创建语法提供程序,查找带有指定属性的类声明
var candidates = context.SyntaxProvider.CreateSyntaxProvider(
(node, _) =>
node is ClassDeclarationSyntax cls &&
cls.AttributeLists
.SelectMany(a => a.Attributes)
.Any(a => a.Name.ToString()
.Contains(AttributeShortNameWithoutSuffix)),
static (ctx, t) =>
(ctx, _) =>
{
var cls = (ClassDeclarationSyntax)ctx.Node;
var symbol = ctx.SemanticModel.GetDeclaredSymbol(cls, t);
return (ClassDecl: cls, Symbol: symbol);
}
)
.Where(x => x.Symbol is not null);
var symbol = ctx.SemanticModel.GetDeclaredSymbol(cls);
return (cls, symbol);
})
.Where(x => x.symbol is not null);
context.RegisterSourceOutput(targets, (spc, pair) =>
var combined = candidates.Combine(context.CompilationProvider);
context.RegisterSourceOutput(combined, (spc, pair) =>
{
try
{
Execute(spc, pair.ClassDecl, pair.Symbol!);
}
catch (Exception ex)
{
EmitError(spc, pair.Symbol, ex);
}
var ((cls, symbol), compilation) = pair;
Execute(spc, compilation, cls, symbol!);
});
}
/// <summary>
/// 执行源代码生成的主要逻辑
/// 解析指定符号上的属性数据
/// </summary>
/// <param name="compilation">编译对象</param>
/// <param name="symbol">命名类型符号</param>
/// <returns>属性数据如果未找到则返回null</returns>
protected abstract AttributeData? ResolveAttribute(
Compilation compilation,
INamedTypeSymbol symbol);
/// <summary>
/// 执行源代码生成
/// </summary>
/// <param name="context">源生产上下文</param>
/// <param name="compilation">编译对象</param>
/// <param name="classDecl">类声明语法节点</param>
/// <param name="symbol">命名类型符号</param>
private void Execute(
SourceProductionContext context,
Compilation compilation,
ClassDeclarationSyntax classDecl,
INamedTypeSymbol symbol)
{
var attr = GetAttribute(symbol);
if (attr == null) return;
var attr = ResolveAttribute(compilation, symbol);
if (attr is null)
return;
// partial 校验
if (!classDecl.Modifiers.Any(SyntaxKind.PartialKeyword))
{
ReportClassMustBePartial(context, classDecl, symbol);
return;
}
// 子类校验
if (!ValidateSymbol(context, classDecl, symbol, attr))
return;
var source = Generate(symbol, attr);
context.AddSource(GetHintName(symbol), source);
context.AddSource(GetHintName(symbol), Generate(symbol, attr));
}
#region
/// <summary>
/// 验证符号的有效性
/// </summary>
@ -101,10 +98,7 @@ public abstract class AttributeClassGeneratorBase : IIncrementalGenerator
SourceProductionContext context,
ClassDeclarationSyntax syntax,
INamedTypeSymbol symbol,
AttributeData attr)
{
return true;
}
AttributeData attr) => true;
/// <summary>
/// 生成源代码
@ -122,27 +116,10 @@ public abstract class AttributeClassGeneratorBase : IIncrementalGenerator
/// <param name="symbol">命名类型符号</param>
/// <returns>生成文件的提示名称</returns>
protected virtual string GetHintName(INamedTypeSymbol symbol)
{
return $"{symbol.Name}.g.cs";
}
#endregion
#region Attribute / Diagnostic
=> $"{symbol.Name}.g.cs";
/// <summary>
/// 获取指定符号的属性数据
/// </summary>
/// <param name="symbol">命名类型符号</param>
/// <returns>属性数据如果未找到则返回null</returns>
protected virtual AttributeData? GetAttribute(INamedTypeSymbol symbol)
{
return symbol.GetAttributes().FirstOrDefault(a =>
string.Equals(a.AttributeClass?.ToDisplayString(), AttributeType.FullName, StringComparison.Ordinal));
}
/// <summary>
/// 报告类必须是partial的诊断信息
/// 报告类必须是部分类的错误
/// </summary>
/// <param name="context">源生产上下文</param>
/// <param name="syntax">类声明语法节点</param>
@ -157,23 +134,4 @@ public abstract class AttributeClassGeneratorBase : IIncrementalGenerator
syntax.Identifier.GetLocation(),
symbol.Name));
}
/// <summary>
/// 发出错误信息
/// </summary>
/// <param name="context">源生产上下文</param>
/// <param name="symbol">命名类型符号</param>
/// <param name="ex">异常对象</param>
protected virtual void EmitError(
SourceProductionContext context,
INamedTypeSymbol? symbol,
Exception ex)
{
var name = symbol?.Name ?? "Unknown";
var text =
$"// source generator error: {ex.Message}\n// {ex.StackTrace}";
context.AddSource($"{name}.Error.g.cs", text);
}
#endregion
}

View File

@ -0,0 +1,41 @@
using System.Linq;
using Microsoft.CodeAnalysis;
namespace GFramework.SourceGenerators.Common.generator;
/// <summary>
/// 元数据属性类生成器基类,用于基于元数据名称解析特性的抽象基类
/// </summary>
public abstract class MetadataAttributeClassGeneratorBase
: AttributeClassGeneratorBase
{
/// <summary>
/// 获取特性元数据名称的抽象属性
/// </summary>
protected abstract string AttributeMetadataName { get; }
/// <summary>
/// 根据元数据名称解析指定符号上的特性
/// </summary>
/// <param name="compilation">编译对象,用于获取类型信息</param>
/// <param name="symbol">命名类型符号,用于查找其上的特性</param>
/// <returns>如果找到匹配的特性则返回AttributeData对象否则返回null</returns>
protected override AttributeData? ResolveAttribute(
Compilation compilation,
INamedTypeSymbol symbol)
{
// 通过元数据名称获取特性符号
var attrSymbol =
compilation.GetTypeByMetadataName(AttributeMetadataName);
if (attrSymbol is null)
return null;
// 在符号的所有特性中查找与目标特性符号匹配的第一个特性
return symbol.GetAttributes()
.FirstOrDefault(a =>
SymbolEqualityComparer.Default.Equals(
a.AttributeClass,
attrSymbol));
}
}

View File

@ -0,0 +1,35 @@
using System;
using System.Linq;
using Microsoft.CodeAnalysis;
namespace GFramework.SourceGenerators.Common.generator;
/// <summary>
/// 基于类型特性的类生成器基类
/// </summary>
public abstract class TypeAttributeClassGeneratorBase
: AttributeClassGeneratorBase
{
/// <summary>
/// 获取要处理的特性类型
/// </summary>
protected abstract Type AttributeType { get; }
/// <summary>
/// 解析指定符号上的特性
/// </summary>
/// <param name="compilation">编译对象(未使用)</param>
/// <param name="symbol">要检查的命名类型符号</param>
/// <returns>匹配的特性数据如果未找到则返回null</returns>
protected override AttributeData? ResolveAttribute(
Compilation compilation,
INamedTypeSymbol symbol)
{
var fullName = AttributeType.FullName;
// 查找符号上匹配指定特性的第一个实例
return symbol.GetAttributes()
.FirstOrDefault(a =>
string.Equals(a.AttributeClass?.ToDisplayString(), fullName, StringComparison.Ordinal));
}
}

View File

@ -1,7 +1,6 @@
using System;
using System.Linq;
using System.Linq;
using System.Text;
using GFramework.SourceGenerators.Abstractions.enums;
using GFramework.SourceGenerators.Common.constants;
using GFramework.SourceGenerators.Common.diagnostics;
using GFramework.SourceGenerators.Common.generator;
using Microsoft.CodeAnalysis;
@ -13,13 +12,10 @@ namespace GFramework.SourceGenerators.enums;
/// 枚举扩展方法生成器,用于自动生成枚举相关的扩展方法
/// </summary>
[Generator]
public sealed class EnumExtensionsGenerator : AttributeClassGeneratorBase
public sealed class EnumExtensionsGenerator : MetadataAttributeClassGeneratorBase
{
/// <summary>
/// 使用强类型 Attribute替代字符串
/// </summary>
protected override Type AttributeType =>
typeof(GenerateEnumExtensionsAttribute);
protected override string AttributeMetadataName =>
$"{PathContests.SourceGeneratorsAbstractionsPath}.enums.GenerateEnumExtensionsAttribute";
/// <summary>
/// 仅用于 Syntax 粗筛选

View File

@ -13,10 +13,10 @@ namespace GFramework.SourceGenerators.logging;
/// 日志生成器用于为标记了LogAttribute的类自动生成日志字段
/// </summary>
[Generator]
public sealed class LoggerGenerator : AttributeClassGeneratorBase
public sealed class LoggerGenerator : TypeAttributeClassGeneratorBase
{
/// <summary>
/// 强类型 Attribute
/// 获取属性元数据的完整名称,用于标识日志属性的完全限定名
/// </summary>
protected override Type AttributeType => typeof(LogAttribute);
@ -25,6 +25,7 @@ public sealed class LoggerGenerator : AttributeClassGeneratorBase
/// </summary>
protected override string AttributeShortNameWithoutSuffix => "Log";
/// <summary>
/// 对类进行额外语义校验(可选)
/// </summary>

View File

@ -1,26 +1,31 @@
using System;
using System.Linq;
using System.Text;
using GFramework.SourceGenerators.Abstractions.rule;
using GFramework.Core.Abstractions.rule;
using GFramework.SourceGenerators.Common.constants;
using GFramework.SourceGenerators.Common.generator;
using GFramework.SourceGenerators.diagnostics;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace GFramework.SourceGenerators.rule;
[Generator]
public sealed class ContextAwareGenerator : AttributeClassGeneratorBase
public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase
{
/// <summary>
/// 使用强类型 Attribute替代字符串
/// 获取属性元数据的完整名称用于标识ContextAwareAttribute的完全限定名
/// </summary>
protected override Type AttributeType => typeof(ContextAwareAttribute);
/// <returns>返回ContextAwareAttribute的完全限定名字符串</returns>
protected override string AttributeMetadataName =>
$"{PathContests.SourceGeneratorsAbstractionsPath}.rule.ContextAwareAttribute";
/// <summary>
/// 仅用于 Syntax 粗筛选
/// </summary>
/// <returns>返回属性的简短名称,不包含后缀</returns>
protected override string AttributeShortNameWithoutSuffix => "ContextAware";
/// <summary>
/// 额外语义校验:必须实现 IContextAware
/// </summary>
@ -30,7 +35,13 @@ public sealed class ContextAwareGenerator : AttributeClassGeneratorBase
INamedTypeSymbol symbol,
AttributeData attr)
{
return true;
if (symbol.AllInterfaces.Any(i =>
i.ToDisplayString() == typeof(IContextAware).FullName)) return true;
context.ReportDiagnostic(Diagnostic.Create(
ContextAwareDiagnostic.ClassMustImplementIContextAware,
syntax.Identifier.GetLocation(),
symbol.Name));
return false;
}
/// <summary>