using System.Text.RegularExpressions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace I2R.Endpoints.Generator; [Generator] public class EndpointGenerator : ISourceGenerator { private const string SourceGenereatedComment = "// Generated by I2R.Endpoints, probably smart to leave it be"; public void Initialize(GeneratorInitializationContext context) { context.RegisterForSyntaxNotifications(() => new EndpointFinder()); } public void Execute(GeneratorExecutionContext context) { var endpointClasses = ((EndpointFinder) context.SyntaxReceiver)?.Endpoints; if (endpointClasses == null) { context.ReportDiagnostic(CreateDebugDiagnostic("no endpoints were found")); return; } foreach (var info in endpointClasses.Select(GetEndpointInfoFromClass)) { context.AddSource(info.GeneratedFileName, info.IsSynchronous ? GetSyncSource(info) : GetAsyncSource(info)); } } private static Diagnostic CreateDebugDiagnostic(string message) { var descriptor = new DiagnosticDescriptor("debug", "Debug", message, "debug", DiagnosticSeverity.Warning, true); return Diagnostic.Create(descriptor, null, ""); } private class EndpointInfo { public string BaseEndpointClassName { get; set; } public bool IsSynchronous { get; set; } public string GeneratedFileName { get; set; } } public static bool IsSyncEndpoint(string name) { return name.StartsWith("SyncEndpoint"); } public static bool IsAyncEndpoint(string name) { return name.StartsWith("AsyncEndpoint"); } private static EndpointInfo GetEndpointInfoFromClass(ClassDeclarationSyntax classDeclarationSyntax) { var baseType = classDeclarationSyntax.BaseList?.Types.FirstOrDefault(); if (baseType == default) { return default; } var fullTypeName = baseType.Type.ToString(); var className = Regex.Match(fullTypeName, "(!?<).+?(?=>)").Value.Replace("<", ""); return new EndpointInfo() { BaseEndpointClassName = className, IsSynchronous = IsSyncEndpoint(baseType.ToString()), GeneratedFileName = className + ".endpoints.g.cs" }; } private static string GetSyncSource(EndpointInfo info) { return $@" {SourceGenereatedComment} namespace {typeof(EndpointGenerator).Namespace}; public static partial class SyncEndpoint {{ public static class Req {{ public abstract class Res : {info.BaseEndpointClassName} {{ public abstract TResponse Handle( TRequest request ); }} public abstract class NoRes : {info.BaseEndpointClassName} {{ public abstract void Handle( TRequest request ); }} }} public static class NoReq {{ public abstract class Res : {info.BaseEndpointClassName} {{ public abstract TResponse Handle(); }} public abstract class NoRes : {info.BaseEndpointClassName} {{ public abstract void Handle(); }} }} }} "; } private static string GetAsyncSource(EndpointInfo info) { return $@" {SourceGenereatedComment} namespace {typeof(EndpointGenerator).Namespace}; public static partial class AsyncEndpoint {{ public static class Req {{ public abstract class Res : {info.BaseEndpointClassName} {{ public abstract Task HandleAsync( TRequest request, CancellationToken cancellationToken = default ); }} public abstract class NoRes : {info.BaseEndpointClassName} {{ public abstract Task HandleAsync( TRequest request, CancellationToken cancellationToken = default ); }} }} public static class NoReq {{ public abstract class Res : {info.BaseEndpointClassName} {{ public abstract Task HandleAsync( CancellationToken cancellationToken = default ); }} public abstract class NoRes : {info.BaseEndpointClassName} {{ public abstract Task HandleAsync( CancellationToken cancellationToken = default ); }} }} }} "; } }