Parameter null-checking analyzer and fixer#58182
Conversation
...zers/CSharp/Analyzers/UseParameterNullChecking/UseParameterNullCheckingDiagnosticAnalyzer.cs
Outdated
Show resolved
Hide resolved
...s/CSharp/CodeFixes/UseParameterNullChecking/CSharpUseParameterNullCheckingCodeFixProvider.cs
Outdated
Show resolved
Hide resolved
...s/CSharp/CodeFixes/UseParameterNullChecking/CSharpUseParameterNullCheckingCodeFixProvider.cs
Outdated
Show resolved
Hide resolved
...s/CSharp/CodeFixes/UseParameterNullChecking/CSharpUseParameterNullCheckingCodeFixProvider.cs
Outdated
Show resolved
Hide resolved
src/Analyzers/CSharp/Tests/UseParameterNullChecking/UseParameterNullCheckingTests.cs
Outdated
Show resolved
Hide resolved
src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CodeStyle/CodeStyleOptions2.cs
Outdated
Show resolved
Hide resolved
...s/CSharp/CodeFixes/UseParameterNullChecking/CSharpUseParameterNullCheckingCodeFixProvider.cs
Outdated
Show resolved
Hide resolved
...zers/CSharp/Analyzers/UseParameterNullChecking/UseParameterNullCheckingDiagnosticAnalyzer.cs
Outdated
Show resolved
Hide resolved
...zers/CSharp/Analyzers/UseParameterNullChecking/UseParameterNullCheckingDiagnosticAnalyzer.cs
Outdated
Show resolved
Hide resolved
...zers/CSharp/Analyzers/UseParameterNullChecking/UseParameterNullCheckingDiagnosticAnalyzer.cs
Outdated
Show resolved
Hide resolved
...Sharp/Analyzers/UseParameterNullChecking/CSharpUseParameterNullCheckingDiagnosticAnalyzer.cs
Outdated
Show resolved
Hide resolved
...Sharp/Analyzers/UseParameterNullChecking/CSharpUseParameterNullCheckingDiagnosticAnalyzer.cs
Outdated
Show resolved
Hide resolved
...Sharp/Analyzers/UseParameterNullChecking/CSharpUseParameterNullCheckingDiagnosticAnalyzer.cs
Outdated
Show resolved
Hide resolved
...Sharp/Analyzers/UseParameterNullChecking/CSharpUseParameterNullCheckingDiagnosticAnalyzer.cs
Show resolved
Hide resolved
...Sharp/Analyzers/UseParameterNullChecking/CSharpUseParameterNullCheckingDiagnosticAnalyzer.cs
Outdated
Show resolved
Hide resolved
...Sharp/Analyzers/UseParameterNullChecking/CSharpUseParameterNullCheckingDiagnosticAnalyzer.cs
Outdated
Show resolved
Hide resolved
...Sharp/Analyzers/UseParameterNullChecking/CSharpUseParameterNullCheckingDiagnosticAnalyzer.cs
Outdated
Show resolved
Hide resolved
...Sharp/Analyzers/UseParameterNullChecking/CSharpUseParameterNullCheckingDiagnosticAnalyzer.cs
Outdated
Show resolved
Hide resolved
...Sharp/Analyzers/UseParameterNullChecking/CSharpUseParameterNullCheckingDiagnosticAnalyzer.cs
Outdated
Show resolved
Hide resolved
...Sharp/Analyzers/UseParameterNullChecking/CSharpUseParameterNullCheckingDiagnosticAnalyzer.cs
Outdated
Show resolved
Hide resolved
|
The fix all in solution results for |
...Sharp/Analyzers/UseParameterNullChecking/CSharpUseParameterNullCheckingDiagnosticAnalyzer.cs
Show resolved
Hide resolved
...Sharp/Analyzers/UseParameterNullChecking/CSharpUseParameterNullCheckingDiagnosticAnalyzer.cs
Outdated
Show resolved
Hide resolved
|
The way to fix this issue is to preprocess the full list of diagnostics you are fixing. So you'll group them by parameter and then fix the parameter once and the N associated statements at the same time. |
|
It seems like the current approach of gathering and checking parameter locations in a set is adequate and doesn't require a separate initial pass over the diagnostics. |
Youssef1313
left a comment
There was a problem hiding this comment.
Worth adding a test like this?
using System;
class C<T> where T : struct
{
public void M(T? s!!) { }
}
|
See |
|
@CyrusNajmabadi I believe I have addressed all your comments. Please let me know if you have any others. |
|
Looking now :) |
| return; | ||
| } | ||
|
|
||
| var argumentNullException = compilation.GetTypeByMetadataName(ArgumentNullExceptionName); |
There was a problem hiding this comment.
| var argumentNullException = compilation.GetTypeByMetadataName(ArgumentNullExceptionName); | |
| var argumentNullException = compilation.GetBestTypeByMetadataName(ArgumentNullExceptionName); |
| if (parameter is not null | ||
| && (!parameter.Type.IsValueType | ||
| || parameter.Type.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T | ||
| || parameter.Type.TypeKind is TypeKind.Pointer or TypeKind.FunctionPointer) |
There was a problem hiding this comment.
extract into helper plz (can be local function or method). mixing the && || and ! is too much for my brain :)
| // if (param is null) { throw new ArgumentNullException(nameof(param)); } | ||
| // if (object.ReferenceEquals(param, null)) { throw new ArgumentNullException(nameof(param)); } | ||
| case IfStatementSyntax ifStatement: | ||
| ExpressionSyntax left, right; |
There was a problem hiding this comment.
i would extract the entire if-handling code to it's own helper.
There was a problem hiding this comment.
I think I won't make this change due to lack of uniformity with the ExpressionStatement case. See #58182 (comment)
| return parameterInBinary; | ||
|
|
||
| // this.field = param ?? throw new ArgumentNullException(nameof(param)); | ||
| case ExpressionStatementSyntax |
There was a problem hiding this comment.
similarly, can you extract this case out to a helper?
There was a problem hiding this comment.
I found that since much of the complexity was in the pattern itself, the extracted logic didn't feel sufficiently complex to justify a single-use helper.
| return false; | ||
| } | ||
|
|
||
| return argumentNullExceptionStringConstructor.Equals(semanticModel.GetSymbolInfo(exceptionCreation, cancellationToken).Symbol); |
There was a problem hiding this comment.
consider reordering. it's a personal nit, but it seems more sensible to first see if you're doing new ArgumntNullException then check the argument.
| { | ||
| using VerifyCS = CSharpCodeFixVerifier<CSharpUseParameterNullCheckingDiagnosticAnalyzer, CSharpUseParameterNullCheckingCodeFixProvider>; | ||
|
|
||
| public class UseParameterNullCheckingTests |
There was a problem hiding this comment.
are there tests for records?
There was a problem hiding this comment.
Are you referring to record primary constructors? Currently I don't think there's a way to get this particular code fix to activate on primary constructors. The "add null check" refactoring should work for it, but it doesn't. Opened #58779
| [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)] | ||
| public async Task TestNotEqualitySwapped() | ||
| { | ||
| var testCode = @"using System; |
There was a problem hiding this comment.
possibly comment that this is just because we don't support this scenario yet.
| } | ||
| }, | ||
| LanguageVersion = LanguageVersionExtensions.CSharpNext | ||
| }.RunAsync(); |
There was a problem hiding this comment.
add test for:
public C(
string x // comment
, string y // comment,
, string z // comment
)
{
}
There was a problem hiding this comment.
Good test case! We actually put the !! on the next line here. It feels like the trailing trivia of the identifier should move over to the !!. Is there an existing helper that lets me "insert a token and move over another token's trivia" in this way?
edit: added some additional tests and just did it the way that seemed reasonable to me.
There was a problem hiding this comment.
nope. you'd have to write the logic yourself. note: i'm also ok with you leaving that out of this PR and waiting until tehre is user feedback :)
| public const string SimplifyPropertyPatternDiagnosticId = "IDE0170"; | ||
|
|
||
| public const string UseTupleSwapDiagnosticId = "IDE0180"; | ||
| public const string UseParameterNullCheckingId = "IDE0190"; |
There was a problem hiding this comment.
| public const string UseParameterNullCheckingId = "IDE0190"; | |
| public const string UseParameterNullCheckingId = "IDE0190"; |
Related to #36024