From 2ae783c12784a1e665643fdaab2a423cf1f62e4d Mon Sep 17 00:00:00 2001
From: GeWuYou <95328647+GeWuYou@users.noreply.github.com>
Date: Mon, 30 Mar 2026 08:40:57 +0800
Subject: [PATCH 1/4] =?UTF-8?q?feat(generator):=20=E6=B7=BB=E5=8A=A0?=
=?UTF-8?q?=E5=AF=B9=20[GetAll]=20=E7=89=B9=E6=80=A7=E7=9A=84=E9=9D=99?=
=?UTF-8?q?=E6=80=81=E5=92=8C=E5=8F=AA=E8=AF=BB=E5=AD=97=E6=AE=B5=E8=B7=B3?=
=?UTF-8?q?=E8=BF=87=E6=94=AF=E6=8C=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 添加了新的诊断规则 GF_ContextGet_007 和 GF_ContextGet_08
- 实现了对静态字段和只读字段的跳过逻辑
- 为 [GetAll] 特性添加了跳过字段的警告提示
- 更新了测试用例验证跳过逻辑的正确性
- 修改了代码生成顺序以确保正确的绑定推断
- 在 README 中添加了关于字段跳过的文档说明
---
.../Constants/PathContests.cs | 6 +
.../Rule/ContextGetGeneratorTests.cs | 423 ++++++++++++++++++
.../AnalyzerReleases.Unshipped.md | 2 +
.../Diagnostics/ContextGetDiagnostics.cs | 38 +-
GFramework.SourceGenerators/GlobalUsings.cs | 5 +-
GFramework.SourceGenerators/README.md | 3 +
.../Rule/ContextGetGenerator.cs | 12 +-
7 files changed, 475 insertions(+), 14 deletions(-)
diff --git a/GFramework.SourceGenerators.Common/Constants/PathContests.cs b/GFramework.SourceGenerators.Common/Constants/PathContests.cs
index e5cd758..344b027 100644
--- a/GFramework.SourceGenerators.Common/Constants/PathContests.cs
+++ b/GFramework.SourceGenerators.Common/Constants/PathContests.cs
@@ -25,6 +25,12 @@ public static class PathContests
///
public const string GameNamespace = $"{BaseNamespace}.Game";
+ ///
+ /// GFramework源代码生成器抽象层命名空间
+ ///
+ public const string SourceGeneratorsPath = $"{BaseNamespace}.SourceGenerators";
+
+
///
/// GFramework源代码生成器抽象层命名空间
///
diff --git a/GFramework.SourceGenerators.Tests/Rule/ContextGetGeneratorTests.cs b/GFramework.SourceGenerators.Tests/Rule/ContextGetGeneratorTests.cs
index 0fc78a9..30d165a 100644
--- a/GFramework.SourceGenerators.Tests/Rule/ContextGetGeneratorTests.cs
+++ b/GFramework.SourceGenerators.Tests/Rule/ContextGetGeneratorTests.cs
@@ -377,6 +377,289 @@ public class ContextGetGeneratorTests
Assert.Pass();
}
+ [Test]
+ public async Task Ignores_NonInferable_Const_Field_For_GetAll_Class_Without_Diagnostic()
+ {
+ var source = """
+ using System;
+ using GFramework.SourceGenerators.Abstractions.Rule;
+
+ namespace GFramework.SourceGenerators.Abstractions.Rule
+ {
+ [AttributeUsage(AttributeTargets.Class, Inherited = false)]
+ public sealed class GetAllAttribute : Attribute { }
+ }
+
+ namespace GFramework.Core.Abstractions.Rule
+ {
+ public interface IContextAware { }
+ }
+
+ namespace GFramework.Core.Rule
+ {
+ public abstract class ContextAwareBase : GFramework.Core.Abstractions.Rule.IContextAware { }
+ }
+
+ namespace GFramework.Core.Abstractions.Model
+ {
+ public interface IModel { }
+ }
+
+ namespace GFramework.Core.Abstractions.Systems
+ {
+ public interface ISystem { }
+ }
+
+ namespace GFramework.Core.Abstractions.Utility
+ {
+ public interface IUtility { }
+ }
+
+ namespace GFramework.Core.Extensions
+ {
+ public static class ContextAwareServiceExtensions
+ {
+ public static T GetModel(this object contextAware) => default!;
+ }
+ }
+
+ namespace TestApp
+ {
+ public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
+
+ [GetAll]
+ public partial class BattlePanel : GFramework.Core.Rule.ContextAwareBase
+ {
+ private const double LogicStep = 0.2;
+ private IInventoryModel _model = null!;
+ }
+ }
+ """;
+
+ const string expected = """
+ //
+ #nullable enable
+
+ using GFramework.Core.Extensions;
+
+ namespace TestApp;
+
+ partial class BattlePanel
+ {
+ private void __InjectContextBindings_Generated()
+ {
+ _model = this.GetModel();
+ }
+ }
+
+ """;
+
+ await GeneratorTest.RunAsync(
+ source,
+ ("TestApp_BattlePanel.ContextGet.g.cs", expected));
+ Assert.Pass();
+ }
+
+ [Test]
+ public async Task Warns_And_Skips_Readonly_Inferred_Field_For_GetAll_Class()
+ {
+ var source = """
+ using System;
+ using GFramework.SourceGenerators.Abstractions.Rule;
+
+ namespace GFramework.SourceGenerators.Abstractions.Rule
+ {
+ [AttributeUsage(AttributeTargets.Class, Inherited = false)]
+ public sealed class GetAllAttribute : Attribute { }
+ }
+
+ namespace GFramework.Core.Abstractions.Rule
+ {
+ public interface IContextAware { }
+ }
+
+ namespace GFramework.Core.Rule
+ {
+ public abstract class ContextAwareBase : GFramework.Core.Abstractions.Rule.IContextAware { }
+ }
+
+ namespace GFramework.Core.Abstractions.Model
+ {
+ public interface IModel { }
+ }
+
+ namespace GFramework.Core.Abstractions.Systems
+ {
+ public interface ISystem { }
+ }
+
+ namespace GFramework.Core.Abstractions.Utility
+ {
+ public interface IUtility { }
+ }
+
+ namespace GFramework.Core.Extensions
+ {
+ public static class ContextAwareServiceExtensions
+ {
+ public static T GetModel(this object contextAware) => default!;
+ public static T GetSystem(this object contextAware) => default!;
+ }
+ }
+
+ namespace TestApp
+ {
+ public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
+ public interface ICombatSystem : GFramework.Core.Abstractions.Systems.ISystem { }
+
+ [GetAll]
+ public partial class BattlePanel : GFramework.Core.Rule.ContextAwareBase
+ {
+ private readonly IInventoryModel _model = null!;
+ private ICombatSystem _system = null!;
+ }
+ }
+ """;
+
+ const string expected = """
+ //
+ #nullable enable
+
+ using GFramework.Core.Extensions;
+
+ namespace TestApp;
+
+ partial class BattlePanel
+ {
+ private void __InjectContextBindings_Generated()
+ {
+ _system = this.GetSystem();
+ }
+ }
+
+ """;
+
+ var test = new CSharpSourceGeneratorTest
+ {
+ TestState =
+ {
+ Sources = { source },
+ GeneratedSources =
+ {
+ (typeof(ContextGetGenerator), "TestApp_BattlePanel.ContextGet.g.cs", expected)
+ }
+ },
+ DisabledDiagnostics = { "GF_Common_Trace_001" }
+ };
+
+ test.ExpectedDiagnostics.Add(new DiagnosticResult("GF_ContextGet_008", DiagnosticSeverity.Warning)
+ .WithSpan(52, 42, 52, 48)
+ .WithArguments("_model"));
+
+ await test.RunAsync();
+ Assert.Pass();
+ }
+
+ [Test]
+ public async Task Warns_And_Skips_Static_Inferred_Field_For_GetAll_Class()
+ {
+ var source = """
+ using System;
+ using GFramework.SourceGenerators.Abstractions.Rule;
+
+ namespace GFramework.SourceGenerators.Abstractions.Rule
+ {
+ [AttributeUsage(AttributeTargets.Class, Inherited = false)]
+ public sealed class GetAllAttribute : Attribute { }
+ }
+
+ namespace GFramework.Core.Abstractions.Rule
+ {
+ public interface IContextAware { }
+ }
+
+ namespace GFramework.Core.Rule
+ {
+ public abstract class ContextAwareBase : GFramework.Core.Abstractions.Rule.IContextAware { }
+ }
+
+ namespace GFramework.Core.Abstractions.Model
+ {
+ public interface IModel { }
+ }
+
+ namespace GFramework.Core.Abstractions.Systems
+ {
+ public interface ISystem { }
+ }
+
+ namespace GFramework.Core.Abstractions.Utility
+ {
+ public interface IUtility { }
+ }
+
+ namespace GFramework.Core.Extensions
+ {
+ public static class ContextAwareServiceExtensions
+ {
+ public static T GetModel(this object contextAware) => default!;
+ public static T GetSystem(this object contextAware) => default!;
+ }
+ }
+
+ namespace TestApp
+ {
+ public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
+ public interface ICombatSystem : GFramework.Core.Abstractions.Systems.ISystem { }
+
+ [GetAll]
+ public partial class BattlePanel : GFramework.Core.Rule.ContextAwareBase
+ {
+ private static IInventoryModel _model = null!;
+ private ICombatSystem _system = null!;
+ }
+ }
+ """;
+
+ const string expected = """
+ //
+ #nullable enable
+
+ using GFramework.Core.Extensions;
+
+ namespace TestApp;
+
+ partial class BattlePanel
+ {
+ private void __InjectContextBindings_Generated()
+ {
+ _system = this.GetSystem();
+ }
+ }
+
+ """;
+
+ var test = new CSharpSourceGeneratorTest
+ {
+ TestState =
+ {
+ Sources = { source },
+ GeneratedSources =
+ {
+ (typeof(ContextGetGenerator), "TestApp_BattlePanel.ContextGet.g.cs", expected)
+ }
+ },
+ DisabledDiagnostics = { "GF_Common_Trace_001" }
+ };
+
+ test.ExpectedDiagnostics.Add(new DiagnosticResult("GF_ContextGet_007", DiagnosticSeverity.Warning)
+ .WithSpan(52, 40, 52, 46)
+ .WithArguments("_model"));
+
+ await test.RunAsync();
+ Assert.Pass();
+ }
+
[Test]
public async Task Skips_Nullable_Service_Like_Field_For_ContextAware_GetAll_Class()
{
@@ -682,6 +965,146 @@ public class ContextGetGeneratorTests
Assert.Pass();
}
+ [Test]
+ public async Task Reports_Diagnostic_For_Readonly_Explicit_GetModel_Field()
+ {
+ var source = """
+ using System;
+ using GFramework.SourceGenerators.Abstractions.Rule;
+
+ namespace GFramework.SourceGenerators.Abstractions.Rule
+ {
+ [AttributeUsage(AttributeTargets.Field, Inherited = false)]
+ public sealed class GetModelAttribute : Attribute { }
+ }
+
+ namespace GFramework.Core.Abstractions.Rule
+ {
+ public interface IContextAware { }
+ }
+
+ namespace GFramework.Core.Abstractions.Model
+ {
+ public interface IModel { }
+ }
+
+ namespace GFramework.Core.Abstractions.Systems
+ {
+ public interface ISystem { }
+ }
+
+ namespace GFramework.Core.Abstractions.Utility
+ {
+ public interface IUtility { }
+ }
+
+ namespace GFramework.Core.Extensions
+ {
+ public static class ContextAwareServiceExtensions
+ {
+ public static T GetModel(this object contextAware) => default!;
+ }
+ }
+
+ namespace TestApp
+ {
+ public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
+
+ public partial class InventoryPanel : GFramework.Core.Abstractions.Rule.IContextAware
+ {
+ [GetModel]
+ private readonly IInventoryModel _model = null!;
+ }
+ }
+ """;
+
+ var test = new CSharpSourceGeneratorTest
+ {
+ TestState =
+ {
+ Sources = { source }
+ },
+ DisabledDiagnostics = { "GF_Common_Trace_001" }
+ };
+
+ test.ExpectedDiagnostics.Add(new DiagnosticResult("GF_ContextGet_003", DiagnosticSeverity.Error)
+ .WithSpan(45, 42, 45, 48)
+ .WithArguments("_model"));
+
+ await test.RunAsync();
+ Assert.Pass();
+ }
+
+ [Test]
+ public async Task Reports_Diagnostic_For_Static_Explicit_GetModel_Field()
+ {
+ var source = """
+ using System;
+ using GFramework.SourceGenerators.Abstractions.Rule;
+
+ namespace GFramework.SourceGenerators.Abstractions.Rule
+ {
+ [AttributeUsage(AttributeTargets.Field, Inherited = false)]
+ public sealed class GetModelAttribute : Attribute { }
+ }
+
+ namespace GFramework.Core.Abstractions.Rule
+ {
+ public interface IContextAware { }
+ }
+
+ namespace GFramework.Core.Abstractions.Model
+ {
+ public interface IModel { }
+ }
+
+ namespace GFramework.Core.Abstractions.Systems
+ {
+ public interface ISystem { }
+ }
+
+ namespace GFramework.Core.Abstractions.Utility
+ {
+ public interface IUtility { }
+ }
+
+ namespace GFramework.Core.Extensions
+ {
+ public static class ContextAwareServiceExtensions
+ {
+ public static T GetModel(this object contextAware) => default!;
+ }
+ }
+
+ namespace TestApp
+ {
+ public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
+
+ public partial class InventoryPanel : GFramework.Core.Abstractions.Rule.IContextAware
+ {
+ [GetModel]
+ private static IInventoryModel _model = null!;
+ }
+ }
+ """;
+
+ var test = new CSharpSourceGeneratorTest
+ {
+ TestState =
+ {
+ Sources = { source }
+ },
+ DisabledDiagnostics = { "GF_Common_Trace_001" }
+ };
+
+ test.ExpectedDiagnostics.Add(new DiagnosticResult("GF_ContextGet_002", DiagnosticSeverity.Error)
+ .WithSpan(45, 40, 45, 46)
+ .WithArguments("_model"));
+
+ await test.RunAsync();
+ Assert.Pass();
+ }
+
[Test]
public async Task Generates_Bindings_For_GetModels_Field_Assignable_From_IReadOnlyList()
{
diff --git a/GFramework.SourceGenerators/AnalyzerReleases.Unshipped.md b/GFramework.SourceGenerators/AnalyzerReleases.Unshipped.md
index 24f780f..8b25a37 100644
--- a/GFramework.SourceGenerators/AnalyzerReleases.Unshipped.md
+++ b/GFramework.SourceGenerators/AnalyzerReleases.Unshipped.md
@@ -13,6 +13,8 @@
GF_ContextGet_004 | GFramework.SourceGenerators.rule | Error | ContextGetDiagnostics
GF_ContextGet_005 | GFramework.SourceGenerators.rule | Error | ContextGetDiagnostics
GF_ContextGet_006 | GFramework.SourceGenerators.rule | Error | ContextGetDiagnostics
+ GF_ContextGet_007 | GFramework.SourceGenerators.rule | Warning | ContextGetDiagnostics
+ GF_ContextGet_008 | GFramework.SourceGenerators.rule | Warning | ContextGetDiagnostics
GF_Priority_001 | GFramework.Priority | Error | PriorityDiagnostic
GF_Priority_002 | GFramework.Priority | Warning | PriorityDiagnostic
GF_Priority_003 | GFramework.Priority | Error | PriorityDiagnostic
diff --git a/GFramework.SourceGenerators/Diagnostics/ContextGetDiagnostics.cs b/GFramework.SourceGenerators/Diagnostics/ContextGetDiagnostics.cs
index 6735ac5..1f18ac6 100644
--- a/GFramework.SourceGenerators/Diagnostics/ContextGetDiagnostics.cs
+++ b/GFramework.SourceGenerators/Diagnostics/ContextGetDiagnostics.cs
@@ -1,4 +1,4 @@
-using Microsoft.CodeAnalysis;
+using GFramework.SourceGenerators.Common.Constants;
namespace GFramework.SourceGenerators.Diagnostics;
@@ -7,6 +7,8 @@ namespace GFramework.SourceGenerators.Diagnostics;
///
public static class ContextGetDiagnostics
{
+ private const string SourceGeneratorsRuleCategory = $"{PathContests.SourceGeneratorsPath}.Rule";
+
///
/// 不支持在嵌套类中生成注入代码。
///
@@ -14,7 +16,7 @@ public static class ContextGetDiagnostics
"GF_ContextGet_001",
"Context Get injection does not support nested classes",
"Class '{0}' cannot use context Get injection inside a nested type",
- "GFramework.SourceGenerators.Rule",
+ SourceGeneratorsRuleCategory,
DiagnosticSeverity.Error,
true);
@@ -25,7 +27,7 @@ public static class ContextGetDiagnostics
"GF_ContextGet_002",
"Static field is not supported for context Get injection",
"Field '{0}' cannot be static when using generated context Get injection",
- "GFramework.SourceGenerators.Rule",
+ SourceGeneratorsRuleCategory,
DiagnosticSeverity.Error,
true);
@@ -36,10 +38,32 @@ public static class ContextGetDiagnostics
"GF_ContextGet_003",
"Readonly field is not supported for context Get injection",
"Field '{0}' cannot be readonly when using generated context Get injection",
- "GFramework.SourceGenerators.Rule",
+ SourceGeneratorsRuleCategory,
DiagnosticSeverity.Error,
true);
+ ///
+ /// 使用 [GetAll] 时,静态字段会被跳过且不会生成注入赋值。
+ ///
+ public static readonly DiagnosticDescriptor GetAllStaticFieldSkipped = new(
+ "GF_ContextGet_007",
+ "Static field will be skipped by [GetAll] context Get injection",
+ "Field '{0}' is static and will be skipped by [GetAll] context Get injection generation",
+ SourceGeneratorsRuleCategory,
+ DiagnosticSeverity.Warning,
+ true);
+
+ ///
+ /// 使用 [GetAll] 时,只读字段会被跳过且不会生成注入赋值。
+ ///
+ public static readonly DiagnosticDescriptor GetAllReadOnlyFieldSkipped = new(
+ "GF_ContextGet_008",
+ "Readonly field will be skipped by [GetAll] context Get injection",
+ "Field '{0}' is readonly and will be skipped by [GetAll] context Get injection generation",
+ SourceGeneratorsRuleCategory,
+ DiagnosticSeverity.Warning,
+ true);
+
///
/// 字段类型与注入特性不匹配。
///
@@ -47,7 +71,7 @@ public static class ContextGetDiagnostics
"GF_ContextGet_004",
"Field type is not valid for the selected context Get attribute",
"Field '{0}' type '{1}' is not valid for [{2}]",
- "GFramework.SourceGenerators.Rule",
+ SourceGeneratorsRuleCategory,
DiagnosticSeverity.Error,
true);
@@ -58,7 +82,7 @@ public static class ContextGetDiagnostics
"GF_ContextGet_005",
"Context-aware type is required",
"Class '{0}' must be context-aware to use generated context Get injection",
- "GFramework.SourceGenerators.Rule",
+ SourceGeneratorsRuleCategory,
DiagnosticSeverity.Error,
true);
@@ -69,7 +93,7 @@ public static class ContextGetDiagnostics
"GF_ContextGet_006",
"Multiple context Get attributes are not supported on the same field",
"Field '{0}' cannot declare multiple generated context Get attributes",
- "GFramework.SourceGenerators.Rule",
+ SourceGeneratorsRuleCategory,
DiagnosticSeverity.Error,
true);
}
\ No newline at end of file
diff --git a/GFramework.SourceGenerators/GlobalUsings.cs b/GFramework.SourceGenerators/GlobalUsings.cs
index 32c7b84..a23ec42 100644
--- a/GFramework.SourceGenerators/GlobalUsings.cs
+++ b/GFramework.SourceGenerators/GlobalUsings.cs
@@ -16,5 +16,8 @@ global using System.Collections.Generic;
global using System.Linq;
global using System.Threading;
global using System.Threading.Tasks;
+global using System.Collections.Immutable;
global using Microsoft.CodeAnalysis;
-global using Microsoft.CodeAnalysis.CSharp.Syntax;
\ No newline at end of file
+global using Microsoft.CodeAnalysis.CSharp.Syntax;
+global using Microsoft.CodeAnalysis.CSharp;
+global using Microsoft.CodeAnalysis.Text;
\ No newline at end of file
diff --git a/GFramework.SourceGenerators/README.md b/GFramework.SourceGenerators/README.md
index 94ace29..6578ddb 100644
--- a/GFramework.SourceGenerators/README.md
+++ b/GFramework.SourceGenerators/README.md
@@ -47,3 +47,6 @@ public partial class InventoryPanel
`Service` 和 `Services` 绑定不会在 `[GetAll]` 下自动推断。对于普通引用类型字段,请显式使用 `[GetService]` 或
`[GetServices]`,避免将非上下文服务字段误判为服务依赖。
+
+`[GetAll]` 会跳过 `const`、`static` 和 `readonly` 字段。若某个字段本来会被 `[GetAll]` 推断为
+`Model`、`System` 或 `Utility` 绑定,但因为字段不可赋值而被跳过,生成器会发出警告提示该字段不会参与生成。
diff --git a/GFramework.SourceGenerators/Rule/ContextGetGenerator.cs b/GFramework.SourceGenerators/Rule/ContextGetGenerator.cs
index 4180f38..6d47253 100644
--- a/GFramework.SourceGenerators/Rule/ContextGetGenerator.cs
+++ b/GFramework.SourceGenerators/Rule/ContextGetGenerator.cs
@@ -1,4 +1,3 @@
-using System.Collections.Immutable;
using System.Text;
using GFramework.SourceGenerators.Common.Constants;
using GFramework.SourceGenerators.Common.Diagnostics;
@@ -299,23 +298,24 @@ public sealed class ContextGetGenerator : IIncrementalGenerator
if (explicitFields.Contains(field))
continue;
- if (!CanInferBinding(context, field))
+ // Infer the target first so [GetAll] only warns for fields it would otherwise bind.
+ if (!TryCreateInferredBinding(field, symbols, out var binding))
continue;
- if (!TryCreateInferredBinding(field, symbols, out var binding))
+ if (!CanApplyInferredBinding(context, field))
continue;
bindings.Add(binding);
}
}
- private static bool CanInferBinding(SourceProductionContext context, IFieldSymbol field)
+ private static bool CanApplyInferredBinding(SourceProductionContext context, IFieldSymbol field)
{
if (field.IsStatic)
{
ReportFieldDiagnostic(
context,
- ContextGetDiagnostics.StaticFieldNotSupported,
+ ContextGetDiagnostics.GetAllStaticFieldSkipped,
field);
return false;
}
@@ -325,7 +325,7 @@ public sealed class ContextGetGenerator : IIncrementalGenerator
ReportFieldDiagnostic(
context,
- ContextGetDiagnostics.ReadOnlyFieldNotSupported,
+ ContextGetDiagnostics.GetAllReadOnlyFieldSkipped,
field);
return false;
}
From 2ae16b22ebe4024c9d02d3fbbea020de1a411023 Mon Sep 17 00:00:00 2001
From: GeWuYou <95328647+GeWuYou@users.noreply.github.com>
Date: Mon, 30 Mar 2026 08:44:36 +0800
Subject: [PATCH 2/4] =?UTF-8?q?docs(constants):=20=E6=9B=B4=E6=96=B0?=
=?UTF-8?q?=E6=BA=90=E4=BB=A3=E7=A0=81=E7=94=9F=E6=88=90=E5=99=A8=E5=91=BD?=
=?UTF-8?q?=E5=90=8D=E7=A9=BA=E9=97=B4=E6=B3=A8=E9=87=8A?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 将GFramework源代码生成器抽象层命名空间注释更正为GFramework源代码生成器根命名空间
---
GFramework.SourceGenerators.Common/Constants/PathContests.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/GFramework.SourceGenerators.Common/Constants/PathContests.cs b/GFramework.SourceGenerators.Common/Constants/PathContests.cs
index 344b027..3facd8b 100644
--- a/GFramework.SourceGenerators.Common/Constants/PathContests.cs
+++ b/GFramework.SourceGenerators.Common/Constants/PathContests.cs
@@ -26,7 +26,7 @@ public static class PathContests
public const string GameNamespace = $"{BaseNamespace}.Game";
///
- /// GFramework源代码生成器抽象层命名空间
+ /// GFramework源代码生成器根命名空间
///
public const string SourceGeneratorsPath = $"{BaseNamespace}.SourceGenerators";
From da34b2fa2abd777022db59174abbdbbab915680f Mon Sep 17 00:00:00 2001
From: GeWuYou <95328647+GeWuYou@users.noreply.github.com>
Date: Mon, 30 Mar 2026 09:13:45 +0800
Subject: [PATCH 3/4] =?UTF-8?q?feat(generator):=20=E6=B7=BB=E5=8A=A0?=
=?UTF-8?q?=E5=AF=B9=20const=20=E5=AD=97=E6=AE=B5=E7=9A=84=E6=98=BE?=
=?UTF-8?q?=E5=BC=8F=E8=B7=B3=E8=BF=87=E6=94=AF=E6=8C=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 在 ContextGetGenerator 中添加对 const 字段的显式检查和跳过逻辑
- 更新文档说明 const、static 和 readonly 字段的处理方式
- 重构测试代码使用 MarkupTestSource 解析器进行更精确的诊断测试
- 添加新的 MarkupTestSource 类用于源码标记解析和诊断定位
---
.../Core/MarkupTestSource.cs | 120 ++++
.../Rule/ContextGetGeneratorTests.cs | 556 +++++++++---------
GFramework.SourceGenerators/README.md | 2 +-
.../Rule/ContextGetGenerator.cs | 7 +-
4 files changed, 407 insertions(+), 278 deletions(-)
create mode 100644 GFramework.SourceGenerators.Tests/Core/MarkupTestSource.cs
diff --git a/GFramework.SourceGenerators.Tests/Core/MarkupTestSource.cs b/GFramework.SourceGenerators.Tests/Core/MarkupTestSource.cs
new file mode 100644
index 0000000..8e97991
--- /dev/null
+++ b/GFramework.SourceGenerators.Tests/Core/MarkupTestSource.cs
@@ -0,0 +1,120 @@
+using System.Text;
+
+namespace GFramework.SourceGenerators.Tests.Core;
+
+///
+/// 为源生成器测试提供轻量的源码标记解析能力。
+///
+public sealed class MarkupTestSource
+{
+ private readonly SourceText _sourceText;
+ private readonly IReadOnlyDictionary _spans;
+
+ private MarkupTestSource(
+ string source,
+ SourceText sourceText,
+ IReadOnlyDictionary spans)
+ {
+ Source = source;
+ _sourceText = sourceText;
+ _spans = spans;
+ }
+
+ ///
+ /// 获取移除标记后的源码文本。
+ ///
+ public string Source { get; }
+
+ ///
+ /// 解析形如 {|#0:identifier|} 的单层标记,并保留去标记后的源码。
+ ///
+ /// 包含测试标记的源码。
+ /// 可用于测试输入和诊断定位的解析结果。
+ /// 标记格式不合法,或存在重复标记编号时抛出。
+ public static MarkupTestSource Parse(string markupSource)
+ {
+ var builder = new StringBuilder(markupSource.Length);
+ var spans = new Dictionary(StringComparer.Ordinal);
+
+ for (var index = 0; index < markupSource.Length; index++)
+ {
+ if (!StartsWithMarker(markupSource, index))
+ {
+ builder.Append(markupSource[index]);
+ continue;
+ }
+
+ index += 3;
+ var markerIdStart = index;
+ while (index < markupSource.Length && markupSource[index] != ':')
+ index++;
+
+ if (index >= markupSource.Length)
+ throw new InvalidOperationException("Unterminated markup marker identifier.");
+
+ var markerId = markupSource.Substring(markerIdStart, index - markerIdStart);
+ if (markerId.Length == 0)
+ throw new InvalidOperationException("Markup marker identifier cannot be empty.");
+
+ var spanStart = builder.Length;
+ index++;
+
+ while (index < markupSource.Length && !EndsWithMarker(markupSource, index))
+ {
+ builder.Append(markupSource[index]);
+ index++;
+ }
+
+ if (index >= markupSource.Length)
+ throw new InvalidOperationException($"Unterminated markup marker '{markerId}'.");
+
+ if (!spans.TryAdd(markerId, TextSpan.FromBounds(spanStart, builder.Length)))
+ throw new InvalidOperationException($"Duplicate markup marker '{markerId}'.");
+
+ index++;
+ }
+
+ var source = builder.ToString();
+ return new MarkupTestSource(source, SourceText.From(source), spans);
+ }
+
+ ///
+ /// 将标记位置应用到诊断断言,避免测试依赖硬编码行列号。
+ ///
+ /// 要补全定位信息的诊断断言。
+ /// 标记编号。
+ /// 包含定位信息的诊断断言。
+ /// 指定标记不存在时抛出。
+ public DiagnosticResult WithSpan(
+ DiagnosticResult diagnosticResult,
+ string markerId)
+ {
+ var span = _spans[markerId];
+ var lineSpan = _sourceText.Lines.GetLinePositionSpan(span);
+
+ return diagnosticResult.WithSpan(
+ lineSpan.Start.Line + 1,
+ lineSpan.Start.Character + 1,
+ lineSpan.End.Line + 1,
+ lineSpan.End.Character + 1);
+ }
+
+ private static bool StartsWithMarker(
+ string text,
+ int index)
+ {
+ return index + 3 < text.Length &&
+ text[index] == '{' &&
+ text[index + 1] == '|' &&
+ text[index + 2] == '#';
+ }
+
+ private static bool EndsWithMarker(
+ string text,
+ int index)
+ {
+ return index + 1 < text.Length &&
+ text[index] == '|' &&
+ text[index + 1] == '}';
+ }
+}
\ No newline at end of file
diff --git a/GFramework.SourceGenerators.Tests/Rule/ContextGetGeneratorTests.cs b/GFramework.SourceGenerators.Tests/Rule/ContextGetGeneratorTests.cs
index 30d165a..1bea458 100644
--- a/GFramework.SourceGenerators.Tests/Rule/ContextGetGeneratorTests.cs
+++ b/GFramework.SourceGenerators.Tests/Rule/ContextGetGeneratorTests.cs
@@ -463,63 +463,63 @@ public class ContextGetGeneratorTests
[Test]
public async Task Warns_And_Skips_Readonly_Inferred_Field_For_GetAll_Class()
{
- var source = """
- using System;
- using GFramework.SourceGenerators.Abstractions.Rule;
+ var source = MarkupTestSource.Parse("""
+ using System;
+ using GFramework.SourceGenerators.Abstractions.Rule;
- namespace GFramework.SourceGenerators.Abstractions.Rule
- {
- [AttributeUsage(AttributeTargets.Class, Inherited = false)]
- public sealed class GetAllAttribute : Attribute { }
- }
+ namespace GFramework.SourceGenerators.Abstractions.Rule
+ {
+ [AttributeUsage(AttributeTargets.Class, Inherited = false)]
+ public sealed class GetAllAttribute : Attribute { }
+ }
- namespace GFramework.Core.Abstractions.Rule
- {
- public interface IContextAware { }
- }
+ namespace GFramework.Core.Abstractions.Rule
+ {
+ public interface IContextAware { }
+ }
- namespace GFramework.Core.Rule
- {
- public abstract class ContextAwareBase : GFramework.Core.Abstractions.Rule.IContextAware { }
- }
+ namespace GFramework.Core.Rule
+ {
+ public abstract class ContextAwareBase : GFramework.Core.Abstractions.Rule.IContextAware { }
+ }
- namespace GFramework.Core.Abstractions.Model
- {
- public interface IModel { }
- }
+ namespace GFramework.Core.Abstractions.Model
+ {
+ public interface IModel { }
+ }
- namespace GFramework.Core.Abstractions.Systems
- {
- public interface ISystem { }
- }
+ namespace GFramework.Core.Abstractions.Systems
+ {
+ public interface ISystem { }
+ }
- namespace GFramework.Core.Abstractions.Utility
- {
- public interface IUtility { }
- }
+ namespace GFramework.Core.Abstractions.Utility
+ {
+ public interface IUtility { }
+ }
- namespace GFramework.Core.Extensions
- {
- public static class ContextAwareServiceExtensions
- {
- public static T GetModel(this object contextAware) => default!;
- public static T GetSystem(this object contextAware) => default!;
- }
- }
+ namespace GFramework.Core.Extensions
+ {
+ public static class ContextAwareServiceExtensions
+ {
+ public static T GetModel(this object contextAware) => default!;
+ public static T GetSystem(this object contextAware) => default!;
+ }
+ }
- namespace TestApp
- {
- public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
- public interface ICombatSystem : GFramework.Core.Abstractions.Systems.ISystem { }
+ namespace TestApp
+ {
+ public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
+ public interface ICombatSystem : GFramework.Core.Abstractions.Systems.ISystem { }
- [GetAll]
- public partial class BattlePanel : GFramework.Core.Rule.ContextAwareBase
- {
- private readonly IInventoryModel _model = null!;
- private ICombatSystem _system = null!;
- }
- }
- """;
+ [GetAll]
+ public partial class BattlePanel : GFramework.Core.Rule.ContextAwareBase
+ {
+ private readonly IInventoryModel {|#0:_model|} = null!;
+ private ICombatSystem _system = null!;
+ }
+ }
+ """);
const string expected = """
//
@@ -543,7 +543,7 @@ public class ContextGetGeneratorTests
{
TestState =
{
- Sources = { source },
+ Sources = { source.Source },
GeneratedSources =
{
(typeof(ContextGetGenerator), "TestApp_BattlePanel.ContextGet.g.cs", expected)
@@ -552,8 +552,9 @@ public class ContextGetGeneratorTests
DisabledDiagnostics = { "GF_Common_Trace_001" }
};
- test.ExpectedDiagnostics.Add(new DiagnosticResult("GF_ContextGet_008", DiagnosticSeverity.Warning)
- .WithSpan(52, 42, 52, 48)
+ test.ExpectedDiagnostics.Add(source.WithSpan(
+ new DiagnosticResult("GF_ContextGet_008", DiagnosticSeverity.Warning),
+ "0")
.WithArguments("_model"));
await test.RunAsync();
@@ -563,63 +564,63 @@ public class ContextGetGeneratorTests
[Test]
public async Task Warns_And_Skips_Static_Inferred_Field_For_GetAll_Class()
{
- var source = """
- using System;
- using GFramework.SourceGenerators.Abstractions.Rule;
+ var source = MarkupTestSource.Parse("""
+ using System;
+ using GFramework.SourceGenerators.Abstractions.Rule;
- namespace GFramework.SourceGenerators.Abstractions.Rule
- {
- [AttributeUsage(AttributeTargets.Class, Inherited = false)]
- public sealed class GetAllAttribute : Attribute { }
- }
+ namespace GFramework.SourceGenerators.Abstractions.Rule
+ {
+ [AttributeUsage(AttributeTargets.Class, Inherited = false)]
+ public sealed class GetAllAttribute : Attribute { }
+ }
- namespace GFramework.Core.Abstractions.Rule
- {
- public interface IContextAware { }
- }
+ namespace GFramework.Core.Abstractions.Rule
+ {
+ public interface IContextAware { }
+ }
- namespace GFramework.Core.Rule
- {
- public abstract class ContextAwareBase : GFramework.Core.Abstractions.Rule.IContextAware { }
- }
+ namespace GFramework.Core.Rule
+ {
+ public abstract class ContextAwareBase : GFramework.Core.Abstractions.Rule.IContextAware { }
+ }
- namespace GFramework.Core.Abstractions.Model
- {
- public interface IModel { }
- }
+ namespace GFramework.Core.Abstractions.Model
+ {
+ public interface IModel { }
+ }
- namespace GFramework.Core.Abstractions.Systems
- {
- public interface ISystem { }
- }
+ namespace GFramework.Core.Abstractions.Systems
+ {
+ public interface ISystem { }
+ }
- namespace GFramework.Core.Abstractions.Utility
- {
- public interface IUtility { }
- }
+ namespace GFramework.Core.Abstractions.Utility
+ {
+ public interface IUtility { }
+ }
- namespace GFramework.Core.Extensions
- {
- public static class ContextAwareServiceExtensions
- {
- public static T GetModel(this object contextAware) => default!;
- public static T GetSystem(this object contextAware) => default!;
- }
- }
+ namespace GFramework.Core.Extensions
+ {
+ public static class ContextAwareServiceExtensions
+ {
+ public static T GetModel(this object contextAware) => default!;
+ public static T GetSystem(this object contextAware) => default!;
+ }
+ }
- namespace TestApp
- {
- public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
- public interface ICombatSystem : GFramework.Core.Abstractions.Systems.ISystem { }
+ namespace TestApp
+ {
+ public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
+ public interface ICombatSystem : GFramework.Core.Abstractions.Systems.ISystem { }
- [GetAll]
- public partial class BattlePanel : GFramework.Core.Rule.ContextAwareBase
- {
- private static IInventoryModel _model = null!;
- private ICombatSystem _system = null!;
- }
- }
- """;
+ [GetAll]
+ public partial class BattlePanel : GFramework.Core.Rule.ContextAwareBase
+ {
+ private static IInventoryModel {|#0:_model|} = null!;
+ private ICombatSystem _system = null!;
+ }
+ }
+ """);
const string expected = """
//
@@ -643,7 +644,7 @@ public class ContextGetGeneratorTests
{
TestState =
{
- Sources = { source },
+ Sources = { source.Source },
GeneratedSources =
{
(typeof(ContextGetGenerator), "TestApp_BattlePanel.ContextGet.g.cs", expected)
@@ -652,8 +653,9 @@ public class ContextGetGeneratorTests
DisabledDiagnostics = { "GF_Common_Trace_001" }
};
- test.ExpectedDiagnostics.Add(new DiagnosticResult("GF_ContextGet_007", DiagnosticSeverity.Warning)
- .WithSpan(52, 40, 52, 46)
+ test.ExpectedDiagnostics.Add(source.WithSpan(
+ new DiagnosticResult("GF_ContextGet_007", DiagnosticSeverity.Warning),
+ "0")
.WithArguments("_model"));
await test.RunAsync();
@@ -832,62 +834,63 @@ public class ContextGetGeneratorTests
[Test]
public async Task Reports_Diagnostic_When_Class_Is_Not_ContextAware()
{
- var source = """
- using System;
- using GFramework.SourceGenerators.Abstractions.Rule;
+ var source = MarkupTestSource.Parse("""
+ using System;
+ using GFramework.SourceGenerators.Abstractions.Rule;
- namespace GFramework.SourceGenerators.Abstractions.Rule
- {
- [AttributeUsage(AttributeTargets.Field, Inherited = false)]
- public sealed class GetModelAttribute : Attribute { }
- }
+ namespace GFramework.SourceGenerators.Abstractions.Rule
+ {
+ [AttributeUsage(AttributeTargets.Field, Inherited = false)]
+ public sealed class GetModelAttribute : Attribute { }
+ }
- namespace GFramework.Core.Abstractions.Model
- {
- public interface IModel { }
- }
+ namespace GFramework.Core.Abstractions.Model
+ {
+ public interface IModel { }
+ }
- namespace GFramework.Core.Abstractions.Systems
- {
- public interface ISystem { }
- }
+ namespace GFramework.Core.Abstractions.Systems
+ {
+ public interface ISystem { }
+ }
- namespace GFramework.Core.Abstractions.Utility
- {
- public interface IUtility { }
- }
+ namespace GFramework.Core.Abstractions.Utility
+ {
+ public interface IUtility { }
+ }
- namespace GFramework.Core.Extensions
- {
- public static class ContextAwareServiceExtensions
- {
- public static T GetModel(this object contextAware) => default!;
- }
- }
+ namespace GFramework.Core.Extensions
+ {
+ public static class ContextAwareServiceExtensions
+ {
+ public static T GetModel(this object contextAware) => default!;
+ }
+ }
- namespace TestApp
- {
- public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
+ namespace TestApp
+ {
+ public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
- public partial class InventoryPanel
- {
- [GetModel]
- private IInventoryModel _model = null!;
- }
- }
- """;
+ public partial class InventoryPanel
+ {
+ [GetModel]
+ private IInventoryModel {|#0:_model|} = null!;
+ }
+ }
+ """);
var test = new CSharpSourceGeneratorTest
{
TestState =
{
- Sources = { source }
+ Sources = { source.Source }
},
DisabledDiagnostics = { "GF_Common_Trace_001" }
};
- test.ExpectedDiagnostics.Add(new DiagnosticResult("GF_ContextGet_005", DiagnosticSeverity.Error)
- .WithSpan(40, 33, 40, 39)
+ test.ExpectedDiagnostics.Add(source.WithSpan(
+ new DiagnosticResult("GF_ContextGet_005", DiagnosticSeverity.Error),
+ "0")
.WithArguments("InventoryPanel"));
await test.RunAsync();
@@ -897,68 +900,69 @@ public class ContextGetGeneratorTests
[Test]
public async Task Reports_Diagnostic_When_GetModels_Field_Is_Not_IReadOnlyList()
{
- var source = """
- using System;
- using System.Collections.Generic;
- using GFramework.SourceGenerators.Abstractions.Rule;
+ var source = MarkupTestSource.Parse("""
+ using System;
+ using System.Collections.Generic;
+ using GFramework.SourceGenerators.Abstractions.Rule;
- namespace GFramework.SourceGenerators.Abstractions.Rule
- {
- [AttributeUsage(AttributeTargets.Field, Inherited = false)]
- public sealed class GetModelsAttribute : Attribute { }
- }
+ namespace GFramework.SourceGenerators.Abstractions.Rule
+ {
+ [AttributeUsage(AttributeTargets.Field, Inherited = false)]
+ public sealed class GetModelsAttribute : Attribute { }
+ }
- namespace GFramework.Core.Abstractions.Rule
- {
- public interface IContextAware { }
- }
+ namespace GFramework.Core.Abstractions.Rule
+ {
+ public interface IContextAware { }
+ }
- namespace GFramework.Core.Abstractions.Model
- {
- public interface IModel { }
- }
+ namespace GFramework.Core.Abstractions.Model
+ {
+ public interface IModel { }
+ }
- namespace GFramework.Core.Abstractions.Systems
- {
- public interface ISystem { }
- }
+ namespace GFramework.Core.Abstractions.Systems
+ {
+ public interface ISystem { }
+ }
- namespace GFramework.Core.Abstractions.Utility
- {
- public interface IUtility { }
- }
+ namespace GFramework.Core.Abstractions.Utility
+ {
+ public interface IUtility { }
+ }
- namespace GFramework.Core.Extensions
- {
- public static class ContextAwareServiceExtensions
- {
- public static IReadOnlyList GetModels(this object contextAware) => default!;
- }
- }
+ namespace GFramework.Core.Extensions
+ {
+ public static class ContextAwareServiceExtensions
+ {
+ public static IReadOnlyList GetModels(this object contextAware) => default!;
+ }
+ }
- namespace TestApp
- {
- public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
+ namespace TestApp
+ {
+ public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
- public partial class InventoryPanel : GFramework.Core.Abstractions.Rule.IContextAware
- {
- [GetModels]
- private List _models = new();
- }
- }
- """;
+ public partial class InventoryPanel : GFramework.Core.Abstractions.Rule.IContextAware
+ {
+ [GetModels]
+ private List {|#0:_models|} = new();
+ }
+ }
+ """);
var test = new CSharpSourceGeneratorTest
{
TestState =
{
- Sources = { source }
+ Sources = { source.Source }
},
DisabledDiagnostics = { "GF_Common_Trace_001" }
};
- test.ExpectedDiagnostics.Add(new DiagnosticResult("GF_ContextGet_004", DiagnosticSeverity.Error)
- .WithSpan(46, 39, 46, 46)
+ test.ExpectedDiagnostics.Add(source.WithSpan(
+ new DiagnosticResult("GF_ContextGet_004", DiagnosticSeverity.Error),
+ "0")
.WithArguments("_models", "System.Collections.Generic.List", "GetModels"));
await test.RunAsync();
@@ -968,67 +972,68 @@ public class ContextGetGeneratorTests
[Test]
public async Task Reports_Diagnostic_For_Readonly_Explicit_GetModel_Field()
{
- var source = """
- using System;
- using GFramework.SourceGenerators.Abstractions.Rule;
+ var source = MarkupTestSource.Parse("""
+ using System;
+ using GFramework.SourceGenerators.Abstractions.Rule;
- namespace GFramework.SourceGenerators.Abstractions.Rule
- {
- [AttributeUsage(AttributeTargets.Field, Inherited = false)]
- public sealed class GetModelAttribute : Attribute { }
- }
+ namespace GFramework.SourceGenerators.Abstractions.Rule
+ {
+ [AttributeUsage(AttributeTargets.Field, Inherited = false)]
+ public sealed class GetModelAttribute : Attribute { }
+ }
- namespace GFramework.Core.Abstractions.Rule
- {
- public interface IContextAware { }
- }
+ namespace GFramework.Core.Abstractions.Rule
+ {
+ public interface IContextAware { }
+ }
- namespace GFramework.Core.Abstractions.Model
- {
- public interface IModel { }
- }
+ namespace GFramework.Core.Abstractions.Model
+ {
+ public interface IModel { }
+ }
- namespace GFramework.Core.Abstractions.Systems
- {
- public interface ISystem { }
- }
+ namespace GFramework.Core.Abstractions.Systems
+ {
+ public interface ISystem { }
+ }
- namespace GFramework.Core.Abstractions.Utility
- {
- public interface IUtility { }
- }
+ namespace GFramework.Core.Abstractions.Utility
+ {
+ public interface IUtility { }
+ }
- namespace GFramework.Core.Extensions
- {
- public static class ContextAwareServiceExtensions
- {
- public static T GetModel(this object contextAware) => default!;
- }
- }
+ namespace GFramework.Core.Extensions
+ {
+ public static class ContextAwareServiceExtensions
+ {
+ public static T GetModel(this object contextAware) => default!;
+ }
+ }
- namespace TestApp
- {
- public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
+ namespace TestApp
+ {
+ public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
- public partial class InventoryPanel : GFramework.Core.Abstractions.Rule.IContextAware
- {
- [GetModel]
- private readonly IInventoryModel _model = null!;
- }
- }
- """;
+ public partial class InventoryPanel : GFramework.Core.Abstractions.Rule.IContextAware
+ {
+ [GetModel]
+ private readonly IInventoryModel {|#0:_model|} = null!;
+ }
+ }
+ """);
var test = new CSharpSourceGeneratorTest
{
TestState =
{
- Sources = { source }
+ Sources = { source.Source }
},
DisabledDiagnostics = { "GF_Common_Trace_001" }
};
- test.ExpectedDiagnostics.Add(new DiagnosticResult("GF_ContextGet_003", DiagnosticSeverity.Error)
- .WithSpan(45, 42, 45, 48)
+ test.ExpectedDiagnostics.Add(source.WithSpan(
+ new DiagnosticResult("GF_ContextGet_003", DiagnosticSeverity.Error),
+ "0")
.WithArguments("_model"));
await test.RunAsync();
@@ -1038,67 +1043,68 @@ public class ContextGetGeneratorTests
[Test]
public async Task Reports_Diagnostic_For_Static_Explicit_GetModel_Field()
{
- var source = """
- using System;
- using GFramework.SourceGenerators.Abstractions.Rule;
+ var source = MarkupTestSource.Parse("""
+ using System;
+ using GFramework.SourceGenerators.Abstractions.Rule;
- namespace GFramework.SourceGenerators.Abstractions.Rule
- {
- [AttributeUsage(AttributeTargets.Field, Inherited = false)]
- public sealed class GetModelAttribute : Attribute { }
- }
+ namespace GFramework.SourceGenerators.Abstractions.Rule
+ {
+ [AttributeUsage(AttributeTargets.Field, Inherited = false)]
+ public sealed class GetModelAttribute : Attribute { }
+ }
- namespace GFramework.Core.Abstractions.Rule
- {
- public interface IContextAware { }
- }
+ namespace GFramework.Core.Abstractions.Rule
+ {
+ public interface IContextAware { }
+ }
- namespace GFramework.Core.Abstractions.Model
- {
- public interface IModel { }
- }
+ namespace GFramework.Core.Abstractions.Model
+ {
+ public interface IModel { }
+ }
- namespace GFramework.Core.Abstractions.Systems
- {
- public interface ISystem { }
- }
+ namespace GFramework.Core.Abstractions.Systems
+ {
+ public interface ISystem { }
+ }
- namespace GFramework.Core.Abstractions.Utility
- {
- public interface IUtility { }
- }
+ namespace GFramework.Core.Abstractions.Utility
+ {
+ public interface IUtility { }
+ }
- namespace GFramework.Core.Extensions
- {
- public static class ContextAwareServiceExtensions
- {
- public static T GetModel(this object contextAware) => default!;
- }
- }
+ namespace GFramework.Core.Extensions
+ {
+ public static class ContextAwareServiceExtensions
+ {
+ public static T GetModel(this object contextAware) => default!;
+ }
+ }
- namespace TestApp
- {
- public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
+ namespace TestApp
+ {
+ public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
- public partial class InventoryPanel : GFramework.Core.Abstractions.Rule.IContextAware
- {
- [GetModel]
- private static IInventoryModel _model = null!;
- }
- }
- """;
+ public partial class InventoryPanel : GFramework.Core.Abstractions.Rule.IContextAware
+ {
+ [GetModel]
+ private static IInventoryModel {|#0:_model|} = null!;
+ }
+ }
+ """);
var test = new CSharpSourceGeneratorTest
{
TestState =
{
- Sources = { source }
+ Sources = { source.Source }
},
DisabledDiagnostics = { "GF_Common_Trace_001" }
};
- test.ExpectedDiagnostics.Add(new DiagnosticResult("GF_ContextGet_002", DiagnosticSeverity.Error)
- .WithSpan(45, 40, 45, 46)
+ test.ExpectedDiagnostics.Add(source.WithSpan(
+ new DiagnosticResult("GF_ContextGet_002", DiagnosticSeverity.Error),
+ "0")
.WithArguments("_model"));
await test.RunAsync();
diff --git a/GFramework.SourceGenerators/README.md b/GFramework.SourceGenerators/README.md
index 6578ddb..7ed6db8 100644
--- a/GFramework.SourceGenerators/README.md
+++ b/GFramework.SourceGenerators/README.md
@@ -49,4 +49,4 @@ public partial class InventoryPanel
`[GetServices]`,避免将非上下文服务字段误判为服务依赖。
`[GetAll]` 会跳过 `const`、`static` 和 `readonly` 字段。若某个字段本来会被 `[GetAll]` 推断为
-`Model`、`System` 或 `Utility` 绑定,但因为字段不可赋值而被跳过,生成器会发出警告提示该字段不会参与生成。
+`Model`、`System` 或 `Utility` 绑定,但因为是不可赋值的 `static` 或 `readonly` 字段而被跳过,生成器会发出警告提示该字段不会参与生成。
diff --git a/GFramework.SourceGenerators/Rule/ContextGetGenerator.cs b/GFramework.SourceGenerators/Rule/ContextGetGenerator.cs
index 6d47253..83aaf0e 100644
--- a/GFramework.SourceGenerators/Rule/ContextGetGenerator.cs
+++ b/GFramework.SourceGenerators/Rule/ContextGetGenerator.cs
@@ -1,7 +1,5 @@
-using System.Text;
using GFramework.SourceGenerators.Common.Constants;
using GFramework.SourceGenerators.Common.Diagnostics;
-using GFramework.SourceGenerators.Common.Extensions;
using GFramework.SourceGenerators.Common.Info;
using GFramework.SourceGenerators.Diagnostics;
@@ -298,6 +296,11 @@ public sealed class ContextGetGenerator : IIncrementalGenerator
if (explicitFields.Contains(field))
continue;
+ // Const fields are compile-time constants, so [GetAll] should skip them explicitly instead of relying on
+ // type inference to fall through implicitly.
+ if (field.IsConst)
+ continue;
+
// Infer the target first so [GetAll] only warns for fields it would otherwise bind.
if (!TryCreateInferredBinding(field, symbols, out var binding))
continue;
From 9590bf1dd95978c73bea1fbf54e504ab8b0136ec Mon Sep 17 00:00:00 2001
From: GeWuYou <95328647+GeWuYou@users.noreply.github.com>
Date: Mon, 30 Mar 2026 09:22:59 +0800
Subject: [PATCH 4/4] =?UTF-8?q?chore(generator):=20=E6=9B=B4=E6=96=B0?=
=?UTF-8?q?=E4=BB=A3=E7=A0=81=E7=94=9F=E6=88=90=E5=99=A8=E4=BE=9D=E8=B5=96?=
=?UTF-8?q?=E5=BC=95=E5=85=A5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 在 ContextGetGenerator 中添加 System.Text 和 Extensions 引入
- 在测试文件中添加 Microsoft.CodeAnalysis.Text 引入
---
GFramework.SourceGenerators.Tests/GlobalUsings.cs | 1 +
GFramework.SourceGenerators/Rule/ContextGetGenerator.cs | 2 ++
2 files changed, 3 insertions(+)
diff --git a/GFramework.SourceGenerators.Tests/GlobalUsings.cs b/GFramework.SourceGenerators.Tests/GlobalUsings.cs
index e8da86b..78c09fe 100644
--- a/GFramework.SourceGenerators.Tests/GlobalUsings.cs
+++ b/GFramework.SourceGenerators.Tests/GlobalUsings.cs
@@ -17,6 +17,7 @@ global using System.Linq;
global using System.Threading;
global using System.Threading.Tasks;
global using Microsoft.CodeAnalysis;
+global using Microsoft.CodeAnalysis.Text;
global using Microsoft.CodeAnalysis.CSharp.Testing;
global using Microsoft.CodeAnalysis.Testing;
global using NUnit.Framework;
\ No newline at end of file
diff --git a/GFramework.SourceGenerators/Rule/ContextGetGenerator.cs b/GFramework.SourceGenerators/Rule/ContextGetGenerator.cs
index 83aaf0e..1263d11 100644
--- a/GFramework.SourceGenerators/Rule/ContextGetGenerator.cs
+++ b/GFramework.SourceGenerators/Rule/ContextGetGenerator.cs
@@ -1,5 +1,7 @@
+using System.Text;
using GFramework.SourceGenerators.Common.Constants;
using GFramework.SourceGenerators.Common.Diagnostics;
+using GFramework.SourceGenerators.Common.Extensions;
using GFramework.SourceGenerators.Common.Info;
using GFramework.SourceGenerators.Diagnostics;