Skip to content

Commit fe448a7

Browse files
authored
Resolve interceptor location using identifier name (#50245)
* Resolve interceptor location using identifier name * Address feedback
1 parent 0b8d2e1 commit fe448a7

File tree

2 files changed

+68
-7
lines changed

2 files changed

+68
-7
lines changed

src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Endpoint.cs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -142,14 +142,23 @@ public static int GetSignatureHashCode(Endpoint endpoint)
142142

143143
private static (string, int, int) GetLocation(IInvocationOperation operation)
144144
{
145-
var operationSpan = operation.Syntax.Span;
145+
// The invocation expression consists of two properties:
146+
// - Expression: which is a `MemberAccessExpressionSyntax` that represents the method being invoked.
147+
// - ArgumentList: the list of arguments being invoked.
148+
// Here, we resolve the `MemberAccessExpressionSyntax` to get the location of the method being invoked.
149+
var memberAccessorExpression = ((MemberAccessExpressionSyntax)((InvocationExpressionSyntax)operation.Syntax).Expression);
150+
// The `MemberAccessExpressionSyntax` in turn includes three properties:
151+
// - Expression: the expression that is being accessed.
152+
// - OperatorToken: the operator token, typically the dot separate.
153+
// - Name: the name of the member being accessed, typically `MapGet` or `MapPost`, etc.
154+
// Here, we resolve the `Name` to extract the location of the method being invoked.
155+
var invocationNameSpan = memberAccessorExpression.Name.Span;
156+
// Resolve LineSpan associated with the name span so we can resolve the line and character number.
157+
var lineSpan = operation.Syntax.SyntaxTree.GetLineSpan(invocationNameSpan);
158+
// Resolve the filepath of the invocation while accounting for source mapped paths.
146159
var filePath = operation.Syntax.SyntaxTree.GetInterceptorFilePath(operation.SemanticModel?.Compilation.Options.SourceReferenceResolver);
147-
var span = operation.Syntax.SyntaxTree.GetLineSpan(operationSpan);
148-
var lineNumber = span.StartLinePosition.Line + 1;
149-
// Calculate the character offset to the end of the Map invocation detected
150-
var invocationLength = ((MemberAccessExpressionSyntax)((InvocationExpressionSyntax)operation.Syntax).Expression).Expression.Span.Length;
151-
var characterNumber = span.StartLinePosition.Character + invocationLength + 2;
152-
return (filePath, lineNumber, characterNumber);
160+
// LineSpan.LinePosition is 0-indexed, but we want to display 1-indexed line and character numbers in the interceptor attribute.
161+
return (filePath, lineSpan.StartLinePosition.Line + 1, lineSpan.StartLinePosition.Character + 1);
153162
}
154163

155164
private static string GetHttpMethod(IInvocationOperation operation)

src/Http/Http.Extensions/test/RequestDelegateGenerator/CompileTimeCreationTests.cs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33
using System.Collections.Immutable;
4+
using System.Globalization;
45
using System.Text;
56
using Microsoft.CodeAnalysis;
67
using Microsoft.AspNetCore.Http.RequestDelegateGenerator;
@@ -96,6 +97,57 @@ public async Task SupportsDifferentInterceptorsFromSameLocation()
9697
await VerifyAgainstBaselineUsingFile(updatedCompilation);
9798
}
9899

100+
[Fact]
101+
public async Task SupportsMapCallOnNewLine()
102+
{
103+
var source = """
104+
app
105+
.MapGet("/hello1/{id}", (int id) => $"Hello {id}!");
106+
EndpointRouteBuilderExtensions
107+
.MapGet(app, "/hello2/{id}", (int id) => $"Hello {id}!");
108+
app.
109+
MapGet("/hello1/{id}", (int id) => $"Hello {id}!");
110+
EndpointRouteBuilderExtensions.
111+
MapGet(app, "/hello2/{id}", (int id) => $"Hello {id}!");
112+
app.
113+
MapGet("/hello1/{id}", (int id) => $"Hello {id}!");
114+
EndpointRouteBuilderExtensions.
115+
MapGet(app, "/hello2/{id}", (int id) => $"Hello {id}!");
116+
app.
117+
118+
119+
MapGet("/hello1/{id}", (int id) => $"Hello {id}!");
120+
EndpointRouteBuilderExtensions.
121+
122+
123+
MapGet(app, "/hello2/{id}", (int id) => $"Hello {id}!");
124+
app.
125+
MapGet
126+
("/hello1/{id}", (int id) => $"Hello {id}!");
127+
EndpointRouteBuilderExtensions.
128+
MapGet
129+
(app, "/hello2/{id}", (int id) => $"Hello {id}!");
130+
app
131+
.
132+
MapGet
133+
("/hello1/{id}", (int id) => $"Hello {id}!");
134+
EndpointRouteBuilderExtensions
135+
.
136+
MapGet
137+
(app, "/hello2/{id}", (int id) => $"Hello {id}!");
138+
""";
139+
var (_, compilation) = await RunGeneratorAsync(source);
140+
var endpoints = GetEndpointsFromCompilation(compilation);
141+
142+
for (int i = 0; i < endpoints.Length; i++)
143+
{
144+
var httpContext = CreateHttpContext();
145+
httpContext.Request.RouteValues["id"] = i.ToString(CultureInfo.InvariantCulture);
146+
await endpoints[i].RequestDelegate(httpContext);
147+
await VerifyResponseBodyAsync(httpContext, $"Hello {i}!");
148+
}
149+
}
150+
99151
[Fact]
100152
public async Task SourceMapsAllPathsInAttribute()
101153
{

0 commit comments

Comments
 (0)