mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-12 22:03:30 +08:00
Merge pull request #172 from GeWuYou/feat/gframework-analysis
Feat/gframework analysis
This commit is contained in:
commit
9239e51644
@ -0,0 +1,372 @@
|
|||||||
|
using GFramework.SourceGenerators.Analyzers;
|
||||||
|
using GFramework.SourceGenerators.Tests.Core;
|
||||||
|
|
||||||
|
namespace GFramework.SourceGenerators.Tests.Analyzers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 验证 Context Get 注册可见性分析器的核心行为。
|
||||||
|
/// </summary>
|
||||||
|
public sealed class ContextRegistrationAnalyzerTests
|
||||||
|
{
|
||||||
|
private const string TestPreamble = """
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace GFramework.Core.Abstractions.Rule
|
||||||
|
{
|
||||||
|
public interface IContextAware { }
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace GFramework.Core.Abstractions.Model
|
||||||
|
{
|
||||||
|
public interface IModel : GFramework.Core.Abstractions.Rule.IContextAware { }
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace GFramework.Core.Abstractions.Systems
|
||||||
|
{
|
||||||
|
public interface ISystem : GFramework.Core.Abstractions.Rule.IContextAware { }
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace GFramework.Core.Abstractions.Utility
|
||||||
|
{
|
||||||
|
public interface IUtility : GFramework.Core.Abstractions.Rule.IContextAware { }
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace GFramework.Core.Abstractions.Architectures
|
||||||
|
{
|
||||||
|
public interface IArchitecture
|
||||||
|
{
|
||||||
|
T RegisterModel<T>(T model) where T : GFramework.Core.Abstractions.Model.IModel;
|
||||||
|
void RegisterModel<T>(Action<T> onCreated = null) where T : class, GFramework.Core.Abstractions.Model.IModel;
|
||||||
|
T RegisterSystem<T>(T system) where T : GFramework.Core.Abstractions.Systems.ISystem;
|
||||||
|
void RegisterSystem<T>(Action<T> onCreated = null) where T : class, GFramework.Core.Abstractions.Systems.ISystem;
|
||||||
|
T RegisterUtility<T>(T utility) where T : GFramework.Core.Abstractions.Utility.IUtility;
|
||||||
|
void RegisterUtility<T>(Action<T> onCreated = null) where T : class, GFramework.Core.Abstractions.Utility.IUtility;
|
||||||
|
IArchitectureModule InstallModule(IArchitectureModule module);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IArchitectureModule
|
||||||
|
{
|
||||||
|
void Install(IArchitecture architecture);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IArchitectureContext
|
||||||
|
{
|
||||||
|
TModel GetModel<TModel>() where TModel : class, GFramework.Core.Abstractions.Model.IModel;
|
||||||
|
IReadOnlyList<TModel> GetModels<TModel>() where TModel : class, GFramework.Core.Abstractions.Model.IModel;
|
||||||
|
TSystem GetSystem<TSystem>() where TSystem : class, GFramework.Core.Abstractions.Systems.ISystem;
|
||||||
|
IReadOnlyList<TSystem> GetSystems<TSystem>() where TSystem : class, GFramework.Core.Abstractions.Systems.ISystem;
|
||||||
|
TUtility GetUtility<TUtility>() where TUtility : class, GFramework.Core.Abstractions.Utility.IUtility;
|
||||||
|
IReadOnlyList<TUtility> GetUtilities<TUtility>() where TUtility : class, GFramework.Core.Abstractions.Utility.IUtility;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace GFramework.Core.Architectures
|
||||||
|
{
|
||||||
|
public abstract class Architecture : GFramework.Core.Abstractions.Architectures.IArchitecture
|
||||||
|
{
|
||||||
|
protected abstract void OnInitialize();
|
||||||
|
|
||||||
|
public virtual T RegisterModel<T>(T model) where T : GFramework.Core.Abstractions.Model.IModel => model;
|
||||||
|
|
||||||
|
public virtual void RegisterModel<T>(Action<T> onCreated = null)
|
||||||
|
where T : class, GFramework.Core.Abstractions.Model.IModel
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual T RegisterSystem<T>(T system) where T : GFramework.Core.Abstractions.Systems.ISystem => system;
|
||||||
|
|
||||||
|
public virtual void RegisterSystem<T>(Action<T> onCreated = null)
|
||||||
|
where T : class, GFramework.Core.Abstractions.Systems.ISystem
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual T RegisterUtility<T>(T utility) where T : GFramework.Core.Abstractions.Utility.IUtility => utility;
|
||||||
|
|
||||||
|
public virtual void RegisterUtility<T>(Action<T> onCreated = null)
|
||||||
|
where T : class, GFramework.Core.Abstractions.Utility.IUtility
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual GFramework.Core.Abstractions.Architectures.IArchitectureModule InstallModule(
|
||||||
|
GFramework.Core.Abstractions.Architectures.IArchitectureModule module)
|
||||||
|
{
|
||||||
|
module.Install(this);
|
||||||
|
return module;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace GFramework.Core.Extensions
|
||||||
|
{
|
||||||
|
public static class ContextAwareServiceExtensions
|
||||||
|
{
|
||||||
|
public static TModel GetModel<TModel>(this GFramework.Core.Abstractions.Rule.IContextAware contextAware)
|
||||||
|
where TModel : class, GFramework.Core.Abstractions.Model.IModel => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public static IReadOnlyList<TModel> GetModels<TModel>(this GFramework.Core.Abstractions.Rule.IContextAware contextAware)
|
||||||
|
where TModel : class, GFramework.Core.Abstractions.Model.IModel => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public static TSystem GetSystem<TSystem>(this GFramework.Core.Abstractions.Rule.IContextAware contextAware)
|
||||||
|
where TSystem : class, GFramework.Core.Abstractions.Systems.ISystem => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public static IReadOnlyList<TSystem> GetSystems<TSystem>(this GFramework.Core.Abstractions.Rule.IContextAware contextAware)
|
||||||
|
where TSystem : class, GFramework.Core.Abstractions.Systems.ISystem => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public static TUtility GetUtility<TUtility>(this GFramework.Core.Abstractions.Rule.IContextAware contextAware)
|
||||||
|
where TUtility : class, GFramework.Core.Abstractions.Utility.IUtility => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public static IReadOnlyList<TUtility> GetUtilities<TUtility>(this GFramework.Core.Abstractions.Rule.IContextAware contextAware)
|
||||||
|
where TUtility : class, GFramework.Core.Abstractions.Utility.IUtility => throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace GFramework.SourceGenerators.Abstractions.Rule
|
||||||
|
{
|
||||||
|
public sealed class GetModelAttribute : Attribute { }
|
||||||
|
public sealed class GetModelsAttribute : Attribute { }
|
||||||
|
public sealed class GetSystemAttribute : Attribute { }
|
||||||
|
public sealed class GetSystemsAttribute : Attribute { }
|
||||||
|
public sealed class GetUtilityAttribute : Attribute { }
|
||||||
|
public sealed class GetUtilitiesAttribute : Attribute { }
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task Reports_Warning_When_FieldInjectedModel_Is_Not_Registered()
|
||||||
|
{
|
||||||
|
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 InventoryPanelSystem : ISystem
|
||||||
|
{
|
||||||
|
[GetModel]
|
||||||
|
private IInventoryModel {|#0:_model|} = null!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class GameArchitecture : Architecture
|
||||||
|
{
|
||||||
|
protected override void OnInitialize()
|
||||||
|
{
|
||||||
|
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 Does_Not_Report_When_FieldInjectedModel_Is_Registered()
|
||||||
|
{
|
||||||
|
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 sealed class InventoryPanelSystem : ISystem
|
||||||
|
{
|
||||||
|
[GetModel]
|
||||||
|
private IInventoryModel _model = null!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class GameArchitecture : Architecture
|
||||||
|
{
|
||||||
|
protected override void OnInitialize()
|
||||||
|
{
|
||||||
|
RegisterModel(new InventoryModel());
|
||||||
|
RegisterSystem(new InventoryPanelSystem());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task Reports_Warning_When_HandWrittenGetSystem_Call_Has_No_Registration()
|
||||||
|
{
|
||||||
|
var markup = MarkupTestSource.Parse(
|
||||||
|
Wrap("""
|
||||||
|
namespace TestApp
|
||||||
|
{
|
||||||
|
using GFramework.Core.Abstractions.Systems;
|
||||||
|
using GFramework.Core.Abstractions.Utility;
|
||||||
|
using GFramework.Core.Architectures;
|
||||||
|
using GFramework.Core.Extensions;
|
||||||
|
|
||||||
|
public interface ICombatSystem : ISystem { }
|
||||||
|
|
||||||
|
public sealed class UiUtility : IUtility
|
||||||
|
{
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
{|#0:this.GetSystem<ICombatSystem>()|};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class GameArchitecture : Architecture
|
||||||
|
{
|
||||||
|
protected override void OnInitialize()
|
||||||
|
{
|
||||||
|
RegisterUtility(new UiUtility());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""));
|
||||||
|
|
||||||
|
await AnalyzerTestDriver<ContextRegistrationAnalyzer>.RunAsync(
|
||||||
|
markup.Source,
|
||||||
|
markup.WithSpan(
|
||||||
|
new DiagnosticResult("GF_ContextRegistration_002", DiagnosticSeverity.Warning)
|
||||||
|
.WithArguments("ICombatSystem", "UiUtility", "GameArchitecture"),
|
||||||
|
"0"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task Does_Not_Report_When_Registration_Comes_From_Installed_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 sealed class InventoryPanelSystem : ISystem
|
||||||
|
{
|
||||||
|
[GetModel]
|
||||||
|
private IInventoryModel _model = null!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class InventoryModule : IArchitectureModule
|
||||||
|
{
|
||||||
|
public void Install(IArchitecture architecture)
|
||||||
|
{
|
||||||
|
architecture.RegisterModel(new InventoryModel());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class GameArchitecture : Architecture
|
||||||
|
{
|
||||||
|
protected override void OnInitialize()
|
||||||
|
{
|
||||||
|
InstallModule(new InventoryModule());
|
||||||
|
RegisterSystem(new InventoryPanelSystem());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task Does_Not_Report_When_Owning_Architecture_Cannot_Be_Uniquely_Determined()
|
||||||
|
{
|
||||||
|
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 InventoryPanelSystem : ISystem
|
||||||
|
{
|
||||||
|
[GetModel]
|
||||||
|
private IInventoryModel _model = null!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class FirstArchitecture : Architecture
|
||||||
|
{
|
||||||
|
protected override void OnInitialize()
|
||||||
|
{
|
||||||
|
RegisterSystem(new InventoryPanelSystem());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class SecondArchitecture : Architecture
|
||||||
|
{
|
||||||
|
protected override void OnInitialize()
|
||||||
|
{
|
||||||
|
RegisterSystem(new InventoryPanelSystem());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task Reports_Warning_When_GetUtilities_Field_Has_No_Registered_Utility()
|
||||||
|
{
|
||||||
|
var markup = MarkupTestSource.Parse(
|
||||||
|
Wrap("""
|
||||||
|
namespace TestApp
|
||||||
|
{
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using GFramework.Core.Abstractions.Systems;
|
||||||
|
using GFramework.Core.Abstractions.Utility;
|
||||||
|
using GFramework.Core.Architectures;
|
||||||
|
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||||
|
|
||||||
|
public interface IInventoryUtility : IUtility { }
|
||||||
|
|
||||||
|
public sealed class InventoryPanelSystem : ISystem
|
||||||
|
{
|
||||||
|
[GetUtilities]
|
||||||
|
private IReadOnlyList<IInventoryUtility> {|#0:_utilities|} = null!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class GameArchitecture : Architecture
|
||||||
|
{
|
||||||
|
protected override void OnInitialize()
|
||||||
|
{
|
||||||
|
RegisterSystem(new InventoryPanelSystem());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""));
|
||||||
|
|
||||||
|
await AnalyzerTestDriver<ContextRegistrationAnalyzer>.RunAsync(
|
||||||
|
markup.Source,
|
||||||
|
markup.WithSpan(
|
||||||
|
new DiagnosticResult("GF_ContextRegistration_003", DiagnosticSeverity.Warning)
|
||||||
|
.WithArguments("IInventoryUtility", "InventoryPanelSystem", "GameArchitecture"),
|
||||||
|
"0"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Wrap(string source)
|
||||||
|
{
|
||||||
|
return $"{TestPreamble}{Environment.NewLine}{Environment.NewLine}{source}";
|
||||||
|
}
|
||||||
|
}
|
||||||
34
GFramework.SourceGenerators.Tests/Core/AnalyzerTest.cs
Normal file
34
GFramework.SourceGenerators.Tests/Core/AnalyzerTest.cs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
|
|
||||||
|
namespace GFramework.SourceGenerators.Tests.Core;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 提供 Roslyn 分析器测试的通用运行入口。
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TAnalyzer">要验证的分析器类型。</typeparam>
|
||||||
|
public static class AnalyzerTestDriver<TAnalyzer>
|
||||||
|
where TAnalyzer : DiagnosticAnalyzer, new()
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 运行分析器测试并断言期望诊断。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source">测试输入源码。</param>
|
||||||
|
/// <param name="diagnostics">期望诊断集合。</param>
|
||||||
|
/// <returns>异步测试任务。</returns>
|
||||||
|
public static async Task RunAsync(
|
||||||
|
string source,
|
||||||
|
params DiagnosticResult[] diagnostics)
|
||||||
|
{
|
||||||
|
var test = new CSharpAnalyzerTest<TAnalyzer, DefaultVerifier>
|
||||||
|
{
|
||||||
|
TestState =
|
||||||
|
{
|
||||||
|
Sources = { source }
|
||||||
|
},
|
||||||
|
DisabledDiagnostics = { "GF_Common_Trace_001" }
|
||||||
|
};
|
||||||
|
|
||||||
|
test.ExpectedDiagnostics.AddRange(diagnostics);
|
||||||
|
await test.RunAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -13,6 +13,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis" Version="4.14.0"/>
|
<PackageReference Include="Microsoft.CodeAnalysis" Version="4.14.0"/>
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0"/>
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0"/>
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing" Version="1.1.3"/>
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.14.0"/>
|
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.14.0"/>
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.14.0"/>
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.14.0"/>
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing" Version="1.1.3"/>
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing" Version="1.1.3"/>
|
||||||
|
|||||||
@ -15,6 +15,9 @@
|
|||||||
GF_ContextGet_006 | GFramework.SourceGenerators.rule | Error | ContextGetDiagnostics
|
GF_ContextGet_006 | GFramework.SourceGenerators.rule | Error | ContextGetDiagnostics
|
||||||
GF_ContextGet_007 | GFramework.SourceGenerators.rule | Warning | ContextGetDiagnostics
|
GF_ContextGet_007 | GFramework.SourceGenerators.rule | Warning | ContextGetDiagnostics
|
||||||
GF_ContextGet_008 | GFramework.SourceGenerators.rule | Warning | ContextGetDiagnostics
|
GF_ContextGet_008 | GFramework.SourceGenerators.rule | Warning | ContextGetDiagnostics
|
||||||
|
GF_ContextRegistration_001 | GFramework.SourceGenerators.rule | Warning | ContextRegistrationDiagnostics
|
||||||
|
GF_ContextRegistration_002 | GFramework.SourceGenerators.rule | Warning | ContextRegistrationDiagnostics
|
||||||
|
GF_ContextRegistration_003 | GFramework.SourceGenerators.rule | Warning | ContextRegistrationDiagnostics
|
||||||
GF_ConfigSchema_001 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
|
GF_ConfigSchema_001 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
|
||||||
GF_ConfigSchema_002 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
|
GF_ConfigSchema_002 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
|
||||||
GF_ConfigSchema_003 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
|
GF_ConfigSchema_003 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
|
||||||
|
|||||||
@ -0,0 +1,907 @@
|
|||||||
|
using System.Collections.Immutable;
|
||||||
|
using GFramework.SourceGenerators.Common.Constants;
|
||||||
|
using GFramework.SourceGenerators.Diagnostics;
|
||||||
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
|
using Microsoft.CodeAnalysis.Operations;
|
||||||
|
|
||||||
|
namespace GFramework.SourceGenerators.Analyzers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 分析 Context Get 使用点是否能在所属架构中找到静态可见的 Model、System、Utility 注册。
|
||||||
|
/// </summary>
|
||||||
|
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||||
|
public sealed class ContextRegistrationAnalyzer : DiagnosticAnalyzer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 当前分析器支持的诊断规则。
|
||||||
|
/// </summary>
|
||||||
|
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
|
||||||
|
ImmutableArray.Create(
|
||||||
|
ContextRegistrationDiagnostics.ModelRegistrationMissing,
|
||||||
|
ContextRegistrationDiagnostics.SystemRegistrationMissing,
|
||||||
|
ContextRegistrationDiagnostics.UtilityRegistrationMissing);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 初始化分析器并注册字段注入与手写 GetX 调用分析。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">分析器上下文。</param>
|
||||||
|
public override void Initialize(AnalysisContext context)
|
||||||
|
{
|
||||||
|
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
|
||||||
|
context.EnableConcurrentExecution();
|
||||||
|
|
||||||
|
context.RegisterCompilationStartAction(static compilationContext =>
|
||||||
|
{
|
||||||
|
var symbols = SymbolCache.Create(compilationContext.Compilation);
|
||||||
|
if (!symbols.IsReady)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var registrationIndex = new Lazy<RegistrationIndex>(
|
||||||
|
() => RegistrationIndex.Build(compilationContext.Compilation, symbols),
|
||||||
|
LazyThreadSafetyMode.ExecutionAndPublication);
|
||||||
|
|
||||||
|
compilationContext.RegisterSyntaxNodeAction(
|
||||||
|
syntaxContext => AnalyzeField(syntaxContext, symbols, registrationIndex.Value),
|
||||||
|
SyntaxKind.VariableDeclarator);
|
||||||
|
|
||||||
|
compilationContext.RegisterOperationAction(
|
||||||
|
operationContext => AnalyzeInvocation(operationContext, symbols, registrationIndex.Value),
|
||||||
|
OperationKind.Invocation);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AnalyzeField(
|
||||||
|
SyntaxNodeAnalysisContext context,
|
||||||
|
SymbolCache symbols,
|
||||||
|
RegistrationIndex registrationIndex)
|
||||||
|
{
|
||||||
|
if (context.Node is not VariableDeclaratorSyntax variableDeclarator)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (context.SemanticModel.GetDeclaredSymbol(variableDeclarator, context.CancellationToken) is not IFieldSymbol fieldSymbol)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!TryCreateBindingRequest(fieldSymbol, variableDeclarator.Identifier.GetLocation(), symbols, out var request))
|
||||||
|
return;
|
||||||
|
|
||||||
|
ReportMissingRegistration(context, registrationIndex, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AnalyzeInvocation(
|
||||||
|
OperationAnalysisContext context,
|
||||||
|
SymbolCache symbols,
|
||||||
|
RegistrationIndex registrationIndex)
|
||||||
|
{
|
||||||
|
if (context.Operation is not IInvocationOperation invocation)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!TryCreateBindingRequest(invocation, context.ContainingSymbol?.ContainingType, symbols, out var request))
|
||||||
|
return;
|
||||||
|
|
||||||
|
ReportMissingRegistration(context, registrationIndex, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ReportMissingRegistration(
|
||||||
|
SyntaxNodeAnalysisContext context,
|
||||||
|
RegistrationIndex registrationIndex,
|
||||||
|
BindingRequest request)
|
||||||
|
{
|
||||||
|
if (!registrationIndex.TryGetOwningArchitecture(request.OwnerType, out var architectureType))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (registrationIndex.HasRegistration(architectureType, request.Kind, request.ServiceType))
|
||||||
|
return;
|
||||||
|
|
||||||
|
context.ReportDiagnostic(CreateMissingRegistrationDiagnostic(request, architectureType));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ReportMissingRegistration(
|
||||||
|
OperationAnalysisContext context,
|
||||||
|
RegistrationIndex registrationIndex,
|
||||||
|
BindingRequest request)
|
||||||
|
{
|
||||||
|
if (!registrationIndex.TryGetOwningArchitecture(request.OwnerType, out var architectureType))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (registrationIndex.HasRegistration(architectureType, request.Kind, request.ServiceType))
|
||||||
|
return;
|
||||||
|
|
||||||
|
context.ReportDiagnostic(CreateMissingRegistrationDiagnostic(request, architectureType));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Diagnostic CreateMissingRegistrationDiagnostic(
|
||||||
|
BindingRequest request,
|
||||||
|
INamedTypeSymbol architectureType)
|
||||||
|
{
|
||||||
|
return Diagnostic.Create(
|
||||||
|
request.Kind switch
|
||||||
|
{
|
||||||
|
ComponentKind.Model => ContextRegistrationDiagnostics.ModelRegistrationMissing,
|
||||||
|
ComponentKind.System => ContextRegistrationDiagnostics.SystemRegistrationMissing,
|
||||||
|
ComponentKind.Utility => ContextRegistrationDiagnostics.UtilityRegistrationMissing,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(request))
|
||||||
|
},
|
||||||
|
request.Location,
|
||||||
|
request.ServiceType.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat),
|
||||||
|
request.OwnerType.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat),
|
||||||
|
architectureType.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryCreateBindingRequest(
|
||||||
|
IFieldSymbol fieldSymbol,
|
||||||
|
Location location,
|
||||||
|
SymbolCache symbols,
|
||||||
|
out BindingRequest request)
|
||||||
|
{
|
||||||
|
request = default;
|
||||||
|
|
||||||
|
if (fieldSymbol.ContainingType is not INamedTypeSymbol ownerType)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
foreach (var attribute in fieldSymbol.GetAttributes())
|
||||||
|
{
|
||||||
|
if (!TryMapAttribute(attribute.AttributeClass, symbols, out var componentKind, out var expectsCollection))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var serviceType = expectsCollection
|
||||||
|
? TryGetCollectionElementType(fieldSymbol.Type, symbols)
|
||||||
|
: fieldSymbol.Type as INamedTypeSymbol;
|
||||||
|
|
||||||
|
if (serviceType == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
request = new BindingRequest(componentKind, ownerType, serviceType, location);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryCreateBindingRequest(
|
||||||
|
IInvocationOperation invocation,
|
||||||
|
INamedTypeSymbol? ownerType,
|
||||||
|
SymbolCache symbols,
|
||||||
|
out BindingRequest request)
|
||||||
|
{
|
||||||
|
request = default;
|
||||||
|
|
||||||
|
var targetMethod = invocation.TargetMethod;
|
||||||
|
if (!targetMethod.IsGenericMethod || targetMethod.TypeArguments.Length != 1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (invocation.Syntax.SyntaxTree.FilePath.EndsWith(".g.cs", StringComparison.OrdinalIgnoreCase))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (targetMethod.TypeArguments[0] is not INamedTypeSymbol serviceType)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (_contextAwareBindingNames.TryGetValue(targetMethod.Name, out var modelKind))
|
||||||
|
{
|
||||||
|
if (!IsSupportedGetInvocationTarget(targetMethod, symbols))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ownerType == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
request = new BindingRequest(modelKind, ownerType, serviceType, invocation.Syntax.GetLocation());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryMapAttribute(
|
||||||
|
INamedTypeSymbol? attributeType,
|
||||||
|
SymbolCache symbols,
|
||||||
|
out ComponentKind componentKind,
|
||||||
|
out bool expectsCollection)
|
||||||
|
{
|
||||||
|
componentKind = default;
|
||||||
|
expectsCollection = false;
|
||||||
|
|
||||||
|
if (attributeType == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (SymbolEqualityComparer.Default.Equals(attributeType, symbols.GetModelAttribute))
|
||||||
|
{
|
||||||
|
componentKind = ComponentKind.Model;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SymbolEqualityComparer.Default.Equals(attributeType, symbols.GetModelsAttribute))
|
||||||
|
{
|
||||||
|
componentKind = ComponentKind.Model;
|
||||||
|
expectsCollection = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SymbolEqualityComparer.Default.Equals(attributeType, symbols.GetSystemAttribute))
|
||||||
|
{
|
||||||
|
componentKind = ComponentKind.System;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SymbolEqualityComparer.Default.Equals(attributeType, symbols.GetSystemsAttribute))
|
||||||
|
{
|
||||||
|
componentKind = ComponentKind.System;
|
||||||
|
expectsCollection = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SymbolEqualityComparer.Default.Equals(attributeType, symbols.GetUtilityAttribute))
|
||||||
|
{
|
||||||
|
componentKind = ComponentKind.Utility;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SymbolEqualityComparer.Default.Equals(attributeType, symbols.GetUtilitiesAttribute))
|
||||||
|
{
|
||||||
|
componentKind = ComponentKind.Utility;
|
||||||
|
expectsCollection = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsSupportedGetInvocationTarget(
|
||||||
|
IMethodSymbol targetMethod,
|
||||||
|
SymbolCache symbols)
|
||||||
|
{
|
||||||
|
if (targetMethod.ContainingType == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (SymbolEqualityComparer.Default.Equals(targetMethod.ContainingType, symbols.ContextAwareServiceExtensions))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return SymbolHelpers.IsAssignableTo(targetMethod.ContainingType, symbols.IArchitectureContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static INamedTypeSymbol? TryGetCollectionElementType(
|
||||||
|
ITypeSymbol fieldType,
|
||||||
|
SymbolCache symbols)
|
||||||
|
{
|
||||||
|
if (fieldType is not INamedTypeSymbol namedType)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (!SymbolEqualityComparer.Default.Equals(namedType.OriginalDefinition, symbols.IReadOnlyList))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return namedType.TypeArguments[0] as INamedTypeSymbol;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly IReadOnlyDictionary<string, ComponentKind> _contextAwareBindingNames =
|
||||||
|
new Dictionary<string, ComponentKind>(StringComparer.Ordinal)
|
||||||
|
{
|
||||||
|
["GetModel"] = ComponentKind.Model,
|
||||||
|
["GetModels"] = ComponentKind.Model,
|
||||||
|
["GetSystem"] = ComponentKind.System,
|
||||||
|
["GetSystems"] = ComponentKind.System,
|
||||||
|
["GetUtility"] = ComponentKind.Utility,
|
||||||
|
["GetUtilities"] = ComponentKind.Utility
|
||||||
|
};
|
||||||
|
|
||||||
|
private enum ComponentKind
|
||||||
|
{
|
||||||
|
Model,
|
||||||
|
System,
|
||||||
|
Utility
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly record struct BindingRequest(
|
||||||
|
ComponentKind Kind,
|
||||||
|
INamedTypeSymbol OwnerType,
|
||||||
|
INamedTypeSymbol ServiceType,
|
||||||
|
Location Location);
|
||||||
|
|
||||||
|
private sealed class SymbolCache
|
||||||
|
{
|
||||||
|
private SymbolCache(
|
||||||
|
INamedTypeSymbol? architectureType,
|
||||||
|
INamedTypeSymbol? iArchitectureType,
|
||||||
|
INamedTypeSymbol? iArchitectureModuleType,
|
||||||
|
INamedTypeSymbol? iArchitectureContextType,
|
||||||
|
INamedTypeSymbol? iReadOnlyListType,
|
||||||
|
INamedTypeSymbol? contextAwareServiceExtensionsType,
|
||||||
|
INamedTypeSymbol? getModelAttribute,
|
||||||
|
INamedTypeSymbol? getModelsAttribute,
|
||||||
|
INamedTypeSymbol? getSystemAttribute,
|
||||||
|
INamedTypeSymbol? getSystemsAttribute,
|
||||||
|
INamedTypeSymbol? getUtilityAttribute,
|
||||||
|
INamedTypeSymbol? getUtilitiesAttribute)
|
||||||
|
{
|
||||||
|
ArchitectureType = architectureType;
|
||||||
|
IArchitectureType = iArchitectureType;
|
||||||
|
IArchitectureModuleType = iArchitectureModuleType;
|
||||||
|
IArchitectureContext = iArchitectureContextType;
|
||||||
|
IReadOnlyList = iReadOnlyListType;
|
||||||
|
ContextAwareServiceExtensions = contextAwareServiceExtensionsType;
|
||||||
|
GetModelAttribute = getModelAttribute;
|
||||||
|
GetModelsAttribute = getModelsAttribute;
|
||||||
|
GetSystemAttribute = getSystemAttribute;
|
||||||
|
GetSystemsAttribute = getSystemsAttribute;
|
||||||
|
GetUtilityAttribute = getUtilityAttribute;
|
||||||
|
GetUtilitiesAttribute = getUtilitiesAttribute;
|
||||||
|
}
|
||||||
|
|
||||||
|
public INamedTypeSymbol? ArchitectureType { get; }
|
||||||
|
|
||||||
|
public INamedTypeSymbol? IArchitectureType { get; }
|
||||||
|
|
||||||
|
public INamedTypeSymbol? IArchitectureModuleType { get; }
|
||||||
|
|
||||||
|
public INamedTypeSymbol? IArchitectureContext { get; }
|
||||||
|
|
||||||
|
public INamedTypeSymbol? IReadOnlyList { get; }
|
||||||
|
|
||||||
|
public INamedTypeSymbol? ContextAwareServiceExtensions { get; }
|
||||||
|
|
||||||
|
public INamedTypeSymbol? GetModelAttribute { get; }
|
||||||
|
|
||||||
|
public INamedTypeSymbol? GetModelsAttribute { get; }
|
||||||
|
|
||||||
|
public INamedTypeSymbol? GetSystemAttribute { get; }
|
||||||
|
|
||||||
|
public INamedTypeSymbol? GetSystemsAttribute { get; }
|
||||||
|
|
||||||
|
public INamedTypeSymbol? GetUtilityAttribute { get; }
|
||||||
|
|
||||||
|
public INamedTypeSymbol? GetUtilitiesAttribute { get; }
|
||||||
|
|
||||||
|
public bool IsReady =>
|
||||||
|
ArchitectureType != null &&
|
||||||
|
IArchitectureType != null &&
|
||||||
|
IArchitectureModuleType != null &&
|
||||||
|
IArchitectureContext != null &&
|
||||||
|
IReadOnlyList != null &&
|
||||||
|
ContextAwareServiceExtensions != null &&
|
||||||
|
GetModelAttribute != null &&
|
||||||
|
GetModelsAttribute != null &&
|
||||||
|
GetSystemAttribute != null &&
|
||||||
|
GetSystemsAttribute != null &&
|
||||||
|
GetUtilityAttribute != null &&
|
||||||
|
GetUtilitiesAttribute != null;
|
||||||
|
|
||||||
|
public static SymbolCache Create(Compilation compilation)
|
||||||
|
{
|
||||||
|
return new SymbolCache(
|
||||||
|
compilation.GetTypeByMetadataName($"{PathContests.CoreNamespace}.Architectures.Architecture"),
|
||||||
|
compilation.GetTypeByMetadataName($"{PathContests.CoreAbstractionsNamespace}.Architectures.IArchitecture"),
|
||||||
|
compilation.GetTypeByMetadataName($"{PathContests.CoreAbstractionsNamespace}.Architectures.IArchitectureModule"),
|
||||||
|
compilation.GetTypeByMetadataName($"{PathContests.CoreAbstractionsNamespace}.Architectures.IArchitectureContext"),
|
||||||
|
compilation.GetTypeByMetadataName("System.Collections.Generic.IReadOnlyList`1"),
|
||||||
|
compilation.GetTypeByMetadataName($"{PathContests.CoreNamespace}.Extensions.ContextAwareServiceExtensions"),
|
||||||
|
compilation.GetTypeByMetadataName($"{PathContests.SourceGeneratorsAbstractionsPath}.Rule.GetModelAttribute"),
|
||||||
|
compilation.GetTypeByMetadataName($"{PathContests.SourceGeneratorsAbstractionsPath}.Rule.GetModelsAttribute"),
|
||||||
|
compilation.GetTypeByMetadataName($"{PathContests.SourceGeneratorsAbstractionsPath}.Rule.GetSystemAttribute"),
|
||||||
|
compilation.GetTypeByMetadataName($"{PathContests.SourceGeneratorsAbstractionsPath}.Rule.GetSystemsAttribute"),
|
||||||
|
compilation.GetTypeByMetadataName($"{PathContests.SourceGeneratorsAbstractionsPath}.Rule.GetUtilityAttribute"),
|
||||||
|
compilation.GetTypeByMetadataName($"{PathContests.SourceGeneratorsAbstractionsPath}.Rule.GetUtilitiesAttribute"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class RegistrationIndex
|
||||||
|
{
|
||||||
|
private RegistrationIndex(
|
||||||
|
Compilation compilation,
|
||||||
|
IReadOnlyDictionary<INamedTypeSymbol, ArchitectureRegistrationData> registrations)
|
||||||
|
{
|
||||||
|
_compilation = compilation;
|
||||||
|
_registrations = registrations;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly Compilation _compilation;
|
||||||
|
private readonly IReadOnlyDictionary<INamedTypeSymbol, ArchitectureRegistrationData> _registrations;
|
||||||
|
|
||||||
|
public static RegistrationIndex Build(
|
||||||
|
Compilation compilation,
|
||||||
|
SymbolCache symbols)
|
||||||
|
{
|
||||||
|
var registrations = new Dictionary<INamedTypeSymbol, ArchitectureRegistrationData>(SymbolEqualityComparer.Default);
|
||||||
|
|
||||||
|
foreach (var type in SymbolHelpers.EnumerateNamedTypes(compilation.Assembly.GlobalNamespace))
|
||||||
|
{
|
||||||
|
if (!SymbolHelpers.IsAssignableTo(type, symbols.ArchitectureType))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (type.IsAbstract)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var data = AnalyzeArchitecture(compilation, symbols, type);
|
||||||
|
if (data.IsEmpty)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
registrations[type] = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RegistrationIndex(compilation, registrations);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetOwningArchitecture(
|
||||||
|
INamedTypeSymbol ownerType,
|
||||||
|
out INamedTypeSymbol architectureType)
|
||||||
|
{
|
||||||
|
architectureType = default!;
|
||||||
|
|
||||||
|
if (_registrations.ContainsKey(ownerType))
|
||||||
|
{
|
||||||
|
architectureType = ownerType;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
INamedTypeSymbol? candidate = null;
|
||||||
|
foreach (var pair in _registrations)
|
||||||
|
{
|
||||||
|
if (!pair.Value.ContainsOwner(ownerType, _compilation))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (candidate != null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
candidate = pair.Key;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (candidate == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
architectureType = candidate;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasRegistration(
|
||||||
|
INamedTypeSymbol architectureType,
|
||||||
|
ComponentKind componentKind,
|
||||||
|
INamedTypeSymbol serviceType)
|
||||||
|
{
|
||||||
|
if (!_registrations.TryGetValue(architectureType, out var data))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return data.HasRegistration(componentKind, serviceType, _compilation);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ArchitectureRegistrationData AnalyzeArchitecture(
|
||||||
|
Compilation compilation,
|
||||||
|
SymbolCache symbols,
|
||||||
|
INamedTypeSymbol architectureType)
|
||||||
|
{
|
||||||
|
var data = new ArchitectureRegistrationData();
|
||||||
|
var visitedMethods = new HashSet<IMethodSymbol>(SymbolEqualityComparer.Default);
|
||||||
|
var pendingMethods = new Queue<IMethodSymbol>();
|
||||||
|
var visitedModules = new HashSet<INamedTypeSymbol>(SymbolEqualityComparer.Default);
|
||||||
|
|
||||||
|
foreach (var rootMethod in GetArchitectureRootMethods(architectureType))
|
||||||
|
pendingMethods.Enqueue(rootMethod);
|
||||||
|
|
||||||
|
while (pendingMethods.Count > 0)
|
||||||
|
{
|
||||||
|
var method = pendingMethods.Dequeue();
|
||||||
|
if (!visitedMethods.Add(method))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
foreach (var invocation in SymbolHelpers.GetInvocationOperations(method, compilation))
|
||||||
|
{
|
||||||
|
if (TryGetRegistration(invocation, symbols, out var kind, out var registeredType))
|
||||||
|
{
|
||||||
|
data.Add(kind, registeredType);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryGetInstalledModuleType(invocation, symbols, out var moduleType) &&
|
||||||
|
visitedModules.Add(moduleType))
|
||||||
|
{
|
||||||
|
AnalyzeModule(compilation, symbols, moduleType, data, visitedModules);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryResolveArchitectureHelperMethod(invocation.TargetMethod, architectureType, out var helperMethod))
|
||||||
|
pendingMethods.Enqueue(helperMethod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AnalyzeModule(
|
||||||
|
Compilation compilation,
|
||||||
|
SymbolCache symbols,
|
||||||
|
INamedTypeSymbol moduleType,
|
||||||
|
ArchitectureRegistrationData data,
|
||||||
|
ISet<INamedTypeSymbol> visitedModules)
|
||||||
|
{
|
||||||
|
var visitedMethods = new HashSet<IMethodSymbol>(SymbolEqualityComparer.Default);
|
||||||
|
var pendingMethods = new Queue<IMethodSymbol>();
|
||||||
|
|
||||||
|
foreach (var rootMethod in GetModuleRootMethods(moduleType))
|
||||||
|
pendingMethods.Enqueue(rootMethod);
|
||||||
|
|
||||||
|
while (pendingMethods.Count > 0)
|
||||||
|
{
|
||||||
|
var method = pendingMethods.Dequeue();
|
||||||
|
if (!visitedMethods.Add(method))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
foreach (var invocation in SymbolHelpers.GetInvocationOperations(method, compilation))
|
||||||
|
{
|
||||||
|
if (TryGetRegistration(invocation, symbols, out var kind, out var registeredType))
|
||||||
|
{
|
||||||
|
data.Add(kind, registeredType);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryGetInstalledModuleType(invocation, symbols, out var nestedModuleType) &&
|
||||||
|
visitedModules.Add(nestedModuleType))
|
||||||
|
{
|
||||||
|
AnalyzeModule(compilation, symbols, nestedModuleType, data, visitedModules);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryResolveModuleHelperMethod(invocation.TargetMethod, moduleType, out var helperMethod))
|
||||||
|
pendingMethods.Enqueue(helperMethod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<IMethodSymbol> GetArchitectureRootMethods(INamedTypeSymbol architectureType)
|
||||||
|
{
|
||||||
|
foreach (var type in SymbolHelpers.EnumerateTypeHierarchy(architectureType))
|
||||||
|
{
|
||||||
|
foreach (var member in type.GetMembers())
|
||||||
|
{
|
||||||
|
if (member is not IMethodSymbol method)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (method.IsStatic)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (method.Name is not ("OnInitialize" or "InstallModules"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (method.DeclaringSyntaxReferences.Length == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
yield return method;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<IMethodSymbol> GetModuleRootMethods(INamedTypeSymbol moduleType)
|
||||||
|
{
|
||||||
|
foreach (var type in SymbolHelpers.EnumerateTypeHierarchy(moduleType))
|
||||||
|
{
|
||||||
|
foreach (var member in type.GetMembers("Install").OfType<IMethodSymbol>())
|
||||||
|
{
|
||||||
|
if (member.IsStatic || member.Parameters.Length != 1)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (member.DeclaringSyntaxReferences.Length == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
yield return member;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryGetInstalledModuleType(
|
||||||
|
IInvocationOperation invocation,
|
||||||
|
SymbolCache symbols,
|
||||||
|
out INamedTypeSymbol moduleType)
|
||||||
|
{
|
||||||
|
moduleType = default!;
|
||||||
|
|
||||||
|
if (invocation.Arguments.Length == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (invocation.TargetMethod.Name is not ("InstallModule" or "InstallGodotModule"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var candidateType = SymbolHelpers.TryGetCreatedType(invocation.Arguments[0].Value);
|
||||||
|
if (candidateType == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!SymbolHelpers.IsAssignableTo(candidateType, symbols.IArchitectureModuleType))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
moduleType = candidateType;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryGetRegistration(
|
||||||
|
IInvocationOperation invocation,
|
||||||
|
SymbolCache symbols,
|
||||||
|
out ComponentKind componentKind,
|
||||||
|
out INamedTypeSymbol registeredType)
|
||||||
|
{
|
||||||
|
componentKind = default;
|
||||||
|
registeredType = default!;
|
||||||
|
|
||||||
|
var targetMethod = invocation.TargetMethod;
|
||||||
|
if (!targetMethod.IsGenericMethod || targetMethod.TypeArguments.Length != 1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (targetMethod.TypeArguments[0] is not INamedTypeSymbol namedType)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (targetMethod.Name == "RegisterModel" &&
|
||||||
|
SymbolHelpers.IsAssignableTo(targetMethod.ContainingType, symbols.IArchitectureType))
|
||||||
|
{
|
||||||
|
componentKind = ComponentKind.Model;
|
||||||
|
registeredType = namedType;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetMethod.Name == "RegisterSystem" &&
|
||||||
|
SymbolHelpers.IsAssignableTo(targetMethod.ContainingType, symbols.IArchitectureType))
|
||||||
|
{
|
||||||
|
componentKind = ComponentKind.System;
|
||||||
|
registeredType = namedType;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetMethod.Name == "RegisterUtility" &&
|
||||||
|
SymbolHelpers.IsAssignableTo(targetMethod.ContainingType, symbols.IArchitectureType))
|
||||||
|
{
|
||||||
|
componentKind = ComponentKind.Utility;
|
||||||
|
registeredType = namedType;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryResolveArchitectureHelperMethod(
|
||||||
|
IMethodSymbol targetMethod,
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryResolveModuleHelperMethod(
|
||||||
|
IMethodSymbol targetMethod,
|
||||||
|
INamedTypeSymbol moduleType,
|
||||||
|
out IMethodSymbol helperMethod)
|
||||||
|
{
|
||||||
|
helperMethod = default!;
|
||||||
|
|
||||||
|
if (targetMethod.MethodKind is not (MethodKind.Ordinary or MethodKind.Constructor))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!SymbolHelpers.IsWithinTypeHierarchy(targetMethod.ContainingType, moduleType))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
helperMethod = targetMethod.DeclaringSyntaxReferences.Length > 0 && !targetMethod.IsAbstract
|
||||||
|
? targetMethod
|
||||||
|
: SymbolHelpers.ResolveHierarchyMethodImplementation(targetMethod, moduleType) ?? targetMethod;
|
||||||
|
return helperMethod.DeclaringSyntaxReferences.Length > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class ArchitectureRegistrationData
|
||||||
|
{
|
||||||
|
private readonly HashSet<INamedTypeSymbol> _models = new(SymbolEqualityComparer.Default);
|
||||||
|
private readonly HashSet<INamedTypeSymbol> _systems = new(SymbolEqualityComparer.Default);
|
||||||
|
private readonly HashSet<INamedTypeSymbol> _utilities = new(SymbolEqualityComparer.Default);
|
||||||
|
|
||||||
|
public bool IsEmpty => _models.Count == 0 && _systems.Count == 0 && _utilities.Count == 0;
|
||||||
|
|
||||||
|
public void Add(ComponentKind componentKind, INamedTypeSymbol registeredType)
|
||||||
|
{
|
||||||
|
GetCollection(componentKind).Add(registeredType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasRegistration(
|
||||||
|
ComponentKind componentKind,
|
||||||
|
INamedTypeSymbol requestedType,
|
||||||
|
Compilation compilation)
|
||||||
|
{
|
||||||
|
return GetCollection(componentKind).Any(candidate =>
|
||||||
|
SymbolHelpers.IsServiceCompatible(candidate, requestedType, compilation));
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ContainsOwner(
|
||||||
|
INamedTypeSymbol ownerType,
|
||||||
|
Compilation compilation)
|
||||||
|
{
|
||||||
|
return _models.Any(candidate => SymbolHelpers.IsOwnershipCompatible(ownerType, candidate, compilation)) ||
|
||||||
|
_systems.Any(candidate => SymbolHelpers.IsOwnershipCompatible(ownerType, candidate, compilation)) ||
|
||||||
|
_utilities.Any(candidate => SymbolHelpers.IsOwnershipCompatible(ownerType, candidate, compilation));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ISet<INamedTypeSymbol> GetCollection(ComponentKind componentKind)
|
||||||
|
{
|
||||||
|
return componentKind switch
|
||||||
|
{
|
||||||
|
ComponentKind.Model => _models,
|
||||||
|
ComponentKind.System => _systems,
|
||||||
|
ComponentKind.Utility => _utilities,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(componentKind))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class SymbolHelpers
|
||||||
|
{
|
||||||
|
public static IEnumerable<INamedTypeSymbol> EnumerateNamedTypes(INamespaceSymbol namespaceSymbol)
|
||||||
|
{
|
||||||
|
foreach (var namespaceMember in namespaceSymbol.GetNamespaceMembers())
|
||||||
|
{
|
||||||
|
foreach (var nestedType in EnumerateNamedTypes(namespaceMember))
|
||||||
|
yield return nestedType;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var typeMember in namespaceSymbol.GetTypeMembers())
|
||||||
|
{
|
||||||
|
yield return typeMember;
|
||||||
|
|
||||||
|
foreach (var nestedType in EnumerateNestedTypes(typeMember))
|
||||||
|
yield return nestedType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<INamedTypeSymbol> EnumerateTypeHierarchy(INamedTypeSymbol type)
|
||||||
|
{
|
||||||
|
for (var current = type; current != null; current = current.BaseType)
|
||||||
|
yield return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsAssignableTo(ITypeSymbol? fromType, ITypeSymbol? toType)
|
||||||
|
{
|
||||||
|
if (fromType == null || toType == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (SymbolEqualityComparer.Default.Equals(fromType, toType))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return fromType.AllInterfaces.Any(interfaceType =>
|
||||||
|
SymbolEqualityComparer.Default.Equals(interfaceType, toType)) ||
|
||||||
|
EnumerateBaseTypes(fromType).Any(baseType =>
|
||||||
|
SymbolEqualityComparer.Default.Equals(baseType, toType));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsWithinTypeHierarchy(
|
||||||
|
INamedTypeSymbol? candidateType,
|
||||||
|
INamedTypeSymbol ownerType)
|
||||||
|
{
|
||||||
|
if (candidateType == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return EnumerateTypeHierarchy(ownerType).Any(type =>
|
||||||
|
SymbolEqualityComparer.Default.Equals(type, candidateType));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IMethodSymbol? ResolveHierarchyMethodImplementation(
|
||||||
|
IMethodSymbol method,
|
||||||
|
INamedTypeSymbol ownerType)
|
||||||
|
{
|
||||||
|
foreach (var type in EnumerateTypeHierarchy(ownerType))
|
||||||
|
{
|
||||||
|
foreach (var candidate in type.GetMembers(method.Name).OfType<IMethodSymbol>())
|
||||||
|
{
|
||||||
|
if (!HasCompatibleSignature(candidate, method))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (SymbolEqualityComparer.Default.Equals(candidate.OriginalDefinition, method.OriginalDefinition))
|
||||||
|
return candidate;
|
||||||
|
|
||||||
|
for (var overridden = candidate.OverriddenMethod; overridden != null; overridden = overridden.OverriddenMethod)
|
||||||
|
{
|
||||||
|
if (SymbolEqualityComparer.Default.Equals(overridden.OriginalDefinition, method.OriginalDefinition))
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<IInvocationOperation> GetInvocationOperations(
|
||||||
|
IMethodSymbol method,
|
||||||
|
Compilation compilation)
|
||||||
|
{
|
||||||
|
foreach (var syntaxReference in method.DeclaringSyntaxReferences)
|
||||||
|
{
|
||||||
|
var syntax = syntaxReference.GetSyntax();
|
||||||
|
var semanticModel = compilation.GetSemanticModel(syntax.SyntaxTree);
|
||||||
|
var operation = syntax switch
|
||||||
|
{
|
||||||
|
MethodDeclarationSyntax { Body: not null } methodDeclaration =>
|
||||||
|
semanticModel.GetOperation(methodDeclaration.Body),
|
||||||
|
MethodDeclarationSyntax { ExpressionBody: not null } methodDeclaration =>
|
||||||
|
semanticModel.GetOperation(methodDeclaration.ExpressionBody.Expression),
|
||||||
|
ConstructorDeclarationSyntax { Body: not null } constructorDeclaration =>
|
||||||
|
semanticModel.GetOperation(constructorDeclaration.Body),
|
||||||
|
ConstructorDeclarationSyntax { ExpressionBody: not null } constructorDeclaration =>
|
||||||
|
semanticModel.GetOperation(constructorDeclaration.ExpressionBody.Expression),
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
|
||||||
|
if (operation == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
foreach (var invocation in operation.DescendantsAndSelf().OfType<IInvocationOperation>())
|
||||||
|
yield return invocation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static INamedTypeSymbol? TryGetCreatedType(IOperation operation)
|
||||||
|
{
|
||||||
|
var current = operation;
|
||||||
|
|
||||||
|
while (current is IConversionOperation conversionOperation)
|
||||||
|
current = conversionOperation.Operand;
|
||||||
|
|
||||||
|
return current switch
|
||||||
|
{
|
||||||
|
IObjectCreationOperation objectCreationOperation => objectCreationOperation.Type as INamedTypeSymbol,
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsServiceCompatible(
|
||||||
|
INamedTypeSymbol registeredType,
|
||||||
|
INamedTypeSymbol requestedType,
|
||||||
|
Compilation compilation)
|
||||||
|
{
|
||||||
|
if (IsAssignableTo(registeredType, requestedType))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return compilation.ClassifyConversion(registeredType, requestedType).IsImplicit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsOwnershipCompatible(
|
||||||
|
INamedTypeSymbol ownerType,
|
||||||
|
INamedTypeSymbol registeredType,
|
||||||
|
Compilation compilation)
|
||||||
|
{
|
||||||
|
if (IsAssignableTo(ownerType, registeredType) || IsAssignableTo(registeredType, ownerType))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return compilation.ClassifyConversion(ownerType, registeredType).IsImplicit ||
|
||||||
|
compilation.ClassifyConversion(registeredType, ownerType).IsImplicit;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<INamedTypeSymbol> EnumerateNestedTypes(INamedTypeSymbol type)
|
||||||
|
{
|
||||||
|
foreach (var nestedType in type.GetTypeMembers())
|
||||||
|
{
|
||||||
|
yield return nestedType;
|
||||||
|
|
||||||
|
foreach (var childType in EnumerateNestedTypes(nestedType))
|
||||||
|
yield return childType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<INamedTypeSymbol> EnumerateBaseTypes(ITypeSymbol type)
|
||||||
|
{
|
||||||
|
for (var current = type.BaseType; current != null; current = current.BaseType)
|
||||||
|
yield return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool HasCompatibleSignature(
|
||||||
|
IMethodSymbol candidate,
|
||||||
|
IMethodSymbol target)
|
||||||
|
{
|
||||||
|
if (candidate.Parameters.Length != target.Parameters.Length)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (candidate.TypeParameters.Length != target.TypeParameters.Length)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
using GFramework.SourceGenerators.Common.Constants;
|
||||||
|
|
||||||
|
namespace GFramework.SourceGenerators.Diagnostics;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 提供 Context Get 注册可见性分析相关诊断。
|
||||||
|
/// </summary>
|
||||||
|
public static class ContextRegistrationDiagnostics
|
||||||
|
{
|
||||||
|
private const string SourceGeneratorsRuleCategory = $"{PathContests.SourceGeneratorsPath}.Rule";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当模型使用点在所属架构中找不到静态可见注册时报告。
|
||||||
|
/// </summary>
|
||||||
|
public static readonly DiagnosticDescriptor ModelRegistrationMissing = new(
|
||||||
|
"GF_ContextRegistration_001",
|
||||||
|
"Model usage has no statically discoverable registration",
|
||||||
|
"Model '{0}' used by '{1}' is not statically registered in architecture '{2}'",
|
||||||
|
SourceGeneratorsRuleCategory,
|
||||||
|
DiagnosticSeverity.Warning,
|
||||||
|
true);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当系统使用点在所属架构中找不到静态可见注册时报告。
|
||||||
|
/// </summary>
|
||||||
|
public static readonly DiagnosticDescriptor SystemRegistrationMissing = new(
|
||||||
|
"GF_ContextRegistration_002",
|
||||||
|
"System usage has no statically discoverable registration",
|
||||||
|
"System '{0}' used by '{1}' is not statically registered in architecture '{2}'",
|
||||||
|
SourceGeneratorsRuleCategory,
|
||||||
|
DiagnosticSeverity.Warning,
|
||||||
|
true);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当工具使用点在所属架构中找不到静态可见注册时报告。
|
||||||
|
/// </summary>
|
||||||
|
public static readonly DiagnosticDescriptor UtilityRegistrationMissing = new(
|
||||||
|
"GF_ContextRegistration_003",
|
||||||
|
"Utility usage has no statically discoverable registration",
|
||||||
|
"Utility '{0}' used by '{1}' is not statically registered in architecture '{2}'",
|
||||||
|
SourceGeneratorsRuleCategory,
|
||||||
|
DiagnosticSeverity.Warning,
|
||||||
|
true);
|
||||||
|
}
|
||||||
@ -50,3 +50,14 @@ public partial class InventoryPanel
|
|||||||
|
|
||||||
`[GetAll]` 会跳过 `const`、`static` 和 `readonly` 字段。若某个字段本来会被 `[GetAll]` 推断为
|
`[GetAll]` 会跳过 `const`、`static` 和 `readonly` 字段。若某个字段本来会被 `[GetAll]` 推断为
|
||||||
`Model`、`System` 或 `Utility` 绑定,但因为是不可赋值的 `static` 或 `readonly` 字段而被跳过,生成器会发出警告提示该字段不会参与生成。
|
`Model`、`System` 或 `Utility` 绑定,但因为是不可赋值的 `static` 或 `readonly` 字段而被跳过,生成器会发出警告提示该字段不会参与生成。
|
||||||
|
|
||||||
|
## 注册分析器
|
||||||
|
|
||||||
|
包现在同时包含一个注册可见性分析器,用于检查 `Model`、`System`、`Utility` 的使用点是否能在所属架构中找到静态可见注册。
|
||||||
|
|
||||||
|
- 覆盖字段特性注入:`[GetModel]`、`[GetModels]`、`[GetSystem]`、`[GetSystems]`、`[GetUtility]`、`[GetUtilities]`
|
||||||
|
- 覆盖手写调用:`GetModel<T>()`、`GetModels<T>()`、`GetSystem<T>()`、`GetSystems<T>()`、`GetUtility<T>()`、`GetUtilities<T>()`
|
||||||
|
- 默认报告 `Warning`
|
||||||
|
- 当前只分析静态可见的注册路径,例如 `OnInitialize()`、`InstallModules()`、`InstallModule(new Module())`
|
||||||
|
|
||||||
|
对于反射、运行时条件分支、外部程序集动态注册等路径,分析器不会强行推断;当无法唯一确定组件所属架构时,也会选择不报,优先降低误报。
|
||||||
|
|||||||
@ -621,6 +621,33 @@ public partial class GameController
|
|||||||
|
|
||||||
如果构造函数执行时上下文尚未建立,过早注入会失败;即使在构造函数中调用了注入方法,也不要在调用之前访问这些字段。
|
如果构造函数执行时上下文尚未建立,过早注入会失败;即使在构造函数中调用了注入方法,也不要在调用之前访问这些字段。
|
||||||
|
|
||||||
|
## 注册可见性分析
|
||||||
|
|
||||||
|
除了生成注入方法,`GFramework.SourceGenerators` 现在还会分析 `Model`、`System`、`Utility` 的使用点是否存在静态可见注册。
|
||||||
|
|
||||||
|
当前支持的使用点:
|
||||||
|
|
||||||
|
- 字段特性注入:`[GetModel]`、`[GetModels]`、`[GetSystem]`、`[GetSystems]`、`[GetUtility]`、`[GetUtilities]`
|
||||||
|
- 手写调用:`this.GetModel<T>()`、`this.GetSystem<T>()`、`this.GetUtility<T>()`
|
||||||
|
|
||||||
|
当前支持的注册来源:
|
||||||
|
|
||||||
|
- `Architecture.OnInitialize()` 中的直接 `RegisterModel/RegisterSystem/RegisterUtility`
|
||||||
|
- `InstallModules()` 中的直接注册
|
||||||
|
- `InstallModule(new SomeModule())` 和 `InstallGodotModule(new SomeModule())` 可静态展开到 `module.Install(...)` 的直接注册
|
||||||
|
|
||||||
|
分析器会在能够唯一定位所属架构时,对缺失注册报告 `Warning`:
|
||||||
|
|
||||||
|
- `GF_ContextRegistration_001`:`Model` 使用点未找到静态可见注册
|
||||||
|
- `GF_ContextRegistration_002`:`System` 使用点未找到静态可见注册
|
||||||
|
- `GF_ContextRegistration_003`:`Utility` 使用点未找到静态可见注册
|
||||||
|
|
||||||
|
这个分析器的目标是提供稳定提示,而不是完整模拟运行时 DI 图。以下场景默认不做强行推断:
|
||||||
|
|
||||||
|
- 运行时条件分支控制的注册
|
||||||
|
- 反射、配置驱动或外部程序集动态注册
|
||||||
|
- 无法唯一判定组件归属架构的多架构场景
|
||||||
|
|
||||||
## 高级场景
|
## 高级场景
|
||||||
|
|
||||||
### 泛型类型支持
|
### 泛型类型支持
|
||||||
|
|||||||
@ -611,9 +611,12 @@ public enum ConflictEnum
|
|||||||
| `GF_ContextGet_001` | 嵌套类不支持生成注入 | 将目标类型提取为顶层类 |
|
| `GF_ContextGet_001` | 嵌套类不支持生成注入 | 将目标类型提取为顶层类 |
|
||||||
| `GF_ContextGet_002` | 注入字段不能为 `static` | 改为实例字段 |
|
| `GF_ContextGet_002` | 注入字段不能为 `static` | 改为实例字段 |
|
||||||
| `GF_ContextGet_003` | 注入字段不能为 `readonly` | 移除 `readonly` |
|
| `GF_ContextGet_003` | 注入字段不能为 `readonly` | 移除 `readonly` |
|
||||||
| `GF_ContextGet_004` | 字段类型与注入特性不匹配 | 使用符合特性约束的字段类型 |
|
| `GF_ContextGet_004` | 字段类型与注入特性不匹配 | 使用符合特性约束的字段类型 |
|
||||||
| `GF_ContextGet_005` | 目标类型必须具备上下文访问能力 | 添加 `[ContextAware]`、实现 `IContextAware` 或继承 `ContextAwareBase` |
|
| `GF_ContextGet_005` | 目标类型必须具备上下文访问能力 | 添加 `[ContextAware]`、实现 `IContextAware` 或继承 `ContextAwareBase` |
|
||||||
| `GF_ContextGet_006` | 同一字段不能声明多个注入特性 | 每个字段只保留一个注入特性 |
|
| `GF_ContextGet_006` | 同一字段不能声明多个注入特性 | 每个字段只保留一个注入特性 |
|
||||||
|
| `GF_ContextRegistration_001` | `Model` 使用点没有静态可见注册 | 在所属架构的初始化链路中显式注册对应 `Model` |
|
||||||
|
| `GF_ContextRegistration_002` | `System` 使用点没有静态可见注册 | 在所属架构的初始化链路中显式注册对应 `System` |
|
||||||
|
| `GF_ContextRegistration_003` | `Utility` 使用点没有静态可见注册 | 在所属架构的初始化链路中显式注册对应 `Utility` |
|
||||||
|
|
||||||
## 性能优势
|
## 性能优势
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user