-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Add analyzer and code fix to recommend against IHeaderDictionary.Add #44463
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
Add analyzer and code fix to recommend against IHeaderDictionary.Add #44463
Conversation
|
Thanks for your PR, @david-acker. Someone from the team will get assigned to your PR shortly and we'll get it reviewed. |
|
@david-acker Can you please put it in src/Framework/AspNetCoreAnalyzers and follow the pattern used there? As for the ID, for now we're just incrementing the number (see https://github.com/dotnet/aspnetcore/blob/main/src/Framework/AspNetCoreAnalyzers/src/Analyzers/DiagnosticDescriptors.cs). |
src/Framework/AspNetCoreAnalyzers/src/CodeFixes/Http/HeaderDictionaryAddFixer.cs
Outdated
Show resolved
Hide resolved
src/Framework/AspNetCoreAnalyzers/test/Http/HeaderDictionaryAddFixerTests.cs
Outdated
Show resolved
Hide resolved
src/Analyzers/Analyzers/test/Microsoft.AspNetCore.Analyzers.Test.csproj
Outdated
Show resolved
Hide resolved
src/Framework/AspNetCoreAnalyzers/src/Analyzers/Http/HeaderDictionaryAddAnalyzer.cs
Outdated
Show resolved
Hide resolved
| <value>Unused route parameter</value> | ||
| </data> | ||
| <data name="Analyzer_HeaderDictionaryAdd_Message" xml:space="preserve"> | ||
| <value>Suggest using IHeaderDictionary.Append or the indexer instead of Add</value> |
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 we add a statement here about the consequences of using IDictionary.Add so the user is more informed?
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.
I've updated the diagnostic message to the following:
Use IHeaderDictionary.Append or the indexer to append or set headers. IDictionary.Add will throw an ArgumentException when attempting to add a duplicate key.
Let me know if there are any adjustments you'd like me to make to this.
src/Framework/AspNetCoreAnalyzers/test/Http/HeaderDictionaryAddFixerTests.cs
Outdated
Show resolved
Hide resolved
src/Framework/AspNetCoreAnalyzers/src/CodeFixes/Http/HeaderDictionaryAddFixer.cs
Outdated
Show resolved
Hide resolved
src/Framework/AspNetCoreAnalyzers/src/CodeFixes/Http/HeaderDictionaryAddFixer.cs
Outdated
Show resolved
Hide resolved
src/Framework/AspNetCoreAnalyzers/src/Analyzers/Http/HeaderDictionaryAddAnalyzer.cs
Outdated
Show resolved
Hide resolved
src/Framework/AspNetCoreAnalyzers/src/CodeFixes/Http/HeaderDictionaryAddFixer.cs
Outdated
Show resolved
Hide resolved
src/Framework/AspNetCoreAnalyzers/src/CodeFixes/Http/HeaderDictionaryAddFixer.cs
Outdated
Show resolved
Hide resolved
|
|
||
| if (invocation is InvocationExpressionSyntax { Expression: MemberAccessExpressionSyntax { Name.Identifier: { } identifierToken } }) | ||
| { | ||
| compilationUnitSyntax = compilationUnitSyntax.ReplaceToken(identifierToken, SyntaxFactory.Identifier("Append")); |
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.
I'm not sure, but could you check if SyntaxFactory.Identifier("Append").WithAdditionalAnnotations(Simplifier.AddImportsAnnotation) will avoid the need to manually add the using?
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.
Hmm, I just tried this but it doesn't seem to add the using. Is there anything else that I would need to do here besides adding the call to WithAdditionalAnnotations(Simplifier.AddImportsAnnotation) on the identifier?
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.
@david-acker Try getting the symbol for Microsoft.AspNetCore.Http.HeaderDictionaryExtensions and create an annotation like the following:
var annotation = new SyntaxAnnotation("SymbolId", DocumentationCommentId.CreateReferenceId(symbol));Then use SyntaxFactory.Identifier("Append").WithAdditionalAnnotations(Simplifier.AddImportsAnnotation, annotation)
src/Framework/AspNetCoreAnalyzers/test/Http/HeaderDictionaryAddAnalyzerTests.cs
Outdated
Show resolved
Hide resolved
src/Framework/AspNetCoreAnalyzers/test/Http/HeaderDictionaryAddAnalyzerTests.cs
Outdated
Show resolved
Hide resolved
captainsafia
left a comment
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.
LGTM!
@david-acker You're welcome to handle the docs independently over on that repo.
Consider having a compilation start action when retrieves Microsoft.AspNetCore.Http.IHeaderDictionary and then do a symbol comparison here.
I don't mind doing the symbol comparison this way, but feel free to adjust if you're open to it.
@Youssef1313 Would appreciate your sign off too!
Youssef1313
left a comment
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.
Besides the comments I already added, LGTM.
|
@captainsafia @Youssef1313 Sounds good! I'll address the remaining comments within the next few days. I'll also file an issue in the docs repo and submit a PR to update the diagnostics doc page that's linked above. |
|
@david-acker Looks like there's some test failures. |
|
@adityamandaleeka Hmm, that's odd. I'm not getting those test failures locally. I'll dig into this a bit. |
Seems to be a pesky line-ending issue. I wonder if there is a way to get the verifier to ignore these when comparing texts... |
Youssef1313
left a comment
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.
These suggestions simplify the addition of using, but the end of line issue still persists
| using Microsoft.CodeAnalysis.CodeActions; | ||
| using Microsoft.CodeAnalysis.CodeFixes; | ||
| using Microsoft.CodeAnalysis.CSharp; | ||
| using Microsoft.CodeAnalysis.CSharp.Syntax; |
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.
| using Microsoft.CodeAnalysis.CSharp.Syntax; | |
| using Microsoft.CodeAnalysis.CSharp.Syntax; | |
| using Microsoft.CodeAnalysis.Simplification; |
|
|
||
| private static async Task<Document> ReplaceWithAppend(Diagnostic diagnostic, Document document, InvocationExpressionSyntax invocation, CancellationToken cancellationToken) | ||
| { | ||
| var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false) as CompilationUnitSyntax; |
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 root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false) as CompilationUnitSyntax; | |
| var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); |
| return document.WithSyntaxRoot( | ||
| AddRequiredUsingDirectiveForAppend(root.ReplaceNode(diagnosticTarget, invocation))); |
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.
| return document.WithSyntaxRoot( | |
| AddRequiredUsingDirectiveForAppend(root.ReplaceNode(diagnosticTarget, invocation))); | |
| var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); | |
| var headerDictionaryExtensionsSymbol = model.Compilation.GetTypeByMetadataName("Microsoft.AspNetCore.Http.HeaderDictionaryExtensions"); | |
| var annotation = new SyntaxAnnotation("SymbolId", DocumentationCommentId.CreateReferenceId(headerDictionaryExtensionsSymbol)); | |
| return document.WithSyntaxRoot( | |
| root.ReplaceNode(diagnosticTarget, invocation.WithAdditionalAnnotations(Simplifier.AddImportsAnnotation, annotation))); |
| private static CompilationUnitSyntax AddRequiredUsingDirectiveForAppend(CompilationUnitSyntax compilationUnitSyntax) | ||
| { | ||
| var usingDirectives = compilationUnitSyntax.Usings; | ||
|
|
||
| var includesRequiredUsingDirective = false; | ||
| var insertionIndex = 0; | ||
|
|
||
| for (var i = 0; i < usingDirectives.Count; i++) | ||
| { | ||
| var namespaceName = usingDirectives[i].Name.ToString(); | ||
|
|
||
| // Always insert the new using directive after any 'System' using directives. | ||
| if (namespaceName.StartsWith("System", StringComparison.Ordinal)) | ||
| { | ||
| insertionIndex = i + 1; | ||
| continue; | ||
| } | ||
|
|
||
| var result = string.Compare("Microsoft.AspNetCore.Http", namespaceName, StringComparison.Ordinal); | ||
|
|
||
| if (result == 0) | ||
| { | ||
| includesRequiredUsingDirective = true; | ||
| break; | ||
| } | ||
|
|
||
| if (result < 0) | ||
| { | ||
| insertionIndex = i; | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| if (includesRequiredUsingDirective) | ||
| { | ||
| return compilationUnitSyntax; | ||
| } | ||
|
|
||
| var requiredUsingDirective = | ||
| SyntaxFactory.UsingDirective( | ||
| SyntaxFactory.QualifiedName( | ||
| SyntaxFactory.QualifiedName( | ||
| SyntaxFactory.IdentifierName("Microsoft"), | ||
| SyntaxFactory.IdentifierName("AspNetCore")), | ||
| SyntaxFactory.IdentifierName("Http"))); | ||
|
|
||
| return compilationUnitSyntax.WithUsings( | ||
| usingDirectives.Insert(insertionIndex, requiredUsingDirective)); | ||
| } | ||
|
|
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.
| private static CompilationUnitSyntax AddRequiredUsingDirectiveForAppend(CompilationUnitSyntax compilationUnitSyntax) | |
| { | |
| var usingDirectives = compilationUnitSyntax.Usings; | |
| var includesRequiredUsingDirective = false; | |
| var insertionIndex = 0; | |
| for (var i = 0; i < usingDirectives.Count; i++) | |
| { | |
| var namespaceName = usingDirectives[i].Name.ToString(); | |
| // Always insert the new using directive after any 'System' using directives. | |
| if (namespaceName.StartsWith("System", StringComparison.Ordinal)) | |
| { | |
| insertionIndex = i + 1; | |
| continue; | |
| } | |
| var result = string.Compare("Microsoft.AspNetCore.Http", namespaceName, StringComparison.Ordinal); | |
| if (result == 0) | |
| { | |
| includesRequiredUsingDirective = true; | |
| break; | |
| } | |
| if (result < 0) | |
| { | |
| insertionIndex = i; | |
| break; | |
| } | |
| } | |
| if (includesRequiredUsingDirective) | |
| { | |
| return compilationUnitSyntax; | |
| } | |
| var requiredUsingDirective = | |
| SyntaxFactory.UsingDirective( | |
| SyntaxFactory.QualifiedName( | |
| SyntaxFactory.QualifiedName( | |
| SyntaxFactory.IdentifierName("Microsoft"), | |
| SyntaxFactory.IdentifierName("AspNetCore")), | |
| SyntaxFactory.IdentifierName("Http"))); | |
| return compilationUnitSyntax.WithUsings( | |
| usingDirectives.Insert(insertionIndex, requiredUsingDirective)); | |
| } |
I'm not sure if adding an editorconfig to the tests with cc @sharwell @CyrusNajmabadi for suggestions. |
|
@captainsafia Could the failing test be skipped on Linux and get the PR merged? |
I'd like to see if trying
might help here. We've had issues before where analyzer tests were not running and resulted in some unsavory unhandled exceptions. Let me see if I can tweak this PR to flow it along. |
@david-acker Could you try that out? Apply the Line 97 in de3019b
|
This reverts commit 620275d.
|
I updated the failing test to be skipped on Linux and macOS. I also reverted the last commit which added the |
|
@david-acker Thanks for submitting this PR! Documentation has already been handled in #45025 (comment). Feel free to submit any updates to the docs if you'd like. |
Description
IHeaderDictionary.Add.AddwithAppendand another for replacingAddwith the indexer.Questions
AspNetCore.Analyzers,Mvc.Analyzers,Mvc.Api.Analyzers, andFramework/AspNetCoreAnalyzersseemed to all have slightly different styles and ways for handling the analyzers, code fixes, tests, etc. I pulled from these when creating this draft, but didn't follow the style of any one of them too rigidly. [Resolved: Moved to Framework/AspNetCoreAnalyzers and used the analyzer and code fix pattern used there.]Fixes #41362