Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve SyntaxLogicalInverter to Handling Additional Cases and reduce Unnecesarry Parentheses #1086

Merged
merged 4 commits into from Jun 1, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions ChangeLog.md
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
Expand Up @@ -67,15 +67,16 @@ internal static SyntaxLogicalInverter GetInstance(Document document)
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 @@ internal static SyntaxLogicalInverter GetInstance(Document document)

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
@@ -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());
}

}