diff --git a/GFramework.Godot.SourceGenerators/AnalyzerReleases.Unshipped.md b/GFramework.Godot.SourceGenerators/AnalyzerReleases.Unshipped.md index cb8e1c2..58de45e 100644 --- a/GFramework.Godot.SourceGenerators/AnalyzerReleases.Unshipped.md +++ b/GFramework.Godot.SourceGenerators/AnalyzerReleases.Unshipped.md @@ -3,7 +3,6 @@ ### New Rules - Rule ID | Category | Severity | Notes -------------|--------------------------|----------|------------------------ - GFLOG001 | GFramework.Godot.Logging | Error | GodotLoggerDiagnostics - GFW_LOG001 | GFramework.Godot.Logging | Warning | GodotLoggerDiagnostics \ No newline at end of file + Rule ID | Category | Severity | Notes +----------------------|--------------------------|----------|------------------------ + GF_Godot_Logging_001 | GFramework.Godot.Logging | Warning | GodotLoggerDiagnostics \ No newline at end of file diff --git a/GFramework.Godot.SourceGenerators/GFramework.Godot.SourceGenerators.csproj b/GFramework.Godot.SourceGenerators/GFramework.Godot.SourceGenerators.csproj index cc6e2c8..fd688e5 100644 --- a/GFramework.Godot.SourceGenerators/GFramework.Godot.SourceGenerators.csproj +++ b/GFramework.Godot.SourceGenerators/GFramework.Godot.SourceGenerators.csproj @@ -23,15 +23,11 @@ - - - - - + @@ -41,4 +37,8 @@ + + + + diff --git a/GFramework.Godot.SourceGenerators/logging/GodotLoggerDiagnostic.cs b/GFramework.Godot.SourceGenerators/logging/GodotLoggerDiagnostic.cs index 66a046d..740b285 100644 --- a/GFramework.Godot.SourceGenerators/logging/GodotLoggerDiagnostic.cs +++ b/GFramework.Godot.SourceGenerators/logging/GodotLoggerDiagnostic.cs @@ -7,25 +7,6 @@ namespace GFramework.Godot.SourceGenerators.logging; /// internal static class GodotLoggerDiagnostics { - /// - /// 诊断描述符:标识使用[GodotLog]特性的类必须声明为partial - /// - /// - /// ID: GFLOG001 - /// 严重性: Error - /// 分类: GFramework.Godot.Logging - /// - public static readonly DiagnosticDescriptor MustBePartial = - new( - "GFLOG001", - "Class must be partial", - "Class '{0}' must be declared as partial to use [GodotLog]", - "GFramework.Godot.Logging", - DiagnosticSeverity.Error, - true - ); - - /// /// 诊断描述符:标识GodotLogAttribute无法在指定类上生成Logger /// @@ -35,7 +16,7 @@ internal static class GodotLoggerDiagnostics /// 分类: GFramework.Godot.Logging /// public static readonly DiagnosticDescriptor LogAttributeInvalid = new( - "GFW_LOG001", + "GF_Godot_Logging_001", "GodotLogAttribute cannot generate Logger", "GodotLogAttribute on class '{0}' is ineffective: {1}", "GFramework.Godot.Logging", diff --git a/GFramework.Godot.SourceGenerators/logging/GodotLoggerGenerator.cs b/GFramework.Godot.SourceGenerators/logging/GodotLoggerGenerator.cs index 89b04e4..db47829 100644 --- a/GFramework.Godot.SourceGenerators/logging/GodotLoggerGenerator.cs +++ b/GFramework.Godot.SourceGenerators/logging/GodotLoggerGenerator.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Text; +using GFramework.SourceGenerators.Common.diagnostics; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -63,7 +64,7 @@ public sealed class GodotLoggerGenerator : IIncrementalGenerator if (!classDecl.Modifiers.Any(SyntaxKind.PartialKeyword)) { spc.ReportDiagnostic(Diagnostic.Create( - GodotLoggerDiagnostics.MustBePartial, + CommonDiagnostics.ClassMustBePartial, classDecl.Identifier.GetLocation(), classSymbol.Name)); diff --git a/GFramework.SourceGenerators.Attributes/rule/ContextAwareAttribute.cs b/GFramework.SourceGenerators.Attributes/rule/ContextAwareAttribute.cs new file mode 100644 index 0000000..d1060d0 --- /dev/null +++ b/GFramework.SourceGenerators.Attributes/rule/ContextAwareAttribute.cs @@ -0,0 +1,11 @@ +using System; + +namespace GFramework.SourceGenerators.Attributes.rule; + +/// +/// 标记该类需要自动实现 IContextAware +/// +[AttributeUsage(AttributeTargets.Class, Inherited = false)] +public sealed class ContextAwareAttribute : Attribute +{ +} \ No newline at end of file diff --git a/GFramework.SourceGenerators.Common/AnalyzerReleases.Shipped.md b/GFramework.SourceGenerators.Common/AnalyzerReleases.Shipped.md new file mode 100644 index 0000000..60b59dd --- /dev/null +++ b/GFramework.SourceGenerators.Common/AnalyzerReleases.Shipped.md @@ -0,0 +1,3 @@ +; Shipped analyzer releases +; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md + diff --git a/GFramework.SourceGenerators.Common/AnalyzerReleases.Unshipped.md b/GFramework.SourceGenerators.Common/AnalyzerReleases.Unshipped.md new file mode 100644 index 0000000..4586830 --- /dev/null +++ b/GFramework.SourceGenerators.Common/AnalyzerReleases.Unshipped.md @@ -0,0 +1,8 @@ +; Unshipped analyzer release +; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md + +### New Rules + + Rule ID | Category | Severity | Notes +---------|-------------------|----------|------------------- + GFC001 | GFramework.Common | Error | CommonDiagnostics \ No newline at end of file diff --git a/GFramework.SourceGenerators.Common/GFramework.SourceGenerators.Common.csproj b/GFramework.SourceGenerators.Common/GFramework.SourceGenerators.Common.csproj new file mode 100644 index 0000000..20d6f77 --- /dev/null +++ b/GFramework.SourceGenerators.Common/GFramework.SourceGenerators.Common.csproj @@ -0,0 +1,17 @@ + + + + netstandard2.0 + 10 + + + + + + + + + + + + diff --git a/GFramework.SourceGenerators.Common/diagnostics/CommonDiagnostics.cs b/GFramework.SourceGenerators.Common/diagnostics/CommonDiagnostics.cs new file mode 100644 index 0000000..9c0cadc --- /dev/null +++ b/GFramework.SourceGenerators.Common/diagnostics/CommonDiagnostics.cs @@ -0,0 +1,29 @@ +using Microsoft.CodeAnalysis; + +namespace GFramework.SourceGenerators.Common.diagnostics; + +/// +/// 提供通用诊断描述符的静态类 +/// +public static class CommonDiagnostics +{ + /// + /// 定义类必须为partial的诊断描述符 + /// + /// + /// 诊断ID: GF001 + /// 诊断消息: "Class '{0}' must be declared partial for code generation" + /// 分类: GFramework.Common + /// 严重性: Error + /// 是否启用: true + /// + public static readonly DiagnosticDescriptor ClassMustBePartial = + new( + "GFC001", + "Class must be partial", + "Class '{0}' must be declared partial for code generation", + "GFramework.Common", + DiagnosticSeverity.Error, + true + ); +} \ No newline at end of file diff --git a/GFramework.SourceGenerators/AnalyzerReleases.Unshipped.md b/GFramework.SourceGenerators/AnalyzerReleases.Unshipped.md index 88d2578..0ba5463 100644 --- a/GFramework.SourceGenerators/AnalyzerReleases.Unshipped.md +++ b/GFramework.SourceGenerators/AnalyzerReleases.Unshipped.md @@ -3,7 +3,6 @@ ### New Rules -| Rule ID | Category | Severity | Notes | -|------------|--------------------|----------|-------------| -| GFLOG001 | GFramework.Logging | Error | Diagnostics | -| GFW_LOG001 | GFramework.Logging | Warning | Diagnostics | \ No newline at end of file + Rule ID | Category | Severity | Notes +----------------|--------------------------|----------|------------------- + GF_Logging_001 | GFramework.Godot.Logging | Warning | LoggerDiagnostics \ No newline at end of file diff --git a/GFramework.SourceGenerators/GFramework.SourceGenerators.csproj b/GFramework.SourceGenerators/GFramework.SourceGenerators.csproj index c0ed0f2..7cc61f7 100644 --- a/GFramework.SourceGenerators/GFramework.SourceGenerators.csproj +++ b/GFramework.SourceGenerators/GFramework.SourceGenerators.csproj @@ -16,6 +16,7 @@ true Generated true + enable @@ -27,6 +28,7 @@ + diff --git a/GFramework.SourceGenerators/logging/LoggerDiagnostic.cs b/GFramework.SourceGenerators/logging/LoggerDiagnostic.cs index 8448556..8a2d945 100644 --- a/GFramework.SourceGenerators/logging/LoggerDiagnostic.cs +++ b/GFramework.SourceGenerators/logging/LoggerDiagnostic.cs @@ -7,28 +7,12 @@ namespace GFramework.SourceGenerators.logging; /// internal static class LoggerDiagnostics { - /// - /// 定义诊断描述符:要求使用[Log]特性的类必须声明为partial - /// - /// - /// 当类使用[Log]特性但未声明为partial时,编译器将报告此错误 - /// - public static readonly DiagnosticDescriptor MustBePartial = - new( - "GFLOG001", - "Class must be partial", - "Class '{0}' must be declared partial to use [Log]", - "GFramework.Logging", - DiagnosticSeverity.Error, - true - ); - /// /// 定义诊断描述符:LogAttribute无法生成Logger的错误情况 /// public static readonly DiagnosticDescriptor LogAttributeInvalid = new( - "GFW_LOG001", + "GF_Logging_001", "LogAttribute cannot generate Logger", "LogAttribute on class '{0}' is ineffective: {1}", "GFramework.Godot.Logging", diff --git a/GFramework.SourceGenerators/logging/LoggerGenerator.cs b/GFramework.SourceGenerators/logging/LoggerGenerator.cs index d4899d9..b298ce5 100644 --- a/GFramework.SourceGenerators/logging/LoggerGenerator.cs +++ b/GFramework.SourceGenerators/logging/LoggerGenerator.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using System.Text; +using GFramework.SourceGenerators.Common.diagnostics; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -64,7 +65,7 @@ public sealed class LoggerGenerator : IIncrementalGenerator if (!classDecl.Modifiers.Any(SyntaxKind.PartialKeyword)) { spc.ReportDiagnostic(Diagnostic.Create( - LoggerDiagnostics.MustBePartial, + CommonDiagnostics.ClassMustBePartial, classDecl.Identifier.GetLocation(), classSymbol.Name)); diff --git a/GFramework.SourceGenerators/rule/ContextAwareGenerator.cs b/GFramework.SourceGenerators/rule/ContextAwareGenerator.cs new file mode 100644 index 0000000..0fd1cb7 --- /dev/null +++ b/GFramework.SourceGenerators/rule/ContextAwareGenerator.cs @@ -0,0 +1,114 @@ +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. 找到所有 class 声明 + var classDeclarations = 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!) + ); + } + + private static INamedTypeSymbol? GetCandidate(GeneratorSyntaxContext context) + { + var classDecl = (ClassDeclarationSyntax)context.Node; + + if (classDecl.AttributeLists.Count == 0) + return null; + + if (context.SemanticModel.GetDeclaredSymbol(classDecl) + is not { } symbol) + return null; + + return Enumerable.Any(symbol.GetAttributes(), + attr => attr.AttributeClass?.ToDisplayString() == AttributeMetadataName) + ? symbol + : null; + } + + + private static void Generate( + SourceProductionContext context, + INamedTypeSymbol symbol) + { + var syntax = symbol.DeclaringSyntaxReferences + .FirstOrDefault()? + .GetSyntax() as ClassDeclarationSyntax; + + if (syntax is null || !syntax.Modifiers.Any(SyntaxKind.PartialKeyword)) + { + context.ReportDiagnostic( + Diagnostic.Create( + CommonDiagnostics.ClassMustBePartial, + syntax?.Identifier.GetLocation(), + symbol.Name + ) + ); + return; + } + + var ns = symbol.ContainingNamespace.IsGlobalNamespace + ? null + : symbol.ContainingNamespace.ToDisplayString(); + + var source = GenerateSource(ns, symbol); + + context.AddSource( + $"{symbol.Name}.ContextAware.g.cs", + source + ); + } + + private static string GenerateSource(string? ns, INamedTypeSymbol symbol) + { + var sb = new StringBuilder(); + + sb.AppendLine("// "); + sb.AppendLine("#nullable enable"); + + if (ns is not null) + { + sb.AppendLine($"namespace {ns};"); + sb.AppendLine(); + } + + sb.AppendLine($"partial class {symbol.Name} : GFramework.Core.rule.IContextAware"); + sb.AppendLine("{"); + + sb.AppendLine( + " protected GFramework.Core.architecture.IArchitectureContext Context { get; private set; } = null!;"); + + sb.AppendLine(""" + void GFramework.Core.rule.IContextAware.SetContext( + GFramework.Core.architecture.IArchitectureContext context) + { + Context = context; + } + """); + + sb.AppendLine("}"); + + return sb.ToString(); + } +} \ No newline at end of file diff --git a/GFramework.csproj b/GFramework.csproj index 5a873a8..286960a 100644 --- a/GFramework.csproj +++ b/GFramework.csproj @@ -44,6 +44,7 @@ + @@ -68,6 +69,7 @@ + @@ -78,5 +80,13 @@ + + + + + + + + diff --git a/GFramework.sln b/GFramework.sln index a1f30d9..ea86a8c 100644 --- a/GFramework.sln +++ b/GFramework.sln @@ -16,6 +16,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Godot.SourceGene EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Godot.SourceGenerators.Attributes", "GFramework.Godot.SourceGenerators.Attributes\GFramework.Godot.SourceGenerators.Attributes.csproj", "{3A1132B7-EC3B-4BB6-A752-8ADC92BC08A0}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.SourceGenerators.Common", "GFramework.SourceGenerators.Common\GFramework.SourceGenerators.Common.csproj", "{3DB57A3A-ACCF-47BE-A17B-2ADD68B6C8AA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -54,5 +56,9 @@ Global {3A1132B7-EC3B-4BB6-A752-8ADC92BC08A0}.Debug|Any CPU.Build.0 = Debug|Any CPU {3A1132B7-EC3B-4BB6-A752-8ADC92BC08A0}.Release|Any CPU.ActiveCfg = Release|Any CPU {3A1132B7-EC3B-4BB6-A752-8ADC92BC08A0}.Release|Any CPU.Build.0 = Release|Any CPU + {3DB57A3A-ACCF-47BE-A17B-2ADD68B6C8AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3DB57A3A-ACCF-47BE-A17B-2ADD68B6C8AA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3DB57A3A-ACCF-47BE-A17B-2ADD68B6C8AA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3DB57A3A-ACCF-47BE-A17B-2ADD68B6C8AA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/GFramework.sln.DotSettings.user b/GFramework.sln.DotSettings.user index c18f938..56537ed 100644 --- a/GFramework.sln.DotSettings.user +++ b/GFramework.sln.DotSettings.user @@ -1,2 +1,3 @@  + ForceIncluded ForceIncluded \ No newline at end of file