RequestDelegateGenerator.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT license.
  3. using System;
  4. using System.Globalization;
  5. using System.IO;
  6. using System.Linq;
  7. using System.Text;
  8. using Microsoft.AspNetCore.App.Analyzers.Infrastructure;
  9. using Microsoft.CodeAnalysis;
  10. using Microsoft.CodeAnalysis.CSharp.Syntax;
  11. using Microsoft.CodeAnalysis.Operations;
  12. using Microsoft.AspNetCore.Http.Generators.StaticRouteHandlerModel;
  13. using Microsoft.AspNetCore.Http.Generators.StaticRouteHandlerModel.Emitters;
  14. namespace Microsoft.AspNetCore.Http.Generators;
  15. [Generator]
  16. public sealed class RequestDelegateGenerator : IIncrementalGenerator
  17. {
  18. private static readonly string[] _knownMethods =
  19. {
  20. "MapGet",
  21. "MapPost",
  22. "MapPut",
  23. "MapDelete",
  24. "MapPatch",
  25. };
  26. public void Initialize(IncrementalGeneratorInitializationContext context)
  27. {
  28. var endpointsWithDiagnostics = context.SyntaxProvider.CreateSyntaxProvider(
  29. predicate: static (node, _) => node is InvocationExpressionSyntax
  30. {
  31. Expression: MemberAccessExpressionSyntax
  32. {
  33. Name: IdentifierNameSyntax
  34. {
  35. Identifier: { ValueText: var method }
  36. }
  37. },
  38. ArgumentList: { Arguments: { Count: 2 } args }
  39. } && _knownMethods.Contains(method),
  40. transform: static (context, token) =>
  41. {
  42. var operation = context.SemanticModel.GetOperation(context.Node, token);
  43. var wellKnownTypes = WellKnownTypes.GetOrCreate(context.SemanticModel.Compilation);
  44. if (operation is IInvocationOperation { Arguments: { Length: 3 } parameters } invocationOperation &&
  45. invocationOperation.GetRouteHandlerArgument() is { Parameter.Type: {} delegateType } &&
  46. SymbolEqualityComparer.Default.Equals(delegateType, wellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_Delegate)))
  47. {
  48. return new Endpoint(invocationOperation, wellKnownTypes, context.SemanticModel);
  49. }
  50. return null;
  51. })
  52. .Where(static endpoint => endpoint != null)
  53. .WithTrackingName(GeneratorSteps.EndpointModelStep);
  54. context.RegisterSourceOutput(endpointsWithDiagnostics, (context, endpoint) =>
  55. {
  56. foreach (var diagnostic in endpoint!.Diagnostics)
  57. {
  58. context.ReportDiagnostic(diagnostic);
  59. }
  60. });
  61. var endpoints = endpointsWithDiagnostics
  62. .Where(endpoint => endpoint!.Diagnostics.Count == 0)
  63. .WithTrackingName(GeneratorSteps.EndpointsWithoutDiagnosicsStep);
  64. var thunks = endpoints.Select((endpoint, _) =>
  65. {
  66. using var stringWriter = new StringWriter(CultureInfo.InvariantCulture);
  67. using var codeWriter = new CodeWriter(stringWriter, baseIndent: 3);
  68. codeWriter.InitializeIndent();
  69. codeWriter.WriteLine($"[{endpoint!.EmitSourceKey()}] = (");
  70. codeWriter.Indent++;
  71. codeWriter.WriteLine("(methodInfo, options) =>");
  72. codeWriter.StartBlock();
  73. codeWriter.WriteLine(@"Debug.Assert(options?.EndpointBuilder != null, ""EndpointBuilder not found."");");
  74. codeWriter.WriteLine($"options.EndpointBuilder.Metadata.Add(new SourceKey{endpoint!.EmitSourceKey()});");
  75. codeWriter.WriteLine("return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };");
  76. codeWriter.EndBlockWithComma();
  77. codeWriter.WriteLine("(del, options, inferredMetadataResult) =>");
  78. codeWriter.StartBlock();
  79. codeWriter.WriteLine($"var handler = ({endpoint!.EmitHandlerDelegateCast()})del;");
  80. codeWriter.WriteLine("EndpointFilterDelegate? filteredInvocation = null;");
  81. endpoint!.EmitRouteOrQueryResolver(codeWriter);
  82. endpoint!.EmitJsonBodyOrServicePreparation(codeWriter);
  83. endpoint!.Response?.EmitJsonPreparation(codeWriter);
  84. if (endpoint.NeedsParameterArray)
  85. {
  86. codeWriter.WriteLine("var parameters = del.Method.GetParameters();");
  87. }
  88. codeWriter.WriteLineNoTabs(string.Empty);
  89. codeWriter.WriteLine("if (options?.EndpointBuilder?.FilterFactories.Count > 0)");
  90. codeWriter.StartBlock();
  91. codeWriter.WriteLine(endpoint!.Response?.IsAwaitable == true
  92. ? "filteredInvocation = GeneratedRouteBuilderExtensionsCore.BuildFilterDelegate(async ic =>"
  93. : "filteredInvocation = GeneratedRouteBuilderExtensionsCore.BuildFilterDelegate(ic =>");
  94. codeWriter.StartBlock();
  95. codeWriter.WriteLine("if (ic.HttpContext.Response.StatusCode == 400)");
  96. codeWriter.StartBlock();
  97. codeWriter.WriteLine("return ValueTask.FromResult<object?>(Results.Empty);");
  98. codeWriter.EndBlock();
  99. endpoint.EmitFilteredInvocation(codeWriter);
  100. codeWriter.EndBlockWithComma();
  101. codeWriter.WriteLine("options.EndpointBuilder,");
  102. codeWriter.WriteLine("handler.Method);");
  103. codeWriter.EndBlock();
  104. codeWriter.WriteLineNoTabs(string.Empty);
  105. endpoint.EmitRequestHandler(codeWriter);
  106. codeWriter.WriteLineNoTabs(string.Empty);
  107. endpoint.EmitFilteredRequestHandler(codeWriter);
  108. codeWriter.WriteLineNoTabs(string.Empty);
  109. codeWriter.WriteLine("RequestDelegate targetDelegate = filteredInvocation is null ? RequestHandler : RequestHandlerFiltered;");
  110. codeWriter.WriteLine("var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection<object>.Empty;");
  111. codeWriter.WriteLine("return new RequestDelegateResult(targetDelegate, metadata);");
  112. codeWriter.Indent--;
  113. codeWriter.Write("}),");
  114. return stringWriter.ToString();
  115. });
  116. var stronglyTypedEndpointDefinitions = endpoints
  117. .Collect()
  118. .Select((endpoints, _) =>
  119. {
  120. var dedupedByDelegate = endpoints.Distinct<Endpoint>(EndpointDelegateComparer.Instance);
  121. using var stringWriter = new StringWriter(CultureInfo.InvariantCulture);
  122. using var codeWriter = new CodeWriter(stringWriter, baseIndent: 2);
  123. foreach (var endpoint in dedupedByDelegate)
  124. {
  125. codeWriter.WriteLine($"internal static global::Microsoft.AspNetCore.Builder.RouteHandlerBuilder {endpoint!.HttpMethod}(");
  126. codeWriter.Indent++;
  127. codeWriter.WriteLine("this global::Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints,");
  128. codeWriter.WriteLine(@"[global::System.Diagnostics.CodeAnalysis.StringSyntax(""Route"")] string pattern,");
  129. codeWriter.WriteLine($"global::{endpoint!.EmitHandlerDelegateType()} handler,");
  130. codeWriter.WriteLine(@"[global::System.Runtime.CompilerServices.CallerFilePath] string filePath = """",");
  131. codeWriter.WriteLine("[global::System.Runtime.CompilerServices.CallerLineNumber]int lineNumber = 0)");
  132. codeWriter.Indent--;
  133. codeWriter.StartBlock();
  134. codeWriter.WriteLine("return global::Microsoft.AspNetCore.Http.Generated.GeneratedRouteBuilderExtensionsCore.MapCore(");
  135. codeWriter.Indent++;
  136. codeWriter.WriteLine("endpoints,");
  137. codeWriter.WriteLine("pattern,");
  138. codeWriter.WriteLine("handler,");
  139. codeWriter.WriteLine($"{endpoint!.EmitVerb()},");
  140. codeWriter.WriteLine("filePath,");
  141. codeWriter.WriteLine("lineNumber);");
  142. codeWriter.Indent--;
  143. codeWriter.EndBlock();
  144. }
  145. return stringWriter.ToString();
  146. });
  147. var endpointHelpers = endpoints
  148. .Collect()
  149. .Select((endpoints, _) =>
  150. {
  151. var hasJsonBodyOrService = endpoints.Any(endpoint => endpoint!.EmitterContext.HasJsonBodyOrService);
  152. var hasJsonBody = endpoints.Any(endpoint => endpoint!.EmitterContext.HasJsonBody);
  153. var hasRouteOrQuery = endpoints.Any(endpoint => endpoint!.EmitterContext.HasRouteOrQuery);
  154. var hasBindAsync = endpoints.Any(endpoint => endpoint!.EmitterContext.HasBindAsync);
  155. var hasParsable = endpoints.Any(endpoint => endpoint!.EmitterContext.HasParsable);
  156. var hasJsonResponse = endpoints.Any(endpoint => endpoint!.EmitterContext.HasJsonResponse);
  157. using var stringWriter = new StringWriter(CultureInfo.InvariantCulture);
  158. using var codeWriter = new CodeWriter(stringWriter, baseIndent: 0);
  159. if (hasRouteOrQuery)
  160. {
  161. codeWriter.WriteLine(RequestDelegateGeneratorSources.ResolveFromRouteOrQueryMethod);
  162. }
  163. if (hasJsonResponse)
  164. {
  165. codeWriter.WriteLine(RequestDelegateGeneratorSources.WriteToResponseAsyncMethod);
  166. }
  167. if (hasJsonBody || hasJsonBodyOrService)
  168. {
  169. codeWriter.WriteLine(RequestDelegateGeneratorSources.TryResolveBodyAsyncMethod);
  170. }
  171. if (hasBindAsync)
  172. {
  173. codeWriter.WriteLine(RequestDelegateGeneratorSources.BindAsyncMethod);
  174. }
  175. if (hasJsonBodyOrService)
  176. {
  177. codeWriter.WriteLine(RequestDelegateGeneratorSources.TryResolveJsonBodyOrServiceAsyncMethod);
  178. }
  179. if (hasParsable)
  180. {
  181. codeWriter.WriteLine(RequestDelegateGeneratorSources.TryParseExplicitMethod);
  182. }
  183. return stringWriter.ToString();
  184. });
  185. var thunksAndEndpoints = thunks.Collect().Combine(stronglyTypedEndpointDefinitions).Combine(endpointHelpers);
  186. context.RegisterSourceOutput(thunksAndEndpoints, (context, sources) =>
  187. {
  188. var ((thunks, endpointsCode), helpers) = sources;
  189. if (thunks.IsDefaultOrEmpty || string.IsNullOrEmpty(endpointsCode))
  190. {
  191. return;
  192. }
  193. var thunksCode = new StringBuilder();
  194. foreach (var thunk in thunks)
  195. {
  196. thunksCode.AppendLine(thunk);
  197. }
  198. var code = RequestDelegateGeneratorSources.GetGeneratedRouteBuilderExtensionsSource(
  199. genericThunks: string.Empty,
  200. thunks: thunksCode.ToString(),
  201. endpoints: endpointsCode,
  202. helperMethods: helpers ?? string.Empty);
  203. context.AddSource("GeneratedRouteBuilderExtensions.g.cs", code);
  204. });
  205. }
  206. }