From d2ecd14ca89393810eab176dada0ff221b07850e Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Sun, 5 Apr 2026 00:25:14 +0800 Subject: [PATCH 1/2] =?UTF-8?q?refactor(analyzer):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E4=B8=8A=E4=B8=8B=E6=96=87=E6=B3=A8=E5=86=8C=E5=88=86=E6=9E=90?= =?UTF-8?q?=E5=99=A8=E4=B8=AD=E7=9A=84=E6=96=B9=E6=B3=95=E8=A7=A3=E6=9E=90?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除对已具备源码方法的特殊处理逻辑 - 统一使用 ResolveHierarchyMethodImplementation 进行方法解析 - 优先解析到当前具体架构类型上的 override 方法 - 为模块安装路径添加一致的 override 解析逻辑 - 添加完整的单元测试验证分析器行为 --- .../ContextRegistrationAnalyzerTests.cs | 101 ++++++++++++++++++ .../Analyzers/ContextRegistrationAnalyzer.cs | 11 +- 2 files changed, 105 insertions(+), 7 deletions(-) diff --git a/GFramework.SourceGenerators.Tests/Analyzers/ContextRegistrationAnalyzerTests.cs b/GFramework.SourceGenerators.Tests/Analyzers/ContextRegistrationAnalyzerTests.cs index 7db229f7..40afbe0f 100644 --- a/GFramework.SourceGenerators.Tests/Analyzers/ContextRegistrationAnalyzerTests.cs +++ b/GFramework.SourceGenerators.Tests/Analyzers/ContextRegistrationAnalyzerTests.cs @@ -365,6 +365,107 @@ public sealed class ContextRegistrationAnalyzerTests "0")); } + [Test] + public async Task Does_Not_Report_When_Inherited_OnInitialize_Calls_Virtual_Helper_Overridden_In_Derived_Architecture() + { + await AnalyzerTestDriver.RunAsync( + Wrap(""" + namespace TestApp + { + using GFramework.Core.Abstractions.Model; + using GFramework.Core.Abstractions.Systems; + using GFramework.Core.Architectures; + using GFramework.SourceGenerators.Abstractions.Rule; + + public interface IInventoryModel : IModel { } + + public sealed class InventoryModel : IInventoryModel { } + + public abstract class ArchitectureBase : Architecture + { + protected override void OnInitialize() + { + RegisterComponents(); + } + + protected virtual void RegisterComponents() + { + } + } + + public sealed class InventoryPanelSystem : ISystem + { + [GetModel] + private IInventoryModel _model = null!; + } + + public sealed class GameArchitecture : ArchitectureBase + { + protected override void RegisterComponents() + { + RegisterModel(new InventoryModel()); + RegisterSystem(new InventoryPanelSystem()); + } + } + } + """)); + } + + [Test] + public async Task Does_Not_Report_When_Inherited_Module_Install_Calls_Virtual_Helper_Overridden_In_Derived_Module() + { + await AnalyzerTestDriver.RunAsync( + Wrap(""" + namespace TestApp + { + using GFramework.Core.Abstractions.Architectures; + using GFramework.Core.Abstractions.Model; + using GFramework.Core.Abstractions.Systems; + using GFramework.Core.Architectures; + using GFramework.SourceGenerators.Abstractions.Rule; + + public interface IInventoryModel : IModel { } + + public sealed class InventoryModel : IInventoryModel { } + + public abstract class ModuleBase : IArchitectureModule + { + public void Install(IArchitecture architecture) + { + RegisterComponents(architecture); + } + + protected virtual void RegisterComponents(IArchitecture architecture) + { + } + } + + public sealed class DerivedInventoryModule : ModuleBase + { + protected override void RegisterComponents(IArchitecture architecture) + { + architecture.RegisterModel(new InventoryModel()); + } + } + + public sealed class InventoryPanelSystem : ISystem + { + [GetModel] + private IInventoryModel _model = null!; + } + + public sealed class GameArchitecture : Architecture + { + protected override void OnInitialize() + { + InstallModule(new DerivedInventoryModule()); + RegisterSystem(new InventoryPanelSystem()); + } + } + } + """)); + } + private static string Wrap(string source) { return $"{TestPreamble}{Environment.NewLine}{Environment.NewLine}{source}"; diff --git a/GFramework.SourceGenerators/Analyzers/ContextRegistrationAnalyzer.cs b/GFramework.SourceGenerators/Analyzers/ContextRegistrationAnalyzer.cs index c691fe65..6b6f2941 100644 --- a/GFramework.SourceGenerators/Analyzers/ContextRegistrationAnalyzer.cs +++ b/GFramework.SourceGenerators/Analyzers/ContextRegistrationAnalyzer.cs @@ -663,10 +663,8 @@ public sealed class ContextRegistrationAnalyzer : DiagnosticAnalyzer if (!SymbolHelpers.IsWithinTypeHierarchy(targetMethod.ContainingType, architectureType)) return false; - // 对已经具备源码的方法保留原始目标,避免把显式的 base 调用重新折回到当前 override。 - helperMethod = targetMethod.DeclaringSyntaxReferences.Length > 0 && !targetMethod.IsAbstract - ? targetMethod - : SymbolHelpers.ResolveHierarchyMethodImplementation(targetMethod, architectureType) ?? targetMethod; + // 优先解析到当前具体架构类型上的 override,只有无法映射时才回退到原始目标方法。 + helperMethod = SymbolHelpers.ResolveHierarchyMethodImplementation(targetMethod, architectureType) ?? targetMethod; return helperMethod.DeclaringSyntaxReferences.Length > 0; } @@ -683,9 +681,8 @@ public sealed class ContextRegistrationAnalyzer : DiagnosticAnalyzer if (!SymbolHelpers.IsWithinTypeHierarchy(targetMethod.ContainingType, moduleType)) return false; - helperMethod = targetMethod.DeclaringSyntaxReferences.Length > 0 && !targetMethod.IsAbstract - ? targetMethod - : SymbolHelpers.ResolveHierarchyMethodImplementation(targetMethod, moduleType) ?? targetMethod; + // 模块安装路径与架构路径一致,也必须优先跟随派生模块上的 override。 + helperMethod = SymbolHelpers.ResolveHierarchyMethodImplementation(targetMethod, moduleType) ?? targetMethod; return helperMethod.DeclaringSyntaxReferences.Length > 0; } } From 37049be600ea601e12615c46b130746dbd3aaa27 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Sun, 5 Apr 2026 09:14:15 +0800 Subject: [PATCH 2/2] =?UTF-8?q?refactor(analyzer):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E4=B8=8A=E4=B8=8B=E6=96=87=E6=B3=A8=E5=86=8C=E5=88=86=E6=9E=90?= =?UTF-8?q?=E5=99=A8=E4=B8=AD=E7=9A=84=E8=BE=85=E5=8A=A9=E6=96=B9=E6=B3=95?= =?UTF-8?q?=E8=A7=A3=E6=9E=90=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修改 TryResolveArchitectureHelperMethod 和 TryResolveModuleHelperMethod 方法参数 - 添加新的 TryResolveHelperMethod 通用方法处理辅助方法调用解析 - 实现对显式 base 调用的特殊处理以保留基类语义 - 添加 IsExplicitBaseInvocation 方法检测显式基础调用 - 更新测试文件验证分析器核心行为 --- .../ContextRegistrationAnalyzerTests.cs | 121 ++++++++++++++++++ .../Analyzers/ContextRegistrationAnalyzer.cs | 55 +++++--- 2 files changed, 158 insertions(+), 18 deletions(-) diff --git a/GFramework.SourceGenerators.Tests/Analyzers/ContextRegistrationAnalyzerTests.cs b/GFramework.SourceGenerators.Tests/Analyzers/ContextRegistrationAnalyzerTests.cs index 40afbe0f..781a77bc 100644 --- a/GFramework.SourceGenerators.Tests/Analyzers/ContextRegistrationAnalyzerTests.cs +++ b/GFramework.SourceGenerators.Tests/Analyzers/ContextRegistrationAnalyzerTests.cs @@ -466,6 +466,127 @@ public sealed class ContextRegistrationAnalyzerTests """)); } + [Test] + public async Task Reports_Warning_When_Derived_Architecture_Explicitly_Calls_Base_Helper() + { + var markup = MarkupTestSource.Parse( + Wrap(""" + namespace TestApp + { + using GFramework.Core.Abstractions.Model; + using GFramework.Core.Abstractions.Systems; + using GFramework.Core.Architectures; + using GFramework.SourceGenerators.Abstractions.Rule; + + public interface IInventoryModel : IModel { } + + public sealed class InventoryModel : IInventoryModel { } + + public sealed class InventoryPanelSystem : ISystem + { + [GetModel] + private IInventoryModel {|#0:_model|} = null!; + } + + public abstract class ArchitectureBase : Architecture + { + protected virtual void RegisterComponents() + { + RegisterSystem(new InventoryPanelSystem()); + } + } + + public sealed class GameArchitecture : ArchitectureBase + { + protected override void OnInitialize() + { + base.RegisterComponents(); + } + + protected override void RegisterComponents() + { + RegisterModel(new InventoryModel()); + RegisterSystem(new InventoryPanelSystem()); + } + } + } + """)); + + await AnalyzerTestDriver.RunAsync( + markup.Source, + markup.WithSpan( + new DiagnosticResult("GF_ContextRegistration_001", DiagnosticSeverity.Warning) + .WithArguments("IInventoryModel", "InventoryPanelSystem", "GameArchitecture"), + "0")); + } + + [Test] + public async Task Reports_Warning_When_Derived_Module_Explicitly_Calls_Base_Helper() + { + var markup = MarkupTestSource.Parse( + Wrap(""" + namespace TestApp + { + using GFramework.Core.Abstractions.Architectures; + using GFramework.Core.Abstractions.Model; + using GFramework.Core.Abstractions.Systems; + using GFramework.Core.Architectures; + using GFramework.SourceGenerators.Abstractions.Rule; + + public interface IInventoryModel : IModel { } + + public sealed class InventoryModel : IInventoryModel { } + + public sealed class InventoryPanelSystem : ISystem + { + [GetModel] + private IInventoryModel {|#0:_model|} = null!; + } + + public abstract class ModuleBase : IArchitectureModule + { + public virtual void Install(IArchitecture architecture) + { + } + + protected virtual void RegisterComponents(IArchitecture architecture) + { + architecture.RegisterSystem(new InventoryPanelSystem()); + } + } + + public sealed class DerivedInventoryModule : ModuleBase + { + public override void Install(IArchitecture architecture) + { + base.RegisterComponents(architecture); + } + + protected override void RegisterComponents(IArchitecture architecture) + { + architecture.RegisterModel(new InventoryModel()); + architecture.RegisterSystem(new InventoryPanelSystem()); + } + } + + public sealed class GameArchitecture : Architecture + { + protected override void OnInitialize() + { + InstallModule(new DerivedInventoryModule()); + } + } + } + """)); + + await AnalyzerTestDriver.RunAsync( + markup.Source, + markup.WithSpan( + new DiagnosticResult("GF_ContextRegistration_001", DiagnosticSeverity.Warning) + .WithArguments("IInventoryModel", "InventoryPanelSystem", "GameArchitecture"), + "0")); + } + private static string Wrap(string source) { return $"{TestPreamble}{Environment.NewLine}{Environment.NewLine}{source}"; diff --git a/GFramework.SourceGenerators/Analyzers/ContextRegistrationAnalyzer.cs b/GFramework.SourceGenerators/Analyzers/ContextRegistrationAnalyzer.cs index 6b6f2941..ffbc0aae 100644 --- a/GFramework.SourceGenerators/Analyzers/ContextRegistrationAnalyzer.cs +++ b/GFramework.SourceGenerators/Analyzers/ContextRegistrationAnalyzer.cs @@ -495,7 +495,7 @@ public sealed class ContextRegistrationAnalyzer : DiagnosticAnalyzer continue; } - if (TryResolveArchitectureHelperMethod(invocation.TargetMethod, architectureType, out var helperMethod)) + if (TryResolveArchitectureHelperMethod(invocation, architectureType, out var helperMethod)) pendingMethods.Enqueue(helperMethod); } } @@ -537,7 +537,7 @@ public sealed class ContextRegistrationAnalyzer : DiagnosticAnalyzer continue; } - if (TryResolveModuleHelperMethod(invocation.TargetMethod, moduleType, out var helperMethod)) + if (TryResolveModuleHelperMethod(invocation, moduleType, out var helperMethod)) pendingMethods.Enqueue(helperMethod); } } @@ -651,38 +651,46 @@ public sealed class ContextRegistrationAnalyzer : DiagnosticAnalyzer } private static bool TryResolveArchitectureHelperMethod( - IMethodSymbol targetMethod, + IInvocationOperation invocation, INamedTypeSymbol architectureType, out IMethodSymbol helperMethod) { - helperMethod = default!; - - if (targetMethod.MethodKind is not (MethodKind.Ordinary or MethodKind.Constructor)) - return false; - - if (!SymbolHelpers.IsWithinTypeHierarchy(targetMethod.ContainingType, architectureType)) - return false; - - // 优先解析到当前具体架构类型上的 override,只有无法映射时才回退到原始目标方法。 - helperMethod = SymbolHelpers.ResolveHierarchyMethodImplementation(targetMethod, architectureType) ?? targetMethod; - return helperMethod.DeclaringSyntaxReferences.Length > 0; + return TryResolveHelperMethod(invocation, architectureType, out helperMethod); } private static bool TryResolveModuleHelperMethod( - IMethodSymbol targetMethod, + IInvocationOperation invocation, INamedTypeSymbol moduleType, out IMethodSymbol helperMethod) + { + return TryResolveHelperMethod(invocation, moduleType, out helperMethod); + } + + /// + /// 解析架构/模块分析中的辅助方法调用。 + /// 普通虚调用应跟随到具体类型上的 override,而显式 base.Xxx() 必须保留基类语义。 + /// + private static bool TryResolveHelperMethod( + IInvocationOperation invocation, + INamedTypeSymbol concreteType, + out IMethodSymbol helperMethod) { helperMethod = default!; + var targetMethod = invocation.TargetMethod; if (targetMethod.MethodKind is not (MethodKind.Ordinary or MethodKind.Constructor)) return false; - if (!SymbolHelpers.IsWithinTypeHierarchy(targetMethod.ContainingType, moduleType)) + if (!SymbolHelpers.IsWithinTypeHierarchy(targetMethod.ContainingType, concreteType)) return false; - // 模块安装路径与架构路径一致,也必须优先跟随派生模块上的 override。 - helperMethod = SymbolHelpers.ResolveHierarchyMethodImplementation(targetMethod, moduleType) ?? targetMethod; + if (SymbolHelpers.IsExplicitBaseInvocation(invocation)) + { + helperMethod = targetMethod; + return helperMethod.DeclaringSyntaxReferences.Length > 0; + } + + helperMethod = SymbolHelpers.ResolveHierarchyMethodImplementation(targetMethod, concreteType) ?? targetMethod; return helperMethod.DeclaringSyntaxReferences.Length > 0; } } @@ -780,6 +788,17 @@ public sealed class ContextRegistrationAnalyzer : DiagnosticAnalyzer SymbolEqualityComparer.Default.Equals(type, candidateType)); } + public static bool IsExplicitBaseInvocation(IInvocationOperation invocation) + { + return invocation.Syntax is InvocationExpressionSyntax + { + Expression: MemberAccessExpressionSyntax + { + Expression: BaseExpressionSyntax + } + }; + } + public static IMethodSymbol? ResolveHierarchyMethodImplementation( IMethodSymbol method, INamedTypeSymbol ownerType)