Skip to content

Commit

Permalink
Merge pull request #41 from marcominerva/develop
Browse files Browse the repository at this point in the history
Optimize code
  • Loading branch information
marcominerva authored Mar 26, 2024
2 parents 5e98fd0 + 7bfdd31 commit af9761b
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 20 deletions.
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;
}
}

0 comments on commit af9761b

Please sign in to comment.