From 40a9b523f54b3e63531fc22313d64a0b49dcae7a Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Sat, 27 Dec 2025 13:04:01 +0800 Subject: [PATCH] =?UTF-8?q?feat(rule):=20=E6=B7=BB=E5=8A=A0=E4=B8=8A?= =?UTF-8?q?=E4=B8=8B=E6=96=87=E6=84=9F=E7=9F=A5=E8=AF=8A=E6=96=AD=E8=A7=84?= =?UTF-8?q?=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 GF_Rule_001 诊断规则,要求类必须实现 IContextAware 接口 - 创建 ContextAwareDiagnostic 类定义诊断规则元数据 - 修改 ContextAwareGenerator 实现 IContextAware 接口检查 - 优化生成器代码结构,添加候选类查找和输出生成功能 - 更新 AnalyzerReleases.Unshipped.md 文档 - 调整测试代码以适配新的诊断规则 - 修复日志诊断规则的命名空间大小写错误 --- .../rule/ContextAwareGeneratorTests.cs | 10 +- .../AnalyzerReleases.Unshipped.md | 7 +- .../logging/LoggerDiagnostic.cs | 2 +- .../rule/ContextAwareDiagnostic.cs | 28 ++++++ .../rule/ContextAwareGenerator.cs | 94 +++++++++++-------- GFramework.sln.DotSettings.user | 8 +- 6 files changed, 94 insertions(+), 55 deletions(-) create mode 100644 GFramework.SourceGenerators/rule/ContextAwareDiagnostic.cs diff --git a/GFramework.SourceGenerators.Tests/rule/ContextAwareGeneratorTests.cs b/GFramework.SourceGenerators.Tests/rule/ContextAwareGeneratorTests.cs index dec09f2..ba1b837 100644 --- a/GFramework.SourceGenerators.Tests/rule/ContextAwareGeneratorTests.cs +++ b/GFramework.SourceGenerators.Tests/rule/ContextAwareGeneratorTests.cs @@ -42,14 +42,6 @@ public class ContextAwareGeneratorTests """; const string frameworkStub = """ - namespace GFramework.Core.rule - { - public interface IContextAware - { - void SetContext(GFramework.Core.architecture.IArchitectureContext context); - } - } - namespace GFramework.Core.architecture { public interface IArchitectureContext {} @@ -61,7 +53,7 @@ public class ContextAwareGeneratorTests #nullable enable namespace TestApp; - partial class MyRule : GFramework.Core.rule.IContextAware + partial class MyRule { protected GFramework.Core.architecture.IArchitectureContext Context { get; private set; } = null!; void GFramework.Core.rule.IContextAware.SetContext( diff --git a/GFramework.SourceGenerators/AnalyzerReleases.Unshipped.md b/GFramework.SourceGenerators/AnalyzerReleases.Unshipped.md index 0ba5463..db19379 100644 --- a/GFramework.SourceGenerators/AnalyzerReleases.Unshipped.md +++ b/GFramework.SourceGenerators/AnalyzerReleases.Unshipped.md @@ -3,6 +3,7 @@ ### New Rules - Rule ID | Category | Severity | Notes -----------------|--------------------------|----------|------------------- - GF_Logging_001 | GFramework.Godot.Logging | Warning | LoggerDiagnostics \ 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 \ No newline at end of file diff --git a/GFramework.SourceGenerators/logging/LoggerDiagnostic.cs b/GFramework.SourceGenerators/logging/LoggerDiagnostic.cs index 8a2d945..743ddc9 100644 --- a/GFramework.SourceGenerators/logging/LoggerDiagnostic.cs +++ b/GFramework.SourceGenerators/logging/LoggerDiagnostic.cs @@ -15,7 +15,7 @@ internal static class LoggerDiagnostics "GF_Logging_001", "LogAttribute cannot generate Logger", "LogAttribute on class '{0}' is ineffective: {1}", - "GFramework.Godot.Logging", + "GFramework.Godot.logging", DiagnosticSeverity.Warning, true); } \ No newline at end of file diff --git a/GFramework.SourceGenerators/rule/ContextAwareDiagnostic.cs b/GFramework.SourceGenerators/rule/ContextAwareDiagnostic.cs new file mode 100644 index 0000000..fcf58ff --- /dev/null +++ b/GFramework.SourceGenerators/rule/ContextAwareDiagnostic.cs @@ -0,0 +1,28 @@ +using Microsoft.CodeAnalysis; + +namespace GFramework.SourceGenerators.rule; + +/// +/// 提供与上下文感知相关的诊断规则定义 +/// +public static class ContextAwareDiagnostic +{ + /// + /// 定义类必须实现IContextAware接口的诊断规则 + /// + /// + /// 诊断ID: GF_Rule_001 + /// 诊断类别: GFramework.SourceGenerators.rule + /// 严重级别: 错误 + /// 启用状态: true + /// 消息格式: "Class '{0}' must implement IContextAware" + /// + public static readonly DiagnosticDescriptor ClassMustImplementIContextAware = new( + "GF_Rule_001", + "Class must implement IContextAware", + "Class '{0}' must implement IContextAware", + "GFramework.SourceGenerators.rule", + DiagnosticSeverity.Error, + true + ); +} \ No newline at end of file diff --git a/GFramework.SourceGenerators/rule/ContextAwareGenerator.cs b/GFramework.SourceGenerators/rule/ContextAwareGenerator.cs index 0f005c9..180e823 100644 --- a/GFramework.SourceGenerators/rule/ContextAwareGenerator.cs +++ b/GFramework.SourceGenerators/rule/ContextAwareGenerator.cs @@ -15,75 +15,87 @@ public sealed class ContextAwareGenerator : IIncrementalGenerator public void Initialize(IncrementalGeneratorInitializationContext context) { - // 1. 找到所有 class 声明 - var classDeclarations = context.SyntaxProvider + // 1️⃣ 查找候选类 + var candidates = context.SyntaxProvider .CreateSyntaxProvider( predicate: static (node, _) => node is ClassDeclarationSyntax, transform: static (ctx, _) => GetCandidate(ctx) ) .Where(static s => s is not null); - // 2. 生成代码 - context.RegisterSourceOutput( - classDeclarations, - static (spc, source) => Generate(spc, source!) - ); + // 2️⃣ 注册生成输出 + context.RegisterSourceOutput(candidates, static (spc, symbol) => + { + if (symbol != null) + GenerateOutput(spc, symbol); + }); } + #region 候选类查找 + private static INamedTypeSymbol? GetCandidate(GeneratorSyntaxContext context) { - var classDecl = (ClassDeclarationSyntax)context.Node; - - if (classDecl.AttributeLists.Count == 0) + if (context.SemanticModel.GetDeclaredSymbol(context.Node) is not INamedTypeSymbol symbol) return null; - if (context.SemanticModel.GetDeclaredSymbol(classDecl) - is not { } symbol) - return null; + // 仅筛选带有 ContextAwareAttribute 的类 + var hasAttr = symbol.GetAttributes() + .Any(attr => attr.AttributeClass?.ToDisplayString() == AttributeMetadataName); - return Enumerable.Any(symbol.GetAttributes(), - attr => attr.AttributeClass?.ToDisplayString() == AttributeMetadataName) - ? symbol - : null; + return hasAttr ? symbol : null; } + #endregion - private static void Generate( - SourceProductionContext context, - INamedTypeSymbol symbol) + #region 输出生成 + 诊断 + + private static void GenerateOutput(SourceProductionContext context, INamedTypeSymbol symbol) { - var syntax = symbol.DeclaringSyntaxReferences - .FirstOrDefault()? - .GetSyntax() as ClassDeclarationSyntax; + var syntax = symbol.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax() as ClassDeclarationSyntax; + if (syntax == null) + return; - if (syntax is null || !syntax.Modifiers.Any(SyntaxKind.PartialKeyword)) + // 1️⃣ 必须是 partial + if (!syntax.Modifiers.Any(SyntaxKind.PartialKeyword)) { - context.ReportDiagnostic( - Diagnostic.Create( - CommonDiagnostics.ClassMustBePartial, - syntax?.Identifier.GetLocation(), - symbol.Name - ) - ); + context.ReportDiagnostic(Diagnostic.Create( + CommonDiagnostics.ClassMustBePartial, + syntax.Identifier.GetLocation(), + symbol.Name + )); return; } + // 2️⃣ 必须实现 IContextAware(直接或间接) + if (!symbol.AllInterfaces.Any(i => i.ToDisplayString() == "GFramework.Core.rule.IContextAware")) + { + context.ReportDiagnostic(Diagnostic.Create( + ContextAwareDiagnostic.ClassMustImplementIContextAware, + syntax.Identifier.GetLocation(), + symbol.Name + )); + return; + } + + // 3️⃣ 生成源码 var ns = symbol.ContainingNamespace.IsGlobalNamespace ? null : symbol.ContainingNamespace.ToDisplayString(); var source = GenerateSource(ns, symbol); - - context.AddSource( - $"{symbol.Name}.ContextAware.g.cs", - source - ); + context + .AddSource( + $"{symbol.Name}.ContextAware.g.cs", + source); } + #endregion + + #region 源码生成 + private static string GenerateSource(string? ns, INamedTypeSymbol symbol) { var sb = new StringBuilder(); - sb.AppendLine("// "); sb.AppendLine("#nullable enable"); @@ -93,20 +105,20 @@ public sealed class ContextAwareGenerator : IIncrementalGenerator sb.AppendLine(); } - sb.AppendLine($"partial class {symbol.Name} : GFramework.Core.rule.IContextAware"); + sb.AppendLine($"partial class {symbol.Name}"); sb.AppendLine("{"); - sb.AppendLine( " protected GFramework.Core.architecture.IArchitectureContext Context { get; private set; } = null!;"); - + sb.AppendLine(); sb.AppendLine(" void GFramework.Core.rule.IContextAware.SetContext("); sb.AppendLine(" GFramework.Core.architecture.IArchitectureContext context)"); sb.AppendLine(" {"); sb.AppendLine(" Context = context;"); sb.AppendLine(" }"); - sb.AppendLine("}"); return sb.ToString().TrimEnd(); } + + #endregion } \ No newline at end of file diff --git a/GFramework.sln.DotSettings.user b/GFramework.sln.DotSettings.user index 51ff59d..ed71e26 100644 --- a/GFramework.sln.DotSettings.user +++ b/GFramework.sln.DotSettings.user @@ -1,11 +1,17 @@  ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded - <SessionState ContinuousTestingMode="0" IsActive="True" Name="Generates_ContextAware_Code" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> + <SessionState ContinuousTestingMode="0" Name="Generates_ContextAware_Code" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> <TestAncestor> <TestId>NUnit3x::BB047F43-6AA0-4EA0-8AE9-E6B9784D9E8E::net8.0::GFramework.SourceGenerators.Tests.rule.ContextAwareGeneratorTests</TestId> </TestAncestor> +</SessionState> + <SessionState ContinuousTestingMode="0" IsActive="True" Name="Generates_ContextAware_Code" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> + <TestAncestor> + <TestId>NUnit3x::BB047F43-6AA0-4EA0-8AE9-E6B9784D9E8E::net8.0::GFramework.SourceGenerators.Tests.rule.ContextAwareGeneratorTests.Generates_ContextAware_Code</TestId> + </TestAncestor> </SessionState> \ No newline at end of file