refactor(source-generators): 重构枚举扩展生成器和日志生成器

- 将 EnumExtensionsGenerator 从 IIncrementalGenerator 迁移到 AttributeClassGeneratorBase
- 将 LoggerGenerator 从 IIncrementalGenerator 迁移到 AttributeClassGeneratorBase
- 添加 AttributeEnumGeneratorBase 基类用于枚举相关生成器
- 更新依赖引用路径,使用新的抽象层和通用生成器基类
- 改进代码生成逻辑,使用强类型 Attribute 替代字符串匹配
- 添加详细的 XML 文档注释
- 修改项目目标框架为多版本支持 (net8.0;net9.0;net10.0)
This commit is contained in:
GwWuYou 2025-12-28 11:25:24 +08:00
parent e4a317b743
commit 4172952b11
4 changed files with 182 additions and 208 deletions

View File

@ -0,0 +1,59 @@
using System;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
namespace GFramework.SourceGenerators.Common.generator;
/// <summary>
/// 基于特性的枚举生成器基类
/// </summary>
/// <typeparam name="TAttribute">特性类型必须继承自Attribute</typeparam>
public abstract class AttributeEnumGeneratorBase<TAttribute> : IIncrementalGenerator
where TAttribute : Attribute
{
/// <summary>
/// 获取特性的短名称(不包含后缀)
/// </summary>
protected abstract string AttributeShortNameWithoutSuffix { get; }
/// <summary>
/// 初始化增量生成器
/// </summary>
/// <param name="context">增量生成器初始化上下文</param>
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// 查找带有指定特性的枚举声明
var enums = context.SyntaxProvider.CreateSyntaxProvider(
(node, _) => node is EnumDeclarationSyntax decl &&
decl.AttributeLists.SelectMany(a => a.Attributes)
.Any(a => a.Name.ToString().Contains(AttributeShortNameWithoutSuffix)),
(ctx, _) =>
{
var decl = (EnumDeclarationSyntax)ctx.Node;
var symbol = ctx.SemanticModel.GetDeclaredSymbol(decl) as INamedTypeSymbol;
return (decl, symbol);
}).Where(x => x.symbol != null);
// 注册源代码输出
context.RegisterSourceOutput(enums, (spc, pair) =>
{
var attr = pair.symbol!.GetAttributes()
.FirstOrDefault(a => a.AttributeClass?.ToDisplayString() == typeof(TAttribute).FullName);
if (attr == null) return;
var src = Generate(pair.symbol!, attr);
spc.AddSource($"{pair.symbol!.Name}.EnumExtensions.g.cs", SourceText.From(src, Encoding.UTF8));
});
}
/// <summary>
/// 生成源代码
/// </summary>
/// <param name="enumSymbol">枚举符号</param>
/// <param name="attr">特性数据</param>
/// <returns>生成的源代码字符串</returns>
protected abstract string Generate(INamedTypeSymbol enumSymbol, AttributeData attr);
}

View File

@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<TargetFramework>net8.0</TargetFramework> <TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>

View File

