diff --git a/ChangeLog.md b/ChangeLog.md index 1f84588956..00c5077e66 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -36,6 +36,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Update processing of .globalconfig file to prioritize file-specific diagnostic severities over global diagnostic severities. [#1066](https://github.com/JosefPihrt/Roslynator/pull/1066/files) - Fix RCS1009 to handles discard designations ([#1063](https://github.com/JosefPihrt/Roslynator/pull/1063/files)). - [CLI] Fix number of formatted documents, file banners added ([#1072](https://github.com/JosefPihrt/Roslynator/pull/1072)). +- Improve support for coalesce expressions in code fixes that require computing the logical inversion of an expression, such as [RCS1208](https://github.com/JosefPihrt/Roslynator/blob/main/docs/analyzers/RCS1208.md) ([#1069](https://github.com/JosefPihrt/Roslynator/pull/1069)). ## [4.2.0] - 2022-11-27 diff --git a/src/CSharp.Workspaces/CSharp/SyntaxLogicalInverter.cs b/src/CSharp.Workspaces/CSharp/SyntaxLogicalInverter.cs index 2b1609e169..d42090810f 100644 --- a/src/CSharp.Workspaces/CSharp/SyntaxLogicalInverter.cs +++ b/src/CSharp.Workspaces/CSharp/SyntaxLogicalInverter.cs @@ -208,6 +208,23 @@ private ExpressionSyntax LogicallyInvertImpl( { return DefaultInvert(expression); } + case SyntaxKind.CoalesceExpression: + { + var binaryExpression = (BinaryExpressionSyntax)expression; + if (binaryExpression.Right.IsKind(SyntaxKind.FalseLiteralExpression)) + { + // !(x ?? false) === (x != true) + return NotEqualsExpression(binaryExpression.Left, TrueLiteralExpression()); + } + + if (binaryExpression.Right.IsKind(SyntaxKind.TrueLiteralExpression)) + { + // !(x ?? true) === (x == false) + return EqualsExpression(binaryExpression.Left, FalseLiteralExpression()); + } + + return DefaultInvert(expression); + } } Debug.Fail($"Logical inversion of unknown kind '{expression.Kind()}'"); diff --git a/src/Tests/Analyzers.Tests/RCS1208ReduceIfNestingTests.cs b/src/Tests/Analyzers.Tests/RCS1208ReduceIfNestingTests.cs index 8c4bb75260..9a91c5a142 100644 --- a/src/Tests/Analyzers.Tests/RCS1208ReduceIfNestingTests.cs +++ b/src/Tests/Analyzers.Tests/RCS1208ReduceIfNestingTests.cs @@ -49,6 +49,126 @@ void M2() } "); } + + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.ReduceIfNesting)] + public async Task Test_InvertingCoalesceToFalse() + { + await VerifyDiagnosticAndFixAsync(@" +class C +{ + void M(bool? p) + { + [|if|] (p??false) + { + M2(); + } + } + + void M2() + { + } +} +", @" +class C +{ + void M(bool? p) + { + if (p != true) + { + return; + } + + M2(); + } + + void M2() + { + } +} +"); + } + + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.ReduceIfNesting)] + public async Task Test_InvertingCoalesceToTrue() + { + await VerifyDiagnosticAndFixAsync(@" +class C +{ + void M(bool? p) + { + [|if|] (p??true) + { + M2(); + } + } + + void M2() + { + } +} +", @" +class C +{ + void M(bool? p) + { + if (p == false) + { + return; + } + + M2(); + } + + void M2() + { + } +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.ReduceIfNesting)] + public async Task Test_InvertingCoalesceToUnknown() + { + await VerifyDiagnosticAndFixAsync(@" +class C +{ + bool b { get; set; } + + void M(bool? p) + { + [|if|] (p??b) + { + M2(); + } + } + + void M2() + { + } +} +", @" +class C +{ + bool b { get; set; } + + void M(bool? p) + { + if (!(p ?? b)) + { + return; + } + + M2(); + } + + void M2() + { + } +} +"); + } [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.ReduceIfNesting)] public async Task TestNoDiagnostic_OverlappingLocalVariables()