diff --git a/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems b/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems
index 77239030f8743..64c484b1d0853 100644
--- a/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems
+++ b/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems
@@ -103,6 +103,7 @@
+
diff --git a/src/Analyzers/CSharp/Analyzers/CSharpAnalyzersResources.resx b/src/Analyzers/CSharp/Analyzers/CSharpAnalyzersResources.resx
index 2d3d526546b29..466c18e00e098 100644
--- a/src/Analyzers/CSharp/Analyzers/CSharpAnalyzersResources.resx
+++ b/src/Analyzers/CSharp/Analyzers/CSharpAnalyzersResources.resx
@@ -340,4 +340,7 @@
Use tuple to swap values
+
+ Use parameter null checking
+
\ No newline at end of file
diff --git a/src/Analyzers/CSharp/Analyzers/UseParameterNullChecking/CSharpUseParameterNullCheckingDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/UseParameterNullChecking/CSharpUseParameterNullCheckingDiagnosticAnalyzer.cs
new file mode 100644
index 0000000000000..db85ff5cdea6a
--- /dev/null
+++ b/src/Analyzers/CSharp/Analyzers/UseParameterNullChecking/CSharpUseParameterNullCheckingDiagnosticAnalyzer.cs
@@ -0,0 +1,316 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using Microsoft.CodeAnalysis.CodeStyle;
+using Microsoft.CodeAnalysis.CSharp.CodeStyle;
+using Microsoft.CodeAnalysis.CSharp.Shared.Extensions;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Shared.Extensions;
+using Roslyn.Utilities;
+
+namespace Microsoft.CodeAnalysis.CSharp.UseParameterNullChecking
+{
+ [DiagnosticAnalyzer(LanguageNames.CSharp)]
+ internal sealed class CSharpUseParameterNullCheckingDiagnosticAnalyzer
+ : AbstractBuiltInCodeStyleDiagnosticAnalyzer
+ {
+ private const string ArgumentNullExceptionName = $"{nameof(System)}.{nameof(ArgumentNullException)}";
+ private static readonly LocalizableResourceString s_resourceTitle = new(nameof(AnalyzersResources.Null_check_can_be_simplified), AnalyzersResources.ResourceManager, typeof(AnalyzersResources));
+
+ public CSharpUseParameterNullCheckingDiagnosticAnalyzer()
+ : base(IDEDiagnosticIds.UseParameterNullCheckingId,
+ EnforceOnBuildValues.UseParameterNullChecking,
+ CSharpCodeStyleOptions.PreferParameterNullChecking,
+ LanguageNames.CSharp,
+ s_resourceTitle,
+ s_resourceTitle)
+ {
+ }
+
+ public override DiagnosticAnalyzerCategory GetAnalyzerCategory()
+ => DiagnosticAnalyzerCategory.SemanticSpanAnalysis;
+
+ protected override void InitializeWorker(AnalysisContext context)
+ => context.RegisterCompilationStartAction(context =>
+ {
+ var compilation = (CSharpCompilation)context.Compilation;
+ if (compilation.LanguageVersion < LanguageVersionExtensions.CSharpNext)
+ {
+ return;
+ }
+
+ var argumentNullException = compilation.GetBestTypeByMetadataName(ArgumentNullExceptionName);
+ if (argumentNullException is null)
+ {
+ return;
+ }
+
+ IMethodSymbol? argumentNullExceptionConstructor = null;
+ IMethodSymbol? argumentNullExceptionStringConstructor = null;
+ foreach (var constructor in argumentNullException.InstanceConstructors)
+ {
+ if (argumentNullExceptionConstructor is not null && argumentNullExceptionStringConstructor is not null)
+ {
+ break;
+ }
+
+ switch (constructor)
+ {
+ case { DeclaredAccessibility: Accessibility.Public, Parameters.Length: 0 }:
+ argumentNullExceptionConstructor = constructor;
+ break;
+ case { DeclaredAccessibility: Accessibility.Public, Parameters.Length: 1 }
+ when constructor.Parameters[0].Type.SpecialType == SpecialType.System_String:
+
+ argumentNullExceptionStringConstructor = constructor;
+ break;
+ }
+ }
+
+ if (argumentNullExceptionConstructor is null || argumentNullExceptionStringConstructor is null)
+ {
+ return;
+ }
+
+ var objectType = compilation.GetSpecialType(SpecialType.System_Object);
+ var referenceEqualsMethod = (IMethodSymbol?)objectType
+ .GetMembers(nameof(ReferenceEquals))
+ .FirstOrDefault(m => m is IMethodSymbol { DeclaredAccessibility: Accessibility.Public, Parameters.Length: 2 });
+
+ // We are potentially interested in any declaration that has parameters.
+ // However, we avoid indexers specifically because of the complexity of locating and deleting equivalent null checks across multiple accessors.
+ context.RegisterSyntaxNodeAction(
+ context => AnalyzeSyntax(context, argumentNullExceptionConstructor, argumentNullExceptionStringConstructor, referenceEqualsMethod),
+ SyntaxKind.ConstructorDeclaration,
+ SyntaxKind.MethodDeclaration,
+ SyntaxKind.LocalFunctionStatement,
+ SyntaxKind.SimpleLambdaExpression,
+ SyntaxKind.ParenthesizedLambdaExpression,
+ SyntaxKind.AnonymousMethodExpression,
+ SyntaxKind.OperatorDeclaration,
+ SyntaxKind.ConversionOperatorDeclaration);
+ });
+
+ private void AnalyzeSyntax(
+ SyntaxNodeAnalysisContext context,
+ IMethodSymbol argumentNullExceptionConstructor,
+ IMethodSymbol argumentNullExceptionStringConstructor,
+ IMethodSymbol? referenceEqualsMethod)
+ {
+ var cancellationToken = context.CancellationToken;
+
+ var semanticModel = context.SemanticModel;
+ var syntaxTree = semanticModel.SyntaxTree;
+
+ var option = context.Options.GetOption(CSharpCodeStyleOptions.PreferParameterNullChecking, syntaxTree, cancellationToken);
+ if (!option.Value)
+ {
+ return;
+ }
+
+ var node = context.Node;
+ var block = node switch
+ {
+ MethodDeclarationSyntax methodDecl => methodDecl.Body,
+ ConstructorDeclarationSyntax constructorDecl => constructorDecl.Body,
+ LocalFunctionStatementSyntax localFunctionStatement => localFunctionStatement.Body,
+ AnonymousFunctionExpressionSyntax anonymousFunction => anonymousFunction.Block,
+ OperatorDeclarationSyntax operatorDecl => operatorDecl.Body,
+ ConversionOperatorDeclarationSyntax conversionDecl => conversionDecl.Body,
+ _ => throw ExceptionUtilities.UnexpectedValue(node)
+ };
+
+ // More scenarios should be supported eventually: https://github.com/dotnet/roslyn/issues/58699
+ if (block is null)
+ {
+ return;
+ }
+
+ var methodSymbol = node is AnonymousFunctionExpressionSyntax
+ ? (IMethodSymbol?)semanticModel.GetSymbolInfo(node, cancellationToken).Symbol
+ : (IMethodSymbol?)semanticModel.GetDeclaredSymbol(node, cancellationToken);
+ if (methodSymbol is null || methodSymbol.Parameters.IsEmpty)
+ {
+ return;
+ }
+
+ foreach (var statement in block.Statements)
+ {
+ var parameter = TryGetParameterNullCheckedByStatement(statement);
+ if (ParameterCanUseNullChecking(parameter)
+ && parameter.DeclaringSyntaxReferences.FirstOrDefault() is SyntaxReference reference
+ && reference.SyntaxTree.Equals(statement.SyntaxTree)
+ && reference.GetSyntax() is ParameterSyntax parameterSyntax)
+ {
+ context.ReportDiagnostic(DiagnosticHelper.Create(
+ Descriptor,
+ statement.GetLocation(),
+ option.Notification.Severity,
+ additionalLocations: new[] { parameterSyntax.GetLocation() },
+ properties: null));
+ }
+ }
+
+ return;
+
+ bool ParameterCanUseNullChecking([NotNullWhen(true)] IParameterSymbol? parameter)
+ {
+ if (parameter is null)
+ return false;
+
+ if (parameter.RefKind != RefKind.None)
+ return false;
+
+ if (parameter.Type.IsValueType)
+ {
+ return parameter.Type.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T
+ || parameter.Type.TypeKind is TypeKind.Pointer or TypeKind.FunctionPointer;
+ }
+
+ return true;
+ }
+
+ IParameterSymbol? TryGetParameterNullCheckedByStatement(StatementSyntax statement)
+ {
+ switch (statement)
+ {
+ // if (param == null) { throw new ArgumentNullException(nameof(param)); }
+ // 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;
+ switch (ifStatement)
+ {
+ case { Condition: BinaryExpressionSyntax { OperatorToken.RawKind: (int)SyntaxKind.EqualsEqualsToken } binary }
+ // Only suggest the fix on built-in `==` operators where we know we won't change behavior
+ when semanticModel.GetSymbolInfo(binary).Symbol is IMethodSymbol { MethodKind: MethodKind.BuiltinOperator }:
+
+ left = binary.Left;
+ right = binary.Right;
+ break;
+ case { Condition: IsPatternExpressionSyntax { Expression: var patternInput, Pattern: ConstantPatternSyntax { Expression: var patternExpression } } }:
+ left = patternInput;
+ right = patternExpression;
+ break;
+ case { Condition: InvocationExpressionSyntax { Expression: var receiver, ArgumentList.Arguments: { Count: 2 } arguments } }
+ when referenceEqualsMethod != null && referenceEqualsMethod.Equals(semanticModel.GetSymbolInfo(receiver, cancellationToken).Symbol):
+
+ left = arguments[0].Expression;
+ right = arguments[1].Expression;
+ break;
+
+ default:
+ return null;
+ }
+
+ var parameterInBinary = left.IsKind(SyntaxKind.NullLiteralExpression) ? TryGetParameter(right)
+ : right.IsKind(SyntaxKind.NullLiteralExpression) ? TryGetParameter(left)
+ : null;
+ if (parameterInBinary is null)
+ {
+ return null;
+ }
+
+ var throwStatement = ifStatement.Statement switch
+ {
+ ThrowStatementSyntax @throw => @throw,
+ BlockSyntax { Statements: { Count: 1 } statements } => statements[0] as ThrowStatementSyntax,
+ _ => null
+ };
+
+ if (throwStatement?.Expression is not ObjectCreationExpressionSyntax thrownInIf
+ || !IsConstructorApplicable(thrownInIf, parameterInBinary))
+ {
+ return null;
+ }
+
+ return parameterInBinary;
+
+ // this.field = param ?? throw new ArgumentNullException(nameof(param));
+ case ExpressionStatementSyntax
+ {
+ Expression: AssignmentExpressionSyntax
+ {
+ Right: BinaryExpressionSyntax
+ {
+ OperatorToken.RawKind: (int)SyntaxKind.QuestionQuestionToken,
+ Left: ExpressionSyntax maybeParameter,
+ Right: ThrowExpressionSyntax { Expression: ObjectCreationExpressionSyntax thrownInNullCoalescing }
+ }
+ }
+ }:
+ var coalescedParameter = TryGetParameter(maybeParameter);
+ if (coalescedParameter is null || !IsConstructorApplicable(thrownInNullCoalescing, coalescedParameter))
+ {
+ return null;
+ }
+
+ return coalescedParameter;
+
+ default:
+ return null;
+ }
+ }
+
+ IParameterSymbol? TryGetParameter(ExpressionSyntax maybeParameter)
+ {
+ // `(object)x == null` is often used to ensure reference equality is used.
+ // therefore, we specially unwrap casts when the cast is to `object`.
+ if (maybeParameter is CastExpressionSyntax { Type: var type, Expression: var operand })
+ {
+ if (semanticModel.GetTypeInfo(type).Type?.SpecialType != SpecialType.System_Object)
+ {
+ return null;
+ }
+
+ maybeParameter = operand;
+ }
+
+ if (semanticModel.GetSymbolInfo(maybeParameter).Symbol is not IParameterSymbol { ContainingSymbol: { } containingSymbol } parameterSymbol || !containingSymbol.Equals(methodSymbol))
+ {
+ return null;
+ }
+
+ return parameterSymbol;
+ }
+
+ bool IsConstructorApplicable(ObjectCreationExpressionSyntax exceptionCreation, IParameterSymbol parameterSymbol)
+ {
+ if (exceptionCreation.ArgumentList?.Arguments is not { } arguments)
+ {
+ return false;
+ }
+
+ // 'new ArgumentNullException()'
+ if (argumentNullExceptionConstructor.Equals(semanticModel.GetSymbolInfo(exceptionCreation, cancellationToken).Symbol))
+ {
+ return arguments.Count == 0;
+ }
+
+ // 'new ArgumentNullException(nameof(param))' (or equivalent)
+ if (!argumentNullExceptionStringConstructor.Equals(semanticModel.GetSymbolInfo(exceptionCreation, cancellationToken).Symbol))
+ {
+ return false;
+ }
+
+ if (arguments.Count != 1)
+ {
+ return false;
+ }
+
+ var constantValue = semanticModel.GetConstantValue(arguments[0].Expression, cancellationToken);
+ if (constantValue.Value is not string constantString || !string.Equals(constantString, parameterSymbol.Name, StringComparison.Ordinal))
+ {
+ return false;
+ }
+
+ return true;
+ }
+ }
+ }
+}
diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.cs.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.cs.xlf
index be38ae577fa07..d3bf326ebc1a3 100644
--- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.cs.xlf
+++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.cs.xlf
@@ -262,6 +262,11 @@
Použít new(...)
{Locked="new(...)"} This is a C# construct and should not be localized.
+
+
+ Use parameter null checking
+
+
Použít porovnávání vzorů
diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.de.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.de.xlf
index de593ee65610b..3e5a10a5c1b01 100644
--- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.de.xlf
+++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.de.xlf
@@ -262,6 +262,11 @@
"new(...)" verwenden
{Locked="new(...)"} This is a C# construct and should not be localized.
+
+
+ Use parameter null checking
+
+
Musterabgleich verwenden
diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.es.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.es.xlf
index ad0f16fb97f56..487cb6690ab0d 100644
--- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.es.xlf
+++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.es.xlf
@@ -262,6 +262,11 @@
Usar "new(...)"
{Locked="new(...)"} This is a C# construct and should not be localized.
+
+
+ Use parameter null checking
+
+
Usar coincidencia de patrones
diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.fr.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.fr.xlf
index f9cf8172a54d7..3ea7e6285c43f 100644
--- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.fr.xlf
+++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.fr.xlf
@@ -262,6 +262,11 @@
Utiliser 'new(...)'
{Locked="new(...)"} This is a C# construct and should not be localized.
+
+
+ Use parameter null checking
+
+
Utiliser les critères spéciaux
diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.it.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.it.xlf
index 2bced89905496..e5166f9b4e1c5 100644
--- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.it.xlf
+++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.it.xlf
@@ -262,6 +262,11 @@
Usa 'new(...)'
{Locked="new(...)"} This is a C# construct and should not be localized.
+
+
+ Use parameter null checking
+
+
Usa i criteri di ricerca
diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ja.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ja.xlf
index f36328909a864..06648a84234eb 100644
--- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ja.xlf
+++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ja.xlf
@@ -262,6 +262,11 @@
'new(...)' を使用する
{Locked="new(...)"} This is a C# construct and should not be localized.
+
+
+ Use parameter null checking
+
+
パターン マッチングを使用します
diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ko.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ko.xlf
index ce043a110e343..c48fe7cd0888d 100644
--- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ko.xlf
+++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ko.xlf
@@ -262,6 +262,11 @@
'new(...)' 사용
{Locked="new(...)"} This is a C# construct and should not be localized.
+
+
+ Use parameter null checking
+
+
패턴 일치 사용
diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pl.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pl.xlf
index af3991d281da5..92deaef069791 100644
--- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pl.xlf
+++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pl.xlf
@@ -262,6 +262,11 @@
Użyj operatora „new(...)”
{Locked="new(...)"} This is a C# construct and should not be localized.
+
+
+ Use parameter null checking
+
+
Użyj dopasowywania wzorców
diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pt-BR.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pt-BR.xlf
index 67408b2147adf..52318280707e5 100644
--- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pt-BR.xlf
+++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pt-BR.xlf
@@ -262,6 +262,11 @@
Usar 'new(...)'
{Locked="new(...)"} This is a C# construct and should not be localized.
+
+
+ Use parameter null checking
+
+
Usar a correspondência de padrão
diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ru.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ru.xlf
index 3e87bac5a4c3b..98bde1d848ccc 100644
--- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ru.xlf
+++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ru.xlf
@@ -262,6 +262,11 @@
Используйте "new(...)".
{Locked="new(...)"} This is a C# construct and should not be localized.
+
+
+ Use parameter null checking
+
+
Используйте сопоставление шаблонов
diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.tr.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.tr.xlf
index f3a2d5a24347d..6627d78551333 100644
--- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.tr.xlf
+++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.tr.xlf
@@ -262,6 +262,11 @@
'new(...)' kullanın
{Locked="new(...)"} This is a C# construct and should not be localized.
+
+
+ Use parameter null checking
+
+
Desen eşleştirme kullan
diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hans.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hans.xlf
index 7c6167a7e02dc..cf626f8bbc06d 100644
--- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hans.xlf
+++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hans.xlf
@@ -262,6 +262,11 @@
使用 "new(...)"
{Locked="new(...)"} This is a C# construct and should not be localized.
+
+
+ Use parameter null checking
+
+
使用模式匹配
diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hant.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hant.xlf
index 25266870695f0..c43a1e4f62e47 100644
--- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hant.xlf
+++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hant.xlf
@@ -262,6 +262,11 @@
使用 'new(...)'
{Locked="new(...)"} This is a C# construct and should not be localized.
+
+
+ Use parameter null checking
+
+
使用模式比對
diff --git a/src/Analyzers/CSharp/CodeFixes/CSharpCodeFixes.projitems b/src/Analyzers/CSharp/CodeFixes/CSharpCodeFixes.projitems
index c58db92c96184..d411389a4b28d 100644
--- a/src/Analyzers/CSharp/CodeFixes/CSharpCodeFixes.projitems
+++ b/src/Analyzers/CSharp/CodeFixes/CSharpCodeFixes.projitems
@@ -51,6 +51,7 @@
+
diff --git a/src/Analyzers/CSharp/CodeFixes/UseParameterNullChecking/CSharpUseParameterNullCheckingCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UseParameterNullChecking/CSharpUseParameterNullCheckingCodeFixProvider.cs
new file mode 100644
index 0000000000000..5bc8f3a915a18
--- /dev/null
+++ b/src/Analyzers/CSharp/CodeFixes/UseParameterNullChecking/CSharpUseParameterNullCheckingCodeFixProvider.cs
@@ -0,0 +1,102 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Immutable;
+using System.Composition;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.CodeActions;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Editing;
+using Microsoft.CodeAnalysis.Host.Mef;
+using Microsoft.CodeAnalysis.PooledObjects;
+using Microsoft.CodeAnalysis.Shared.Extensions;
+using Roslyn.Utilities;
+
+namespace Microsoft.CodeAnalysis.CSharp.UseParameterNullChecking
+{
+ [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.UseParameterNullChecking), Shared]
+ internal sealed class CSharpUseParameterNullCheckingCodeFixProvider : SyntaxEditorBasedCodeFixProvider
+ {
+ [ImportingConstructor]
+ [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
+ public CSharpUseParameterNullCheckingCodeFixProvider()
+ {
+ }
+
+ public override ImmutableArray FixableDiagnosticIds
+ => ImmutableArray.Create(IDEDiagnosticIds.UseParameterNullCheckingId);
+
+ internal sealed override CodeFixCategory CodeFixCategory => CodeFixCategory.CodeStyle;
+
+ public override Task RegisterCodeFixesAsync(CodeFixContext context)
+ {
+ var diagnostic = context.Diagnostics[0];
+ context.RegisterCodeFix(
+ new MyCodeAction(c => FixAsync(context.Document, diagnostic, c)),
+ context.Diagnostics);
+ return Task.CompletedTask;
+ }
+
+ protected override Task FixAllAsync(
+ Document document, ImmutableArray diagnostics,
+ SyntaxEditor editor, CancellationToken cancellationToken)
+ {
+ // Tracking parameters which have already been fixed by a fix-all operation.
+ // This avoids crashing the fixer when the same parameter is null-tested multiple times.
+ using var _ = PooledHashSet.GetInstance(out var fixedParameterLocations);
+ foreach (var diagnostic in diagnostics)
+ {
+ var node = diagnostic.Location.FindNode(getInnermostNodeForTie: true, cancellationToken: cancellationToken);
+ switch (node)
+ {
+ case IfStatementSyntax ifStatement:
+ editor.RemoveNode(ifStatement);
+ break;
+ case ExpressionStatementSyntax expressionStatement:
+ // this.item = item ?? throw new ArgumentNullException(nameof(item));
+ var assignment = (AssignmentExpressionSyntax)expressionStatement.Expression;
+ var nullCoalescing = (BinaryExpressionSyntax)assignment.Right;
+ var parameterReferenceSyntax = nullCoalescing.Left;
+ editor.ReplaceNode(nullCoalescing, parameterReferenceSyntax.WithAppendedTrailingTrivia(SyntaxFactory.ElasticMarker));
+ break;
+ default:
+ throw ExceptionUtilities.UnexpectedValue(node);
+ }
+
+ var parameterLocation = diagnostic.AdditionalLocations[0];
+ if (fixedParameterLocations.Add(parameterLocation))
+ {
+ var parameterSyntax = (ParameterSyntax)parameterLocation.FindNode(cancellationToken);
+ if (parameterSyntax.ExclamationExclamationToken.IsKind(SyntaxKind.None))
+ {
+ var identifier = parameterSyntax.Identifier;
+ var newIdentifier = identifier.WithoutTrailingTrivia();
+ var newExclamationExclamationToken = SyntaxFactory.Token(SyntaxTriviaList.Empty, SyntaxKind.ExclamationExclamationToken, identifier.TrailingTrivia);
+ editor.ReplaceNode(parameterSyntax, parameterSyntax.Update(
+ parameterSyntax.AttributeLists,
+ parameterSyntax.Modifiers,
+ parameterSyntax.Type,
+ newIdentifier,
+ newExclamationExclamationToken,
+ parameterSyntax.Default));
+ }
+ }
+ }
+
+ return Task.CompletedTask;
+ }
+
+ private class MyCodeAction : CustomCodeActions.DocumentChangeAction
+ {
+ public MyCodeAction(Func> createChangedDocument)
+ : base(CSharpAnalyzersResources.Use_parameter_null_checking, createChangedDocument, nameof(CSharpUseParameterNullCheckingCodeFixProvider))
+ {
+ }
+ }
+ }
+}
diff --git a/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems b/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems
index f084e0292c272..2aa0e218be626 100644
--- a/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems
+++ b/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems
@@ -92,6 +92,7 @@
+
diff --git a/src/Analyzers/CSharp/Tests/UseParameterNullChecking/UseParameterNullCheckingTests.cs b/src/Analyzers/CSharp/Tests/UseParameterNullChecking/UseParameterNullCheckingTests.cs
new file mode 100644
index 0000000000000..bc69220f51600
--- /dev/null
+++ b/src/Analyzers/CSharp/Tests/UseParameterNullChecking/UseParameterNullCheckingTests.cs
@@ -0,0 +1,1549 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Shared.Extensions;
+using Microsoft.CodeAnalysis.CSharp.UseParameterNullChecking;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics;
+using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions;
+using Microsoft.CodeAnalysis.Test.Utilities;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.UseParameterNullChecking
+{
+ using VerifyCS = CSharpCodeFixVerifier;
+
+ public class UseParameterNullCheckingTests
+ {
+ [Theory]
+ [Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ [InlineData("==")]
+ [InlineData("is")]
+ public async Task TestNoBraces(string @operator)
+ {
+ await new VerifyCS.Test()
+ {
+ TestCode = @"
+using System;
+
+class C
+{
+ void M(string s)
+ {
+ [|if (s " + @operator + @" null)
+ throw new ArgumentNullException(nameof(s));|]
+ }
+}",
+ FixedCode = @"
+using System;
+
+class C
+{
+ void M(string s!!)
+ {
+ }
+}",
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Theory]
+ [Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ [InlineData("==")]
+ [InlineData("is")]
+ public async Task TestWithBraces(string @operator)
+ {
+ await new VerifyCS.Test()
+ {
+ TestCode = @"
+using System;
+
+class C
+{
+ void M(string s)
+ {
+ [|if (s " + @operator + @" null)
+ {
+ throw new ArgumentNullException(nameof(s));
+ }|]
+ }
+}",
+ FixedCode = @"
+using System;
+
+class C
+{
+ void M(string s!!)
+ {
+ }
+}",
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Theory]
+ [Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ [InlineData("==")]
+ [InlineData("is")]
+ public async Task TestLocalFunction(string @operator)
+ {
+ await new VerifyCS.Test()
+ {
+ TestCode = @"
+using System;
+
+class C
+{
+ void M()
+ {
+ local("""");
+ void local(string s)
+ {
+ [|if (s " + @operator + @" null)
+ {
+ throw new ArgumentNullException(nameof(s));
+ }|]
+ }
+ }
+}",
+ FixedCode = @"
+using System;
+
+class C
+{
+ void M()
+ {
+ local("""");
+ void local(string s!!)
+ {
+ }
+ }
+}",
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestEqualitySwapped()
+ {
+ await new VerifyCS.Test()
+ {
+ TestCode = @"
+using System;
+
+class C
+{
+ void M(string s)
+ {
+ [|if (null == (object)s)
+ throw new ArgumentNullException(nameof(s));|]
+ }
+}",
+ FixedCode = @"
+using System;
+
+class C
+{
+ void M(string s!!)
+ {
+ }
+}",
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestNotEquality()
+ {
+ var testCode = @"
+using System;
+
+class C
+{
+ void M(string s)
+ {
+ if ((object)s != null)
+ throw new ArgumentNullException(nameof(s));
+ }
+}";
+ await new VerifyCS.Test()
+ {
+ TestCode = testCode,
+ FixedCode = testCode,
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestNullCoalescingThrow()
+ {
+ await new VerifyCS.Test()
+ {
+ TestCode = @"
+using System;
+
+class C
+{
+ private readonly string s;
+ public C(string s)
+ {
+ [|this.s = s ?? throw new ArgumentNullException(nameof(s));|]
+ }
+}",
+ FixedCode = @"
+using System;
+
+class C
+{
+ private readonly string s;
+ public C(string s!!)
+ {
+ this.s = s;
+ }
+}",
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestOperator()
+ {
+ await new VerifyCS.Test()
+ {
+ TestCode = @"
+using System;
+
+class C
+{
+ public static C operator +(C c, string s)
+ {
+ [|if (s is null)
+ throw new ArgumentNullException(nameof(s));|]
+
+ return new C();
+ }
+}",
+ FixedCode = @"
+using System;
+
+class C
+{
+ public static C operator +(C c, string s!!)
+ {
+ return new C();
+ }
+}",
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestConversionOperator()
+ {
+ await new VerifyCS.Test()
+ {
+ TestCode = @"
+using System;
+
+class C
+{
+ public static implicit operator C(string s)
+ {
+ [|if (s is null)
+ throw new ArgumentNullException(nameof(s));|]
+
+ return new C();
+ }
+}",
+ FixedCode = @"
+using System;
+
+class C
+{
+ public static implicit operator C(string s!!)
+ {
+ return new C();
+ }
+}",
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestNullCoalescingThrowExpressionBody()
+ {
+ // We'd like to support this eventually. https://github.com/dotnet/roslyn/issues/58699
+ var testCode = @"
+using System;
+
+class C
+{
+ private readonly string s;
+ public C(string s)
+ => this.s = s ?? throw new ArgumentNullException(nameof(s));
+}";
+ await new VerifyCS.Test()
+ {
+ TestCode = testCode,
+ FixedCode = testCode,
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestNullCoalescingThrowBaseClause()
+ {
+ // We'd like to support this eventually. https://github.com/dotnet/roslyn/issues/58699
+ var testCode = @"
+using System;
+
+class Base { public Base(string s) { } }
+class C : Base
+{
+ public C(string s) : base(s ?? throw new ArgumentNullException(nameof(s))) { }
+}";
+ await new VerifyCS.Test()
+ {
+ TestCode = testCode,
+ FixedCode = testCode,
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestAlreadyNullChecked()
+ {
+ await new VerifyCS.Test()
+ {
+ TestCode = @"using System;
+
+class C
+{
+ public C(string s!!)
+ {
+ [|if (s is null)
+ throw new ArgumentNullException(nameof(s));|]
+ }
+}",
+ FixedCode = @"using System;
+
+class C
+{
+ public C(string s!!)
+ {
+ }
+}",
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestSingleLineNoBraces()
+ {
+ await new VerifyCS.Test()
+ {
+ TestCode = @"using System;
+
+class C
+{
+ public C(string s)
+ {
+ [|if (s is null) throw new ArgumentNullException(nameof(s));|]
+ }
+}",
+ FixedCode = @"using System;
+
+class C
+{
+ public C(string s!!)
+ {
+ }
+}",
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestSingleLineWithBraces()
+ {
+ await new VerifyCS.Test()
+ {
+ TestCode = @"using System;
+
+class C
+{
+ public C(string s)
+ {
+ [|if (s is null) { throw new ArgumentNullException(nameof(s)); }|]
+ }
+}",
+ FixedCode = @"using System;
+
+class C
+{
+ public C(string s!!)
+ {
+ }
+}",
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestLeadingAndTrailingTrivia1()
+ {
+ await new VerifyCS.Test()
+ {
+ TestCode = @"using System;
+
+class C
+{
+ public C(string s)
+ {
+ // comment1
+ [|if (s is null) { throw new ArgumentNullException(nameof(s)); }|] // comment2
+ }
+}",
+ FixedCode = @"using System;
+
+class C
+{
+ public C(string s!!)
+ {
+ }
+}",
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestLeadingAndTrailingTrivia2()
+ {
+ await new VerifyCS.Test()
+ {
+ TestCode = @"using System;
+
+class C
+{
+ public C(
+ string x // comment
+ , string y // comment,
+ , string z // comment
+ )
+ {
+ [|if (x is null) throw new ArgumentNullException(nameof(x));|]
+ [|if (y is null) throw new ArgumentNullException(nameof(y));|]
+ [|if (z is null) throw new ArgumentNullException(nameof(z));|]
+ }
+}",
+ FixedCode = @"using System;
+
+class C
+{
+ public C(
+ string x!! // comment
+ , string y!! // comment,
+ , string z!! // comment
+ )
+ {
+ }
+}",
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestLeadingAndTrailingTrivia3()
+ {
+ await new VerifyCS.Test()
+ {
+ TestCode = @"using System;
+
+class C
+{
+ public C(
+ string x /* comment */ !!
+ )
+ {
+ [|if (x is null) throw new ArgumentNullException(nameof(x));|]
+ }
+}",
+ FixedCode = @"using System;
+
+class C
+{
+ public C(
+ string x /* comment */ !!
+ )
+ {
+ }
+}",
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestAssignThenTest()
+ {
+ await new VerifyCS.Test()
+ {
+ TestCode = @"using System;
+
+class C
+{
+ public C(string s)
+ {
+ s = ""a"";
+ [|if (s is null) { throw new ArgumentNullException(nameof(s)); }|]
+ }
+}",
+ FixedCode = @"using System;
+
+class C
+{
+ public C(string s!!)
+ {
+ s = ""a"";
+ }
+}",
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestRedundantNullChecks()
+ {
+ await new VerifyCS.Test()
+ {
+ TestCode = @"
+using System;
+
+class C
+{
+ public C(string s)
+ {
+ [|if (s is null)
+ throw new ArgumentNullException(nameof(s));|]
+
+ [|if (s is null)
+ throw new ArgumentNullException(nameof(s));|]
+ }
+}",
+ FixedCode =
+@"
+using System;
+
+class C
+{
+ public C(string s!!)
+ {
+ }
+}",
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Theory, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ [InlineData("ref")]
+ [InlineData("in")]
+ [InlineData("out")]
+ public async Task TestRefParameter(string refKind)
+ {
+ // https://github.com/dotnet/roslyn/issues/58699
+ // When the implementation changes to permit ref/in parameters, we should also change the fixer.
+ var testCode = @"
+using System;
+
+class C
+{
+ public C(" + refKind + @" string s)
+ {
+ if (s is null)
+ throw new ArgumentNullException(nameof(s));
+ }
+}";
+ await new VerifyCS.Test()
+ {
+ TestCode = testCode,
+ FixedCode = testCode,
+ CompilerDiagnostics = Testing.CompilerDiagnostics.None,
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Theory, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ [InlineData("object")]
+ [InlineData("C")]
+ public async Task TestReferenceEqualsCheck(string className)
+ {
+ await new VerifyCS.Test()
+ {
+ TestCode = @"
+using System;
+
+class C
+{
+ private readonly string s;
+ void M1(string s)
+ {
+ [|if (" + className + @".ReferenceEquals(s, null))
+ {
+ throw new ArgumentNullException(nameof(s));
+ }|]
+ }
+
+ void M2(string s)
+ {
+ [|if (" + className + @".ReferenceEquals(null, s))
+ {
+ throw new ArgumentNullException(nameof(s));
+ }|]
+ }
+}",
+ FixedCode = @"
+using System;
+
+class C
+{
+ private readonly string s;
+ void M1(string s!!)
+ {
+ }
+
+ void M2(string s!!)
+ {
+ }
+}",
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestNotCustomReferenceEqualsCheck()
+ {
+ var testCode = @"
+using System;
+
+class C
+{
+ private readonly string s;
+ void M(string s)
+ {
+ if (ReferenceEquals(s, null))
+ {
+ throw new ArgumentNullException(nameof(s));
+ }
+ }
+
+ bool ReferenceEquals(object o1, object o2) => false;
+}";
+ await new VerifyCS.Test()
+ {
+ TestCode = testCode,
+ FixedCode = testCode,
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestNotEqualitySwapped()
+ {
+ // https://github.com/dotnet/roslyn/issues/58699
+ var testCode = @"using System;
+
+class C
+{
+ void M(string s)
+ {
+ if (null != (object)s)
+ throw new ArgumentNullException(nameof(s));
+ }
+}";
+ await new VerifyCS.Test()
+ {
+ TestCode = testCode,
+ FixedCode = testCode,
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestMissingPreCSharp11()
+ {
+ var testCode = @"
+using System;
+
+class C
+{
+ void M(string s)
+ {
+ if ((object)s == null)
+ throw new ArgumentNullException(nameof(s));
+ }
+}";
+ await new VerifyCS.Test()
+ {
+ TestCode = testCode,
+ FixedCode = testCode,
+ LanguageVersion = LanguageVersion.CSharp10
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestOnlyForObjectCast()
+ {
+ var testCode = @"using System;
+
+class C
+{
+ void M(string s)
+ {
+ if ((string)s == null)
+ throw new ArgumentNullException(nameof(s));
+ }
+}";
+ await new VerifyCS.Test()
+ {
+ TestCode = testCode,
+ FixedCode = testCode,
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestFixAll1()
+ {
+ await new VerifyCS.Test()
+ {
+ TestCode = @"
+using System;
+
+class C
+{
+ void M(string s1, string s2)
+ {
+ [|if ((object)s1 == null)
+ throw new ArgumentNullException(nameof(s1));|]
+
+ [|if (null == (object)s2)
+ throw new ArgumentNullException(nameof(s2));|]
+ }
+}",
+ FixedCode = @"
+using System;
+
+class C
+{
+ void M(string s1!!, string s2!!)
+ {
+ }
+}",
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestNested1()
+ {
+ var testCode = @"using System;
+
+class C
+{
+ void M(string s2)
+ {
+ if ((object)((object)s2 == null) == null)
+ throw new ArgumentNullException(nameof(s2));
+ }
+}";
+ await new VerifyCS.Test()
+ {
+ TestCode = testCode,
+ FixedCode = testCode,
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestNestedStatements()
+ {
+ var testCode = @"using System;
+
+class C
+{
+ void M(string s2)
+ {
+ {
+ if (s2 == null)
+ throw new ArgumentNullException(nameof(s2));
+ }
+ }
+}";
+ await new VerifyCS.Test()
+ {
+ TestCode = testCode,
+ FixedCode = testCode,
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestConstrainedTypeParameter()
+ {
+ await new VerifyCS.Test()
+ {
+ TestCode = @"
+using System;
+
+class C
+{
+ void M(T s) where T : class
+ {
+ [|if ((object)s == null)
+ throw new ArgumentNullException(nameof(s));|]
+ }
+}",
+ FixedCode = @"
+using System;
+
+class C
+{
+ void M(T s!!) where T : class
+ {
+ }
+}",
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestOnStructConstrainedTypeParameter()
+ {
+ await new VerifyCS.Test()
+ {
+ TestCode = @"
+using System;
+
+class C
+{
+ void M1(T s) where T : struct
+ {
+ if ((object)s == null)
+ throw new ArgumentNullException(nameof(s));
+ }
+
+ void M2(T? s) where T : struct
+ {
+ [|if ((object)s == null)
+ throw new ArgumentNullException(nameof(s));|]
+ }
+}",
+ FixedCode = @"
+using System;
+
+class C
+{
+ void M1(T s) where T : struct
+ {
+ if ((object)s == null)
+ throw new ArgumentNullException(nameof(s));
+ }
+
+ void M2(T? s!!) where T : struct
+ {
+ }
+}",
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestUnconstrainedTypeParameter()
+ {
+ await new VerifyCS.Test()
+ {
+ TestCode =
+@"using System;
+
+class C
+{
+ void M(T s)
+ {
+ [|if ((object)s == null)
+ throw new ArgumentNullException(nameof(s));|]
+ }
+}",
+ FixedCode =
+@"using System;
+
+class C
+{
+ void M(T s!!)
+ {
+ }
+}",
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestComplexExpr()
+ {
+ var testCode = @"using System;
+
+class C
+{
+ void M(string[] s)
+ {
+ if ((object)s[0] == null)
+ throw new ArgumentNullException(nameof(s));
+ }
+}";
+ await new VerifyCS.Test()
+ {
+ TestCode = testCode,
+ FixedCode = testCode,
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestComplexExpr2()
+ {
+ var testCode = @"using System;
+
+class C
+{
+ void M(string s1, string s2)
+ {
+ if (s1 == null || s2 == null)
+ throw new ArgumentNullException(nameof(s1));
+ }
+}";
+ await new VerifyCS.Test()
+ {
+ TestCode = testCode,
+ FixedCode = testCode,
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestNotOnDefault()
+ {
+ var testCode = @"
+using System;
+
+class C
+{
+ void M(string s)
+ {
+ if ((object)s == default)
+ throw new ArgumentNullException(nameof(s));
+ }
+}";
+ await new VerifyCS.Test()
+ {
+ TestCode = testCode,
+ FixedCode = testCode,
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestOptionalParameter()
+ {
+ await new VerifyCS.Test()
+ {
+ TestCode = @"
+using System;
+
+class C
+{
+ void M(string s = ""a"")
+ {
+ [|if ((object)s == null)
+ throw new ArgumentNullException(nameof(s));|]
+ }
+}",
+ FixedCode = @"
+using System;
+
+class C
+{
+ void M(string s!! = ""a"")
+ {
+ }
+}",
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestWithStringExceptionArgument()
+ {
+ await new VerifyCS.Test()
+ {
+ TestCode = @"
+using System;
+
+class C
+{
+ void M(string s)
+ {
+ [|if (s == null)
+ throw new ArgumentNullException(""s"");|]
+ }
+}",
+ FixedCode = @"
+using System;
+
+class C
+{
+ void M(string s!!)
+ {
+ }
+}",
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestWithStringExceptionArgument2()
+ {
+ await new VerifyCS.Test()
+ {
+ TestCode = @"
+using System;
+
+class C
+{
+ void M(string HelloWorld)
+ {
+ [|if (HelloWorld == null)
+ throw new ArgumentNullException(""Hello"" + ""World"");|]
+ }
+}",
+ FixedCode = @"
+using System;
+
+class C
+{
+ void M(string HelloWorld!!)
+ {
+ }
+}",
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestNotWithUnexpectedExceptionArgument()
+ {
+ var testCode = @"using System;
+
+class C
+{
+ void M(string s)
+ {
+ if (s == null)
+ throw new ArgumentNullException(""banana"");
+ }
+}";
+ await new VerifyCS.Test()
+ {
+ TestCode = testCode,
+ FixedCode = testCode,
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestWithExceptionNoArguments()
+ {
+ await new VerifyCS.Test()
+ {
+ TestCode = @"using System;
+
+class C
+{
+ void M(string s)
+ {
+ [|if (s == null)
+ throw new ArgumentNullException();|]
+ }
+}",
+ FixedCode = @"using System;
+
+class C
+{
+ void M(string s!!)
+ {
+ }
+}",
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestSimpleLambda()
+ {
+ await new VerifyCS.Test()
+ {
+ TestCode = @"using System;
+
+class C
+{
+ Action lambda = x =>
+ {
+ [|if (x is null)
+ throw new ArgumentNullException(nameof(x));|]
+ };
+}
+",
+ FixedCode = @"using System;
+
+class C
+{
+ Action lambda = x!! =>
+ {
+ };
+}
+",
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestParenthesizedLambdaNoType()
+ {
+ await new VerifyCS.Test()
+ {
+ TestCode = @"using System;
+
+class C
+{
+ Action lambda = (x) =>
+ {
+ [|if (x is null)
+ throw new ArgumentNullException(nameof(x));|]
+ };
+}
+",
+ FixedCode = @"using System;
+
+class C
+{
+ Action lambda = (x!!) =>
+ {
+ };
+}
+",
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestParenthesizedLambdaWithType()
+ {
+ await new VerifyCS.Test()
+ {
+ TestCode = @"using System;
+
+class C
+{
+ Action lambda = (string x) =>
+ {
+ [|if (x is null)
+ throw new ArgumentNullException(nameof(x));|]
+ };
+}
+",
+ FixedCode = @"using System;
+
+class C
+{
+ Action lambda = (string x!!) =>
+ {
+ };
+}
+",
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestAnonymousMethod()
+ {
+ await new VerifyCS.Test()
+ {
+ TestCode = @"using System;
+
+class C
+{
+ Action lambda = delegate (string x)
+ {
+ [|if (x is null)
+ throw new ArgumentNullException(nameof(x));|]
+ };
+}
+",
+ FixedCode = @"using System;
+
+class C
+{
+ Action lambda = delegate (string x!!)
+ {
+ };
+}
+",
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestLocalFunctionWithinAccessor()
+ {
+ var testCode = @"using System;
+class C
+{
+ private int _p;
+
+ public int P
+ {
+ get => _p;
+ set
+ {
+ local();
+
+ void local()
+ {
+ if (value == null) throw new ArgumentNullException(nameof(value));
+ _p = value;
+ }
+ }
+ }
+}
+";
+ await new VerifyCS.Test()
+ {
+ TestCode = testCode,
+ FixedCode = testCode,
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestLocalFunctionCapturingParameter()
+ {
+ var testCode = @"using System;
+class C
+{
+ void M(string param)
+ {
+ local();
+
+ void local()
+ {
+ if (param == null)
+ throw new ArgumentNullException(nameof(param));
+ }
+ }
+}
+";
+ await new VerifyCS.Test()
+ {
+ TestCode = testCode,
+ FixedCode = testCode,
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestNotWithUserDefinedOperator()
+ {
+ var testCode = @"using System;
+class C
+{
+ public static bool operator ==(C c1, C c2) => true;
+ public static bool operator !=(C c1, C c2) => false;
+
+ static void M(C c)
+ {
+ if (c == null)
+ throw new ArgumentNullException(nameof(c));
+ }
+}
+";
+ await new VerifyCS.Test()
+ {
+ TestCode = testCode,
+ FixedCode = testCode,
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestWithUnusedUserDefinedOperator()
+ {
+ await new VerifyCS.Test()
+ {
+ TestCode = @"using System;
+class C
+{
+ public static bool operator ==(C c1, C c2) => true;
+ public static bool operator !=(C c1, C c2) => false;
+
+ static void M(C c)
+ {
+ [|if ((object)c == null)
+ throw new ArgumentNullException(nameof(c));|]
+ }
+}
+",
+ FixedCode = @"using System;
+class C
+{
+ public static bool operator ==(C c1, C c2) => true;
+ public static bool operator !=(C c1, C c2) => false;
+
+ static void M(C c!!)
+ {
+ }
+}
+",
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestIsWithUnusedUserDefinedOperator()
+ {
+ await new VerifyCS.Test()
+ {
+ TestCode = @"using System;
+class C
+{
+ public static bool operator ==(C c1, C c2) => true;
+ public static bool operator !=(C c1, C c2) => false;
+
+ static void M(C c)
+ {
+ [|if (c is null)
+ throw new ArgumentNullException(nameof(c));|]
+ }
+}
+",
+ FixedCode = @"using System;
+class C
+{
+ public static bool operator ==(C c1, C c2) => true;
+ public static bool operator !=(C c1, C c2) => false;
+
+ static void M(C c!!)
+ {
+ }
+}
+",
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestWithPreprocessorDirective()
+ {
+ await new VerifyCS.Test()
+ {
+ TestCode = @"using System;
+class C
+{
+ static void M(C c)
+ {
+#nullable enable
+ [|if ((object)c == null)
+ throw new ArgumentNullException(nameof(c));|]
+#nullable disable
+ }
+}
+",
+ FixedCode = @"using System;
+class C
+{
+ static void M(C c!!)
+ {
+#nullable disable
+ }
+}
+",
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestWithIfPreprocessorDirective()
+ {
+ await new VerifyCS.Test()
+ {
+ TestCode = @"#define DEBUG
+using System;
+class C
+{
+ static void M(C c)
+ {
+#if DEBUG
+ [|if ((object)c == null)
+ throw new ArgumentNullException(nameof(c));|]
+#endif
+ }
+}
+",
+ FixedCode = @"#define DEBUG
+using System;
+class C
+{
+ static void M(C c!!)
+ {
+
+#if DEBUG
+#endif
+ }
+}
+",
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestWithAlias()
+ {
+ await new VerifyCS.Test()
+ {
+ TestCode = @"using Ex = System.ArgumentNullException;
+class C
+{
+ static void M(C c)
+ {
+ [|if ((object)c == null)
+ throw new Ex(nameof(c));|]
+ }
+}
+",
+ FixedCode = @"using Ex = System.ArgumentNullException;
+class C
+{
+ static void M(C c!!)
+ {
+ }
+}
+",
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestPointer()
+ {
+ await new VerifyCS.Test()
+ {
+ TestCode = @"using System;
+class C
+{
+ static unsafe void M(int* ptr)
+ {
+ [|if (ptr == null)
+ throw new ArgumentNullException(nameof(ptr));|]
+ }
+}
+",
+ FixedCode = @"using System;
+class C
+{
+ static unsafe void M(int* ptr!!)
+ {
+ }
+}
+",
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestFunctionPointer()
+ {
+ await new VerifyCS.Test()
+ {
+ TestCode = @"using System;
+class C
+{
+ static unsafe void M(delegate* ptr)
+ {
+ [|if (ptr == null)
+ throw new ArgumentNullException(nameof(ptr));|]
+ }
+}
+",
+ FixedCode = @"using System;
+class C
+{
+ static unsafe void M(delegate* ptr!!)
+ {
+ }
+}
+",
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
+ public async Task TestPartialMethod()
+ {
+ var partialDeclaration = @"
+partial class C
+{
+ static partial void M(C c);
+}
+";
+
+ await new VerifyCS.Test()
+ {
+ TestState =
+ {
+ Sources =
+ {
+ partialDeclaration,
+ @"using System;
+partial class C
+{
+ static partial void M(C c)
+ {
+ [|if ((object)c == null)
+ throw new ArgumentNullException(nameof(c));|]
+ }
+}
+"
+ }
+ },
+ FixedState =
+ {
+ Sources =
+ {
+ partialDeclaration,
+ @"using System;
+partial class C
+{
+ static partial void M(C c!!)
+ {
+ }
+}
+"
+ }
+ },
+ LanguageVersion = LanguageVersionExtensions.CSharpNext
+ }.RunAsync();
+ }
+ }
+}
diff --git a/src/Analyzers/Core/Analyzers/EnforceOnBuildValues.cs b/src/Analyzers/Core/Analyzers/EnforceOnBuildValues.cs
index e1026b4c559a1..4f5e6d27eedfe 100644
--- a/src/Analyzers/Core/Analyzers/EnforceOnBuildValues.cs
+++ b/src/Analyzers/Core/Analyzers/EnforceOnBuildValues.cs
@@ -87,6 +87,7 @@ internal static class EnforceOnBuildValues
public const EnforceOnBuild PopulateSwitchStatement = /*IDE0010*/ EnforceOnBuild.WhenExplicitlyEnabled;
public const EnforceOnBuild UseInferredMemberName = /*IDE0037*/ EnforceOnBuild.WhenExplicitlyEnabled;
public const EnforceOnBuild UseIsNullCheck = /*IDE0041*/ EnforceOnBuild.WhenExplicitlyEnabled;
+ public const EnforceOnBuild UseParameterNullChecking = /*IDE0190*/ EnforceOnBuild.WhenExplicitlyEnabled;
public const EnforceOnBuild AddRequiredParentheses = /*IDE0048*/ EnforceOnBuild.WhenExplicitlyEnabled;
public const EnforceOnBuild ExpressionValueIsUnused = /*IDE0058*/ EnforceOnBuild.WhenExplicitlyEnabled;
public const EnforceOnBuild MakeStructFieldsWritable = /*IDE0064*/ EnforceOnBuild.WhenExplicitlyEnabled;
diff --git a/src/Analyzers/Core/Analyzers/IDEDiagnosticIds.cs b/src/Analyzers/Core/Analyzers/IDEDiagnosticIds.cs
index 0b02a866c04ed..592390e6f38ab 100644
--- a/src/Analyzers/Core/Analyzers/IDEDiagnosticIds.cs
+++ b/src/Analyzers/Core/Analyzers/IDEDiagnosticIds.cs
@@ -167,6 +167,8 @@ internal static class IDEDiagnosticIds
public const string UseTupleSwapDiagnosticId = "IDE0180";
+ public const string UseParameterNullCheckingId = "IDE0190";
+
// Analyzer error Ids
public const string AnalyzerChangedId = "IDE1001";
public const string AnalyzerDependencyConflictId = "IDE1002";
diff --git a/src/Analyzers/Core/CodeFixes/PredefinedCodeFixProviderNames.cs b/src/Analyzers/Core/CodeFixes/PredefinedCodeFixProviderNames.cs
index 68ee4523ee54a..e84bd13bf549f 100644
--- a/src/Analyzers/Core/CodeFixes/PredefinedCodeFixProviderNames.cs
+++ b/src/Analyzers/Core/CodeFixes/PredefinedCodeFixProviderNames.cs
@@ -133,6 +133,7 @@ internal static class PredefinedCodeFixProviderNames
public const string UseInterpolatedVerbatimString = nameof(UseInterpolatedVerbatimString);
public const string UseIsNotExpression = nameof(UseIsNotExpression);
public const string UseIsNullCheck = nameof(UseIsNullCheck);
+ public const string UseParameterNullChecking = nameof(UseParameterNullChecking);
public const string UseIsNullCheckForCastAndEqualityOperator = nameof(UseIsNullCheckForCastAndEqualityOperator);
public const string UseIsNullCheckForReferenceEquals = nameof(UseIsNullCheckForReferenceEquals);
public const string UseLocalFunction = nameof(UseLocalFunction);
diff --git a/src/Compilers/Test/Core/Traits/Traits.cs b/src/Compilers/Test/Core/Traits/Traits.cs
index 66a26418fe87d..cb0992ea28c49 100644
--- a/src/Compilers/Test/Core/Traits/Traits.cs
+++ b/src/Compilers/Test/Core/Traits/Traits.cs
@@ -194,6 +194,7 @@ public static class Features
public const string CodeActionsUseInterpolatedVerbatimString = "CodeActions.UseInterpolatedVerbatimString";
public const string CodeActionsUseIsNotExpression = "CodeActions.UseIsNotExpression";
public const string CodeActionsUseIsNullCheck = "CodeActions.UseIsNullCheck";
+ public const string CodeActionsUseParameterNullChecking = "CodeActions.UseParameterNullChecking";
public const string CodeActionsUseLocalFunction = "CodeActions.UseLocalFunction";
public const string CodeActionsUseNamedArguments = "CodeActions.UseNamedArguments";
public const string CodeActionsUseNotPattern = "CodeActions.UseNotPattern";
diff --git a/src/EditorFeatures/CSharpTest/InitializeParameter/AddParameterCheckTests.cs b/src/EditorFeatures/CSharpTest/InitializeParameter/AddParameterCheckTests.cs
index 7470bfef5aabb..5a7a43b86ad35 100644
--- a/src/EditorFeatures/CSharpTest/InitializeParameter/AddParameterCheckTests.cs
+++ b/src/EditorFeatures/CSharpTest/InitializeParameter/AddParameterCheckTests.cs
@@ -1,4 +1,4 @@
-// Licensed to the .NET Foundation under one or more agreements.
+// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
@@ -76,6 +76,27 @@ public C([||]string s!!)
}.RunAsync();
}
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInitializeParameter)]
+ public async Task TestRecordPrimaryConstructor()
+ {
+ // https://github.com/dotnet/roslyn/issues/58779
+ // Note: we declare a field within the record to work around missing IsExternalInit errors
+ await new VerifyCS.Test
+ {
+ LanguageVersion = LanguageVersionExtensions.CSharpNext,
+ TestCode = @"
+using System;
+
+record Rec([||]string s) { public string s = s; }
+",
+ FixedCode = @"
+using System;
+
+record Rec(string s) { public string s = s; }
+"
+ }.RunAsync();
+ }
+
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInitializeParameter)]
public async Task TestSimpleReferenceType_AlreadyNullChecked2()
{
diff --git a/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs b/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs
index 02c55dcec4e8c..5d8b95a977999 100644
--- a/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs
+++ b/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs
@@ -429,6 +429,9 @@ public void CSharp_VerifyIDEDiagnosticSeveritiesAreConfigurable()
# IDE0180
dotnet_diagnostic.IDE0180.severity = %value%
+# IDE0190
+dotnet_diagnostic.IDE0190.severity = %value%
+
# IDE2000
dotnet_diagnostic.IDE2000.severity = %value%
@@ -1010,6 +1013,9 @@ No editorconfig based code style option
# IDE0180, PreferTupleSwap
csharp_style_prefer_tuple_swap = true
+# IDE0190, PreferParameterNullChecking
+csharp_style_prefer_parameter_null_checking = true
+
# IDE1005, PreferConditionalDelegateCall
csharp_style_conditional_delegate_call = true
diff --git a/src/VisualStudio/CSharp/Impl/CSharpVSResources.resx b/src/VisualStudio/CSharp/Impl/CSharpVSResources.resx
index 258958fa8a8b1..40a3b3131603e 100644
--- a/src/VisualStudio/CSharp/Impl/CSharpVSResources.resx
+++ b/src/VisualStudio/CSharp/Impl/CSharpVSResources.resx
@@ -524,6 +524,9 @@
Prefer 'is null' for reference equality checks
'is null' is a C# string and should not be localized.
+
+ Prefer parameter null checking
+
Report invalid placeholders in 'string.Format' calls
diff --git a/src/VisualStudio/CSharp/Impl/EditorConfigSettings/DataProvider/CodeStyle/CSharpCodeStyleSettingsProvider.cs b/src/VisualStudio/CSharp/Impl/EditorConfigSettings/DataProvider/CodeStyle/CSharpCodeStyleSettingsProvider.cs
index 1e726b9de6f24..5a391c3d78d37 100644
--- a/src/VisualStudio/CSharp/Impl/EditorConfigSettings/DataProvider/CodeStyle/CSharpCodeStyleSettingsProvider.cs
+++ b/src/VisualStudio/CSharp/Impl/EditorConfigSettings/DataProvider/CodeStyle/CSharpCodeStyleSettingsProvider.cs
@@ -103,6 +103,10 @@ private IEnumerable GetNullCheckingCodeStyleOptions(AnalyzerCo
description: CSharpVSResources.Prefer_null_check_over_type_check,
editorConfigOptions: editorConfigOptions,
visualStudioOptions: visualStudioOptions, updater: updaterService, fileName: FileName);
+ yield return CodeStyleSetting.Create(option: CSharpCodeStyleOptions.PreferParameterNullChecking,
+ description: CSharpVSResources.Prefer_parameter_null_checking,
+ editorConfigOptions: editorConfigOptions,
+ visualStudioOptions: visualStudioOptions, updater: updaterService, fileName: FileName);
}
private IEnumerable GetModifierCodeStyleOptions(AnalyzerConfigOptions editorConfigOptions, OptionSet visualStudioOptions, OptionUpdater updaterService)
diff --git a/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.Style.cs b/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.Style.cs
index e8f977663d36b..f4b23b785a1b0 100644
--- a/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.Style.cs
+++ b/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.Style.cs
@@ -237,6 +237,12 @@ public string Style_PreferIsNullCheckOverReferenceEqualityMethod
set { SetXmlOption(CodeStyleOptions2.PreferIsNullCheckOverReferenceEqualityMethod, value); }
}
+ public string Style_PreferParameterNullChecking
+ {
+ get { return GetXmlOption(CSharpCodeStyleOptions.PreferParameterNullChecking); }
+ set { SetXmlOption(CSharpCodeStyleOptions.PreferParameterNullChecking, value); }
+ }
+
public string Style_PreferNullCheckOverTypeCheck
{
get { return GetXmlOption(CSharpCodeStyleOptions.PreferNullCheckOverTypeCheck); }
diff --git a/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs b/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs
index d2fbb60139c4b..4a4d360b1675b 100644
--- a/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs
+++ b/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs
@@ -1122,6 +1122,28 @@ void M2(string value1, string value2)
//]
}}
}}
+";
+
+ private static readonly string s_preferParameterNullChecking = $@"
+using System;
+
+class Customer
+{{
+//[
+ // {ServicesVSResources.Prefer_colon}
+ void M1(string value!!)
+ {{
+ }}
+//]
+//[
+ // {ServicesVSResources.Over_colon}
+ void M2(string value)
+ {{
+ if (value is null)
+ throw new ArgumentNullException(nameof(value));
+ }}
+//]
+}}
";
private static readonly string s_preferNullcheckOverTypeCheck = $@"
@@ -2108,6 +2130,7 @@ internal StyleViewModel(OptionStore optionStore, IServiceProvider serviceProvide
CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CodeStyleOptions2.PreferCoalesceExpression, ServicesVSResources.Prefer_coalesce_expression, s_preferCoalesceExpression, s_preferCoalesceExpression, this, optionStore, nullCheckingGroupTitle));
CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CodeStyleOptions2.PreferNullPropagation, ServicesVSResources.Prefer_null_propagation, s_preferNullPropagation, s_preferNullPropagation, this, optionStore, nullCheckingGroupTitle));
CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CodeStyleOptions2.PreferIsNullCheckOverReferenceEqualityMethod, CSharpVSResources.Prefer_is_null_for_reference_equality_checks, s_preferIsNullOverReferenceEquals, s_preferIsNullOverReferenceEquals, this, optionStore, nullCheckingGroupTitle));
+ CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CSharpCodeStyleOptions.PreferParameterNullChecking, CSharpVSResources.Prefer_parameter_null_checking, s_preferParameterNullChecking, s_preferParameterNullChecking, this, optionStore, nullCheckingGroupTitle));
CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CSharpCodeStyleOptions.PreferNullCheckOverTypeCheck, CSharpVSResources.Prefer_null_check_over_type_check, s_preferNullcheckOverTypeCheck, s_preferNullcheckOverTypeCheck, this, optionStore, nullCheckingGroupTitle));
// Using directive preferences.
diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.cs.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.cs.xlf
index bcb0b9dda1e3a..c06cecf28b22f 100644
--- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.cs.xlf
+++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.cs.xlf
@@ -142,6 +142,11 @@
Upřednostňovat kontrolu hodnoty null před kontrolou typu
+
+
+ Prefer parameter null checking
+
+
Upřednostňovat porovnávání vzorů
diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.de.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.de.xlf
index c8727c855de95..5939f02eec3c0 100644
--- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.de.xlf
+++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.de.xlf
@@ -142,6 +142,11 @@
"NULL"-Überprüfung vor Typüberprüfung bevorzugen
+
+
+ Prefer parameter null checking
+
+
Musterabgleich bevorzugen
diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.es.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.es.xlf
index 9f520f7aa0694..9881a2f526e40 100644
--- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.es.xlf
+++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.es.xlf
@@ -142,6 +142,11 @@
Preferir comprobación "null' sobre comprobación de tipo
+
+
+ Prefer parameter null checking
+
+
Preferir coincidencia de patrones
diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.fr.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.fr.xlf
index 4b3d4bbe92c02..bd23ab7804c77 100644
--- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.fr.xlf
+++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.fr.xlf
@@ -142,6 +142,11 @@
Préférer la vérification « null » à la vérification de type
+
+
+ Prefer parameter null checking
+
+
Préférer les critères spéciaux
diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.it.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.it.xlf
index 436467d250a55..129b9798d0e8e 100644
--- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.it.xlf
+++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.it.xlf
@@ -142,6 +142,11 @@
Preferisci il controllo 'null' al controllo del tipo
+
+
+ Prefer parameter null checking
+
+
Preferisci i criteri di ricerca
diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ja.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ja.xlf
index 74ded6521f820..23702d25d215c 100644
--- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ja.xlf
+++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ja.xlf
@@ -142,6 +142,11 @@
型のチェックよりも 'null 値' チェックを優先する
+
+
+ Prefer parameter null checking
+
+
パターン マッチングを優先する
diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ko.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ko.xlf
index ae5e528b54aef..7c44d4a628d73 100644
--- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ko.xlf
+++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ko.xlf
@@ -142,6 +142,11 @@
형식 검사보다 'null' 검사 선호
+
+
+ Prefer parameter null checking
+
+
패턴 일치 선호
diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pl.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pl.xlf
index 230dbc961c0fd..c6fc1fd110317 100644
--- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pl.xlf
+++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pl.xlf
@@ -142,6 +142,11 @@
Ustaw preferencje na sprawdzanie wartości „null” zamiast sprawdzania typu
+
+
+ Prefer parameter null checking
+
+
Preferuj dopasowywanie do wzorca
diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pt-BR.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pt-BR.xlf
index c5ae128b775b0..d6544ac27686b 100644
--- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pt-BR.xlf
+++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pt-BR.xlf
@@ -142,6 +142,11 @@
Preferir a verificação 'nula' à verificação de tipo
+
+
+ Prefer parameter null checking
+
+
Preferir a correspondência de padrões
diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ru.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ru.xlf
index c020550f114e7..40f05e0f8675c 100644
--- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ru.xlf
+++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ru.xlf
@@ -142,6 +142,11 @@
Предпочитать проверку "null" проверке типа
+
+
+ Prefer parameter null checking
+
+
Предпочитать соответствие шаблону
diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.tr.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.tr.xlf
index d95ae7aa6044c..c0ec9176998da 100644
--- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.tr.xlf
+++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.tr.xlf
@@ -142,6 +142,11 @@
Tür denetimi yerine 'null' denetimini tercih edin
+
+
+ Prefer parameter null checking
+
+
Desen eşleştirmeyi tercih et
diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hans.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hans.xlf
index 36757db206d37..c6461e38bfd9e 100644
--- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hans.xlf
+++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hans.xlf
@@ -142,6 +142,11 @@
与类型检查相比,首选 Null 检查
+
+
+ Prefer parameter null checking
+
+
首选模式匹配
diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hant.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hant.xlf
index d020b3844fde6..e922ea7b6dfd6 100644
--- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hant.xlf
+++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hant.xlf
@@ -142,6 +142,11 @@
建議使用 'null' 檢查而非鍵入檢查
+
+
+ Prefer parameter null checking
+
+
建議使用模式比對
diff --git a/src/VisualStudio/Core/Test/Options/CSharpEditorConfigGeneratorTests.vb b/src/VisualStudio/Core/Test/Options/CSharpEditorConfigGeneratorTests.vb
index 9ba945865f45c..0fd050a669a7b 100644
--- a/src/VisualStudio/Core/Test/Options/CSharpEditorConfigGeneratorTests.vb
+++ b/src/VisualStudio/Core/Test/Options/CSharpEditorConfigGeneratorTests.vb
@@ -118,6 +118,7 @@ csharp_style_prefer_switch_expression = true
# Null-checking preferences
csharp_style_conditional_delegate_call = true
+csharp_style_prefer_parameter_null_checking = true
# Modifier preferences
csharp_prefer_static_local_function = true
@@ -351,6 +352,7 @@ csharp_style_prefer_switch_expression = true
# Null-checking preferences
csharp_style_conditional_delegate_call = true
+csharp_style_prefer_parameter_null_checking = true
# Modifier preferences
csharp_prefer_static_local_function = true
diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/CodeStyle/CSharpCodeStyleOptions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/CodeStyle/CSharpCodeStyleOptions.cs
index be5c6225f880a..a9430fceb2571 100644
--- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/CodeStyle/CSharpCodeStyleOptions.cs
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/CodeStyle/CSharpCodeStyleOptions.cs
@@ -300,6 +300,12 @@ private static Option2> CreateUsingDirectiv
"csharp_style_prefer_null_check_over_type_check",
$"TextEditor.CSharp.Specific.{nameof(PreferNullCheckOverTypeCheck)}");
+ internal static readonly Option2> PreferParameterNullChecking = CreateOption(
+ CSharpCodeStyleOptionGroups.NullCheckingPreferences, nameof(PreferParameterNullChecking),
+ defaultValue: s_trueWithSuggestionEnforcement,
+ "csharp_style_prefer_parameter_null_checking",
+ $"TextEditor.CSharp.Specific.{nameof(PreferParameterNullChecking)}");
+
public static Option2> AllowEmbeddedStatementsOnSameLine { get; } = CreateOption(
CSharpCodeStyleOptionGroups.NewLinePreferences, nameof(AllowEmbeddedStatementsOnSameLine),
defaultValue: CodeStyleOptions2.TrueWithSilentEnforcement,