-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
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
Outdated
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. |
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i would extract the entire if-handling code to it's own helper.
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 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
similarly, can you extract this case out to a helper?
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 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
are there tests for records?
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add test for:
public C(
string x // comment
, string y // comment,
, string z // 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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 :)
@@ -166,6 +166,7 @@ internal static class IDEDiagnosticIds | |||
public const string SimplifyPropertyPatternDiagnosticId = "IDE0170"; | |||
|
|||
public const string UseTupleSwapDiagnosticId = "IDE0180"; | |||
public const string UseParameterNullCheckingId = "IDE0190"; |
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.
public const string UseParameterNullCheckingId = "IDE0190"; | |
public const string UseParameterNullCheckingId = "IDE0190"; |
Related to #36024