mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-22 10:34:30 +08:00
feat(rule): 添加上下文感知诊断规则
- 新增 GF_Rule_001 诊断规则,要求类必须实现 IContextAware 接口 - 创建 ContextAwareDiagnostic 类定义诊断规则元数据 - 修改 ContextAwareGenerator 实现 IContextAware 接口检查 - 优化生成器代码结构,添加候选类查找和输出生成功能 - 更新 AnalyzerReleases.Unshipped.md 文档 - 调整测试代码以适配新的诊断规则 - 修复日志诊断规则的命名空间大小写错误
This commit is contained in:
parent
eebd7de409
commit
40a9b523f5
@ -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(
|
||||
|
||||
@ -4,5 +4,6 @@
|
||||
### New Rules
|
||||
|
||||
Rule ID | Category | Severity | Notes
|
||||
----------------|--------------------------|----------|-------------------
|
||||
----------------|----------------------------------|----------|------------------------
|
||||
GF_Logging_001 | GFramework.Godot.Logging | Warning | LoggerDiagnostics
|
||||
GF_Rule_001 | GFramework.SourceGenerators.rule | Error | ContextAwareDiagnostic
|
||||
@ -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);
|
||||
}
|
||||
28
GFramework.SourceGenerators/rule/ContextAwareDiagnostic.cs
Normal file
28
GFramework.SourceGenerators/rule/ContextAwareDiagnostic.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace GFramework.SourceGenerators.rule;
|
||||
|
||||
/// <summary>
|
||||
/// 提供与上下文感知相关的诊断规则定义
|
||||
/// </summary>
|
||||
public static class ContextAwareDiagnostic
|
||||
{
|
||||
/// <summary>
|
||||
/// 定义类必须实现IContextAware接口的诊断规则
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 诊断ID: GF_Rule_001
|
||||
/// 诊断类别: GFramework.SourceGenerators.rule
|
||||
/// 严重级别: 错误
|
||||
/// 启用状态: true
|
||||
/// 消息格式: "Class '{0}' must implement IContextAware"
|
||||
/// </remarks>
|
||||
public static readonly DiagnosticDescriptor ClassMustImplementIContextAware = new(
|
||||
"GF_Rule_001",
|
||||
"Class must implement IContextAware",
|
||||
"Class '{0}' must implement IContextAware",
|
||||
"GFramework.SourceGenerators.rule",
|
||||
DiagnosticSeverity.Error,
|
||||
true
|
||||
);
|
||||
}
|
||||
@ -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)
|
||||
{
|
||||
var syntax = symbol.DeclaringSyntaxReferences
|
||||
.FirstOrDefault()?
|
||||
.GetSyntax() as ClassDeclarationSyntax;
|
||||
#region 输出生成 + 诊断
|
||||
|
||||
if (syntax is null || !syntax.Modifiers.Any(SyntaxKind.PartialKeyword))
|
||||
private static void GenerateOutput(SourceProductionContext context, INamedTypeSymbol symbol)
|
||||
{
|
||||
context.ReportDiagnostic(
|
||||
Diagnostic.Create(
|
||||
var syntax = symbol.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax() as ClassDeclarationSyntax;
|
||||
if (syntax == null)
|
||||
return;
|
||||
|
||||
// 1️⃣ 必须是 partial
|
||||
if (!syntax.Modifiers.Any(SyntaxKind.PartialKeyword))
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(
|
||||
CommonDiagnostics.ClassMustBePartial,
|
||||
syntax?.Identifier.GetLocation(),
|
||||
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(
|
||||
context
|
||||
.AddSource(
|
||||
$"{symbol.Name}.ContextAware.g.cs",
|
||||
source
|
||||
);
|
||||
source);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 源码生成
|
||||
|
||||
private static string GenerateSource(string? ns, INamedTypeSymbol symbol)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.AppendLine("// <auto-generated/>");
|
||||
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
|
||||
}
|
||||
@ -1,11 +1,17 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAnalyzerTest_00601_002Ecs_002Fl_003AD_0021_003FTool_003FDevelopment_0020Tools_003FJetBrains_003F_002EJetBrains_003F_002ERider_003Fconfig_003Fresharper_002Dhost_003FSourcesCache_003Fe7a998cc7aa4af29968182b46ff292b3634fc9b5961db3d3d2a5291dc5a7843_003FAnalyzerTest_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADiagnostic_002Ecs_002Fl_003AD_0021_003FTool_003FDevelopment_0020Tools_003FJetBrains_003F_002EJetBrains_003F_002ERider_003Fconfig_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8fed4175e0a54d839582ab555761a2de4d5128_003Ff3_003Fd48d28bd_003FDiagnostic_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADiagnostic_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FLoacl_003F_002EJetBrains_003F_002ERider_003Fconfig_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8fed4175e0a54d839582ab555761a2de4d5128_003Ff3_003Fd48d28bd_003FDiagnostic_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AInputEventAction_002Ecs_002Fl_003AD_0021_003FTool_003FDevelopment_0020Tools_003FJetBrains_003F_002EJetBrains_003F_002ERider_003Fconfig_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F1c378f459c054fecaf4484a0fa6d44c055a800_003F18_003F33b52a1c_003FInputEventAction_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIVerifierExtensions_002Ecs_002Fl_003AD_0021_003FTool_003FDevelopment_0020Tools_003FJetBrains_003F_002EJetBrains_003F_002ERider_003Fconfig_003Fresharper_002Dhost_003FSourcesCache_003F3a7fb23d304bcd1dd4fcb38114e45d28a6a1446eb6b71c918bcb1d73a6cbf3_003FIVerifierExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASolutionState_002Ecs_002Fl_003AD_0021_003FTool_003FDevelopment_0020Tools_003FJetBrains_003F_002EJetBrains_003F_002ERider_003Fconfig_003Fresharper_002Dhost_003FSourcesCache_003F59c43162311a41dc4e9a06273ab3fb541e6a64318777591f77584020ff865_003FSolutionState_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=c33c8ad6_002Dd992_002D4817_002D855f_002D764f7c7897fa/@EntryIndexedValue"><SessionState ContinuousTestingMode="0" IsActive="True" Name="Generates_ContextAware_Code" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
|
||||
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=c33c8ad6_002Dd992_002D4817_002D855f_002D764f7c7897fa/@EntryIndexedValue"><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></s:String>
|
||||
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=cb493c10_002D1cc7_002D408b_002Db1eb_002D2e8a3ffd30ee/@EntryIndexedValue"><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></s:String></wpf:ResourceDictionary>
|
||||
Loading…
x
Reference in New Issue
Block a user