Skip to content

Commit

Permalink
Improve SyntaxLogicalInverter to Handling Additional Cases and reduce…
Browse files Browse the repository at this point in the history
… Unnecessary Parentheses (#1086)

Co-authored-by: Josef Pihrt <josef@pihrt.net>
  • Loading branch information
jamesHargreaves12 and josefpihrt authored Jun 1, 2023
1 parent 6833900 commit ee6668f
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 9 deletions.
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());
}

}

0 comments on commit ee6668f

Please sign in to comment.