Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize code #41

Merged
merged 2 commits into from
Mar 26, 2024
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ These methods rely on Reflection to scan the Assembly and find the classes that

If you're working with .NET 7.0 or higher, the reccommended approach is to use the **MinimalHelpers.Routing.Analyzers** package, that provides a Source Generator for endpoints registration, as described later.

## MinimalHelpers.Routing.Analyzers
## MinimalHelpers.Routing.Analyzers (.NET 7.0 or higher)

[![Nuget](https://img.shields.io/nuget/v/MinimalHelpers.Routing.Analyzers)](https://www.nuget.org/packages/MinimalHelpers.Routing.Analyzers)
[![Nuget](https://img.shields.io/nuget/dt/MinimalHelpers.Routing.Analyzers)](https://www.nuget.org/packages/MinimalHelpers.Routing.Analyzers)
Expand Down
2 changes: 1 addition & 1 deletion samples/MinimalSample/MinimalSample.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.0" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.3" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
</ItemGroup>

Expand Down
16 changes: 16 additions & 0 deletions src/MinimalHelpers.Routing.Analyzers/CompilationExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;

namespace MinimalHelpers.Routing.Analyzers;

internal static class CompilationExtensions
{
/// <summary>
/// Checks whether a given compilation (assumed to be for C#) is using at least a given language version.
/// </summary>
/// <param name="compilation">The <see cref="Compilation"/> to consider for analysis.</param>
/// <param name="languageVersion">The minimum language version to check.</param>
/// <returns>Whether <paramref name="compilation"/> is using at least the specified language version.</returns>
public static bool HasLanguageVersionAtLeastEqualTo(this Compilation compilation, LanguageVersion languageVersion)
=> ((CSharpCompilation)compilation).LanguageVersion >= languageVersion;
}
47 changes: 29 additions & 18 deletions src/MinimalHelpers.Routing.Analyzers/EndpointHandlerGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace MinimalHelpers.Routing.Analyzers;
Expand All @@ -11,12 +12,22 @@ public class EndpointHandlerGenerator : IIncrementalGenerator
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var provider = context.SyntaxProvider.CreateSyntaxProvider(
(node, _) => node is ClassDeclarationSyntax,
(syntaxtContext, _) => (ClassDeclarationSyntax)syntaxtContext.Node
).Where(c => c is not null);
static (node, _) => node is ClassDeclarationSyntax classDeclaration && classDeclaration.HasOrPotentiallyHasBaseTypes(),
static (context, token) =>
{
if (!context.SemanticModel.Compilation.HasLanguageVersionAtLeastEqualTo(LanguageVersion.CSharp11))
{
return default;
}

token.ThrowIfCancellationRequested();

return (ClassDeclarationSyntax)context.Node;
}
).Where(static c => c is not null);

var compilation = context.CompilationProvider.Combine(provider.Collect());
context.RegisterSourceOutput(compilation, Execute);
context.RegisterSourceOutput(compilation, Execute!);
}

private void Execute(SourceProductionContext context, (Compilation Compilation, ImmutableArray<ClassDeclarationSyntax> Classes) tuple)
Expand All @@ -41,7 +52,7 @@ namespace Microsoft.AspNetCore.Routing;
#nullable disable warnings

/// <summary>
/// Provides extension methods for <see cref="IEndpointRouteHandlerBuilder" /> to add route handlers.
/// Provides extension methods for <see cref="IEndpointRouteBuilder" /> to add route handlers.
/// </summary>
public static class EndpointRouteBuilderExtensions
{
Expand Down Expand Up @@ -78,23 +89,23 @@ public static IEndpointRouteBuilder MapEndpoints(this IEndpointRouteBuilder endp

private static string GetIEndpointRouteHandlerBuilderInterface()
=> """
// <auto-generated />
namespace Microsoft.AspNetCore.Routing;
// <auto-generated />
namespace Microsoft.AspNetCore.Routing;

#nullable enable annotations
#nullable disable warnings
#nullable enable annotations
#nullable disable warnings

/// <summary>
/// Defines a contract for a class that holds one or more route handlers that must be registered by the application.
/// </summary>
public interface IEndpointRouteHandlerBuilder
{
/// <summary>
/// Defines a contract for a class that holds one or more route handlers that must be registered by the application.
/// Maps route endpoints to the corresponding handlers.
/// </summary>
public interface IEndpointRouteHandlerBuilder
{
/// <summary>
/// Maps route endpoints to the corresponding handlers.
/// </summary>
static abstract void MapEndpoints(IEndpointRouteBuilder endpoints);
}
""";
static abstract void MapEndpoints(IEndpointRouteBuilder endpoints);
}
""";

// determine the namespace the class/enum/struct is declared in, if any
// https://andrewlock.net/creating-a-source-generator-part-5-finding-a-type-declarations-namespace-and-type-hierarchy/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace MinimalHelpers.Routing.Analyzers;

/// <summary>
/// Extension methods for the <see cref="SyntaxNode"/> type.
/// </summary>
internal static class TypeDeclarationSyntaxExtensions
{
/// <summary>
/// Checks whether a given <see cref="TypeDeclarationSyntax"/> has or could possibly have any base types, using only syntax.
/// </summary>
/// <param name="typeDeclaration">The input <see cref="TypeDeclarationSyntax"/> instance to check.</param>
/// <returns>Whether <paramref name="typeDeclaration"/> has or could possibly have any base types.</returns>
public static bool HasOrPotentiallyHasBaseTypes(this TypeDeclarationSyntax typeDeclaration)
{
// If the base types list is not empty, the type can definitely has implemented interfaces
if (typeDeclaration.BaseList is { Types.Count: > 0 })
{
return true;
}

// If the base types list is empty, check if the type is partial. If it is, it means
// that there could be another partial declaration with a non-empty base types list.
foreach (var modifier in typeDeclaration.Modifiers)
{
if (modifier.IsKind(SyntaxKind.PartialKeyword))
{
return true;
}
}

return false;
}

/// <summary>
/// Checks whether a given <see cref="TypeDeclarationSyntax"/> has or could possibly have any attributes, using only syntax.
/// </summary>
/// <param name="typeDeclaration">The input <see cref="TypeDeclarationSyntax"/> instance to check.</param>
/// <returns>Whether <paramref name="typeDeclaration"/> has or could possibly have any attributes.</returns>
public static bool HasOrPotentiallyHasAttributes(this TypeDeclarationSyntax typeDeclaration)
{
// If the type has any attributes lists, then clearly it can have attributes
if (typeDeclaration.AttributeLists.Count > 0)
{
return true;
}

// If the declaration has no attribute lists, check if the type is partial. If it is, it means
// that there could be another partial declaration with some attribute lists over them.
foreach (var modifier in typeDeclaration.Modifiers)
{
if (modifier.IsKind(SyntaxKind.PartialKeyword))
{
return true;
}
}

return false;
}
}
Loading