133 lines
5.0 KiB
C#
133 lines
5.0 KiB
C#
using System;
|
|
using System.Collections.Immutable;
|
|
using System.Linq;
|
|
using Microsoft.CodeAnalysis;
|
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
|
|
|
namespace Svrnty.CQRS.Grpc.Generators;
|
|
|
|
/// <summary>
|
|
/// Incremental source generator that generates .proto files from C# commands and queries
|
|
/// </summary>
|
|
[Generator]
|
|
public class ProtoFileSourceGenerator : IIncrementalGenerator
|
|
{
|
|
public void Initialize(IncrementalGeneratorInitializationContext context)
|
|
{
|
|
// Register a post-initialization output to generate the proto file
|
|
context.RegisterPostInitializationOutput(ctx =>
|
|
{
|
|
// Generate a placeholder - the actual proto will be generated in the source output
|
|
});
|
|
|
|
// Collect all command and query types
|
|
var commandsAndQueries = context.SyntaxProvider
|
|
.CreateSyntaxProvider(
|
|
predicate: static (s, _) => IsCommandOrQuery(s),
|
|
transform: static (ctx, _) => GetTypeSymbol(ctx))
|
|
.Where(static m => m is not null)
|
|
.Collect();
|
|
|
|
// Combine with compilation to have access to it
|
|
var compilationAndTypes = context.CompilationProvider.Combine(commandsAndQueries);
|
|
|
|
// Generate proto file when commands/queries change
|
|
context.RegisterSourceOutput(compilationAndTypes, (spc, source) =>
|
|
{
|
|
var (compilation, types) = source;
|
|
|
|
if (types.IsDefaultOrEmpty)
|
|
return;
|
|
|
|
try
|
|
{
|
|
// Get the root namespace from the compilation - this matches what GrpcGenerator does
|
|
var rootNamespace = compilation.AssemblyName ?? "Generated";
|
|
var packageName = "cqrs";
|
|
var csharpNamespace = $"{rootNamespace}.Grpc";
|
|
|
|
// Generate the proto file content
|
|
var generator = new ProtoFileGenerator(compilation);
|
|
var protoContent = generator.Generate(packageName, csharpNamespace);
|
|
|
|
// Output as an embedded resource that can be extracted
|
|
var protoFileName = "cqrs_services.proto";
|
|
|
|
// Generate a C# class that contains the proto content
|
|
// This allows build tools to extract it if needed
|
|
var csContent = $$"""
|
|
// <auto-generated />
|
|
#nullable enable
|
|
|
|
namespace Svrnty.CQRS.Grpc.Generated
|
|
{
|
|
/// <summary>
|
|
/// Contains the auto-generated Protocol Buffer definition
|
|
/// </summary>
|
|
internal static class GeneratedProtoFile
|
|
{
|
|
public const string FileName = "{{protoFileName}}";
|
|
|
|
public const string Content = @"{{protoContent.Replace("\"", "\"\"")}}";
|
|
}
|
|
}
|
|
""";
|
|
|
|
spc.AddSource("GeneratedProtoFile.g.cs", csContent);
|
|
|
|
// Report that we generated the proto content
|
|
var descriptor = new DiagnosticDescriptor(
|
|
"CQRSGRPC002",
|
|
"Proto file generated",
|
|
"Generated proto file content in GeneratedProtoFile class",
|
|
"Svrnty.CQRS.Grpc",
|
|
DiagnosticSeverity.Info,
|
|
isEnabledByDefault: true);
|
|
|
|
spc.ReportDiagnostic(Diagnostic.Create(descriptor, Location.None));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Report diagnostic if generation fails
|
|
var descriptor = new DiagnosticDescriptor(
|
|
"CQRSGRPC001",
|
|
"Proto file generation failed",
|
|
"Failed to generate proto file: {0}",
|
|
"Svrnty.CQRS.Grpc",
|
|
DiagnosticSeverity.Warning,
|
|
isEnabledByDefault: true);
|
|
|
|
spc.ReportDiagnostic(Diagnostic.Create(descriptor, Location.None, ex.Message));
|
|
}
|
|
});
|
|
}
|
|
|
|
private static bool IsCommandOrQuery(SyntaxNode node)
|
|
{
|
|
if (node is not TypeDeclarationSyntax typeDecl)
|
|
return false;
|
|
|
|
var name = typeDecl.Identifier.Text;
|
|
return name.EndsWith("Command") || name.EndsWith("Query");
|
|
}
|
|
|
|
private static INamedTypeSymbol? GetTypeSymbol(GeneratorSyntaxContext context)
|
|
{
|
|
var typeDecl = (TypeDeclarationSyntax)context.Node;
|
|
var symbol = context.SemanticModel.GetDeclaredSymbol(typeDecl) as INamedTypeSymbol;
|
|
|
|
// Skip if it has GrpcIgnore attribute
|
|
if (symbol?.GetAttributes().Any(a => a.AttributeClass?.Name == "GrpcIgnoreAttribute") == true)
|
|
return null;
|
|
|
|
return symbol;
|
|
}
|
|
|
|
private static string? GetBuildProperty(SourceProductionContext context, string propertyName)
|
|
{
|
|
// Try to get build properties from the compilation options
|
|
// This is a simplified approach - in practice, you might need analyzer config
|
|
return null; // Will use defaults
|
|
}
|
|
}
|