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 = """
|
const string frameworkStub = """
|
||||||
namespace GFramework.Core.rule
|
|
||||||
{
|
|
||||||
public interface IContextAware
|
|
||||||
{
|
|
||||||
void SetContext(GFramework.Core.architecture.IArchitectureContext context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace GFramework.Core.architecture
|
namespace GFramework.Core.architecture
|
||||||
{
|
{
|
||||||
public interface IArchitectureContext {}
|
public interface IArchitectureContext {}
|
||||||
@ -61,7 +53,7 @@ public class ContextAwareGeneratorTests
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
namespace TestApp;
|
namespace TestApp;
|
||||||
|
|
||||||
partial class MyRule : GFramework.Core.rule.IContextAware
|
partial class MyRule
|
||||||
{
|
{
|
||||||
protected GFramework.Core.architecture.IArchitectureContext Context { get; private set; } = null!;
|
protected GFramework.Core.architecture.IArchitectureContext Context { get; private set; } = null!;
|
||||||
void GFramework.Core.rule.IContextAware.SetContext(
|
void GFramework.Core.rule.IContextAware.SetContext(
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
### 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
|
||||||
@ -15,7 +15,7 @@ internal static class LoggerDiagnostics
|
|||||||
"GF_Logging_001",
|
"GF_Logging_001",
|
||||||
"LogAttribute cannot generate Logger",
|
"LogAttribute cannot generate Logger",
|
||||||
"LogAttribute on class '{0}' is ineffective: {1}",
|
"LogAttribute on class '{0}' is ineffective: {1}",
|
||||||
"GFramework.Godot.Logging",
|
"GFramework.Godot.logging",
|
||||||
DiagnosticSeverity.Warning,
|
DiagnosticSeverity.Warning,
|
||||||
true);
|
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)
|
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||||
{
|
{
|
||||||
// 1. 找到所有 class 声明
|
// 1️⃣ 查找候选类
|
||||||
var classDeclarations = context.SyntaxProvider
|
var candidates = context.SyntaxProvider
|
||||||
.CreateSyntaxProvider(
|
.CreateSyntaxProvider(
|
||||||
predicate: static (node, _) => node is ClassDeclarationSyntax,
|
predicate: static (node, _) => node is ClassDeclarationSyntax,
|
||||||
transform: static (ctx, _) => GetCandidate(ctx)
|
transform: static (ctx, _) => GetCandidate(ctx)
|
||||||
)
|
)
|
||||||
.Where(static s => s is not null);
|
.Where(static s => s is not null);
|
||||||
|
|
||||||
// 2. 生成代码
|
// 2️⃣ 注册生成输出
|
||||||
context.RegisterSourceOutput(
|
context.RegisterSourceOutput(candidates, static (spc, symbol) =>
|
||||||
classDeclarations,
|
{
|
||||||
static (spc, source) => Generate(spc, source!)
|
if (symbol != null)
|
||||||
);
|
GenerateOutput(spc, symbol);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region 候选类查找
|
||||||
|
|
||||||
private static INamedTypeSymbol? GetCandidate(GeneratorSyntaxContext context)
|
private static INamedTypeSymbol? GetCandidate(GeneratorSyntaxContext context)
|
||||||
{
|
{
|
||||||
var classDecl = (ClassDeclarationSyntax)context.Node;
|
if (context.SemanticModel.GetDeclaredSymbol(context.Node) is not INamedTypeSymbol symbol)
|
||||||
|
|
||||||
if (classDecl.AttributeLists.Count == 0)
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if (context.SemanticModel.GetDeclaredSymbol(classDecl)
|
// 仅筛选带有 ContextAwareAttribute 的类
|
||||||
is not { } symbol)
|
var hasAttr = symbol.GetAttributes()
|
||||||
return null;
|
.Any(attr => attr.AttributeClass?.ToDisplayString() == AttributeMetadataName);
|
||||||
|
|
||||||
return Enumerable.Any(symbol.GetAttributes(),
|
return hasAttr ? symbol : null;
|
||||||
attr => attr.AttributeClass?.ToDisplayString() == AttributeMetadataName)
|
|
||||||
? symbol
|
|
||||||
: null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
private static void Generate(
|
#region 输出生成 + 诊断
|
||||||
SourceProductionContext context,
|
|
||||||
INamedTypeSymbol symbol)
|
private static void GenerateOutput(SourceProductionContext context, INamedTypeSymbol symbol)
|
||||||
{
|
{
|
||||||
var syntax = symbol.DeclaringSyntaxReferences
|
var syntax = symbol.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax() as ClassDeclarationSyntax;
|
||||||
.FirstOrDefault()?
|
if (syntax == null)
|
||||||
.GetSyntax() as ClassDeclarationSyntax;
|
return;
|
||||||
|
|
||||||
if (syntax is null || !syntax.Modifiers.Any(SyntaxKind.PartialKeyword))
|
// 1️⃣ 必须是 partial
|
||||||
|
if (!syntax.Modifiers.Any(SyntaxKind.PartialKeyword))
|
||||||
{
|
{
|
||||||
context.ReportDiagnostic(
|
context.ReportDiagnostic(Diagnostic.Create(
|
||||||
Diagnostic.Create(
|
CommonDiagnostics.ClassMustBePartial,
|
||||||
CommonDiagnostics.ClassMustBePartial,
|
syntax.Identifier.GetLocation(),
|
||||||
syntax?.Identifier.GetLocation(),
|
symbol.Name
|
||||||
symbol.Name
|
));
|
||||||
)
|
|
||||||
);
|
|
||||||
return;
|
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
|
var ns = symbol.ContainingNamespace.IsGlobalNamespace
|
||||||
? null
|
? null
|
||||||
: symbol.ContainingNamespace.ToDisplayString();
|
: symbol.ContainingNamespace.ToDisplayString();
|
||||||
|
|
||||||
var source = GenerateSource(ns, symbol);
|
var source = GenerateSource(ns, symbol);
|
||||||
|
context
|
||||||
context.AddSource(
|
.AddSource(
|
||||||
$"{symbol.Name}.ContextAware.g.cs",
|
$"{symbol.Name}.ContextAware.g.cs",
|
||||||
source
|
source);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 源码生成
|
||||||
|
|
||||||
private static string GenerateSource(string? ns, INamedTypeSymbol symbol)
|
private static string GenerateSource(string? ns, INamedTypeSymbol symbol)
|
||||||
{
|
{
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
sb.AppendLine("// <auto-generated/>");
|
sb.AppendLine("// <auto-generated/>");
|
||||||
sb.AppendLine("#nullable enable");
|
sb.AppendLine("#nullable enable");
|
||||||
|
|
||||||
@ -93,20 +105,20 @@ public sealed class ContextAwareGenerator : IIncrementalGenerator
|
|||||||
sb.AppendLine();
|
sb.AppendLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.AppendLine($"partial class {symbol.Name} : GFramework.Core.rule.IContextAware");
|
sb.AppendLine($"partial class {symbol.Name}");
|
||||||
sb.AppendLine("{");
|
sb.AppendLine("{");
|
||||||
|
|
||||||
sb.AppendLine(
|
sb.AppendLine(
|
||||||
" protected GFramework.Core.architecture.IArchitectureContext Context { get; private set; } = null!;");
|
" protected GFramework.Core.architecture.IArchitectureContext Context { get; private set; } = null!;");
|
||||||
|
sb.AppendLine();
|
||||||
sb.AppendLine(" void GFramework.Core.rule.IContextAware.SetContext(");
|
sb.AppendLine(" void GFramework.Core.rule.IContextAware.SetContext(");
|
||||||
sb.AppendLine(" GFramework.Core.architecture.IArchitectureContext context)");
|
sb.AppendLine(" GFramework.Core.architecture.IArchitectureContext context)");
|
||||||
sb.AppendLine(" {");
|
sb.AppendLine(" {");
|
||||||
sb.AppendLine(" Context = context;");
|
sb.AppendLine(" Context = context;");
|
||||||
sb.AppendLine(" }");
|
sb.AppendLine(" }");
|
||||||
|
|
||||||
sb.AppendLine("}");
|
sb.AppendLine("}");
|
||||||
|
|
||||||
return sb.ToString().TrimEnd();
|
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">
|
<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_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_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_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_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/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>
|
<TestAncestor>
|
||||||
<TestId>NUnit3x::BB047F43-6AA0-4EA0-8AE9-E6B9784D9E8E::net8.0::GFramework.SourceGenerators.Tests.rule.ContextAwareGeneratorTests</TestId>
|
<TestId>NUnit3x::BB047F43-6AA0-4EA0-8AE9-E6B9784D9E8E::net8.0::GFramework.SourceGenerators.Tests.rule.ContextAwareGeneratorTests</TestId>
|
||||||
</TestAncestor>
|
</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>
|
</SessionState></s:String></wpf:ResourceDictionary>
|
||||||
Loading…
x
Reference in New Issue
Block a user