Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed

- Improve inversion of logical expressions to handling additional cases ([#1086](https://github.com/josefpihrt/roslynator/pull/1086)).

## [4.3.0] - 2023-04-24

### Changed
Expand Down
26 changes: 17 additions & 9 deletions src/CSharp.Workspaces/CSharp/SyntaxLogicalInverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,16 @@ public ExpressionSyntax LogicallyInvert(
return newExpression.WithTriviaFrom(expression);
}

private ParenthesizedExpressionSyntax LogicallyInvertAndParenthesize(
private ExpressionSyntax LogicallyInvertAndParenthesize(
ExpressionSyntax expression,
SemanticModel semanticModel,
CancellationToken cancellationToken)
{
if (expression is null)
return null;

return LogicallyInvertImpl(expression, semanticModel, cancellationToken).Parenthesize();
var inverted = LogicallyInvertImpl(expression, semanticModel, cancellationToken);
return inverted.IsKind(SyntaxKind.LogicalNotExpression, SyntaxKind.ParenthesizedExpression) ? inverted : inverted.Parenthesize();
}

private ExpressionSyntax LogicallyInvertImpl(
Expand All @@ -88,18 +89,22 @@ private ExpressionSyntax LogicallyInvertImpl(

switch (expression.Kind())
{
case SyntaxKind.IdentifierName:
case SyntaxKind.SimpleMemberAccessExpression:
case SyntaxKind.InvocationExpression:
case SyntaxKind.ElementAccessExpression:
case SyntaxKind.CheckedExpression:
case SyntaxKind.UncheckedExpression:
case SyntaxKind.DefaultExpression:
case SyntaxKind.ConditionalAccessExpression:
{
return DefaultInvert(expression, false);
}
case SyntaxKind.PostIncrementExpression:
case SyntaxKind.PostDecrementExpression:
case SyntaxKind.ObjectCreationExpression:
case SyntaxKind.AnonymousObjectCreationExpression:
case SyntaxKind.TypeOfExpression:
case SyntaxKind.DefaultExpression:
case SyntaxKind.CheckedExpression:
case SyntaxKind.UncheckedExpression:
case SyntaxKind.IdentifierName:
{
return DefaultInvert(expression);
}
Expand Down Expand Up @@ -500,7 +505,7 @@ private ExpressionSyntax InvertIsPattern(IsPatternExpressionSyntax isPattern)

return isPattern.WithPattern(newConstantPattern);
}
else if (constantExpression.IsKind(SyntaxKind.NullLiteralExpression))
else if (constantExpression.IsKind(SyntaxKind.NullLiteralExpression, SyntaxKind.NumericLiteralExpression, SyntaxKind.StringLiteralExpression))
{
UnaryPatternSyntax notPattern = NotPattern(constantPattern.WithoutTrivia()).WithTriviaFrom(constantPattern);

Expand All @@ -515,14 +520,17 @@ private ExpressionSyntax InvertIsPattern(IsPatternExpressionSyntax isPattern)
return DefaultInvert(isPattern);
}

private static PrefixUnaryExpressionSyntax DefaultInvert(ExpressionSyntax expression)
private static PrefixUnaryExpressionSyntax DefaultInvert(ExpressionSyntax expression, bool needsParenthesize = true)
{
SyntaxDebug.Assert(expression.Kind() != SyntaxKind.ParenthesizedExpression, expression);

SyntaxTriviaList leadingTrivia = expression.GetLeadingTrivia();
expression = expression.WithoutLeadingTrivia();
if (needsParenthesize)
expression = expression.Parenthesize();

return LogicalNotExpression(
expression.WithoutLeadingTrivia().Parenthesize(),
expression,
Token(leadingTrivia, SyntaxKind.ExclamationToken, SyntaxTriviaList.Empty));
}
}
70 changes: 70 additions & 0 deletions src/Tests/CSharp.Workspaces.Tests/SyntaxLogicalInverterTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using Xunit;

namespace Roslynator.CSharp.Workspaces.Tests;

public class SyntaxLogicallyInvertTests
{
private SyntaxLogicalInverter _inverter;

public SyntaxLogicallyInvertTests()
{
_inverter = SyntaxLogicalInverter.Default;
}

[Theory]
[InlineData(@"x", @"!x")]
[InlineData(@"!x", @"x")]
[InlineData(@"x is ""abc""", @"x is not ""abc""")]
[InlineData(@"x is 1", @"x is not 1")]
[InlineData(@"x is null", @"x is not null")]
[InlineData(@"x is true", @"x is false")]
[InlineData(@"true", @"false")]
[InlineData(@"false", @"true")]
[InlineData(@"x >= 3", @"x < 3")]
[InlineData(@"x > 3", @"x <= 3")]
[InlineData(@"x <= 3", @"x > 3")]
[InlineData(@"x < 3", @"x >= 3")]
[InlineData(@"x == y", @"x != y")]
[InlineData(@"x != y", @"x == y")]
[InlineData(@"(bool)x || (bool)y", @"!((bool)x) && !((bool)y)")]
[InlineData(@"(bool)x && (bool)y", @"!((bool)x) || !((bool)y)")]
[InlineData(@"x ?? true", @"x == false")]
[InlineData(@"x ?? false", @"x != true")]
[InlineData(@"(bool)x ? y : z", @"(bool)x ? !y : !z")]
[InlineData(@"x[0]", @"!x[0]")]
[InlineData(@"default(bool)", @"!default(bool)")]
[InlineData(@"checked(x + y)", @"!checked(x + y)")]
[InlineData(@"unchecked(x + y)", @"!unchecked(x + y)")]
[InlineData(@"(bool)x", @"!((bool)x)")]
[InlineData(@"x & y", @"!x | !y")]
[InlineData(@"x ^ y", @"!(x ^ y)")]
[InlineData(@"x | y", @"!x & !y")]
[InlineData(@"x = y", @"!(x = y)")]
[InlineData(@"await x", @"!(await x)")]
[InlineData(@"x ?? y", @"!(x ?? y)")]
[InlineData(@"x.a", @"!x.a")]
[InlineData(@"x.a()", @"!x.a()")]
[InlineData(@"x?.a", @"!x?.a")]
public async Task LogicallyInvert(string source, string expected)
{
var sourceCode = $"class C {{ void M(dynamic x, dynamic y, dynamic z){{ if({source})return;}} }}";
var workspace = new AdhocWorkspace();
var newProject = workspace.AddProject("TestProject", LanguageNames.CSharp);
var newDocument = workspace.AddDocument(newProject.Id, "TestDocument.cs", SourceText.From(sourceCode));
var syntaxTree = await newDocument.GetSyntaxTreeAsync();
var compilation = await newDocument.Project.GetCompilationAsync();
var semanticModel = compilation.GetSemanticModel(syntaxTree);

var expression = syntaxTree.GetRoot().DescendantNodes().OfType<IfStatementSyntax>().Single().Condition;

var result = _inverter.LogicallyInvert(expression, semanticModel, CancellationToken.None);
Assert.Equal(expected, result.NormalizeWhitespace().ToFullString());
}

}