Skip to content

Commit

Permalink
Merge pull request #703 from manfred-brands/issues/691_IsNull
Browse files Browse the repository at this point in the history
NUnit2010: Add support for detecting use of 'is' pattern inside 'Assert.That'
  • Loading branch information
manfred-brands authored Mar 11, 2024
2 parents 59a9926 + f93b9cc commit 4798485
Show file tree
Hide file tree
Showing 4 changed files with 199 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,26 @@ public void AnalyzeWhenNotEqualsOperatorUsed()
RoslynAssert.Diagnostics(analyzer, isNotEqualToDiagnostic, testCode);
}

[Test]
public void AnalyzeWhenIsOperatorUsed()
{
var testCode = TestUtility.WrapInTestMethod(@"
var actual = ""abc"";
Assert.That(↓actual is ""abc"");");

RoslynAssert.Diagnostics(analyzer, isEqualToDiagnostic, testCode);
}

[Test]
public void AnalyzeWhenIsNotOperatorUsed()
{
var testCode = TestUtility.WrapInTestMethod(@"
var actual = ""abc"";
Assert.That(↓actual is not ""bcd"");");

RoslynAssert.Diagnostics(analyzer, isNotEqualToDiagnostic, testCode);
}

[Test]
public void AnalyzeWhenEqualsInstanceMethodUsed()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,76 @@ public void FixesNotEqualsOperator()
RoslynAssert.CodeFix(analyzer, fix, equalConstraintDiagnostic, code, fixedCode);
}

[Test]
public void FixesIsOperator()
{
var code = TestUtility.WrapInTestMethod(@"
var actual = ""abc"";
Assert.That(actual is ""abc"");");

var fixedCode = TestUtility.WrapInTestMethod(@"
var actual = ""abc"";
Assert.That(actual, Is.EqualTo(""abc""));");

RoslynAssert.CodeFix(analyzer, fix, equalConstraintDiagnostic, code, fixedCode);
}

[Test]
public void FixesIsNotOperator()
{
var code = TestUtility.WrapInTestMethod(@"
var actual = ""abc"";
Assert.That(actual is not ""abc"");");

var fixedCode = TestUtility.WrapInTestMethod(@"
var actual = ""abc"";
Assert.That(actual, Is.Not.EqualTo(""abc""));");

RoslynAssert.CodeFix(analyzer, fix, equalConstraintDiagnostic, code, fixedCode);
}

[Test]
public void FixesComplexIsOperator()
{
var code = TestUtility.WrapInTestMethod(@"
var actual = ""abc"";
Assert.That(actual is ""abc"" or ""def"");");

var fixedCode = TestUtility.WrapInTestMethod(@"
var actual = ""abc"";
Assert.That(actual, Is.EqualTo(""abc"").Or.EqualTo(""def""));");

RoslynAssert.CodeFix(analyzer, fix, equalConstraintDiagnostic, code, fixedCode);
}

[Test]
public void FixesComplexIsNotOperator()
{
var code = TestUtility.WrapInTestMethod(@"
var actual = ""abc"";
Assert.That(actual is not ""abc"" and not ""def"");");

var fixedCode = TestUtility.WrapInTestMethod(@"
var actual = ""abc"";
Assert.That(actual, Is.Not.EqualTo(""abc"").And.Not.EqualTo(""def""));");

RoslynAssert.CodeFix(analyzer, fix, equalConstraintDiagnostic, code, fixedCode);
}

[Test]
public void FixesComplexRelationalIsOperator()
{
var code = TestUtility.WrapInTestMethod(@"
double actual = 1.234;
Assert.That(actual is > 1 and <= 2 or 3 or > 4);");

var fixedCode = TestUtility.WrapInTestMethod(@"
double actual = 1.234;
Assert.That(actual, Is.GreaterThan(1).And.LessThanOrEqualTo(2).Or.EqualTo(3).Or.GreaterThan(4));");

RoslynAssert.CodeFix(analyzer, fix, equalConstraintDiagnostic, code, fixedCode);
}

[Test]
public void FixesEqualsInstanceMethod()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ protected override (DiagnosticDescriptor? descriptor, string? suggestedConstrain
shouldReport = true;
negated = !negated;
}
else if (actual is IIsPatternOperation isPatternOperation)
{
shouldReport = true;
if (isPatternOperation.Pattern is INegatedPatternOperation)
negated = true;
}

if (shouldReport)
{
Expand Down
105 changes: 103 additions & 2 deletions src/nunit.analyzers/ConstraintUsage/EqualConstraintUsageCodeFix.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,38 @@ public class EqualConstraintUsageCodeFix : BaseConditionConstraintCodeFix
protected override (ExpressionSyntax? actual, ExpressionSyntax? constraintExpression) GetActualAndConstraintExpression(ExpressionSyntax conditionNode, string suggestedConstraintString)
{
var (actual, expected) = GetActualExpected(conditionNode);
var constraintExpression = GetConstraintExpression(suggestedConstraintString, expected);

InvocationExpressionSyntax? constraintExpression;

if (expected is ExpressionSyntax expression)
{
constraintExpression = GetConstraintExpression(suggestedConstraintString, expression);
}
else if (expected is PatternSyntax pattern)
{
constraintExpression = this.ConvertPattern(
SyntaxFactory.IdentifierName(NUnitFrameworkConstants.NameOfIs),
pattern);
}
else
{
constraintExpression = null;
}

return (actual, constraintExpression);
}

private static (ExpressionSyntax? actual, ExpressionSyntax? expected) GetActualExpected(SyntaxNode conditionNode)
private static (ExpressionSyntax? actual, ExpressionOrPatternSyntax? expected) GetActualExpected(SyntaxNode conditionNode)
{
if (conditionNode is BinaryExpressionSyntax binaryExpression &&
(binaryExpression.IsKind(SyntaxKind.EqualsExpression) || binaryExpression.IsKind(SyntaxKind.NotEqualsExpression)))
{
return (binaryExpression.Left, binaryExpression.Right);
}
else if (conditionNode is IsPatternExpressionSyntax isPatternExpression)
{
return (isPatternExpression.Expression, isPatternExpression.Pattern);
}
else
{
if (conditionNode is PrefixUnaryExpressionSyntax prefixUnary
Expand Down Expand Up @@ -58,5 +79,85 @@ private static (ExpressionSyntax? actual, ExpressionSyntax? expected) GetActualE

return (null, null);
}

/// <summary>
/// Converts an 'is' pattern to a corresponding nunit EqualTo invocation.
/// </summary>
/// <remarks>
/// We support:
/// constant-pattern,
/// relational-pattern: &lt;, &lt;=, &gt;, &gt;=.
/// not supported-pattern,
/// supported-pattern or supported-pattern,
/// supported-pattern and supported-pattern.
/// </remarks>
private InvocationExpressionSyntax? ConvertPattern(ExpressionSyntax member, PatternSyntax pattern)
{
if (pattern is ConstantPatternSyntax constantPattern)
{
return SyntaxFactory.InvocationExpression(
SyntaxFactory.MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
member,
SyntaxFactory.IdentifierName(NUnitFrameworkConstants.NameOfIsEqualTo)),
SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument(constantPattern.Expression))));
}
else if (pattern is RelationalPatternSyntax relationalPattern)
{
string? identifier = relationalPattern.OperatorToken.Kind() switch
{
SyntaxKind.LessThanToken => NUnitFrameworkConstants.NameOfIsLessThan,
SyntaxKind.LessThanEqualsToken => NUnitFrameworkConstants.NameOfIsLessThanOrEqualTo,
SyntaxKind.GreaterThanToken => NUnitFrameworkConstants.NameOfIsGreaterThan,
SyntaxKind.GreaterThanEqualsToken => NUnitFrameworkConstants.NameOfIsGreaterThanOrEqualTo,
_ => null,
};

if (identifier is not null)
{
return SyntaxFactory.InvocationExpression(
SyntaxFactory.MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
member,
SyntaxFactory.IdentifierName(identifier)),
SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument(relationalPattern.Expression))));
}
}
else if (pattern is UnaryPatternSyntax unaryPattern && unaryPattern.IsKind(SyntaxKind.NotPattern))
{
return this.ConvertPattern(
SyntaxFactory.MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
member,
SyntaxFactory.IdentifierName(NUnitFrameworkConstants.NameOfIsNot)),
unaryPattern.Pattern);
}
else if (pattern is BinaryPatternSyntax binaryPattern)
{
string? constraint = binaryPattern.Kind() switch
{
SyntaxKind.OrPattern => NUnitFrameworkConstants.NameOfConstraintExpressionOr,
SyntaxKind.AndPattern => NUnitFrameworkConstants.NameOfConstraintExpressionAnd,
_ => null,
};

if (constraint is not null)
{
InvocationExpressionSyntax? leftExpression = this.ConvertPattern(member, binaryPattern.Left);

if (leftExpression is not null)
{
return this.ConvertPattern(
SyntaxFactory.MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
leftExpression,
SyntaxFactory.IdentifierName(constraint)),
binaryPattern.Right);
}
}
}

return null;
}
}
}

0 comments on commit 4798485

Please sign in to comment.