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
{
///
/// 日志生成器,用于为标记了LogAttribute的类自动生成日志字段
///
[Generator]
public sealed class LoggerGenerator : IIncrementalGenerator
{
private const string AttributeMetadataName =
"GFramework.Generator.Attributes.Logging.LogAttribute";
///
/// 初始化增量生成器
///
/// 增量生成器初始化上下文
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// 1. 拿到 LogAttribute Symbol
var logAttributeSymbol =
context.CompilationProvider.Select((compilation, _) =>
compilation.GetTypeByMetadataName(AttributeMetadataName));
// 2. 在 SyntaxProvider 阶段就拿到 SemanticModel
var candidates =
context.SyntaxProvider.CreateSyntaxProvider(
static (node, _) => node is ClassDeclarationSyntax,
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);
// 3. 合并 Attribute Symbol 并筛选
var targets =
candidates.Combine(logAttributeSymbol)
.Where(pair =>
{
var symbol = pair.Left.Symbol!;
var attrSymbol = pair.Right;
if (attrSymbol is null) return false;
return symbol.GetAttributes().Any(a =>
SymbolEqualityComparer.Default.Equals(a.AttributeClass, attrSymbol));
});
// 4. 输出代码
context.RegisterSourceOutput(targets, (spc, pair) =>
{
var classDecl = pair.Left.ClassDecl;
var classSymbol = pair.Left.Symbol!;
// 必须是 partial
if (!classDecl.Modifiers.Any(SyntaxKind.PartialKeyword))
{
spc.ReportDiagnostic(
Diagnostic.Create(
Diagnostics.MustBePartial,
classDecl.Identifier.GetLocation(),
classSymbol.Name
));
return;
}
var source = Generate(classSymbol);
spc.AddSource(
$"{classSymbol.Name}.Logger.g.cs",
SourceText.From(source, Encoding.UTF8));
});
}
///
/// 生成日志字段代码
///
/// 类符号
/// 生成的代码字符串
private static string Generate(INamedTypeSymbol classSymbol)
{
var ns = classSymbol.ContainingNamespace.IsGlobalNamespace
? null
: classSymbol.ContainingNamespace.ToDisplayString();
var className = classSymbol.Name;
var attr = classSymbol.GetAttributes()
.First(a => a.AttributeClass!.ToDisplayString() == AttributeMetadataName);
string categoryExpr;
if (attr.ConstructorArguments.Length > 0 &&
attr.ConstructorArguments[0].Value is string s &&
!string.IsNullOrWhiteSpace(s))
{
// 用户显式指定字符串
categoryExpr = $"\"{s}\"";
}
else
{
// 默认使用 nameof(Class)
categoryExpr = $"nameof({className})";
}
var fieldName = GetNamedArg(attr, "FieldName", "_log");
var access = GetNamedArg(attr, "AccessModifier", "private");
var isStatic = GetNamedArg(attr, "IsStatic", true);
var staticKeyword = isStatic ? "static " : "";
var sb = new StringBuilder();
sb.AppendLine("// ");
sb.AppendLine("using GFramework.Core.logging;");
if (ns is not null)
{
sb.AppendLine($"namespace {ns}");
sb.AppendLine("{");
}
sb.AppendLine($" public partial class {className}");
sb.AppendLine(" {");
sb.AppendLine(
$" {access} {staticKeyword}readonly ILog {fieldName} =" +
$" Log.CreateLogger(\"{categoryExpr}\");");
sb.AppendLine(" }");
if (ns is not null)
sb.AppendLine("}");
return sb.ToString();
}
///
/// 获取属性参数的值
///
/// 参数类型
/// 属性数据
/// 参数名称
/// 默认值
/// 参数值或默认值
private static T GetNamedArg(AttributeData attr, string name, T defaultValue)
{
foreach (var kv in attr.NamedArguments)
{
if (kv.Key == name && kv.Value.Value is T v)
return v;
}
return defaultValue;
}
}
}