aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--I2R.Endpoints.sln4
-rw-r--r--code/lib/AsyncEndpoint.cs (renamed from src/AsyncEndpoint.cs)2
-rw-r--r--code/lib/I2R.Endpoints.csproj (renamed from src/I2R.Endpoints.csproj)20
-rw-r--r--code/lib/SyncEndpoint.cs (renamed from src/SyncEndpoint.cs)2
-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
-rw-r--r--src/EndpointFinder.cs19
-rw-r--r--src/EndpointGenerator.cs184
-rw-r--r--src/Helpers.cs11
10 files changed, 198 insertions, 225 deletions
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/src/AsyncEndpoint.cs b/code/lib/AsyncEndpoint.cs
index 10998ab..505032c 100644
--- a/src/AsyncEndpoint.cs
+++ b/code/lib/AsyncEndpoint.cs
@@ -1,6 +1,6 @@
namespace I2R.Endpoints;
-public static partial class AsyncEndpoint<BaseEndpoint>
+public static partial class AsyncEndpoint<TBaseEndpoint>
{
public static class Req<TRequest>
{
diff --git a/src/I2R.Endpoints.csproj b/code/lib/I2R.Endpoints.csproj
index 4948bed..90ecd90 100644
--- a/src/I2R.Endpoints.csproj
+++ b/code/lib/I2R.Endpoints.csproj
@@ -1,12 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
+
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
- <PackageProjectUrl>https://git.ivar.systems/dotnet-endpoints</PackageProjectUrl>
<PackageLicenseUrl>https://git.ivar.systems/dotnet-endpoints/tree/COPYING</PackageLicenseUrl>
<RepositoryUrl>https://git.ivar.systems/dotnet-endpoints</RepositoryUrl>
<RepositoryType>git</RepositoryType>
- <PackageVersion>1.0.0</PackageVersion>
+ <PackageVersion>1.1.0</PackageVersion>
<Title>I2R.Endpoints</Title>
<Authors>Ivar Løvlie</Authors>
<Description>A library that enables single file endpoints (or whatever).</Description>
@@ -14,13 +14,17 @@
<PackageReleaseNotes>
Initial realease.
</PackageReleaseNotes>
+ <IsPackable>true</IsPackable>
</PropertyGroup>
+
<ItemGroup>
- <FrameworkReference Include="Microsoft.AspNetCore.App" />
- <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" />
+ <ProjectReference Include="..\source-generator\I2R.Endpoints.Generator.csproj"
+ OutputItemType="Analyzer"
+ ReferenceOutputAssembly="false"/>
</ItemGroup>
+
+ <ItemGroup>
+ <AnalyzerReference Include="..\source-generator\I2R.Endpoints.Generator.csproj"/>
+ </ItemGroup>
+
</Project>
diff --git a/src/SyncEndpoint.cs b/code/lib/SyncEndpoint.cs
index fc82288..f30923a 100644
--- a/src/SyncEndpoint.cs
+++ b/code/lib/SyncEndpoint.cs
@@ -1,6 +1,6 @@
namespace I2R.Endpoints;
-public static partial class SyncEndpoint<BaseEndpoint>
+public static partial class SyncEndpoint<TBaseEndpoint>
{
public static class Req<TRequest>
{
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>
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<ClassDeclarationSyntax> AsyncEndpoints { get; } = new();
- public HashSet<ClassDeclarationSyntax> 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<T{className}>
-{{
- public static class Req<TRequest>
- {{
- public abstract class Res<TResponse> : {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<TResponse> : {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<T>
-{{
- public static class Req<TRequest>
- {{
- public abstract class Res<TResponse> : {className}
- {{
- public abstract Task<TResponse> 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<TResponse> : {className}
- {{
- public abstract Task<TResponse> 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