Merge pull request #173 from GeWuYou/refactor/analyzer-method-resolution

refactor(analyzer): 优化上下文注册分析器中的方法解析逻辑
This commit is contained in:
gewuyou 2026-04-05 10:03:20 +08:00 committed by GitHub
commit a22e522cf9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 259 additions and 21 deletions

View File

@ -365,6 +365,228 @@ public sealed class ContextRegistrationAnalyzerTests
"0"));
}
[Test]
public async Task Does_Not_Report_When_Inherited_OnInitialize_Calls_Virtual_Helper_Overridden_In_Derived_Architecture()
{
await AnalyzerTestDriver<ContextRegistrationAnalyzer>.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<ContextRegistrationAnalyzer>.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());
}
}
}
"""));
}
[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<ContextRegistrationAnalyzer>.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<ContextRegistrationAnalyzer>.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}";

View File

@ -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,41 +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;
// 对已经具备源码的方法保留原始目标,避免把显式的 base 调用重新折回到当前 override。
helperMethod = targetMethod.DeclaringSyntaxReferences.Length > 0 && !targetMethod.IsAbstract
? targetMethod
: 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);
}
/// <summary>
/// 解析架构/模块分析中的辅助方法调用。
/// 普通虚调用应跟随到具体类型上的 override而显式 <c>base.Xxx()</c> 必须保留基类语义。
/// </summary>
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;
helperMethod = targetMethod.DeclaringSyntaxReferences.Length > 0 && !targetMethod.IsAbstract
? targetMethod
: 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;
}
}
@ -783,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)