feat(logging): 添加LogAttribute诊断验证功能

- 定义新的诊断描述符GFW_LOG001用于检测LogAttribute无法生成Logger的情况
- 在代码生成过程中验证LogAttribute是否存在及构造参数有效性
- 当LogAttribute缺失或参数无效时报告诊断错误并提供详细信息
- 修复代码格式化问题并优化代码生成逻辑
- 更新AnalyzerReleases.Unshipped.md文档添加新诊断规则
- 改进Generate方法参数传递和错误处理机制
This commit is contained in:
GwWuYou 2025-12-23 22:31:42 +08:00
parent 0221ed92a6
commit d4b37345db
3 changed files with 67 additions and 26 deletions

View File

@ -6,3 +6,4 @@
Rule ID | Category | Severity | Notes Rule ID | Category | Severity | Notes
--------|----------|----------|------- --------|----------|----------|-------
GFLOG001 | GFramework.Logging | Error | Diagnostics GFLOG001 | GFramework.Logging | Error | Diagnostics
GFW_LOG001 | GFramework.Logging | Warning | Diagnostics

View File

@ -7,8 +7,9 @@ namespace GFramework.Generator.generator.logging;
/// </summary> /// </summary>
internal static class Diagnostics internal static class Diagnostics
{ {
/// <summary> /// <summary>
/// 定义一个诊断描述符,用于检查使用[Log]特性的类是否声明为partial /// 定义诊断描述符:要求使用[Log]特性的类必须声明为partial
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// 当类使用[Log]特性但未声明为partial时编译器将报告此错误 /// 当类使用[Log]特性但未声明为partial时编译器将报告此错误
@ -22,4 +23,17 @@ internal static class Diagnostics
DiagnosticSeverity.Error, DiagnosticSeverity.Error,
isEnabledByDefault: true isEnabledByDefault: true
); );
/// <summary>
/// 定义诊断描述符LogAttribute无法生成Logger的错误情况
/// </summary>
public static readonly DiagnosticDescriptor LogAttributeInvalid =
new(
id: "GFW_LOG001",
title: "LogAttribute 无法生成 Logger",
messageFormat: "类 '{0}' 上的 LogAttribute 无法生效:{1}",
category: "GFramework.Logging",
DiagnosticSeverity.Warning,
isEnabledByDefault: true);
} }

View File

@ -70,7 +70,7 @@ namespace GFramework.Generator.generator.logging
return; return;
} }
var source = Generate(classSymbol); var source = Generate(classSymbol, spc);
spc.AddSource( spc.AddSource(
$"{classSymbol.Name}.Logger.g.cs", $"{classSymbol.Name}.Logger.g.cs",
SourceText.From(source, Encoding.UTF8)); SourceText.From(source, Encoding.UTF8));
@ -81,8 +81,9 @@ namespace GFramework.Generator.generator.logging
/// 生成日志字段代码 /// 生成日志字段代码
/// </summary> /// </summary>
/// <param name="classSymbol">类符号</param> /// <param name="classSymbol">类符号</param>
/// <param name="spc">源代码生成上下文</param>
/// <returns>生成的代码字符串</returns> /// <returns>生成的代码字符串</returns>
private static string Generate(INamedTypeSymbol classSymbol) private static string Generate(INamedTypeSymbol classSymbol, SourceProductionContext spc)
{ {
var ns = classSymbol.ContainingNamespace.IsGlobalNamespace var ns = classSymbol.ContainingNamespace.IsGlobalNamespace
? null ? null
@ -91,24 +92,47 @@ namespace GFramework.Generator.generator.logging
var className = classSymbol.Name; var className = classSymbol.Name;
var attr = classSymbol.GetAttributes() var attr = classSymbol.GetAttributes()
.First(a => a.AttributeClass!.ToDisplayString() == AttributeMetadataName); .FirstOrDefault(a =>
a.AttributeClass?.ToDisplayString() == AttributeMetadataName);
string categoryExpr; if (attr is null)
{
// 理论上不会发生,但防御式处理
spc.ReportDiagnostic(
Diagnostic.Create(
Diagnostics.LogAttributeInvalid,
classSymbol.Locations.FirstOrDefault(),
className,
"未找到 LogAttribute"
));
return string.Empty;
}
if (attr.ConstructorArguments.Length > 0 && // === 解析 category ===
attr.ConstructorArguments[0].Value is string s && string category;
if (attr.ConstructorArguments.Length == 0)
{
// 默认:类名
category = className;
}
else if (attr.ConstructorArguments[0].Value is string s &&
!string.IsNullOrWhiteSpace(s)) !string.IsNullOrWhiteSpace(s))
{ {
// 用户显式指定字符串 category = s;
categoryExpr = $"\"{s}\"";
} }
else else
{ {
// 默认使用 nameof(Class) spc.ReportDiagnostic(
categoryExpr = $"nameof({className})"; Diagnostic.Create(
Diagnostics.LogAttributeInvalid,
classSymbol.Locations.FirstOrDefault(),
className,
"LogAttribute 的构造参数不是有效的 string"
));
return string.Empty;
} }
var fieldName = GetNamedArg(attr, "FieldName", "_log"); var fieldName = GetNamedArg(attr, "FieldName", "_log");
var access = GetNamedArg(attr, "AccessModifier", "private"); var access = GetNamedArg(attr, "AccessModifier", "private");
var isStatic = GetNamedArg(attr, "IsStatic", true); var isStatic = GetNamedArg(attr, "IsStatic", true);
@ -128,8 +152,8 @@ namespace GFramework.Generator.generator.logging
sb.AppendLine($" public partial class {className}"); sb.AppendLine($" public partial class {className}");
sb.AppendLine(" {"); sb.AppendLine(" {");
sb.AppendLine( sb.AppendLine(
$" {access} {staticKeyword}readonly ILog {fieldName} =" + $" {access} {staticKeyword}readonly ILog {fieldName} = " +
$" Log.CreateLogger(\"{categoryExpr}\");"); $"Log.CreateLogger(\"{category}\");");
sb.AppendLine(" }"); sb.AppendLine(" }");
if (ns is not null) if (ns is not null)
@ -138,6 +162,7 @@ namespace GFramework.Generator.generator.logging
return sb.ToString(); return sb.ToString();
} }
/// <summary> /// <summary>
/// 获取属性参数的值 /// 获取属性参数的值
/// </summary> /// </summary>
@ -153,6 +178,7 @@ namespace GFramework.Generator.generator.logging
if (kv.Key == name && kv.Value.Value is T v) if (kv.Key == name && kv.Value.Value is T v)
return v; return v;
} }
return defaultValue; return defaultValue;
} }
} }