From c26a06db1050e539abb456ec94b019074d1026db Mon Sep 17 00:00:00 2001 From: Josef Pihrt Date: Fri, 18 Sep 2020 14:29:07 +0200 Subject: [PATCH] Rename analyzer SimplifyBooleanExpression to UnnecessaryNullCheck (RCS1199) (fix #373) --- .../BinaryExpressionCodeFixProvider.cs | 57 ++- .../SimplifyBooleanExpressionRefactoring.cs | 44 -- src/Analyzers/Analyzers.xml | 19 +- .../SimplifyBooleanExpressionAnalyzer.cs | 118 ----- .../Analysis/UnnecessaryNullCheckAnalyzer.cs | 135 ++++++ .../CSharp/DiagnosticDescriptors.Generated.cs | 12 +- .../CSharp/DiagnosticIdentifiers.Generated.cs | 2 +- .../SimplifyBooleanExpression.cs | 48 -- .../RCS1199UnnecessaryNullCheckTests.cs | 448 ++++++++++++++++++ 9 files changed, 659 insertions(+), 224 deletions(-) delete mode 100644 src/Analyzers.CodeFixes/CSharp/Refactorings/SimplifyBooleanExpressionRefactoring.cs delete mode 100644 src/Analyzers/CSharp/Analysis/SimplifyBooleanExpressionAnalyzer.cs create mode 100644 src/Analyzers/CSharp/Analysis/UnnecessaryNullCheckAnalyzer.cs delete mode 100644 src/Tests/Analyzers.Tests.Old/SimplifyBooleanExpression.cs create mode 100644 src/Tests/Analyzers.Tests/RCS1199UnnecessaryNullCheckTests.cs diff --git a/src/Analyzers.CodeFixes/CSharp/CodeFixes/BinaryExpressionCodeFixProvider.cs b/src/Analyzers.CodeFixes/CSharp/CodeFixes/BinaryExpressionCodeFixProvider.cs index d256430c87..0b1b8a4561 100644 --- a/src/Analyzers.CodeFixes/CSharp/CodeFixes/BinaryExpressionCodeFixProvider.cs +++ b/src/Analyzers.CodeFixes/CSharp/CodeFixes/BinaryExpressionCodeFixProvider.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; using Roslynator.CodeFixes; using Roslynator.CSharp.Refactorings; using Roslynator.CSharp.Syntax; @@ -38,7 +39,7 @@ public sealed override ImmutableArray FixableDiagnosticIds DiagnosticIdentifiers.ValueTypeObjectIsNeverEqualToNull, DiagnosticIdentifiers.JoinStringExpressions, DiagnosticIdentifiers.UseExclusiveOrOperator, - DiagnosticIdentifiers.SimplifyBooleanExpression, + DiagnosticIdentifiers.UnnecessaryNullCheck, DiagnosticIdentifiers.UseShortCircuitingOperator, DiagnosticIdentifiers.UnnecessaryOperator); } @@ -198,11 +199,11 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) context.RegisterCodeFix(codeAction, diagnostic); break; } - case DiagnosticIdentifiers.SimplifyBooleanExpression: + case DiagnosticIdentifiers.UnnecessaryNullCheck: { CodeAction codeAction = CodeAction.Create( - "Simplify boolean expression", - cancellationToken => SimplifyBooleanExpressionRefactoring.RefactorAsync(document, binaryExpression, cancellationToken), + "Remove unnecessary null check", + ct => RemoveUnnecessaryNullCheckAsync(document, binaryExpression, ct), GetEquivalenceKey(diagnostic)); context.RegisterCodeFix(codeAction, diagnostic); @@ -335,5 +336,53 @@ private static ConditionalAccessExpressionSyntax CreateConditionalAccess(Express expression.Parenthesize(), MemberBindingExpression(IdentifierName("Length"))); } + + private static async Task RemoveUnnecessaryNullCheckAsync( + Document document, + BinaryExpressionSyntax logicalAnd, + CancellationToken cancellationToken) + { + BinaryExpressionInfo binaryExpressionInfo = SyntaxInfo.BinaryExpressionInfo(logicalAnd); + + ExpressionSyntax right = binaryExpressionInfo.Right; + + SemanticModel semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + + NullCheckExpressionInfo nullCheck = SyntaxInfo.NullCheckExpressionInfo(binaryExpressionInfo.Left, semanticModel, NullCheckStyles.HasValue | NullCheckStyles.NotEqualsToNull); + + var binaryExpression = right as BinaryExpressionSyntax; + + ExpressionSyntax newRight; + switch (right.Kind()) + { + case SyntaxKind.SimpleMemberAccessExpression: + { + newRight = TrueLiteralExpression().WithTriviaFrom(right); + break; + } + case SyntaxKind.LogicalNotExpression: + { + newRight = FalseLiteralExpression().WithTriviaFrom(right); + break; + } + default: + { + newRight = binaryExpression.Right; + break; + } + } + + BinaryExpressionSyntax newBinaryExpression = BinaryExpression( + (binaryExpression != null) + ? right.Kind() + : SyntaxKind.EqualsExpression, + nullCheck.Expression.WithLeadingTrivia(logicalAnd.GetLeadingTrivia()), + (binaryExpression != null) + ? ((BinaryExpressionSyntax)right).OperatorToken + : Token(SyntaxKind.EqualsEqualsToken).WithTriviaFrom(logicalAnd.OperatorToken), + newRight).WithFormatterAnnotation(); + + return await document.ReplaceNodeAsync(logicalAnd, newBinaryExpression, cancellationToken).ConfigureAwait(false); + } } } \ No newline at end of file diff --git a/src/Analyzers.CodeFixes/CSharp/Refactorings/SimplifyBooleanExpressionRefactoring.cs b/src/Analyzers.CodeFixes/CSharp/Refactorings/SimplifyBooleanExpressionRefactoring.cs deleted file mode 100644 index 563bdbbd55..0000000000 --- a/src/Analyzers.CodeFixes/CSharp/Refactorings/SimplifyBooleanExpressionRefactoring.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Josef Pihrt. All rights reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Text; -using static Roslynator.CSharp.CSharpFactory; - -namespace Roslynator.CSharp.Refactorings -{ - internal static class SimplifyBooleanExpressionRefactoring - { - public static Task RefactorAsync( - Document document, - BinaryExpressionSyntax logicalAnd, - CancellationToken cancellationToken) - { - ExpressionSyntax left = logicalAnd.Left; - ExpressionSyntax right = logicalAnd.Right; - - var memberAccessExpression = (MemberAccessExpressionSyntax)left.WalkDownParentheses(); - ExpressionSyntax expression = memberAccessExpression.Expression; - - SyntaxTriviaList trailingTrivia = logicalAnd - .DescendantTrivia(TextSpan.FromBounds(expression.Span.End, left.Span.End)) - .ToSyntaxTriviaList() - .EmptyIfWhitespace() - .AddRange(left.GetTrailingTrivia()); - - BinaryExpressionSyntax equalsExpression = EqualsExpression( - expression - .WithLeadingTrivia(left.GetLeadingTrivia()) - .WithTrailingTrivia(trailingTrivia), - SyntaxFactory.Token(SyntaxKind.EqualsEqualsToken).WithTriviaFrom(logicalAnd.OperatorToken), - (right.WalkDownParentheses().IsKind(SyntaxKind.LogicalNotExpression, SyntaxKind.EqualsExpression)) - ? FalseLiteralExpression() - : TrueLiteralExpression().WithTriviaFrom(right)); - - return document.ReplaceNodeAsync(logicalAnd, equalsExpression, cancellationToken); - } - } -} diff --git a/src/Analyzers/Analyzers.xml b/src/Analyzers/Analyzers.xml index 948cab3095..fef032cabc 100644 --- a/src/Analyzers/Analyzers.xml +++ b/src/Analyzers/Analyzers.xml @@ -3930,10 +3930,10 @@ string x = s + i; // [|Id|]]]> - + RCS1199 - Simplify boolean expression. - Simplification + Unncessary null check. + Redundancy Info true @@ -3947,6 +3947,19 @@ if (x.HasValue && x.Value) // [|Id|] }]]> + + + + diff --git a/src/Analyzers/CSharp/Analysis/SimplifyBooleanExpressionAnalyzer.cs b/src/Analyzers/CSharp/Analysis/SimplifyBooleanExpressionAnalyzer.cs deleted file mode 100644 index 95d9407603..0000000000 --- a/src/Analyzers/CSharp/Analysis/SimplifyBooleanExpressionAnalyzer.cs +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright (c) Josef Pihrt. All rights reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Immutable; -using System.Threading; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; -using Roslynator.CSharp.Syntax; -using static Roslynator.CSharp.CSharpFactory; - -namespace Roslynator.CSharp.Analysis -{ - [DiagnosticAnalyzer(LanguageNames.CSharp)] - public class SimplifyBooleanExpressionAnalyzer : BaseDiagnosticAnalyzer - { - public override ImmutableArray SupportedDiagnostics - { - get { return ImmutableArray.Create(DiagnosticDescriptors.SimplifyBooleanExpression); } - } - - public override void Initialize(AnalysisContext context) - { - base.Initialize(context); - - context.RegisterSyntaxNodeAction(f => AnalyzeLogicalAndExpression(f), SyntaxKind.LogicalAndExpression); - } - - private static void AnalyzeLogicalAndExpression(SyntaxNodeAnalysisContext context) - { - var logicalAnd = (BinaryExpressionSyntax)context.Node; - - if (logicalAnd.SpanContainsDirectives()) - return; - - BinaryExpressionInfo logicalAndInfo = SyntaxInfo.BinaryExpressionInfo(logicalAnd); - - if (!logicalAndInfo.Success) - return; - - ExpressionSyntax left = logicalAndInfo.Left; - - if (!IsPropertyOfNullableOfT(left, "HasValue", context.SemanticModel, context.CancellationToken)) - return; - - ExpressionSyntax right = logicalAndInfo.Right; - - switch (right.Kind()) - { - case SyntaxKind.LogicalNotExpression: - { - var logicalNot = (PrefixUnaryExpressionSyntax)right; - - Analyze(context, logicalAnd, left, logicalNot.Operand?.WalkDownParentheses()); - break; - } - case SyntaxKind.EqualsExpression: - { - BinaryExpressionInfo equalsExpressionInfo = SyntaxInfo.BinaryExpressionInfo((BinaryExpressionSyntax)right); - - if (equalsExpressionInfo.Success - && equalsExpressionInfo.Right.Kind() == SyntaxKind.FalseLiteralExpression) - { - Analyze(context, logicalAnd, left, equalsExpressionInfo.Left); - } - - break; - } - case SyntaxKind.SimpleMemberAccessExpression: - { - Analyze(context, logicalAnd, left, right); - break; - } - } - } - - private static void Analyze( - SyntaxNodeAnalysisContext context, - BinaryExpressionSyntax logicalAnd, - ExpressionSyntax expression1, - ExpressionSyntax expression2) - { - if (IsPropertyOfNullableOfT(expression2, "Value", context.SemanticModel, context.CancellationToken)) - { - expression1 = ((MemberAccessExpressionSyntax)expression1).Expression; - expression2 = ((MemberAccessExpressionSyntax)expression2).Expression; - - if (expression1 != null - && expression2 != null - && AreEquivalent(expression1, expression2)) - { - DiagnosticHelpers.ReportDiagnostic(context, DiagnosticDescriptors.SimplifyBooleanExpression, logicalAnd); - } - } - } - - private static bool IsPropertyOfNullableOfT(ExpressionSyntax expression, string name, SemanticModel semanticModel, CancellationToken cancellationToken) - { - if (expression?.Kind() == SyntaxKind.SimpleMemberAccessExpression) - { - var memberAccessExpression = (MemberAccessExpressionSyntax)expression; - - SimpleNameSyntax simpleName = memberAccessExpression.Name; - - if (simpleName?.Kind() == SyntaxKind.IdentifierName) - { - var identifierName = (IdentifierNameSyntax)simpleName; - - return string.Equals(identifierName.Identifier.ValueText, name, StringComparison.Ordinal) - && SyntaxUtility.IsPropertyOfNullableOfT(expression, name, semanticModel, cancellationToken); - } - } - - return false; - } - } -} diff --git a/src/Analyzers/CSharp/Analysis/UnnecessaryNullCheckAnalyzer.cs b/src/Analyzers/CSharp/Analysis/UnnecessaryNullCheckAnalyzer.cs new file mode 100644 index 0000000000..30bdeb3c48 --- /dev/null +++ b/src/Analyzers/CSharp/Analysis/UnnecessaryNullCheckAnalyzer.cs @@ -0,0 +1,135 @@ +// Copyright (c) Josef Pihrt. All rights reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Roslynator.CSharp.Syntax; +using static Roslynator.CSharp.CSharpFactory; + +namespace Roslynator.CSharp.Analysis +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class UnnecessaryNullCheckAnalyzer : BaseDiagnosticAnalyzer + { + public override ImmutableArray SupportedDiagnostics + { + get { return ImmutableArray.Create(DiagnosticDescriptors.UnnecessaryNullCheck); } + } + + public override void Initialize(AnalysisContext context) + { + base.Initialize(context); + + context.RegisterSyntaxNodeAction(f => AnalyzeLogicalAndExpression(f), SyntaxKind.LogicalAndExpression); + } + + private static void AnalyzeLogicalAndExpression(SyntaxNodeAnalysisContext context) + { + var logicalAnd = (BinaryExpressionSyntax)context.Node; + + if (logicalAnd.SpanContainsDirectives()) + return; + + BinaryExpressionInfo logicalAndInfo = SyntaxInfo.BinaryExpressionInfo(logicalAnd); + + if (!logicalAndInfo.Success) + return; + + NullCheckExpressionInfo nullCheck = SyntaxInfo.NullCheckExpressionInfo( + logicalAndInfo.Left, + context.SemanticModel, + NullCheckStyles.NotEqualsToNull | NullCheckStyles.HasValue, + cancellationToken: context.CancellationToken); + + if (!nullCheck.Success) + return; + + ExpressionSyntax right = logicalAndInfo.Right; + + switch (right.Kind()) + { + case SyntaxKind.LogicalNotExpression: + { + var logicalNot = (PrefixUnaryExpressionSyntax)right; + + Analyze(nullCheck.Expression, logicalNot.Operand?.WalkDownParentheses(), null); + break; + } + case SyntaxKind.EqualsExpression: + case SyntaxKind.LessThanExpression: + case SyntaxKind.LessThanOrEqualExpression: + case SyntaxKind.GreaterThanExpression: + case SyntaxKind.GreaterThanOrEqualExpression: + { + BinaryExpressionInfo binaryExpressionInfo = SyntaxInfo.BinaryExpressionInfo((BinaryExpressionSyntax)right); + + if (!binaryExpressionInfo.Success) + break; + + ExpressionSyntax left = binaryExpressionInfo.Left; + + Analyze(nullCheck.Expression, left, binaryExpressionInfo.Right); + break; + } + case SyntaxKind.SimpleMemberAccessExpression: + { + AnalyzeSimpleMemberAccessExpression(nullCheck.Expression, (MemberAccessExpressionSyntax)right, null); + break; + } + } + + void Analyze(ExpressionSyntax expression1, ExpressionSyntax expression2, ExpressionSyntax expression3) + { + if (expression2.IsKind(SyntaxKind.SimpleMemberAccessExpression)) + AnalyzeSimpleMemberAccessExpression(expression1, (MemberAccessExpressionSyntax)expression2, expression3); + } + + void AnalyzeSimpleMemberAccessExpression(ExpressionSyntax expression, MemberAccessExpressionSyntax memberAccessExpression, ExpressionSyntax expression3) + { + if (!(memberAccessExpression.Name is IdentifierNameSyntax identifierName) + || !string.Equals(identifierName.Identifier.ValueText, "Value", StringComparison.Ordinal)) + { + return; + } + + if (!SyntaxUtility.IsPropertyOfNullableOfT(memberAccessExpression, "Value", context.SemanticModel, context.CancellationToken)) + return; + + if (!AreEquivalent(expression, memberAccessExpression.Expression)) + return; + + if (expression3 != null) + { + switch (expression3.Kind()) + { + case SyntaxKind.NumericLiteralExpression: + case SyntaxKind.StringLiteralExpression: + case SyntaxKind.CharacterLiteralExpression: + case SyntaxKind.TrueLiteralExpression: + case SyntaxKind.FalseLiteralExpression: + { + break; + } + case SyntaxKind.NullLiteralExpression: + case SyntaxKind.DefaultLiteralExpression: + { + return; + } + default: + { + if (context.SemanticModel.GetTypeSymbol(expression3, context.CancellationToken).IsNullableType()) + return; + + break; + } + } + } + + DiagnosticHelpers.ReportDiagnostic(context, DiagnosticDescriptors.UnnecessaryNullCheck, logicalAnd); + } + } + } +} diff --git a/src/Analyzers/CSharp/DiagnosticDescriptors.Generated.cs b/src/Analyzers/CSharp/DiagnosticDescriptors.Generated.cs index 552ad1e668..0787eafd6c 100644 --- a/src/Analyzers/CSharp/DiagnosticDescriptors.Generated.cs +++ b/src/Analyzers/CSharp/DiagnosticDescriptors.Generated.cs @@ -1868,15 +1868,15 @@ public static partial class DiagnosticDescriptors customTags: Array.Empty()); /// RCS1199 - public static readonly DiagnosticDescriptor SimplifyBooleanExpression = DiagnosticDescriptorFactory.Default.Create( - id: DiagnosticIdentifiers.SimplifyBooleanExpression, - title: "Simplify boolean expression.", - messageFormat: "Simplify boolean expression.", - category: DiagnosticCategories.Simplification, + public static readonly DiagnosticDescriptor UnnecessaryNullCheck = DiagnosticDescriptorFactory.Default.Create( + id: DiagnosticIdentifiers.UnnecessaryNullCheck, + title: "Unncessary null check.", + messageFormat: "Unncessary null check.", + category: DiagnosticCategories.Redundancy, defaultSeverity: DiagnosticSeverity.Info, isEnabledByDefault: true, description: null, - helpLinkUri: DiagnosticIdentifiers.SimplifyBooleanExpression, + helpLinkUri: DiagnosticIdentifiers.UnnecessaryNullCheck, customTags: Array.Empty()); /// RCS1200 diff --git a/src/Analyzers/CSharp/DiagnosticIdentifiers.Generated.cs b/src/Analyzers/CSharp/DiagnosticIdentifiers.Generated.cs index fc871cf968..a7c0216654 100644 --- a/src/Analyzers/CSharp/DiagnosticIdentifiers.Generated.cs +++ b/src/Analyzers/CSharp/DiagnosticIdentifiers.Generated.cs @@ -159,7 +159,7 @@ public static partial class DiagnosticIdentifiers public const string CallExtensionMethodAsInstanceMethod = "RCS1196"; public const string OptimizeStringBuilderAppendCall = "RCS1197"; public const string AvoidBoxingOfValueType = "RCS1198"; - public const string SimplifyBooleanExpression = "RCS1199"; + public const string UnnecessaryNullCheck = "RCS1199"; public const string CallThenByInsteadOfOrderBy = "RCS1200"; public const string UseMethodChaining = "RCS1201"; public const string AvoidNullReferenceException = "RCS1202"; diff --git a/src/Tests/Analyzers.Tests.Old/SimplifyBooleanExpression.cs b/src/Tests/Analyzers.Tests.Old/SimplifyBooleanExpression.cs deleted file mode 100644 index 7ccdc78193..0000000000 --- a/src/Tests/Analyzers.Tests.Old/SimplifyBooleanExpression.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Josef Pihrt. All rights reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; - -#pragma warning disable RCS1049 - -namespace Roslynator.CSharp.Analyzers.Tests -{ - internal static class SimplifyBooleanExpression - { - private static void Foo() - { - bool? x = null; - bool? x2 = null; - - if (x.HasValue && x.Value) { } - - if ((x.HasValue) && (x.Value)) { } - - if (x.HasValue && !x.Value) { } - - if ((x.HasValue) && (!x.Value)) { } - - if ((x.HasValue) && (!(x.Value))) { } - - if (x.HasValue && x.Value == false) { } - - if ((x.HasValue) && (x.Value == false)) { } - - if ((x.HasValue) && ((x.Value) == false)) { } - - //n - - if (x2.HasValue && x.Value) { } - if (x.HasValue && x2.Value) { } - if (x.HasValue && x.HasValue) { } - - if (x2.HasValue && !x.Value) { } - if (x.HasValue && !x2.Value) { } - if (x.HasValue && !x.HasValue) { } - - if (x2.HasValue && x.Value == false) { } - if (x.HasValue && x2.Value == false) { } - if (x.HasValue && x.HasValue) { } - } - } -} diff --git a/src/Tests/Analyzers.Tests/RCS1199UnnecessaryNullCheckTests.cs b/src/Tests/Analyzers.Tests/RCS1199UnnecessaryNullCheckTests.cs new file mode 100644 index 0000000000..ff6756a224 --- /dev/null +++ b/src/Tests/Analyzers.Tests/RCS1199UnnecessaryNullCheckTests.cs @@ -0,0 +1,448 @@ +// Copyright (c) Josef Pihrt. All rights reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +using Roslynator.CSharp.CodeFixes; +using Xunit; + +namespace Roslynator.CSharp.Analysis.Tests +{ + public class RCS1199UnnecessaryNullCheckTests : AbstractCSharpFixVerifier + { + public override DiagnosticDescriptor Descriptor { get; } = DiagnosticDescriptors.UnnecessaryNullCheck; + + public override DiagnosticAnalyzer Analyzer { get; } = new UnnecessaryNullCheckAnalyzer(); + + public override CodeFixProvider FixProvider { get; } = new BinaryExpressionCodeFixProvider(); + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UnnecessaryNullCheck)] + public async Task Test_HasValue_Bool() + { + await VerifyDiagnosticAndFixAsync(@" +class C +{ + void M() + { + bool? x = null; + + if ([|x.HasValue && x.Value|]) { } + } +} +", @" +class C +{ + void M() + { + bool? x = null; + + if (x == true) { } + } +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UnnecessaryNullCheck)] + public async Task Test_HasValue_Bool_EqualsTrue() + { + await VerifyDiagnosticAndFixAsync(@" +class C +{ + void M() + { + bool? x = null; + + if ([|x.HasValue && x.Value == true|]) { } + } +} +", @" +class C +{ + void M() + { + bool? x = null; + + if (x == true) { } + } +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UnnecessaryNullCheck)] + public async Task Test_HasValue_Bool_Parentheses() + { + await VerifyDiagnosticAndFixAsync(@" +class C +{ + void M() + { + bool? x = null; + + if ([|(x.HasValue) && (x.Value)|]) { } + } +} +", @" +class C +{ + void M() + { + bool? x = null; + + if (x == true) { } + } +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UnnecessaryNullCheck)] + public async Task Test_HasValue_Bool_False() + { + await VerifyDiagnosticAndFixAsync(@" +class C +{ + void M() + { + bool? x = null; + + if ([|x.HasValue && !x.Value|]) { } + } +} +", @" +class C +{ + void M() + { + bool? x = null; + + if (x == false) { } + } +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UnnecessaryNullCheck)] + public async Task Test_HasValue_Bool_EqualsFalse() + { + await VerifyDiagnosticAndFixAsync(@" +class C +{ + void M() + { + bool? x = null; + + if ([|x.HasValue && x.Value == false|]) { } + } +} +", @" +class C +{ + void M() + { + bool? x = null; + + if (x == false) { } + } +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UnnecessaryNullCheck)] + public async Task Test_HasValue_ValueEquals() + { + await VerifyDiagnosticAndFixAsync(@" +using System; + +class C +{ + void M() + { + DateTime? x = default; + DateTime y = default; + + if ([|x.HasValue && x.Value == y|]) { } + } +} +", @" +using System; + +class C +{ + void M() + { + DateTime? x = default; + DateTime y = default; + + if (x == y) { } + } +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UnnecessaryNullCheck)] + public async Task Test_NotEqualsToNull_ValueEquals() + { + await VerifyDiagnosticAndFixAsync(@" +using System; + +class C +{ + void M() + { + DateTime? x = default; + DateTime y = default; + + if ([|x != null && x.Value == y|]) { } + } +} +", @" +using System; + +class C +{ + void M() + { + DateTime? x = default; + DateTime y = default; + + if (x == y) { } + } +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UnnecessaryNullCheck)] + public async Task Test_NotEqualsToNull_ValueLessThan() + { + await VerifyDiagnosticAndFixAsync(@" +using System; + +class C +{ + void M() + { + DateTime? x = default; + DateTime y = default; + + if ([|x != null && x.Value < y|]) { } + } +} +", @" +using System; + +class C +{ + void M() + { + DateTime? x = default; + DateTime y = default; + + if (x < y) { } + } +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UnnecessaryNullCheck)] + public async Task Test_NotEqualsToNull_ValueLessThanOrEquals() + { + await VerifyDiagnosticAndFixAsync(@" +using System; + +class C +{ + void M() + { + DateTime? x = default; + DateTime y = default; + + if ([|x != null && x.Value <= y|]) { } + } +} +", @" +using System; + +class C +{ + void M() + { + DateTime? x = default; + DateTime y = default; + + if (x <= y) { } + } +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UnnecessaryNullCheck)] + public async Task Test_NotEqualsToNull_ValueGreaterThan() + { + await VerifyDiagnosticAndFixAsync(@" +using System; + +class C +{ + void M() + { + DateTime? x = default; + DateTime y = default; + + if ([|x != null && x.Value > y|]) { } + } +} +", @" +using System; + +class C +{ + void M() + { + DateTime? x = default; + DateTime y = default; + + if (x > y) { } + } +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UnnecessaryNullCheck)] + public async Task Test_NotEqualsToNull_ValueGreaterThanOrEquals() + { + await VerifyDiagnosticAndFixAsync(@" +using System; + +class C +{ + void M() + { + DateTime? x = default; + DateTime y = default; + + if ([|x != null && x.Value >= y|]) { } + } +} +", @" +using System; + +class C +{ + void M() + { + DateTime? x = default; + DateTime y = default; + + if (x >= y) { } + } +} +"); + } + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UnnecessaryNullCheck)] + public async Task TestNoDiagnostic() + { + await VerifyNoDiagnosticAsync(@" +class C +{ + void M() + { + bool? x = null; + bool? x2 = null; + + if (x2.HasValue && x.Value) { } + + if (x.HasValue && x2.Value) { } + + if (x.HasValue && x.HasValue) { } + + if (x2.HasValue && !x.Value) { } + + if (x.HasValue && !x2.Value) { } + + if (x.HasValue && !x.HasValue) { } + + if (x2.HasValue && x.Value == false) { } + + if (x.HasValue && x2.Value == false) { } + + if (x.HasValue && x.HasValue) { } + } +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UnnecessaryNullCheck)] + public async Task TestNoDiagnostic2() + { + await VerifyNoDiagnosticAsync(@" +using System; + +class C +{ + void M() + { + Version x = null; + Version y = null; + + if (x != null && x == y) { } + + if (x != null && x < y) { } + + if (x != null && x <= y) { } + + if (x != null && x > y) { } + + if (x != null && x >= y) { } + } +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UnnecessaryNullCheck)] + public async Task TestNoDiagnostic_RightSideIsNullable() + { + await VerifyNoDiagnosticAsync(@" +using System; + +class C +{ + void M() + { + int? x = null; + int? y = null; + + if (x != null && x.Value == y) { } + + if (x != null && x.Value < y) { } + + if (x != null && x.Value <= y) { } + + if (x != null && x.Value > y) { } + + if (x != null && x.Value >= y) { } + } +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UnnecessaryNullCheck)] + public async Task TestNoDiagnostic_RightSideIsNullOrDefault() + { + await VerifyNoDiagnosticAsync(@" +using System; + +class C +{ + void M() + { + int? x = null; + + if (x != null && x.Value == null) { } + + if (x != null && x.Value == default) { } + } +} +", options: Options.AddAllowedCompilerDiagnosticId("CS0472")); + } + } +}