GFramework/GFramework.SourceGenerators/rule/ContextAwareGenerator.cs
GeWuYou 40a9b523f5 feat(rule): 添加上下文感知诊断规则
- 新增 GF_Rule_001 诊断规则,要求类必须实现 IContextAware 接口
- 创建 ContextAwareDiagnostic 类定义诊断规则元数据
- 修改 ContextAwareGenerator 实现 IContextAware 接口检查
- 优化生成器代码结构,添加候选类查找和输出生成功能
- 更新 AnalyzerReleases.Unshipped.md 文档
- 调整测试代码以适配新的诊断规则
- 修复日志诊断规则的命名空间大小写错误
2025-12-27 13:04:01 +08:00

124 lines
3.9 KiB
C#
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Linq;
using System.Text;
using GFramework.SourceGenerators.Common.diagnostics;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace GFramework.SourceGenerators.rule;
[Generator]
public sealed class ContextAwareGenerator : IIncrementalGenerator
{
private const string AttributeMetadataName =
"GFramework.SourceGenerators.Attributes.rule.ContextAwareAttribute";
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// 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(candidates, static (spc, symbol) =>
{
if (symbol != null)
GenerateOutput(spc, symbol);
});
}
#region
private static INamedTypeSymbol? GetCandidate(GeneratorSyntaxContext context)
{
if (context.SemanticModel.GetDeclaredSymbol(context.Node) is not INamedTypeSymbol symbol)
return null;
// 仅筛选带有 ContextAwareAttribute 的类
var hasAttr = symbol.GetAttributes()
.Any(attr => attr.AttributeClass?.ToDisplayString() == AttributeMetadataName);
return hasAttr ? symbol : null;
}
#endregion
#region +
private static void GenerateOutput(SourceProductionContext context, INamedTypeSymbol symbol)
{
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(),
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);
}
#endregion
#region
private static string GenerateSource(string? ns, INamedTypeSymbol symbol)
{
var sb = new StringBuilder();
sb.AppendLine("// <auto-generated/>");
sb.AppendLine("#nullable enable");
if (ns is not null)
{
sb.AppendLine($"namespace {ns};");
sb.AppendLine();
}
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
}