diff --git a/src/FluentAssertions.Analyzers.TestUtils/GenerateCode.cs b/src/FluentAssertions.Analyzers.TestUtils/GenerateCode.cs index fe4f2bd8..93c91aa9 100644 --- a/src/FluentAssertions.Analyzers.TestUtils/GenerateCode.cs +++ b/src/FluentAssertions.Analyzers.TestUtils/GenerateCode.cs @@ -236,6 +236,7 @@ public static string XunitAssertion(string methodArguments, string assertion) => public static string Nunit3Assertion(string methodArguments, string assertion) => new StringBuilder() .AppendLine("using System;") + .AppendLine("using System.Collections;") .AppendLine("using System.Collections.Generic;") .AppendLine("using System.Collections.Immutable;") .AppendLine("using System.Text.RegularExpressions;") @@ -257,6 +258,7 @@ public static string Nunit3Assertion(string methodArguments, string assertion) = public static string Nunit4Assertion(string methodArguments, string assertion) => new StringBuilder() .AppendLine("using System;") + .AppendLine("using System.Collections;") .AppendLine("using System.Collections.Generic;") .AppendLine("using System.Collections.Immutable;") .AppendLine("using System.Text.RegularExpressions;") diff --git a/src/FluentAssertions.Analyzers.Tests/DiagnosticVerifier.cs b/src/FluentAssertions.Analyzers.Tests/DiagnosticVerifier.cs index 50a84930..e4ce2004 100644 --- a/src/FluentAssertions.Analyzers.Tests/DiagnosticVerifier.cs +++ b/src/FluentAssertions.Analyzers.Tests/DiagnosticVerifier.cs @@ -38,6 +38,24 @@ public static class DiagnosticVerifier public static void VerifyFix(CodeFixVerifierArguments arguments) => VerifyFix(arguments, arguments.DiagnosticAnalyzers.Single(), arguments.CodeFixProviders.Single(), arguments.FixedSources.Single()); + public static void VerifyNoFix(CodeFixVerifierArguments arguments) + => VerifyNoFix(arguments, arguments.DiagnosticAnalyzers.Single(), arguments.CodeFixProviders.Single()); + private static void VerifyNoFix(CsProjectArguments arguments, DiagnosticAnalyzer analyzer, CodeFixProvider codeFixProvider) + { + var document = CsProjectGenerator.CreateDocument(arguments); + var analyzerDiagnostics = GetSortedDiagnosticsFromDocuments(new[] { analyzer }, new[] { document }); + var compilerDiagnostics = GetCompilerDiagnostics(document); + var attempts = analyzerDiagnostics.Length; + + for (int i = 0; i < attempts; ++i) + { + var actions = new List(); + var context = new CodeFixContext(document, analyzerDiagnostics[0], (a, d) => actions.Add(a), CancellationToken.None); + codeFixProvider.RegisterCodeFixesAsync(context).Wait(); + + actions.Should().BeEmpty("There should be no code fix actions available to suppress the diagnostic."); + } + } /// /// General verifier for codefixes. diff --git a/src/FluentAssertions.Analyzers.Tests/Tips/NunitTests.cs b/src/FluentAssertions.Analyzers.Tests/Tips/NunitTests.cs index 88b18c60..61165355 100644 --- a/src/FluentAssertions.Analyzers.Tests/Tips/NunitTests.cs +++ b/src/FluentAssertions.Analyzers.Tests/Tips/NunitTests.cs @@ -667,16 +667,119 @@ public void Nunit4_AssertIsNotInstanceOf_TestCodeFix(string oldAssertion, string #endregion + #region Assert.Contains.cs + + [DataTestMethod] + [AssertionDiagnostic("Assert.Contains(expected, actual{0});")] + [Implemented] + public void Nunit3_AssertContains_ICollection_TestCodeNoFix(string assertion) + => Nunit3VerifyNoFix("object expected, ICollection actual", assertion); + + [DataTestMethod] + [AssertionDiagnostic("ClassicAssert.Contains(expected, actual{0});")] + [Implemented] + public void Nunit4_AssertContains_ICollection_TestCodeNoFix(string assertion) + => Nunit4VerifyNoFix("object expected, ICollection actual", assertion); + + [DataTestMethod] + [AssertionDiagnostic("Assert.Contains(expected, actual{0});")] + [Implemented] + public void Nunit3_AssertContains_TestAnalyzer(string assertion) + => Nunit3VerifyDiagnostic("object expected, string[] actual", assertion); + + [DataTestMethod] + [AssertionDiagnostic("ClassicAssert.Contains(expected, actual{0});")] + [Implemented] + public void Nunit4_AssertContains_TestAnalyzer(string assertion) + => Nunit4VerifyDiagnostic("object expected, string[] actual", assertion); + + [DataTestMethod] + [AssertionCodeFix( + oldAssertion: "Assert.Contains(expected, actual{0});", + newAssertion: "actual.Should().Contain(expected{0});")] + [Implemented] + public void Nunit3_AssertContains_TestCodeFix(string oldAssertion, string newAssertion) + { + Nunit3VerifyFix("string expected, string[] actual", oldAssertion, newAssertion); + Nunit3VerifyFix("string expected, List actual", oldAssertion, newAssertion); + Nunit3VerifyFix("string expected, object[] actual", oldAssertion, newAssertion); + Nunit3VerifyFix("string expected, List actual", oldAssertion, newAssertion); + Nunit3VerifyFix("DateTime expected, DateTime[] actual", oldAssertion, newAssertion); + Nunit3VerifyFix("DateTime expected, List actual", oldAssertion, newAssertion); + } + + [DataTestMethod] + [AssertionCodeFix( + oldAssertion: "ClassicAssert.Contains(expected, actual{0});", + newAssertion: "actual.Should().Contain(expected{0});")] + [Implemented] + public void Nunit4_AssertContains_TestCodeFix(string oldAssertion, string newAssertion) + { + Nunit4VerifyFix("string expected, string[] actual", oldAssertion, newAssertion); + Nunit4VerifyFix("string expected, List actual", oldAssertion, newAssertion); + Nunit4VerifyFix("string expected, object[] actual", oldAssertion, newAssertion); + Nunit4VerifyFix("string expected, List actual", oldAssertion, newAssertion); + Nunit4VerifyFix("DateTime expected, DateTime[] actual", oldAssertion, newAssertion); + Nunit4VerifyFix("DateTime expected, List actual", oldAssertion, newAssertion); + } + + [DataTestMethod] + [DataRow( + /* methodArguments: */ "object expected, string[] actual", + /* oldAssertion: */ "Assert.Contains(expected, actual);", + /* newAssertion: */ "actual.Should().Contain((string)expected);")] + [DataRow( + /* methodArguments: */ "object expected, List actual", + /* oldAssertion: */ "Assert.Contains(expected, actual);", + /* newAssertion: */ "actual.Should().Contain((string)expected);")] + [DataRow( + /* methodArguments: */ "object expected, DateTime[] actual", + /* oldAssertion: */ "Assert.Contains(expected, actual);", + /* newAssertion: */ "actual.Should().Contain((DateTime)expected);")] + [DataRow( + /* methodArguments: */ "object expected, List actual", + /* oldAssertion: */ "Assert.Contains(expected, actual);", + /* newAssertion: */ "actual.Should().Contain((DateTime)expected);")] + [Implemented] + public void Nunit3_AssertContains_WithCasting_TestCodeFix(string methodArguments, string oldAssertion, string newAssertion) + => Nunit3VerifyFix(methodArguments, oldAssertion, newAssertion); + + [DataTestMethod] + [DataRow( + /* methodArguments: */ "object expected, string[] actual", + /* oldAssertion: */ "ClassicAssert.Contains(expected, actual);", + /* newAssertion: */ "actual.Should().Contain((string)expected);")] + [DataRow( + /* methodArguments: */ "object expected, List actual", + /* oldAssertion: */ "ClassicAssert.Contains(expected, actual);", + /* newAssertion: */ "actual.Should().Contain((string)expected);")] + [DataRow( + /* methodArguments: */ "object expected, DateTime[] actual", + /* oldAssertion: */ "ClassicAssert.Contains(expected, actual);", + /* newAssertion: */ "actual.Should().Contain((DateTime)expected);")] + [DataRow( + /* methodArguments: */ "object expected, List actual", + /* oldAssertion: */ "ClassicAssert.Contains(expected, actual);", + /* newAssertion: */ "actual.Should().Contain((DateTime)expected);")] + [Implemented] + public void Nunit4_AssertContains_WithCasting_TestCodeFix(string methodArguments, string oldAssertion, string newAssertion) + => Nunit4VerifyFix(methodArguments, oldAssertion, newAssertion); + + #endregion + private void Nunit3VerifyDiagnostic(string methodArguments, string assertion) => VerifyDiagnostic(GenerateCode.Nunit3Assertion(methodArguments, assertion), PackageReference.Nunit_3_14_0); private void Nunit3VerifyFix(string methodArguments, string oldAssertion, string newAssertion) => VerifyFix(GenerateCode.Nunit3Assertion(methodArguments, oldAssertion), GenerateCode.Nunit3Assertion(methodArguments, newAssertion), PackageReference.Nunit_3_14_0); + private void Nunit3VerifyNoFix(string methodArguments, string assertion) + => VerifyNoFix(GenerateCode.Nunit3Assertion(methodArguments, assertion), PackageReference.Nunit_3_14_0); private void Nunit4VerifyDiagnostic(string methodArguments, string assertion) => VerifyDiagnostic(GenerateCode.Nunit4Assertion(methodArguments, assertion), PackageReference.Nunit_4_0_1); private void Nunit4VerifyFix(string methodArguments, string oldAssertion, string newAssertion) => VerifyFix(GenerateCode.Nunit4Assertion(methodArguments, oldAssertion), GenerateCode.Nunit4Assertion(methodArguments, newAssertion), PackageReference.Nunit_4_0_1); - + private void Nunit4VerifyNoFix(string methodArguments, string assertion) + => VerifyNoFix(GenerateCode.Nunit4Assertion(methodArguments, assertion), PackageReference.Nunit_4_0_1); private void VerifyDiagnostic(string source, PackageReference nunit) { DiagnosticVerifier.VerifyDiagnostic(new DiagnosticVerifierArguments() @@ -689,7 +792,7 @@ private void VerifyDiagnostic(string source, PackageReference nunit) Message = AssertAnalyzer.Message, Locations = new DiagnosticResultLocation[] { - new("Test0.cs", 15, 13) + new("Test0.cs", 16, 13) }, Severity = DiagnosticSeverity.Info }) @@ -706,4 +809,13 @@ private void VerifyFix(string oldSource, string newSource, PackageReference nuni .WithPackageReferences(PackageReference.FluentAssertions_6_12_0, nunit) ); } + private void VerifyNoFix(string source, PackageReference nunit) + { + DiagnosticVerifier.VerifyNoFix(new CodeFixVerifierArguments() + .WithDiagnosticAnalyzer() + .WithCodeFixProvider() + .WithSources(source) + .WithPackageReferences(PackageReference.FluentAssertions_6_12_0, nunit) + ); + } } \ No newline at end of file diff --git a/src/FluentAssertions.Analyzers/Tips/NunitCodeFixProvider.cs b/src/FluentAssertions.Analyzers/Tips/NunitCodeFixProvider.cs index 2b89878a..863dbf8b 100644 --- a/src/FluentAssertions.Analyzers/Tips/NunitCodeFixProvider.cs +++ b/src/FluentAssertions.Analyzers/Tips/NunitCodeFixProvider.cs @@ -5,6 +5,8 @@ using Microsoft.CodeAnalysis.Operations; using CreateChangedDocument = System.Func>; using System; +using FluentAssertions.Analyzers.Utilities; +using Microsoft.CodeAnalysis.Simplification; namespace FluentAssertions.Analyzers; @@ -171,6 +173,30 @@ private CreateChangedDocument TryComputeFixForNunitClassicAssert(IInvocationOper case "IsNotInstanceOf" when ArgumentsAreTypeOf(invocation, t.Object): // Assert.IsNotInstanceOf(object actual) case "IsNotInstanceOf" when ArgumentsAreTypeOf(invocation, t.Object, t.String, t.ObjectArray): // Assert.IsNotInstanceOf(object actual, string message, params object[] parms) return DocumentEditorUtils.RenameGenericMethodToSubjectShouldGenericAssertion(invocation, context, "NotBeOfType", subjectIndex: 0, argumentsToRemove: []); + case "Contains": // Assert.Contains(object expected, ICollection actual) + { + var collectionArgument = invocation.Arguments[1].Value.UnwrapConversion(); + if (collectionArgument.Type.ImplementsOrIsInterface(SpecialType.System_Collections_Generic_IEnumerable_T)) + { + return async ctx => await DocumentEditorUtils.RewriteExpression(invocation, [ + (EditActionContext editActionContext) => + { + ITypeSymbol elementType = collectionArgument.Type switch + { + INamedTypeSymbol namedType => namedType.TypeArguments[0], + IArrayTypeSymbol arrayType => arrayType.ElementType, + _ => null + }; + + var argumentToCast = editActionContext.InvocationExpression.ArgumentList.Arguments[0]; + var castExpression = editActionContext.Editor.Generator.CastExpression(elementType, argumentToCast.Expression); + editActionContext.Editor.ReplaceNode(argumentToCast.Expression, castExpression.WithAdditionalAnnotations(Simplifier.Annotation)); + }, + EditAction.SubjectShouldAssertion(argumentIndex: 1, "Contain") + ], context, ctx); + } + return null; + } } return null; }