-
Notifications
You must be signed in to change notification settings - Fork 10.3k
Implement code fixer to change the code from calling Configure methods to using builder.Configuration #42630
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
Conversation
@@ -18,6 +18,9 @@ EndProject | |||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Mvc", "Mvc", "{819B136D-B8B6-46AE-8C4F-5469BBBFC46B}" | |||
EndProject | |||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc", "src\Mvc\Mvc\src\Microsoft.AspNetCore.Mvc.csproj", "{D4FBDF11-7A65-4205-8AF6-ABC190EFCF50}" | |||
ProjectSection(ProjectDependencies) = postProject |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What project is being added as a dependency here? Ideally, there should be no changes to the solution file...
var method = bodyExpression.Name; | ||
|
||
expression = expression.WithName(method); | ||
invocationExpression = invocationExpression.WithExpression(expression); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
invocationExpression = invocationExpression.WithExpression(expression); | |
invocationExpression = invocationExpression.WithExpression(expression).WithArgumentList(argument); |
This modifications can all be chained together.
|
||
if (invocation is InvocationExpressionSyntax { } invocationExpression) | ||
{ | ||
var expression = invocationExpression.Expression as MemberAccessExpressionSyntax; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
var expression = invocationExpression.Expression as MemberAccessExpressionSyntax; | |
var invokedMethodExpr = invocationExpression.Expression as MemberAccessExpressionSyntax; |
To make it a little clearer what this is.
builder = builder.WithName(configuration); | ||
expression = expression.WithExpression(builder); | ||
|
||
var initArgument = invocationExpression.ArgumentList.Arguments[0]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's add a guard here in case there are no arguments provided to the invocation.
expression = expression.WithExpression(builder); | ||
|
||
var initArgument = invocationExpression.ArgumentList.Arguments[0]; | ||
var lambdaExpression = initArgument.Expression as SimpleLambdaExpressionSyntax; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might help to move this to a inline static method called ExtractArgumentBody
to make it clearer what this is doing.
invocationExpression = invocationExpression.WithExpression(expression); | ||
invocationExpression = invocationExpression.WithArgumentList(argument); | ||
|
||
editor.ReplaceNode(invocation, invocationExpression); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
editor.ReplaceNode(invocation, invocationExpression); | |
editor.ReplaceNode(invocation, newInvocationExpression); |
So it is easier to distinguish between the unmodified and modified invocation.
|
||
var initArgument = invocationExpression.ArgumentList.Arguments[0]; | ||
var lambdaExpression = initArgument.Expression as SimpleLambdaExpressionSyntax; | ||
var body = lambdaExpression.ExpressionBody as InvocationExpressionSyntax; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add some comments here that show what each of these expressions reflects?
// logging.AddJson();
var body = lambdaExpression.ExpressionBody;
Or what have you.
var expectedDiagnostic = new DiagnosticResult(DiagnosticDescriptors.DisallowConfigureAppConfigureHostBuilder).WithArguments("ConfigureAppConfiguration").WithLocation(0); | ||
|
||
//Act | ||
await VerifyCS.VerifyCodeFixAsync(source, expectedDiagnostic, fixedSource); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's add a test scenario where the same ConfigureAppConfiguration
contains multiple calls inside it.
builder.Host.ConfigureAppConfiguration(builder =>
{
builder.AddJsonFile("foo.json", optional: true);
builder.AddEnvironmentVariables();
});
should become
builder.Configuration.
.AddJsonFile("foo.json", optional: true)
.AddEnvironmentVariables();
using Microsoft.Extensions.Configuration; | ||
var builder = WebApplication.CreateBuilder(args); | ||
builder.Configuration.AddJsonFile(""foo.json"", optional: true); | ||
builder.Configuration.AddJsonFile(""foo.json"", optional: true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't seem like the correct behavior here. I think it's unlikely for users to make the same invocations in different ConfigureX calls, but if they do, it should only produce a single invocation.
return document; | ||
} | ||
|
||
var invocation = root.FindNode(diagnostic.Location.SourceSpan); //whole invocation starting from "builder" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
var invocation = root.FindNode(diagnostic.Location.SourceSpan); //whole invocation starting from "builder" | |
var diagnosticTarget = root.FindNode(diagnostic.Location.SourceSpan); //whole invocation starting from "builder" |
Since at this point, we haven't actually verified at runtime that it's an invocation node. That doesn't happen until the if-statement below. This also helps us disambiguate between the different invocations being modified.
|
||
var invocation = root.FindNode(diagnostic.Location.SourceSpan); //whole invocation starting from "builder" | ||
|
||
if (invocation is InvocationExpressionSyntax { } newInvocationExpr) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (invocation is InvocationExpressionSyntax { } newInvocationExpr) | |
if (diagnosticTarget is InvocationExpressionSyntax { } originalInvocation) |
|
||
var configuration = SyntaxFactory.IdentifierName("Configuration"); | ||
|
||
if (hostBasedInvocationMethodExpr.Expression is not MemberAccessExpressionSyntax subExpression) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can probably combine the if-statement on L56 and this one into a single on that clearly communicates that we are looking "up" the accessors.
if (newInvocationExpr.Expression is not MemberAccessExpressionSyntax hostBasedInvocationMethodExpr
&& hostBasedInvocationMethodExpr.Expression is not MemberAccessExpressionSyntax hostExpression)
{
}
The values should be assigned correctly here.
{ | ||
return editor.OriginalDocument; | ||
} | ||
if (subExpression.Name != null) //subExpression.Name is Host/Webhost |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is Name
actually nullable here? Also, it might help to invert the if statement and return early here? I'm having trouble visualizing what happens if we don't hit this case.
var bodyEnum = ExtractParenLambdaBody(parenLambdaExpr).GetEnumerator(); | ||
while (bodyEnum.MoveNext()) | ||
{ | ||
if (bodyEnum.Current is not ExpressionStatementSyntax currentStatement) //builder.AddJsonFile() or builder.AddEnvironmentVariables() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can combine this if-statement and the one below into one statement with an &&
.
|
||
if (initArgumentExpr is ParenthesizedLambdaExpressionSyntax { } parenLambdaExpr) | ||
{ | ||
var bodyEnum = ExtractParenLambdaBody(parenLambdaExpr).GetEnumerator(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
var bodyEnum = ExtractParenLambdaBody(parenLambdaExpr).GetEnumerator(); | |
var lambdaStatements = ExtractParenLambdaBody(parenLambdaExpr); |
if (initArgumentExpr is ParenthesizedLambdaExpressionSyntax { } parenLambdaExpr) | ||
{ | ||
var bodyEnum = ExtractParenLambdaBody(parenLambdaExpr).GetEnumerator(); | ||
while (bodyEnum.MoveNext()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use a foreach
here.
foreach (var statement in lambdaStatements)
{
}
} | ||
private static SyntaxList<StatementSyntax> ExtractParenLambdaBody(ParenthesizedLambdaExpressionSyntax initArgumentExpr) | ||
{ | ||
var body = initArgumentExpr.Block.Statements; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to do any validation on Block
here? For example, check if it is null...
This part, when placed inside a loop, would continuously add the methods called to the final invocation. originalInvocation would be returned as the modified invocation, while hostBasedInvocationMethodExpr would be discarded. Example: Before:
After:
Walkthrough: First iteration:
Second iteration:
|
{Fixer: Replacing builder.Host.ConfigureAppConfiguration/builder.Host.ConfigureHostConfiguration/builder.WebHost.ConfigureAppConfiguration with builder.Configuration directly}
Implement
Description
Add code fixer that calls builder.Configuration instead of using ConfigureAppConfiguration/ConfigureHostConfiguration with builder.Host/builder.WebHost.
Fixes #35815