mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-22 10:34:30 +08:00
refactor(generators): 重构源代码生成器基类架构
- 将 AttributeClassGeneratorBase 抽象基类拆分为 MetadataAttributeClassGeneratorBase 和 TypeAttributeClassGeneratorBase - 为 GodotLoggerGenerator 实现 TypeAttributeClassGeneratorBase 基类 - 为 EnumExtensionsGenerator 实现 MetadataAttributeClassGeneratorBase 基类 - 为 LoggerGenerator 实现 TypeAttributeClassGeneratorBase 基类 - 为 ContextAwareGenerator 实现 MetadataAttributeClassGeneratorBase 基类 - 添加 ContextAwareGenerator 中对 IContextAware 接口实现的验证逻辑 - 简化 AttributeClassGeneratorBase 中的语法提供程序实现 - 移除 AttributeClassGeneratorBase 中的异常处理和错误输出逻辑 - 优化属性解析机制,使用元数据名称或类型进行特性查找
This commit is contained in:
parent
af24a64d3b
commit
414e49c413
@ -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;
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
@ -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 粗筛选
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user