From 330bd8b0b067761174368d7e393b068da2f33301 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Thu, 5 Mar 2026 22:18:30 +0800 Subject: [PATCH] =?UTF-8?q?feat(generator):=20=E6=B7=BB=E5=8A=A0=E4=BC=98?= =?UTF-8?q?=E5=85=88=E7=BA=A7=E6=BA=90=E4=BB=A3=E7=A0=81=E7=94=9F=E6=88=90?= =?UTF-8?q?=E5=99=A8=E5=92=8C=E7=9B=B8=E5=85=B3=E5=88=86=E6=9E=90=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 PriorityGenerator 源生成器,自动生成 IPrioritized 接口实现 - 添加 PriorityAttribute 特性,用于标记类的优先级值 - 实现 PriorityUsageAnalyzer 分析器,检测优先级使用建议 - 添加预定义的 PriorityGroup 常量,提供标准优先级分组 - 在 AnalyzerReleases.Unshipped.md 中注册新的诊断规则 - 更新项目依赖,升级 Meziantou.Analyzer 和 Polyfill 版本 - 为测试项目添加源生成器项目引用 - 添加 PriorityGenerator 的快照测试用例 --- .../bases/PriorityGroup.cs | 71 +++++ .../GFramework.Core.Tests.csproj | 3 + .../bases/PriorityAttribute.cs | 35 +++ .../GFramework.SourceGenerators.Common.csproj | 1 + .../bases/PriorityGeneratorSnapshotTests.cs | 266 ++++++++++++++++++ .../AnalyzerReleases.Unshipped.md | 13 +- .../analyzers/PriorityUsageAnalyzer.cs | 129 +++++++++ .../bases/PriorityGenerator.cs | 134 +++++++++ .../diagnostics/PriorityDiagnostic.cs | 63 +++++ 9 files changed, 711 insertions(+), 4 deletions(-) create mode 100644 GFramework.Core.Abstractions/bases/PriorityGroup.cs create mode 100644 GFramework.SourceGenerators.Abstractions/bases/PriorityAttribute.cs create mode 100644 GFramework.SourceGenerators.Tests/bases/PriorityGeneratorSnapshotTests.cs create mode 100644 GFramework.SourceGenerators/analyzers/PriorityUsageAnalyzer.cs create mode 100644 GFramework.SourceGenerators/bases/PriorityGenerator.cs create mode 100644 GFramework.SourceGenerators/diagnostics/PriorityDiagnostic.cs diff --git a/GFramework.Core.Abstractions/bases/PriorityGroup.cs b/GFramework.Core.Abstractions/bases/PriorityGroup.cs new file mode 100644 index 0000000..cee876c --- /dev/null +++ b/GFramework.Core.Abstractions/bases/PriorityGroup.cs @@ -0,0 +1,71 @@ +namespace GFramework.Core.Abstractions.bases; + +/// +/// 预定义的优先级分组常量 +/// +/// +/// 提供标准化的优先级值,用于统一管理系统、服务等组件的执行顺序。 +/// 优先级值越小,优先级越高(负数表示高优先级)。 +/// +public static class PriorityGroup +{ + /// + /// 关键优先级 - 最高优先级,用于核心系统和基础设施 + /// + /// + /// 适用场景: + /// - 日志系统 + /// - 配置管理 + /// - IoC 容器初始化 + /// - 架构核心组件 + /// + public const int Critical = -100; + + /// + /// 高优先级 - 用于重要但非核心的系统 + /// + /// + /// 适用场景: + /// - 事件总线 + /// - 资源管理器 + /// - 输入系统 + /// - 网络管理器 + /// + public const int High = -50; + + /// + /// 普通优先级 - 默认优先级 + /// + /// + /// 适用场景: + /// - 游戏逻辑系统 + /// - UI 系统 + /// - 音频系统 + /// - 大部分业务逻辑 + /// + public const int Normal = 0; + + /// + /// 低优先级 - 用于非关键系统 + /// + /// + /// 适用场景: + /// - 统计系统 + /// - 调试工具 + /// - 性能监控 + /// - 辅助功能 + /// + public const int Low = 50; + + /// + /// 延迟优先级 - 最低优先级,用于可延迟执行的系统 + /// + /// + /// 适用场景: + /// - 分析和遥测 + /// - 后台数据同步 + /// - 缓存清理 + /// - 非紧急任务 + /// + public const int Deferred = 100; +} \ No newline at end of file diff --git a/GFramework.Core.Tests/GFramework.Core.Tests.csproj b/GFramework.Core.Tests/GFramework.Core.Tests.csproj index bd51047..eed0620 100644 --- a/GFramework.Core.Tests/GFramework.Core.Tests.csproj +++ b/GFramework.Core.Tests/GFramework.Core.Tests.csproj @@ -19,6 +19,9 @@ + + + diff --git a/GFramework.SourceGenerators.Abstractions/bases/PriorityAttribute.cs b/GFramework.SourceGenerators.Abstractions/bases/PriorityAttribute.cs new file mode 100644 index 0000000..8c2d399 --- /dev/null +++ b/GFramework.SourceGenerators.Abstractions/bases/PriorityAttribute.cs @@ -0,0 +1,35 @@ +namespace GFramework.SourceGenerators.Abstractions.bases; + +/// +/// 标记类的优先级,自动生成 接口实现 +/// +/// +/// 使用此特性可以避免手动实现 IPrioritized 接口。 +/// 优先级值越小,优先级越高(负数表示高优先级)。 +/// +/// +/// +/// [Priority(10)] +/// public partial class MySystem : AbstractSystem +/// { +/// // 自动生成: public int Priority => 10; +/// } +/// +/// +[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] +public sealed class PriorityAttribute : Attribute +{ + /// + /// 初始化 类的新实例 + /// + /// 优先级值,越小优先级越高 + public PriorityAttribute(int value) + { + Value = value; + } + + /// + /// 获取优先级值 + /// + public int Value { get; } +} \ No newline at end of file diff --git a/GFramework.SourceGenerators.Common/GFramework.SourceGenerators.Common.csproj b/GFramework.SourceGenerators.Common/GFramework.SourceGenerators.Common.csproj index 91eb394..52e5487 100644 --- a/GFramework.SourceGenerators.Common/GFramework.SourceGenerators.Common.csproj +++ b/GFramework.SourceGenerators.Common/GFramework.SourceGenerators.Common.csproj @@ -30,6 +30,7 @@ all runtime; build; native; contentfiles; analyzers + diff --git a/GFramework.SourceGenerators.Tests/bases/PriorityGeneratorSnapshotTests.cs b/GFramework.SourceGenerators.Tests/bases/PriorityGeneratorSnapshotTests.cs new file mode 100644 index 0000000..c5c7c56 --- /dev/null +++ b/GFramework.SourceGenerators.Tests/bases/PriorityGeneratorSnapshotTests.cs @@ -0,0 +1,266 @@ +using System.IO; +using GFramework.SourceGenerators.bases; +using GFramework.SourceGenerators.Tests.core; +using NUnit.Framework; + +namespace GFramework.SourceGenerators.Tests.bases; + +/// +/// Priority 生成器快照测试类 +/// +[TestFixture] +public class PriorityGeneratorSnapshotTests +{ + /// + /// 测试基本的 Priority 特性生成 + /// + [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.RunAsync( + source, + Path.Combine( + TestContext.CurrentContext.TestDirectory, + "bases", + "snapshots", + "PriorityGenerator", + "BasicPriority")); + } + + /// + /// 测试负数优先级 + /// + [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.RunAsync( + source, + Path.Combine( + TestContext.CurrentContext.TestDirectory, + "bases", + "snapshots", + "PriorityGenerator", + "NegativePriority")); + } + + /// + /// 测试使用 PriorityGroup 枚举 + /// + [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.RunAsync( + source, + Path.Combine( + TestContext.CurrentContext.TestDirectory, + "bases", + "snapshots", + "PriorityGenerator", + "PriorityGroup")); + } + + /// + /// 测试泛型类支持 + /// + [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 + { + } + } + """; + + await GeneratorSnapshotTest.RunAsync( + source, + Path.Combine( + TestContext.CurrentContext.TestDirectory, + "bases", + "snapshots", + "PriorityGenerator", + "GenericClass")); + } + + /// + /// 测试嵌套类支持 + /// + [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.RunAsync( + source, + Path.Combine( + TestContext.CurrentContext.TestDirectory, + "bases", + "snapshots", + "PriorityGenerator", + "NestedClass")); + } +} \ No newline at end of file diff --git a/GFramework.SourceGenerators/AnalyzerReleases.Unshipped.md b/GFramework.SourceGenerators/AnalyzerReleases.Unshipped.md index 4cf23a9..ce9dd59 100644 --- a/GFramework.SourceGenerators/AnalyzerReleases.Unshipped.md +++ b/GFramework.SourceGenerators/AnalyzerReleases.Unshipped.md @@ -3,7 +3,12 @@ ### New Rules - Rule ID | Category | Severity | Notes -----------------|----------------------------------|----------|------------------------ - GF_Logging_001 | GFramework.Godot.logging | Warning | LoggerDiagnostics - GF_Rule_001 | GFramework.SourceGenerators.rule | Error | ContextAwareDiagnostic \ No newline at end of file + Rule ID | Category | Severity | Notes +-----------------------|----------------------------------|----------|------------------------ + GF_Logging_001 | GFramework.Godot.logging | Warning | LoggerDiagnostics + 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 \ No newline at end of file diff --git a/GFramework.SourceGenerators/analyzers/PriorityUsageAnalyzer.cs b/GFramework.SourceGenerators/analyzers/PriorityUsageAnalyzer.cs new file mode 100644 index 0000000..37d82a7 --- /dev/null +++ b/GFramework.SourceGenerators/analyzers/PriorityUsageAnalyzer.cs @@ -0,0 +1,129 @@ +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; + +namespace GFramework.SourceGenerators.analyzers; + +/// +/// 优先级使用分析器,检测应该使用 GetAllByPriority 而非 GetAll 的场景 +/// +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class PriorityUsageAnalyzer : DiagnosticAnalyzer +{ + /// + /// 诊断 ID + /// + private const string DiagnosticId = "GF_Priority_Usage_001"; + + /// + /// 诊断规则 + /// + 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 方法以确保按优先级排序。"); + + /// + /// 支持的诊断规则 + /// + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + /// + /// 初始化分析器 + /// + 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); + }); + } + + /// + /// 分析方法调用 + /// + 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); + } + + /// + /// 检查类型是否实现了指定接口 + /// + private static bool ImplementsInterface(INamedTypeSymbol type, INamedTypeSymbol interfaceType) + { + return type.AllInterfaces.Any(i => SymbolEqualityComparer.Default.Equals(i, interfaceType)); + } +} \ No newline at end of file diff --git a/GFramework.SourceGenerators/bases/PriorityGenerator.cs b/GFramework.SourceGenerators/bases/PriorityGenerator.cs new file mode 100644 index 0000000..a509347 --- /dev/null +++ b/GFramework.SourceGenerators/bases/PriorityGenerator.cs @@ -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; + +/// +/// Priority 特性生成器,为标记了 [Priority] 的类自动生成 IPrioritized 接口实现 +/// +[Generator] +public sealed class PriorityGenerator : MetadataAttributeClassGeneratorBase +{ + /// + /// 获取特性的元数据名称 + /// + protected override string AttributeMetadataName => + $"{PathContests.SourceGeneratorsAbstractionsPath}.bases.PriorityAttribute"; + + /// + /// 获取特性的短名称(不包含后缀) + /// + protected override string AttributeShortNameWithoutSuffix => "Priority"; + + /// + /// 验证符号是否符合生成条件 + /// + 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; + } + + /// + /// 生成源代码 + /// + 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("// "); + 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(" /// "); + sb.AppendLine($" /// 获取优先级值: {priorityValue}"); + sb.AppendLine(" /// "); + sb.AppendLine($" public int Priority => {priorityValue};"); + sb.AppendLine("}"); + + return sb.ToString().TrimEnd(); + } + + /// + /// 获取生成文件的提示名称 + /// + protected override string GetHintName(INamedTypeSymbol symbol) + { + return $"{symbol.Name}.Priority.g.cs"; + } +} \ No newline at end of file diff --git a/GFramework.SourceGenerators/diagnostics/PriorityDiagnostic.cs b/GFramework.SourceGenerators/diagnostics/PriorityDiagnostic.cs new file mode 100644 index 0000000..f9fb2d7 --- /dev/null +++ b/GFramework.SourceGenerators/diagnostics/PriorityDiagnostic.cs @@ -0,0 +1,63 @@ +using Microsoft.CodeAnalysis; + +namespace GFramework.SourceGenerators.diagnostics; + +/// +/// Priority 特性相关的诊断信息 +/// +internal static class PriorityDiagnostic +{ + private const string Category = "GFramework.Priority"; + + /// + /// GF_Priority_001: Priority 特性只能应用于类 + /// + public static readonly DiagnosticDescriptor OnlyApplyToClass = new( + id: "GF_Priority_001", + title: "Priority 特性只能应用于类", + messageFormat: "Priority 特性只能应用于类,不能应用于 '{0}'", + category: Category, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: "Priority 特性设计用于类级别的优先级标记,不支持其他类型。" + ); + + /// + /// GF_Priority_002: 类已手动实现 IPrioritized 接口 + /// + public static readonly DiagnosticDescriptor AlreadyImplemented = new( + id: "GF_Priority_002", + title: "类已实现 IPrioritized 接口", + messageFormat: "类 '{0}' 已手动实现 IPrioritized 接口,将跳过自动生成", + category: Category, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: "当类已经手动实现 IPrioritized 接口时,源生成器将跳过代码生成以避免冲突。" + ); + + /// + /// GF_Priority_003: 类必须声明为 partial + /// + 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 接口实现。" + ); + + /// + /// GF_Priority_004: Priority 值缺失或无效 + /// + public static readonly DiagnosticDescriptor InvalidValue = new( + id: "GF_Priority_004", + title: "Priority 值无效", + messageFormat: "Priority 特性的值无效或缺失", + category: Category, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: "Priority 特性必须提供一个有效的整数值。" + ); +} \ No newline at end of file