diff --git a/GFramework.SourceGenerators.Common/Constants/PathContests.cs b/GFramework.SourceGenerators.Common/Constants/PathContests.cs
index e5cd758..3facd8b 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/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/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.Tests/Rule/ContextGetGeneratorTests.cs b/GFramework.SourceGenerators.Tests/Rule/ContextGetGeneratorTests.cs
index 0fc78a9..1bea458 100644
--- a/GFramework.SourceGenerators.Tests/Rule/ContextGetGeneratorTests.cs
+++ b/GFramework.SourceGenerators.Tests/Rule/ContextGetGeneratorTests.cs
@@ -377,6 +377,291 @@ 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 = 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.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 {|#0:_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.Source },
+ GeneratedSources =
+ {
+ (typeof(ContextGetGenerator), "TestApp_BattlePanel.ContextGet.g.cs", expected)
+ }
+ },
+ DisabledDiagnostics = { "GF_Common_Trace_001" }
+ };
+
+ test.ExpectedDiagnostics.Add(source.WithSpan(
+ new DiagnosticResult("GF_ContextGet_008", DiagnosticSeverity.Warning),
+ "0")
+ .WithArguments("_model"));
+
+ await test.RunAsync();
+ Assert.Pass();
+ }
+
+ [Test]
+ public async Task Warns_And_Skips_Static_Inferred_Field_For_GetAll_Class()
+ {
+ 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.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 {|#0:_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.Source },
+ GeneratedSources =
+ {
+ (typeof(ContextGetGenerator), "TestApp_BattlePanel.ContextGet.g.cs", expected)
+ }
+ },
+ DisabledDiagnostics = { "GF_Common_Trace_001" }
+ };
+
+ test.ExpectedDiagnostics.Add(source.WithSpan(
+ new DiagnosticResult("GF_ContextGet_007", DiagnosticSeverity.Warning),
+ "0")
+ .WithArguments("_model"));
+
+ await test.RunAsync();
+ Assert.Pass();
+ }
+
[Test]
public async Task Skips_Nullable_Service_Like_Field_For_ContextAware_GetAll_Class()
{
@@ -549,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();
@@ -614,74 +900,217 @@ 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();
Assert.Pass();
}
+ [Test]
+ public async Task Reports_Diagnostic_For_Readonly_Explicit_GetModel_Field()
+ {
+ 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.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 {|#0:_model|} = null!;
+ }
+ }
+ """);
+
+ var test = new CSharpSourceGeneratorTest
+ {
+ TestState =
+ {
+ Sources = { source.Source }
+ },
+ DisabledDiagnostics = { "GF_Common_Trace_001" }
+ };
+
+ test.ExpectedDiagnostics.Add(source.WithSpan(
+ new DiagnosticResult("GF_ContextGet_003", DiagnosticSeverity.Error),
+ "0")
+ .WithArguments("_model"));
+
+ await test.RunAsync();
+ Assert.Pass();
+ }
+
+ [Test]
+ public async Task Reports_Diagnostic_For_Static_Explicit_GetModel_Field()
+ {
+ 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.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 {|#0:_model|} = null!;
+ }
+ }
+ """);
+
+ var test = new CSharpSourceGeneratorTest
+ {
+ TestState =
+ {
+ Sources = { source.Source }
+ },
+ DisabledDiagnostics = { "GF_Common_Trace_001" }
+ };
+
+ test.ExpectedDiagnostics.Add(source.WithSpan(
+ new DiagnosticResult("GF_ContextGet_002", DiagnosticSeverity.Error),
+ "0")
+ .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..7ed6db8 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` 绑定,但因为是不可赋值的 `static` 或 `readonly` 字段而被跳过,生成器会发出警告提示该字段不会参与生成。
diff --git a/GFramework.SourceGenerators/Rule/ContextGetGenerator.cs b/GFramework.SourceGenerators/Rule/ContextGetGenerator.cs
index 4180f38..1263d11 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,29 @@ public sealed class ContextGetGenerator : IIncrementalGenerator
if (explicitFields.Contains(field))
continue;
- if (!CanInferBinding(context, field))
+ // 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;
+ 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 +330,7 @@ public sealed class ContextGetGenerator : IIncrementalGenerator
ReportFieldDiagnostic(
context,
- ContextGetDiagnostics.ReadOnlyFieldNotSupported,
+ ContextGetDiagnostics.GetAllReadOnlyFieldSkipped,
field);
return false;
}