Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,228 @@ await AnalyzerTestDriver<ContextRegistrationAnalyzer>.RunAsync(
"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}";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ private static ArchitectureRegistrationData AnalyzeArchitecture(
continue;
}

if (TryResolveArchitectureHelperMethod(invocation.TargetMethod, architectureType, out var helperMethod))
if (TryResolveArchitectureHelperMethod(invocation, architectureType, out var helperMethod))
pendingMethods.Enqueue(helperMethod);
}
}
Expand Down Expand Up @@ -537,7 +537,7 @@ private static void AnalyzeModule(
continue;
}

if (TryResolveModuleHelperMethod(invocation.TargetMethod, moduleType, out var helperMethod))
if (TryResolveModuleHelperMethod(invocation, moduleType, out var helperMethod))
pendingMethods.Enqueue(helperMethod);
}
}
Expand Down Expand Up @@ -651,41 +651,46 @@ private static bool TryGetRegistration(
}

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;
}
}
Expand Down Expand Up @@ -783,6 +788,17 @@ public static bool IsWithinTypeHierarchy(
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)
Expand Down
Loading