diff --git a/GFramework.Core.SourceGenerators/Enums/EnumExtensionsGenerator.cs b/GFramework.Core.SourceGenerators/Enums/EnumExtensionsGenerator.cs
index 493c10e1..a73d076d 100644
--- a/GFramework.Core.SourceGenerators/Enums/EnumExtensionsGenerator.cs
+++ b/GFramework.Core.SourceGenerators/Enums/EnumExtensionsGenerator.cs
@@ -1,4 +1,5 @@
-using GFramework.SourceGenerators.Common.Constants;
+using GFramework.Core.SourceGenerators.Abstractions.Enums;
+using GFramework.SourceGenerators.Common.Constants;
using GFramework.SourceGenerators.Common.Diagnostics;
using GFramework.SourceGenerators.Common.Generator;
@@ -18,6 +19,12 @@ public sealed class EnumExtensionsGenerator : AttributeEnumGeneratorBase
///
protected override string AttributeShortNameWithoutSuffix => "GenerateEnumExtensions";
+ ///
+ /// 按元数据名称解析枚举上的 GenerateEnumExtensionsAttribute。
+ ///
+ /// 当前编译上下文。
+ /// 待检查的枚举符号。
+ /// 匹配到的属性数据;若未标注目标属性则返回 。
protected override AttributeData? ResolveAttribute(Compilation compilation, INamedTypeSymbol symbol)
{
var attrSymbol = compilation.GetTypeByMetadataName(AttributeMetadataName);
@@ -30,6 +37,15 @@ public sealed class EnumExtensionsGenerator : AttributeEnumGeneratorBase
SymbolEqualityComparer.Default.Equals(a.AttributeClass, attrSymbol));
}
+ ///
+ /// 验证候选符号仍然是可生成扩展方法的枚举类型。
+ ///
+ /// 源生成诊断上下文。
+ /// 当前编译上下文。
+ /// 候选枚举声明语法。
+ /// 候选命名类型符号。
+ /// 已解析出的生成器属性。
+ /// 当符号满足生成前置条件时返回 。
protected override bool ValidateSymbol(SourceProductionContext context, Compilation compilation,
EnumDeclarationSyntax syntax,
INamedTypeSymbol symbol, AttributeData attr)
@@ -56,6 +72,14 @@ public sealed class EnumExtensionsGenerator : AttributeEnumGeneratorBase
? null
: symbol.ContainingNamespace.ToDisplayString();
+ var generateIsMethods = GetNamedBooleanArgument(
+ attr,
+ nameof(GenerateEnumExtensionsAttribute.GenerateIsMethods),
+ true);
+ var generateIsInMethod = GetNamedBooleanArgument(
+ attr,
+ nameof(GenerateEnumExtensionsAttribute.GenerateIsInMethod),
+ true);
var enumName = symbol.Name;
var fullEnumName = symbol.ToDisplayString();
var members = symbol.GetMembers()
@@ -74,23 +98,28 @@ public sealed class EnumExtensionsGenerator : AttributeEnumGeneratorBase
sb.AppendLine($" public static partial class {enumName}Extensions");
sb.AppendLine(" {");
- // 生成 IsX 方法
- foreach (var memberName in members.Select(m => m.Name))
+ // 两个生成开关是彼此独立的契约,需要分别控制输出,并保持空行布局稳定,便于快照精确回归。
+ var hasGeneratedMembers = false;
+
+ if (generateIsMethods)
{
- sb.AppendLine($" /// 是否为 {memberName}");
- sb.AppendLine(
- $" public static bool Is{memberName}(this {fullEnumName} value) => value == {fullEnumName}.{memberName};");
- sb.AppendLine();
+ hasGeneratedMembers = AppendIsMethods(
+ sb,
+ members,
+ fullEnumName);
}
- // 生成 IsIn 方法
- sb.AppendLine(" /// 判断是否属于指定集合");
- sb.AppendLine($" public static bool IsIn(this {fullEnumName} value, params {fullEnumName}[] values)");
- sb.AppendLine(" {");
- sb.AppendLine(" if (values == null) return false;");
- sb.AppendLine(" foreach (var v in values) if (value == v) return true;");
- sb.AppendLine(" return false;");
- sb.AppendLine(" }");
+ if (generateIsInMethod)
+ {
+ if (hasGeneratedMembers)
+ {
+ sb.AppendLine();
+ }
+
+ AppendIsInMethod(
+ sb,
+ fullEnumName);
+ }
sb.AppendLine(" }");
sb.AppendLine("}"); // namespace
@@ -107,4 +136,69 @@ public sealed class EnumExtensionsGenerator : AttributeEnumGeneratorBase
{
return $"{symbol.Name}.EnumExtensions.g.cs";
}
+
+ ///
+ /// 读取属性上的命名布尔参数,并在参数未显式提供时回退到属性契约默认值。
+ ///
+ /// 待读取的属性数据。
+ /// 命名参数名称。
+ /// 属性未提供该参数时使用的默认值。
+ /// 解析得到的布尔值;若参数缺失或类型不匹配则返回 。
+ private static bool GetNamedBooleanArgument(AttributeData attribute, string argumentName, bool defaultValue)
+ {
+ foreach (var namedArgument in attribute.NamedArguments)
+ {
+ if (namedArgument.Key == argumentName &&
+ namedArgument.Value.Value is bool value)
+ {
+ return value;
+ }
+ }
+
+ return defaultValue;
+ }
+
+ ///
+ /// 为每个枚举成员追加单值判断扩展方法。
+ ///
+ /// 目标源码构建器。
+ /// 需要生成扩展方法的枚举成员。
+ /// 枚举的完整类型名。
+ /// 当至少生成了一个方法时返回 。
+ private static bool AppendIsMethods(StringBuilder builder, IEnumerable members, string fullEnumName)
+ {
+ var hasGeneratedMembers = false;
+
+ foreach (var memberName in members.Select(m => m.Name))
+ {
+ if (hasGeneratedMembers)
+ {
+ builder.AppendLine();
+ }
+
+ builder.AppendLine($" /// 是否为 {memberName}");
+ builder.AppendLine(
+ $" public static bool Is{memberName}(this {fullEnumName} value) => value == {fullEnumName}.{memberName};");
+ hasGeneratedMembers = true;
+ }
+
+ return hasGeneratedMembers;
+ }
+
+ ///
+ /// 追加用于多值匹配的 IsIn 扩展方法。
+ ///
+ /// 目标源码构建器。
+ /// 枚举的完整类型名。
+ private static void AppendIsInMethod(StringBuilder builder, string fullEnumName)
+ {
+ builder.AppendLine(" /// 判断是否属于指定集合");
+ builder.AppendLine(
+ $" public static bool IsIn(this {fullEnumName} value, params {fullEnumName}[] values)");
+ builder.AppendLine(" {");
+ builder.AppendLine(" if (values == null) return false;");
+ builder.AppendLine(" foreach (var v in values) if (value == v) return true;");
+ builder.AppendLine(" return false;");
+ builder.AppendLine(" }");
+ }
}
diff --git a/GFramework.SourceGenerators.Tests/Enums/EnumExtensionsGeneratorSnapshotTests.cs b/GFramework.SourceGenerators.Tests/Enums/EnumExtensionsGeneratorSnapshotTests.cs
index e5c27226..ce109a36 100644
--- a/GFramework.SourceGenerators.Tests/Enums/EnumExtensionsGeneratorSnapshotTests.cs
+++ b/GFramework.SourceGenerators.Tests/Enums/EnumExtensionsGeneratorSnapshotTests.cs
@@ -4,11 +4,17 @@ using GFramework.SourceGenerators.Tests.Core;
namespace GFramework.SourceGenerators.Tests.Enums;
+///
+/// 验证枚举扩展生成器在不同属性开关组合下的快照输出。
+///
[TestFixture]
public class EnumExtensionsGeneratorSnapshotTests
{
private const string EnumAttributeNamespace = "GFramework.Core.SourceGenerators.Abstractions.Enums";
+ ///
+ /// 验证默认配置会为普通枚举生成逐项判断方法与集合判断方法。
+ ///
[Test]
public async Task Snapshot_BasicEnum_IsMethods()
{
@@ -24,14 +30,12 @@ public class EnumExtensionsGeneratorSnapshotTests
await GeneratorSnapshotTest.RunAsync(
source,
- Path.Combine(
- TestContext.CurrentContext.TestDirectory,
- "enums",
- "snapshots",
- "EnumExtensionsGenerator",
- "BasicEnum_IsMethods"));
+ GetSnapshotFolder("BasicEnum_IsMethods"));
}
+ ///
+ /// 验证默认配置在较小枚举上仍会生成集合判断方法。
+ ///
[Test]
public async Task Snapshot_BasicEnum_IsInMethod()
{
@@ -46,14 +50,12 @@ public class EnumExtensionsGeneratorSnapshotTests
await GeneratorSnapshotTest.RunAsync(
source,
- Path.Combine(
- TestContext.CurrentContext.TestDirectory,
- "enums",
- "snapshots",
- "EnumExtensionsGenerator",
- "BasicEnum_IsInMethod"));
+ GetSnapshotFolder("BasicEnum_IsInMethod"));
}
+ ///
+ /// 验证带显式位标志值的枚举也会生成对应扩展方法。
+ ///
[Test]
public async Task Snapshot_EnumWithFlagValues()
{
@@ -71,14 +73,12 @@ public class EnumExtensionsGeneratorSnapshotTests
await GeneratorSnapshotTest.RunAsync(
source,
- Path.Combine(
- TestContext.CurrentContext.TestDirectory,
- "enums",
- "snapshots",
- "EnumExtensionsGenerator",
- "EnumWithFlagValues"));
+ GetSnapshotFolder("EnumWithFlagValues"));
}
+ ///
+ /// 验证关闭逐项判断开关后仅保留集合判断方法。
+ ///
[Test]
public async Task Snapshot_DisableIsMethods()
{
@@ -94,14 +94,12 @@ public class EnumExtensionsGeneratorSnapshotTests
await GeneratorSnapshotTest.RunAsync(
source,
- Path.Combine(
- TestContext.CurrentContext.TestDirectory,
- "enums",
- "snapshots",
- "EnumExtensionsGenerator",
- "DisableIsMethods"));
+ GetSnapshotFolder("DisableIsMethods"));
}
+ ///
+ /// 验证关闭集合判断开关后仅保留逐项判断方法。
+ ///
[Test]
public async Task Snapshot_DisableIsInMethod()
{
@@ -117,16 +115,63 @@ public class EnumExtensionsGeneratorSnapshotTests
await GeneratorSnapshotTest.RunAsync(
source,
- Path.Combine(
- TestContext.CurrentContext.TestDirectory,
- "enums",
- "snapshots",
- "EnumExtensionsGenerator",
- "DisableIsInMethod"));
+ GetSnapshotFolder("DisableIsInMethod"));
}
+ ///
+ /// 验证同时关闭两个生成开关时不会输出任何扩展方法。
+ ///
+ [Test]
+ public async Task Snapshot_DisableAllGeneratedMethods()
+ {
+ var source = BuildSource(
+ """
+ public enum Status
+ {
+ Active,
+ Inactive
+ }
+ """,
+ "[GenerateEnumExtensions(GenerateIsMethods = false, GenerateIsInMethod = false)]");
+
+ await GeneratorSnapshotTest.RunAsync(
+ source,
+ GetSnapshotFolder("DisableAllGeneratedMethods"));
+ }
+
+ ///
+ /// 将运行时测试目录映射回仓库内已提交的枚举快照目录。
+ ///
+ /// 快照场景名称。
+ /// 场景对应的绝对快照目录。
+ private static string GetSnapshotFolder(string scenarioName)
+ {
+ return Path.GetFullPath(
+ Path.Combine(
+ TestContext.CurrentContext.TestDirectory,
+ "..",
+ "..",
+ "..",
+ "Enums",
+ "snapshots",
+ "EnumExtensionsGenerator",
+ scenarioName));
+ }
+
+ ///
+ /// 构造最小自洽的测试输入源码,以稳定驱动枚举扩展生成器的快照测试。
+ ///
+ /// 要注入到测试命名空间中的枚举声明文本。
+ /// 枚举上的属性使用方式,默认启用所有生成选项。
+ /// 包含内联测试属性与目标枚举声明的完整源码。
+ ///
+ /// 这里内联声明 GenerateEnumExtensionsAttribute,以便每个快照输入保持最小自洽。
+ /// 属性命名空间必须与生成器按 metadata name 查找的契约保持一致;如果命名空间、属性名或参数发生变更,
+ /// 需要同步更新该模板与相关快照,否则测试可能出现静默漂移。
+ ///
private static string BuildSource(string enumBody, string attributeUsage = "[GenerateEnumExtensions]")
{
+ // 保持属性声明与测试输入同处一个模板中,能够明确锁定生成器对元数据名称和可选参数的语义假设。
return $$"""
using System;
diff --git a/GFramework.SourceGenerators.Tests/Enums/snapshots/EnumExtensionsGenerator/BasicEnum_IsInMethod/Status.EnumExtensions.g.cs b/GFramework.SourceGenerators.Tests/Enums/snapshots/EnumExtensionsGenerator/BasicEnum_IsInMethod/Status.EnumExtensions.g.cs
new file mode 100644
index 00000000..2897d09c
--- /dev/null
+++ b/GFramework.SourceGenerators.Tests/Enums/snapshots/EnumExtensionsGenerator/BasicEnum_IsInMethod/Status.EnumExtensions.g.cs
@@ -0,0 +1,21 @@
+//
+using System;
+namespace TestApp
+{
+ public static partial class StatusExtensions
+ {
+ /// 是否为 Active
+ public static bool IsActive(this TestApp.Status value) => value == TestApp.Status.Active;
+
+ /// 是否为 Inactive
+ public static bool IsInactive(this TestApp.Status value) => value == TestApp.Status.Inactive;
+
+ /// 判断是否属于指定集合
+ public static bool IsIn(this TestApp.Status value, params TestApp.Status[] values)
+ {
+ if (values == null) return false;
+ foreach (var v in values) if (value == v) return true;
+ return false;
+ }
+ }
+}
diff --git a/GFramework.SourceGenerators.Tests/Enums/snapshots/EnumExtensionsGenerator/BasicEnum_IsMethods/Status.EnumExtensions.g.cs b/GFramework.SourceGenerators.Tests/Enums/snapshots/EnumExtensionsGenerator/BasicEnum_IsMethods/Status.EnumExtensions.g.cs
new file mode 100644
index 00000000..db9fa7ba
--- /dev/null
+++ b/GFramework.SourceGenerators.Tests/Enums/snapshots/EnumExtensionsGenerator/BasicEnum_IsMethods/Status.EnumExtensions.g.cs
@@ -0,0 +1,24 @@
+//
+using System;
+namespace TestApp
+{
+ public static partial class StatusExtensions
+ {
+ /// 是否为 Active
+ public static bool IsActive(this TestApp.Status value) => value == TestApp.Status.Active;
+
+ /// 是否为 Inactive
+ public static bool IsInactive(this TestApp.Status value) => value == TestApp.Status.Inactive;
+
+ /// 是否为 Pending
+ public static bool IsPending(this TestApp.Status value) => value == TestApp.Status.Pending;
+
+ /// 判断是否属于指定集合
+ public static bool IsIn(this TestApp.Status value, params TestApp.Status[] values)
+ {
+ if (values == null) return false;
+ foreach (var v in values) if (value == v) return true;
+ return false;
+ }
+ }
+}
diff --git a/GFramework.SourceGenerators.Tests/Enums/snapshots/EnumExtensionsGenerator/DisableAllGeneratedMethods/Status.EnumExtensions.g.cs b/GFramework.SourceGenerators.Tests/Enums/snapshots/EnumExtensionsGenerator/DisableAllGeneratedMethods/Status.EnumExtensions.g.cs
new file mode 100644
index 00000000..74b39b20
--- /dev/null
+++ b/GFramework.SourceGenerators.Tests/Enums/snapshots/EnumExtensionsGenerator/DisableAllGeneratedMethods/Status.EnumExtensions.g.cs
@@ -0,0 +1,8 @@
+//
+using System;
+namespace TestApp
+{
+ public static partial class StatusExtensions
+ {
+ }
+}
diff --git a/GFramework.SourceGenerators.Tests/Enums/snapshots/EnumExtensionsGenerator/DisableIsInMethod/Status.EnumExtensions.g.cs b/GFramework.SourceGenerators.Tests/Enums/snapshots/EnumExtensionsGenerator/DisableIsInMethod/Status.EnumExtensions.g.cs
new file mode 100644
index 00000000..3563db50
--- /dev/null
+++ b/GFramework.SourceGenerators.Tests/Enums/snapshots/EnumExtensionsGenerator/DisableIsInMethod/Status.EnumExtensions.g.cs
@@ -0,0 +1,13 @@
+//
+using System;
+namespace TestApp
+{
+ public static partial class StatusExtensions
+ {
+ /// 是否为 Active
+ public static bool IsActive(this TestApp.Status value) => value == TestApp.Status.Active;
+
+ /// 是否为 Inactive
+ public static bool IsInactive(this TestApp.Status value) => value == TestApp.Status.Inactive;
+ }
+}
diff --git a/GFramework.SourceGenerators.Tests/Enums/snapshots/EnumExtensionsGenerator/DisableIsMethods/Status.EnumExtensions.g.cs b/GFramework.SourceGenerators.Tests/Enums/snapshots/EnumExtensionsGenerator/DisableIsMethods/Status.EnumExtensions.g.cs
new file mode 100644
index 00000000..41154546
--- /dev/null
+++ b/GFramework.SourceGenerators.Tests/Enums/snapshots/EnumExtensionsGenerator/DisableIsMethods/Status.EnumExtensions.g.cs
@@ -0,0 +1,15 @@
+//
+using System;
+namespace TestApp
+{
+ public static partial class StatusExtensions
+ {
+ /// 判断是否属于指定集合
+ public static bool IsIn(this TestApp.Status value, params TestApp.Status[] values)
+ {
+ if (values == null) return false;
+ foreach (var v in values) if (value == v) return true;
+ return false;
+ }
+ }
+}
diff --git a/GFramework.SourceGenerators.Tests/Enums/snapshots/EnumExtensionsGenerator/EnumWithFlagValues/Permissions.EnumExtensions.g.cs b/GFramework.SourceGenerators.Tests/Enums/snapshots/EnumExtensionsGenerator/EnumWithFlagValues/Permissions.EnumExtensions.g.cs
new file mode 100644
index 00000000..ebd87525
--- /dev/null
+++ b/GFramework.SourceGenerators.Tests/Enums/snapshots/EnumExtensionsGenerator/EnumWithFlagValues/Permissions.EnumExtensions.g.cs
@@ -0,0 +1,27 @@
+//
+using System;
+namespace TestApp
+{
+ public static partial class PermissionsExtensions
+ {
+ /// 是否为 None
+ public static bool IsNone(this TestApp.Permissions value) => value == TestApp.Permissions.None;
+
+ /// 是否为 Read
+ public static bool IsRead(this TestApp.Permissions value) => value == TestApp.Permissions.Read;
+
+ /// 是否为 Write
+ public static bool IsWrite(this TestApp.Permissions value) => value == TestApp.Permissions.Write;
+
+ /// 是否为 Execute
+ public static bool IsExecute(this TestApp.Permissions value) => value == TestApp.Permissions.Execute;
+
+ /// 判断是否属于指定集合
+ public static bool IsIn(this TestApp.Permissions value, params TestApp.Permissions[] values)
+ {
+ if (values == null) return false;
+ foreach (var v in values) if (value == v) return true;
+ return false;
+ }
+ }
+}
diff --git a/GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj b/GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj
index 885865ab..5bd55cf2 100644
--- a/GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj
+++ b/GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj
@@ -29,6 +29,8 @@
+
+