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

NUnit2010: Add support for detecting use of 'is' pattern inside 'Assert.That' #703

Merged
merged 1 commit into from
Mar 11, 2024
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
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;
}
}
}