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;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using GFramework.Godot.SourceGenerators.Abstractions.logging;
using GFramework.SourceGenerators.Common.constants; using GFramework.SourceGenerators.Common.constants;
using GFramework.SourceGenerators.Common.diagnostics; using GFramework.SourceGenerators.Common.generator;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
namespace GFramework.Godot.SourceGenerators.logging; namespace GFramework.Godot.SourceGenerators.logging;
@ -14,95 +12,25 @@ namespace GFramework.Godot.SourceGenerators.logging;
/// 日志生成器用于为标记了GodotLogAttribute的类自动生成日志字段 /// 日志生成器用于为标记了GodotLogAttribute的类自动生成日志字段
/// </summary> /// </summary>
[Generator] [Generator]
public sealed class GodotLoggerGenerator : IIncrementalGenerator public sealed class GodotLoggerGenerator : TypeAttributeClassGeneratorBase
{ {
private const string AttributeMetadataName = protected override Type AttributeType => typeof(GodotLogAttribute);
$"{PathContests.GodotSourceGeneratorsAbstractionsPath}.logging.GodotLogAttribute";
private const string AttributeShortNameWithoutSuffix = "GodotLog";
/// <summary> protected override string AttributeShortNameWithoutSuffix => "GodotLog";
/// 初始化源生成器,设置语法提供程序和源输出注册
/// </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>
/// 生成日志字段的源代码 /// 生成日志字段的源代码
/// </summary> /// </summary>
/// <param name="classSymbol">类符号</param> /// <param name="symbol">类符号</param>
/// <param name="attr">GodotLogAttribute属性数据</param> /// <param name="attr">GodotLogAttribute属性数据</param>
/// <returns>生成的源代码字符串</returns> /// <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 ? null
: classSymbol.ContainingNamespace.ToDisplayString(); : symbol.ContainingNamespace.ToDisplayString();
var className = classSymbol.Name; var className = symbol.Name;
// 解析构造函数参数 // 解析构造函数参数
var name = className; var name = className;
@ -144,11 +72,19 @@ public sealed class GodotLoggerGenerator : IIncrementalGenerator
} }
/// <summary> /// <summary>
/// 从属性数据中获取指定名称的命名参数值 /// 获取生成文件的提示名称
/// </summary>
/// <param name="symbol">类型符号</param>
/// <returns>生成文件的提示名称</returns>
protected override string GetHintName(INamedTypeSymbol symbol)
=> $"{symbol.Name}.Logger.g.cs";
/// <summary>
/// 获取属性的命名参数值
/// </summary> /// </summary>
/// <param name="attr">属性数据</param> /// <param name="attr">属性数据</param>
/// <param name="name">参数名称</param> /// <param name="name">参数名称</param>
/// <returns>参数值如果未找到则返回null</returns> /// <returns>参数值</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; => 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 GFramework.SourceGenerators.Common.diagnostics;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp;
@ -13,13 +12,7 @@ namespace GFramework.SourceGenerators.Common.generator;
public abstract class AttributeClassGeneratorBase : IIncrementalGenerator public abstract class AttributeClassGeneratorBase : IIncrementalGenerator
{ {
/// <summary> /// <summary>
/// 获取属性的元数据名称 /// 获取属性的短名称(不包含后缀)
/// </summary>
protected abstract Type AttributeType { get; }
/// <summary>
/// Attribute 的短名称(不含 Attribute 后缀)
/// 仅用于 Syntax 层宽松匹配
/// </summary> /// </summary>
protected abstract string AttributeShortNameWithoutSuffix { get; } protected abstract string AttributeShortNameWithoutSuffix { get; }
@ -29,66 +22,70 @@ public abstract class AttributeClassGeneratorBase : IIncrementalGenerator
/// <param name="context">增量生成器初始化上下文</param> /// <param name="context">增量生成器初始化上下文</param>
public void Initialize(IncrementalGeneratorInitializationContext context) public void Initialize(IncrementalGeneratorInitializationContext context)
{ {
var targets = context.SyntaxProvider.CreateSyntaxProvider( // 创建语法提供程序,查找带有指定属性的类声明
var candidates = context.SyntaxProvider.CreateSyntaxProvider(
(node, _) => (node, _) =>
node is ClassDeclarationSyntax cls && node is ClassDeclarationSyntax cls &&
cls.AttributeLists cls.AttributeLists
.SelectMany(a => a.Attributes) .SelectMany(a => a.Attributes)
.Any(a => a.Name.ToString() .Any(a => a.Name.ToString()
.Contains(AttributeShortNameWithoutSuffix)), .Contains(AttributeShortNameWithoutSuffix)),
static (ctx, t) => (ctx, _) =>
{ {
var cls = (ClassDeclarationSyntax)ctx.Node; var cls = (ClassDeclarationSyntax)ctx.Node;
var symbol = ctx.SemanticModel.GetDeclaredSymbol(cls, t); var symbol = ctx.SemanticModel.GetDeclaredSymbol(cls);
return (ClassDecl: cls, Symbol: symbol); return (cls, symbol);
} })
) .Where(x => x.symbol is not null);
.Where(x => x.Symbol is not null);
context.RegisterSourceOutput(targets, (spc, pair) => var combined = candidates.Combine(context.CompilationProvider);
context.RegisterSourceOutput(combined, (spc, pair) =>
{ {
try var ((cls, symbol), compilation) = pair;
{ Execute(spc, compilation, cls, symbol!);
Execute(spc, pair.ClassDecl, pair.Symbol!);
}
catch (Exception ex)
{
EmitError(spc, pair.Symbol, ex);
}
}); });
} }
/// <summary> /// <summary>
/// 执行源代码生成的主要逻辑 /// 解析指定符号上的属性数据
/// </summary>
/// <param name="compilation">编译对象</param>
/// <param name="symbol">命名类型符号</param>
/// <returns>属性数据如果未找到则返回null</returns>
protected abstract AttributeData? ResolveAttribute(
Compilation compilation,
INamedTypeSymbol symbol);
/// <summary>
/// 执行源代码生成
/// </summary> /// </summary>
/// <param name="context">源生产上下文</param> /// <param name="context">源生产上下文</param>
/// <param name="compilation">编译对象</param>
/// <param name="classDecl">类声明语法节点</param> /// <param name="classDecl">类声明语法节点</param>
/// <param name="symbol">命名类型符号</param> /// <param name="symbol">命名类型符号</param>
private void Execute( private void Execute(
SourceProductionContext context, SourceProductionContext context,
Compilation compilation,
ClassDeclarationSyntax classDecl, ClassDeclarationSyntax classDecl,
INamedTypeSymbol symbol) INamedTypeSymbol symbol)
{ {
var attr = GetAttribute(symbol); var attr = ResolveAttribute(compilation, symbol);
if (attr == null) return; if (attr is null)
return;
// partial 校验
if (!classDecl.Modifiers.Any(SyntaxKind.PartialKeyword)) if (!classDecl.Modifiers.Any(SyntaxKind.PartialKeyword))
{ {
ReportClassMustBePartial(context, classDecl, symbol); ReportClassMustBePartial(context, classDecl, symbol);
return; return;
} }
// 子类校验
if (!ValidateSymbol(context, classDecl, symbol, attr)) if (!ValidateSymbol(context, classDecl, symbol, attr))
return; return;
var source = Generate(symbol, attr); context.AddSource(GetHintName(symbol), Generate(symbol, attr));
context.AddSource(GetHintName(symbol), source);
} }
#region
/// <summary> /// <summary>
/// 验证符号的有效性 /// 验证符号的有效性
/// </summary> /// </summary>
@ -101,10 +98,7 @@ public abstract class AttributeClassGeneratorBase : IIncrementalGenerator
SourceProductionContext context, SourceProductionContext context,
ClassDeclarationSyntax syntax, ClassDeclarationSyntax syntax,
INamedTypeSymbol symbol, INamedTypeSymbol symbol,
AttributeData attr) AttributeData attr) => true;
{
return true;
}
/// <summary> /// <summary>
/// 生成源代码 /// 生成源代码
@ -122,27 +116,10 @@ public abstract class AttributeClassGeneratorBase : IIncrementalGenerator
/// <param name="symbol">命名类型符号</param> /// <param name="symbol">命名类型符号</param>
/// <returns>生成文件的提示名称</returns> /// <returns>生成文件的提示名称</returns>
protected virtual string GetHintName(INamedTypeSymbol symbol) protected virtual string GetHintName(INamedTypeSymbol symbol)
{ => $"{symbol.Name}.g.cs";
return $"{symbol.Name}.g.cs";
}
#endregion
#region Attribute / Diagnostic
/// <summary> /// <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> /// </summary>
/// <param name="context">源生产上下文</param> /// <param name="context">源生产上下文</param>
/// <param name="syntax">类声明语法节点</param> /// <param name="syntax">类声明语法节点</param>
@ -157,23 +134,4 @@ public abstract class AttributeClassGeneratorBase : IIncrementalGenerator
syntax.Identifier.GetLocation(), syntax.Identifier.GetLocation(),
symbol.Name)); 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 System.Text;
using GFramework.SourceGenerators.Abstractions.enums; using GFramework.SourceGenerators.Common.constants;
using GFramework.SourceGenerators.Common.diagnostics; using GFramework.SourceGenerators.Common.diagnostics;
using GFramework.SourceGenerators.Common.generator; using GFramework.SourceGenerators.Common.generator;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
@ -13,13 +12,10 @@ namespace GFramework.SourceGenerators.enums;
/// 枚举扩展方法生成器,用于自动生成枚举相关的扩展方法 /// 枚举扩展方法生成器,用于自动生成枚举相关的扩展方法
/// </summary> /// </summary>
[Generator] [Generator]
public sealed class EnumExtensionsGenerator : AttributeClassGeneratorBase public sealed class EnumExtensionsGenerator : MetadataAttributeClassGeneratorBase
{ {
/// <summary> protected override string AttributeMetadataName =>
/// 使用强类型 Attribute替代字符串 $"{PathContests.SourceGeneratorsAbstractionsPath}.enums.GenerateEnumExtensionsAttribute";
/// </summary>
protected override Type AttributeType =>
typeof(GenerateEnumExtensionsAttribute);
/// <summary> /// <summary>
/// 仅用于 Syntax 粗筛选 /// 仅用于 Syntax 粗筛选

View File

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

View File

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