diff --git a/GFramework.SourceGenerators.Common/generator/AttributeEnumGeneratorBase.cs b/GFramework.SourceGenerators.Common/generator/AttributeEnumGeneratorBase.cs new file mode 100644 index 0000000..ec36fed --- /dev/null +++ b/GFramework.SourceGenerators.Common/generator/AttributeEnumGeneratorBase.cs @@ -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; + +/// +/// 基于特性的枚举生成器基类 +/// +/// 特性类型,必须继承自Attribute +public abstract class AttributeEnumGeneratorBase : IIncrementalGenerator + where TAttribute : Attribute +{ + /// + /// 获取特性的短名称(不包含后缀) + /// + protected abstract string AttributeShortNameWithoutSuffix { get; } + + /// + /// 初始化增量生成器 + /// + /// 增量生成器初始化上下文 + 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)); + }); + } + + /// + /// 生成源代码 + /// + /// 枚举符号 + /// 特性数据 + /// 生成的源代码字符串 + protected abstract string Generate(INamedTypeSymbol enumSymbol, AttributeData attr); +} \ No newline at end of file diff --git a/GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj b/GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj index 911926f..71449aa 100644 --- a/GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj +++ b/GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj @@ -3,7 +3,7 @@ enable enable - net8.0 + net8.0;net9.0;net10.0 diff --git a/GFramework.SourceGenerators/enums/EnumExtensionsGenerator.cs b/GFramework.SourceGenerators/enums/EnumExtensionsGenerator.cs index 1f23cab..a90a29d 100644 --- a/GFramework.SourceGenerators/enums/EnumExtensionsGenerator.cs +++ b/GFramework.SourceGenerators/enums/EnumExtensionsGenerator.cs @@ -1,80 +1,82 @@ using System; using System.Linq; 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.CSharp.Syntax; -using Microsoft.CodeAnalysis.Text; namespace GFramework.SourceGenerators.enums; +/// +/// 枚举扩展方法生成器,用于自动生成枚举相关的扩展方法 +/// [Generator] -public class EnumExtensionsGenerator : IIncrementalGenerator +public sealed class EnumExtensionsGenerator : AttributeClassGeneratorBase { - private const string AttributeFullName = - $"{PathContests.RootAbstractionsPath}.enums.GenerateEnumExtensionsAttribute"; + /// + /// 使用强类型 Attribute,替代字符串 + /// + protected override Type AttributeType => + typeof(GenerateEnumExtensionsAttribute); - public void Initialize(IncrementalGeneratorInitializationContext context) + /// + /// 仅用于 Syntax 粗筛选 + /// + protected override string AttributeShortNameWithoutSuffix => "GenerateEnumExtensions"; + + /// + /// 验证符号是否为有效的枚举类型 + /// + /// 源生产上下文 + /// 类声明语法节点 + /// 命名类型符号 + /// 属性数据 + /// 验证是否通过 + protected override bool ValidateSymbol( + SourceProductionContext context, + ClassDeclarationSyntax syntax, + INamedTypeSymbol symbol, + AttributeData attr) { - // 1. 找到所有 EnumDeclarationSyntax 节点 - 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) => + if (symbol.TypeKind != TypeKind.Enum) { - foreach (var enumSymbol in list.Select(item => item.Symbol)) - try - { - var src = GenerateForEnum(enumSymbol); - var hintName = $"{enumSymbol.Name}.EnumExtensions.g.cs"; - spc.AddSource(hintName, SourceText.From(src, Encoding.UTF8)); - } - catch (Exception ex) - { - // 发生异常时生成一个注释文件(避免完全静默失败) - var err = $"// EnumExtensionsGenerator failed for {enumSymbol?.Name}: {ex.Message}"; - spc.AddSource($"{enumSymbol?.Name}.EnumExtensions.Error.g.cs", - SourceText.From(err, Encoding.UTF8)); - } - }); + var loc = syntax.Identifier.GetLocation(); + context.ReportDiagnostic(Diagnostic.Create( + CommonDiagnostics.ClassMustBePartial, // 可以定义一个新的 Enum 专用 Diagnostic + loc, + symbol.Name + )); + return false; + } + + return true; } - private static string GenerateForEnum(INamedTypeSymbol enumSymbol) + /// + /// 生成枚举扩展方法的源代码 + /// + /// 枚举类型符号 + /// 属性数据 + /// 生成的源代码字符串 + protected override string Generate(INamedTypeSymbol symbol, AttributeData attr) { - var ns = enumSymbol.ContainingNamespace.IsGlobalNamespace + var ns = symbol.ContainingNamespace.IsGlobalNamespace ? null - : enumSymbol.ContainingNamespace.ToDisplayString(); - var enumName = enumSymbol.Name; - var fullEnumName = enumSymbol.ToDisplayString(); // 包含命名空间 - var members = enumSymbol.GetMembers().OfType().Where(f => f.ConstantValue != null).ToArray(); + : symbol.ContainingNamespace.ToDisplayString(); + + var enumName = symbol.Name; + var fullEnumName = symbol.ToDisplayString(); + var members = symbol.GetMembers() + .OfType() + .Where(f => f.ConstantValue != null) + .ToArray(); var sb = new StringBuilder(); sb.AppendLine("// "); sb.AppendLine("using System;"); + if (!string.IsNullOrEmpty(ns)) { sb.AppendLine($"namespace {ns}"); @@ -89,25 +91,18 @@ public class EnumExtensionsGenerator : IIncrementalGenerator sb.AppendLine($" public static partial class {enumName}Extensions"); sb.AppendLine(" {"); - // 1. 单项 IsX 方法 -// 替换原第93行开始的 foreach 块 - var memberChecks = members.Select(m => + // 生成 IsX 方法 + foreach (var memberName in members.Select(m => m.Name)) { - var memberName = m.Name; - var safeMethodName = $"Is{memberName}"; - return $@" /// Auto-generated: 是否为 {memberName} - public static bool {safeMethodName}(this {fullEnumName} value) => value == {fullEnumName}.{memberName}; + sb.AppendLine($" /// Auto-generated: 是否为 {memberName}"); + sb.AppendLine( + $" public static bool Is{memberName}(this {fullEnumName} value) => value == {fullEnumName}.{memberName};"); + sb.AppendLine(); + } -"; - }).ToArray(); - - sb.Append(string.Join("", memberChecks)); - - - // 2. IsIn(params ...) 方法 + // 生成 IsIn 方法 sb.AppendLine(" /// Auto-generated: 判断是否属于指定集合"); - sb.AppendLine( - $" public static bool IsIn(this {fullEnumName} value, params {fullEnumName}[] values)"); + sb.AppendLine($" public static bool IsIn(this {fullEnumName} value, params {fullEnumName}[] values)"); sb.AppendLine(" {"); sb.AppendLine(" if (values == null) return false;"); sb.AppendLine(" foreach (var v in values) if (value == v) return true;"); @@ -119,4 +114,12 @@ public class EnumExtensionsGenerator : IIncrementalGenerator return sb.ToString(); } + + /// + /// 获取生成文件的提示名称 + /// + /// 命名类型符号 + /// 生成文件的提示名称 + protected override string GetHintName(INamedTypeSymbol symbol) + => $"{symbol.Name}.EnumExtensions.g.cs"; } \ No newline at end of file diff --git a/GFramework.SourceGenerators/logging/LoggerGenerator.cs b/GFramework.SourceGenerators/logging/LoggerGenerator.cs index 554ab2a..7d50aff 100644 --- a/GFramework.SourceGenerators/logging/LoggerGenerator.cs +++ b/GFramework.SourceGenerators/logging/LoggerGenerator.cs @@ -1,12 +1,10 @@ using System; using System.Linq; using System.Text; -using GFramework.SourceGenerators.Common.diagnostics; -using GFramework.SourceGenerators.constants; +using GFramework.SourceGenerators.Abstractions.logging; +using GFramework.SourceGenerators.Common.generator; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Text; namespace GFramework.SourceGenerators.logging; @@ -14,171 +12,85 @@ namespace GFramework.SourceGenerators.logging; /// 日志生成器,用于为标记了LogAttribute的类自动生成日志字段 /// [Generator] -public sealed class LoggerGenerator : IIncrementalGenerator +public sealed class LoggerGenerator : AttributeClassGeneratorBase { - private const string AttributeMetadataName = $"{PathContests.RootAbstractionsPath}.logging.LogAttribute"; - private const string AttributeShortName = "LogAttribute"; - private const string AttributeShortNameWithoutSuffix = "Log"; + /// + /// 强类型 Attribute + /// + protected override Type AttributeType => typeof(LogAttribute); /// - /// 初始化生成器,设置语法过滤和代码生成逻辑 + /// 用于语法快速筛选 /// - /// 增量生成器初始化上下文 - public void Initialize(IncrementalGeneratorInitializationContext context) + protected override string AttributeShortNameWithoutSuffix => "Log"; + + /// + /// 对类进行额外语义校验(可选) + /// + protected override bool ValidateSymbol( + SourceProductionContext context, + ClassDeclarationSyntax syntax, + INamedTypeSymbol symbol, + AttributeData attr) { - // 1. 语法过滤:快速筛选候选类 - var targets = context.SyntaxProvider.CreateSyntaxProvider( - 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)); - } - }); + // 可以加自定义规则,比如确保类是 public 或实现某接口 + return true; } /// - /// 获取类符号上的LogAttribute特性 + /// 生成 Logger 字段的代码 /// - /// 类符号 - /// LogAttribute特性数据,如果不存在则返回null - private static AttributeData? GetAttribute(INamedTypeSymbol classSymbol) + protected override string Generate(INamedTypeSymbol symbol, AttributeData attr) { - return classSymbol.GetAttributes().FirstOrDefault(a => - { - var cls = a.AttributeClass; - if (cls == null) return false; - - // 宽松匹配:全名匹配 OR 名字匹配 - return cls.ToDisplayString() == AttributeMetadataName || - cls.Name == AttributeShortName; - }); - } - - /// - /// 生成日志字段代码 - /// - /// 类符号 - /// LogAttribute特性数据 - /// 生成的C#代码字符串 - private static string Generate(INamedTypeSymbol classSymbol, AttributeData attr) - { - var ns = classSymbol.ContainingNamespace.IsGlobalNamespace + var ns = symbol.ContainingNamespace.IsGlobalNamespace ? 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) { var argValue = attr.ConstructorArguments[0].Value; - - name = argValue switch - { - // 情况 1: 参数存在,但值为 null (例如 [Log] 且构造函数有默认值 null) - null => className, - // 情况 2: 参数存在,且是有效的字符串 (例如 [Log("MyCategory")]) - string s when !string.IsNullOrWhiteSpace(s) => s, - _ => $"{className}_InvalidArg" - }; + name = argValue is string s && !string.IsNullOrWhiteSpace(s) + ? s + : className; } - // === 解析 Named Arguments (更加安全的获取方式) === + // 解析命名参数 var fieldName = GetNamedArg(attr, "FieldName")?.ToString() ?? "_log"; - var access = - GetNamedArg(attr, "AccessModifier")?.ToString() ?? - "private"; // 注意:如果你的 AccessModifier 是枚举,这里得到的可能是 int 或枚举名 - - // 处理 bool 类型 + var access = GetNamedArg(attr, "AccessModifier")?.ToString() ?? "private"; 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 sb = new StringBuilder(); sb.AppendLine("// "); - sb.AppendLine("using GFramework.Core.logging;"); // 确保这里引用了 ILog 和 Log 类 + sb.AppendLine("using GFramework.Core.logging;"); if (ns is not null) { - sb.AppendLine($"namespace {ns}"); - sb.AppendLine("{"); + sb.AppendLine($"namespace {ns};"); + sb.AppendLine(); } - sb.AppendLine($" public partial class {className}"); - sb.AppendLine(" {"); - sb.AppendLine(" /// Auto-generated logger"); + sb.AppendLine($"partial class {className}"); + sb.AppendLine("{"); + sb.AppendLine(" /// Auto-generated logger"); sb.AppendLine( - $" {access} {staticKeyword}readonly ILogger {fieldName} = " + - $"new ConsoleLoggerFactory().GetLogger(\"{name}\");"); - sb.AppendLine(" }"); + $" {access} {staticKeyword}readonly ILogger {fieldName} = new ConsoleLoggerFactory().GetLogger(\"{name}\");"); + sb.AppendLine("}"); - if (ns is not null) - sb.AppendLine("}"); - - return sb.ToString(); + return sb.ToString().TrimEnd(); } /// - /// 从特性数据中获取命名参数的值 + /// 可以自定义生成文件名 /// - /// 特性数据 - /// 参数名称 - /// 参数值,如果不存在则返回null + protected override string GetHintName(INamedTypeSymbol symbol) + => $"{symbol.Name}.Logger.g.cs"; + private static object? GetNamedArg(AttributeData attr, string name) - { - return (from kv in attr.NamedArguments where kv.Key == name select kv.Value.Value).FirstOrDefault(); - } + => attr.NamedArguments.FirstOrDefault(kv => kv.Key == name).Value.Value; } \ No newline at end of file