diff --git a/Net.Sdk.Web.Extensions.SourceGenerators.Tests/Builder.cs b/Net.Sdk.Web.Extensions.SourceGenerators.Tests/Builder.cs index 6202cdd..bcf45fa 100644 --- a/Net.Sdk.Web.Extensions.SourceGenerators.Tests/Builder.cs +++ b/Net.Sdk.Web.Extensions.SourceGenerators.Tests/Builder.cs @@ -4,7 +4,11 @@ public static class Builder { public static void Build() { - EndpointRouteBuilder builder = new EndpointRouteBuilder(); - builder.MapGetSimpleRoute(); + var builder = WebApplication.CreateSlimBuilder(); + builder.WithRoutes(); + + var app = builder.Build(); + + app.UseRoutes(); } } diff --git a/Net.Sdk.Web.Extensions.SourceGenerators.Tests/EndpointRouteBuilder.cs b/Net.Sdk.Web.Extensions.SourceGenerators.Tests/EndpointRouteBuilder.cs deleted file mode 100644 index 2da8714..0000000 --- a/Net.Sdk.Web.Extensions.SourceGenerators.Tests/EndpointRouteBuilder.cs +++ /dev/null @@ -1,13 +0,0 @@ - -namespace Net.Sdk.Web.Extensions.SourceGenerators.Tests; - -public class EndpointRouteBuilder : IEndpointRouteBuilder -{ - public IServiceProvider ServiceProvider { get; } - public ICollection DataSources { get; } - - public IApplicationBuilder CreateApplicationBuilder() - { - throw new NotImplementedException(); - } -} diff --git a/Net.Sdk.Web.Extensions.SourceGenerators.Tests/Routes/SimpleRoute.cs b/Net.Sdk.Web.Extensions.SourceGenerators.Tests/Routes/SimpleRoute.cs index 05d35ba..894e91a 100644 --- a/Net.Sdk.Web.Extensions.SourceGenerators.Tests/Routes/SimpleRoute.cs +++ b/Net.Sdk.Web.Extensions.SourceGenerators.Tests/Routes/SimpleRoute.cs @@ -1,19 +1,17 @@ -using Net.Sdk.Web.Extensions.SourceGenerators.Tests.Filters; +using Microsoft.AspNetCore.Mvc; +using Net.Sdk.Web.Extensions.SourceGenerators.Tests.Filters; using Net.Sdk.Web.Extensions.SourceGenerators.Tests.Models; namespace Net.Sdk.Web.Extensions.SourceGenerators.Tests.Routes; -[GenerateMapGet(Pattern = "simple")] +[GenerateRoute(Pattern = "simple")] [RouteFilter(RouteFilterType = typeof(SimpleFilter))] -public sealed class SimpleRoute : IRoute +[RouteFilter(RouteFilterType = typeof(SimpleFilter))] +public sealed class SimpleRoute { - public Task HandleRequest(SimpleRequest? request, CancellationToken cancellationToken) + [GenerateMapGet(Pattern = "get/{api}/{id}")] + public async Task GetSimple(string api, string id, [FromBody] SimpleRequest reques) { - throw new NotImplementedException(); - } - - public Task PreProcess(HttpContext? context, CancellationToken cancellationToken) - { - throw new NotImplementedException(); + return Results.Ok(); } } diff --git a/Net.Sdk.Web.Extensions.SourceGenerators.Tests/Routes/SimpleRoute2.cs b/Net.Sdk.Web.Extensions.SourceGenerators.Tests/Routes/SimpleRoute2.cs new file mode 100644 index 0000000..3acaf11 --- /dev/null +++ b/Net.Sdk.Web.Extensions.SourceGenerators.Tests/Routes/SimpleRoute2.cs @@ -0,0 +1,11 @@ +namespace Net.Sdk.Web.Extensions.SourceGenerators.Tests.Routes; + +[GenerateRoute] +public class SimpleRoute2 +{ + [GenerateMapPost(Pattern = "somethingSimple")] + public async Task GetSomething() + { + + } +} diff --git a/Net.Sdk.Web.Extensions.SourceGenerators/Constants.cs b/Net.Sdk.Web.Extensions.SourceGenerators/Constants.cs index 1de1ce2..d32d849 100644 --- a/Net.Sdk.Web.Extensions.SourceGenerators/Constants.cs +++ b/Net.Sdk.Web.Extensions.SourceGenerators/Constants.cs @@ -17,17 +17,10 @@ public static class Constants public const string UsingMicrosoftAspNetCoreHttp = "Microsoft.AspNetCore.Http"; public const string UsingMicrosoftAspNetCoreRouting = "Microsoft.AspNetCore.Routing"; public const string UsingMicrosoftAspNetCoreBuilder = "Microsoft.AspNetCore.Builder"; + public const string UsingMicrosoftAspNetCoreMvc = "Microsoft.AspNetCore.Mvc"; - public const string RouteFileName = "Route"; - public const string RouteInterface = "IRoute"; - public const string RouteTypeParameter = "TRequest"; - public const string PreProcessRequestReturnType = "Task"; - public const string PreProcessRequestMethodName = "PreProcess"; - public const string HandleRequestReturnType = "Task"; - public const string HandleRequestMethodName = "HandleRequest"; - public const string HttpContextTypeName = "HttpContext?"; - public const string HttpContextParameterName = "context"; - public const string RequestParameterName = "request"; + public const string RouteAttributeName = "GenerateRouteAttribute"; + public const string RouteAttributeShortName = "GenerateRoute"; public const string ResponseTypeName = "Response"; public const string IResultTypeName = "IResult"; diff --git a/Net.Sdk.Web.Extensions.SourceGenerators/MapAttributeGenerator.cs b/Net.Sdk.Web.Extensions.SourceGenerators/MapAttributeGenerator.cs new file mode 100644 index 0000000..53c92ff --- /dev/null +++ b/Net.Sdk.Web.Extensions.SourceGenerators/MapAttributeGenerator.cs @@ -0,0 +1,47 @@ +using System; +using Microsoft.CodeAnalysis; +using Sybil; + +namespace Net.Sdk.Web.Extensions.SourceGenerators; + +[Generator(LanguageNames.CSharp)] +#nullable enable +public class MapAttributeGenerator : IIncrementalGenerator +{ + public void Initialize(IncrementalGeneratorInitializationContext context) + { + context.RegisterPostInitializationOutput(context => + { + GenerateMapAttribute(context, Constants.GetAttributeName); + GenerateMapAttribute(context, Constants.PostAttributeName); + GenerateMapAttribute(context, Constants.PutAttributeName); + GenerateMapAttribute(context, Constants.DeleteAttributeName); + }); + } + + private static void GenerateMapAttribute(IncrementalGeneratorPostInitializationContext context, string attributeName) + { + var builder = SyntaxBuilder.CreateCompilationUnit() + .WithNamespace( + SyntaxBuilder.CreateNamespace(Constants.Namespace) + .WithClass(SyntaxBuilder.CreateClass(attributeName) + .WithModifier(Constants.Public) + .WithConstructor(SyntaxBuilder.CreateConstructor(attributeName) + .WithModifier(Constants.Public)) + .WithAttribute(SyntaxBuilder.CreateAttribute("AttributeUsage") + .WithArgument(AttributeTargets.Method) + .WithArgument("Inherited", false) + .WithArgument("AllowMultiple", false)) + .WithBaseClass(nameof(Attribute)) + .WithProperty(SyntaxBuilder.CreateProperty(Constants.StringType, Constants.PatternPropertyName) + .WithModifier(Constants.Public) + .WithAccessor(SyntaxBuilder.CreateGetter()) + .WithAccessor(SyntaxBuilder.CreateSetter())))); + + var syntax = builder.Build(); + var source = syntax.ToFullString(); + context.AddSource($"{attributeName}.g", source); + } +} + +#nullable disable \ No newline at end of file diff --git a/Net.Sdk.Web.Extensions.SourceGenerators/MapEndpointGenerator.cs b/Net.Sdk.Web.Extensions.SourceGenerators/MapEndpointGenerator.cs deleted file mode 100644 index 33af6d3..0000000 --- a/Net.Sdk.Web.Extensions.SourceGenerators/MapEndpointGenerator.cs +++ /dev/null @@ -1,276 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Sybil; -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Text; - -namespace Net.Sdk.Web.Extensions.SourceGenerators; - -[Generator(LanguageNames.CSharp)] -#nullable enable -public class MapEndpointGenerator : IIncrementalGenerator -{ - public void Initialize(IncrementalGeneratorInitializationContext context) - { - context.RegisterPostInitializationOutput(GenerateRouteFilterAttribute); - - var classDeclarations = context.SyntaxProvider.CreateSyntaxProvider( - predicate: static (s, _) => s is ClassDeclarationSyntax, - transform: static (ctx, _) => GetFilteredClassDeclarationSyntax(ctx)).Where(static c => c is not null); - var compilationAndClasses = context.CompilationProvider.Combine(classDeclarations.Collect()); - context.RegisterSourceOutput(compilationAndClasses, (sourceProductionContext, tuple) => Execute(tuple.Left, tuple.Right, sourceProductionContext)); - } - - private static ClassDeclarationSyntax? GetFilteredClassDeclarationSyntax(GeneratorSyntaxContext context) - { - var classDeclarationSyntax = (ClassDeclarationSyntax)context.Node; - if (classDeclarationSyntax.AttributeLists - .SelectMany(l => l.Attributes) - .OfType() - .Any(s => s.Name.ToString() == Constants.GetAttributeName || s.Name.ToString() == Constants.GetAttributeShortName || - s.Name.ToString() == Constants.PostAttributeName || s.Name.ToString() == Constants.PostAttributeShortName || - s.Name.ToString() == Constants.PutAttributeName || s.Name.ToString() == Constants.PutAttributeShortName || - s.Name.ToString() == Constants.DeleteAttributeName || s.Name.ToString() == Constants.DeleteAttributeShortName)) - { - return classDeclarationSyntax; - } - - return default; - } - - private static void Execute(Compilation compilation, ImmutableArray classes, SourceProductionContext sourceProductionContext) - { - if (classes.IsDefaultOrEmpty) - { - return; - } - - var maybeLanguageVersion = (compilation.SyntaxTrees.FirstOrDefault()?.Options as CSharpParseOptions)?.LanguageVersion; - if (!maybeLanguageVersion.HasValue) - { - return; - } - - var languageVersion = maybeLanguageVersion.Value; - var builder = SyntaxBuilder.CreateCompilationUnit() - .WithUsing(Constants.UsingSystemThreading) - .WithUsing(Constants.UsingMicrosoftAspNetCoreRouting) - .WithUsing(Constants.UsingMicrosoftAspNetCoreHttp) - .WithUsing(Constants.UsingMicrosoftAspNetCoreBuilder); - - var routeUsings = new HashSet(); - - var namespaceBuilder = languageVersion >= LanguageVersion.CSharp10 ? SyntaxBuilder.CreateFileScopedNamespace(Constants.Namespace) : SyntaxBuilder.CreateNamespace(Constants.Namespace); - builder.WithNamespace(namespaceBuilder); - - var webAppBuilder = SyntaxBuilder.CreateClass(Constants.WebApplicationExtensionsName) - .WithModifiers($"{Constants.Public} {Constants.Static}"); - - var extensionClassBuilder = SyntaxBuilder.CreateClass(Constants.EndpointRouteBuilderExtensionsName) - .WithModifiers($"{Constants.Public} {Constants.Static}"); - namespaceBuilder.WithClass(webAppBuilder); - namespaceBuilder.WithClass(extensionClassBuilder); - - var useRoutesWebAppMethodBuilder = SyntaxBuilder.CreateMethod(Constants.WebApplicationTypeName, Constants.UseRoutesMethodName) - .WithThisParameter(Constants.WebApplicationTypeName, Constants.BuilderParameterName) - .WithModifiers($"{Constants.Public} {Constants.Static}"); - - var useRoutesMethodBuilder = SyntaxBuilder.CreateMethod(Constants.IEndpointRouteBuilderTypeName, Constants.MapAllRoutes) - .WithThisParameter(Constants.IEndpointRouteBuilderTypeName, Constants.BuilderParameterName) - .WithModifiers($"{Constants.Public} {Constants.Static}"); - var useRoutesBody = new StringBuilder(); - var useRoutesWebAppBody = new StringBuilder(); - foreach (var classDeclarationSyntax in classes) - { - if (classDeclarationSyntax is null) - { - continue; - } - - if (GetParentOfType(classDeclarationSyntax) is BaseNamespaceDeclarationSyntax baseNamespaceDeclarationSyntax) - { - routeUsings.Add(baseNamespaceDeclarationSyntax.Name.ToString()); - } - - var attributes = classDeclarationSyntax.AttributeLists - .SelectMany(l => l.Attributes) - .OfType().ToList(); - - if (attributes.FirstOrDefault(a => a.Name.ToString() == Constants.GetAttributeName || a.Name.ToString() == Constants.GetAttributeShortName) is AttributeSyntax getAttributeSyntax) - { - var methodName = $"MapGet{classDeclarationSyntax.Identifier}"; - var methodBuilder = GetMethodBuilderByType("Get", classDeclarationSyntax, getAttributeSyntax, compilation, attributes, routeUsings); - if (methodBuilder is not null) - { - useRoutesBody.AppendLine($"{methodName}(builder);"); - useRoutesWebAppBody.AppendLine($"{Constants.EndpointRouteBuilderExtensionsName}.{methodName}(builder);"); - extensionClassBuilder.WithMethod(methodBuilder); - } - } - else if (attributes.FirstOrDefault(a => a.Name.ToString() == Constants.PostAttributeName || a.Name.ToString() == Constants.PostAttributeShortName) is AttributeSyntax postAttributeSyntax) - { - var methodName = $"MapPost{classDeclarationSyntax.Identifier}"; - var methodBuilder = GetMethodBuilderByType("Post", classDeclarationSyntax, postAttributeSyntax, compilation, attributes, routeUsings); - if (methodBuilder is not null) - { - useRoutesBody.AppendLine($"{methodName}(builder);"); - useRoutesWebAppBody.AppendLine($"{Constants.EndpointRouteBuilderExtensionsName}.{methodName}(builder);"); - extensionClassBuilder.WithMethod(methodBuilder); - } - } - else if (attributes.FirstOrDefault(a => a.Name.ToString() == Constants.PutAttributeName || a.Name.ToString() == Constants.PutAttributeShortName) is AttributeSyntax putAttributeSyntax) - { - var methodName = $"MapPut{classDeclarationSyntax.Identifier}"; - var methodBuilder = GetMethodBuilderByType("Put", classDeclarationSyntax, putAttributeSyntax, compilation, attributes, routeUsings); - if (methodBuilder is not null) - { - useRoutesBody.AppendLine($"{methodName}(builder);"); - useRoutesWebAppBody.AppendLine($"{Constants.EndpointRouteBuilderExtensionsName}.{methodName}(builder);"); - extensionClassBuilder.WithMethod(methodBuilder); - } - } - else if (attributes.FirstOrDefault(a => a.Name.ToString() == Constants.DeleteAttributeName || a.Name.ToString() == Constants.DeleteAttributeShortName) is AttributeSyntax deleteAttributeSyntax) - { - var methodName = $"MapDelete{classDeclarationSyntax.Identifier}"; - var methodBuilder = GetMethodBuilderByType("Delete", classDeclarationSyntax, deleteAttributeSyntax, compilation, attributes, routeUsings); - if (methodBuilder is not null) - { - useRoutesBody.AppendLine($"{methodName}(builder);"); - useRoutesWebAppBody.AppendLine($"{Constants.EndpointRouteBuilderExtensionsName}.{methodName}(builder);"); - extensionClassBuilder.WithMethod(methodBuilder); - } - } - } - - useRoutesBody.AppendLine("return builder;"); - useRoutesWebAppBody.AppendLine($"return builder;"); - useRoutesMethodBuilder.WithBody(useRoutesBody.ToString()); - useRoutesWebAppMethodBuilder.WithBody(useRoutesWebAppBody.ToString()); - extensionClassBuilder.WithMethod(useRoutesMethodBuilder); - webAppBuilder.WithMethod(useRoutesWebAppMethodBuilder); - foreach (var classUsing in routeUsings) - { - builder.WithUsing(classUsing); - } - - var fileSource = builder.Build().ToFullString(); - sourceProductionContext.AddSource($"{Constants.EndpointRouteBuilderExtensionsName}.g", fileSource); - } - - private static MethodBuilder? GetMethodBuilderByType(string type, ClassDeclarationSyntax classDeclarationSyntax, AttributeSyntax mapAttributeSyntax, Compilation compilation, List attributes, HashSet usings) - { - var methodBuilder = SyntaxBuilder.CreateMethod(Constants.IEndpointRouteBuilderTypeName, $"Map{type}{classDeclarationSyntax.Identifier}") - .WithThisParameter(Constants.IEndpointRouteBuilderTypeName, Constants.BuilderParameterName) - .WithModifiers($"{Constants.Public} {Constants.Static}"); - - var pattern = mapAttributeSyntax.ArgumentList?.Arguments.OfType().FirstOrDefault(a => a.NameEquals?.Name.Identifier.Text == Constants.PatternPropertyName)?.Expression.ToString().Trim('"'); - if (pattern is null) - { - return default; - } - - var routeFilterSb = new StringBuilder(); - foreach(var attribute in attributes.Where(a => a.Name.ToString() == Constants.RouteFilterAttributeName || a.Name.ToString() == Constants.RouteFilterAttributeShortName)) - { - var semanticModel = compilation.GetSemanticModel(classDeclarationSyntax.SyntaxTree); - if (GetRouteFilterTypeName(semanticModel, attribute) is not string routeFilterType) - { - continue; - } - - var namespaceIndex = routeFilterType.LastIndexOf("."); - if (namespaceIndex > 0) - { - var routeFilterNamespace = routeFilterType.Substring(0, namespaceIndex); - routeFilterType = routeFilterType.Substring(namespaceIndex + 1); - usings.Add(routeFilterNamespace); - } - - routeFilterSb.Append('\n') - .Append(@$".AddEndpointFilter<{routeFilterType}>()"); - } - - methodBuilder.WithBody(@$" - builder.Map{type}(""{pattern}"", async (HttpContext context, {classDeclarationSyntax.Identifier} route) => - {{ - var request = await route.PreProcess(context, context.RequestAborted); - var response = await route.HandleRequest(request, context.RequestAborted); - return response.GetResult(); - }}){routeFilterSb}; - - return builder;"); - - return methodBuilder; - } - - public static string? GetRouteFilterTypeName(SemanticModel semanticModel, AttributeSyntax attributeSyntax) - { - var routeFilterTypeArgument = attributeSyntax.ArgumentList?.Arguments - .FirstOrDefault(arg => arg.NameEquals?.Name.Identifier.Text == Constants.RouteFilterTypePropertyName); - - if (routeFilterTypeArgument is null) - { - return default; - } - - if (routeFilterTypeArgument.Expression is not TypeOfExpressionSyntax typeExpression) - { - return default; - } - - var typeSymbol = semanticModel.GetTypeInfo(typeExpression.Type); - if (typeSymbol.Type is INamedTypeSymbol namedTypeSymbol) - { - // Get the fully qualified name (namespace + type name) - return namedTypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); - } - - return default; - } - - private static T? GetParentOfType(SyntaxNode syntaxNode) - { - if (syntaxNode.Parent is null) - { - return default; - } - - if (syntaxNode.Parent is T parentNode) - { - return parentNode; - } - - return GetParentOfType(syntaxNode.Parent); - } - - private static void GenerateRouteFilterAttribute(IncrementalGeneratorPostInitializationContext context) - { - var builder = SyntaxBuilder.CreateCompilationUnit() - .WithNamespace( - SyntaxBuilder.CreateNamespace(Constants.Namespace) - .WithClass(SyntaxBuilder.CreateClass(Constants.RouteFilterAttributeName) - .WithModifier(Constants.Public) - .WithConstructor(SyntaxBuilder.CreateConstructor(Constants.RouteFilterAttributeName) - .WithModifier(Constants.Public)) - .WithAttribute(SyntaxBuilder.CreateAttribute("AttributeUsage") - .WithArgument(AttributeTargets.Class) - .WithArgument("Inherited", false) - .WithArgument("AllowMultiple", true)) - .WithBaseClass(nameof(Attribute)) - .WithProperty(SyntaxBuilder.CreateProperty($"{Constants.TypeType}?", Constants.RouteFilterTypePropertyName) - .WithModifier(Constants.Public) - .WithAccessor(SyntaxBuilder.CreateGetter()) - .WithAccessor(SyntaxBuilder.CreateSetter())))); - - - var syntax = builder.Build(); - var source = syntax.ToFullString(); - context.AddSource($"{Constants.RouteFilterAttributeName}.g", $"#nullable enable\n{source}\n#nullable disable\n"); - } -} - -#nullable disable \ No newline at end of file diff --git a/Net.Sdk.Web.Extensions.SourceGenerators/Net.Sdk.Web.Extensions.SourceGenerators.csproj b/Net.Sdk.Web.Extensions.SourceGenerators/Net.Sdk.Web.Extensions.SourceGenerators.csproj index 2cb9848..8f45218 100644 --- a/Net.Sdk.Web.Extensions.SourceGenerators/Net.Sdk.Web.Extensions.SourceGenerators.csproj +++ b/Net.Sdk.Web.Extensions.SourceGenerators/Net.Sdk.Web.Extensions.SourceGenerators.csproj @@ -2,7 +2,7 @@ netstandard2.0 - 0.8.7 + 0.8.8 latest true Alexandru Macocian diff --git a/Net.Sdk.Web.Extensions.SourceGenerators/ResponseGenerator.cs b/Net.Sdk.Web.Extensions.SourceGenerators/ResponseGenerator.cs deleted file mode 100644 index 5f7c9a8..0000000 --- a/Net.Sdk.Web.Extensions.SourceGenerators/ResponseGenerator.cs +++ /dev/null @@ -1,121 +0,0 @@ -using Microsoft.CodeAnalysis; -using Sybil; - -namespace Net.Sdk.Web.Extensions.SourceGenerators; - -[Generator(LanguageNames.CSharp)] -#nullable enable -public class ResponseGenerator : IIncrementalGenerator -{ - public void Initialize(IncrementalGeneratorInitializationContext context) - { - context.RegisterPostInitializationOutput(context => - { - GenerateResponse(context); - }); - } - - private static void GenerateResponse(IncrementalGeneratorPostInitializationContext context) - { - var responseBaseClassBuilder = SyntaxBuilder.CreateCompilationUnit() - .WithUsing(Constants.UsingMicrosoftAspNetCoreHttp) - .WithNamespace(SyntaxBuilder.CreateNamespace(Constants.Namespace) - .WithClass(SyntaxBuilder.CreateClass(Constants.ResponseTypeName) - .WithModifiers($"{Constants.Public} {Constants.Abstract}") - .WithMethod(SyntaxBuilder.CreateMethod(Constants.IResultTypeName, Constants.GetResult) - .WithModifier(Constants.Public) - .WithBody( - @"return this switch - { - SuccessResponse success => Results.StatusCode(success.StatusCode), - FailureResponse failure => Results.Problem( - detail: string.IsNullOrWhiteSpace(failure.ErrorMessage) is false ? failure.ErrorMessage : default, - statusCode: failure.StatusCode), - ContentResponse content => Results.Content(content.ContentString, content.ContentType), - JsonResponse json => Results.Ok(json.Value), - _ => throw new InvalidOperationException($""Unexpected response type {this.GetType().Name}"") - };")) - .WithMethod(SyntaxBuilder.CreateMethod(Constants.SuccessResponseTypeName, Constants.OkSuccessResponse) - .WithModifiers($"{Constants.Public} {Constants.Static}") - .WithExpression("new() { StatusCode = 200 };")) - .WithMethod(SyntaxBuilder.CreateMethod(Constants.SuccessResponseTypeName, Constants.CreatedSuccessResponse) - .WithModifiers($"{Constants.Public} {Constants.Static}") - .WithExpression("new() { StatusCode = 201 };")) - .WithMethod(SyntaxBuilder.CreateMethod(Constants.ContentResponseTypeName, Constants.ContentContentResponse) - .WithModifiers($"{Constants.Public} {Constants.Static}") - .WithParameter(Constants.StringType, Constants.ContentParameterName) - .WithParameter($"{Constants.StringType}?", Constants.ContentTypeParameterName, Constants.Default) - .WithExpression("new() { ContentString = content, ContentType = contentType };")) - .WithMethod(SyntaxBuilder.CreateMethod(Constants.JsonResponseTypeName, Constants.JsonJsonResponse) - .WithModifiers($"{Constants.Public} {Constants.Static}") - .WithParameter(Constants.ObjectType, Constants.ValueParameterName) - .WithExpression("new() { Value = value };")) - .WithMethod(SyntaxBuilder.CreateMethod(Constants.ContentResponseTypeName, Constants.PageContentResponse) - .WithModifiers($"{Constants.Public} {Constants.Static}") - .WithParameter(Constants.StringType, Constants.ContentParameterName) - .WithExpression(@"new() { ContentString = content, ContentType = ""text/html"" };")) - .WithMethod(SyntaxBuilder.CreateMethod(Constants.FailureResponseTypeName, Constants.BadRequestFailureResponse) - .WithModifiers($"{Constants.Public} {Constants.Static}") - .WithParameter(Constants.StringType, Constants.ErrorMessageParameterName) - .WithExpression(@"new() { StatusCode = 400, ErrorMessage = errorMessage };")) - .WithMethod(SyntaxBuilder.CreateMethod(Constants.FailureResponseTypeName, Constants.UnauthorizedFailureResponse) - .WithModifiers($"{Constants.Public} {Constants.Static}") - .WithParameter(Constants.StringType, Constants.ErrorMessageParameterName) - .WithExpression(@"new() { StatusCode = 401, ErrorMessage = errorMessage };")) - .WithMethod(SyntaxBuilder.CreateMethod(Constants.FailureResponseTypeName, Constants.ForbiddenFailureResponse) - .WithModifiers($"{Constants.Public} {Constants.Static}") - .WithParameter(Constants.StringType, Constants.ErrorMessageParameterName) - .WithExpression(@"new() { StatusCode = 403, ErrorMessage = errorMessage };")) - .WithMethod(SyntaxBuilder.CreateMethod(Constants.FailureResponseTypeName, Constants.NotFoundFailureResponse) - .WithModifiers($"{Constants.Public} {Constants.Static}") - .WithParameter(Constants.StringType, Constants.ErrorMessageParameterName) - .WithExpression(@"new() { StatusCode = 404, ErrorMessage = errorMessage };")) - .WithMethod(SyntaxBuilder.CreateMethod(Constants.FailureResponseTypeName, Constants.ServerErrorFailureResponse) - .WithModifiers($"{Constants.Public} {Constants.Static}") - .WithParameter(Constants.StringType, Constants.ErrorMessageParameterName) - .WithExpression(@"new() { StatusCode = 500, ErrorMessage = errorMessage };"))) - .WithClass(SyntaxBuilder.CreateClass(Constants.SuccessResponseTypeName) - .WithModifiers($"{Constants.Public} {Constants.Sealed}") - .WithBaseClass(Constants.ResponseTypeName) - .WithProperty(SyntaxBuilder.CreateProperty(Constants.IntType, Constants.StatusCodePropertyName) - .WithModifier(Constants.Public) - .WithAccessor(SyntaxBuilder.CreateGetter()) - .WithAccessor(SyntaxBuilder.CreateSetter()))) - .WithClass(SyntaxBuilder.CreateClass(Constants.FailureResponseTypeName) - .WithModifiers($"{Constants.Public} {Constants.Sealed}") - .WithBaseClass(Constants.ResponseTypeName) - .WithProperty(SyntaxBuilder.CreateProperty(Constants.IntType, Constants.StatusCodePropertyName) - .WithModifier(Constants.Public) - .WithAccessor(SyntaxBuilder.CreateGetter()) - .WithAccessor(SyntaxBuilder.CreateSetter())) - .WithProperty(SyntaxBuilder.CreateProperty($"{Constants.StringType}?", Constants.ErrorMessagePropertyName) - .WithModifier(Constants.Public) - .WithAccessor(SyntaxBuilder.CreateGetter()) - .WithAccessor(SyntaxBuilder.CreateSetter()))) - .WithClass(SyntaxBuilder.CreateClass(Constants.ContentResponseTypeName) - .WithModifiers($"{Constants.Public} {Constants.Sealed}") - .WithBaseClass(Constants.ResponseTypeName) - .WithProperty(SyntaxBuilder.CreateProperty($"{Constants.StringType}?", Constants.ContentStringPropertyName) - .WithModifier(Constants.Public) - .WithAccessor(SyntaxBuilder.CreateGetter()) - .WithAccessor(SyntaxBuilder.CreateSetter())) - .WithProperty(SyntaxBuilder.CreateProperty($"{Constants.StringType}?", Constants.ContentTypePropertyName) - .WithModifier(Constants.Public) - .WithAccessor(SyntaxBuilder.CreateGetter()) - .WithAccessor(SyntaxBuilder.CreateSetter()))) - .WithClass(SyntaxBuilder.CreateClass(Constants.JsonResponseTypeName) - .WithModifiers($"{Constants.Public} {Constants.Sealed}") - .WithBaseClass(Constants.ResponseTypeName) - .WithProperty(SyntaxBuilder.CreateProperty($"{Constants.ObjectType}?", Constants.ValuePropertyName) - .WithModifier(Constants.Public) - .WithAccessor(SyntaxBuilder.CreateGetter()) - .WithAccessor(SyntaxBuilder.CreateSetter()))) - ); - - var syntax = responseBaseClassBuilder.Build(); - var source = syntax.ToFullString(); - context.AddSource($"Response.g", $"#nullable enable\n{source}\n#nullable disable\n"); - } -} - -#nullable disable \ No newline at end of file diff --git a/Net.Sdk.Web.Extensions.SourceGenerators/RouteAttributeGenerator.cs b/Net.Sdk.Web.Extensions.SourceGenerators/RouteAttributeGenerator.cs index 0c9a4c1..55678f5 100644 --- a/Net.Sdk.Web.Extensions.SourceGenerators/RouteAttributeGenerator.cs +++ b/Net.Sdk.Web.Extensions.SourceGenerators/RouteAttributeGenerator.cs @@ -1,6 +1,6 @@ -using System; -using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis; using Sybil; +using System; namespace Net.Sdk.Web.Extensions.SourceGenerators; @@ -10,37 +10,28 @@ public class RouteAttributeGenerator : IIncrementalGenerator { public void Initialize(IncrementalGeneratorInitializationContext context) { - context.RegisterPostInitializationOutput(context => - { - GenerateMapAttribute(context, Constants.GetAttributeName); - GenerateMapAttribute(context, Constants.PostAttributeName); - GenerateMapAttribute(context, Constants.PutAttributeName); - GenerateMapAttribute(context, Constants.DeleteAttributeName); - }); + context.RegisterPostInitializationOutput(GenerateAttribute); } - private static void GenerateMapAttribute(IncrementalGeneratorPostInitializationContext context, string attributeName) + private static void GenerateAttribute(IncrementalGeneratorPostInitializationContext context) { var builder = SyntaxBuilder.CreateCompilationUnit() - .WithNamespace( - SyntaxBuilder.CreateNamespace(Constants.Namespace) - .WithClass(SyntaxBuilder.CreateClass(attributeName) + .WithNamespace(SyntaxBuilder.CreateNamespace(Constants.Namespace) + .WithClass(SyntaxBuilder.CreateClass(Constants.RouteAttributeName) + .WithBaseClass(nameof(Attribute)) + .WithModifiers($"{Constants.Public} {Constants.Sealed}") + .WithAttribute(SyntaxBuilder.CreateAttribute("AttributeUsage") + .WithArgument(AttributeTargets.Class) + .WithArgument("Inherited", false) + .WithArgument("AllowMultiple", false)) + .WithProperty(SyntaxBuilder.CreateProperty(Constants.StringType, Constants.PatternPropertyName) .WithModifier(Constants.Public) - .WithConstructor(SyntaxBuilder.CreateConstructor(attributeName) - .WithModifier(Constants.Public)) - .WithAttribute(SyntaxBuilder.CreateAttribute("AttributeUsage") - .WithArgument(AttributeTargets.Class) - .WithArgument("Inherited", false) - .WithArgument("AllowMultiple", false)) - .WithBaseClass(nameof(Attribute)) - .WithProperty(SyntaxBuilder.CreateProperty(Constants.StringType, Constants.PatternPropertyName) - .WithModifier(Constants.Public) - .WithAccessor(SyntaxBuilder.CreateGetter()) - .WithAccessor(SyntaxBuilder.CreateSetter())))); + .WithAccessor(SyntaxBuilder.CreateGetter()) + .WithAccessor(SyntaxBuilder.CreateSetter())))); var syntax = builder.Build(); var source = syntax.ToFullString(); - context.AddSource($"{attributeName}.g", source); + context.AddSource($"{Constants.RouteAttributeName}.g", source); } } diff --git a/Net.Sdk.Web.Extensions.SourceGenerators/RouteInterfaceGenerator.cs b/Net.Sdk.Web.Extensions.SourceGenerators/RouteInterfaceGenerator.cs deleted file mode 100644 index 6ba08f6..0000000 --- a/Net.Sdk.Web.Extensions.SourceGenerators/RouteInterfaceGenerator.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Microsoft.CodeAnalysis; -using Sybil; - -namespace Net.Sdk.Web.Extensions.SourceGenerators; - -[Generator(LanguageNames.CSharp)] -#nullable enable -public class RouteInterfaceGenerator : IIncrementalGenerator -{ - public void Initialize(IncrementalGeneratorInitializationContext context) - { - context.RegisterPostInitializationOutput(context => - { - GenerateInterface(context); - }); - } - - private static void GenerateInterface(IncrementalGeneratorPostInitializationContext context) - { - var builder = SyntaxBuilder.CreateCompilationUnit() - .WithNamespace(SyntaxBuilder.CreateNamespace(Constants.Namespace) - .WithInterface(SyntaxBuilder.CreateInterface(Constants.RouteInterface) - .WithTypeParameter(SyntaxBuilder.CreateTypeParameter(Constants.RouteTypeParameter)) - .WithTypeParameterConstraint(SyntaxBuilder.CreateTypeParameterConstraint(Constants.RouteTypeParameter) - .WithClass()) - .WithMethod(SyntaxBuilder.CreateMethod(Constants.PreProcessRequestReturnType, Constants.PreProcessRequestMethodName) - .WithModifier(Constants.Public) - .WithParameter(Constants.HttpContextTypeName, Constants.HttpContextParameterName) - .WithParameter(Constants.CancellationTokenTypeName, Constants.CancellationTokenParameterName) - .WithNoBody()) - .WithMethod(SyntaxBuilder.CreateMethod(Constants.HandleRequestReturnType, Constants.HandleRequestMethodName) - .WithModifier(Constants.Public) - .WithParameter($"{Constants.RouteTypeParameter}?", Constants.RequestParameterName) - .WithParameter(Constants.CancellationTokenTypeName, Constants.CancellationTokenParameterName) - .WithNoBody()))); - - var syntax = builder.Build(); - var source = syntax.ToFullString(); - context.AddSource($"{Constants.RouteFileName}.g", source); - } -} - -#nullable disable \ No newline at end of file diff --git a/Net.Sdk.Web.Extensions.SourceGenerators/UseRoutesGenerator.cs b/Net.Sdk.Web.Extensions.SourceGenerators/UseRoutesGenerator.cs new file mode 100644 index 0000000..23916ab --- /dev/null +++ b/Net.Sdk.Web.Extensions.SourceGenerators/UseRoutesGenerator.cs @@ -0,0 +1,323 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Sybil; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; + +namespace Net.Sdk.Web.Extensions.SourceGenerators; + +[Generator(LanguageNames.CSharp)] +#nullable enable +public class UseRoutesGenerator : IIncrementalGenerator +{ + public void Initialize(IncrementalGeneratorInitializationContext context) + { + context.RegisterPostInitializationOutput(GenerateRouteFilterAttribute); + + var classDeclarations = context.SyntaxProvider.CreateSyntaxProvider( + predicate: static (s, _) => s is ClassDeclarationSyntax, + transform: static (ctx, _) => GetFilteredClassDeclarationSyntax(ctx)).Where(static c => c is not null); + var compilationAndClasses = context.CompilationProvider.Combine(classDeclarations.Collect()); + context.RegisterSourceOutput(compilationAndClasses, (sourceProductionContext, tuple) => Execute(tuple.Left, tuple.Right, sourceProductionContext)); + } + + private static (ClassDeclarationSyntax, List)? GetFilteredClassDeclarationSyntax(GeneratorSyntaxContext context) + { + var classDeclarationSyntax = (ClassDeclarationSyntax)context.Node; + if (classDeclarationSyntax.AttributeLists + .SelectMany(l => l.Attributes) + .OfType() + .Any(s => s.Name.ToString() is Constants.RouteAttributeName or Constants.RouteAttributeShortName)) + { + return ( + classDeclarationSyntax, + classDeclarationSyntax.Members + .OfType() + .Where(m => m.AttributeLists + .SelectMany(l => l.Attributes) + .OfType() + .Any(s => s.Name.ToString() is Constants.GetAttributeName + or Constants.GetAttributeShortName + or Constants.PostAttributeName + or Constants.PostAttributeShortName + or Constants.PutAttributeName + or Constants.PutAttributeShortName + or Constants.DeleteAttributeName + or Constants.DeleteAttributeShortName)) + .ToList()); + } + + return default; + } + + private static void Execute(Compilation compilation, ImmutableArray<(ClassDeclarationSyntax, List)?> classToMethodMapping, SourceProductionContext sourceProductionContext) + { + if (classToMethodMapping.IsDefaultOrEmpty) + { + return; + } + + var maybeLanguageVersion = (compilation.SyntaxTrees.FirstOrDefault()?.Options as CSharpParseOptions)?.LanguageVersion; + if (!maybeLanguageVersion.HasValue) + { + return; + } + + var languageVersion = maybeLanguageVersion.Value; + var builder = SyntaxBuilder.CreateCompilationUnit() + .WithUsing(Constants.UsingSystemThreading) + .WithUsing(Constants.UsingMicrosoftAspNetCoreRouting) + .WithUsing(Constants.UsingMicrosoftAspNetCoreHttp) + .WithUsing(Constants.UsingMicrosoftAspNetCoreBuilder) + .WithUsing(Constants.UsingMicrosoftAspNetCoreMvc); + + var routeUsings = new HashSet(); + + var namespaceBuilder = languageVersion >= LanguageVersion.CSharp10 ? SyntaxBuilder.CreateFileScopedNamespace(Constants.Namespace) : SyntaxBuilder.CreateNamespace(Constants.Namespace); + builder.WithNamespace(namespaceBuilder); + var webAppBuilder = SyntaxBuilder.CreateClass(Constants.WebApplicationExtensionsName) + .WithModifiers($"{Constants.Public} {Constants.Static}"); + namespaceBuilder.WithClass(webAppBuilder); + var useRoutesWebAppMethodBuilder = SyntaxBuilder.CreateMethod(Constants.WebApplicationTypeName, Constants.UseRoutesMethodName) + .WithThisParameter(Constants.WebApplicationTypeName, Constants.BuilderParameterName) + .WithModifiers($"{Constants.Public} {Constants.Static}"); + webAppBuilder.WithMethod(useRoutesWebAppMethodBuilder); + + var useRoutesWebAppBody = new StringBuilder(); + foreach (var classToMethodMap in classToMethodMapping) + { + if (classToMethodMap is null) + { + continue; + } + + var classDeclarationSyntax = classToMethodMap.Value.Item1; + var methodDeclarationSyntaxes = classToMethodMap.Value.Item2; + if (methodDeclarationSyntaxes is null || + methodDeclarationSyntaxes.Count == 0) + { + continue; + } + + if (GetParentOfType(classDeclarationSyntax) is BaseNamespaceDeclarationSyntax baseNamespaceDeclarationSyntax) + { + routeUsings.Add(baseNamespaceDeclarationSyntax.Name.ToString()); + foreach(var u in baseNamespaceDeclarationSyntax.Usings) + { + routeUsings.Add(u.NamespaceOrType.ToString()); + } + } + + if (GetParentOfType(classDeclarationSyntax) is CompilationUnitSyntax parentUnitSyntax) + { + foreach (var u in parentUnitSyntax.Usings) + { + routeUsings.Add(u.NamespaceOrType.ToString()); + } + } + + var classAttributes = classDeclarationSyntax.AttributeLists + .SelectMany(l => l.Attributes) + .OfType().ToList(); + var basePattern = classAttributes.FirstOrDefault(a => a.Name.ToString() == Constants.RouteAttributeName || a.Name.ToString() == Constants.RouteAttributeShortName)? + .ArgumentList?.Arguments.OfType() + .FirstOrDefault(a => a.NameEquals?.Name.Identifier.Text == Constants.PatternPropertyName)?.Expression.ToString().Trim('"'); + basePattern ??= string.Empty; + foreach(var methodDeclarationSyntax in methodDeclarationSyntaxes) + { + var methodAttributes = methodDeclarationSyntax.AttributeLists + .SelectMany(l => l.Attributes) + .OfType().ToList(); + var pattern = methodAttributes.FirstOrDefault(a => a.Name.ToString() is Constants.GetAttributeName + or Constants.GetAttributeShortName + or Constants.PostAttributeName + or Constants.PostAttributeShortName + or Constants.PutAttributeName + or Constants.PutAttributeShortName + or Constants.DeleteAttributeName + or Constants.DeleteAttributeShortName) + .ArgumentList?.Arguments + .OfType() + .FirstOrDefault(a => a.NameEquals?.Name.Identifier.Text == Constants.PatternPropertyName) + ?.Expression.ToString().Trim('"'); + + if (string.IsNullOrWhiteSpace(basePattern) && + string.IsNullOrWhiteSpace(pattern)) + { + continue; + } + + var outerPattern = $"/{basePattern.Trim('/')}"; + var innerPattern = $"/{pattern?.Trim('/')}"; + var finalPattern = $"{outerPattern}{innerPattern}".Replace("//", "/"); + + if (methodAttributes.FirstOrDefault(a => a.Name.ToString() is Constants.GetAttributeName or Constants.GetAttributeShortName) is AttributeSyntax && + GetMethodBodyByType("Get", finalPattern, classDeclarationSyntax, methodDeclarationSyntax, compilation, routeUsings) is string getMethodBody) + { + useRoutesWebAppBody.AppendLine(getMethodBody); + } + else if (methodAttributes.FirstOrDefault(a => a.Name.ToString() is Constants.PostAttributeName or Constants.PostAttributeShortName) is AttributeSyntax && + GetMethodBodyByType("Post", finalPattern, classDeclarationSyntax, methodDeclarationSyntax, compilation, routeUsings) is string postMethodBody) + { + useRoutesWebAppBody.AppendLine(postMethodBody); + } + else if (methodAttributes.FirstOrDefault(a => a.Name.ToString() is Constants.PutAttributeName or Constants.PutAttributeShortName) is AttributeSyntax && + GetMethodBodyByType("Put", finalPattern, classDeclarationSyntax, methodDeclarationSyntax, compilation, routeUsings) is string putMethodBoty) + { + useRoutesWebAppBody.AppendLine(putMethodBoty); + } + else if (methodAttributes.FirstOrDefault(a => a.Name.ToString() is Constants.DeleteAttributeName or Constants.DeleteAttributeShortName) is AttributeSyntax && + GetMethodBodyByType("Delete", finalPattern, classDeclarationSyntax, methodDeclarationSyntax, compilation, routeUsings) is string deleteMethodBody) + { + useRoutesWebAppBody.AppendLine(deleteMethodBody); + } + } + } + + useRoutesWebAppBody.AppendLine("return builder;"); + useRoutesWebAppMethodBuilder.WithBody(useRoutesWebAppBody.ToString()); + foreach (var classUsing in routeUsings) + { + builder.WithUsing(classUsing); + } + + var fileSource = builder.Build().ToFullString(); + sourceProductionContext.AddSource($"{Constants.EndpointRouteBuilderExtensionsName}.g", fileSource); + } + + private static string? GetMethodBodyByType( + string type, + string pattern, + ClassDeclarationSyntax classDeclarationSyntax, + MethodDeclarationSyntax methodDeclarationSyntax, + Compilation compilation, + HashSet usings) + { + // Go over all attributes on the method and parent class and find all RouteFilters + var routeFilterSb = new StringBuilder(); + foreach(var attribute in methodDeclarationSyntax.AttributeLists + .Concat(classDeclarationSyntax.AttributeLists) + .SelectMany(a => a.Attributes) + .OfType() + .Where(a => a.Name.ToString() == Constants.RouteFilterAttributeName || a.Name.ToString() == Constants.RouteFilterAttributeShortName)) + { + var semanticModel = compilation.GetSemanticModel(methodDeclarationSyntax.SyntaxTree); + if (GetRouteFilterTypeName(semanticModel, attribute) is not string routeFilterType) + { + continue; + } + + var namespaceIndex = routeFilterType.LastIndexOf("."); + if (namespaceIndex > 0) + { + var routeFilterNamespace = routeFilterType.Substring(0, namespaceIndex); + routeFilterType = routeFilterType.Substring(namespaceIndex + 1); + usings.Add(routeFilterNamespace); + } + + routeFilterSb.Append('\n') + .Append(@$".AddEndpointFilter<{routeFilterType}>()"); + } + + // Get the method signature and generate it in Map + var parameters = GetMethodParameters(methodDeclarationSyntax); + var variables = string.Join(", ", methodDeclarationSyntax.ParameterList.Parameters + .Select(param => param.Identifier.Text)); + + return @$" + builder.Map{type}(""{pattern}"", async ({classDeclarationSyntax.Identifier} route{(parameters.Length > 0 ? $", {parameters}" : "")}) => + {{ + return await route.{methodDeclarationSyntax.Identifier}({variables}); + }}){routeFilterSb};"; + } + + private static string GetMethodParameters(MethodDeclarationSyntax methodDeclaration) + { + var parameters = methodDeclaration.ParameterList.Parameters + .Select(param => + { + var parameterType = param.Type?.ToString(); + var parameterName = param.Identifier.Text; + var attributes = param.AttributeLists + .SelectMany(attrList => attrList.Attributes) + .Select(attr => $"[{attr.Name}]"); + + var attributesString = string.Join(" ", attributes); + return $"{attributesString} {parameterType} {parameterName}".Trim(); + }); + + var parametersString = string.Join(", ", parameters); + return parametersString; + } + + public static string? GetRouteFilterTypeName(SemanticModel semanticModel, AttributeSyntax attributeSyntax) + { + var routeFilterTypeArgument = attributeSyntax.ArgumentList?.Arguments + .FirstOrDefault(arg => arg.NameEquals?.Name.Identifier.Text == Constants.RouteFilterTypePropertyName); + + if (routeFilterTypeArgument is null) + { + return default; + } + + if (routeFilterTypeArgument.Expression is not TypeOfExpressionSyntax typeExpression) + { + return default; + } + + var typeSymbol = semanticModel.GetTypeInfo(typeExpression.Type); + if (typeSymbol.Type is INamedTypeSymbol namedTypeSymbol) + { + // Get the fully qualified name (namespace + type name) + return namedTypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + } + + return default; + } + + private static T? GetParentOfType(SyntaxNode syntaxNode) + { + if (syntaxNode.Parent is null) + { + return default; + } + + if (syntaxNode.Parent is T parentNode) + { + return parentNode; + } + + return GetParentOfType(syntaxNode.Parent); + } + + private static void GenerateRouteFilterAttribute(IncrementalGeneratorPostInitializationContext context) + { + var builder = SyntaxBuilder.CreateCompilationUnit() + .WithNamespace( + SyntaxBuilder.CreateNamespace(Constants.Namespace) + .WithClass(SyntaxBuilder.CreateClass(Constants.RouteFilterAttributeName) + .WithModifier(Constants.Public) + .WithConstructor(SyntaxBuilder.CreateConstructor(Constants.RouteFilterAttributeName) + .WithModifier(Constants.Public)) + .WithAttribute(SyntaxBuilder.CreateAttribute("AttributeUsage") + .WithArgument(AttributeTargets.Class) + .WithArgument("Inherited", false) + .WithArgument("AllowMultiple", true)) + .WithBaseClass(nameof(Attribute)) + .WithProperty(SyntaxBuilder.CreateProperty($"{Constants.TypeType}?", Constants.RouteFilterTypePropertyName) + .WithModifier(Constants.Public) + .WithAccessor(SyntaxBuilder.CreateGetter()) + .WithAccessor(SyntaxBuilder.CreateSetter())))); + + + var syntax = builder.Build(); + var source = syntax.ToFullString(); + context.AddSource($"{Constants.RouteFilterAttributeName}.g", $"#nullable enable\n{source}\n#nullable disable\n"); + } +} + +#nullable disable \ No newline at end of file diff --git a/Net.Sdk.Web.Extensions.SourceGenerators/WithRoutesGenerator.cs b/Net.Sdk.Web.Extensions.SourceGenerators/WithRoutesGenerator.cs index 775ad4c..d26b8c8 100644 --- a/Net.Sdk.Web.Extensions.SourceGenerators/WithRoutesGenerator.cs +++ b/Net.Sdk.Web.Extensions.SourceGenerators/WithRoutesGenerator.cs @@ -28,10 +28,7 @@ public class WithRoutesGenerator : IIncrementalGenerator if (classDeclarationSyntax.AttributeLists .SelectMany(l => l.Attributes) .OfType() - .Any(s => s.Name.ToString() == Constants.GetAttributeName || s.Name.ToString() == Constants.GetAttributeShortName || - s.Name.ToString() == Constants.PostAttributeName || s.Name.ToString() == Constants.PostAttributeShortName || - s.Name.ToString() == Constants.PutAttributeName || s.Name.ToString() == Constants.PutAttributeShortName || - s.Name.ToString() == Constants.DeleteAttributeName || s.Name.ToString() == Constants.DeleteAttributeShortName)) + .Any(s => s.Name.ToString() == Constants.RouteAttributeName || s.Name.ToString() == Constants.RouteAttributeShortName)) { return classDeclarationSyntax; }