mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-23 03:04:29 +08:00
- 使用预定义的 Diagnostics.MustBePartial 替代手动创建 DiagnosticDescriptor - 优化构造函数参数检查逻辑,添加对 null 值和无效类型的处理 - 增加三种参数情况的处理:null 值、有效字符串、无效类型 - 添加错误情况下的回退机制,使用默认分类值 - 改进代码注释和错误处理的健壮性
181 lines
7.6 KiB
C#
181 lines
7.6 KiB
C#
using System;
|
||
using System.Linq;
|
||
using System.Text;
|
||
using Microsoft.CodeAnalysis;
|
||
using Microsoft.CodeAnalysis.CSharp;
|
||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||
using Microsoft.CodeAnalysis.Text;
|
||
|
||
namespace GFramework.Generator.generator.logging
|
||
{
|
||
[Generator]
|
||
public sealed class LoggerGenerator : IIncrementalGenerator
|
||
{
|
||
// 请确保这里的命名空间和类名与 Attributes 项目中定义的完全一致(注意大小写!)
|
||
private const string AttributeMetadataName = "GFramework.Generator.Attributes.generator.logging.LogAttribute";
|
||
private const string AttributeShortName = "LogAttribute";
|
||
private const string AttributeShortNameWithoutSuffix = "Log";
|
||
|
||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||
{
|
||
// 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-catch 块 ===
|
||
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))
|
||
{
|
||
// 如果 Diagnostics 类有问题,这里可能会崩,先注释掉或确保该类存在
|
||
spc.ReportDiagnostic(Diagnostic.Create(
|
||
Diagnostics.MustBePartial,
|
||
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));
|
||
}
|
||
});
|
||
}
|
||
|
||
private static AttributeData GetAttribute(INamedTypeSymbol classSymbol)
|
||
{
|
||
return classSymbol.GetAttributes().FirstOrDefault(a =>
|
||
{
|
||
var cls = a.AttributeClass;
|
||
if (cls == null) return false;
|
||
|
||
// 宽松匹配:全名匹配 OR 名字匹配
|
||
return cls.ToDisplayString() == AttributeMetadataName ||
|
||
cls.Name == AttributeShortName;
|
||
});
|
||
}
|
||
|
||
private static string Generate(INamedTypeSymbol classSymbol, AttributeData attr)
|
||
{
|
||
var ns = classSymbol.ContainingNamespace.IsGlobalNamespace
|
||
? null
|
||
: classSymbol.ContainingNamespace.ToDisplayString();
|
||
|
||
var className = classSymbol.Name;
|
||
|
||
// === 解析 Category ===
|
||
string category = className; // 默认使用类名
|
||
|
||
// 检查是否有构造函数参数
|
||
if (attr.ConstructorArguments.Length > 0)
|
||
{
|
||
var argValue = attr.ConstructorArguments[0].Value;
|
||
|
||
// 情况 1: 参数存在,但值为 null (例如 [Log] 且构造函数有默认值 null)
|
||
if (argValue is null)
|
||
{
|
||
category = className;
|
||
}
|
||
// 情况 2: 参数存在,且是有效的字符串 (例如 [Log("MyCategory")])
|
||
else if (argValue is string s && !string.IsNullOrWhiteSpace(s))
|
||
{
|
||
category = s;
|
||
}
|
||
// 情况 3: 参数存在,但类型不对 (防御性编程)
|
||
else
|
||
{
|
||
// 这里可以选择报错,或者回退到默认值。
|
||
// 为了让用户知道写错了,保留报错逻辑是合理的。
|
||
// 注意:这里需要你有 ReportDiagnostic 的逻辑,如果没有,为了不中断生成,建议回退到默认值。
|
||
|
||
// 如果你在 Generate 方法里无法轻松调用 ReportDiagnostic,
|
||
// 建议直接忽略错误使用默认值,或者生成一段 #error 代码。
|
||
category = $"{className}_InvalidArg";
|
||
}
|
||
}
|
||
|
||
// === 解析 Named Arguments (更加安全的获取方式) ===
|
||
var fieldName = GetNamedArg(attr, "FieldName")?.ToString() ?? "_log";
|
||
var access =
|
||
GetNamedArg(attr, "AccessModifier")?.ToString() ??
|
||
"private"; // 注意:如果你的 AccessModifier 是枚举,这里得到的可能是 int 或枚举名
|
||
|
||
// 处理 bool 类型
|
||
var isStaticObj = GetNamedArg(attr, "IsStatic");
|
||
var isStatic = isStaticObj is not bool b || b; // 默认为 true
|
||
|
||
var staticKeyword = isStatic ? "static " : "";
|
||
|
||
var sb = new StringBuilder();
|
||
sb.AppendLine("// <auto-generated />");
|
||
sb.AppendLine("using GFramework.Core.logging;"); // 确保这里引用了 ILog 和 Log 类
|
||
|
||
if (ns is not null)
|
||
{
|
||
sb.AppendLine($"namespace {ns}");
|
||
sb.AppendLine("{");
|
||
}
|
||
|
||
sb.AppendLine($" public partial class {className}");
|
||
sb.AppendLine(" {");
|
||
sb.AppendLine($" /// <summary>Auto-generated logger</summary>");
|
||
sb.AppendLine(
|
||
$" {access} {staticKeyword}readonly ILog {fieldName} = " +
|
||
$"Log.CreateLogger(\"{category}\");");
|
||
sb.AppendLine(" }");
|
||
|
||
if (ns is not null)
|
||
sb.AppendLine("}");
|
||
|
||
return sb.ToString();
|
||
}
|
||
|
||
private static object? GetNamedArg(AttributeData attr, string name)
|
||
{
|
||
foreach (var kv in attr.NamedArguments)
|
||
{
|
||
if (kv.Key == name)
|
||
return kv.Value.Value;
|
||
}
|
||
|
||
return null;
|
||
}
|
||
}
|
||
} |