From 603b06325defe3c4fdc2132640fb91613cdefeab Mon Sep 17 00:00:00 2001
From: GwWuYou <95328647+GeWuYou@users.noreply.github.com>
Date: Mon, 29 Dec 2025 20:30:54 +0800
Subject: [PATCH] =?UTF-8?q?refactor(source-generators):=20=E4=BC=98?=
=?UTF-8?q?=E5=8C=96ContextAware=E7=94=9F=E6=88=90=E5=99=A8=E5=AE=9E?=
=?UTF-8?q?=E7=8E=B0=E5=B9=B6=E6=B7=BB=E5=8A=A0=E5=BF=AB=E7=85=A7=E6=B5=8B?=
=?UTF-8?q?=E8=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 为ContextAwareGenerator添加详细的XML文档注释
- 简化接口验证逻辑,合并条件判断语句
- 修正特性数据参数命名,统一使用attr命名
- 为接口实现方法添加global::前缀以确保类型解析正确
- 移除未使用的回退方法体,简化方法实现逻辑
- 新增GeneratorSnapshotTest通用快照测试类
- 添加ContextAwareGeneratorSnapshotTests快照测试
- 移除原有的硬编码期望值测试方法
- 修正接口实现中的全局命名空间前缀格式
---
.../GFramework.SourceGenerators.Tests.csproj | 4 +
.../core/GeneratorSnapshotTest.cs | 70 +++++++++++
.../ContextAwareGeneratorSnapshotTests.cs | 69 +++++++++++
.../rule/ContextAwareGeneratorTests.cs | 87 +-------------
.../rule/ContextAwareGenerator.cs | 110 +++++++++++-------
5 files changed, 214 insertions(+), 126 deletions(-)
create mode 100644 GFramework.SourceGenerators.Tests/core/GeneratorSnapshotTest.cs
create mode 100644 GFramework.SourceGenerators.Tests/rule/ContextAwareGeneratorSnapshotTests.cs
diff --git a/GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj b/GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj
index 75655b0..073a052 100644
--- a/GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj
+++ b/GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj
@@ -22,4 +22,8 @@
+
+
+
+
diff --git a/GFramework.SourceGenerators.Tests/core/GeneratorSnapshotTest.cs b/GFramework.SourceGenerators.Tests/core/GeneratorSnapshotTest.cs
new file mode 100644
index 0000000..23e0e6b
--- /dev/null
+++ b/GFramework.SourceGenerators.Tests/core/GeneratorSnapshotTest.cs
@@ -0,0 +1,70 @@
+using Microsoft.CodeAnalysis.CSharp.Testing;
+using Microsoft.CodeAnalysis.Testing;
+using NUnit.Framework;
+
+namespace GFramework.SourceGenerators.Tests.core;
+
+///
+/// 用于测试源代码生成器的快照测试类
+///
+/// 要测试的源代码生成器类型
+public static class GeneratorSnapshotTest
+ where TGenerator : new()
+{
+ ///
+ /// 运行源代码生成器的快照测试
+ ///
+ /// 输入的源代码字符串
+ /// 快照文件存储的文件夹路径
+ /// 异步任务
+ public static async Task RunAsync(
+ string source,
+ string snapshotFolder)
+ {
+ var test = new CSharpSourceGeneratorTest
+ {
+ TestState =
+ {
+ Sources = { source }
+ },
+ TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck,
+ DisabledDiagnostics = { "GF_Common_Trace_001" }
+ };
+
+ await test.RunAsync();
+
+ var generated = test.TestState.GeneratedSources;
+
+ foreach (var (filename, content) in generated)
+ {
+ var path = Path.Combine(
+ snapshotFolder,
+ filename);
+
+ if (!File.Exists(path))
+ {
+ // 第一次运行:生成 snapshot
+ Directory.CreateDirectory(Path.GetDirectoryName(path)!);
+ await File.WriteAllTextAsync(path, content.ToString());
+
+ Assert.Fail(
+ $"Snapshot not found. Generated new snapshot at:\n{path}");
+ }
+
+ var expected = await File.ReadAllTextAsync(path);
+
+ Assert.That(
+ Normalize(expected),
+ Is.EqualTo(Normalize(content.ToString())),
+ $"Snapshot mismatch: {filename}");
+ }
+ }
+
+ ///
+ /// 标准化文本内容,将换行符统一为\n并去除首尾空白
+ ///
+ /// 要标准化的文本
+ /// 标准化后的文本
+ private static string Normalize(string text)
+ => text.Replace("\r\n", "\n").Trim();
+}
\ No newline at end of file
diff --git a/GFramework.SourceGenerators.Tests/rule/ContextAwareGeneratorSnapshotTests.cs b/GFramework.SourceGenerators.Tests/rule/ContextAwareGeneratorSnapshotTests.cs
new file mode 100644
index 0000000..f4fd820
--- /dev/null
+++ b/GFramework.SourceGenerators.Tests/rule/ContextAwareGeneratorSnapshotTests.cs
@@ -0,0 +1,69 @@
+using GFramework.SourceGenerators.rule;
+using GFramework.SourceGenerators.Tests.core;
+using NUnit.Framework;
+
+namespace GFramework.SourceGenerators.Tests.rule;
+
+///
+/// 上下文感知生成器快照测试类
+/// 用于测试ContextAwareGenerator源代码生成器的输出快照
+///
+[TestFixture]
+public class ContextAwareGeneratorSnapshotTests
+{
+ ///
+ /// 测试ContextAwareGenerator源代码生成器的快照功能
+ /// 验证生成器对带有ContextAware特性的类的处理结果
+ ///
+ /// 异步任务,无返回值
+ [Test]
+ public async Task Snapshot_ContextAwareGenerator()
+ {
+ // 定义测试用的源代码,包含ContextAware特性和相关接口定义
+ const string source = """
+ using System;
+
+ namespace GFramework.SourceGenerators.Abstractions.rule
+ {
+ [AttributeUsage(AttributeTargets.Class)]
+ public sealed class ContextAwareAttribute : Attribute { }
+ }
+
+ namespace GFramework.Core.Abstractions.rule
+ {
+ public interface IContextAware
+ {
+ void SetContext(
+ GFramework.Core.Abstractions.architecture.IArchitectureContext context);
+
+ GFramework.Core.Abstractions.architecture.IArchitectureContext GetContext();
+ }
+ }
+
+ namespace GFramework.Core.Abstractions.architecture
+ {
+ public interface IArchitectureContext { }
+ }
+
+ namespace TestApp
+ {
+ using GFramework.SourceGenerators.Abstractions.rule;
+ using GFramework.Core.Abstractions.rule;
+
+ [ContextAware]
+ public partial class MyRule : IContextAware
+ {
+ }
+ }
+ """;
+
+ // 执行生成器快照测试,将生成的代码与预期快照进行比较
+ await GeneratorSnapshotTest.RunAsync(
+ source,
+ snapshotFolder: Path.Combine(
+ TestContext.CurrentContext.TestDirectory,
+ "rule",
+ "snapshots",
+ "ContextAwareGenerator"));
+ }
+}
\ No newline at end of file
diff --git a/GFramework.SourceGenerators.Tests/rule/ContextAwareGeneratorTests.cs b/GFramework.SourceGenerators.Tests/rule/ContextAwareGeneratorTests.cs
index 3c36dd7..a714823 100644
--- a/GFramework.SourceGenerators.Tests/rule/ContextAwareGeneratorTests.cs
+++ b/GFramework.SourceGenerators.Tests/rule/ContextAwareGeneratorTests.cs
@@ -1,4 +1,4 @@
-using GFramework.SourceGenerators.rule;
+using GFramework.SourceGenerators.rule;
using GFramework.SourceGenerators.Tests.core;
using NUnit.Framework;
@@ -7,83 +7,6 @@ namespace GFramework.SourceGenerators.Tests.rule;
[TestFixture]
public class ContextAwareGeneratorTests
{
- [Test]
- public async Task Generates_ContextAware_Code()
- {
- const string source = """
- using System;
-
- namespace GFramework.SourceGenerators.Abstractions.rule
- {
- [AttributeUsage(AttributeTargets.Class)]
- public sealed class ContextAwareAttribute : Attribute
- {
- }
- }
-
- namespace TestApp
- {
- using GFramework.SourceGenerators.Abstractions.rule;
-
- [ContextAware]
- public partial class MyRule
- : GFramework.Core.Abstractions.rule.IContextAware
- {
- }
- }
- """;
-
- const string frameworkStub = """
- namespace GFramework.Core.Abstractions.rule
- {
- public interface IContextAware
- {
- void SetContext(
- GFramework.Core.Abstractions.architecture.IArchitectureContext context);
-
- GFramework.Core.Abstractions.architecture.IArchitectureContext GetContext();
- }
- }
-
- namespace GFramework.Core.Abstractions.architecture
- {
- public interface IArchitectureContext {}
- }
- """;
-
- const string expected = """
- //
- #nullable enable
-
- namespace TestApp;
-
- partial class MyRule
- {
- ///
- /// 自动注入的架构上下文
- ///
- protected GFramework.Core.Abstractions.architecture.IArchitectureContext Context { get; private set; } = null!;
-
- void global::GFramework.Core.Abstractions.rule.IContextAware.SetContext(
- global::GFramework.Core.Abstractions.architecture.IArchitectureContext context)
- {
- Context = context;
- }
-
- global::GFramework.Core.Abstractions.architecture.IArchitectureContext
- global::GFramework.Core.Abstractions.rule.IContextAware.GetContext()
- {
- return Context;
- }
- }
- """;
-
- await GeneratorTest.RunAsync(
- source + "\n" + frameworkStub,
- ("MyRule.ContextAware.g.cs", expected)
- );
- }
-
[Test]
public async Task Generates_ContextAware_Code_When_Interface_Inherits_IContextAware()
{
@@ -145,20 +68,20 @@ public class ContextAwareGeneratorTests
///
protected GFramework.Core.Abstractions.architecture.IArchitectureContext Context { get; private set; } = null!;
- void GFramework.Core.Abstractions.rule.IContextAware.SetContext(
- GFramework.Core.Abstractions.architecture.IArchitectureContext context)
+ void global::GFramework.Core.Abstractions.rule.IContextAware.SetContext(global::GFramework.Core.Abstractions.architecture.IArchitectureContext context)
{
Context = context;
}
- GFramework.Core.Abstractions.architecture.IArchitectureContext
- GFramework.Core.Abstractions.rule.IContextAware.GetContext()
+ global::GFramework.Core.Abstractions.architecture.IArchitectureContext global::GFramework.Core.Abstractions.rule.IContextAware.GetContext()
{
return Context;
}
+
}
""";
+
await GeneratorTest.RunAsync(
source + "\n" + frameworkStub,
("MyRule.ContextAware.g.cs", expected)
diff --git a/GFramework.SourceGenerators/rule/ContextAwareGenerator.cs b/GFramework.SourceGenerators/rule/ContextAwareGenerator.cs
index 5d825b4..199b7f4 100644
--- a/GFramework.SourceGenerators/rule/ContextAwareGenerator.cs
+++ b/GFramework.SourceGenerators/rule/ContextAwareGenerator.cs
@@ -8,14 +8,32 @@ using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace GFramework.SourceGenerators.rule;
+///
+/// 上下文感知生成器,用于为标记了ContextAware特性的类自动生成IContextAware接口实现
+///
[Generator]
public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase
{
+ ///
+ /// 获取特性的元数据名称
+ ///
protected override string AttributeMetadataName =>
$"{PathContests.SourceGeneratorsAbstractionsPath}.rule.ContextAwareAttribute";
+ ///
+ /// 获取特性的短名称(不包含后缀)
+ ///
protected override string AttributeShortNameWithoutSuffix => "ContextAware";
+ ///
+ /// 验证符号是否符合生成条件
+ ///
+ /// 源生产上下文
+ /// 编译对象
+ /// 类声明语法节点
+ /// 命名类型符号
+ /// 特性数据
+ /// 验证是否通过
protected override bool ValidateSymbol(
SourceProductionContext context,
Compilation compilation,
@@ -26,16 +44,8 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase
var iContextAware = compilation.GetTypeByMetadataName(
$"{PathContests.CoreAbstractionsNamespace}.rule.IContextAware");
- if (iContextAware is null)
- {
- context.ReportDiagnostic(Diagnostic.Create(
- ContextAwareDiagnostic.ClassMustImplementIContextAware,
- syntax.Identifier.GetLocation(),
- symbol.Name));
- return false;
- }
-
- if (!symbol.AllInterfaces.Any(i =>
+ if (iContextAware is null ||
+ !symbol.AllInterfaces.Any(i =>
SymbolEqualityComparer.Default.Equals(i, iContextAware)))
{
context.ReportDiagnostic(Diagnostic.Create(
@@ -49,12 +59,12 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase
}
///
- /// 生成源代码
+ /// 生成源代码
///
/// 源生产上下文
/// 编译对象
/// 命名类型符号
- /// 属性数据
+ /// 特性数据
/// 生成的源代码字符串
protected override string Generate(
SourceProductionContext context,
@@ -65,8 +75,9 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase
var ns = symbol.ContainingNamespace.IsGlobalNamespace
? null
: symbol.ContainingNamespace.ToDisplayString();
+
var iContextAware = compilation.GetTypeByMetadataName(
- $"{PathContests.CoreAbstractionsNamespace}.rule.IContextAware");
+ $"{PathContests.CoreAbstractionsNamespace}.rule.IContextAware")!;
var sb = new StringBuilder();
sb.AppendLine("// ");
@@ -83,18 +94,27 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase
sb.AppendLine("{");
GenerateContextProperty(sb);
- GenerateInterfaceImplementations(sb, iContextAware!);
+ GenerateInterfaceImplementations(sb, iContextAware);
sb.AppendLine("}");
return sb.ToString().TrimEnd();
}
+ ///
+ /// 获取生成文件的提示名称
+ ///
+ /// 命名类型符号
+ /// 生成文件的提示名称
protected override string GetHintName(INamedTypeSymbol symbol)
=> $"{symbol.Name}.ContextAware.g.cs";
// =========================
- // 生成 Context 属性
+ // Context 属性(无 global::,与测试一致)
// =========================
+ ///
+ /// 生成Context属性
+ ///
+ /// 字符串构建器
private static void GenerateContextProperty(StringBuilder sb)
{
sb.AppendLine(" /// ");
@@ -106,28 +126,39 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase
}
// =========================
- // 自动实现接口方法
+ // 显式接口实现(使用 global::)
// =========================
+ ///
+ /// 生成接口实现
+ ///
+ /// 字符串构建器
+ /// 接口符号
private static void GenerateInterfaceImplementations(
StringBuilder sb,
INamedTypeSymbol interfaceSymbol)
{
- var interfaceFullName = interfaceSymbol.ToDisplayString(
+ var interfaceName = interfaceSymbol.ToDisplayString(
SymbolDisplayFormat.FullyQualifiedFormat);
- foreach (var member in interfaceSymbol.GetMembers().OfType())
+ foreach (var method in interfaceSymbol.GetMembers().OfType())
{
- if (member.MethodKind != MethodKind.Ordinary)
+ if (method.MethodKind != MethodKind.Ordinary)
continue;
- GenerateMethod(sb, interfaceFullName, member);
+ GenerateMethod(sb, interfaceName, method);
sb.AppendLine();
}
}
+ ///
+ /// 生成方法实现
+ ///
+ /// 字符串构建器
+ /// 接口名称
+ /// 方法符号
private static void GenerateMethod(
StringBuilder sb,
- string interfaceFullName,
+ string interfaceName,
IMethodSymbol method)
{
var returnType = method.ReturnType.ToDisplayString(
@@ -138,16 +169,19 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase
$"{p.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)} {p.Name}"));
sb.AppendLine(
- $" {returnType} {interfaceFullName}.{method.Name}({parameters})");
-
+ $" {returnType} {interfaceName}.{method.Name}({parameters})");
sb.AppendLine(" {");
+
GenerateMethodBody(sb, method);
+
sb.AppendLine(" }");
}
- // =========================
- // 方法语义策略
- // =========================
+ ///
+ /// 生成方法体
+ ///
+ /// 字符串构建器
+ /// 方法符号
private static void GenerateMethodBody(
StringBuilder sb,
IMethodSymbol method)
@@ -163,25 +197,13 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase
break;
default:
- GenerateFallbackBody(sb, method);
+ if (!method.ReturnsVoid)
+ {
+ sb.AppendLine(
+ $" throw new System.NotImplementedException(\"Method '{method.Name}' is not supported.\");");
+ }
+
break;
}
}
-
- private static void GenerateFallbackBody(
- StringBuilder sb,
- IMethodSymbol method)
- {
- if (method.ReturnsVoid)
- {
- sb.AppendLine(" // no-op");
- }
- else
- {
- sb.AppendLine(
- $" throw new System.NotImplementedException(");
- sb.AppendLine(
- $" \"Method '{method.Name}' is not supported by ContextAwareGenerator.\");");
- }
- }
}
\ No newline at end of file