diff --git a/src/Analyzers/MSTest.Analyzers/UseProperAssertMethodsAnalyzer.cs b/src/Analyzers/MSTest.Analyzers/UseProperAssertMethodsAnalyzer.cs index cfa77db522..dac84bcaaa 100644 --- a/src/Analyzers/MSTest.Analyzers/UseProperAssertMethodsAnalyzer.cs +++ b/src/Analyzers/MSTest.Analyzers/UseProperAssertMethodsAnalyzer.cs @@ -207,6 +207,7 @@ private static bool IsIsNotNullPattern(IOperation operation, [NotNullWhen(true)] private static bool IsEqualsNullBinaryOperator(IOperation operation, [NotNullWhen(true)] out SyntaxNode? expressionUnderTest) { if (operation is IBinaryOperation { OperatorKind: BinaryOperatorKind.Equals, RightOperand: { } rightOperand } binaryOperation && + binaryOperation.OperatorMethod is not { MethodKind: MethodKind.UserDefinedOperator } && rightOperand.WalkDownConversion() is ILiteralOperation { ConstantValue: { HasValue: true, Value: null } }) { expressionUnderTest = binaryOperation.LeftOperand.Syntax; @@ -221,6 +222,7 @@ private static bool IsEqualsNullBinaryOperator(IOperation operation, [NotNullWhe private static bool IsNotEqualsNullBinaryOperator(IOperation operation, [NotNullWhen(true)] out SyntaxNode? expressionUnderTest) { if (operation is IBinaryOperation { OperatorKind: BinaryOperatorKind.NotEquals, RightOperand: { } rightOperand } binaryOperation && + binaryOperation.OperatorMethod is not { MethodKind: MethodKind.UserDefinedOperator } && rightOperand.WalkDownConversion() is ILiteralOperation { ConstantValue: { HasValue: true, Value: null } }) { expressionUnderTest = binaryOperation.LeftOperand.Syntax; @@ -253,7 +255,8 @@ private static EqualityCheckStatus RecognizeEqualityCheck(IOperation operation, toBecomeActual = isPattern1.Value.Syntax; return EqualityCheckStatus.Equals; } - else if (operation is IBinaryOperation { OperatorKind: BinaryOperatorKind.Equals } binaryOperation1) + else if (operation is IBinaryOperation { OperatorKind: BinaryOperatorKind.Equals } binaryOperation1 && + binaryOperation1.OperatorMethod is not { MethodKind: MethodKind.UserDefinedOperator }) { // This is quite arbitrary. We can do extra checks to see which one (if any) looks like a "constant" and make it the expected. toBecomeExpected = binaryOperation1.RightOperand.Syntax; @@ -266,7 +269,8 @@ private static EqualityCheckStatus RecognizeEqualityCheck(IOperation operation, toBecomeActual = isPattern2.Value.Syntax; return EqualityCheckStatus.NotEquals; } - else if (operation is IBinaryOperation { OperatorKind: BinaryOperatorKind.NotEquals } binaryOperation2) + else if (operation is IBinaryOperation { OperatorKind: BinaryOperatorKind.NotEquals } binaryOperation2 && + binaryOperation2.OperatorMethod is not { MethodKind: MethodKind.UserDefinedOperator }) { // This is quite arbitrary. We can do extra checks to see which one (if any) looks like a "constant" and make it the expected. toBecomeExpected = binaryOperation2.RightOperand.Syntax; diff --git a/test/UnitTests/MSTest.Analyzers.UnitTests/UseProperAssertMethodsAnalyzerTests.cs b/test/UnitTests/MSTest.Analyzers.UnitTests/UseProperAssertMethodsAnalyzerTests.cs index cdb5bfaf7c..1676550103 100644 --- a/test/UnitTests/MSTest.Analyzers.UnitTests/UseProperAssertMethodsAnalyzerTests.cs +++ b/test/UnitTests/MSTest.Analyzers.UnitTests/UseProperAssertMethodsAnalyzerTests.cs @@ -11,6 +11,14 @@ namespace MSTest.Analyzers.Test; [TestClass] public sealed class UseProperAssertMethodsAnalyzerTests { + private const string SomeClassWithUserDefinedEqualityOperators = """ + public class SomeClass + { + public static bool operator ==(SomeClass x, SomeClass y) => true; + public static bool operator !=(SomeClass x, SomeClass y) => false; + } + """; + [TestMethod] public async Task WhenAssertIsTrueWithEqualsNullArgument() { @@ -51,6 +59,29 @@ await VerifyCS.VerifyCodeFixAsync( fixedCode); } + [TestMethod] + public async Task WhenAssertIsTrueWithEqualsNullArgumentAndUserDefinedOperator() + { + string code = $$""" + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class MyTestClass + { + [TestMethod] + public void MyTestMethod() + { + SomeClass x = new SomeClass(); + Assert.IsTrue(x == null); + } + } + + {{SomeClassWithUserDefinedEqualityOperators}} + """; + + await VerifyCS.VerifyCodeFixAsync(code, code); + } + [TestMethod] public async Task WhenAssertIsTrueWithIsNullArgument() { @@ -91,6 +122,50 @@ await VerifyCS.VerifyCodeFixAsync( fixedCode); } + [TestMethod] + public async Task WhenAssertIsTrueWithIsNullArgumentAndUserDefinedOperator() + { + string code = $$""" + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class MyTestClass + { + [TestMethod] + public void MyTestMethod() + { + SomeClass x = new SomeClass(); + {|#0:Assert.IsTrue(x is null)|}; + } + } + + {{SomeClassWithUserDefinedEqualityOperators}} + """; + + string fixedCode = $$""" + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class MyTestClass + { + [TestMethod] + public void MyTestMethod() + { + SomeClass x = new SomeClass(); + Assert.IsNull(x); + } + } + + {{SomeClassWithUserDefinedEqualityOperators}} + """; + + await VerifyCS.VerifyCodeFixAsync( + code, + // /0/Test0.cs(10,9): info MSTEST0037: Use 'Assert.IsNull' instead of 'Assert.IsTrue' + VerifyCS.DiagnosticIgnoringAdditionalLocations().WithLocation(0).WithArguments("IsNull", "IsTrue"), + fixedCode); + } + [TestMethod] public async Task WhenAssertIsTrueWithNotEqualsNullArgument() { @@ -131,6 +206,29 @@ await VerifyCS.VerifyCodeFixAsync( fixedCode); } + [TestMethod] + public async Task WhenAssertIsTrueWithNotEqualsNullArgumentAndUserDefinedOperator() + { + string code = $$""" + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class MyTestClass + { + [TestMethod] + public void MyTestMethod() + { + SomeClass x = new SomeClass(); + Assert.IsTrue(x != null); + } + } + + {{SomeClassWithUserDefinedEqualityOperators}} + """; + + await VerifyCS.VerifyCodeFixAsync(code, code); + } + [TestMethod] public async Task WhenAssertIsTrueWithIsNotNullArgument() { @@ -171,6 +269,50 @@ await VerifyCS.VerifyCodeFixAsync( fixedCode); } + [TestMethod] + public async Task WhenAssertIsTrueWithIsNotNullArgumentAndUserDefinedOperator() + { + string code = $$""" + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class MyTestClass + { + [TestMethod] + public void MyTestMethod() + { + SomeClass x = new SomeClass(); + {|#0:Assert.IsTrue(x is not null)|}; + } + } + + {{SomeClassWithUserDefinedEqualityOperators}} + """; + + string fixedCode = $$""" + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class MyTestClass + { + [TestMethod] + public void MyTestMethod() + { + SomeClass x = new SomeClass(); + Assert.IsNotNull(x); + } + } + + {{SomeClassWithUserDefinedEqualityOperators}} + """; + + await VerifyCS.VerifyCodeFixAsync( + code, + // /0/Test0.cs(10,9): info MSTEST0037: Use 'Assert.IsNotNull' instead of 'Assert.IsTrue' + VerifyCS.DiagnosticIgnoringAdditionalLocations().WithLocation(0).WithArguments("IsNotNull", "IsTrue"), + fixedCode); + } + [TestMethod] public async Task WhenAssertIsFalseWithEqualsNullArgument() { @@ -211,6 +353,29 @@ await VerifyCS.VerifyCodeFixAsync( fixedCode); } + [TestMethod] + public async Task WhenAssertIsFalseWithEqualsNullArgumentAndUserDefinedOperator() + { + string code = $$""" + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class MyTestClass + { + [TestMethod] + public void MyTestMethod() + { + SomeClass x = new SomeClass(); + Assert.IsFalse(x == null); + } + } + + {{SomeClassWithUserDefinedEqualityOperators}} + """; + + await VerifyCS.VerifyCodeFixAsync(code, code); + } + [TestMethod] public async Task WhenAssertIsFalseWithIsNullArgument() { @@ -251,6 +416,50 @@ await VerifyCS.VerifyCodeFixAsync( fixedCode); } + [TestMethod] + public async Task WhenAssertIsFalseWithIsNullArgumentAndUserDefinedOperator() + { + string code = $$""" + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class MyTestClass + { + [TestMethod] + public void MyTestMethod() + { + SomeClass x = new SomeClass(); + {|#0:Assert.IsFalse(x is null)|}; + } + } + + {{SomeClassWithUserDefinedEqualityOperators}} + """; + + string fixedCode = $$""" + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class MyTestClass + { + [TestMethod] + public void MyTestMethod() + { + SomeClass x = new SomeClass(); + Assert.IsNotNull(x); + } + } + + {{SomeClassWithUserDefinedEqualityOperators}} + """; + + await VerifyCS.VerifyCodeFixAsync( + code, + // /0/Test0.cs(10,9): info MSTEST0037: Use 'Assert.IsNotNull' instead of 'Assert.IsFalse' + VerifyCS.DiagnosticIgnoringAdditionalLocations().WithLocation(0).WithArguments("IsNotNull", "IsFalse"), + fixedCode); + } + [TestMethod] public async Task WhenAssertIsFalseWithNotEqualsNullArgument() { @@ -291,6 +500,29 @@ await VerifyCS.VerifyCodeFixAsync( fixedCode); } + [TestMethod] + public async Task WhenAssertIsFalseWithNotEqualsNullArgumentAndUserDefinedOperator() + { + string code = $$""" + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class MyTestClass + { + [TestMethod] + public void MyTestMethod() + { + SomeClass x = new SomeClass(); + Assert.IsFalse(x != null); + } + } + + {{SomeClassWithUserDefinedEqualityOperators}} + """; + + await VerifyCS.VerifyCodeFixAsync(code, code); + } + [TestMethod] public async Task WhenAssertIsFalseWithIsNotNullArgument() { @@ -331,6 +563,50 @@ await VerifyCS.VerifyCodeFixAsync( fixedCode); } + [TestMethod] + public async Task WhenAssertIsFalseWithIsNotNullArgumentAndUserDefinedOperator() + { + string code = $$""" + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class MyTestClass + { + [TestMethod] + public void MyTestMethod() + { + SomeClass x = new SomeClass(); + {|#0:Assert.IsFalse(x is not null)|}; + } + } + + {{SomeClassWithUserDefinedEqualityOperators}} + """; + + string fixedCode = $$""" + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class MyTestClass + { + [TestMethod] + public void MyTestMethod() + { + SomeClass x = new SomeClass(); + Assert.IsNull(x); + } + } + + {{SomeClassWithUserDefinedEqualityOperators}} + """; + + await VerifyCS.VerifyCodeFixAsync( + code, + // /0/Test0.cs(10,9): info MSTEST0037: Use 'Assert.IsNull' instead of 'Assert.IsFalse' + VerifyCS.DiagnosticIgnoringAdditionalLocations().WithLocation(0).WithArguments("IsNull", "IsFalse"), + fixedCode); + } + [TestMethod] public async Task WhenAssertIsTrueAndArgumentIsEquality() { @@ -373,6 +649,30 @@ await VerifyCS.VerifyCodeFixAsync( fixedCode); } + [TestMethod] + public async Task WhenAssertIsTrueAndArgumentIsEqualityAndUserDefinedOperator() + { + string code = $$""" + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class MyTestClass + { + [TestMethod] + public void MyTestMethod() + { + SomeClass x = new SomeClass(); + SomeClass y = new SomeClass(); + Assert.IsTrue(x == y); + } + } + + {{SomeClassWithUserDefinedEqualityOperators}} + """; + + await VerifyCS.VerifyCodeFixAsync(code, code); + } + [TestMethod] public async Task WhenAssertIsTrueAndArgumentIsInequality() { @@ -415,6 +715,30 @@ await VerifyCS.VerifyCodeFixAsync( fixedCode); } + [TestMethod] + public async Task WhenAssertIsTrueAndArgumentIsInequalityAndUserDefinedOperator() + { + string code = $$""" + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class MyTestClass + { + [TestMethod] + public void MyTestMethod() + { + SomeClass x = new SomeClass(); + SomeClass y = new SomeClass(); + Assert.IsTrue(x != y); + } + } + + {{SomeClassWithUserDefinedEqualityOperators}} + """; + + await VerifyCS.VerifyCodeFixAsync(code, code); + } + [TestMethod] public async Task WhenAssertIsFalseAndArgumentIsEquality() { @@ -457,6 +781,30 @@ await VerifyCS.VerifyCodeFixAsync( fixedCode); } + [TestMethod] + public async Task WhenAssertIsFalseAndArgumentIsEqualityAndUserDefinedOperator() + { + string code = $$""" + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class MyTestClass + { + [TestMethod] + public void MyTestMethod() + { + SomeClass x = new SomeClass(); + SomeClass y = new SomeClass(); + Assert.IsFalse(x == y); + } + } + + {{SomeClassWithUserDefinedEqualityOperators}} + """; + + await VerifyCS.VerifyCodeFixAsync(code, code); + } + [TestMethod] public async Task WhenAssertIsFalseAndArgumentIsInequality() { @@ -499,6 +847,30 @@ await VerifyCS.VerifyCodeFixAsync( fixedCode); } + [TestMethod] + public async Task WhenAssertIsFalseAndArgumentIsInequalityAndUserDefinedOperator() + { + string code = $$""" + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class MyTestClass + { + [TestMethod] + public void MyTestMethod() + { + SomeClass x = new SomeClass(); + SomeClass y = new SomeClass(); + Assert.IsFalse(x != y); + } + } + + {{SomeClassWithUserDefinedEqualityOperators}} + """; + + await VerifyCS.VerifyCodeFixAsync(code, code); + } + [TestMethod] public async Task WhenAssertAreEqualAndExpectedIsNull() {