GFramework/GFramework.SourceGenerators/rule/ContextAwareGenerator.cs
GwWuYou 603b06325d refactor(source-generators): 优化ContextAware生成器实现并添加快照测试
- 为ContextAwareGenerator添加详细的XML文档注释
- 简化接口验证逻辑,合并条件判断语句
- 修正特性数据参数命名,统一使用attr命名
- 为接口实现方法添加global::前缀以确保类型解析正确
- 移除未使用的回退方法体,简化方法实现逻辑
- 新增GeneratorSnapshotTest通用快照测试类
- 添加ContextAwareGeneratorSnapshotTests快照测试
- 移除原有的硬编码期望值测试方法
- 修正接口实现中的全局命名空间前缀格式
2025-12-29 20:30:54 +08:00

209 lines
6.8 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Linq;
using System.Text;
using GFramework.SourceGenerators.Common.constants;
using GFramework.SourceGenerators.Common.generator;
using GFramework.SourceGenerators.diagnostics;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace GFramework.SourceGenerators.rule;
/// <summary>
/// 上下文感知生成器用于为标记了ContextAware特性的类自动生成IContextAware接口实现
/// </summary>
[Generator]
public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase
{
/// <summary>
/// 获取特性的元数据名称
/// </summary>
protected override string AttributeMetadataName =>
$"{PathContests.SourceGeneratorsAbstractionsPath}.rule.ContextAwareAttribute";
/// <summary>
/// 获取特性的短名称(不包含后缀)
/// </summary>
protected override string AttributeShortNameWithoutSuffix => "ContextAware";
/// <summary>
/// 验证符号是否符合生成条件
/// </summary>
/// <param name="context">源生产上下文</param>
/// <param name="compilation">编译对象</param>
/// <param name="syntax">类声明语法节点</param>
/// <param name="symbol">命名类型符号</param>
/// <param name="attr">特性数据</param>
/// <returns>验证是否通过</returns>
protected override bool ValidateSymbol(
SourceProductionContext context,
Compilation compilation,
ClassDeclarationSyntax syntax,
INamedTypeSymbol symbol,
AttributeData attr)
{
var iContextAware = compilation.GetTypeByMetadataName(
$"{PathContests.CoreAbstractionsNamespace}.rule.IContextAware");
if (iContextAware is null ||
!symbol.AllInterfaces.Any(i =>
SymbolEqualityComparer.Default.Equals(i, iContextAware)))
{
context.ReportDiagnostic(Diagnostic.Create(
ContextAwareDiagnostic.ClassMustImplementIContextAware,
syntax.Identifier.GetLocation(),
symbol.Name));
return false;
}
return true;
}
/// <summary>
/// 生成源代码
/// </summary>
/// <param name="context">源生产上下文</param>
/// <param name="compilation">编译对象</param>
/// <param name="symbol">命名类型符号</param>
/// <param name="attr">特性数据</param>
/// <returns>生成的源代码字符串</returns>
protected override string Generate(
SourceProductionContext context,
Compilation compilation,
INamedTypeSymbol symbol,
AttributeData attr)
{
var ns = symbol.ContainingNamespace.IsGlobalNamespace
? null
: symbol.ContainingNamespace.ToDisplayString();
var iContextAware = compilation.GetTypeByMetadataName(
$"{PathContests.CoreAbstractionsNamespace}.rule.IContextAware")!;
var sb = new StringBuilder();
sb.AppendLine("// <auto-generated/>");
sb.AppendLine("#nullable enable");
sb.AppendLine();
if (ns is not null)
{
sb.AppendLine($"namespace {ns};");
sb.AppendLine();
}
sb.AppendLine($"partial class {symbol.Name}");
sb.AppendLine("{");
GenerateContextProperty(sb);
GenerateInterfaceImplementations(sb, iContextAware);
sb.AppendLine("}");
return sb.ToString().TrimEnd();
}
/// <summary>
/// 获取生成文件的提示名称
/// </summary>
/// <param name="symbol">命名类型符号</param>
/// <returns>生成文件的提示名称</returns>
protected override string GetHintName(INamedTypeSymbol symbol)
=> $"{symbol.Name}.ContextAware.g.cs";
// =========================
// Context 属性(无 global::,与测试一致)
// =========================
/// <summary>
/// 生成Context属性
/// </summary>
/// <param name="sb">字符串构建器</param>
private static void GenerateContextProperty(StringBuilder sb)
{
sb.AppendLine(" /// <summary>");
sb.AppendLine(" /// 自动注入的架构上下文");
sb.AppendLine(" /// </summary>");
sb.AppendLine(
$" protected {PathContests.CoreAbstractionsNamespace}.architecture.IArchitectureContext Context {{ get; private set; }} = null!;");
sb.AppendLine();
}
// =========================
// 显式接口实现(使用 global::
// =========================
/// <summary>
/// 生成接口实现
/// </summary>
/// <param name="sb">字符串构建器</param>
/// <param name="interfaceSymbol">接口符号</param>
private static void GenerateInterfaceImplementations(
StringBuilder sb,
INamedTypeSymbol interfaceSymbol)
{
var interfaceName = interfaceSymbol.ToDisplayString(
SymbolDisplayFormat.FullyQualifiedFormat);
foreach (var method in interfaceSymbol.GetMembers().OfType<IMethodSymbol>())
{
if (method.MethodKind != MethodKind.Ordinary)
continue;
GenerateMethod(sb, interfaceName, method);
sb.AppendLine();
}
}
/// <summary>
/// 生成方法实现
/// </summary>
/// <param name="sb">字符串构建器</param>
/// <param name="interfaceName">接口名称</param>
/// <param name="method">方法符号</param>
private static void GenerateMethod(
StringBuilder sb,
string interfaceName,
IMethodSymbol method)
{
var returnType = method.ReturnType.ToDisplayString(
SymbolDisplayFormat.FullyQualifiedFormat);
var parameters = string.Join(", ",
method.Parameters.Select(p =>
$"{p.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)} {p.Name}"));
sb.AppendLine(
$" {returnType} {interfaceName}.{method.Name}({parameters})");
sb.AppendLine(" {");
GenerateMethodBody(sb, method);
sb.AppendLine(" }");
}
/// <summary>
/// 生成方法体
/// </summary>
/// <param name="sb">字符串构建器</param>
/// <param name="method">方法符号</param>
private static void GenerateMethodBody(
StringBuilder sb,
IMethodSymbol method)
{
switch (method.Name)
{
case "SetContext":
sb.AppendLine(" Context = context;");
break;
case "GetContext":
sb.AppendLine(" return Context;");
break;
default:
if (!method.ReturnsVoid)
{
sb.AppendLine(
$" throw new System.NotImplementedException(\"Method '{method.Name}' is not supported.\");");
}
break;
}
}
}