-
Notifications
You must be signed in to change notification settings - Fork 10.4k
Add initial parameter support to RequestDelegateGenerator #46407
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
Changes from all commits
63bc01f
ae9e6fc
4164537
bba095c
141fad8
258d799
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
|
@@ -12,61 +13,118 @@ namespace Microsoft.AspNetCore.Http.Generators.StaticRouteHandlerModel; | |
|
||
internal class Endpoint | ||
{ | ||
public string HttpMethod { get; } | ||
public EndpointRoute Route { get; } | ||
public EndpointResponse Response { get; } | ||
public List<DiagnosticDescriptor> Diagnostics { get; } = new List<DiagnosticDescriptor>(); | ||
public (string, int) Location { get; } | ||
public IInvocationOperation Operation { get; } | ||
|
||
private WellKnownTypes WellKnownTypes { get; } | ||
private string? _argumentListCache; | ||
|
||
public Endpoint(IInvocationOperation operation, WellKnownTypes wellKnownTypes) | ||
{ | ||
Operation = operation; | ||
WellKnownTypes = wellKnownTypes; | ||
Location = GetLocation(); | ||
HttpMethod = GetHttpMethod(); | ||
Response = new EndpointResponse(Operation, wellKnownTypes); | ||
Route = new EndpointRoute(Operation); | ||
} | ||
Location = GetLocation(operation); | ||
HttpMethod = GetHttpMethod(operation); | ||
|
||
private (string, int) GetLocation() | ||
{ | ||
var filePath = Operation.Syntax.SyntaxTree.FilePath; | ||
var span = Operation.Syntax.SyntaxTree.GetLineSpan(Operation.Syntax.Span); | ||
var lineNumber = span.EndLinePosition.Line + 1; | ||
return (filePath, lineNumber); | ||
} | ||
if (!operation.TryGetRouteHandlerPattern(out var routeToken)) | ||
{ | ||
Diagnostics.Add(DiagnosticDescriptors.UnableToResolveRoutePattern); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm inclined to have each component of the model maintain its own diagnostics. As we build up the diagnostics we emit, it'll make it easier to warn for specific scenarios that might not be immediately accessible at the top-level. For example, let's say that we find a The downside is it does make the code to aggregate them all when we emit a little honky but that seems worth it for the benefits of having diagnostics localized to where they would be thrown (similar to an exception). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would be better to give EndpointParameter a backreference to Endpoints.Diagnostics if we want to capture the diagnostic while constructing the parameter. I just didn't like greedily creating a list for each small component even though it's not that expensive. The aggregation and repeated declarations were kind of verbose too as you say. |
||
return; | ||
} | ||
|
||
private string GetHttpMethod() | ||
{ | ||
var syntax = (InvocationExpressionSyntax)Operation.Syntax; | ||
var expression = (MemberAccessExpressionSyntax)syntax.Expression; | ||
var name = (IdentifierNameSyntax)expression.Name; | ||
var identifier = name.Identifier; | ||
return identifier.ValueText; | ||
RoutePattern = routeToken.ValueText; | ||
|
||
if (!operation.TryGetRouteHandlerMethod(out var method)) | ||
{ | ||
Diagnostics.Add(DiagnosticDescriptors.UnableToResolveMethod); | ||
return; | ||
} | ||
|
||
Response = new EndpointResponse(method, wellKnownTypes); | ||
|
||
if (method.Parameters.Length == 0) | ||
{ | ||
return; | ||
} | ||
|
||
var parameters = new EndpointParameter[method.Parameters.Length]; | ||
|
||
for (var i = 0; i < method.Parameters.Length; i++) | ||
{ | ||
var parameter = new EndpointParameter(method.Parameters[i], wellKnownTypes); | ||
|
||
if (parameter.Source == EndpointParameterSource.Unknown) | ||
{ | ||
Diagnostics.Add(DiagnosticDescriptors.GetUnableToResolveParameterDescriptor(parameter.Name)); | ||
return; | ||
} | ||
|
||
parameters[i] = parameter; | ||
} | ||
|
||
Parameters = parameters; | ||
} | ||
|
||
public override bool Equals(object o) | ||
public string HttpMethod { get; } | ||
public string? RoutePattern { get; } | ||
public EndpointResponse? Response { get; } | ||
public EndpointParameter[] Parameters { get; } = Array.Empty<EndpointParameter>(); | ||
public string EmitArgumentList() => _argumentListCache ??= string.Join(", ", Parameters.Select(p => p.EmitArgument())); | ||
|
||
public List<DiagnosticDescriptor> Diagnostics { get; } = new List<DiagnosticDescriptor>(); | ||
|
||
public (string File, int LineNumber) Location { get; } | ||
public IInvocationOperation Operation { get; } | ||
|
||
public override bool Equals(object o) => | ||
o is Endpoint other && Location == other.Location && SignatureEquals(this, other); | ||
|
||
public override int GetHashCode() => | ||
HashCode.Combine(Location, GetSignatureHashCode(this)); | ||
|
||
public static bool SignatureEquals(Endpoint a, Endpoint b) | ||
{ | ||
if (o is null) | ||
if (!a.Response.WrappedResponseType.Equals(b.Response.WrappedResponseType, StringComparison.Ordinal) || | ||
!a.HttpMethod.Equals(b.HttpMethod, StringComparison.Ordinal) || | ||
a.Parameters.Length != b.Parameters.Length) | ||
{ | ||
return false; | ||
} | ||
|
||
if (o is Endpoint endpoint) | ||
for (var i = 0; i < a.Parameters.Length; i++) | ||
{ | ||
return endpoint.HttpMethod.Equals(HttpMethod, StringComparison.OrdinalIgnoreCase) && | ||
endpoint.Location.Item1.Equals(Location.Item1, StringComparison.OrdinalIgnoreCase) && | ||
endpoint.Location.Item2.Equals(Location.Item2) && | ||
endpoint.Response.Equals(Response) && | ||
endpoint.Diagnostics.SequenceEqual(Diagnostics); | ||
if (a.Parameters[i].Equals(b.Parameters[i])) | ||
{ | ||
return false; | ||
} | ||
} | ||
|
||
return false; | ||
return true; | ||
} | ||
|
||
public override int GetHashCode() => | ||
HashCode.Combine(HttpMethod, Route, Location, Response, Diagnostics); | ||
public static int GetSignatureHashCode(Endpoint endpoint) | ||
{ | ||
var hashCode = new HashCode(); | ||
hashCode.Add(endpoint.Response.WrappedResponseType); | ||
hashCode.Add(endpoint.HttpMethod); | ||
|
||
foreach (var parameter in endpoint.Parameters) | ||
{ | ||
hashCode.Add(parameter); | ||
} | ||
|
||
return hashCode.ToHashCode(); | ||
} | ||
|
||
private static (string, int) GetLocation(IInvocationOperation operation) | ||
{ | ||
var filePath = operation.Syntax.SyntaxTree.FilePath; | ||
var span = operation.Syntax.SyntaxTree.GetLineSpan(operation.Syntax.Span); | ||
var lineNumber = span.StartLinePosition.Line + 1; | ||
return (filePath, lineNumber); | ||
} | ||
|
||
private static string GetHttpMethod(IInvocationOperation operation) | ||
{ | ||
var syntax = (InvocationExpressionSyntax)operation.Syntax; | ||
var expression = (MemberAccessExpressionSyntax)syntax.Expression; | ||
var name = (IdentifierNameSyntax)expression.Name; | ||
var identifier = name.Identifier; | ||
return identifier.ValueText; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,14 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
using System; | ||
using System.Collections; | ||
|
||
using System.Collections.Generic; | ||
|
||
namespace Microsoft.AspNetCore.Http.Generators.StaticRouteHandlerModel; | ||
|
||
internal sealed class EndpointDelegateComparer : IEqualityComparer<Endpoint>, IComparer<Endpoint> | ||
internal sealed class EndpointDelegateComparer : IEqualityComparer<Endpoint> | ||
{ | ||
public static readonly EndpointDelegateComparer Instance = new EndpointDelegateComparer(); | ||
|
||
public bool Equals(Endpoint a, Endpoint b) => Compare(a, b) == 0; | ||
|
||
public int GetHashCode(Endpoint endpoint) => HashCode.Combine( | ||
endpoint.Response.WrappedResponseType, | ||
endpoint.Response.IsVoid, | ||
endpoint.Response.IsAwaitable, | ||
endpoint.HttpMethod); | ||
|
||
public int Compare(Endpoint a, Endpoint b) | ||
{ | ||
if (a.Response.IsAwaitable == b.Response.IsAwaitable && | ||
a.Response.IsVoid == b.Response.IsVoid && | ||
a.Response.WrappedResponseType.Equals(b.Response.WrappedResponseType, StringComparison.Ordinal) && | ||
a.HttpMethod.Equals(b.HttpMethod, StringComparison.Ordinal)) | ||
{ | ||
return 0; | ||
} | ||
return -1; | ||
} | ||
public bool Equals(Endpoint a, Endpoint b) => Endpoint.SignatureEquals(a, b); | ||
public int GetHashCode(Endpoint endpoint) => Endpoint.GetSignatureHashCode(endpoint); | ||
} |
Uh oh!
There was an error while loading. Please reload this page.