mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-25 21:34:28 +08:00
feat(generator): 添加优先级源代码生成器和相关分析器
- 新增 PriorityGenerator 源生成器,自动生成 IPrioritized 接口实现 - 添加 PriorityAttribute 特性,用于标记类的优先级值 - 实现 PriorityUsageAnalyzer 分析器,检测优先级使用建议 - 添加预定义的 PriorityGroup 常量,提供标准优先级分组 - 在 AnalyzerReleases.Unshipped.md 中注册新的诊断规则 - 更新项目依赖,升级 Meziantou.Analyzer 和 Polyfill 版本 - 为测试项目添加源生成器项目引用 - 添加 PriorityGenerator 的快照测试用例
This commit is contained in:
parent
f8c39e46a5
commit
330bd8b0b0
71
GFramework.Core.Abstractions/bases/PriorityGroup.cs
Normal file
71
GFramework.Core.Abstractions/bases/PriorityGroup.cs
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
namespace GFramework.Core.Abstractions.bases;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 预定义的优先级分组常量
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 提供标准化的优先级值,用于统一管理系统、服务等组件的执行顺序。
|
||||||
|
/// 优先级值越小,优先级越高(负数表示高优先级)。
|
||||||
|
/// </remarks>
|
||||||
|
public static class PriorityGroup
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 关键优先级 - 最高优先级,用于核心系统和基础设施
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 适用场景:
|
||||||
|
/// - 日志系统
|
||||||
|
/// - 配置管理
|
||||||
|
/// - IoC 容器初始化
|
||||||
|
/// - 架构核心组件
|
||||||
|
/// </remarks>
|
||||||
|
public const int Critical = -100;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 高优先级 - 用于重要但非核心的系统
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 适用场景:
|
||||||
|
/// - 事件总线
|
||||||
|
/// - 资源管理器
|
||||||
|
/// - 输入系统
|
||||||
|
/// - 网络管理器
|
||||||
|
/// </remarks>
|
||||||
|
public const int High = -50;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 普通优先级 - 默认优先级
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 适用场景:
|
||||||
|
/// - 游戏逻辑系统
|
||||||
|
/// - UI 系统
|
||||||
|
/// - 音频系统
|
||||||
|
/// - 大部分业务逻辑
|
||||||
|
/// </remarks>
|
||||||
|
public const int Normal = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 低优先级 - 用于非关键系统
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 适用场景:
|
||||||
|
/// - 统计系统
|
||||||
|
/// - 调试工具
|
||||||
|
/// - 性能监控
|
||||||
|
/// - 辅助功能
|
||||||
|
/// </remarks>
|
||||||
|
public const int Low = 50;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 延迟优先级 - 最低优先级,用于可延迟执行的系统
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 适用场景:
|
||||||
|
/// - 分析和遥测
|
||||||
|
/// - 后台数据同步
|
||||||
|
/// - 缓存清理
|
||||||
|
/// - 非紧急任务
|
||||||
|
/// </remarks>
|
||||||
|
public const int Deferred = 100;
|
||||||
|
}
|
||||||
@ -19,6 +19,9 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\GFramework.Core\GFramework.Core.csproj"/>
|
<ProjectReference Include="..\GFramework.Core\GFramework.Core.csproj"/>
|
||||||
|
<ProjectReference Include="..\GFramework.SourceGenerators.Abstractions\GFramework.SourceGenerators.Abstractions.csproj"/>
|
||||||
|
<ProjectReference Include="..\GFramework.SourceGenerators.Common\GFramework.SourceGenerators.Common.csproj"/>
|
||||||
|
<ProjectReference Include="..\GFramework.SourceGenerators\GFramework.SourceGenerators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@ -0,0 +1,35 @@
|
|||||||
|
namespace GFramework.SourceGenerators.Abstractions.bases;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 标记类的优先级,自动生成 <see cref="GFramework.Core.Abstractions.bases.IPrioritized"/> 接口实现
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 使用此特性可以避免手动实现 IPrioritized 接口。
|
||||||
|
/// 优先级值越小,优先级越高(负数表示高优先级)。
|
||||||
|
/// </remarks>
|
||||||
|
/// <example>
|
||||||
|
/// <code>
|
||||||
|
/// [Priority(10)]
|
||||||
|
/// public partial class MySystem : AbstractSystem
|
||||||
|
/// {
|
||||||
|
/// // 自动生成: public int Priority => 10;
|
||||||
|
/// }
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||||
|
public sealed class PriorityAttribute : Attribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 初始化 <see cref="PriorityAttribute"/> 类的新实例
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">优先级值,越小优先级越高</param>
|
||||||
|
public PriorityAttribute(int value)
|
||||||
|
{
|
||||||
|
Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取优先级值
|
||||||
|
/// </summary>
|
||||||
|
public int Value { get; }
|
||||||
|
}
|
||||||
@ -30,6 +30,7 @@
|
|||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
<PackageReference Include="System.Collections.Immutable" Version="9.0.0"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AdditionalFiles Include="AnalyzerReleases.Shipped.md"/>
|
<AdditionalFiles Include="AnalyzerReleases.Shipped.md"/>
|
||||||
|
|||||||
@ -0,0 +1,266 @@
|
|||||||
|
using System.IO;
|
||||||
|
using GFramework.SourceGenerators.bases;
|
||||||
|
using GFramework.SourceGenerators.Tests.core;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace GFramework.SourceGenerators.Tests.bases;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Priority 生成器快照测试类
|
||||||
|
/// </summary>
|
||||||
|
[TestFixture]
|
||||||
|
public class PriorityGeneratorSnapshotTests
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 测试基本的 Priority 特性生成
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public async Task Snapshot_BasicPriority()
|
||||||
|
{
|
||||||
|
const string source = """
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace GFramework.SourceGenerators.Abstractions.bases
|
||||||
|
{
|
||||||
|
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||||
|
public sealed class PriorityAttribute : Attribute
|
||||||
|
{
|
||||||
|
public int Value { get; }
|
||||||
|
public PriorityAttribute(int value) { Value = value; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace GFramework.Core.Abstractions.bases
|
||||||
|
{
|
||||||
|
public interface IPrioritized
|
||||||
|
{
|
||||||
|
int Priority { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace TestApp
|
||||||
|
{
|
||||||
|
using GFramework.SourceGenerators.Abstractions.bases;
|
||||||
|
|
||||||
|
[Priority(10)]
|
||||||
|
public partial class MySystem
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
await GeneratorSnapshotTest<PriorityGenerator>.RunAsync(
|
||||||
|
source,
|
||||||
|
Path.Combine(
|
||||||
|
TestContext.CurrentContext.TestDirectory,
|
||||||
|
"bases",
|
||||||
|
"snapshots",
|
||||||
|
"PriorityGenerator",
|
||||||
|
"BasicPriority"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 测试负数优先级
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public async Task Snapshot_NegativePriority()
|
||||||
|
{
|
||||||
|
const string source = """
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace GFramework.SourceGenerators.Abstractions.bases
|
||||||
|
{
|
||||||
|
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||||
|
public sealed class PriorityAttribute : Attribute
|
||||||
|
{
|
||||||
|
public int Value { get; }
|
||||||
|
public PriorityAttribute(int value) { Value = value; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace GFramework.Core.Abstractions.bases
|
||||||
|
{
|
||||||
|
public interface IPrioritized
|
||||||
|
{
|
||||||
|
int Priority { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace TestApp
|
||||||
|
{
|
||||||
|
using GFramework.SourceGenerators.Abstractions.bases;
|
||||||
|
|
||||||
|
[Priority(-100)]
|
||||||
|
public partial class CriticalSystem
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
await GeneratorSnapshotTest<PriorityGenerator>.RunAsync(
|
||||||
|
source,
|
||||||
|
Path.Combine(
|
||||||
|
TestContext.CurrentContext.TestDirectory,
|
||||||
|
"bases",
|
||||||
|
"snapshots",
|
||||||
|
"PriorityGenerator",
|
||||||
|
"NegativePriority"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 测试使用 PriorityGroup 枚举
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public async Task Snapshot_PriorityGroup()
|
||||||
|
{
|
||||||
|
const string source = """
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace GFramework.SourceGenerators.Abstractions.bases
|
||||||
|
{
|
||||||
|
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||||
|
public sealed class PriorityAttribute : Attribute
|
||||||
|
{
|
||||||
|
public int Value { get; }
|
||||||
|
public PriorityAttribute(int value) { Value = value; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace GFramework.Core.Abstractions.bases
|
||||||
|
{
|
||||||
|
public interface IPrioritized
|
||||||
|
{
|
||||||
|
int Priority { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class PriorityGroup
|
||||||
|
{
|
||||||
|
public const int Critical = -100;
|
||||||
|
public const int High = -50;
|
||||||
|
public const int Normal = 0;
|
||||||
|
public const int Low = 50;
|
||||||
|
public const int Deferred = 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace TestApp
|
||||||
|
{
|
||||||
|
using GFramework.SourceGenerators.Abstractions.bases;
|
||||||
|
using GFramework.Core.Abstractions.bases;
|
||||||
|
|
||||||
|
[Priority(PriorityGroup.High)]
|
||||||
|
public partial class HighPrioritySystem
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
await GeneratorSnapshotTest<PriorityGenerator>.RunAsync(
|
||||||
|
source,
|
||||||
|
Path.Combine(
|
||||||
|
TestContext.CurrentContext.TestDirectory,
|
||||||
|
"bases",
|
||||||
|
"snapshots",
|
||||||
|
"PriorityGenerator",
|
||||||
|
"PriorityGroup"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 测试泛型类支持
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public async Task Snapshot_GenericClass()
|
||||||
|
{
|
||||||
|
const string source = """
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace GFramework.SourceGenerators.Abstractions.bases
|
||||||
|
{
|
||||||
|
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||||
|
public sealed class PriorityAttribute : Attribute
|
||||||
|
{
|
||||||
|
public int Value { get; }
|
||||||
|
public PriorityAttribute(int value) { Value = value; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace GFramework.Core.Abstractions.bases
|
||||||
|
{
|
||||||
|
public interface IPrioritized
|
||||||
|
{
|
||||||
|
int Priority { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace TestApp
|
||||||
|
{
|
||||||
|
using GFramework.SourceGenerators.Abstractions.bases;
|
||||||
|
|
||||||
|
[Priority(20)]
|
||||||
|
public partial class GenericSystem<T>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
await GeneratorSnapshotTest<PriorityGenerator>.RunAsync(
|
||||||
|
source,
|
||||||
|
Path.Combine(
|
||||||
|
TestContext.CurrentContext.TestDirectory,
|
||||||
|
"bases",
|
||||||
|
"snapshots",
|
||||||
|
"PriorityGenerator",
|
||||||
|
"GenericClass"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 测试嵌套类支持
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public async Task Snapshot_NestedClass()
|
||||||
|
{
|
||||||
|
const string source = """
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace GFramework.SourceGenerators.Abstractions.bases
|
||||||
|
{
|
||||||
|
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||||
|
public sealed class PriorityAttribute : Attribute
|
||||||
|
{
|
||||||
|
public int Value { get; }
|
||||||
|
public PriorityAttribute(int value) { Value = value; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace GFramework.Core.Abstractions.bases
|
||||||
|
{
|
||||||
|
public interface IPrioritized
|
||||||
|
{
|
||||||
|
int Priority { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace TestApp
|
||||||
|
{
|
||||||
|
using GFramework.SourceGenerators.Abstractions.bases;
|
||||||
|
|
||||||
|
public class OuterClass
|
||||||
|
{
|
||||||
|
[Priority(30)]
|
||||||
|
public partial class NestedSystem
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
await GeneratorSnapshotTest<PriorityGenerator>.RunAsync(
|
||||||
|
source,
|
||||||
|
Path.Combine(
|
||||||
|
TestContext.CurrentContext.TestDirectory,
|
||||||
|
"bases",
|
||||||
|
"snapshots",
|
||||||
|
"PriorityGenerator",
|
||||||
|
"NestedClass"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,7 +3,12 @@
|
|||||||
|
|
||||||
### New Rules
|
### New Rules
|
||||||
|
|
||||||
Rule ID | Category | Severity | Notes
|
Rule ID | Category | Severity | Notes
|
||||||
----------------|----------------------------------|----------|------------------------
|
-----------------------|----------------------------------|----------|------------------------
|
||||||
GF_Logging_001 | GFramework.Godot.logging | Warning | LoggerDiagnostics
|
GF_Logging_001 | GFramework.Godot.logging | Warning | LoggerDiagnostics
|
||||||
GF_Rule_001 | GFramework.SourceGenerators.rule | Error | ContextAwareDiagnostic
|
GF_Rule_001 | GFramework.SourceGenerators.rule | Error | ContextAwareDiagnostic
|
||||||
|
GF_Priority_001 | GFramework.Priority | Error | PriorityDiagnostic
|
||||||
|
GF_Priority_002 | GFramework.Priority | Warning | PriorityDiagnostic
|
||||||
|
GF_Priority_003 | GFramework.Priority | Error | PriorityDiagnostic
|
||||||
|
GF_Priority_004 | GFramework.Priority | Error | PriorityDiagnostic
|
||||||
|
GF_Priority_Usage_001 | GFramework.Usage | Info | PriorityUsageAnalyzer
|
||||||
129
GFramework.SourceGenerators/analyzers/PriorityUsageAnalyzer.cs
Normal file
129
GFramework.SourceGenerators/analyzers/PriorityUsageAnalyzer.cs
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
using System.Collections.Immutable;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
|
using Microsoft.CodeAnalysis.Operations;
|
||||||
|
|
||||||
|
namespace GFramework.SourceGenerators.analyzers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 优先级使用分析器,检测应该使用 GetAllByPriority 而非 GetAll 的场景
|
||||||
|
/// </summary>
|
||||||
|
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||||
|
public sealed class PriorityUsageAnalyzer : DiagnosticAnalyzer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 诊断 ID
|
||||||
|
/// </summary>
|
||||||
|
private const string DiagnosticId = "GF_Priority_Usage_001";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 诊断规则
|
||||||
|
/// </summary>
|
||||||
|
private static readonly DiagnosticDescriptor Rule = new(
|
||||||
|
id: DiagnosticId,
|
||||||
|
title: "建议使用 GetAllByPriority",
|
||||||
|
messageFormat: "类型 '{0}' 实现了 IPrioritized 接口,建议使用 GetAllByPriority<{0}>() 而非 GetAll<{0}>()",
|
||||||
|
category: "GFramework.Usage",
|
||||||
|
defaultSeverity: DiagnosticSeverity.Info,
|
||||||
|
isEnabledByDefault: true,
|
||||||
|
description: "当获取实现了 IPrioritized 接口的服务时,应使用 GetAllByPriority 方法以确保按优先级排序。");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 支持的诊断规则
|
||||||
|
/// </summary>
|
||||||
|
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 初始化分析器
|
||||||
|
/// </summary>
|
||||||
|
public override void Initialize(AnalysisContext context)
|
||||||
|
{
|
||||||
|
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
|
||||||
|
context.EnableConcurrentExecution();
|
||||||
|
|
||||||
|
context.RegisterCompilationStartAction(compilationContext =>
|
||||||
|
{
|
||||||
|
// 缓存符号查找
|
||||||
|
var iPrioritized = compilationContext.Compilation.GetTypeByMetadataName(
|
||||||
|
"GFramework.Core.Abstractions.bases.IPrioritized");
|
||||||
|
|
||||||
|
if (iPrioritized == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var iocContainer = compilationContext.Compilation.GetTypeByMetadataName(
|
||||||
|
"GFramework.Core.Abstractions.ioc.IIocContainer");
|
||||||
|
|
||||||
|
var architectureContext = compilationContext.Compilation.GetTypeByMetadataName(
|
||||||
|
"GFramework.Core.Abstractions.architecture.IArchitectureContext");
|
||||||
|
|
||||||
|
compilationContext.RegisterOperationAction(
|
||||||
|
operationContext => AnalyzeInvocation(
|
||||||
|
operationContext,
|
||||||
|
iPrioritized,
|
||||||
|
iocContainer,
|
||||||
|
architectureContext),
|
||||||
|
OperationKind.Invocation);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 分析方法调用
|
||||||
|
/// </summary>
|
||||||
|
private static void AnalyzeInvocation(
|
||||||
|
OperationAnalysisContext context,
|
||||||
|
INamedTypeSymbol iPrioritized,
|
||||||
|
INamedTypeSymbol? iocContainer,
|
||||||
|
INamedTypeSymbol? architectureContext)
|
||||||
|
{
|
||||||
|
var invocation = (IInvocationOperation)context.Operation;
|
||||||
|
var method = invocation.TargetMethod;
|
||||||
|
|
||||||
|
// 检查方法名是否为 GetAll
|
||||||
|
if (method.Name != "GetAll")
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 检查是否为泛型方法
|
||||||
|
if (!method.IsGenericMethod || method.TypeArguments.Length != 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 检查方法来源
|
||||||
|
var containingType = method.ContainingType;
|
||||||
|
if (iocContainer != null && SymbolEqualityComparer.Default.Equals(containingType, iocContainer))
|
||||||
|
{
|
||||||
|
// 来自 IIocContainer
|
||||||
|
}
|
||||||
|
else if (architectureContext != null &&
|
||||||
|
SymbolEqualityComparer.Default.Equals(containingType, architectureContext))
|
||||||
|
{
|
||||||
|
// 来自 IArchitectureContext
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查泛型参数是否实现了 IPrioritized
|
||||||
|
var typeArgument = method.TypeArguments[0];
|
||||||
|
if (typeArgument is not INamedTypeSymbol namedType)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!ImplementsInterface(namedType, iPrioritized))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 报告诊断
|
||||||
|
var diagnostic = Diagnostic.Create(
|
||||||
|
Rule,
|
||||||
|
invocation.Syntax.GetLocation(),
|
||||||
|
typeArgument.ToDisplayString());
|
||||||
|
|
||||||
|
context.ReportDiagnostic(diagnostic);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 检查类型是否实现了指定接口
|
||||||
|
/// </summary>
|
||||||
|
private static bool ImplementsInterface(INamedTypeSymbol type, INamedTypeSymbol interfaceType)
|
||||||
|
{
|
||||||
|
return type.AllInterfaces.Any(i => SymbolEqualityComparer.Default.Equals(i, interfaceType));
|
||||||
|
}
|
||||||
|
}
|
||||||
134
GFramework.SourceGenerators/bases/PriorityGenerator.cs
Normal file
134
GFramework.SourceGenerators/bases/PriorityGenerator.cs
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
using System.Text;
|
||||||
|
using GFramework.SourceGenerators.Common.constants;
|
||||||
|
using GFramework.SourceGenerators.Common.generator;
|
||||||
|
using GFramework.SourceGenerators.diagnostics;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
|
||||||
|
namespace GFramework.SourceGenerators.bases;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Priority 特性生成器,为标记了 [Priority] 的类自动生成 IPrioritized 接口实现
|
||||||
|
/// </summary>
|
||||||
|
[Generator]
|
||||||
|
public sealed class PriorityGenerator : MetadataAttributeClassGeneratorBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取特性的元数据名称
|
||||||
|
/// </summary>
|
||||||
|
protected override string AttributeMetadataName =>
|
||||||
|
$"{PathContests.SourceGeneratorsAbstractionsPath}.bases.PriorityAttribute";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取特性的短名称(不包含后缀)
|
||||||
|
/// </summary>
|
||||||
|
protected override string AttributeShortNameWithoutSuffix => "Priority";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 验证符号是否符合生成条件
|
||||||
|
/// </summary>
|
||||||
|
protected override bool ValidateSymbol(
|
||||||
|
SourceProductionContext context,
|
||||||
|
Compilation compilation,
|
||||||
|
ClassDeclarationSyntax syntax,
|
||||||
|
INamedTypeSymbol symbol,
|
||||||
|
AttributeData attr)
|
||||||
|
{
|
||||||
|
// 1. 必须是 class
|
||||||
|
if (symbol.TypeKind != TypeKind.Class)
|
||||||
|
{
|
||||||
|
context.ReportDiagnostic(Diagnostic.Create(
|
||||||
|
PriorityDiagnostic.OnlyApplyToClass,
|
||||||
|
syntax.Identifier.GetLocation(),
|
||||||
|
symbol.ToDisplayString()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 必须是 partial
|
||||||
|
if (!syntax.Modifiers.Any(SyntaxKind.PartialKeyword))
|
||||||
|
{
|
||||||
|
context.ReportDiagnostic(Diagnostic.Create(
|
||||||
|
PriorityDiagnostic.MustBePartial,
|
||||||
|
syntax.Identifier.GetLocation(),
|
||||||
|
symbol.Name));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 检查是否已手动实现 IPrioritized
|
||||||
|
var iPrioritized = compilation.GetTypeByMetadataName(
|
||||||
|
$"{PathContests.CoreAbstractionsNamespace}.bases.IPrioritized");
|
||||||
|
|
||||||
|
if (iPrioritized != null && symbol.AllInterfaces.Contains(iPrioritized, SymbolEqualityComparer.Default))
|
||||||
|
{
|
||||||
|
context.ReportDiagnostic(Diagnostic.Create(
|
||||||
|
PriorityDiagnostic.AlreadyImplemented,
|
||||||
|
syntax.Identifier.GetLocation(),
|
||||||
|
symbol.Name));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 验证特性参数
|
||||||
|
if (attr.ConstructorArguments.Length == 0 ||
|
||||||
|
attr.ConstructorArguments[0].Value is not int)
|
||||||
|
{
|
||||||
|
context.ReportDiagnostic(Diagnostic.Create(
|
||||||
|
PriorityDiagnostic.InvalidValue,
|
||||||
|
syntax.Identifier.GetLocation()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 生成源代码
|
||||||
|
/// </summary>
|
||||||
|
protected override string Generate(
|
||||||
|
SourceProductionContext context,
|
||||||
|
Compilation compilation,
|
||||||
|
INamedTypeSymbol symbol,
|
||||||
|
AttributeData attr)
|
||||||
|
{
|
||||||
|
var ns = symbol.ContainingNamespace.IsGlobalNamespace
|
||||||
|
? null
|
||||||
|
: symbol.ContainingNamespace.ToDisplayString();
|
||||||
|
|
||||||
|
var priorityValue = (int)attr.ConstructorArguments[0].Value!;
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.AppendLine("// <auto-generated/>");
|
||||||
|
sb.AppendLine("#nullable enable");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
if (ns is not null)
|
||||||
|
{
|
||||||
|
sb.AppendLine($"namespace {ns};");
|
||||||
|
sb.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成泛型参数(如果有)
|
||||||
|
var typeParameters = symbol.TypeParameters.Length > 0
|
||||||
|
? $"<{string.Join(", ", symbol.TypeParameters.Select(tp => tp.Name))}>"
|
||||||
|
: string.Empty;
|
||||||
|
|
||||||
|
sb.AppendLine(
|
||||||
|
$"partial class {symbol.Name}{typeParameters} : global::GFramework.Core.Abstractions.bases.IPrioritized");
|
||||||
|
sb.AppendLine("{");
|
||||||
|
sb.AppendLine(" /// <summary>");
|
||||||
|
sb.AppendLine($" /// 获取优先级值: {priorityValue}");
|
||||||
|
sb.AppendLine(" /// </summary>");
|
||||||
|
sb.AppendLine($" public int Priority => {priorityValue};");
|
||||||
|
sb.AppendLine("}");
|
||||||
|
|
||||||
|
return sb.ToString().TrimEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取生成文件的提示名称
|
||||||
|
/// </summary>
|
||||||
|
protected override string GetHintName(INamedTypeSymbol symbol)
|
||||||
|
{
|
||||||
|
return $"{symbol.Name}.Priority.g.cs";
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,63 @@
|
|||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
|
||||||
|
namespace GFramework.SourceGenerators.diagnostics;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Priority 特性相关的诊断信息
|
||||||
|
/// </summary>
|
||||||
|
internal static class PriorityDiagnostic
|
||||||
|
{
|
||||||
|
private const string Category = "GFramework.Priority";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// GF_Priority_001: Priority 特性只能应用于类
|
||||||
|
/// </summary>
|
||||||
|
public static readonly DiagnosticDescriptor OnlyApplyToClass = new(
|
||||||
|
id: "GF_Priority_001",
|
||||||
|
title: "Priority 特性只能应用于类",
|
||||||
|
messageFormat: "Priority 特性只能应用于类,不能应用于 '{0}'",
|
||||||
|
category: Category,
|
||||||
|
defaultSeverity: DiagnosticSeverity.Error,
|
||||||
|
isEnabledByDefault: true,
|
||||||
|
description: "Priority 特性设计用于类级别的优先级标记,不支持其他类型。"
|
||||||
|
);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// GF_Priority_002: 类已手动实现 IPrioritized 接口
|
||||||
|
/// </summary>
|
||||||
|
public static readonly DiagnosticDescriptor AlreadyImplemented = new(
|
||||||
|
id: "GF_Priority_002",
|
||||||
|
title: "类已实现 IPrioritized 接口",
|
||||||
|
messageFormat: "类 '{0}' 已手动实现 IPrioritized 接口,将跳过自动生成",
|
||||||
|
category: Category,
|
||||||
|
defaultSeverity: DiagnosticSeverity.Warning,
|
||||||
|
isEnabledByDefault: true,
|
||||||
|
description: "当类已经手动实现 IPrioritized 接口时,源生成器将跳过代码生成以避免冲突。"
|
||||||
|
);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// GF_Priority_003: 类必须声明为 partial
|
||||||
|
/// </summary>
|
||||||
|
public static readonly DiagnosticDescriptor MustBePartial = new(
|
||||||
|
id: "GF_Priority_003",
|
||||||
|
title: "类必须声明为 partial",
|
||||||
|
messageFormat: "类 '{0}' 使用了 Priority 特性,必须声明为 partial",
|
||||||
|
category: Category,
|
||||||
|
defaultSeverity: DiagnosticSeverity.Error,
|
||||||
|
isEnabledByDefault: true,
|
||||||
|
description: "源生成器需要在 partial 类中生成 IPrioritized 接口实现。"
|
||||||
|
);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// GF_Priority_004: Priority 值缺失或无效
|
||||||
|
/// </summary>
|
||||||
|
public static readonly DiagnosticDescriptor InvalidValue = new(
|
||||||
|
id: "GF_Priority_004",
|
||||||
|
title: "Priority 值无效",
|
||||||
|
messageFormat: "Priority 特性的值无效或缺失",
|
||||||
|
category: Category,
|
||||||
|
defaultSeverity: DiagnosticSeverity.Error,
|
||||||
|
isEnabledByDefault: true,
|
||||||
|
description: "Priority 特性必须提供一个有效的整数值。"
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user