diff --git a/src/nunit.analyzers.tests/ConstraintsUsage/EqualConstraintUsageAnalyzerTests.cs b/src/nunit.analyzers.tests/ConstraintsUsage/EqualConstraintUsageAnalyzerTests.cs
index df963251..41741e9a 100644
--- a/src/nunit.analyzers.tests/ConstraintsUsage/EqualConstraintUsageAnalyzerTests.cs
+++ b/src/nunit.analyzers.tests/ConstraintsUsage/EqualConstraintUsageAnalyzerTests.cs
@@ -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()
{
diff --git a/src/nunit.analyzers.tests/ConstraintsUsage/EqualConstraintUsageCodeFixTests.cs b/src/nunit.analyzers.tests/ConstraintsUsage/EqualConstraintUsageCodeFixTests.cs
index e9db73a5..9b6fc0be 100644
--- a/src/nunit.analyzers.tests/ConstraintsUsage/EqualConstraintUsageCodeFixTests.cs
+++ b/src/nunit.analyzers.tests/ConstraintsUsage/EqualConstraintUsageCodeFixTests.cs
@@ -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()
{
diff --git a/src/nunit.analyzers/ConstraintUsage/EqualConstraintUsageAnalyzer.cs b/src/nunit.analyzers/ConstraintUsage/EqualConstraintUsageAnalyzer.cs
index cf5769be..f1118a8d 100644
--- a/src/nunit.analyzers/ConstraintUsage/EqualConstraintUsageAnalyzer.cs
+++ b/src/nunit.analyzers/ConstraintUsage/EqualConstraintUsageAnalyzer.cs
@@ -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)
{
diff --git a/src/nunit.analyzers/ConstraintUsage/EqualConstraintUsageCodeFix.cs b/src/nunit.analyzers/ConstraintUsage/EqualConstraintUsageCodeFix.cs
index 36c233c6..cc059520 100644
--- a/src/nunit.analyzers/ConstraintUsage/EqualConstraintUsageCodeFix.cs
+++ b/src/nunit.analyzers/ConstraintUsage/EqualConstraintUsageCodeFix.cs
@@ -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
@@ -58,5 +79,85 @@ private static (ExpressionSyntax? actual, ExpressionSyntax? expected) GetActualE
return (null, null);
}
+
+ ///
+ /// Converts an 'is' pattern to a corresponding nunit EqualTo invocation.
+ ///
+ ///
+ /// We support:
+ /// constant-pattern,
+ /// relational-pattern: <, <=, >, >=.
+ /// not supported-pattern,
+ /// supported-pattern or supported-pattern,
+ /// supported-pattern and supported-pattern.
+ ///
+ 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;
+ }
}
}