@ -1,80 +1,82 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using GFramework.SourceGenerators.constants; using GFramework.SourceGenerators.Abstractions.enums;
using GFramework.SourceGenerators.Common.diagnostics;
using GFramework.SourceGenerators.Common.generator;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
namespace GFramework.SourceGenerators.enums; namespace GFramework.SourceGenerators.enums;
/// <summary>
/// 枚举扩展方法生成器,用于自动生成枚举相关的扩展方法
/// </summary>
[Generator] [Generator]
public class EnumExtensionsGenerator : IIncrementalGenerator public sealed class EnumExtensionsGenerator : AttributeClassGeneratorBase
{ {
private const string AttributeFullName = /// <summary>
$"{PathContests.RootAbstractionsPath}.enums.GenerateEnumExtensionsAttribute"; /// 使用强类型 Attribute替代字符串
/// </summary>
protected override Type AttributeType =>
typeof(GenerateEnumExtensionsAttribute);
public void Initialize(IncrementalGeneratorInitializationContext context) /// <summary>
/// 仅用于 Syntax 粗筛选
/// </summary>
protected override string AttributeShortNameWithoutSuffix => "GenerateEnumExtensions";
/// <summary>
/// 验证符号是否为有效的枚举类型
/// </summary>
/// <param name="context">源生产上下文</param>
/// <param name="syntax">类声明语法节点</param>
/// <param name="symbol">命名类型符号</param>
/// <param name="attr">属性数据</param>
/// <returns>验证是否通过</returns>
protected override bool ValidateSymbol(
SourceProductionContext context,
ClassDeclarationSyntax syntax,
INamedTypeSymbol symbol,
AttributeData attr)
{ {
// 1. 找到所有 EnumDeclarationSyntax 节点 if (symbol.TypeKind != TypeKind.Enum)
var enumDecls = context.SyntaxProvider
.CreateSyntaxProvider(
(s, _) => s is EnumDeclarationSyntax,
(ctx, _) =>
(EnumDecl: (EnumDeclarationSyntax)ctx.Node, ctx.SemanticModel))
.Where(t => t.EnumDecl != null);
// 2. 解析为 symbol 并过滤带 Attribute 的 enum
var enumSymbols = enumDecls
.Select((t, _) =>
{
var model = t.SemanticModel;
var enumDecl = t.EnumDecl;
var symbol = model.GetDeclaredSymbol(enumDecl) as INamedTypeSymbol;
return symbol;
})
.Where(symbol => symbol != null)
.Select((symbol, _) =>
{
var hasAttr = symbol!.GetAttributes().Any(ad =>
ad.AttributeClass?.ToDisplayString() == AttributeFullName);
return (Symbol: symbol, HasAttr: hasAttr);
})
.Where(x => x.HasAttr)
.Collect();
// 3. 为每个 enum 生成代码
context.RegisterSourceOutput(enumSymbols, (spc, list) =>
{ {
foreach (var enumSymbol in list.Select(item => item.Symbol)) var loc = syntax.Identifier.GetLocation();
try context.ReportDiagnostic(Diagnostic.Create(
{ CommonDiagnostics.ClassMustBePartial, // 可以定义一个新的 Enum 专用 Diagnostic
var src = GenerateForEnum(enumSymbol); loc,
var hintName = $"{enumSymbol.Name}.EnumExtensions.g.cs"; symbol.Name
spc.AddSource(hintName, SourceText.From(src, Encoding.UTF8)); ));
} return false;
catch (Exception ex) }
{
// 发生异常时生成一个注释文件(避免完全静默失败) return true;
var err = $"// EnumExtensionsGenerator failed for {enumSymbol?.Name}: {ex.Message}";
spc.AddSource($"{enumSymbol?.Name}.EnumExtensions.Error.g.cs",
SourceText.From(err, Encoding.UTF8));
}
});
} }
private static string GenerateForEnum(INamedTypeSymbol enumSymbol) /// <summary>
/// 生成枚举扩展方法的源代码
/// </summary>
/// <param name="symbol">枚举类型符号</param>
/// <param name="attr">属性数据</param>
/// <returns>生成的源代码字符串</returns>
protected override string Generate(INamedTypeSymbol symbol, AttributeData attr)
{ {
var ns = enumSymbol.ContainingNamespace.IsGlobalNamespace var ns = symbol.ContainingNamespace.IsGlobalNamespace
? null ? null
: enumSymbol.ContainingNamespace.ToDisplayString(); : symbol.ContainingNamespace.ToDisplayString();
var enumName = enumSymbol.Name;
var fullEnumName = enumSymbol.ToDisplayString(); // 包含命名空间 var enumName = symbol.Name;
var members = enumSymbol.GetMembers().OfType<IFieldSymbol>().Where(f => f.ConstantValue != null).ToArray(); var fullEnumName = symbol.ToDisplayString();
var members = symbol.GetMembers()
.OfType<IFieldSymbol>()
.Where(f => f.ConstantValue != null)
.ToArray();
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.AppendLine("// <auto-generated />"); sb.AppendLine("// <auto-generated />");
sb.AppendLine("using System;"); sb.AppendLine("using System;");
if (!string.IsNullOrEmpty(ns)) if (!string.IsNullOrEmpty(ns))
{ {
sb.AppendLine($"namespace {ns}"); sb.AppendLine($"namespace {ns}");
@ -89,25 +91,18 @@ public class EnumExtensionsGenerator : IIncrementalGenerator
sb.AppendLine($" public static partial class {enumName}Extensions"); sb.AppendLine($" public static partial class {enumName}Extensions");
sb.AppendLine(" {"); sb.AppendLine(" {");
// 1. 单项 IsX 方法 // 生成 IsX 方法
// 替换原第93行开始的 foreach 块 foreach (var memberName in members.Select(m => m.Name))
var memberChecks = members.Select(m =>
{ {
var memberName = m.Name; sb.AppendLine($" /// <summary>Auto-generated: 是否为 {memberName}</summary>");
var safeMethodName = $"Is{memberName}"; sb.AppendLine(
return $@" /// <summary>Auto-generated: 是否为 {memberName}</summary> $" public static bool Is{memberName}(this {fullEnumName} value) => value == {fullEnumName}.{memberName};");
public static bool {safeMethodName}(this {fullEnumName} value) => value == {fullEnumName}.{memberName}; sb.AppendLine();
}
"; // 生成 IsIn 方法
}).ToArray();
sb.Append(string.Join("", memberChecks));
// 2. IsIn(params ...) 方法
sb.AppendLine(" /// <summary>Auto-generated: 判断是否属于指定集合</summary>"); sb.AppendLine(" /// <summary>Auto-generated: 判断是否属于指定集合</summary>");
sb.AppendLine( sb.AppendLine($" public static bool IsIn(this {fullEnumName} value, params {fullEnumName}[] values)");
$" public static bool IsIn(this {fullEnumName} value, params {fullEnumName}[] values)");
sb.AppendLine(" {"); sb.AppendLine(" {");
sb.AppendLine(" if (values == null) return false;"); sb.AppendLine(" if (values == null) return false;");
sb.AppendLine(" foreach (var v in values) if (value == v) return true;"); sb.AppendLine(" foreach (var v in values) if (value == v) return true;");
@ -119,4 +114,12 @@ public class EnumExtensionsGenerator : IIncrementalGenerator
return sb.ToString(); return sb.ToString();
} }
/// <summary>
/// 获取生成文件的提示名称
/// </summary>
/// <param name="symbol">命名类型符号</param>
/// <returns>生成文件的提示名称</returns>
protected override string GetHintName(INamedTypeSymbol symbol)
=> $"{symbol.Name}.EnumExtensions.g.cs";
} }

View File

@ -1,12 +1,10 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using GFramework.SourceGenerators.Common.diagnostics; using GFramework.SourceGenerators.Abstractions.logging;
using GFramework.SourceGenerators.constants; using GFramework.SourceGenerators.Common.generator;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
namespace GFramework.SourceGenerators.logging; namespace GFramework.SourceGenerators.logging;
@ -14,171 +12,85 @@ namespace GFramework.SourceGenerators.logging;
/// 日志生成器用于为标记了LogAttribute的类自动生成日志字段 /// 日志生成器用于为标记了LogAttribute的类自动生成日志字段
/// </summary> /// </summary>
[Generator] [Generator]
public sealed class LoggerGenerator : IIncrementalGenerator public sealed class LoggerGenerator : AttributeClassGeneratorBase
{ {
private const string AttributeMetadataName = $"{PathContests.RootAbstractionsPath}.logging.LogAttribute"; /// <summary>
private const string AttributeShortName = "LogAttribute"; /// 强类型 Attribute
private const string AttributeShortNameWithoutSuffix = "Log"; /// </summary>
protected override Type AttributeType => typeof(LogAttribute);
/// <summary> /// <summary>
/// 初始化生成器,设置语法过滤和代码生成逻辑 /// 用于语法快速筛选
/// </summary> /// </summary>
/// <param name="context">增量生成器初始化上下文</param> protected override string AttributeShortNameWithoutSuffix => "Log";
public void Initialize(IncrementalGeneratorInitializationContext context)
/// <summary>
/// 对类进行额外语义校验(可选)
/// </summary>
protected override bool ValidateSymbol(
SourceProductionContext context,
ClassDeclarationSyntax syntax,
INamedTypeSymbol symbol,
AttributeData attr)
{ {
// 1. 语法过滤:快速筛选候选类 // 可以加自定义规则,比如确保类是 public 或实现某接口
var targets = context.SyntaxProvider.CreateSyntaxProvider( return true;
static (node, _) =>
{
if (node is not ClassDeclarationSyntax cls) return false;
// 只要包含 Log 字眼的 Attribute 就先放行
return cls.AttributeLists.SelectMany(a => a.Attributes).Any(a =>
{
var name = a.Name.ToString();
// 简单的字符串匹配,防止错过别名情况
return name.Contains(AttributeShortNameWithoutSuffix);
});
},
static (ctx, _) =>
{
var classDecl = (ClassDeclarationSyntax)ctx.Node;
var symbol = ctx.SemanticModel.GetDeclaredSymbol(classDecl);
return (ClassDecl: classDecl, Symbol: symbol);
})
.Where(x => x.Symbol is not null);
// 2. 生成代码
context.RegisterSourceOutput(targets, (spc, pair) =>
{
try
{
var classDecl = pair.ClassDecl;
var classSymbol = pair.Symbol!;
// 再次确认是否真的含有目标 Attribute (语义检查)
var attr = GetAttribute(classSymbol);
if (attr == null) return; // 可能是名字相似但不是我们要的 Attribute
// 检查 partial
if (!classDecl.Modifiers.Any(SyntaxKind.PartialKeyword))
{
spc.ReportDiagnostic(Diagnostic.Create(
CommonDiagnostics.ClassMustBePartial,
classDecl.Identifier.GetLocation(),
classSymbol.Name));
return;
}
var source = Generate(classSymbol, attr);
var hintName = $"{classSymbol.Name}.Logger.g.cs";
spc.AddSource(hintName, SourceText.From(source, Encoding.UTF8));
}
catch (Exception ex)
{
// === 关键修复:生成错误报告文件 ===
var errorSource = $"// source generator error: {ex.Message}\n// StackTrace:\n// {ex.StackTrace}";
// 替换非法字符以防文件名报错
var safeName = pair.Symbol?.Name ?? "Unknown";
spc.AddSource($"{safeName}.Logger.Error.g.cs", SourceText.From(errorSource, Encoding.UTF8));
}
});
} }
/// <summary> /// <summary>
/// 获取类符号上的LogAttribute特性 /// 生成 Logger 字段的代码
/// </summary> /// </summary>
/// <param name="classSymbol">类符号</param> protected override string Generate(INamedTypeSymbol symbol, AttributeData attr)
/// <returns>LogAttribute特性数据如果不存在则返回null</returns>
private static AttributeData? GetAttribute(INamedTypeSymbol classSymbol)
{ {
return classSymbol.GetAttributes().FirstOrDefault(a => var ns = symbol.ContainingNamespace.IsGlobalNamespace
{
var cls = a.AttributeClass;
if (cls == null) return false;
// 宽松匹配:全名匹配 OR 名字匹配
return cls.ToDisplayString() == AttributeMetadataName ||
cls.Name == AttributeShortName;
});
}
/// <summary>
/// 生成日志字段代码
/// </summary>
/// <param name="classSymbol">类符号</param>
/// <param name="attr">LogAttribute特性数据</param>
/// <returns>生成的C#代码字符串</returns>
private static string Generate(INamedTypeSymbol classSymbol, AttributeData attr)
{
var ns = classSymbol.ContainingNamespace.IsGlobalNamespace
? null ? null
: classSymbol.ContainingNamespace.ToDisplayString(); : symbol.ContainingNamespace.ToDisplayString();
var className = classSymbol.Name; var className = symbol.Name;
// === 解析 Name === // 解析构造函数参数
var name = className; // 默认使用类名 var name = className;
// 检查是否有构造函数参数
if (attr.ConstructorArguments.Length > 0) if (attr.ConstructorArguments.Length > 0)
{ {
var argValue = attr.ConstructorArguments[0].Value; var argValue = attr.ConstructorArguments[0].Value;
name = argValue is string s && !string.IsNullOrWhiteSpace(s)
name = argValue switch ? s
{ : className;
// 情况 1: 参数存在,但值为 null (例如 [Log] 且构造函数有默认值 null)
null => className,
// 情况 2: 参数存在,且是有效的字符串 (例如 [Log("MyCategory")])
string s when !string.IsNullOrWhiteSpace(s) => s,
_ => $"{className}_InvalidArg"
};
} }
// === 解析 Named Arguments (更加安全的获取方式) === // 解析命名参数
var fieldName = GetNamedArg(attr, "FieldName")?.ToString() ?? "_log"; var fieldName = GetNamedArg(attr, "FieldName")?.ToString() ?? "_log";
var access = var access = GetNamedArg(attr, "AccessModifier")?.ToString() ?? "private";
GetNamedArg(attr, "AccessModifier")?.ToString() ??
"private"; // 注意:如果你的 AccessModifier 是枚举,这里得到的可能是 int 或枚举名
// 处理 bool 类型
var isStaticObj = GetNamedArg(attr, "IsStatic"); var isStaticObj = GetNamedArg(attr, "IsStatic");
var isStatic = isStaticObj is not bool b || b; // 默认为 true var isStatic = isStaticObj is not bool b || b; // 默认 true
var staticKeyword = isStatic ? "static " : ""; var staticKeyword = isStatic ? "static " : "";
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.AppendLine("// <auto-generated />"); sb.AppendLine("// <auto-generated />");
sb.AppendLine("using GFramework.Core.logging;"); // 确保这里引用了 ILog 和 Log 类 sb.AppendLine("using GFramework.Core.logging;");
if (ns is not null) if (ns is not null)
{ {
sb.AppendLine($"namespace {ns}"); sb.AppendLine($"namespace {ns};");
sb.AppendLine("{"); sb.AppendLine();
} }
sb.AppendLine($" public partial class {className}"); sb.AppendLine($"partial class {className}");
sb.AppendLine(" {"); sb.AppendLine("{");
sb.AppendLine(" /// <summary>Auto-generated logger</summary>"); sb.AppendLine(" /// <summary>Auto-generated logger</summary>");
sb.AppendLine( sb.AppendLine(
$" {access} {staticKeyword}readonly ILogger {fieldName} = " + $" {access} {staticKeyword}readonly ILogger {fieldName} = new ConsoleLoggerFactory().GetLogger(\"{name}\");");
$"new ConsoleLoggerFactory().GetLogger(\"{name}\");"); sb.AppendLine("}");
sb.AppendLine(" }");
if (ns is not null) return sb.ToString().TrimEnd();
sb.AppendLine("}");
return sb.ToString();
} }
/// <summary> /// <summary>
/// 从特性数据中获取命名参数的值 /// 可以自定义生成文件名
/// </summary> /// </summary>
/// <param name="attr">特性数据</param> protected override string GetHintName(INamedTypeSymbol symbol)
/// <param name="name">参数名称</param> => $"{symbol.Name}.Logger.g.cs";
/// <returns>参数值如果不存在则返回null</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;
return (from kv in attr.NamedArguments where kv.Key == name select kv.Value.Value).FirstOrDefault();
}
} }