Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,10 @@ coverage/
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
nunit-*.xml

# Verify snapshots (received files)
*.received.*

# User-specific files
*.user
4 changes: 4 additions & 0 deletions src/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
</PropertyGroup>
<ItemGroup>
<!-- Analyzers -->
<PackageVersion Include="Endpointer" Version="0.1.0" />
<PackageVersion Include="Meziantou.Analyzer" Version="2.0.285" />
<PackageVersion Include="SonarAnalyzer.CSharp" Version="10.18.0.131500" />
<PackageVersion Include="Roslynator.Analyzers" Version="4.15.0" />
Expand All @@ -19,5 +20,8 @@
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="5.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.TimeProvider.Testing" Version="10.2.0" />
<PackageVersion Include="Basic.Reference.Assemblies.Net80" Version="1.8.4" />
<PackageVersion Include="Verify.SourceGenerators" Version="2.5.0" />
<PackageVersion Include="Verify.TUnit" Version="31.4.3" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
<PackageReference Include="TUnit" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" />
<PackageReference Include="Basic.Reference.Assemblies.Net80" />
<PackageReference Include="Verify.SourceGenerators" />
<PackageReference Include="Verify.TUnit" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
[
// <auto-generated/>
#nullable enable

using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;

namespace Endpointer;

public static class EndpointerExtensions
{
public static IServiceCollection AddEndpointer(this IServiceCollection services)
{
services.AddScoped<App.Users.GetUserEndpoint>();
services.AddScoped<App.Users.CreateUserEndpoint>();
services.AddScoped<App.Orders.GetOrderEndpoint>();

return services;
}

public static IEndpointRouteBuilder MapEndpointer(this IEndpointRouteBuilder endpoints)
{
// App.Users.GetUserEndpoint
new App.Users.GetUserEndpoint.Endpoint().MapEndpoint(endpoints);

// App.Users.CreateUserEndpoint
new App.Users.CreateUserEndpoint.Endpoint().MapEndpoint(endpoints);

// App.Orders.GetOrderEndpoint
new App.Orders.GetOrderEndpoint.Endpoint().MapEndpoint(endpoints);

return endpoints;
}
}

]
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[
// <auto-generated/>
#nullable enable

using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;

namespace Endpointer;

public static class EndpointerExtensions
{
public static IServiceCollection AddEndpointer(this IServiceCollection services)
{
return services;
}

public static IEndpointRouteBuilder MapEndpointer(this IEndpointRouteBuilder endpoints)
{
return endpoints;
}
}

]
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[
// <auto-generated/>
#nullable enable

using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;

namespace Endpointer;

public static class EndpointerExtensions
{
public static IServiceCollection AddEndpointer(this IServiceCollection services)
{
services.AddScoped<TestApp.GetTimeEndpoint>();

return services;
}

public static IEndpointRouteBuilder MapEndpointer(this IEndpointRouteBuilder endpoints)
{
// TestApp.GetTimeEndpoint
new TestApp.GetTimeEndpoint.Endpoint().MapEndpoint(endpoints);

return endpoints;
}
}

]
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[
// <auto-generated/>
#nullable enable

using Microsoft.AspNetCore.Routing;

namespace Endpointer;

/// <summary>
/// Marker interface for endpoint classes to be discovered by the source generator.
/// </summary>
public interface IEndpoint
{
void MapEndpoint(IEndpointRouteBuilder endpoints);
}
]
149 changes: 149 additions & 0 deletions src/Endpointer.Generator.Tests/EndpointerGeneratorSnapshotTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
using Basic.Reference.Assemblies;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;

namespace Endpointer.Generator.Tests;

public class EndpointerGeneratorSnapshotTests
{
// Stub for ASP.NET Core types needed in test compilation
private const string AspNetCoreStubs = """
namespace Microsoft.AspNetCore.Routing
{
public interface IEndpointRouteBuilder { }
}
namespace Microsoft.Extensions.DependencyInjection
{
public interface IServiceCollection { }

public static class ServiceCollectionExtensions
{
public static IServiceCollection AddScoped<T>(this IServiceCollection services) => services;
}
}
""";

[Test]
public Task IEndpointInterface_MatchesSnapshot()
{
var (result, _) = RunGenerator("");

var generatedSources = result.Results[0].GeneratedSources
.Where(s => string.Equals(s.HintName, "IEndpoint.g.cs", StringComparison.Ordinal))
.Select(s => s.SourceText.ToString());

return Verify(generatedSources);
}

[Test]
public Task EndpointerRegistration_WithNoEndpoints_MatchesSnapshot()
{
var (result, _) = RunGenerator("");

var generatedSources = result.Results[0].GeneratedSources
.Where(s => string.Equals(s.HintName, "EndpointerRegistration.g.cs", StringComparison.Ordinal))
.Select(s => s.SourceText.ToString());

return Verify(generatedSources);
}

[Test]
public Task EndpointerRegistration_WithSingleEndpoint_MatchesSnapshot()
{
const string source = """
using Endpointer;
using Microsoft.AspNetCore.Routing;

namespace TestApp;

public class GetTimeEndpoint
{
public class Endpoint : IEndpoint
{
public void MapEndpoint(IEndpointRouteBuilder endpoints) { }
}
}
""";

var (result, _) = RunGenerator(source);

var generatedSources = result.Results[0].GeneratedSources
.Where(s => string.Equals(s.HintName, "EndpointerRegistration.g.cs", StringComparison.Ordinal))
.Select(s => s.SourceText.ToString());

return Verify(generatedSources);
}

[Test]
public Task EndpointerRegistration_WithMultipleEndpoints_MatchesSnapshot()
{
const string source = """
using Endpointer;
using Microsoft.AspNetCore.Routing;

namespace App.Users
{
public class GetUserEndpoint
{
public class Endpoint : IEndpoint
{
public void MapEndpoint(IEndpointRouteBuilder endpoints) { }
}
}

public class CreateUserEndpoint
{
public class Endpoint : IEndpoint
{
public void MapEndpoint(IEndpointRouteBuilder endpoints) { }
}
}
}

namespace App.Orders
{
public class GetOrderEndpoint
{
public class Endpoint : IEndpoint
{
public void MapEndpoint(IEndpointRouteBuilder endpoints) { }
}
}
}
""";

var (result, _) = RunGenerator(source);

var generatedSources = result.Results[0].GeneratedSources
.Where(s => string.Equals(s.HintName, "EndpointerRegistration.g.cs", StringComparison.Ordinal))
.Select(s => s.SourceText.ToString());

return Verify(generatedSources);
}

private static (GeneratorDriverRunResult Result, Compilation OutputCompilation) RunGenerator(string source)
{
var syntaxTrees = new List<SyntaxTree>
{
CSharpSyntaxTree.ParseText(AspNetCoreStubs),
};

if (!string.IsNullOrEmpty(source))
{
syntaxTrees.Add(CSharpSyntaxTree.ParseText(source));
}

var compilation = CSharpCompilation.Create(
assemblyName: "TestAssembly",
syntaxTrees: syntaxTrees,
references: Net80.References.All,
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));

var generator = new EndpointerGenerator();

GeneratorDriver driver = CSharpGeneratorDriver.Create(generator);
driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out _);

return (driver.GetRunResult(), outputCompilation);
}
}
Loading