From dfb5b08f26573799a7254b64e022759ed4acf102 Mon Sep 17 00:00:00 2001 From: ivarlovlie Date: Wed, 21 Dec 2022 19:14:48 +0100 Subject: feat: latest, not working --- I2R.Endpoints.sln | 4 +- code/lib/AsyncEndpoint.cs | 40 +++++ code/lib/I2R.Endpoints.csproj | 30 ++++ code/lib/SyncEndpoint.cs | 34 ++++ code/source-generator/EndpointFinder.cs | 17 ++ code/source-generator/EndpointGenerator.cs | 146 ++++++++++++++++ .../I2R.Endpoints.Generator.csproj | 18 ++ src/AsyncEndpoint.cs | 40 ----- src/EndpointFinder.cs | 19 --- src/EndpointGenerator.cs | 184 --------------------- src/Helpers.cs | 11 -- src/I2R.Endpoints.csproj | 26 --- src/SyncEndpoint.cs | 34 ---- 13 files changed, 288 insertions(+), 315 deletions(-) create mode 100644 code/lib/AsyncEndpoint.cs create mode 100644 code/lib/I2R.Endpoints.csproj create mode 100644 code/lib/SyncEndpoint.cs create mode 100644 code/source-generator/EndpointFinder.cs create mode 100644 code/source-generator/EndpointGenerator.cs create mode 100644 code/source-generator/I2R.Endpoints.Generator.csproj delete mode 100644 src/AsyncEndpoint.cs delete mode 100644 src/EndpointFinder.cs delete mode 100644 src/EndpointGenerator.cs delete mode 100644 src/Helpers.cs delete mode 100644 src/I2R.Endpoints.csproj delete mode 100644 src/SyncEndpoint.cs diff --git a/I2R.Endpoints.sln b/I2R.Endpoints.sln index 9eeafd4..f7f4a63 100644 --- a/I2R.Endpoints.sln +++ b/I2R.Endpoints.sln @@ -3,7 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "I2R.Endpoints", "src\I2R.Endpoints.csproj", "{7A6F787E-4FB2-42BB-B143-60D9399AB4BB}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "I2R.Endpoints", "code\lib\I2R.Endpoints.csproj", "{7A6F787E-4FB2-42BB-B143-60D9399AB4BB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "I2R.Endpoints.Generator", "code\source-generator\I2R.Endpoints.Generator.csproj", "{7A6F787E-4FB2-42BB-B143-60D9399AB4BB}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/code/lib/AsyncEndpoint.cs b/code/lib/AsyncEndpoint.cs new file mode 100644 index 0000000..505032c --- /dev/null +++ b/code/lib/AsyncEndpoint.cs @@ -0,0 +1,40 @@ +namespace I2R.Endpoints; + +public static partial class AsyncEndpoint +{ + public static class Req + { + public abstract class Res + { + public abstract Task HandleAsync( + TRequest request, + CancellationToken cancellationToken = default + ); + } + + public abstract class NoRes + { + public abstract Task HandleAsync( + TRequest request, + CancellationToken cancellationToken = default + ); + } + } + + public static class NoReq + { + public abstract class Res + { + public abstract Task HandleAsync( + CancellationToken cancellationToken = default + ); + } + + public abstract class NoRes + { + public abstract Task HandleAsync( + CancellationToken cancellationToken = default + ); + } + } +} \ No newline at end of file diff --git a/code/lib/I2R.Endpoints.csproj b/code/lib/I2R.Endpoints.csproj new file mode 100644 index 0000000..90ecd90 --- /dev/null +++ b/code/lib/I2R.Endpoints.csproj @@ -0,0 +1,30 @@ + + + + net7.0 + enable + https://git.ivar.systems/dotnet-endpoints/tree/COPYING + https://git.ivar.systems/dotnet-endpoints + git + 1.1.0 + I2R.Endpoints + Ivar Løvlie + A library that enables single file endpoints (or whatever). + Ivar Løvlie + + Initial realease. + + true + + + + + + + + + + + diff --git a/code/lib/SyncEndpoint.cs b/code/lib/SyncEndpoint.cs new file mode 100644 index 0000000..f30923a --- /dev/null +++ b/code/lib/SyncEndpoint.cs @@ -0,0 +1,34 @@ +namespace I2R.Endpoints; + +public static partial class SyncEndpoint +{ + public static class Req + { + public abstract class Res + { + public abstract TResponse Handle( + TRequest request + ); + } + + public abstract class NoRes + { + public abstract void Handle( + TRequest request + ); + } + } + + public static class NoReq + { + public abstract class Res + { + public abstract TResponse Handle(); + } + + public abstract class NoRes + { + public abstract void Handle(); + } + } +} \ No newline at end of file diff --git a/code/source-generator/EndpointFinder.cs b/code/source-generator/EndpointFinder.cs new file mode 100644 index 0000000..b30451e --- /dev/null +++ b/code/source-generator/EndpointFinder.cs @@ -0,0 +1,17 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace I2R.Endpoints.Generator; + +public class EndpointFinder : ISyntaxReceiver +{ + public HashSet Endpoints { get; } = new(); + + public void OnVisitSyntaxNode(SyntaxNode syntaxNode) { + if (syntaxNode is not ClassDeclarationSyntax endpoint) return; + if ((endpoint.BaseList?.Types.Any(c => EndpointGenerator.IsSyncEndpoint(c.ToString())) ?? false) + || (endpoint.BaseList?.Types.Any(c => EndpointGenerator.IsAyncEndpoint(c.ToString())) ?? false)) { + Endpoints.Add(endpoint); + } + } +} diff --git a/code/source-generator/EndpointGenerator.cs b/code/source-generator/EndpointGenerator.cs new file mode 100644 index 0000000..989a1c5 --- /dev/null +++ b/code/source-generator/EndpointGenerator.cs @@ -0,0 +1,146 @@ +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 + ); + }} + }} +}} +"; + } +} diff --git a/code/source-generator/I2R.Endpoints.Generator.csproj b/code/source-generator/I2R.Endpoints.Generator.csproj new file mode 100644 index 0000000..f67f7d3 --- /dev/null +++ b/code/source-generator/I2R.Endpoints.Generator.csproj @@ -0,0 +1,18 @@ + + + netstandard2.0 + 10 + true + true + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + diff --git a/src/AsyncEndpoint.cs b/src/AsyncEndpoint.cs deleted file mode 100644 index 10998ab..0000000 --- a/src/AsyncEndpoint.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace I2R.Endpoints; - -public static partial class AsyncEndpoint -{ - public static class Req - { - public abstract class Res - { - public abstract Task HandleAsync( - TRequest request, - CancellationToken cancellationToken = default - ); - } - - public abstract class NoRes - { - public abstract Task HandleAsync( - TRequest request, - CancellationToken cancellationToken = default - ); - } - } - - public static class NoReq - { - public abstract class Res - { - public abstract Task HandleAsync( - CancellationToken cancellationToken = default - ); - } - - public abstract class NoRes - { - public abstract Task HandleAsync( - CancellationToken cancellationToken = default - ); - } - } -} \ No newline at end of file diff --git a/src/EndpointFinder.cs b/src/EndpointFinder.cs deleted file mode 100644 index ce1b321..0000000 --- a/src/EndpointFinder.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -namespace I2R.Endpoints; - -public class EndpointFinder : ISyntaxReceiver -{ - public HashSet AsyncEndpoints { get; } = new(); - public HashSet SyncEndpoints { get; } = new(); - - public void OnVisitSyntaxNode(SyntaxNode syntaxNode) { - if (syntaxNode is not ClassDeclarationSyntax endpoint) return; - if (endpoint.BaseList?.Types.Any(c => c.ToString().StartsWith("AsyncEndpoint")) ?? false) { - AsyncEndpoints.Add(endpoint); - } else if (endpoint.BaseList?.Types.Any(c => c.ToString().StartsWith("SyncEndpoint")) ?? false) { - SyncEndpoints.Add(endpoint); - } - } -} \ No newline at end of file diff --git a/src/EndpointGenerator.cs b/src/EndpointGenerator.cs deleted file mode 100644 index c0b7c1b..0000000 --- a/src/EndpointGenerator.cs +++ /dev/null @@ -1,184 +0,0 @@ -using System.Text.RegularExpressions; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -namespace I2R.Endpoints; - -[Generator] -public class EndpointGenerator : ISourceGenerator -{ - private const string SourceGenereatedComment = "// Generated, probably smart to leave it be"; - - public void Initialize(GeneratorInitializationContext context) { - context.RegisterForSyntaxNotifications(() => new EndpointFinder()); - } - - private Diagnostic CreateDebugDiagnostic(string message) { - var descriptor = new DiagnosticDescriptor("debug", "Debug", message, "debug", DiagnosticSeverity.Warning, true); - return Diagnostic.Create(descriptor, null, ""); - } - - public class NeededInfo - { - public string BaseEndpointClassName { get; set; } - public string BaseEnpointNamespaceName { get; set; } - } - - private NeededInfo ExtractNeededInfo(ClassDeclarationSyntax classDeclarationSyntax) { - var baseType = classDeclarationSyntax.BaseList?.Types.FirstOrDefault(); - if (baseType == default) { - return default; - } - - var fullTypeName = baseType?.Type.ToString(); - var typeNamespace = GetNamespace(baseType); - var className = Regex.Match(fullTypeName, "(!?<).+?(?=>)").Value.Replace("<", ""); - return new NeededInfo() { - BaseEndpointClassName = className, - BaseEnpointNamespaceName = typeNamespace - }; - } - - public void Execute(GeneratorExecutionContext context) { - var asyncEndpoints = ((EndpointFinder) context.SyntaxReceiver)?.AsyncEndpoints; - var syncEndpoints = ((EndpointFinder) context.SyntaxReceiver)?.SyncEndpoints; - if (asyncEndpoints == null) { - context.ReportDiagnostic(CreateDebugDiagnostic("no endpoints were found")); - return; - } - - foreach (var endpoint in syncEndpoints) { - var info = ExtractNeededInfo(endpoint); - context.AddSource(info.BaseEndpointClassName + "s.g.cs", GetSyncSource(info.BaseEndpointClassName, info.BaseEnpointNamespaceName)); - } - - foreach (var endpoint in asyncEndpoints) { - var info = ExtractNeededInfo(endpoint); - context.AddSource(info.BaseEndpointClassName + "s.g.cs", GetAsyncSource(info.BaseEndpointClassName, info.BaseEnpointNamespaceName)); - } - } - - private string GetSyncSource(string className, string namespaceName) { - return $@" -{SourceGenereatedComment} -namespace {namespaceName}; -public static partial class SyncEndpoint -{{ - public static class Req - {{ - public abstract class Res : {className} - {{ - public abstract TResponse Handle( - TRequest request - ); - }} - - public abstract class NoRes : {className} - {{ - public abstract void Handle( - TRequest request - ); - }} - }} - - public static class NoReq - {{ - public abstract class Res : {className} - {{ - public abstract TResponse Handle(); - }} - - public abstract class NoRes : {className} - {{ - public abstract void Handle(); - }} - }} -}} -"; - } - - private string GetAsyncSource(string className, string namespaceName) { - return $@" -{SourceGenereatedComment} -namespace {namespaceName}; -public static partial class AsyncEndpoint -{{ - public static class Req - {{ - public abstract class Res : {className} - {{ - public abstract Task HandleAsync( - TRequest request, - CancellationToken cancellationToken = default - ); - }} - - public abstract class NoRes : {className} - {{ - public abstract Task HandleAsync( - TRequest request, - CancellationToken cancellationToken = default - ); - }} - }} - - public static class NoReq - {{ - public abstract class Res : {className} - {{ - public abstract Task HandleAsync( - CancellationToken cancellationToken = default - ); - }} - - public abstract class NoRes : {className} - {{ - public abstract Task HandleAsync( - CancellationToken cancellationToken = default - ); - }} - }} -}} -"; - } - - // determine the namespace the class/enum/struct is declared in, if any - static string GetNamespace(BaseTypeSyntax syntax) { - // If we don't have a namespace at all we'll return an empty string - // This accounts for the "default namespace" case - var nameSpace = string.Empty; - - // Get the containing syntax node for the type declaration - // (could be a nested type, for example) - var potentialNamespaceParent = syntax.Parent; - - // Keep moving "out" of nested classes etc until we get to a namespace - // or until we run out of parents - while (potentialNamespaceParent != null && - potentialNamespaceParent is not NamespaceDeclarationSyntax - && potentialNamespaceParent is not FileScopedNamespaceDeclarationSyntax) { - potentialNamespaceParent = potentialNamespaceParent.Parent; - } - - // Build up the final namespace by looping until we no longer have a namespace declaration - if (potentialNamespaceParent is BaseNamespaceDeclarationSyntax namespaceParent) { - // We have a namespace. Use that as the type - nameSpace = namespaceParent.Name.ToString(); - - // Keep moving "out" of the namespace declarations until we - // run out of nested namespace declarations - while (true) { - if (namespaceParent.Parent is not NamespaceDeclarationSyntax parent) { - break; - } - - // Add the outer namespace as a prefix to the final namespace - nameSpace = $"{namespaceParent.Name}.{nameSpace}"; - namespaceParent = parent; - } - } - - // return the final namespace - return nameSpace; - } -} \ No newline at end of file diff --git a/src/Helpers.cs b/src/Helpers.cs deleted file mode 100644 index 0159ae8..0000000 --- a/src/Helpers.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Microsoft.CodeAnalysis.CSharp.Syntax; - -namespace I2R.Endpoints; - -public static class Helpers -{ - public static bool Inherits(this ClassDeclarationSyntax source, string name) { - return source.BaseList?.Types.Select(baseType => baseType) - .Any(baseType => baseType.ToString() == name) ?? false; - } -} \ No newline at end of file diff --git a/src/I2R.Endpoints.csproj b/src/I2R.Endpoints.csproj deleted file mode 100644 index 4948bed..0000000 --- a/src/I2R.Endpoints.csproj +++ /dev/null @@ -1,26 +0,0 @@ - - - net7.0 - enable - https://git.ivar.systems/dotnet-endpoints - https://git.ivar.systems/dotnet-endpoints/tree/COPYING - https://git.ivar.systems/dotnet-endpoints - git - 1.0.0 - I2R.Endpoints - Ivar Løvlie - A library that enables single file endpoints (or whatever). - Ivar Løvlie - - Initial realease. - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - diff --git a/src/SyncEndpoint.cs b/src/SyncEndpoint.cs deleted file mode 100644 index fc82288..0000000 --- a/src/SyncEndpoint.cs +++ /dev/null @@ -1,34 +0,0 @@ -namespace I2R.Endpoints; - -public static partial class SyncEndpoint -{ - public static class Req - { - public abstract class Res - { - public abstract TResponse Handle( - TRequest request - ); - } - - public abstract class NoRes - { - public abstract void Handle( - TRequest request - ); - } - } - - public static class NoReq - { - public abstract class Res - { - public abstract TResponse Handle(); - } - - public abstract class NoRes - { - public abstract void Handle(); - } - } -} \ No newline at end of file -- cgit v1.3