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; }