Add analyzer/codefixer to use AddAuthorizationBuilder#47428
Conversation
…cy, AddFallbackPolicy invocations
…erFailure invocation
|
Thanks for your PR, @david-acker. Someone from the team will get assigned to your PR shortly and we'll get it reviewed. |
|
@halter73 can you please review this? Thanks! |
halter73
left a comment
There was a problem hiding this comment.
LGTM! Is there any particular reason this is still a draft? @captainsafia Can you take a look too?
| } | ||
| } | ||
|
|
||
| private static bool CanReplaceWithAddAuthorizationBuilder(Diagnostic diagnostic, SyntaxNode root, [NotNullWhen(true)] out InvocationExpressionSyntax? invocation) |
There was a problem hiding this comment.
When would this return false after a diagnostic is emitted? If it's possible, should we avoid raising the diagnostic if we cannot do the fix?
There was a problem hiding this comment.
Offhand, I don't believe there should be any situations where a diagnostic would be emitted and this would return false. The analyzer should account for all of the scenarios where we wouldn't be able to apply the code fix and not emit a diagnostic in those situations. But I'll take a closer look to make sure the analyzer isn't missing anything.
There was a problem hiding this comment.
I'd just add an assert to document this if you're confident.
| SyntaxFactory.Space, | ||
| SyntaxFactory.Space, | ||
| SyntaxFactory.Space, | ||
| SyntaxFactory.Space), |
There was a problem hiding this comment.
This part seems to be the sketchiest to me. I guess our tests expect SyntaxFactory.EndOfLine("\r\n"). Is this indentation relative to the previous line? Can we add tests for a more deeply nested call into AddAuthorization(...) like in HostBuilder.ConfigureServices?
There was a problem hiding this comment.
I agree. This part still felt pretty hacky to me, which is why I left the PR in draft. I haven't confirmed it yet, but I'm assuming this may also related to the test that's failing due to some line ending issues.
I added this in an attempt to improve the formatting of the code generated by the code fix so that it would match the example "after" code example in the issue, otherwise there wouldn't be any indentation relative to the previous line.
Should we be concerned with the fixed code being "pretty" (within reason) or should we leave this up to the user to handle? If we do want to handle formatting as part of the code fix, I can go ahead and add tests for more deeply nested scenarios. Otherwise, I'll remove this and update the fixedSource values in the tests.
Fixed example: without the added indentation and newline (from MultipleAddPolicyCalls_UsingExpressionBody_FixedWithAddAuthorizationBuilder)
builder.Services.AddAuthorizationBuilder().AddPolicy("AtLeast18", policy =>
policy.Requirements.Add(new MinimumAgeRequirement(18))).AddPolicy("AtLeast21", policy =>
policy.Requirements.Add(new MinimumAgeRequirement(21)));There was a problem hiding this comment.
I think we want it to look reasonably pretty if we're going to do this. The example without the added indentation and newline does look bad.
There was a problem hiding this comment.
How helpful is the SyntaxNode.NormalizeWhitespace API here for formatting the InvocationExpression? I know it sometimes doesn't play too nicely with deeply-nested calls but wondering if it might help here.
There was a problem hiding this comment.
NormalizeWhitespace seemed to be removing new lines when I tried using it initially, which was causing some issues, but I'll do more testing to see if I can get it to work. Maybe calling NormalizeWhitespace and then manually adding a new line for each chained call would work?
There was a problem hiding this comment.
Following up on this. Calling NormalizeWhitespace on the final InvocationExpression actually seems to format the block body examples OK, although the expression body examples ended up being pretty long since NormalizeWhitespace doesn't add new lines after the chained calls. So it looks like we'll need to add the EndOfLineTrivia ourselves if we want go this route.
Example: MultipleAddPolicyCalls_UsingBlockBody_FixedWithAddAuthorizationBuilder
builder.Services.AddAuthorizationBuilder().AddPolicy("AtLeast18", policy =>
{
policy.Requirements.Add(new MinimumAgeRequirement(18));
}).AddPolicy("AtLeast21", policy =>
{
policy.Requirements.Add(new MinimumAgeRequirement(21));
});Example: MultipleAddPolicyCalls_UsingExpressionBody_FixedWithAddAuthorizationBuilder
builder.Services.AddAuthorizationBuilder().AddPolicy("AtLeast18", policy => policy.Requirements.Add(new MinimumAgeRequirement(18))).AddPolicy("AtLeast21", policy => policy.Requirements.Add(new MinimumAgeRequirement(21)));There was a problem hiding this comment.
I also added two tests for the nested AddAuthorization scenarios and made some adjustments to have leading trivia is handled in AddAuthorizationBuilderFixer to ensure that indentation is correct.
|
@halter73 @captainsafia How should we handle the line-ending issues with the Linux and MacOS test failures? Should we just add |
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
|
I didn't realize we're skipping tests due to line-ending issues. Ideally, we should split or normalize the line endings before asserting so developers can run the tests on any platform even if we don't expect platform-specific failures. We shouldn't assume everyone is developing on Windows. We do this for our RequestDelegateGenerator tests. |
|
How would you recommend hooking this into the current verification logic that's being used for these analyzer/code fix tests (from Also, as an aside, these tests did actually pass when I ran them locally on MacOS with a clean clone of this branch, oddly enough. I'm curious why this would be the case, since they seem to be failing consistently on CI. |
|
@david-acker This issue with line-endings originally came up in #45642. At the time, we implemented the |
captainsafia
left a comment
There was a problem hiding this comment.
Looking good! Thanks for the ReplaceLineEndings fixup. Left some comments and questions inline.
| await VerifyCodeFix(source, new[] { diagnostic }, fixedSource); | ||
| } | ||
|
|
||
| private static async Task VerifyCodeFix(string source, DiagnosticResult[] diagnostics, string fixedSource) |
There was a problem hiding this comment.
Can we add a test for the scenario where the AddAuthorization call is assigned to a variable?
var services = builder.Services.AddAuthorization(options => ...);
I believe we will get the desired behavior here (not emitting a diagnostic) based on the IsLastCallInChain check but good to encode that behavior in tests.
There was a problem hiding this comment.
Added AddAuthorization_CallAssignedToVariable_NoDiagnostic.
|
|
||
| private static bool IsCompatibleWithAuthorizationBuilder(IInvocationOperation invocation, AuthorizationOptionsTypes authorizationOptionsTypes) | ||
| { | ||
| if (invocation is { Arguments: { Length: 2 } invocationArguments } |
There was a problem hiding this comment.
Nit: the pattern checks here are very dense. Maybe factor these out into separate methods so it is clearer what we are asserting with each check?
There was a problem hiding this comment.
I've refactored each of these pattern checks into a separate private method to make it clearer what each one does. Right now I've replaced each check with it's respective method to keep things as "flat" as possible, although this does require these methods to effectively be chained with their out parameters.
Let me know if there are any additional improvements or changes you would suggest here!
| { | ||
| if (operation is IExpressionStatementOperation { Operation: { } expressionStatementOperation }) | ||
| { | ||
| if (expressionStatementOperation is ISimpleAssignmentOperation { Target: IPropertyReferenceOperation { Property.ContainingType: { } propertyReferenceContainingType } } |
There was a problem hiding this comment.
Do we need add tests that what these code segments are checking (assuming I understood the code correctly) like:
builder.Services.AddAuthorization(options => ConfigureOptions(options));There was a problem hiding this comment.
I've added some new tests and updated a few of the existing ones to ensure that all of the branches in these private methods are covered by tests. I also removed the MethodReferenceOperation-related branch as this was unreachable.
|
|
||
| foreach (var diagnostic in context.Diagnostics) | ||
| { | ||
| if (CanReplaceWithAddAuthorizationBuilder(diagnostic, root, out var invocation)) |
There was a problem hiding this comment.
Yay! Thanks for doing the feasibility check before registering the codefix. This was an issue in the first few codefixers that we did...
Add
UseAuthorizationBuilderAnalyzer,AddAuthorizationBuilderFixerAdds an analyzer/codefixer to recommend that
AddAuthorizationbe converted toAddAuthorizationBuilder.Description
The code fix converts any usage of the setters for the
DefaultPolicy,FaillbackPolicy, andInvokeHandlersAfterFailureproperties onAuthorizationOptionsto an equivalent method call (SetDefaultPolicy,SetFallbackPolicy, andSetInvokeHandlersAfterFailure) onAuthorizationBuilder.No diagnostic is reported when the configure action passed to
AddAuthorizationuses theGetPolicy(String)method, theDefaultPolicygetter, theFallbackPolicygetter, or theInvokeHandlersAfterFailuregetter onAuthorizationOptionsas these do not exist onAuthorizationBuilderand thus cannot be converted.No diagnostic is reported if the configure action passed to
AddAuthorizationcontains operations unrelated toAuthorizationOptions, to avoid unintentionally deleting code when applying the code fix, as it would not be easy, or possible, to automatically map this toAddAuthorizationBuilder's fluent API.Fixes #45219