aboutsummaryrefslogtreecommitdiffstats
path: root/code/source-generator
diff options
context:
space:
mode:
Diffstat (limited to 'code/source-generator')
-rw-r--r--code/source-generator/EndpointFinder.cs17
-rw-r--r--code/source-generator/EndpointGenerator.cs146
-rw-r--r--code/source-generator/I2R.Endpoints.Generator.csproj18
3 files changed, 181 insertions, 0 deletions
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<ClassDeclarationSyntax> 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<T{info.BaseEndpointClassName}>
+{{
+ public static class Req<TRequest>
+ {{
+ public abstract class Res<TResponse> : {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<TResponse> : {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<T>
+{{
+ public static class Req<TRequest>
+ {{
+ public abstract class Res<TResponse> : {info.BaseEndpointClassName}
+ {{
+ public abstract Task<TResponse> 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<TResponse> : {info.BaseEndpointClassName}
+ {{
+ public abstract Task<TResponse> 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 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <TargetFramework>netstandard2.0</TargetFramework>
+ <LangVersion>10</LangVersion>
+ <ImplicitUsings>true</ImplicitUsings>
+ <DevelopmentDependency>true</DevelopmentDependency>
+ </PropertyGroup>
+ <ItemGroup>
+ <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ </PackageReference>
+ <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0"/>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false"/>
+ </ItemGroup>
+</Project>