From e24e46332f8b759a3a429bcbc12b590bfed22372 Mon Sep 17 00:00:00 2001 From: Meir Blachman Date: Thu, 6 Jun 2024 16:35:05 +0300 Subject: [PATCH] feat: Rewrite parameters order when handling constant values in assertions (#370) * feat: Rewrite parameters order when handling constant values in assertions --- docs/MsTestAnalyzer.md | 30 ++++++- .../MsTestAnalyzerTests.cs | 33 ++++++++ .../Tips/MsTestTests.cs | 83 ++++++++++++++++--- .../Tips/MsTestCodeFixProvider.cs | 24 +++--- .../Utilities/OperartionExtensions.cs | 2 + 5 files changed, 148 insertions(+), 24 deletions(-) diff --git a/docs/MsTestAnalyzer.md b/docs/MsTestAnalyzer.md index cf058f39..d32c4b7d 100644 --- a/docs/MsTestAnalyzer.md +++ b/docs/MsTestAnalyzer.md @@ -11,6 +11,7 @@ This is a generated file, please edit src\FluentAssertions.Analyzers.FluentAsser - [AssertIsInstanceOfType](#scenario-assertisinstanceoftype) - `obj.Should().BeOfType>();` - [AssertIsNotInstanceOfType](#scenario-assertisnotinstanceoftype) - `obj.Should().NotBeOfType>();` - [AssertObjectAreEqual](#scenario-assertobjectareequal) - `obj1.Should().Be(obj2);` +- [AssertObjectAreEqual_LiteralValue](#scenario-assertobjectareequal_literalvalue) - `obj1.Should().Be("foo");` - [AssertOptionalIntegerAreEqual](#scenario-assertoptionalintegerareequal) - `number1.Should().Be(number2);` - [AssertOptionalIntegerAndNullAreEqual](#scenario-assertoptionalintegerandnullareequal) - `number.Should().BeNull();` - [AssertDoubleAreEqual](#scenario-assertdoubleareequal) - `number1.Should().BeApproximately(number2, delta);` @@ -228,6 +229,31 @@ Assert.AreEqual(obj2, obj1); /* fail message: Assert.AreEqual failed. Expected:< obj1.Should().Be(obj2); /* fail message: Expected obj1 to be 42, but found "foo". */ ``` +### scenario: AssertObjectAreEqual_LiteralValue + +```cs +// arrange +object obj1 = "foo"; + +// old assertion: +Assert.AreEqual(obj1, "foo"); + +// new assertion: +obj1.Should().Be("foo"); +``` + +#### Failure messages + +```cs +object obj1 = "foo"; + +// old assertion: +Assert.AreEqual(obj1, "bar"); /* fail message: Assert.AreEqual failed. Expected:. Actual:. */ + +// new assertion: +obj1.Should().Be("bar"); /* fail message: Expected obj1 to be "bar", but found "foo". */ +``` + ### scenario: AssertOptionalIntegerAreEqual ```cs @@ -950,7 +976,7 @@ Action action = ThrowException; // old assertion: Assert.ThrowsException(action); /* fail message: Assert.ThrowsException failed. Threw exception InvalidOperationException, but exception ArgumentException was expected. Exception Message: Operation is not valid due to the current state of the object. -Stack Trace: at FluentAssertions.Analyzers.FluentAssertionAnalyzerDocs.MsTestAnalyzerTests.g__ThrowException|106_0() in /Users/runner/work/fluentassertions.analyzers/src/FluentAssertions.Analyzers.FluentAssertionAnalyzerDocs/MsTestAnalyzerTests.cs:line 1265 +Stack Trace: at FluentAssertions.Analyzers.FluentAssertionAnalyzerDocs.MsTestAnalyzerTests.g__ThrowException|109_0() in /Users/runner/work/fluentassertions.analyzers/src/FluentAssertions.Analyzers.FluentAssertionAnalyzerDocs/MsTestAnalyzerTests.cs:line 1298 at Microsoft.VisualStudio.TestTools.UnitTesting.Assert.ThrowsException[T](Action action, String message, Object[] parameters) */ // new assertion: @@ -980,7 +1006,7 @@ Func action = ThrowExceptionAsync; // old assertion: await Assert.ThrowsExceptionAsync(action); /* fail message: Assert.ThrowsException failed. Threw exception InvalidOperationException, but exception ArgumentException was expected. Exception Message: Operation is not valid due to the current state of the object. -Stack Trace: at FluentAssertions.Analyzers.FluentAssertionAnalyzerDocs.MsTestAnalyzerTests.g__ThrowExceptionAsync|109_0() in /Users/runner/work/fluentassertions.analyzers/src/FluentAssertions.Analyzers.FluentAssertionAnalyzerDocs/MsTestAnalyzerTests.cs:line 1301 +Stack Trace: at FluentAssertions.Analyzers.FluentAssertionAnalyzerDocs.MsTestAnalyzerTests.g__ThrowExceptionAsync|112_0() in /Users/runner/work/fluentassertions.analyzers/src/FluentAssertions.Analyzers.FluentAssertionAnalyzerDocs/MsTestAnalyzerTests.cs:line 1334 at Microsoft.VisualStudio.TestTools.UnitTesting.Assert.ThrowsExceptionAsync[T](Func`1 action, String message, Object[] parameters) */ // new assertion: diff --git a/src/FluentAssertions.Analyzers.FluentAssertionAnalyzerDocs/MsTestAnalyzerTests.cs b/src/FluentAssertions.Analyzers.FluentAssertionAnalyzerDocs/MsTestAnalyzerTests.cs index 23619ace..fdb1ad1a 100644 --- a/src/FluentAssertions.Analyzers.FluentAssertionAnalyzerDocs/MsTestAnalyzerTests.cs +++ b/src/FluentAssertions.Analyzers.FluentAssertionAnalyzerDocs/MsTestAnalyzerTests.cs @@ -267,6 +267,39 @@ public void AssertObjectAreEqual_Failure_NewAssertion() obj1.Should().Be(obj2); } + [TestMethod] + public void AssertObjectAreEqual_LiteralValue() + { + // arrange + object obj1 = "foo"; + + // old assertion: + Assert.AreEqual(obj1, "foo"); + + // new assertion: + obj1.Should().Be("foo"); + } + + [TestMethod, ExpectedException(typeof(AssertFailedException))] + public void AssertObjectAreEqual_LiteralValue_Failure_OldAssertion() + { + // arrange + object obj1 = "foo"; + + // old assertion: + Assert.AreEqual(obj1, "bar"); + } + + [TestMethod, ExpectedException(typeof(AssertFailedException))] + public void AssertObjectAreEqual_LiteralValue_Failure_NewAssertion() + { + // arrange + object obj1 = "foo"; + + // new assertion: + obj1.Should().Be("bar"); + } + [TestMethod] public void AssertOptionalIntegerAreEqual() { diff --git a/src/FluentAssertions.Analyzers.Tests/Tips/MsTestTests.cs b/src/FluentAssertions.Analyzers.Tests/Tips/MsTestTests.cs index f18daa09..43ba91a0 100644 --- a/src/FluentAssertions.Analyzers.Tests/Tips/MsTestTests.cs +++ b/src/FluentAssertions.Analyzers.Tests/Tips/MsTestTests.cs @@ -322,33 +322,26 @@ public void AssertOptionalIntegerAreEqual_TestCodeFix(string oldAssertion, strin [DataTestMethod] [AssertionDiagnostic("Assert.AreEqual(actual, null{0});")] + [AssertionDiagnostic("Assert.AreEqual(null, actual{0});")] [Implemented] - public void AssertOptionalIntegerAndNullAreEqual1_TestAnalyzer(string assertion) => VerifyCSharpDiagnostic("int? actual", assertion); + public void AssertOptionalIntegerAndNullAreEqual_TestAnalyzer(string assertion) => VerifyCSharpDiagnostic("int? actual", assertion); [DataTestMethod] [AssertionCodeFix( oldAssertion: "Assert.AreEqual(actual, null{0});", newAssertion: "actual.Should().BeNull({0});")] - [Implemented] - public void AssertOptionalIntegerAndNullAreEqual1_TestCodeFix(string oldAssertion, string newAssertion) - => VerifyCSharpFix("int? actual", oldAssertion, newAssertion); - - [DataTestMethod] - [AssertionDiagnostic("Assert.AreEqual(null, actual{0});")] - [Implemented] - public void AssertOptionalIntegerAndNullAreEqual2_TestAnalyzer(string assertion) => VerifyCSharpDiagnostic("int? actual", assertion); - - [DataTestMethod] [AssertionCodeFix( oldAssertion: "Assert.AreEqual(null, actual{0});", newAssertion: "actual.Should().BeNull({0});")] [Implemented] - public void AssertOptionalIntegerAndNullAreEqual2_TestCodeFix(string oldAssertion, string newAssertion) + public void AssertOptionalIntegerAndNullAreEqual_TestCodeFix(string oldAssertion, string newAssertion) => VerifyCSharpFix("int? actual", oldAssertion, newAssertion); [DataTestMethod] [AssertionDiagnostic("Assert.AreEqual(expected, actual, delta{0});")] [AssertionDiagnostic("Assert.AreEqual(expected, actual, 0.6{0});")] + [AssertionDiagnostic("Assert.AreEqual(actual, 4.2d, 0.6{0});")] + [AssertionDiagnostic("Assert.AreEqual(4.2d, actual, 0.6{0});")] [Implemented] public void AssertDoubleAreEqual_TestAnalyzer(string assertion) => VerifyCSharpDiagnostic("double actual, double expected, double delta", assertion); @@ -359,6 +352,12 @@ public void AssertOptionalIntegerAndNullAreEqual2_TestCodeFix(string oldAssertio [AssertionCodeFix( oldAssertion: "Assert.AreEqual(expected, actual, 0.6{0});", newAssertion: "actual.Should().BeApproximately(expected, 0.6{0});")] + [AssertionCodeFix( + oldAssertion: "Assert.AreEqual(actual, 4.2d, 0.6{0});", + newAssertion: "actual.Should().BeApproximately(4.2d, 0.6{0});")] + [AssertionCodeFix( + oldAssertion: "Assert.AreEqual(4.2d, actual, 0.6{0});", + newAssertion: "actual.Should().BeApproximately(4.2d, 0.6{0});")] [Implemented] public void AssertDoubleAreEqual_TestCodeFix(string oldAssertion, string newAssertion) => VerifyCSharpFix("double actual, double expected, double delta", oldAssertion, newAssertion); @@ -366,6 +365,8 @@ public void AssertDoubleAreEqual_TestCodeFix(string oldAssertion, string newAsse [DataTestMethod] [AssertionDiagnostic("Assert.AreEqual(expected, actual, delta{0});")] [AssertionDiagnostic("Assert.AreEqual(expected, actual, 0.6f{0});")] + [AssertionDiagnostic("Assert.AreEqual(actual, 4.2f, 0.6f{0});")] + [AssertionDiagnostic("Assert.AreEqual(4.2f, actual, 0.6f{0});")] [Implemented] public void AssertFloatAreEqual_TestAnalyzer(string assertion) => VerifyCSharpDiagnostic("float actual, float expected, float delta", assertion); @@ -376,16 +377,32 @@ public void AssertDoubleAreEqual_TestCodeFix(string oldAssertion, string newAsse [AssertionCodeFix( oldAssertion: "Assert.AreEqual(expected, actual, 0.6f{0});", newAssertion: "actual.Should().BeApproximately(expected, 0.6f{0});")] + [AssertionCodeFix( + oldAssertion: "Assert.AreEqual(actual, 4.2f, 0.6f{0});", + newAssertion: "actual.Should().BeApproximately(4.2f, 0.6f{0});")] + [AssertionCodeFix( + oldAssertion: "Assert.AreEqual(4.2f, actual, 0.6f{0});", + newAssertion: "actual.Should().BeApproximately(4.2f, 0.6f{0});")] [Implemented] public void AssertFloatAreEqual_TestCodeFix(string oldAssertion, string newAssertion) => VerifyCSharpFix("float actual, float expected, float delta", oldAssertion, newAssertion); [DataTestMethod] [AssertionDiagnostic("Assert.AreEqual(expected, actual{0});")] + [AssertionDiagnostic("Assert.AreEqual(actual, \"literal\"{0});")] + [AssertionDiagnostic("Assert.AreEqual(\"literal\", actual{0});")] [AssertionDiagnostic("Assert.AreEqual(expected, actual, false{0});")] + [AssertionDiagnostic("Assert.AreEqual(actual, \"literal\", false{0});")] + [AssertionDiagnostic("Assert.AreEqual(\"literal\", actual, false{0});")] [AssertionDiagnostic("Assert.AreEqual(expected, actual, true{0});")] + [AssertionDiagnostic("Assert.AreEqual(actual, \"literal\", true{0});")] + [AssertionDiagnostic("Assert.AreEqual(\"literal\", actual, true{0});")] [AssertionDiagnostic("Assert.AreEqual(expected, actual, false, System.Globalization.CultureInfo.CurrentCulture{0});")] + [AssertionDiagnostic("Assert.AreEqual(actual, \"literal\", false, System.Globalization.CultureInfo.CurrentCulture{0});")] + [AssertionDiagnostic("Assert.AreEqual(\"literal\", actual, false, System.Globalization.CultureInfo.CurrentCulture{0});")] [AssertionDiagnostic("Assert.AreEqual(expected, actual, true, System.Globalization.CultureInfo.CurrentCulture{0});")] + [AssertionDiagnostic("Assert.AreEqual(actual, \"literal\", true, System.Globalization.CultureInfo.CurrentCulture{0});")] + [AssertionDiagnostic("Assert.AreEqual(\"literal\", actual, true, System.Globalization.CultureInfo.CurrentCulture{0});")] [Implemented] public void AssertStringAreEqual_TestAnalyzer(string assertion) => VerifyCSharpDiagnostic("string actual, string expected", assertion); @@ -393,18 +410,48 @@ public void AssertFloatAreEqual_TestCodeFix(string oldAssertion, string newAsser [AssertionCodeFix( oldAssertion: "Assert.AreEqual(expected, actual{0});", newAssertion: "actual.Should().Be(expected{0});")] + [AssertionCodeFix( + oldAssertion: "Assert.AreEqual(actual, \"literal\"{0});", + newAssertion: "actual.Should().Be(\"literal\"{0});")] + [AssertionCodeFix( + oldAssertion: "Assert.AreEqual(\"literal\", actual{0});", + newAssertion: "actual.Should().Be(\"literal\"{0});")] [AssertionCodeFix( oldAssertion: "Assert.AreEqual(expected, actual, false{0});", newAssertion: "actual.Should().Be(expected{0});")] + [AssertionCodeFix( + oldAssertion: "Assert.AreEqual(actual, \"literal\", false{0});", + newAssertion: "actual.Should().Be(\"literal\"{0});")] + [AssertionCodeFix( + oldAssertion: "Assert.AreEqual(\"literal\", actual, false{0});", + newAssertion: "actual.Should().Be(\"literal\"{0});")] [AssertionCodeFix( oldAssertion: "Assert.AreEqual(expected, actual, true{0});", newAssertion: "actual.Should().BeEquivalentTo(expected{0});")] + [AssertionCodeFix( + oldAssertion: "Assert.AreEqual(actual, \"literal\", true{0});", + newAssertion: "actual.Should().BeEquivalentTo(\"literal\"{0});")] + [AssertionCodeFix( + oldAssertion: "Assert.AreEqual(\"literal\", actual, true{0});", + newAssertion: "actual.Should().BeEquivalentTo(\"literal\"{0});")] [AssertionCodeFix( oldAssertion: "Assert.AreEqual(expected, actual, false, System.Globalization.CultureInfo.CurrentCulture{0});", newAssertion: "actual.Should().Be(expected{0});")] + [AssertionCodeFix( + oldAssertion: "Assert.AreEqual(actual, \"literal\", false, System.Globalization.CultureInfo.CurrentCulture{0});", + newAssertion: "actual.Should().Be(\"literal\"{0});")] + [AssertionCodeFix( + oldAssertion: "Assert.AreEqual(\"literal\", actual, false, System.Globalization.CultureInfo.CurrentCulture{0});", + newAssertion: "actual.Should().Be(\"literal\"{0});")] [AssertionCodeFix( oldAssertion: "Assert.AreEqual(expected, actual, true, System.Globalization.CultureInfo.CurrentCulture{0});", newAssertion: "actual.Should().BeEquivalentTo(expected{0});")] + [AssertionCodeFix( + oldAssertion: "Assert.AreEqual(actual, \"literal\", true, System.Globalization.CultureInfo.CurrentCulture{0});", + newAssertion: "actual.Should().BeEquivalentTo(\"literal\"{0});")] + [AssertionCodeFix( + oldAssertion: "Assert.AreEqual(\"literal\", actual, true, System.Globalization.CultureInfo.CurrentCulture{0});", + newAssertion: "actual.Should().BeEquivalentTo(\"literal\"{0});")] [Implemented] public void AssertStringAreEqual_TestCodeFix(string oldAssertion, string newAssertion) => VerifyCSharpFix("string actual, string expected", oldAssertion, newAssertion); @@ -455,6 +502,7 @@ public void AssertOptionalIntAreNotEqual_TestCodeFix(string oldAssertion, string [DataTestMethod] [AssertionDiagnostic("Assert.AreNotEqual(actual, null{0});")] + [AssertionDiagnostic("Assert.AreNotEqual(null, actual{0});")] [Implemented] public void AssertOptionalIntAndNullAreNotEqual_TestAnalyzer(string assertion) => VerifyCSharpDiagnostic("int? actual", assertion); @@ -462,6 +510,9 @@ public void AssertOptionalIntAreNotEqual_TestCodeFix(string oldAssertion, string [AssertionCodeFix( oldAssertion: "Assert.AreNotEqual(actual, null{0});", newAssertion: "actual.Should().NotBeNull({0});")] + [AssertionCodeFix( + oldAssertion: "Assert.AreNotEqual(null, actual{0});", + newAssertion: "actual.Should().NotBeNull({0});")] [Implemented] public void AssertOptionalIntAndNullAreNotEqual_TestCodeFix(string oldAssertion, string newAssertion) => VerifyCSharpFix("int? actual", oldAssertion, newAssertion); @@ -469,6 +520,8 @@ public void AssertOptionalIntAndNullAreNotEqual_TestCodeFix(string oldAssertion, [DataTestMethod] [AssertionDiagnostic("Assert.AreNotEqual(expected, actual, delta{0});")] [AssertionDiagnostic("Assert.AreNotEqual(expected, actual, 0.6f{0});")] + [AssertionDiagnostic("Assert.AreNotEqual(actual, 4.2f, 0.6f{0});")] + [AssertionDiagnostic("Assert.AreNotEqual(4.2f, actual, 0.6f{0});")] [Implemented] public void AssertFloatAreNotEqual_TestAnalyzer(string assertion) => VerifyCSharpDiagnostic("float actual, float expected, float delta", assertion); @@ -479,6 +532,12 @@ public void AssertOptionalIntAndNullAreNotEqual_TestCodeFix(string oldAssertion, [AssertionCodeFix( oldAssertion: "Assert.AreNotEqual(expected, actual, 0.6f{0});", newAssertion: "actual.Should().NotBeApproximately(expected, 0.6f{0});")] + [AssertionCodeFix( + oldAssertion: "Assert.AreNotEqual(actual, 4.2f, 0.6f{0});", + newAssertion: "actual.Should().NotBeApproximately(4.2f, 0.6f{0});")] + [AssertionCodeFix( + oldAssertion: "Assert.AreNotEqual(4.2f, actual, 0.6f{0});", + newAssertion: "actual.Should().NotBeApproximately(4.2f, 0.6f{0});")] [Implemented] public void AssertFloatAreNotEqual_TestCodeFix(string oldAssertion, string newAssertion) => VerifyCSharpFix("float actual, float expected, float delta", oldAssertion, newAssertion); diff --git a/src/FluentAssertions.Analyzers/Tips/MsTestCodeFixProvider.cs b/src/FluentAssertions.Analyzers/Tips/MsTestCodeFixProvider.cs index 5cae26d6..90eeca61 100644 --- a/src/FluentAssertions.Analyzers/Tips/MsTestCodeFixProvider.cs +++ b/src/FluentAssertions.Analyzers/Tips/MsTestCodeFixProvider.cs @@ -1,5 +1,6 @@ using System.Collections.Immutable; using System.Composition; +using System.Globalization; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Operations; @@ -26,6 +27,8 @@ protected override CreateChangedDocument TryComputeFixCore(IInvocationOperation private CreateChangedDocument TryComputeFixForAssert(IInvocationOperation invocation, CodeFixContext context, TestingFrameworkCodeFixContext t) { + var actualSubjectIndex = invocation.Arguments.Length is 1 ? 0 + : invocation.Arguments[1].IsLiteralValue() ? 0 : 1; switch (invocation.TargetMethod.Name) { case "AreEqual" when ArgumentsAreTypeOf(invocation, t.String, t.String, t.Boolean): // AreEqual(string? expected, string? actual, bool ignoreCase) @@ -34,7 +37,8 @@ private CreateChangedDocument TryComputeFixForAssert(IInvocationOperation invoca { var ignoreCase = invocation.Arguments.Length >= 3 && invocation.Arguments[2].IsLiteralValue(true); var newMethod = ignoreCase ? "BeEquivalentTo" : "Be"; - return DocumentEditorUtils.RenameMethodToSubjectShouldAssertion(invocation, context, newMethod, subjectIndex: 1, argumentsToRemove: [2]); + + return DocumentEditorUtils.RenameMethodToSubjectShouldAssertion(invocation, context, newMethod, actualSubjectIndex, argumentsToRemove: [2]); } case "AreEqual" when ArgumentsAreTypeOf(invocation, t.String, t.String, t.Boolean, t.CultureInfo): // AreEqual(string? expected, string? actual, bool ignoreCase, CultureInfo? culture) case "AreEqual" when ArgumentsAreTypeOf(invocation, t.String, t.String, t.Boolean, t.CultureInfo, t.String): // AreEqual(string? expected, string? actual, bool ignoreCase, CultureInfo? culture, string? message) @@ -48,7 +52,7 @@ private CreateChangedDocument TryComputeFixForAssert(IInvocationOperation invoca break; // TODO: Handle other cultures } - return DocumentEditorUtils.RenameMethodToSubjectShouldAssertion(invocation, context, newMethod, subjectIndex: 1, argumentsToRemove: [2, 3]); + return DocumentEditorUtils.RenameMethodToSubjectShouldAssertion(invocation, context, newMethod, actualSubjectIndex, argumentsToRemove: [2, 3]); } case "AreEqual" when ArgumentsAreTypeOf(invocation, t.Float, t.Float, t.Float): // AreEqual(float expected, float actual, float delta) case "AreEqual" when ArgumentsAreTypeOf(invocation, t.Float, t.Float, t.Float, t.String): // AreEqual(float expected, float actual, float delta, string? message) @@ -59,7 +63,7 @@ private CreateChangedDocument TryComputeFixForAssert(IInvocationOperation invoca case "AreEqual" when ArgumentsAreTypeOf(invocation, t.Decimal, t.Decimal, t.Decimal): // AreEqual(decimal expected, decimal actual, decimal delta) case "AreEqual" when ArgumentsAreTypeOf(invocation, t.Decimal, t.Decimal, t.Decimal, t.String): // AreEqual(decimal expected, decimal actual, decimal delta, string? message) case "AreEqual" when ArgumentsAreTypeOf(invocation, t.Decimal, t.Decimal, t.Decimal, t.String, t.ObjectArray): // AreEqual(decimal expected, decimal actual, decimal delta, string? message, params object?[]? parameters) - return DocumentEditorUtils.RenameMethodToSubjectShouldAssertion(invocation, context, "BeApproximately", subjectIndex: 1, argumentsToRemove: []); + return DocumentEditorUtils.RenameMethodToSubjectShouldAssertion(invocation, context, "BeApproximately", actualSubjectIndex, argumentsToRemove: []); case "AreEqual" when ArgumentsAreTypeOf(invocation, startFromIndex: 2): // AreEqual(T? expected, T? actual) case "AreEqual" when ArgumentsAreTypeOf(invocation, startFromIndex: 2, t.String): // AreEqual(T? expected, T? actual, string? message) case "AreEqual" when ArgumentsAreTypeOf(invocation, startFromIndex: 2, t.String, t.ObjectArray): // AreEqual(T? expected, T? actual, string? message, params object?[]? parameters) @@ -71,14 +75,14 @@ private CreateChangedDocument TryComputeFixForAssert(IInvocationOperation invoca { return DocumentEditorUtils.RenameMethodToSubjectShouldAssertion(invocation, context, "BeNull", subjectIndex: 0, argumentsToRemove: [1]); } - return DocumentEditorUtils.RenameMethodToSubjectShouldAssertion(invocation, context, "Be", subjectIndex: 1, argumentsToRemove: []); + return DocumentEditorUtils.RenameMethodToSubjectShouldAssertion(invocation, context, "Be", actualSubjectIndex, argumentsToRemove: []); case "AreNotEqual" when ArgumentsAreTypeOf(invocation, t.String, t.String, t.Boolean): // AreNotEqual(string? expected, string? actual, bool ignoreCase) case "AreNotEqual" when ArgumentsAreTypeOf(invocation, t.String, t.String, t.Boolean, t.String): // AreNotEqual(string? expected, string? actual, bool ignoreCase, string? message) case "AreNotEqual" when ArgumentsAreTypeOf(invocation, t.String, t.String, t.Boolean, t.String, t.ObjectArray): // AreNotEqual(string? expected, string? actual, bool ignoreCase, string? message, params object?[]? parameters) { var ignoreCase = invocation.Arguments.Length >= 3 && invocation.Arguments[2].IsLiteralValue(true); var newMethod = ignoreCase ? "NotBeEquivalentTo" : "NotBe"; - return DocumentEditorUtils.RenameMethodToSubjectShouldAssertion(invocation, context, newMethod, subjectIndex: 1, argumentsToRemove: [2]); + return DocumentEditorUtils.RenameMethodToSubjectShouldAssertion(invocation, context, newMethod, actualSubjectIndex, argumentsToRemove: [2]); } case "AreNotEqual" when ArgumentsAreTypeOf(invocation, t.String, t.String, t.Boolean, t.CultureInfo): // AreNotEqual(string? expected, string? actual, bool ignoreCase, CultureInfo? culture) case "AreNotEqual" when ArgumentsAreTypeOf(invocation, t.String, t.String, t.Boolean, t.CultureInfo, t.String): // AreNotEqual(string? expected, string? actual, bool ignoreCase, CultureInfo? culture, string? message) @@ -92,7 +96,7 @@ private CreateChangedDocument TryComputeFixForAssert(IInvocationOperation invoca break; // TODO: Handle other cultures } - return DocumentEditorUtils.RenameMethodToSubjectShouldAssertion(invocation, context, newMethod, subjectIndex: 1, argumentsToRemove: [2, 3]); + return DocumentEditorUtils.RenameMethodToSubjectShouldAssertion(invocation, context, newMethod, actualSubjectIndex, argumentsToRemove: [2, 3]); } case "AreNotEqual" when ArgumentsAreTypeOf(invocation, t.Float, t.Float, t.Float): // AreNotEqual(float expected, float actual, float delta) case "AreNotEqual" when ArgumentsAreTypeOf(invocation, t.Float, t.Float, t.Float, t.String): // AreNotEqual(float expected, float actual, float delta, string? message) @@ -103,7 +107,7 @@ private CreateChangedDocument TryComputeFixForAssert(IInvocationOperation invoca case "AreNotEqual" when ArgumentsAreTypeOf(invocation, t.Decimal, t.Decimal, t.Decimal): // AreNotEqual(decimal expected, decimal actual, decimal delta) case "AreNotEqual" when ArgumentsAreTypeOf(invocation, t.Decimal, t.Decimal, t.Decimal, t.String): // AreNotEqual(decimal expected, decimal actual, decimal delta, string? message) case "AreNotEqual" when ArgumentsAreTypeOf(invocation, t.Decimal, t.Decimal, t.Decimal, t.String, t.ObjectArray): // AreNotEqual(decimal expected, decimal actual, decimal delta, string? message, params object?[]? parameters) - return DocumentEditorUtils.RenameMethodToSubjectShouldAssertion(invocation, context, "NotBeApproximately", subjectIndex: 1, argumentsToRemove: []); + return DocumentEditorUtils.RenameMethodToSubjectShouldAssertion(invocation, context, "NotBeApproximately", actualSubjectIndex, argumentsToRemove: []); case "AreNotEqual" when ArgumentsAreTypeOf(invocation, startFromIndex: 2): // AreNotEqual(T? expected, T? actual) case "AreNotEqual" when ArgumentsAreTypeOf(invocation, startFromIndex: 2, t.String): // AreNotEqual(T? expected, T? actual, string? message) case "AreNotEqual" when ArgumentsAreTypeOf(invocation, startFromIndex: 2, t.String, t.ObjectArray): // AreNotEqual(T? expected, T? actual, string? message, params object?[]? parameters) @@ -115,15 +119,15 @@ private CreateChangedDocument TryComputeFixForAssert(IInvocationOperation invoca { return DocumentEditorUtils.RenameMethodToSubjectShouldAssertion(invocation, context, "NotBeNull", subjectIndex: 0, argumentsToRemove: [1]); } - return DocumentEditorUtils.RenameMethodToSubjectShouldAssertion(invocation, context, "NotBe", subjectIndex: 1, argumentsToRemove: []); + return DocumentEditorUtils.RenameMethodToSubjectShouldAssertion(invocation, context, "NotBe", actualSubjectIndex, argumentsToRemove: []); case "AreSame" when ArgumentsAreTypeOf(invocation, startFromIndex: 2): // AreSame(string? expected, string? actual) case "AreSame" when ArgumentsAreTypeOf(invocation, startFromIndex: 2, t.String): // AreSame(string? expected, string? actual, string? message) case "AreSame" when ArgumentsAreTypeOf(invocation, startFromIndex: 2, t.String, t.ObjectArray): // AreSame(string? expected, string? actual, string? message, params object?[]? parameters) - return DocumentEditorUtils.RenameMethodToSubjectShouldAssertion(invocation, context, "BeSameAs", subjectIndex: 1, argumentsToRemove: []); + return DocumentEditorUtils.RenameMethodToSubjectShouldAssertion(invocation, context, "BeSameAs", actualSubjectIndex, argumentsToRemove: []); case "AreNotSame" when ArgumentsAreTypeOf(invocation, startFromIndex: 2): // AreNotSame(string? expected, string? actual) case "AreNotSame" when ArgumentsAreTypeOf(invocation, startFromIndex: 2, t.String): // AreNotSame(string? expected, string? actual, string? message) case "AreNotSame" when ArgumentsAreTypeOf(invocation, startFromIndex: 2, t.String, t.ObjectArray): // AreNotSame(string? expected, string? actual, string? message, params object?[]? parameters) - return DocumentEditorUtils.RenameMethodToSubjectShouldAssertion(invocation, context, "NotBeSameAs", subjectIndex: 1, argumentsToRemove: []); + return DocumentEditorUtils.RenameMethodToSubjectShouldAssertion(invocation, context, "NotBeSameAs", actualSubjectIndex, argumentsToRemove: []); case "IsTrue" when ArgumentsAreTypeOf(invocation, t.Boolean): // IsTrue(bool condition) case "IsTrue" when ArgumentsAreTypeOf(invocation, t.Boolean, t.String): // IsTrue(bool condition, string? message) case "IsTrue" when ArgumentsAreTypeOf(invocation, t.Boolean, t.String, t.ObjectArray): // IsTrue(bool condition, string? message, params object?[]? parameters) diff --git a/src/FluentAssertions.Analyzers/Utilities/OperartionExtensions.cs b/src/FluentAssertions.Analyzers/Utilities/OperartionExtensions.cs index 5eacc01b..8195ea21 100644 --- a/src/FluentAssertions.Analyzers/Utilities/OperartionExtensions.cs +++ b/src/FluentAssertions.Analyzers/Utilities/OperartionExtensions.cs @@ -112,6 +112,8 @@ public static bool IsStaticPropertyReference(this IArgumentOperation argument, I && propertyReference.Property.Name == property; } + public static bool IsLiteralValue(this IArgumentOperation argument) + => UnwrapConversion(argument.Value).Kind is OperationKind.Literal; public static bool IsLiteralValue(this IArgumentOperation argument, T value) => UnwrapConversion(argument.Value) is ILiteralOperation literal && literal.ConstantValue.HasValue && literal.ConstantValue.Value.Equals(value); public static bool IsLiteralNull(this IArgumentOperation argument)