From d6f4f3bf50cef8226bbd587e85bbf1c564f2fd08 Mon Sep 17 00:00:00 2001 From: James Terwilliger Date: Tue, 28 Nov 2023 17:57:53 -0800 Subject: [PATCH 01/10] https://github.com/xunit/xunit/issues/1244 --- src/xunit.analyzers/Utility/Descriptors.cs | 9 +++- .../MemberDataShouldReferenceValidMember.cs | 51 +++++++++++-------- 2 files changed, 39 insertions(+), 21 deletions(-) diff --git a/src/xunit.analyzers/Utility/Descriptors.cs b/src/xunit.analyzers/Utility/Descriptors.cs index 471a8816..d59cc40f 100644 --- a/src/xunit.analyzers/Utility/Descriptors.cs +++ b/src/xunit.analyzers/Utility/Descriptors.cs @@ -413,7 +413,14 @@ static DiagnosticDescriptor Rule( "A class for '{0}' collection definition must be declared in the '{1}' assembly" ); - // Placeholder for rule X1042 + public static DiagnosticDescriptor X1042_MemberDataTheoryDataIsRecommendedForStronglyTypedAnalysis { get; } = + Rule( + "xUnit1042", + "The member referenced by the MemberData attribute returns untyped data rows", + Usage, + Info, + "The member referenced by the MemberData attribute returns untyped data rows, such as object[]. Consider using TheoryData<> as the return type to provide better type safety." + ); // Placeholder for rule X1043 diff --git a/src/xunit.analyzers/X1000/MemberDataShouldReferenceValidMember.cs b/src/xunit.analyzers/X1000/MemberDataShouldReferenceValidMember.cs index 116f1bf5..8bc83444 100644 --- a/src/xunit.analyzers/X1000/MemberDataShouldReferenceValidMember.cs +++ b/src/xunit.analyzers/X1000/MemberDataShouldReferenceValidMember.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using System.Threading; @@ -150,7 +151,8 @@ public override void AnalyzeCompilation( } // If the member returns TheoryData, ensure that the types are compatible - VerifyTheoryDataUsage(semanticModel, context, testMethod, theoryDataTypes, memberReturnType, memberName, declaredMemberTypeSymbol, attributeSyntax); + if (IsTheoryDataType(memberReturnType, theoryDataTypes, out var theoryReturnType)) + VerifyTheoryDataUsage(semanticModel, context, testMethod, theoryReturnType, memberName, declaredMemberTypeSymbol, attributeSyntax); // Get the arguments that are to be passed to the method var extraArguments = attributeSyntax.ArgumentList.Arguments.Skip(1).TakeWhile(a => a.NameEquals is null).ToList(); @@ -205,14 +207,7 @@ public static (INamedTypeSymbol? TestClass, ITypeSymbol? MemberClass) GetClassTy return (null, null); var testClassTypeSymbol = semanticModel.GetDeclaredSymbol(classSyntax); - if (testClassTypeSymbol is null) - return (null, null); - - var declaredMemberTypeSymbol = memberTypeSymbol ?? testClassTypeSymbol; - if (declaredMemberTypeSymbol is null) - return (testClassTypeSymbol, null); - - return (testClassTypeSymbol, declaredMemberTypeSymbol); + return (testClassTypeSymbol, memberTypeSymbol ?? testClassTypeSymbol); } static IList? GetParameterExpressionsFromArrayArgument( @@ -621,12 +616,36 @@ static void VerifyDataSourceReturnType( ReportIncorrectReturnType(context, iEnumerableOfObjectArrayType, iEnumerableOfTheoryDataRowType, attributeSyntax, memberProperties, memberType); } + static bool IsTheoryDataType( + ITypeSymbol? memberReturnType, + Dictionary theoryDataTypes, + [NotNullWhen(true)] out INamedTypeSymbol? theoryReturnType) + { + theoryReturnType = default; + if (memberReturnType is not INamedTypeSymbol namedReturnType || !namedReturnType.IsGenericType) + return false; + + INamedTypeSymbol? working = namedReturnType; + while (working is not null) + { + var returnTypeArguments = namedReturnType.TypeArguments; + if (theoryDataTypes.TryGetValue(returnTypeArguments.Length, out var theoryDataType) + && SymbolEqualityComparer.Default.Equals(theoryDataType, namedReturnType.OriginalDefinition)) + break; + } + + if (working is null) + return false; + + theoryReturnType = working; + return true; + } + static void VerifyTheoryDataUsage( SemanticModel semanticModel, SyntaxNodeAnalysisContext context, MethodDeclarationSyntax testMethod, - Dictionary theoryDataTypes, - ITypeSymbol? memberReturnType, + INamedTypeSymbol theoryReturnType, string memberName, ITypeSymbol memberType, AttributeSyntax attributeSyntax) @@ -634,15 +653,7 @@ static void VerifyTheoryDataUsage( if (memberType is not INamedTypeSymbol namedMemberType) return; - if (memberReturnType is not INamedTypeSymbol namedReturnType || !namedReturnType.IsGenericType) - return; - - var returnTypeArguments = namedReturnType.TypeArguments; - if (!theoryDataTypes.TryGetValue(returnTypeArguments.Length, out var theoryDataType)) - return; - if (!SymbolEqualityComparer.Default.Equals(theoryDataType, namedReturnType.OriginalDefinition)) - return; - + var returnTypeArguments = theoryReturnType.TypeArguments; var testMethodSymbol = semanticModel.GetDeclaredSymbol(testMethod, context.CancellationToken); if (testMethodSymbol is null) return; From cd7c2b21f4a9043deee0c5aa1bd11ecfa2826115 Mon Sep 17 00:00:00 2001 From: James Terwilliger Date: Tue, 28 Nov 2023 18:17:47 -0800 Subject: [PATCH 02/10] edit --- .../X1000/MemberDataShouldReferenceValidMember.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/xunit.analyzers/X1000/MemberDataShouldReferenceValidMember.cs b/src/xunit.analyzers/X1000/MemberDataShouldReferenceValidMember.cs index 8bc83444..2abe230d 100644 --- a/src/xunit.analyzers/X1000/MemberDataShouldReferenceValidMember.cs +++ b/src/xunit.analyzers/X1000/MemberDataShouldReferenceValidMember.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using System.Threading; @@ -31,7 +30,8 @@ public MemberDataShouldReferenceValidMember() : Descriptors.X1037_MemberDataTheoryDataTypeArgumentsMustMatchTestMethodParameters_TooFewTypeParameters, Descriptors.X1038_MemberDataTheoryDataTypeArgumentsMustMatchTestMethodParameters_ExtraTypeParameters, Descriptors.X1039_MemberDataTheoryDataTypeArgumentsMustMatchTestMethodParameters_IncompatibleTypes, - Descriptors.X1040_MemberDataTheoryDataTypeArgumentsMustMatchTestMethodParameters_IncompatibleNullability + Descriptors.X1040_MemberDataTheoryDataTypeArgumentsMustMatchTestMethodParameters_IncompatibleNullability, + Descriptors.X1042_MemberDataTheoryDataIsRecommendedForStronglyTypedAnalysis ) { } @@ -152,7 +152,7 @@ public override void AnalyzeCompilation( // If the member returns TheoryData, ensure that the types are compatible if (IsTheoryDataType(memberReturnType, theoryDataTypes, out var theoryReturnType)) - VerifyTheoryDataUsage(semanticModel, context, testMethod, theoryReturnType, memberName, declaredMemberTypeSymbol, attributeSyntax); + VerifyTheoryDataUsage(semanticModel, context, testMethod, theoryReturnType!, memberName, declaredMemberTypeSymbol, attributeSyntax); // Get the arguments that are to be passed to the method var extraArguments = attributeSyntax.ArgumentList.Arguments.Skip(1).TakeWhile(a => a.NameEquals is null).ToList(); @@ -619,7 +619,7 @@ static void VerifyDataSourceReturnType( static bool IsTheoryDataType( ITypeSymbol? memberReturnType, Dictionary theoryDataTypes, - [NotNullWhen(true)] out INamedTypeSymbol? theoryReturnType) + out INamedTypeSymbol? theoryReturnType) { theoryReturnType = default; if (memberReturnType is not INamedTypeSymbol namedReturnType || !namedReturnType.IsGenericType) From 24925627c814f13a5bb3e250db543af1201d8009 Mon Sep 17 00:00:00 2001 From: James Terwilliger Date: Tue, 28 Nov 2023 18:31:20 -0800 Subject: [PATCH 03/10] Helps if I set the loop iterator --- .../X1000/MemberDataShouldReferenceValidMember.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/xunit.analyzers/X1000/MemberDataShouldReferenceValidMember.cs b/src/xunit.analyzers/X1000/MemberDataShouldReferenceValidMember.cs index 2abe230d..dc52b02a 100644 --- a/src/xunit.analyzers/X1000/MemberDataShouldReferenceValidMember.cs +++ b/src/xunit.analyzers/X1000/MemberDataShouldReferenceValidMember.cs @@ -632,6 +632,7 @@ static bool IsTheoryDataType( if (theoryDataTypes.TryGetValue(returnTypeArguments.Length, out var theoryDataType) && SymbolEqualityComparer.Default.Equals(theoryDataType, namedReturnType.OriginalDefinition)) break; + working = working.BaseType; } if (working is null) From d419e85e2b8d41f0223b59cd0b3b809c3aa38bf9 Mon Sep 17 00:00:00 2001 From: James Terwilliger Date: Tue, 28 Nov 2023 20:48:09 -0800 Subject: [PATCH 04/10] Test fixes --- ...mberDataShouldReferenceValidMemberTests.cs | 297 +++++++++++++++--- ...ferenceValidMember_ExtraValueFixerTests.cs | 12 +- ...ldReferenceValidMember_NameOfFixerTests.cs | 4 +- ...eUsedForIncompatibleParameterFixerTests.cs | 8 +- ...alidMember_ParamsForNonMethodFixerTests.cs | 4 +- ...ferenceValidMember_ReturnTypeFixerTests.cs | 4 +- ...ldReferenceValidMember_StaticFixerTests.cs | 4 +- ...ferenceValidMember_VisibilityFixerTests.cs | 4 +- .../MemberDataShouldReferenceValidMember.cs | 20 +- 9 files changed, 295 insertions(+), 62 deletions(-) diff --git a/src/xunit.analyzers.tests/Analyzers/X1000/MemberDataShouldReferenceValidMemberTests.cs b/src/xunit.analyzers.tests/Analyzers/X1000/MemberDataShouldReferenceValidMemberTests.cs index b6bf6665..e4b58ff8 100644 --- a/src/xunit.analyzers.tests/Analyzers/X1000/MemberDataShouldReferenceValidMemberTests.cs +++ b/src/xunit.analyzers.tests/Analyzers/X1000/MemberDataShouldReferenceValidMemberTests.cs @@ -1,3 +1,4 @@ +using System; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Testing; @@ -26,7 +27,15 @@ public partial class TestClass { public void TestMethod() { } }"; - await Verify.VerifyAnalyzer(new[] { source, sharedCode }); + DiagnosticResult[] expected = + { + Verify + .Diagnostic("xUnit1042") + .WithSpan(3, 6, 3, 36) + .WithSeverity(DiagnosticSeverity.Info) + }; + + await Verify.VerifyAnalyzer(new[] { source, sharedCode }, expected); } [Fact] @@ -38,7 +47,15 @@ public partial class TestClass { public void TestMethod() { } }"; - await Verify.VerifyAnalyzer(new[] { source, sharedCode }); + DiagnosticResult[] expected = + { + Verify + .Diagnostic("xUnit1042") + .WithSpan(3, 6, 3, 85) + .WithSeverity(DiagnosticSeverity.Info) + }; + + await Verify.VerifyAnalyzer(new[] { source, sharedCode }, expected); } [Fact] @@ -49,11 +66,17 @@ public partial class TestClass { [Xunit.MemberData(""Data"")] public void TestMethod() { } }"; - var expected = + DiagnosticResult[] expected = + { + Verify + .Diagnostic("xUnit1042") + .WithSpan(3, 6, 3, 30) + .WithSeverity(DiagnosticSeverity.Info), Verify .Diagnostic("xUnit1014") .WithSpan(3, 23, 3, 29) - .WithArguments("Data", "TestClass"); + .WithArguments("Data", "TestClass") + }; await Verify.VerifyAnalyzer(new[] { source, sharedCode }, expected); } @@ -66,11 +89,17 @@ public partial class TestClass { [Xunit.MemberData(""OtherData"", MemberType = typeof(OtherClass))] public void TestMethod() { } }"; - var expected = + DiagnosticResult[] expected = + { + Verify + .Diagnostic("xUnit1042") + .WithSpan(3, 6, 3, 68) + .WithSeverity(DiagnosticSeverity.Info), Verify .Diagnostic("xUnit1014") .WithSpan(3, 23, 3, 34) - .WithArguments("OtherData", "OtherClass"); + .WithArguments("OtherData", "OtherClass") + }; await Verify.VerifyAnalyzer(new[] { source, sharedCode }, expected); } @@ -164,11 +193,18 @@ public class TestClass {{ [Xunit.MemberData(nameof(Data))] public void TestMethod() {{ }} }}"; - var expected = + + DiagnosticResult[] expected = + { Verify .Diagnostic("xUnit1016") .WithSpan(5, 6, 5, 36) - .WithSeverity(DiagnosticSeverity.Error); + .WithSeverity(DiagnosticSeverity.Error), + Verify + .Diagnostic("xUnit1042") + .WithSpan(5, 6, 5, 36) + .WithSeverity(DiagnosticSeverity.Info) + }; await Verify.VerifyAnalyzer(source, expected); } @@ -184,7 +220,15 @@ public class TestClass { public void TestMethod() { } }"; - await Verify.VerifyAnalyzer(source); + DiagnosticResult[] expected = + { + Verify + .Diagnostic("xUnit1042") + .WithSpan(5, 6, 5, 36) + .WithSeverity(DiagnosticSeverity.Info) + }; + + await Verify.VerifyAnalyzer(source, expected); } [Theory] @@ -211,11 +255,17 @@ public class TestClass {{ public void TestMethod() {{ }} }}"; var source2 = @"public static class OtherClass { public const string Data = ""Data""; }"; - var expected = + DiagnosticResult[] expected = + { Verify .Diagnostic("xUnit1016") .WithSpan(8, 6, 8, 24 + dataNameExpressionLength) - .WithSeverity(DiagnosticSeverity.Error); + .WithSeverity(DiagnosticSeverity.Error), + Verify + .Diagnostic("xUnit1042") + .WithSpan(8, 6, 8, 24 + dataNameExpressionLength) + .WithSeverity(DiagnosticSeverity.Info) + }; await Verify.VerifyAnalyzer(new[] { source1, source2 }, expected); } @@ -230,11 +280,17 @@ public class TestClass { [Xunit.MemberData(nameof(Data))] public void TestMethod() { } }"; - var expected = + DiagnosticResult[] expected = + { Verify .Diagnostic("xUnit1017") .WithSpan(5, 6, 5, 36) - .WithSeverity(DiagnosticSeverity.Error); + .WithSeverity(DiagnosticSeverity.Error), + Verify + .Diagnostic("xUnit1042") + .WithSpan(5, 6, 5, 36) + .WithSeverity(DiagnosticSeverity.Info) + }; await Verify.VerifyAnalyzer(source, expected); } @@ -250,7 +306,15 @@ public class TestClass { public void TestMethod() { } }"; - await Verify.VerifyAnalyzer(source); + DiagnosticResult[] expected = + { + Verify + .Diagnostic("xUnit1042") + .WithSpan(5, 6, 5, 36) + .WithSeverity(DiagnosticSeverity.Info) + }; + + await Verify.VerifyAnalyzer(source, expected); } [Theory] @@ -289,7 +353,15 @@ public class TestClass {{ public void TestMethod() {{ }} }}"; - await Verify.VerifyAnalyzer(source); + DiagnosticResult[] expected = + { + Verify + .Diagnostic("xUnit1042") + .WithSpan(5, 6, 5, 36) + .WithSeverity(DiagnosticSeverity.Info) + }; + + await Verify.VerifyAnalyzer(source, expected); } [Theory] @@ -340,7 +412,17 @@ public class TestClass {{ public void TestMethod(int _) {{ }} }}"; - await Verify.VerifyAnalyzer(source); + DiagnosticResult[] expected = !memberType.Contains("TheoryData") + ? + new DiagnosticResult[] { + Verify + .Diagnostic("xUnit1042") + .WithSpan(5, 6, 5, 36) + .WithSeverity(DiagnosticSeverity.Info) + } + : Array.Empty(); + + await Verify.VerifyAnalyzer(source, expected); } [Fact] @@ -365,8 +447,15 @@ public static List DataRowSource() => new TheoryDataRow(0, null) { Skip = ""Don't run this!"" }, }; }"; + DiagnosticResult[] expected = + { + Verify + .Diagnostic("xUnit1042") + .WithSpan(9, 6, 9, 39) + .WithSeverity(DiagnosticSeverity.Info) + }; - await Verify.VerifyAnalyzerV3(source); + await Verify.VerifyAnalyzerV3(source, expected); } [Fact] @@ -379,11 +468,17 @@ public static System.Collections.Generic.IEnumerable Data { set { } } [Xunit.MemberData(nameof(Data))] public void TestMethod() { } }"; - var expected = + DiagnosticResult[] expected = + { Verify .Diagnostic("xUnit1020") .WithSpan(5, 6, 5, 36) - .WithSeverity(DiagnosticSeverity.Error); + .WithSeverity(DiagnosticSeverity.Error), + Verify + .Diagnostic("xUnit1042") + .WithSpan(5, 6, 5, 36) + .WithSeverity(DiagnosticSeverity.Info) + }; await Verify.VerifyAnalyzer(source, expected); } @@ -401,11 +496,17 @@ public class TestClass {{ [Xunit.MemberData(nameof(Data))] public void TestMethod() {{ }} }}"; - var expected = + DiagnosticResult[] expected = + { Verify .Diagnostic("xUnit1020") .WithSpan(5, 6, 5, 36) - .WithSeverity(DiagnosticSeverity.Error); + .WithSeverity(DiagnosticSeverity.Error), + Verify + .Diagnostic("xUnit1042") + .WithSpan(5, 6, 5, 36) + .WithSeverity(DiagnosticSeverity.Info) + }; await Verify.VerifyAnalyzer(source, expected); } @@ -426,21 +527,33 @@ public void TestMethod() {{ }} var argV2 = string.Format(paramsArgument, "parameters"); var sourceV2 = string.Format(sourceTemplate, argV2); - var expectedV2 = + DiagnosticResult[] expectedV2 = + { + Verify + .Diagnostic("xUnit1042") + .WithSpan(5, 6, 5, 70 + argV2.Length) + .WithSeverity(DiagnosticSeverity.Info), Verify .Diagnostic("xUnit1021") .WithSpan(5, 37, 5, 37 + argV2.Length) - .WithSeverity(DiagnosticSeverity.Warning); + .WithSeverity(DiagnosticSeverity.Warning) + }; await Verify.VerifyAnalyzerV2(sourceV2, expectedV2); var argV3 = string.Format(paramsArgument, "arguments"); var sourceV3 = string.Format(sourceTemplate, argV3); - var expectedV3 = + DiagnosticResult[] expectedV3 = + { + Verify + .Diagnostic("xUnit1042") + .WithSpan(5, 6, 5, 70 + argV3.Length) + .WithSeverity(DiagnosticSeverity.Info), Verify .Diagnostic("xUnit1021") .WithSpan(5, 37, 5, 37 + argV3.Length) - .WithSeverity(DiagnosticSeverity.Warning); + .WithSeverity(DiagnosticSeverity.Warning) + }; await Verify.VerifyAnalyzerV3(sourceV3, expectedV3); } @@ -461,21 +574,33 @@ public void TestMethod() {{ }} var argV2 = string.Format(paramsArgument, "parameters"); var sourceV2 = string.Format(sourceTemplate, argV2); - var expectedV2 = + DiagnosticResult[] expectedV2 = + { + Verify + .Diagnostic("xUnit1042") + .WithSpan(5, 6, 5, 70 + argV2.Length) + .WithSeverity(DiagnosticSeverity.Info), Verify .Diagnostic("xUnit1021") .WithSpan(5, 37, 5, 37 + argV2.Length) - .WithSeverity(DiagnosticSeverity.Warning); + .WithSeverity(DiagnosticSeverity.Warning) + }; await Verify.VerifyAnalyzerV2(sourceV2, expectedV2); var argV3 = string.Format(paramsArgument, "arguments"); var sourceV3 = string.Format(sourceTemplate, argV3); - var expectedV3 = + DiagnosticResult[] expectedV3 = + { + Verify + .Diagnostic("xUnit1042") + .WithSpan(5, 6, 5, 70 + argV3.Length) + .WithSeverity(DiagnosticSeverity.Info), Verify .Diagnostic("xUnit1021") .WithSpan(5, 37, 5, 37 + argV3.Length) - .WithSeverity(DiagnosticSeverity.Warning); + .WithSeverity(DiagnosticSeverity.Warning) + }; await Verify.VerifyAnalyzerV3(sourceV3, expectedV3); } @@ -491,7 +616,15 @@ public class TestClass { public void TestMethod() { } }"; - await Verify.VerifyAnalyzer(source); + DiagnosticResult[] expected = + { + Verify + .Diagnostic("xUnit1042") + .WithSpan(5, 6, 5, 68) + .WithSeverity(DiagnosticSeverity.Info) + }; + + await Verify.VerifyAnalyzer(source, expected); } [Fact] @@ -507,7 +640,15 @@ private static void TestData() { } public void TestMethod(int n) { } }"; - await Verify.VerifyAnalyzer(source); + DiagnosticResult[] expected = + { + Verify + .Diagnostic("xUnit1042") + .WithSpan(7, 6, 7, 60) + .WithSeverity(DiagnosticSeverity.Info) + }; + + await Verify.VerifyAnalyzer(source, expected); } [Fact] @@ -521,7 +662,15 @@ public class TestClass { public void TestMethod(int n) { } }"; - await Verify.VerifyAnalyzer(source); + DiagnosticResult[] expected = + { + Verify + .Diagnostic("xUnit1042") + .WithSpan(5, 6, 5, 63) + .WithSeverity(DiagnosticSeverity.Info) + }; + + await Verify.VerifyAnalyzer(source, expected); } // https://github.com/xunit/xunit/issues/2817 @@ -545,7 +694,15 @@ public enum Foo {{ Bar }} public static IEnumerable SomeData(Foo foo) => Array.Empty(); }}"; - await Verify.VerifyAnalyzer(source); + DiagnosticResult[] expected = + { + Verify + .Diagnostic("xUnit1042") + .WithSpan(8, 6, 8, 43) + .WithSeverity(DiagnosticSeverity.Info) + }; + + await Verify.VerifyAnalyzer(source, expected); } [Fact] @@ -562,6 +719,10 @@ public void TestMethod(int n) { } DiagnosticResult[] expected = { + Verify + .Diagnostic("xUnit1042") + .WithSpan(5, 6, 5, 60) + .WithSeverity(DiagnosticSeverity.Info), Verify .Diagnostic("xUnit1035") .WithSpan(5, 56, 5, 57) @@ -585,6 +746,10 @@ public void TestMethod(int n) { } DiagnosticResult[] expected = { + Verify + .Diagnostic("xUnit1042") + .WithSpan(5, 6, 5, 67) + .WithSeverity(DiagnosticSeverity.Info), Verify .Diagnostic("xUnit1035") .WithSpan(5, 59, 5, 64) @@ -608,11 +773,15 @@ public void TestMethod(int n) { } DiagnosticResult[] expected = { + Verify + .Diagnostic("xUnit1042") + .WithSpan(5, 6, 5, 63) + .WithSeverity(DiagnosticSeverity.Info), Verify .Diagnostic("xUnit1036") - .WithSpan(5, 59, 5, 60) - .WithArguments("2") - }; + .WithSpan(5, 59, 5, 60) + .WithArguments("2") + }; await Verify.VerifyAnalyzer(source, expected); } @@ -631,6 +800,10 @@ public void TestMethod(int n) { } DiagnosticResult[] expected = { + Verify + .Diagnostic("xUnit1042") + .WithSpan(5, 6, 5, 46) + .WithSeverity(DiagnosticSeverity.Info), Verify .Diagnostic("xUnit1036") .WithSpan(5, 44, 5, 45) @@ -653,7 +826,15 @@ private static void TestData() { } public void TestMethod(int n) { } }"; - await Verify.VerifyAnalyzer(source); + DiagnosticResult[] expected = + { + Verify + .Diagnostic("xUnit1042") + .WithSpan(7, 6, 7, 60) + .WithSeverity(DiagnosticSeverity.Info) + }; + + await Verify.VerifyAnalyzer(source, expected); } [Fact] @@ -670,7 +851,15 @@ public void TestMethod(int n) { } #nullable restore "; - await Verify.VerifyAnalyzer(LanguageVersion.CSharp8, source); + DiagnosticResult[] expected = + { + Verify + .Diagnostic("xUnit1042") + .WithSpan(6, 6, 6, 60) + .WithSeverity(DiagnosticSeverity.Info) + }; + + await Verify.VerifyAnalyzer(LanguageVersion.CSharp8, source, expected); } [Fact] @@ -688,6 +877,10 @@ public void TestMethod(string n) {{ }} DiagnosticResult[] expected = { + Verify + .Diagnostic("xUnit1042") + .WithSpan(6, 6, 6, 69) + .WithSeverity(DiagnosticSeverity.Info), Verify .Diagnostic("xUnit1034") .WithSpan(6, 56, 6, 60) @@ -720,7 +913,15 @@ private static void TestData() { } public void TestMethod(int n) { } }"; - await Verify.VerifyAnalyzer(source); + DiagnosticResult[] expected = + { + Verify + .Diagnostic("xUnit1042") + .WithSpan(11, 6, 11, 60) + .WithSeverity(DiagnosticSeverity.Info) + }; + + await Verify.VerifyAnalyzer(source, expected); } // Tests related to TheoryData<> usage @@ -859,7 +1060,19 @@ public void Test(IEnumerable seq) }} "; - await Verify.VerifyAnalyzer(LanguageVersion.CSharp10, source); + DiagnosticResult[] expected = + { + Verify + .Diagnostic("xUnit1042") + .WithSpan(11, 4, 11, 56) + .WithSeverity(DiagnosticSeverity.Info), + Verify + .Diagnostic("xUnit1042") + .WithSpan(12, 4, 12, 55) + .WithSeverity(DiagnosticSeverity.Info) + }; + + await Verify.VerifyAnalyzer(LanguageVersion.CSharp10, source, expected); } [Fact] @@ -885,6 +1098,10 @@ public void Test(IEnumerable seq) DiagnosticResult[] expected = { + Verify + .Diagnostic("xUnit1042") + .WithSpan(11, 4, 11, 59) + .WithSeverity(DiagnosticSeverity.Info), Verify .Diagnostic("xUnit1035") .WithSpan(11, 52, 11, 53) diff --git a/src/xunit.analyzers.tests/Fixes/X1000/MemberDataShouldReferenceValidMember_ExtraValueFixerTests.cs b/src/xunit.analyzers.tests/Fixes/X1000/MemberDataShouldReferenceValidMember_ExtraValueFixerTests.cs index 8c2434f6..7f905f58 100644 --- a/src/xunit.analyzers.tests/Fixes/X1000/MemberDataShouldReferenceValidMember_ExtraValueFixerTests.cs +++ b/src/xunit.analyzers.tests/Fixes/X1000/MemberDataShouldReferenceValidMember_ExtraValueFixerTests.cs @@ -11,7 +11,7 @@ public async void RemovesUnusedData() using Xunit; public class TestClass { - public static System.Collections.Generic.IEnumerable TestData(int n) { yield return new object[] { n }; } + public static TheoryData TestData(int n) => new TheoryData(); [Theory] [MemberData(nameof(TestData), 42, {|xUnit1036:21.12|})] @@ -22,7 +22,7 @@ public void TestMethod(int a) { } using Xunit; public class TestClass { - public static System.Collections.Generic.IEnumerable TestData(int n) { yield return new object[] { n }; } + public static TheoryData TestData(int n) => new TheoryData(); [Theory] [MemberData(nameof(TestData), 42)] @@ -43,7 +43,7 @@ public async void AddsParameterWithCorrectType( using Xunit; public class TestClass {{ - public static System.Collections.Generic.IEnumerable TestData(int n) {{ yield return new object[] {{ n }}; }} + public static TheoryData TestData(int n) => new TheoryData(); [Theory] [MemberData(nameof(TestData), 42, {{|xUnit1036:{value}|}})] @@ -54,7 +54,7 @@ public void TestMethod(int a) {{ }} using Xunit; public class TestClass {{ - public static System.Collections.Generic.IEnumerable TestData(int n, {valueType} p) {{ yield return new object[] {{ n }}; }} + public static TheoryData TestData(int n, {valueType} p) => new TheoryData(); [Theory] [MemberData(nameof(TestData), 42, {value})] @@ -71,7 +71,7 @@ public async void AddsParameterWithNonConflictingName() using Xunit; public class TestClass {{ - public static System.Collections.Generic.IEnumerable TestData(int p) {{ yield return new object[] {{ p }}; }} + public static TheoryData TestData(int p) => new TheoryData(); [Theory] [MemberData(nameof(TestData), 42, {{|xUnit1036:21.12|}})] @@ -82,7 +82,7 @@ public void TestMethod(int n) {{ }} using Xunit; public class TestClass {{ - public static System.Collections.Generic.IEnumerable TestData(int p, double p_2) {{ yield return new object[] {{ p }}; }} + public static TheoryData TestData(int p, double p_2) => new TheoryData(); [Theory] [MemberData(nameof(TestData), 42, 21.12)] diff --git a/src/xunit.analyzers.tests/Fixes/X1000/MemberDataShouldReferenceValidMember_NameOfFixerTests.cs b/src/xunit.analyzers.tests/Fixes/X1000/MemberDataShouldReferenceValidMember_NameOfFixerTests.cs index 6b003873..21afe329 100644 --- a/src/xunit.analyzers.tests/Fixes/X1000/MemberDataShouldReferenceValidMember_NameOfFixerTests.cs +++ b/src/xunit.analyzers.tests/Fixes/X1000/MemberDataShouldReferenceValidMember_NameOfFixerTests.cs @@ -16,7 +16,7 @@ public class TestClass { public static IEnumerable DataSource = Array.Empty(); [Theory] - [MemberData({|xUnit1014:""DataSource""|})] + [{|xUnit1042:MemberData({|xUnit1014:""DataSource""|})|}] public void TestMethod(int a) { } }"; @@ -29,7 +29,7 @@ public class TestClass { public static IEnumerable DataSource = Array.Empty(); [Theory] - [MemberData(nameof(DataSource))] + [{|xUnit1042:MemberData(nameof(DataSource))|}] public void TestMethod(int a) { } }"; diff --git a/src/xunit.analyzers.tests/Fixes/X1000/MemberDataShouldReferenceValidMember_NullShouldNotBeUsedForIncompatibleParameterFixerTests.cs b/src/xunit.analyzers.tests/Fixes/X1000/MemberDataShouldReferenceValidMember_NullShouldNotBeUsedForIncompatibleParameterFixerTests.cs index 4cc28162..1e766fa4 100644 --- a/src/xunit.analyzers.tests/Fixes/X1000/MemberDataShouldReferenceValidMember_NullShouldNotBeUsedForIncompatibleParameterFixerTests.cs +++ b/src/xunit.analyzers.tests/Fixes/X1000/MemberDataShouldReferenceValidMember_NullShouldNotBeUsedForIncompatibleParameterFixerTests.cs @@ -12,7 +12,7 @@ public async void MakesParameterNullable() using Xunit; public class TestClass { - public static System.Collections.Generic.IEnumerable TestData(int n, int k) { yield return new object[] { n }; } + public static TheoryData TestData(int n, int k) => new TheoryData(); [Theory] [MemberData(nameof(TestData), 42, {|xUnit1034:null|})] @@ -23,7 +23,7 @@ public void TestMethod(int a) { } using Xunit; public class TestClass { - public static System.Collections.Generic.IEnumerable TestData(int n, int? k) { yield return new object[] { n }; } + public static TheoryData TestData(int n, int? k) => new TheoryData(); [Theory] [MemberData(nameof(TestData), 42, null)] @@ -44,7 +44,7 @@ public class TestClass { public static System.Collections.Generic.IEnumerable TestData(int n, string k) { yield return new object[] { n }; } [Theory] - [MemberData(nameof(TestData), 42, {|xUnit1034:null|})] + [{|xUnit1042:MemberData(nameof(TestData), 42, {|xUnit1034:null|})|}] public void TestMethod(int a) { } #nullable restore }"; @@ -57,7 +57,7 @@ public class TestClass { public static System.Collections.Generic.IEnumerable TestData(int n, string? k) { yield return new object[] { n }; } [Theory] - [MemberData(nameof(TestData), 42, null)] + [{|xUnit1042:MemberData(nameof(TestData), 42, null)|}] public void TestMethod(int a) { } #nullable restore }"; diff --git a/src/xunit.analyzers.tests/Fixes/X1000/MemberDataShouldReferenceValidMember_ParamsForNonMethodFixerTests.cs b/src/xunit.analyzers.tests/Fixes/X1000/MemberDataShouldReferenceValidMember_ParamsForNonMethodFixerTests.cs index 8867a66a..1244fd4a 100644 --- a/src/xunit.analyzers.tests/Fixes/X1000/MemberDataShouldReferenceValidMember_ParamsForNonMethodFixerTests.cs +++ b/src/xunit.analyzers.tests/Fixes/X1000/MemberDataShouldReferenceValidMember_ParamsForNonMethodFixerTests.cs @@ -14,7 +14,7 @@ public async void RemovesParametersFromNonMethodMemberData() public class TestClass { - public static IEnumerable DataSource = Array.Empty(); + public static TheoryData DataSource = new TheoryData(); [Theory] [MemberData(nameof(DataSource), {|xUnit1021:""abc"", 123|})] @@ -28,7 +28,7 @@ public void TestMethod(int a) { } public class TestClass { - public static IEnumerable DataSource = Array.Empty(); + public static TheoryData DataSource = new TheoryData(); [Theory] [MemberData(nameof(DataSource))] diff --git a/src/xunit.analyzers.tests/Fixes/X1000/MemberDataShouldReferenceValidMember_ReturnTypeFixerTests.cs b/src/xunit.analyzers.tests/Fixes/X1000/MemberDataShouldReferenceValidMember_ReturnTypeFixerTests.cs index 7ed39f16..a67d7f09 100644 --- a/src/xunit.analyzers.tests/Fixes/X1000/MemberDataShouldReferenceValidMember_ReturnTypeFixerTests.cs +++ b/src/xunit.analyzers.tests/Fixes/X1000/MemberDataShouldReferenceValidMember_ReturnTypeFixerTests.cs @@ -27,7 +27,7 @@ public class TestClass { public static IEnumerable Data => null; [Theory] - [MemberData(nameof(Data))] + [{|xUnit1042:MemberData(nameof(Data))|}] public void TestMethod(int a) { } }"; @@ -57,7 +57,7 @@ public class TestClass { public static IEnumerable Data => null; [Theory] - [MemberData(nameof(Data))] + [{|xUnit1042:MemberData(nameof(Data))|}] public void TestMethod(int a) { } }"; diff --git a/src/xunit.analyzers.tests/Fixes/X1000/MemberDataShouldReferenceValidMember_StaticFixerTests.cs b/src/xunit.analyzers.tests/Fixes/X1000/MemberDataShouldReferenceValidMember_StaticFixerTests.cs index 76d2821f..9605c37f 100644 --- a/src/xunit.analyzers.tests/Fixes/X1000/MemberDataShouldReferenceValidMember_StaticFixerTests.cs +++ b/src/xunit.analyzers.tests/Fixes/X1000/MemberDataShouldReferenceValidMember_StaticFixerTests.cs @@ -12,7 +12,7 @@ public async void MarksDataMemberAsStatic() using Xunit; public class TestClass { - public IEnumerable TestData => null; + public TheoryData TestData => null; [Theory] [{|xUnit1017:MemberData(nameof(TestData))|}] @@ -24,7 +24,7 @@ public void TestMethod(int x) { } using Xunit; public class TestClass { - public static IEnumerable TestData => null; + public static TheoryData TestData => null; [Theory] [MemberData(nameof(TestData))] diff --git a/src/xunit.analyzers.tests/Fixes/X1000/MemberDataShouldReferenceValidMember_VisibilityFixerTests.cs b/src/xunit.analyzers.tests/Fixes/X1000/MemberDataShouldReferenceValidMember_VisibilityFixerTests.cs index 1ca39d35..094a912c 100644 --- a/src/xunit.analyzers.tests/Fixes/X1000/MemberDataShouldReferenceValidMember_VisibilityFixerTests.cs +++ b/src/xunit.analyzers.tests/Fixes/X1000/MemberDataShouldReferenceValidMember_VisibilityFixerTests.cs @@ -15,7 +15,7 @@ public async void SetsPublicModifier(string badModifier) using Xunit; public class TestClass {{ - {badModifier}static IEnumerable TestData => null; + {badModifier}static TheoryData TestData => null; [Theory] [{{|xUnit1016:MemberData(nameof(TestData))|}}] @@ -27,7 +27,7 @@ public void TestMethod(int x) {{ }} using Xunit; public class TestClass { - public static IEnumerable TestData => null; + public static TheoryData TestData => null; [Theory] [MemberData(nameof(TestData))] diff --git a/src/xunit.analyzers/X1000/MemberDataShouldReferenceValidMember.cs b/src/xunit.analyzers/X1000/MemberDataShouldReferenceValidMember.cs index dc52b02a..537bfda3 100644 --- a/src/xunit.analyzers/X1000/MemberDataShouldReferenceValidMember.cs +++ b/src/xunit.analyzers/X1000/MemberDataShouldReferenceValidMember.cs @@ -139,7 +139,8 @@ public override void AnalyzeCompilation( ReportNonStatic(context, attributeSyntax, memberProperties); // Make sure the member returns a compatible type - VerifyDataSourceReturnType(context, compilation, xunitContext, memberReturnType, memberProperties, attributeSyntax); + bool IsValidMemberReturnType = + VerifyDataSourceReturnType(context, compilation, xunitContext, memberReturnType, memberProperties, attributeSyntax); // Make sure public properties have a public getter if (memberSymbol.Kind == SymbolKind.Property && memberSymbol.DeclaredAccessibility == Accessibility.Public) @@ -151,8 +152,11 @@ public override void AnalyzeCompilation( } // If the member returns TheoryData, ensure that the types are compatible + // If the member does not return TheoryData, gently suggest to the user that TheoryData is better for type safety if (IsTheoryDataType(memberReturnType, theoryDataTypes, out var theoryReturnType)) VerifyTheoryDataUsage(semanticModel, context, testMethod, theoryReturnType!, memberName, declaredMemberTypeSymbol, attributeSyntax); + else if (IsValidMemberReturnType) + ReportMemberReturnsTypeUnsafeValue(context, attributeSyntax); // Get the arguments that are to be passed to the method var extraArguments = attributeSyntax.ArgumentList.Arguments.Skip(1).TakeWhile(a => a.NameEquals is null).ToList(); @@ -438,6 +442,16 @@ static void ReportNonPublicPropertyGetter( ) ); + static void ReportMemberReturnsTypeUnsafeValue( + SyntaxNodeAnalysisContext context, + AttributeSyntax attribute) => + context.ReportDiagnostic( + Diagnostic.Create( + Descriptors.X1042_MemberDataTheoryDataIsRecommendedForStronglyTypedAnalysis, + attribute.GetLocation() + ) + ); + static void ReportNonStatic( SyntaxNodeAnalysisContext context, AttributeSyntax attribute, @@ -597,7 +611,7 @@ static void VerifyDataMethodParameterUsage( } } - static void VerifyDataSourceReturnType( + static bool VerifyDataSourceReturnType( SyntaxNodeAnalysisContext context, Compilation compilation, XunitContext xunitContext, @@ -614,6 +628,8 @@ static void VerifyDataSourceReturnType( if (!valid) ReportIncorrectReturnType(context, iEnumerableOfObjectArrayType, iEnumerableOfTheoryDataRowType, attributeSyntax, memberProperties, memberType); + + return valid; } static bool IsTheoryDataType( From 84570b5d32daff34640017a0cf28bb07857b1937 Mon Sep 17 00:00:00 2001 From: James Terwilliger Date: Tue, 28 Nov 2023 21:13:51 -0800 Subject: [PATCH 05/10] Subclass tests --- ...mberDataShouldReferenceValidMemberTests.cs | 145 ++++++++++++++++++ .../MemberDataShouldReferenceValidMember.cs | 6 +- 2 files changed, 148 insertions(+), 3 deletions(-) diff --git a/src/xunit.analyzers.tests/Analyzers/X1000/MemberDataShouldReferenceValidMemberTests.cs b/src/xunit.analyzers.tests/Analyzers/X1000/MemberDataShouldReferenceValidMemberTests.cs index e4b58ff8..d2aa01b7 100644 --- a/src/xunit.analyzers.tests/Analyzers/X1000/MemberDataShouldReferenceValidMemberTests.cs +++ b/src/xunit.analyzers.tests/Analyzers/X1000/MemberDataShouldReferenceValidMemberTests.cs @@ -1038,6 +1038,69 @@ public void TestMethod(T? n) {{ }} await Verify.VerifyAnalyzer(LanguageVersion.CSharp10, source); } + [Theory] + [MemberData(nameof(MemberSyntaxAndArgs))] + public async void DoesNotFindWarning_IfHasValidTheoryDataSubclassMember( + string memberSyntax, + string memberArgs) + { + var source = $@" +using Xunit; + +public class DerivedTheoryData : TheoryData {{ }} + +public class TestClass {{ + public static DerivedTheoryData TestData{memberSyntax}new(); + + [MemberData(nameof(TestData){memberArgs})] + public void TestMethod(int n) {{ }} +}}"; + + await Verify.VerifyAnalyzer(LanguageVersion.CSharp10, source); + } + + [Theory] + [MemberData(nameof(MemberSyntaxAndArgs))] + public async void DoesNotFindWarning_IfHasValidTheoryDataGenericSubclassMember( + string memberSyntax, + string memberArgs) + { + var source = $@" +using Xunit; + +public class DerivedTheoryData : TheoryData {{ }} + +public class TestClass {{ + public static DerivedTheoryData TestData{memberSyntax}new(); + + [MemberData(nameof(TestData){memberArgs})] + public void TestMethod(int n) {{ }} +}}"; + + await Verify.VerifyAnalyzer(LanguageVersion.CSharp10, source); + } + + [Theory] + [MemberData(nameof(MemberSyntaxAndArgs))] + public async void DoesNotFindWarning_IfHasValidTheoryDataDoubleGenericSubclassMember( + string memberSyntax, + string memberArgs) + { + var source = $@" +using Xunit; + +public class DerivedTheoryData : TheoryData {{ }} + +public class TestClass {{ + public static DerivedTheoryData TestData{memberSyntax}new(); + + [MemberData(nameof(TestData){memberArgs})] + public void TestMethod(int n) {{ }} +}}"; + + await Verify.VerifyAnalyzer(LanguageVersion.CSharp10, source); + } + [Fact] public async void DoesNotFindWarning_WithIntArrayArguments() { @@ -1142,6 +1205,35 @@ public void TestMethod(int n) {{ }} await Verify.VerifyAnalyzer(LanguageVersion.CSharp10, source, expected); } + [Theory] + [MemberData(nameof(MemberSyntaxAndArgs))] + public async void FindWarning_IfHasValidSubclassTheoryDataMemberWithTooManyTypeParameters( + string memberSyntax, + string memberArgs) + { + var source = $@" +using Xunit; + +public class DerivedTheoryData : TheoryData {{ }} + +public class TestClass {{ + public static DerivedTheoryData TestData{memberSyntax}new(); + + [MemberData(nameof(TestData){memberArgs})] + public void TestMethod(int n) {{ }} +}}"; + + DiagnosticResult[] expected = + { + Verify + .Diagnostic("xUnit1038") + .WithSpan(9, 6, 9, 34 + memberArgs.Length) + .WithSeverity(DiagnosticSeverity.Error) + }; + + await Verify.VerifyAnalyzer(LanguageVersion.CSharp10, source, expected); + } + [Fact] public async void FindWarning_WhenExtraTypeExistsPastArrayForParamsArray() { @@ -1164,6 +1256,30 @@ public void PuzzleOne(int _1, params string[] _2) { } await Verify.VerifyAnalyzer(source, expected); } + [Fact] + public async void FindWarning_WhenSubclassedExtraTypeExistsPastArrayForParamsArray() + { + var source = @" +using Xunit; + +public class DerivedTheoryData : TheoryData { } + +public class TestClass { + public static DerivedTheoryData TestData = new DerivedTheoryData(); + + [MemberData(nameof(TestData))] + public void PuzzleOne(int _1, params string[] _2) { } +}"; + + var expected = + Verify + .Diagnostic("xUnit1038") + .WithSpan(9, 6, 9, 34) + .WithSeverity(DiagnosticSeverity.Error); + + await Verify.VerifyAnalyzer(source, expected); + } + [Theory] [MemberData(nameof(MemberSyntaxAndArgs))] public async void FindWarning_IfHasValidTheoryDataMemberWithNotEnoughTypeParameters( @@ -1189,6 +1305,35 @@ public void TestMethod(int n, string f) {{ }} await Verify.VerifyAnalyzer(LanguageVersion.CSharp10, source, expected); } + [Theory] + [MemberData(nameof(MemberSyntaxAndArgs))] + public async void FindWarning_IfHasValidSubclassedTheoryDataMemberWithNotEnoughTypeParameters( + string memberSyntax, + string memberArgs) + { + var source = $@" +using Xunit; + +public class DerivedTheoryData : TheoryData {{ }} + +public class TestClass {{ + public static DerivedTheoryData TestData{memberSyntax}new(); + + [Xunit.MemberData(nameof(TestData){memberArgs})] + public void TestMethod(int n, string f) {{ }} +}}"; + + DiagnosticResult[] expected = + { + Verify + .Diagnostic("xUnit1037") + .WithSpan(9, 6, 9, 40 + memberArgs.Length) + .WithSeverity(DiagnosticSeverity.Error) + }; + + await Verify.VerifyAnalyzer(LanguageVersion.CSharp10, source, expected); + } + public static TheoryData TypeWithMemberSyntaxAndArgs() { var result = new TheoryData(); diff --git a/src/xunit.analyzers/X1000/MemberDataShouldReferenceValidMember.cs b/src/xunit.analyzers/X1000/MemberDataShouldReferenceValidMember.cs index 537bfda3..e3d34d7c 100644 --- a/src/xunit.analyzers/X1000/MemberDataShouldReferenceValidMember.cs +++ b/src/xunit.analyzers/X1000/MemberDataShouldReferenceValidMember.cs @@ -638,15 +638,15 @@ static bool IsTheoryDataType( out INamedTypeSymbol? theoryReturnType) { theoryReturnType = default; - if (memberReturnType is not INamedTypeSymbol namedReturnType || !namedReturnType.IsGenericType) + if (memberReturnType is not INamedTypeSymbol namedReturnType) return false; INamedTypeSymbol? working = namedReturnType; while (working is not null) { - var returnTypeArguments = namedReturnType.TypeArguments; + var returnTypeArguments = working.TypeArguments; if (theoryDataTypes.TryGetValue(returnTypeArguments.Length, out var theoryDataType) - && SymbolEqualityComparer.Default.Equals(theoryDataType, namedReturnType.OriginalDefinition)) + && SymbolEqualityComparer.Default.Equals(theoryDataType, working.OriginalDefinition)) break; working = working.BaseType; } From 90ce1d27e19c484e948c9921bdf8be5c5f0a2d1b Mon Sep 17 00:00:00 2001 From: Brad Wilson Date: Sun, 3 Dec 2023 12:35:10 -0800 Subject: [PATCH 06/10] Alphabetize --- .../MemberDataShouldReferenceValidMember.cs | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/xunit.analyzers/X1000/MemberDataShouldReferenceValidMember.cs b/src/xunit.analyzers/X1000/MemberDataShouldReferenceValidMember.cs index e3d34d7c..7a32fcf3 100644 --- a/src/xunit.analyzers/X1000/MemberDataShouldReferenceValidMember.cs +++ b/src/xunit.analyzers/X1000/MemberDataShouldReferenceValidMember.cs @@ -265,6 +265,32 @@ public static (INamedTypeSymbol? TestClass, ITypeSymbol? MemberClass) GetClassTy return null; } + static bool IsTheoryDataType( + ITypeSymbol? memberReturnType, + Dictionary theoryDataTypes, + out INamedTypeSymbol? theoryReturnType) + { + theoryReturnType = default; + if (memberReturnType is not INamedTypeSymbol namedReturnType) + return false; + + INamedTypeSymbol? working = namedReturnType; + while (working is not null) + { + var returnTypeArguments = working.TypeArguments; + if (theoryDataTypes.TryGetValue(returnTypeArguments.Length, out var theoryDataType) + && SymbolEqualityComparer.Default.Equals(theoryDataType, working.OriginalDefinition)) + break; + working = working.BaseType; + } + + if (working is null) + return false; + + theoryReturnType = working; + return true; + } + static void ReportIllegalNonMethodArguments( SyntaxNodeAnalysisContext context, AttributeSyntax attribute, @@ -632,32 +658,6 @@ static bool VerifyDataSourceReturnType( return valid; } - static bool IsTheoryDataType( - ITypeSymbol? memberReturnType, - Dictionary theoryDataTypes, - out INamedTypeSymbol? theoryReturnType) - { - theoryReturnType = default; - if (memberReturnType is not INamedTypeSymbol namedReturnType) - return false; - - INamedTypeSymbol? working = namedReturnType; - while (working is not null) - { - var returnTypeArguments = working.TypeArguments; - if (theoryDataTypes.TryGetValue(returnTypeArguments.Length, out var theoryDataType) - && SymbolEqualityComparer.Default.Equals(theoryDataType, working.OriginalDefinition)) - break; - working = working.BaseType; - } - - if (working is null) - return false; - - theoryReturnType = working; - return true; - } - static void VerifyTheoryDataUsage( SemanticModel semanticModel, SyntaxNodeAnalysisContext context, From 45e49db14dec36b7a4c5c42a588d547941da528c Mon Sep 17 00:00:00 2001 From: Brad Wilson Date: Sun, 3 Dec 2023 12:37:21 -0800 Subject: [PATCH 07/10] Add [NotNullWhen] for IsTheoryDataType --- .../X1000/MemberDataShouldReferenceValidMember.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/xunit.analyzers/X1000/MemberDataShouldReferenceValidMember.cs b/src/xunit.analyzers/X1000/MemberDataShouldReferenceValidMember.cs index 7a32fcf3..1d4b78bb 100644 --- a/src/xunit.analyzers/X1000/MemberDataShouldReferenceValidMember.cs +++ b/src/xunit.analyzers/X1000/MemberDataShouldReferenceValidMember.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using System.Threading; @@ -154,7 +155,7 @@ public override void AnalyzeCompilation( // If the member returns TheoryData, ensure that the types are compatible // If the member does not return TheoryData, gently suggest to the user that TheoryData is better for type safety if (IsTheoryDataType(memberReturnType, theoryDataTypes, out var theoryReturnType)) - VerifyTheoryDataUsage(semanticModel, context, testMethod, theoryReturnType!, memberName, declaredMemberTypeSymbol, attributeSyntax); + VerifyTheoryDataUsage(semanticModel, context, testMethod, theoryReturnType, memberName, declaredMemberTypeSymbol, attributeSyntax); else if (IsValidMemberReturnType) ReportMemberReturnsTypeUnsafeValue(context, attributeSyntax); @@ -268,7 +269,7 @@ public static (INamedTypeSymbol? TestClass, ITypeSymbol? MemberClass) GetClassTy static bool IsTheoryDataType( ITypeSymbol? memberReturnType, Dictionary theoryDataTypes, - out INamedTypeSymbol? theoryReturnType) + [NotNullWhen(true)] out INamedTypeSymbol? theoryReturnType) { theoryReturnType = default; if (memberReturnType is not INamedTypeSymbol namedReturnType) From e1fec7dca46afbbbafebc71c05e1f3ab1d956f7e Mon Sep 17 00:00:00 2001 From: Brad Wilson Date: Sun, 3 Dec 2023 12:53:21 -0800 Subject: [PATCH 08/10] Simplify single expected values, other test formatting --- ...mberDataShouldReferenceValidMemberTests.cs | 145 ++++++------------ 1 file changed, 51 insertions(+), 94 deletions(-) diff --git a/src/xunit.analyzers.tests/Analyzers/X1000/MemberDataShouldReferenceValidMemberTests.cs b/src/xunit.analyzers.tests/Analyzers/X1000/MemberDataShouldReferenceValidMemberTests.cs index d2aa01b7..23205e4b 100644 --- a/src/xunit.analyzers.tests/Analyzers/X1000/MemberDataShouldReferenceValidMemberTests.cs +++ b/src/xunit.analyzers.tests/Analyzers/X1000/MemberDataShouldReferenceValidMemberTests.cs @@ -27,13 +27,11 @@ public partial class TestClass { public void TestMethod() { } }"; - DiagnosticResult[] expected = - { + var expected = Verify .Diagnostic("xUnit1042") .WithSpan(3, 6, 3, 36) - .WithSeverity(DiagnosticSeverity.Info) - }; + .WithSeverity(DiagnosticSeverity.Info); await Verify.VerifyAnalyzer(new[] { source, sharedCode }, expected); } @@ -47,13 +45,11 @@ public partial class TestClass { public void TestMethod() { } }"; - DiagnosticResult[] expected = - { + var expected = Verify .Diagnostic("xUnit1042") .WithSpan(3, 6, 3, 85) - .WithSeverity(DiagnosticSeverity.Info) - }; + .WithSeverity(DiagnosticSeverity.Info); await Verify.VerifyAnalyzer(new[] { source, sharedCode }, expected); } @@ -220,13 +216,11 @@ public class TestClass { public void TestMethod() { } }"; - DiagnosticResult[] expected = - { + var expected = Verify .Diagnostic("xUnit1042") .WithSpan(5, 6, 5, 36) - .WithSeverity(DiagnosticSeverity.Info) - }; + .WithSeverity(DiagnosticSeverity.Info); await Verify.VerifyAnalyzer(source, expected); } @@ -306,13 +300,11 @@ public class TestClass { public void TestMethod() { } }"; - DiagnosticResult[] expected = - { + var expected = Verify .Diagnostic("xUnit1042") .WithSpan(5, 6, 5, 36) - .WithSeverity(DiagnosticSeverity.Info) - }; + .WithSeverity(DiagnosticSeverity.Info); await Verify.VerifyAnalyzer(source, expected); } @@ -353,13 +345,11 @@ public class TestClass {{ public void TestMethod() {{ }} }}"; - DiagnosticResult[] expected = - { + var expected = Verify .Diagnostic("xUnit1042") .WithSpan(5, 6, 5, 36) - .WithSeverity(DiagnosticSeverity.Info) - }; + .WithSeverity(DiagnosticSeverity.Info); await Verify.VerifyAnalyzer(source, expected); } @@ -412,15 +402,15 @@ public class TestClass {{ public void TestMethod(int _) {{ }} }}"; - DiagnosticResult[] expected = !memberType.Contains("TheoryData") - ? - new DiagnosticResult[] { + DiagnosticResult[] expected = + !memberType.Contains("TheoryData") + ? new DiagnosticResult[] { Verify .Diagnostic("xUnit1042") .WithSpan(5, 6, 5, 36) .WithSeverity(DiagnosticSeverity.Info) } - : Array.Empty(); + : Array.Empty(); await Verify.VerifyAnalyzer(source, expected); } @@ -447,13 +437,11 @@ public static List DataRowSource() => new TheoryDataRow(0, null) { Skip = ""Don't run this!"" }, }; }"; - DiagnosticResult[] expected = - { + var expected = Verify .Diagnostic("xUnit1042") .WithSpan(9, 6, 9, 39) - .WithSeverity(DiagnosticSeverity.Info) - }; + .WithSeverity(DiagnosticSeverity.Info); await Verify.VerifyAnalyzerV3(source, expected); } @@ -616,15 +604,13 @@ public class TestClass { public void TestMethod() { } }"; - DiagnosticResult[] expected = - { + var expected = Verify .Diagnostic("xUnit1042") .WithSpan(5, 6, 5, 68) - .WithSeverity(DiagnosticSeverity.Info) - }; + .WithSeverity(DiagnosticSeverity.Info); - await Verify.VerifyAnalyzer(source, expected); + await Verify.VerifyAnalyzer(source, expected); } [Fact] @@ -640,13 +626,11 @@ private static void TestData() { } public void TestMethod(int n) { } }"; - DiagnosticResult[] expected = - { + var expected = Verify .Diagnostic("xUnit1042") .WithSpan(7, 6, 7, 60) - .WithSeverity(DiagnosticSeverity.Info) - }; + .WithSeverity(DiagnosticSeverity.Info); await Verify.VerifyAnalyzer(source, expected); } @@ -662,15 +646,13 @@ public class TestClass { public void TestMethod(int n) { } }"; - DiagnosticResult[] expected = - { + var expected = Verify .Diagnostic("xUnit1042") .WithSpan(5, 6, 5, 63) - .WithSeverity(DiagnosticSeverity.Info) - }; + .WithSeverity(DiagnosticSeverity.Info); - await Verify.VerifyAnalyzer(source, expected); + await Verify.VerifyAnalyzer(source, expected); } // https://github.com/xunit/xunit/issues/2817 @@ -694,13 +676,11 @@ public enum Foo {{ Bar }} public static IEnumerable SomeData(Foo foo) => Array.Empty(); }}"; - DiagnosticResult[] expected = - { + var expected = Verify .Diagnostic("xUnit1042") .WithSpan(8, 6, 8, 43) - .WithSeverity(DiagnosticSeverity.Info) - }; + .WithSeverity(DiagnosticSeverity.Info); await Verify.VerifyAnalyzer(source, expected); } @@ -714,8 +694,7 @@ public class TestClass { [Xunit.MemberData(nameof(TestData), new object[] { 1 })] public void TestMethod(int n) { } -}" - ; +}"; DiagnosticResult[] expected = { @@ -741,8 +720,7 @@ public class TestClass { [Xunit.MemberData(nameof(TestData), new object[] { 1, ""bob"" })] public void TestMethod(int n) { } -}" - ; +}"; DiagnosticResult[] expected = { @@ -768,8 +746,7 @@ public class TestClass { [Xunit.MemberData(nameof(TestData), new object[] { 1, 2 })] public void TestMethod(int n) { } -}" - ; +}"; DiagnosticResult[] expected = { @@ -781,7 +758,7 @@ public void TestMethod(int n) { } .Diagnostic("xUnit1036") .WithSpan(5, 59, 5, 60) .WithArguments("2") - }; + }; await Verify.VerifyAnalyzer(source, expected); } @@ -795,8 +772,7 @@ public class TestClass { [Xunit.MemberData(nameof(TestData), 1, 2)] public void TestMethod(int n) { } -}" - ; +}"; DiagnosticResult[] expected = { @@ -826,13 +802,11 @@ private static void TestData() { } public void TestMethod(int n) { } }"; - DiagnosticResult[] expected = - { + var expected = Verify .Diagnostic("xUnit1042") .WithSpan(7, 6, 7, 60) - .WithSeverity(DiagnosticSeverity.Info) - }; + .WithSeverity(DiagnosticSeverity.Info); await Verify.VerifyAnalyzer(source, expected); } @@ -848,16 +822,13 @@ public class TestClass { [Xunit.MemberData(nameof(TestData), new object[] { 1 })] public void TestMethod(int n) { } } -#nullable restore -"; +#nullable restore"; - DiagnosticResult[] expected = - { + var expected = Verify .Diagnostic("xUnit1042") .WithSpan(6, 6, 6, 60) - .WithSeverity(DiagnosticSeverity.Info) - }; + .WithSeverity(DiagnosticSeverity.Info); await Verify.VerifyAnalyzer(LanguageVersion.CSharp8, source, expected); } @@ -913,14 +884,12 @@ private static void TestData() { } public void TestMethod(int n) { } }"; - DiagnosticResult[] expected = - { + var expected = Verify .Diagnostic("xUnit1042") .WithSpan(11, 6, 11, 60) - .WithSeverity(DiagnosticSeverity.Info) - }; - + .WithSeverity(DiagnosticSeverity.Info); + await Verify.VerifyAnalyzer(source, expected); } @@ -1120,8 +1089,7 @@ public void Test(IEnumerable seq) {{ Assert.NotEmpty(seq); }} -}} -"; +}}"; DiagnosticResult[] expected = { @@ -1156,8 +1124,7 @@ public void Test(IEnumerable seq) {{ Assert.NotEmpty(seq); }} -}} -"; +}}"; DiagnosticResult[] expected = { @@ -1194,13 +1161,11 @@ public class TestClass {{ public void TestMethod(int n) {{ }} }}"; - DiagnosticResult[] expected = - { + var expected = Verify .Diagnostic("xUnit1038") .WithSpan(5, 6, 5, 40 + memberArgs.Length) - .WithSeverity(DiagnosticSeverity.Error) - }; + .WithSeverity(DiagnosticSeverity.Error); await Verify.VerifyAnalyzer(LanguageVersion.CSharp10, source, expected); } @@ -1223,13 +1188,11 @@ public class TestClass {{ public void TestMethod(int n) {{ }} }}"; - DiagnosticResult[] expected = - { + var expected = Verify .Diagnostic("xUnit1038") .WithSpan(9, 6, 9, 34 + memberArgs.Length) - .WithSeverity(DiagnosticSeverity.Error) - }; + .WithSeverity(DiagnosticSeverity.Error); await Verify.VerifyAnalyzer(LanguageVersion.CSharp10, source, expected); } @@ -1294,13 +1257,11 @@ public class TestClass {{ public void TestMethod(int n, string f) {{ }} }}"; - DiagnosticResult[] expected = - { + var expected = Verify .Diagnostic("xUnit1037") .WithSpan(5, 6, 5, 40 + memberArgs.Length) - .WithSeverity(DiagnosticSeverity.Error) - }; + .WithSeverity(DiagnosticSeverity.Error); await Verify.VerifyAnalyzer(LanguageVersion.CSharp10, source, expected); } @@ -1323,13 +1284,11 @@ public class TestClass {{ public void TestMethod(int n, string f) {{ }} }}"; - DiagnosticResult[] expected = - { + var expected = Verify .Diagnostic("xUnit1037") .WithSpan(9, 6, 9, 40 + memberArgs.Length) - .WithSeverity(DiagnosticSeverity.Error) - }; + .WithSeverity(DiagnosticSeverity.Error); await Verify.VerifyAnalyzer(LanguageVersion.CSharp10, source, expected); } @@ -1417,14 +1376,12 @@ public class TestClass {{ public void TestMethod(string f) {{ }} }}"; - DiagnosticResult[] expected = - { + var expected = Verify .Diagnostic("xUnit1039") .WithSpan(6, 28, 6, 34) .WithSeverity(DiagnosticSeverity.Error) - .WithArguments(type, "TestClass", "TestData", "f") - }; + .WithArguments(type, "TestClass", "TestData", "f"); await Verify.VerifyAnalyzer(LanguageVersion.CSharp10, source, expected); } From 3618869e62d1a5fafa1c6a5dd2abf8b9ec899a9f Mon Sep 17 00:00:00 2001 From: Brad Wilson Date: Sun, 3 Dec 2023 17:48:48 -0800 Subject: [PATCH 09/10] Restructure MemberDataShouldReferenceValidMemberTests --- ...mberDataShouldReferenceValidMemberTests.cs | 1900 ++++++++--------- ...ferenceValidMember_ReturnTypeFixerTests.cs | 2 +- .../CallerArgumentExpressionAttribute.cs | 18 + src/xunit.analyzers.tests/Utility/Guard.cs | 290 +++ .../Utility/MatrixTheoryData.cs | 228 ++ .../MemberDataShouldReferenceValidMember.cs | 58 +- 6 files changed, 1439 insertions(+), 1057 deletions(-) create mode 100644 src/xunit.analyzers.tests/Utility/CallerArgumentExpressionAttribute.cs create mode 100644 src/xunit.analyzers.tests/Utility/Guard.cs create mode 100644 src/xunit.analyzers.tests/Utility/MatrixTheoryData.cs diff --git a/src/xunit.analyzers.tests/Analyzers/X1000/MemberDataShouldReferenceValidMemberTests.cs b/src/xunit.analyzers.tests/Analyzers/X1000/MemberDataShouldReferenceValidMemberTests.cs index 23205e4b..cb652552 100644 --- a/src/xunit.analyzers.tests/Analyzers/X1000/MemberDataShouldReferenceValidMemberTests.cs +++ b/src/xunit.analyzers.tests/Analyzers/X1000/MemberDataShouldReferenceValidMemberTests.cs @@ -1,4 +1,3 @@ -using System; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Testing; @@ -7,418 +6,297 @@ public class MemberDataShouldReferenceValidMemberTests { - static readonly string sharedCode = @" + public class X1014_MemberDataShouldUseNameOfOperator + { + static readonly string sharedCode = @" using System.Collections.Generic; +using Xunit; public partial class TestClass { - public static IEnumerable Data { get; set; } + public static TheoryData Data { get; set; } } public class OtherClass { - public static IEnumerable OtherData { get; set; } + public static TheoryData OtherData { get; set; } }"; - [Fact] - public async void DoesNotFindError_ForNameofOnSameClass() - { - var source = @" + [Fact] + public async void NameofOnSameClass_DoesNotTrigger() + { + var source = @" public partial class TestClass { [Xunit.MemberData(nameof(Data))] - public void TestMethod() { } + public void TestMethod(int _) { } }"; - var expected = - Verify - .Diagnostic("xUnit1042") - .WithSpan(3, 6, 3, 36) - .WithSeverity(DiagnosticSeverity.Info); - - await Verify.VerifyAnalyzer(new[] { source, sharedCode }, expected); - } + await Verify.VerifyAnalyzer(new[] { source, sharedCode }); + } - [Fact] - public async void DoesNotFindError_ForNameofOnOtherClass() - { - var source = @" + [Fact] + public async void NameofOnOtherClass_DoesNotTrigger() + { + var source = @" public partial class TestClass { [Xunit.MemberData(nameof(OtherClass.OtherData), MemberType = typeof(OtherClass))] - public void TestMethod() { } + public void TestMethod(int _) { } }"; - var expected = - Verify - .Diagnostic("xUnit1042") - .WithSpan(3, 6, 3, 85) - .WithSeverity(DiagnosticSeverity.Info); - - await Verify.VerifyAnalyzer(new[] { source, sharedCode }, expected); - } + await Verify.VerifyAnalyzer(new[] { source, sharedCode }); + } - [Fact] - public async void FindsError_ForStringReferenceOnSameClass() - { - var source = @" + [Fact] + public async void StringNameOnSameClass_Triggers() + { + var source = @" public partial class TestClass { [Xunit.MemberData(""Data"")] - public void TestMethod() { } + public void TestMethod(int _) { } }"; - DiagnosticResult[] expected = - { - Verify - .Diagnostic("xUnit1042") - .WithSpan(3, 6, 3, 30) - .WithSeverity(DiagnosticSeverity.Info), - Verify - .Diagnostic("xUnit1014") - .WithSpan(3, 23, 3, 29) - .WithArguments("Data", "TestClass") - }; + var expected = + Verify + .Diagnostic("xUnit1014") + .WithSpan(3, 23, 3, 29) + .WithArguments("Data", "TestClass"); - await Verify.VerifyAnalyzer(new[] { source, sharedCode }, expected); - } + await Verify.VerifyAnalyzer(new[] { source, sharedCode }, expected); + } - [Fact] - public async void FindsError_ForStringReferenceOnOtherClass() - { - var source = @" + [Fact] + public async void StringNameOnOtherClass_Triggers() + { + var source = @" public partial class TestClass { [Xunit.MemberData(""OtherData"", MemberType = typeof(OtherClass))] - public void TestMethod() { } + public void TestMethod(int _) { } }"; - DiagnosticResult[] expected = - { - Verify - .Diagnostic("xUnit1042") - .WithSpan(3, 6, 3, 68) - .WithSeverity(DiagnosticSeverity.Info), - Verify - .Diagnostic("xUnit1014") - .WithSpan(3, 23, 3, 34) - .WithArguments("OtherData", "OtherClass") - }; + var expected = + Verify + .Diagnostic("xUnit1014") + .WithSpan(3, 23, 3, 34) + .WithArguments("OtherData", "OtherClass"); - await Verify.VerifyAnalyzer(new[] { source, sharedCode }, expected); - } - - [Fact] - public async void FindsError_ForInvalidNameString() - { - var source = @" -public class TestClass { - [Xunit.MemberData(""BogusName"")] - public void TestMethod() { } -}"; - var expected = - Verify - .Diagnostic("xUnit1015") - .WithSpan(3, 6, 3, 35) - .WithSeverity(DiagnosticSeverity.Error) - .WithArguments("BogusName", "TestClass"); - - await Verify.VerifyAnalyzer(source, expected); + await Verify.VerifyAnalyzer(new[] { source, sharedCode }, expected); + } } - [Fact] - public async void FindsError_ForInvalidNameString_UsingMemberType() + public class X1015_MemberDataMustReferenceExistingMember { - var source = @" -public class TestClass { - [Xunit.MemberData(""BogusName"", MemberType = typeof(TestClass))] - public void TestMethod() { } -}"; - var expected = - Verify - .Diagnostic("xUnit1015") - .WithSpan(3, 6, 3, 67) - .WithSeverity(DiagnosticSeverity.Error) - .WithArguments("BogusName", "TestClass"); - - await Verify.VerifyAnalyzer(source, expected); - } + [Theory] + [InlineData("")] + [InlineData(", MemberType = typeof(TestClass)")] + public async void InvalidStringNameOnSameClass_Triggers(string memberType) + { + var source = @$" +public class TestClass {{ + [Xunit.MemberData(""BogusName""{memberType})] + public void TestMethod() {{ }} +}}"; + var expected = + Verify + .Diagnostic("xUnit1015") + .WithSpan(3, 6, 3, 35 + memberType.Length) + .WithSeverity(DiagnosticSeverity.Error) + .WithArguments("BogusName", "TestClass"); + + await Verify.VerifyAnalyzer(source, expected); + } - [Fact] - public async void FindsError_ForInvalidNameString_UsingMemberTypeWithOtherType() - { - var source1 = @" + [Fact] + public async void InvalidStringNameOnOtherClass_Triggers() + { + var source1 = @" public class TestClass { [Xunit.MemberData(""BogusName"", MemberType = typeof(OtherClass))] public void TestMethod() { } }"; - var source2 = "public class OtherClass { }"; - var expected = - Verify - .Diagnostic("xUnit1015") - .WithSpan(3, 6, 3, 68) - .WithSeverity(DiagnosticSeverity.Error) - .WithArguments("BogusName", "OtherClass"); - - await Verify.VerifyAnalyzer(new[] { source1, source2 }, expected); - } + var source2 = "public class OtherClass { }"; + var expected = + Verify + .Diagnostic("xUnit1015") + .WithSpan(3, 6, 3, 68) + .WithSeverity(DiagnosticSeverity.Error) + .WithArguments("BogusName", "OtherClass"); + + await Verify.VerifyAnalyzer(new[] { source1, source2 }, expected); + } - [Fact] - public async void FindsError_ForValidNameofExpression_UsingMemberTypeSpecifyingOtherType() - { - var source1 = @" + [Fact] + public async void InvalidNameofOnOtherClass_Triggers() + { + var source1 = @" public class TestClass { [Xunit.MemberData(nameof(TestClass.TestMethod), MemberType = typeof(OtherClass))] public void TestMethod() { } }"; - var source2 = "public class OtherClass { }"; - var expected = - Verify - .Diagnostic("xUnit1015") - .WithSpan(3, 6, 3, 85) - .WithSeverity(DiagnosticSeverity.Error) - .WithArguments("TestMethod", "OtherClass"); - - await Verify.VerifyAnalyzer(new[] { source1, source2 }, expected); + var source2 = "public class OtherClass { }"; + var expected = + Verify + .Diagnostic("xUnit1015") + .WithSpan(3, 6, 3, 85) + .WithSeverity(DiagnosticSeverity.Error) + .WithArguments("TestMethod", "OtherClass"); + + await Verify.VerifyAnalyzer(new[] { source1, source2 }, expected); + } } - [Theory] - [InlineData("")] - [InlineData("private")] - [InlineData("protected")] - [InlineData("internal")] - [InlineData("protected internal")] - public async void FindsError_ForNonPublicMember(string accessModifier) + public class X1016_MemberDataMustReferencePublicMember { - var source = $@" -public class TestClass {{ - {accessModifier} static System.Collections.Generic.IEnumerable Data = null; - - [Xunit.MemberData(nameof(Data))] - public void TestMethod() {{ }} -}}"; - - DiagnosticResult[] expected = + [Fact] + public async void PublicMember_DoesNotTrigger() { - Verify - .Diagnostic("xUnit1016") - .WithSpan(5, 6, 5, 36) - .WithSeverity(DiagnosticSeverity.Error), - Verify - .Diagnostic("xUnit1042") - .WithSpan(5, 6, 5, 36) - .WithSeverity(DiagnosticSeverity.Info) - }; - - await Verify.VerifyAnalyzer(source, expected); - } - - [Fact] - public async void DoesNotFindError_ForPublicMember() - { - var source = @" + var source = @" public class TestClass { - public static System.Collections.Generic.IEnumerable Data = null; + public static Xunit.TheoryData Data = null; [Xunit.MemberData(nameof(Data))] - public void TestMethod() { } + public void TestMethod(int _) { } }"; - var expected = - Verify - .Diagnostic("xUnit1042") - .WithSpan(5, 6, 5, 36) - .WithSeverity(DiagnosticSeverity.Info); - - await Verify.VerifyAnalyzer(source, expected); - } + await Verify.VerifyAnalyzer(source); + } - [Theory] - [InlineData("{|xUnit1014:\"Data\"|}")] - [InlineData("DataNameConst")] - [InlineData("DataNameofConst")] - [InlineData("nameof(Data)")] - [InlineData("nameof(TestClass.Data)")] - [InlineData("OtherClass.Data")] - [InlineData("nameof(OtherClass.Data)")] - public async void FindsError_ForNameExpressions(string dataNameExpression) - { - TestFileMarkupParser.GetPositionsAndSpans(dataNameExpression, out var parsedDataNameExpression, out _, out _); - var dataNameExpressionLength = parsedDataNameExpression.Length; + public static MatrixTheoryData NonPublicTestData => + new( + new[] { "", "private", "protected", "internal", "protected internal" }, + new[] { "{|xUnit1014:\"Data\"|}", "DataNameConst", "DataNameofConst", "nameof(Data)", "nameof(TestClass.Data)", "OtherClass.Data", "nameof(OtherClass.Data)" } + ); + + [Theory] + [MemberData(nameof(NonPublicTestData))] + public async void NonPublicNameExpression_Triggers( + string accessModifier, + string dataNameExpression) + { + TestFileMarkupParser.GetPositionsAndSpans(dataNameExpression, out var parsedDataNameExpression, out _, out _); + var dataNameExpressionLength = parsedDataNameExpression.Length; - var source1 = $@" + var source1 = $@" public class TestClass {{ const string DataNameConst = ""Data""; const string DataNameofConst = nameof(Data); - private static System.Collections.Generic.IEnumerable Data = null; + {accessModifier} static Xunit.TheoryData Data = null; [Xunit.MemberData({dataNameExpression})] - public void TestMethod() {{ }} + public void TestMethod(int _) {{ }} }}"; - var source2 = @"public static class OtherClass { public const string Data = ""Data""; }"; - DiagnosticResult[] expected = - { - Verify - .Diagnostic("xUnit1016") - .WithSpan(8, 6, 8, 24 + dataNameExpressionLength) - .WithSeverity(DiagnosticSeverity.Error), - Verify - .Diagnostic("xUnit1042") - .WithSpan(8, 6, 8, 24 + dataNameExpressionLength) - .WithSeverity(DiagnosticSeverity.Info) - }; - - await Verify.VerifyAnalyzer(new[] { source1, source2 }, expected); + var source2 = @"public static class OtherClass { public const string Data = ""Data""; }"; + var expected = + Verify + .Diagnostic("xUnit1016") + .WithSpan(8, 6, 8, 24 + dataNameExpressionLength) + .WithSeverity(DiagnosticSeverity.Error); + + await Verify.VerifyAnalyzer(new[] { source1, source2 }, expected); + } } - [Fact] - public async void FindsError_ForInstanceMember() + public class X1017_MemberDataMustReferenceStaticMember { - var source = @" + [Fact] + public async void StaticMember_DoesNotTrigger() + { + var source = @" public class TestClass { - public System.Collections.Generic.IEnumerable Data = null; + public static Xunit.TheoryData Data = null; [Xunit.MemberData(nameof(Data))] - public void TestMethod() { } + public void TestMethod(int _) { } }"; - DiagnosticResult[] expected = - { - Verify - .Diagnostic("xUnit1017") - .WithSpan(5, 6, 5, 36) - .WithSeverity(DiagnosticSeverity.Error), - Verify - .Diagnostic("xUnit1042") - .WithSpan(5, 6, 5, 36) - .WithSeverity(DiagnosticSeverity.Info) - }; - await Verify.VerifyAnalyzer(source, expected); - } + await Verify.VerifyAnalyzer(source); + } - [Fact] - public async void DoesNotFindError_ForStaticMember() - { - var source = @" + [Fact] + public async void InstanceMember_Triggers() + { + var source = @" public class TestClass { - public static System.Collections.Generic.IEnumerable Data = null; + public Xunit.TheoryData Data = null; [Xunit.MemberData(nameof(Data))] - public void TestMethod() { } + public void TestMethod(int _) { } }"; + var expected = + Verify + .Diagnostic("xUnit1017") + .WithSpan(5, 6, 5, 36) + .WithSeverity(DiagnosticSeverity.Error); - var expected = - Verify - .Diagnostic("xUnit1042") - .WithSpan(5, 6, 5, 36) - .WithSeverity(DiagnosticSeverity.Info); - - await Verify.VerifyAnalyzer(source, expected); + await Verify.VerifyAnalyzer(source, expected); + } } - [Theory] - [InlineData("public delegate System.Collections.Generic.IEnumerable Data();")] - [InlineData("public static class Data { }")] - [InlineData("public static event System.EventHandler Data;")] - public async void FindsError_ForInvalidMemberKind(string member) + public class X1018_MemberDataMustReferenceValidMemberKind { - var source = $@" + [Theory] + [InlineData("Data;")] + [InlineData("Data { get; set; }")] + [InlineData("Data() { return null; }")] + public async void ValidMemberKind_DoesNotTrigger(string member) + { + var source = $@" public class TestClass {{ - {member} + public static Xunit.TheoryData {member} [Xunit.MemberData(nameof(Data))] - public void TestMethod() {{ }} + public void TestMethod(int _) {{ }} }}"; - var expected = - Verify - .Diagnostic("xUnit1018") - .WithSpan(5, 6, 5, 36) - .WithSeverity(DiagnosticSeverity.Error); - await Verify.VerifyAnalyzer(source, expected); - } + await Verify.VerifyAnalyzer(source); + } - [Theory] - [InlineData("public static System.Collections.Generic.IEnumerable Data;")] - [InlineData("public static System.Collections.Generic.IEnumerable Data { get; set; }")] - [InlineData("public static System.Collections.Generic.IEnumerable Data() { return null; }")] - public async void DoesNotFindError_ForValidMemberKind(string member) - { - var source = $@" + [Theory] + [InlineData("public delegate System.Collections.Generic.IEnumerable Data();")] + [InlineData("public static class Data { }")] + [InlineData("public static event System.EventHandler Data;")] + public async void InvalidMemberKind_Triggers(string member) + { + var source = $@" public class TestClass {{ {member} [Xunit.MemberData(nameof(Data))] public void TestMethod() {{ }} }}"; + var expected = + Verify + .Diagnostic("xUnit1018") + .WithSpan(5, 6, 5, 36) + .WithSeverity(DiagnosticSeverity.Error); - var expected = - Verify - .Diagnostic("xUnit1042") - .WithSpan(5, 6, 5, 36) - .WithSeverity(DiagnosticSeverity.Info); - - await Verify.VerifyAnalyzer(source, expected); + await Verify.VerifyAnalyzer(source, expected); + } } - [Theory] - [InlineData("System.Collections.Generic.IEnumerable")] - [InlineData("object[]")] - [InlineData("object")] - [InlineData("System.Tuple")] - [InlineData("System.Tuple[]")] - public async void FindsError_ForInvalidMemberType(string memberType) + public class X1019_MemberDataMustReferenceMemberOfValidType { - var source = $@" -public class TestClass {{ - public static {memberType} Data; - - [Xunit.MemberData(nameof(Data))] - public void TestMethod() {{ }} -}}"; - var expectedV2 = - Verify - .Diagnostic("xUnit1019") - .WithSpan(5, 6, 5, 36) - .WithSeverity(DiagnosticSeverity.Error) - .WithArguments("'System.Collections.Generic.IEnumerable'", memberType); - - await Verify.VerifyAnalyzerV2(source, expectedV2); - - var expectedV3 = - Verify - .Diagnostic("xUnit1019") - .WithSpan(5, 6, 5, 36) - .WithSeverity(DiagnosticSeverity.Error) - .WithArguments("'System.Collections.Generic.IEnumerable' or 'System.Collections.Generic.IEnumerable'", memberType); - - await Verify.VerifyAnalyzerV3(source, expectedV3); - } + // The base type of IEnumerable triggers xUnit1042, which is covered in the tests + // below in X1042_MemberDataTheoryDataIsRecommendedForStronglyTypedAnalysis, so we'll only + // test TheoryData<> and IEnumerable here. - [Theory] - [InlineData("System.Collections.Generic.IEnumerable")] - [InlineData("System.Collections.Generic.List")] - [InlineData("Xunit.TheoryData")] - public async void DoesNotFindError_ForCompatibleMemberType(string memberType) - { - var source = $@" -public class TestClass {{ - public static {memberType} Data; + [Fact] + public async void TheoryData_DoesNotTrigger() + { + var source = @" +using System.Collections.Generic; +using Xunit; - [Xunit.MemberData(nameof(Data))] - public void TestMethod(int _) {{ }} -}}"; +public class TestClass { + public static TheoryData Data; - DiagnosticResult[] expected = - !memberType.Contains("TheoryData") - ? new DiagnosticResult[] { - Verify - .Diagnostic("xUnit1042") - .WithSpan(5, 6, 5, 36) - .WithSeverity(DiagnosticSeverity.Info) - } - : Array.Empty(); + [MemberData(nameof(Data))] + public void TestMethod(int _) { } +}"; - await Verify.VerifyAnalyzer(source, expected); - } + await Verify.VerifyAnalyzer(source); + } - [Fact] - public async void DoesNotFindError_ForITheoryDataRow_V3() - { - var source = @" + [Fact] + public async void ITheoryDataRow_DoesNotTrigger() + { + var source = @" using System.Collections.Generic; using Xunit; using Xunit.Sdk; @@ -437,231 +315,291 @@ public static List DataRowSource() => new TheoryDataRow(0, null) { Skip = ""Don't run this!"" }, }; }"; - var expected = - Verify - .Diagnostic("xUnit1042") - .WithSpan(9, 6, 9, 39) - .WithSeverity(DiagnosticSeverity.Info); - await Verify.VerifyAnalyzerV3(source, expected); + await Verify.VerifyAnalyzerV3(source); + } + + [Theory] + [InlineData("System.Collections.Generic.IEnumerable")] + [InlineData("object[]")] + [InlineData("object")] + [InlineData("System.Tuple")] + [InlineData("System.Tuple[]")] + public async void InvalidMemberType_Triggers(string memberType) + { + var source = $@" +public class TestClass {{ + public static {memberType} Data; + + [Xunit.MemberData(nameof(Data))] + public void TestMethod() {{ }} +}}"; + var expectedV2 = + Verify + .Diagnostic("xUnit1019") + .WithSpan(5, 6, 5, 36) + .WithSeverity(DiagnosticSeverity.Error) + .WithArguments("'System.Collections.Generic.IEnumerable'", memberType); + + await Verify.VerifyAnalyzerV2(source, expectedV2); + + var expectedV3 = + Verify + .Diagnostic("xUnit1019") + .WithSpan(5, 6, 5, 36) + .WithSeverity(DiagnosticSeverity.Error) + .WithArguments("'System.Collections.Generic.IEnumerable' or 'System.Collections.Generic.IEnumerable'", memberType); + + await Verify.VerifyAnalyzerV3(source, expectedV3); + } } - [Fact] - public async void FindsError_ForMemberPropertyWithoutGetter() + public class X1020_MemberDataPropertyMustHaveGetter { - var source = @" + [Fact] + public async void PropertyWithoutGetter_Triggers() + { + var source = @" public class TestClass { - public static System.Collections.Generic.IEnumerable Data { set { } } + public static Xunit.TheoryData Data { set { } } [Xunit.MemberData(nameof(Data))] - public void TestMethod() { } + public void TestMethod(int _) { } }"; - DiagnosticResult[] expected = - { - Verify - .Diagnostic("xUnit1020") - .WithSpan(5, 6, 5, 36) - .WithSeverity(DiagnosticSeverity.Error), - Verify - .Diagnostic("xUnit1042") - .WithSpan(5, 6, 5, 36) - .WithSeverity(DiagnosticSeverity.Info) - }; + var expected = + Verify + .Diagnostic("xUnit1020") + .WithSpan(5, 6, 5, 36) + .WithSeverity(DiagnosticSeverity.Error); - await Verify.VerifyAnalyzer(source, expected); - } + await Verify.VerifyAnalyzer(source, expected); + } - [Theory] - [InlineData("internal")] - [InlineData("protected")] - [InlineData("private")] - public async void FindsError_ForMemberPropertyWithNonPublicGetter(string visibility) - { - var source = $@" + [Theory] + [InlineData("internal")] + [InlineData("protected")] + [InlineData("private")] + public async void PropertyWithNonPublicGetter_Triggers(string visibility) + { + var source = $@" public class TestClass {{ - public static System.Collections.Generic.IEnumerable Data {{ {visibility} get {{ return null; }} set {{ }} }} + public static Xunit.TheoryData Data {{ {visibility} get {{ return null; }} set {{ }} }} [Xunit.MemberData(nameof(Data))] - public void TestMethod() {{ }} + public void TestMethod(int _) {{ }} }}"; - DiagnosticResult[] expected = - { - Verify - .Diagnostic("xUnit1020") - .WithSpan(5, 6, 5, 36) - .WithSeverity(DiagnosticSeverity.Error), - Verify - .Diagnostic("xUnit1042") - .WithSpan(5, 6, 5, 36) - .WithSeverity(DiagnosticSeverity.Info) - }; + var expected = + Verify + .Diagnostic("xUnit1020") + .WithSpan(5, 6, 5, 36) + .WithSeverity(DiagnosticSeverity.Error); - await Verify.VerifyAnalyzer(source, expected); + await Verify.VerifyAnalyzer(source, expected); + } } - [Theory] - [InlineData("'a', 123")] - [InlineData("new object[] {{ 'a', 123 }}")] - [InlineData("{0}: new object[] {{ 'a', 123 }}")] - public async void FindsWarning_ForMemberDataParametersForFieldMember(string paramsArgument) + public class X1021_MemberDataNonMethodShouldNotHaveParameters { - var sourceTemplate = @" + [Theory] + [InlineData("1")] // implicit params + [InlineData("new object[] { 1 }")] // explicit params + public async void MethodMemberWithParameters_DoesNotTrigger(string parameter) + { + var source = @$" public class TestClass {{ - public static System.Collections.Generic.IEnumerable Data; + private static void TestData() {{ }} - [Xunit.MemberData(nameof(Data), {0}, MemberType = typeof(TestClass))] - public void TestMethod() {{ }} + public static Xunit.TheoryData TestData(int n) => new Xunit.TheoryData {{ n }}; + + [Xunit.MemberData(nameof(TestData), {parameter})] + public void TestMethod(int n) {{ }} }}"; - var argV2 = string.Format(paramsArgument, "parameters"); - var sourceV2 = string.Format(sourceTemplate, argV2); - DiagnosticResult[] expectedV2 = + await Verify.VerifyAnalyzer(source); + } + + [Theory] + [InlineData("1, 2")] // implicit params + [InlineData("new object[] { 1, 2 }")] // explicit params + public async void MethodMemberWithParamsArrayParameters_DoesNotTrigger(string parameters) { - Verify - .Diagnostic("xUnit1042") - .WithSpan(5, 6, 5, 70 + argV2.Length) - .WithSeverity(DiagnosticSeverity.Info), - Verify - .Diagnostic("xUnit1021") - .WithSpan(5, 37, 5, 37 + argV2.Length) - .WithSeverity(DiagnosticSeverity.Warning) - }; + var source = @$" +public class TestClass {{ + public static Xunit.TheoryData TestData(params int[] n) => new Xunit.TheoryData {{ n[0] }}; - await Verify.VerifyAnalyzerV2(sourceV2, expectedV2); + [Xunit.MemberData(nameof(TestData), {parameters})] + public void TestMethod(int n) {{ }} +}}"; - var argV3 = string.Format(paramsArgument, "arguments"); - var sourceV3 = string.Format(sourceTemplate, argV3); - DiagnosticResult[] expectedV3 = + await Verify.VerifyAnalyzer(source); + } + + [Theory] + [InlineData("1")] // implicit params + [InlineData("new object[] { 1 }")] // explicit params + public async void MethodMemberOnBaseType_DoesNotTrigger(string parameter) { - Verify - .Diagnostic("xUnit1042") - .WithSpan(5, 6, 5, 70 + argV3.Length) - .WithSeverity(DiagnosticSeverity.Info), - Verify - .Diagnostic("xUnit1021") - .WithSpan(5, 37, 5, 37 + argV3.Length) - .WithSeverity(DiagnosticSeverity.Warning) - }; + var source = $@" +public class TestClassBase {{ + public static Xunit.TheoryData TestData(int n) => new Xunit.TheoryData {{ n }}; +}} - await Verify.VerifyAnalyzerV3(sourceV3, expectedV3); - } +public class TestClass : TestClassBase {{ + private static void TestData() {{ }} - [Theory] - [InlineData("'a', 123")] - [InlineData("new object[] {{ 'a', 123 }}")] - [InlineData("{0}: new object[] {{ 'a', 123 }}")] - public async void FindsWarning_ForMemberDataParametersForPropertyMember(string paramsArgument) - { - var sourceTemplate = @" + [Xunit.MemberData(nameof(TestData), {parameter})] + public void TestMethod(int n) {{ }} +}}"; + + await Verify.VerifyAnalyzer(source); + } + + [Theory] + [InlineData("'a', 123")] + [InlineData("new object[] {{ 'a', 123 }}")] + [InlineData("{0}: new object[] {{ 'a', 123 }}")] + public async void FieldMemberWithParameters_Triggers(string paramsArgument) + { + var sourceTemplate = @" public class TestClass {{ - public static System.Collections.Generic.IEnumerable Data {{ get; set; }} + public static Xunit.TheoryData Data; [Xunit.MemberData(nameof(Data), {0}, MemberType = typeof(TestClass))] - public void TestMethod() {{ }} + public void TestMethod(int _) {{ }} }}"; - var argV2 = string.Format(paramsArgument, "parameters"); - var sourceV2 = string.Format(sourceTemplate, argV2); - DiagnosticResult[] expectedV2 = - { - Verify - .Diagnostic("xUnit1042") - .WithSpan(5, 6, 5, 70 + argV2.Length) - .WithSeverity(DiagnosticSeverity.Info), - Verify - .Diagnostic("xUnit1021") - .WithSpan(5, 37, 5, 37 + argV2.Length) - .WithSeverity(DiagnosticSeverity.Warning) - }; - - await Verify.VerifyAnalyzerV2(sourceV2, expectedV2); + var argV2 = string.Format(paramsArgument, "parameters"); + var sourceV2 = string.Format(sourceTemplate, argV2); + var expectedV2 = + Verify + .Diagnostic("xUnit1021") + .WithSpan(5, 37, 5, 37 + argV2.Length) + .WithSeverity(DiagnosticSeverity.Warning); + + await Verify.VerifyAnalyzerV2(sourceV2, expectedV2); + + var argV3 = string.Format(paramsArgument, "arguments"); + var sourceV3 = string.Format(sourceTemplate, argV3); + var expectedV3 = + Verify + .Diagnostic("xUnit1021") + .WithSpan(5, 37, 5, 37 + argV3.Length) + .WithSeverity(DiagnosticSeverity.Warning); + + await Verify.VerifyAnalyzerV3(sourceV3, expectedV3); + } - var argV3 = string.Format(paramsArgument, "arguments"); - var sourceV3 = string.Format(sourceTemplate, argV3); - DiagnosticResult[] expectedV3 = + [Theory] + [InlineData("'a', 123")] + [InlineData("new object[] {{ 'a', 123 }}")] + [InlineData("{0}: new object[] {{ 'a', 123 }}")] + public async void PropertyMemberWithParameters_Triggers(string paramsArgument) { - Verify - .Diagnostic("xUnit1042") - .WithSpan(5, 6, 5, 70 + argV3.Length) - .WithSeverity(DiagnosticSeverity.Info), - Verify - .Diagnostic("xUnit1021") - .WithSpan(5, 37, 5, 37 + argV3.Length) - .WithSeverity(DiagnosticSeverity.Warning) - }; + var sourceTemplate = @" +public class TestClass {{ + public static Xunit.TheoryData Data {{ get; set; }} + + [Xunit.MemberData(nameof(Data), {0}, MemberType = typeof(TestClass))] + public void TestMethod(int _) {{ }} +}}"; - await Verify.VerifyAnalyzerV3(sourceV3, expectedV3); + var argV2 = string.Format(paramsArgument, "parameters"); + var sourceV2 = string.Format(sourceTemplate, argV2); + var expectedV2 = + Verify + .Diagnostic("xUnit1021") + .WithSpan(5, 37, 5, 37 + argV2.Length) + .WithSeverity(DiagnosticSeverity.Warning); + + await Verify.VerifyAnalyzerV2(sourceV2, expectedV2); + + var argV3 = string.Format(paramsArgument, "arguments"); + var sourceV3 = string.Format(sourceTemplate, argV3); + var expectedV3 = + Verify + .Diagnostic("xUnit1021") + .WithSpan(5, 37, 5, 37 + argV3.Length) + .WithSeverity(DiagnosticSeverity.Warning); + + await Verify.VerifyAnalyzerV3(sourceV3, expectedV3); + } } - [Fact] - public async void DoesNotFindWarning_ForMemberDataAttributeWithNamedParameter() + public class X1034_MemberDataArgumentsMustMatchMethodParameters_NullShouldNotBeUsedForIncompatibleParameter { - var source = @" -public class TestClass { - public static System.Collections.Generic.IEnumerable Data; - - [Xunit.MemberData(nameof(Data), MemberType = typeof(TestClass))] - public void TestMethod() { } -}"; + [Theory] + [InlineData("", "string")] + [InlineData("#nullable enable", "string?")] + public async void PassingNullForNullableReferenceType_DoesNotTrigger( + string header, + string argumentType) + { + var source = $@" +{header} +public class TestClass {{ + public static Xunit.TheoryData TestData({argumentType} f) => new Xunit.TheoryData {{ 42 }}; - var expected = - Verify - .Diagnostic("xUnit1042") - .WithSpan(5, 6, 5, 68) - .WithSeverity(DiagnosticSeverity.Info); + [Xunit.MemberData(nameof(TestData), new object[] {{ null }})] + public void TestMethod(int _) {{ }} +}}"; - await Verify.VerifyAnalyzer(source, expected); - } + await Verify.VerifyAnalyzer(LanguageVersion.CSharp8, source); + } - [Fact] - public async void DoesNotFindWarning_IfHasValidMember() - { - var source = @" + [Fact] + public async void PassingNullForStructType_Triggers() + { + var source = @" public class TestClass { - private static void TestData() { } + public static Xunit.TheoryData TestData(int n) => new Xunit.TheoryData { n }; - public static System.Collections.Generic.IEnumerable TestData(int n) { yield return new object[] { n }; } - - [Xunit.MemberData(nameof(TestData), new object[] { 1 })] - public void TestMethod(int n) { } + [Xunit.MemberData(nameof(TestData), new object[] { null })] + public void TestMethod(int _) { } }"; - var expected = - Verify - .Diagnostic("xUnit1042") - .WithSpan(7, 6, 7, 60) - .WithSeverity(DiagnosticSeverity.Info); + var expected = + Verify + .Diagnostic("xUnit1034") + .WithSpan(5, 56, 5, 60) + .WithSeverity(DiagnosticSeverity.Warning) + .WithArguments("n", "int"); - await Verify.VerifyAnalyzer(source, expected); - } + await Verify.VerifyAnalyzer(source, expected); + } - [Fact] - public async void DoesNotFindWarning_IfHasValidMemberWithParams() - { - var source = @" + [Fact] + public async void PassingNullForNonNullableReferenceType_Triggers() + { + var source = @" +#nullable enable public class TestClass { - public static System.Collections.Generic.IEnumerable TestData(params int[] n) { yield return new object[] { n[0] }; } + public static Xunit.TheoryData TestData(string f) => new Xunit.TheoryData { f }; - [Xunit.MemberData(nameof(TestData), new object[] { 1, 2 })] - public void TestMethod(int n) { } -}"; + [Xunit.MemberData(nameof(TestData), new object[] { null })] + public void TestMethod(string _) { } +} +#nullable restore"; - var expected = - Verify - .Diagnostic("xUnit1042") - .WithSpan(5, 6, 5, 63) - .WithSeverity(DiagnosticSeverity.Info); + var expected = + Verify + .Diagnostic("xUnit1034") + .WithSpan(6, 56, 6, 60) + .WithSeverity(DiagnosticSeverity.Warning) + .WithArguments("f", "string"); - await Verify.VerifyAnalyzer(source, expected); + await Verify.VerifyAnalyzer(LanguageVersion.CSharp8, source, expected); + } } - // https://github.com/xunit/xunit/issues/2817 - [Theory] - [InlineData("Foo.Bar")] - [InlineData("(Foo)42")] - public async void DoesNotFindWarning_IfEnumValueIsValid(string enumValue) + public class X1035_MemberDataArgumentsMustMatchMethodParameters_IncompatibleValueType { - var source = $@" + // https://github.com/xunit/xunit/issues/2817 + [Theory] + [InlineData("Foo.Bar")] + [InlineData("(Foo)42")] + public async void ValidEnumValue_DoesNotTrigger(string enumValue) + { + var source = $@" using System; using System.Collections.Generic; using Xunit; @@ -673,749 +611,651 @@ public void TestMethod(int _) {{ }} public enum Foo {{ Bar }} - public static IEnumerable SomeData(Foo foo) => Array.Empty(); + public static Xunit.TheoryData SomeData(Foo foo) => new Xunit.TheoryData(); }}"; - var expected = - Verify - .Diagnostic("xUnit1042") - .WithSpan(8, 6, 8, 43) - .WithSeverity(DiagnosticSeverity.Info); - - await Verify.VerifyAnalyzer(source, expected); - } + await Verify.VerifyAnalyzer(source); + } - [Fact] - public async void FindWarning_IfHasValidMemberWithIncorrectArgumentTypes() - { - var source = @" + [Fact] + public async void ValidMemberWithIncorrectArgumentTypes_Triggers() + { + var source = @" public class TestClass { - public static System.Collections.Generic.IEnumerable TestData(string n) { yield return new object[] { n }; } + public static Xunit.TheoryData TestData(string n) => new Xunit.TheoryData { n.Length }; [Xunit.MemberData(nameof(TestData), new object[] { 1 })] public void TestMethod(int n) { } }"; - DiagnosticResult[] expected = - { - Verify - .Diagnostic("xUnit1042") - .WithSpan(5, 6, 5, 60) - .WithSeverity(DiagnosticSeverity.Info), - Verify - .Diagnostic("xUnit1035") - .WithSpan(5, 56, 5, 57) - .WithArguments("n", "string") - }; + var expected = + Verify + .Diagnostic("xUnit1035") + .WithSpan(5, 56, 5, 57) + .WithArguments("n", "string"); - await Verify.VerifyAnalyzer(source, expected); - } + await Verify.VerifyAnalyzer(source, expected); + } - [Fact] - public async void FindWarning_IfHasValidMemberWithIncorrectArgumentTypesParams() - { - var source = @" + [Fact] + public async void ValidMemberWithIncorrectArgumentTypesParams_Triggers() + { + var source = @" public class TestClass { - public static System.Collections.Generic.IEnumerable TestData(params int[] n) { yield return new object[] { n }; } + public static Xunit.TheoryData TestData(params int[] n) => new Xunit.TheoryData { n[0] }; [Xunit.MemberData(nameof(TestData), new object[] { 1, ""bob"" })] public void TestMethod(int n) { } }"; - DiagnosticResult[] expected = - { - Verify - .Diagnostic("xUnit1042") - .WithSpan(5, 6, 5, 67) - .WithSeverity(DiagnosticSeverity.Info), - Verify - .Diagnostic("xUnit1035") - .WithSpan(5, 59, 5, 64) - .WithArguments("n", "int") - }; + var expected = + Verify + .Diagnostic("xUnit1035") + .WithSpan(5, 59, 5, 64) + .WithArguments("n", "int"); - await Verify.VerifyAnalyzer(source, expected); + await Verify.VerifyAnalyzer(source, expected); + } } - [Fact] - public async void FindWarning_IfHasValidMemberWithIncorrectArgumentCount() + public class X1036_MemberDataArgumentsMustMatchMethodParameters_ExtraValue { - var source = @" -public class TestClass { - public static System.Collections.Generic.IEnumerable TestData(int n) { yield return new object[] { n }; } - - [Xunit.MemberData(nameof(TestData), new object[] { 1, 2 })] - public void TestMethod(int n) { } -}"; - - DiagnosticResult[] expected = + [Theory] + [InlineData("1")] + [InlineData("new object[] { 1 }")] + public async void ValidArgumentCount_DoesNotTrigger(string parameter) { - Verify - .Diagnostic("xUnit1042") - .WithSpan(5, 6, 5, 63) - .WithSeverity(DiagnosticSeverity.Info), - Verify - .Diagnostic("xUnit1036") - .WithSpan(5, 59, 5, 60) - .WithArguments("2") - }; + var source = $@" +public class TestClass {{ + private static void TestData() {{ }} - await Verify.VerifyAnalyzer(source, expected); - } + public static Xunit.TheoryData TestData(int n) => new Xunit.TheoryData {{ n }}; - [Fact] - public async void FindWarning_IfHasValidMemberWithIncorrectParamsArgumentCount() - { - var source = @" -public class TestClass { - public static System.Collections.Generic.IEnumerable TestData(int n) { yield return new object[] { n }; } + [Xunit.MemberData(nameof(TestData), {parameter})] + public void TestMethod(int n) {{ }} +}}"; - [Xunit.MemberData(nameof(TestData), 1, 2)] - public void TestMethod(int n) { } -}"; + await Verify.VerifyAnalyzer(source); + } - DiagnosticResult[] expected = + [Theory] + [InlineData("1")] + [InlineData("new object[] { 1 }")] + public async void ValidArgumentCount_InNullableContext_DoesNotTrigger(string parameter) { - Verify - .Diagnostic("xUnit1042") - .WithSpan(5, 6, 5, 46) - .WithSeverity(DiagnosticSeverity.Info), - Verify - .Diagnostic("xUnit1036") - .WithSpan(5, 44, 5, 45) - .WithArguments("2") - }; - - await Verify.VerifyAnalyzer(source, expected); - } - - [Fact] - public async void DoesNotFindWarning_IfHasValidListMember() - { - var source = @" -public class TestClass { - private static void TestData() { } - - public static System.Collections.Generic.List TestData(int n) { return new System.Collections.Generic.List { new object[] { n } }; } - - [Xunit.MemberData(nameof(TestData), new object[] { 1 })] - public void TestMethod(int n) { } -}"; - - var expected = - Verify - .Diagnostic("xUnit1042") - .WithSpan(7, 6, 7, 60) - .WithSeverity(DiagnosticSeverity.Info); - - await Verify.VerifyAnalyzer(source, expected); - } - - [Fact] - public async void DoesNotFindWarning_IfHasValidNonNullableListMember_InNullableContext() - { - var source = @" + var source = $@" #nullable enable -public class TestClass { - public static System.Collections.Generic.List TestData(int n) { return new System.Collections.Generic.List { new object[] { n } }; } +public class TestClass {{ + public static Xunit.TheoryData TestData(int n) => new Xunit.TheoryData {{ n }}; - [Xunit.MemberData(nameof(TestData), new object[] { 1 })] - public void TestMethod(int n) { } -} + [Xunit.MemberData(nameof(TestData), {parameter})] + public void TestMethod(int n) {{ }} +}} #nullable restore"; - var expected = - Verify - .Diagnostic("xUnit1042") - .WithSpan(6, 6, 6, 60) - .WithSeverity(DiagnosticSeverity.Info); - - await Verify.VerifyAnalyzer(LanguageVersion.CSharp8, source, expected); - } + await Verify.VerifyAnalyzer(LanguageVersion.CSharp8, source); + } - [Fact] - public async void FindWarning_IfPassingNullToNonNullableMethodParameter_InNullableContext() - { - var source = @$" -#nullable enable + [Theory] + [InlineData("1, 2", 44)] + [InlineData("new object[] { 1, 2 }", 59)] + public async void TooManyArguments_Triggers( + string parameters, + int startColumn) + { + var source = $@" public class TestClass {{ - public static System.Collections.Generic.List TestData(int n, string f) {{ return new System.Collections.Generic.List {{ new object[] {{ f }} }}; }} + public static Xunit.TheoryData TestData(int n) => new Xunit.TheoryData {{ n }}; - [Xunit.MemberData(nameof(TestData), new object[] {{ null, null }})] - public void TestMethod(string n) {{ }} -}} -#nullable restore"; + [Xunit.MemberData(nameof(TestData), {parameters})] + public void TestMethod(int n) {{ }} +}}"; - DiagnosticResult[] expected = - { - Verify - .Diagnostic("xUnit1042") - .WithSpan(6, 6, 6, 69) - .WithSeverity(DiagnosticSeverity.Info), - Verify - .Diagnostic("xUnit1034") - .WithSpan(6, 56, 6, 60) - .WithSeverity(DiagnosticSeverity.Warning) - .WithArguments("n", "int"), - Verify - .Diagnostic("xUnit1034") - .WithSpan(6, 62, 6, 66) - .WithSeverity(DiagnosticSeverity.Warning) - .WithArguments("f", "string"), - }; + var expected = + Verify + .Diagnostic("xUnit1036") + .WithSpan(5, startColumn, 5, startColumn + 1) + .WithArguments("2"); - await Verify.VerifyAnalyzer(LanguageVersion.CSharp8, source, expected); + await Verify.VerifyAnalyzer(source, expected); + } } - [Fact] - public async void DoesNotFindWarning_IfHasValidMemberInBaseClass() + public class X1037_MemberDataTheoryDataTypeArgumentsMustMatchTestMethodParameters_TooFewTypeParameters { - var source = @" -public class TestClassBase { - public static System.Collections.Generic.IEnumerable TestData(int n) { - yield return new object[] { n }; - } -} + public static TheoryData MemberSyntaxAndArgs = new() + { + { " = ", "" }, // Field + { " => ", "" }, // Property + { "() => ", "" }, // Method w/o args + { "(int n) => ", ", 42" }, // Method w/ args + }; -public class TestClass : TestClassBase { - private static void TestData() { } + [Theory] + [MemberData(nameof(MemberSyntaxAndArgs))] + public async void ValidTheoryDataMemberWithNotEnoughTypeParameters_Triggers( + string memberSyntax, + string memberArgs) + { + var source = $@" +public class TestClass {{ + public static Xunit.TheoryData TestData{memberSyntax}new Xunit.TheoryData(); - [Xunit.MemberData(nameof(TestData), new object[] { 1 })] - public void TestMethod(int n) { } -}"; + [Xunit.MemberData(nameof(TestData){memberArgs})] + public void TestMethod(int n, string f) {{ }} +}}"; - var expected = - Verify - .Diagnostic("xUnit1042") - .WithSpan(11, 6, 11, 60) - .WithSeverity(DiagnosticSeverity.Info); + var expected = + Verify + .Diagnostic("xUnit1037") + .WithSpan(5, 6, 5, 40 + memberArgs.Length) + .WithSeverity(DiagnosticSeverity.Error); - await Verify.VerifyAnalyzer(source, expected); - } + await Verify.VerifyAnalyzer(source, expected); + } - // Tests related to TheoryData<> usage + [Theory] + [MemberData(nameof(MemberSyntaxAndArgs))] + public async void ValidSubclassedTheoryDataMemberWithNotEnoughTypeParameters_Triggers( + string memberSyntax, + string memberArgs) + { + var source = $@" +using Xunit; + +public class DerivedTheoryData : TheoryData {{ }} - public static TheoryData MemberSyntaxAndArgs = new() - { - { " = ", "" }, // Field - { " => ", "" }, // Property - { "() => ", "" }, // Method w/o args - { "(int n) => ", ", 42" }, // Method w/ args - }; - - [Theory] - [MemberData(nameof(MemberSyntaxAndArgs))] - public async void DoesNotFindWarning_IfHasValidTheoryDataMember( - string memberSyntax, - string memberArgs) - { - var source = $@" public class TestClass {{ - public static Xunit.TheoryData TestData{memberSyntax}new(); + public static DerivedTheoryData TestData{memberSyntax}new DerivedTheoryData(); [Xunit.MemberData(nameof(TestData){memberArgs})] - public void TestMethod(int n) {{ }} + public void TestMethod(int n, string f) {{ }} }}"; - await Verify.VerifyAnalyzer(LanguageVersion.CSharp10, source); + var expected = + Verify + .Diagnostic("xUnit1037") + .WithSpan(9, 6, 9, 40 + memberArgs.Length) + .WithSeverity(DiagnosticSeverity.Error); + + await Verify.VerifyAnalyzer(source, expected); + } } - [Theory] - [MemberData(nameof(MemberSyntaxAndArgs))] - public async void DoesNotFindWarning_IfHasValidTheoryDataMemberWithOptionalParameters( - string memberSyntax, - string memberArgs) + public class X1038_MemberDataTheoryDataTypeArgumentsMustMatchTestMethodParameters_ExtraTypeParameters { - var source = $@" -public class TestClass {{ - public static Xunit.TheoryData TestData{memberSyntax}new(); + public static MatrixTheoryData<(string syntax, string args), string> MemberSyntaxAndArgs_WithTheoryDataType(string theoryDataTypes) => + new( + new[] + { + ( " = ", "" ), // Field + ( " => ", "" ), // Property + ( "() => ", "" ), // Method w/o args + ( "(int n) => ", ", 42" ), // Method w/ args + }, + new[] + { + $"TheoryData<{theoryDataTypes}>", + "DerivedTheoryData", + $"DerivedTheoryData<{theoryDataTypes}>" + } + ); - [Xunit.MemberData(nameof(TestData){memberArgs})] - public void TestMethod(int n, int a = 0) {{ }} -}}"; + [Theory] + [MemberData(nameof(MemberSyntaxAndArgs_WithTheoryDataType), "int", DisableDiscoveryEnumeration = true)] + public async void ValidTheoryData_DoesNotTrigger( + (string syntax, string args) member, + string theoryDataType) + { + var source = $@" +using Xunit; - await Verify.VerifyAnalyzer(LanguageVersion.CSharp10, source); - } +public class DerivedTheoryData : TheoryData {{ }} +public class DerivedTheoryData : TheoryData {{ }} - [Theory] - [MemberData(nameof(MemberSyntaxAndArgs))] - public async void DoesNotFindWarning_IfHasValidTheoryDataMemberWithNeededParams( - string memberSyntax, - string memberArgs) - { - var source = $@" public class TestClass {{ - public static Xunit.TheoryData TestData{memberSyntax}new(); + public static {theoryDataType} TestData{member.syntax}new {theoryDataType}(); - [Xunit.MemberData(nameof(TestData){memberArgs})] - public void TestMethod(int n, params int[] a) {{ }} + [MemberData(nameof(TestData){member.args})] + public void TestMethod(int n) {{ }} }}"; - await Verify.VerifyAnalyzer(LanguageVersion.CSharp10, source); - } + await Verify.VerifyAnalyzer(source); + } + + [Theory] + [MemberData(nameof(MemberSyntaxAndArgs_WithTheoryDataType), "int", DisableDiscoveryEnumeration = true)] + public async void ValidTheoryDataWithOptionalParameters_DoesNotTrigger( + (string syntax, string args) member, + string theoryDataType) + { + var source = $@" +using Xunit; + +public class DerivedTheoryData : TheoryData {{ }} +public class DerivedTheoryData : TheoryData {{ }} - [Theory] - [MemberData(nameof(MemberSyntaxAndArgs))] - public async void DoesNotFindWarning_IfHasValidTheoryDataMemberWithExtraParams( - string memberSyntax, - string memberArgs) - { - var source = $@" public class TestClass {{ - public static Xunit.TheoryData TestData{memberSyntax}new(); + public static {theoryDataType} TestData{member.syntax}new {theoryDataType}(); - [Xunit.MemberData(nameof(TestData){memberArgs})] - public void TestMethod(int n, params int[] a) {{ }} + [MemberData(nameof(TestData){member.args})] + public void TestMethod(int n, int a = 0) {{ }} }}"; - await Verify.VerifyAnalyzer(LanguageVersion.CSharp10, source); - } + await Verify.VerifyAnalyzer(source); + } + + [Theory] + [MemberData(nameof(MemberSyntaxAndArgs_WithTheoryDataType), "int", DisableDiscoveryEnumeration = true)] + public async void ValidTheoryDataWithNoValuesForParamsArray_DoesNotTrigger( + (string syntax, string args) member, + string theoryDataType) + { + var source = $@" +using Xunit; + +public class DerivedTheoryData : TheoryData {{ }} +public class DerivedTheoryData : TheoryData {{ }} - [Theory] - [MemberData(nameof(MemberSyntaxAndArgs))] - public async void DoesNotFindWarning_WithGenericArgument( - string memberSyntax, - string memberArgs) - { - var source = $@" public class TestClass {{ - public static Xunit.TheoryData TestData{memberSyntax}new(); + public static {theoryDataType} TestData{member.syntax}new {theoryDataType}(); - [Xunit.MemberData(nameof(TestData){memberArgs})] - public void TestMethod(T n) {{ }} + [Xunit.MemberData(nameof(TestData){member.args})] + public void TestMethod(int n, params int[] a) {{ }} }}"; - await Verify.VerifyAnalyzer(LanguageVersion.CSharp10, source); - } + await Verify.VerifyAnalyzer(source); + } - [Theory] - [MemberData(nameof(MemberSyntaxAndArgs))] - public async void DoesNotFindWarning_WithGenericNullableArgument( - string memberSyntax, - string memberArgs) - { - var source = $@" -#nullable enable + [Theory] + [MemberData(nameof(MemberSyntaxAndArgs_WithTheoryDataType), "int, int", DisableDiscoveryEnumeration = true)] + public async void ValidTheoryDataWithSingleValueForParamsArray_DoesNotTrigger( + (string syntax, string args) member, + string theoryDataType) + { + var source = $@" +using Xunit; + +public class DerivedTheoryData : TheoryData {{ }} +public class DerivedTheoryData : TheoryData {{ }} public class TestClass {{ - public static Xunit.TheoryData TestData{memberSyntax}new(); + public static {theoryDataType} TestData{member.syntax}new {theoryDataType}(); - [Xunit.MemberData(nameof(TestData){memberArgs})] - public void TestMethod(T? n) {{ }} + [MemberData(nameof(TestData){member.args})] + public void TestMethod(int n, params int[] a) {{ }} }}"; - await Verify.VerifyAnalyzer(LanguageVersion.CSharp10, source); - } + await Verify.VerifyAnalyzer(source); + } - [Theory] - [MemberData(nameof(MemberSyntaxAndArgs))] - public async void DoesNotFindWarning_IfHasValidTheoryDataSubclassMember( - string memberSyntax, - string memberArgs) - { - var source = $@" + [Theory] + [MemberData(nameof(MemberSyntaxAndArgs_WithTheoryDataType), "int", DisableDiscoveryEnumeration = true)] + public async void ValidTheoryDataWithGenericTestParameter_DoesNotTrigger( + (string syntax, string args) member, + string theoryDataType) + { + var source = $@" using Xunit; public class DerivedTheoryData : TheoryData {{ }} +public class DerivedTheoryData : TheoryData {{ }} public class TestClass {{ - public static DerivedTheoryData TestData{memberSyntax}new(); + public static {theoryDataType} TestData{member.syntax}new {theoryDataType}(); - [MemberData(nameof(TestData){memberArgs})] - public void TestMethod(int n) {{ }} + [MemberData(nameof(TestData){member.args})] + public void TestMethod(T n) {{ }} }}"; - await Verify.VerifyAnalyzer(LanguageVersion.CSharp10, source); - } + await Verify.VerifyAnalyzer(source); + } + + [Theory] + [MemberData(nameof(MemberSyntaxAndArgs_WithTheoryDataType), "int", DisableDiscoveryEnumeration = true)] + public async void ValidTheoryDataWithNullableGenericTestParameter_DoesNotTrigger( + (string syntax, string args) member, + string theoryDataType) + { + var source = $@" +#nullable enable - [Theory] - [MemberData(nameof(MemberSyntaxAndArgs))] - public async void DoesNotFindWarning_IfHasValidTheoryDataGenericSubclassMember( - string memberSyntax, - string memberArgs) - { - var source = $@" using Xunit; +public class DerivedTheoryData : TheoryData {{ }} public class DerivedTheoryData : TheoryData {{ }} public class TestClass {{ - public static DerivedTheoryData TestData{memberSyntax}new(); + public static {theoryDataType} TestData{member.syntax}new {theoryDataType}(); - [MemberData(nameof(TestData){memberArgs})] - public void TestMethod(int n) {{ }} + [Xunit.MemberData(nameof(TestData){member.args})] + public void TestMethod(T? n) {{ }} }}"; - await Verify.VerifyAnalyzer(LanguageVersion.CSharp10, source); - } + await Verify.VerifyAnalyzer(LanguageVersion.CSharp9, source); + } - [Theory] - [MemberData(nameof(MemberSyntaxAndArgs))] - public async void DoesNotFindWarning_IfHasValidTheoryDataDoubleGenericSubclassMember( - string memberSyntax, - string memberArgs) - { - var source = $@" + [Theory] + [InlineData(" = ", "")] // Field + [InlineData(" => ", "")] // Property + [InlineData("() => ", "")] // Method w/o args + [InlineData("(int n) => ", ", 42")] // Method w/ args + public async void ValidTheoryDataDoubleGenericSubclassMember_DoesNotTrigger( + string memberSyntax, + string memberArgs) + { + var source = $@" using Xunit; public class DerivedTheoryData : TheoryData {{ }} public class TestClass {{ - public static DerivedTheoryData TestData{memberSyntax}new(); + public static DerivedTheoryData TestData{memberSyntax}new DerivedTheoryData(); [MemberData(nameof(TestData){memberArgs})] public void TestMethod(int n) {{ }} }}"; - await Verify.VerifyAnalyzer(LanguageVersion.CSharp10, source); - } + await Verify.VerifyAnalyzer(source); + } - [Fact] - public async void DoesNotFindWarning_WithIntArrayArguments() - { - var source = $@" + [Fact] + public async void WithIntArrayArguments_DoesNotTrigger() + { + var source = @" using System.Collections.Generic; using Xunit; -public class TestClass -{{ - public static IEnumerable GetSequences(IEnumerable seq) => - new List(); - - [Theory] - [MemberData(nameof(GetSequences), new int[] {{ 1, 2 }})] - [MemberData(nameof(GetSequences), new [] {{ 3, 4, 5}})] - public void Test(IEnumerable seq) - {{ - Assert.NotEmpty(seq); - }} -}}"; - - DiagnosticResult[] expected = - { - Verify - .Diagnostic("xUnit1042") - .WithSpan(11, 4, 11, 56) - .WithSeverity(DiagnosticSeverity.Info), - Verify - .Diagnostic("xUnit1042") - .WithSpan(12, 4, 12, 55) - .WithSeverity(DiagnosticSeverity.Info) - }; - - await Verify.VerifyAnalyzer(LanguageVersion.CSharp10, source, expected); - } +public class TestClass { + public static TheoryData> GetSequences(IEnumerable seq) => new TheoryData> { seq }; - [Fact] - public async void FindsWarning_WithObjectArrayArguments() - { - var source = $@" -using System.Collections.Generic; -using Xunit; + [Theory] + [MemberData(nameof(GetSequences), new[] { 1, 2 })] + [MemberData(nameof(GetSequences), new[] { 3, 4, 5 })] + public void Test(IEnumerable seq) { + Assert.NotEmpty(seq); + } +}"; -public class TestClass -{{ - public static IEnumerable GetSequences(IEnumerable seq) => - new List(); - - [Theory] - [MemberData(nameof(GetSequences), new object[] {{ 1, 2 }})] - public void Test(IEnumerable seq) - {{ - Assert.NotEmpty(seq); - }} -}}"; + await Verify.VerifyAnalyzer(source); + } - DiagnosticResult[] expected = + [Theory] + [MemberData(nameof(MemberSyntaxAndArgs_WithTheoryDataType), "int, string", DisableDiscoveryEnumeration = true)] + public async void ValidSubclassTheoryDataMemberWithTooManyTypeParameters_Triggers( + (string syntax, string args) member, + string theoryDataType) { - Verify - .Diagnostic("xUnit1042") - .WithSpan(11, 4, 11, 59) - .WithSeverity(DiagnosticSeverity.Info), - Verify - .Diagnostic("xUnit1035") - .WithSpan(11, 52, 11, 53) - .WithArguments("seq", "System.Collections.Generic.IEnumerable") - .WithSeverity(DiagnosticSeverity.Error), - Verify - .Diagnostic("xUnit1036") - .WithSpan(11, 55, 11, 56) - .WithArguments("2") - .WithSeverity(DiagnosticSeverity.Error) - }; + var source = $@" +using Xunit; - await Verify.VerifyAnalyzer(LanguageVersion.CSharp10, source, expected); - } +public class DerivedTheoryData : TheoryData {{ }} +public class DerivedTheoryData : TheoryData {{ }} - [Theory] - [MemberData(nameof(MemberSyntaxAndArgs))] - public async void FindWarning_IfHasValidTheoryDataMemberWithTooManyTypeParameters( - string memberSyntax, - string memberArgs) - { - var source = $@" public class TestClass {{ - public static Xunit.TheoryData TestData{memberSyntax}new(); + public static {theoryDataType} TestData{member.syntax}new {theoryDataType}(); - [Xunit.MemberData(nameof(TestData){memberArgs})] + [MemberData(nameof(TestData){member.args})] public void TestMethod(int n) {{ }} }}"; - var expected = - Verify - .Diagnostic("xUnit1038") - .WithSpan(5, 6, 5, 40 + memberArgs.Length) - .WithSeverity(DiagnosticSeverity.Error); + var expected = + Verify + .Diagnostic("xUnit1038") + .WithSpan(10, 6, 10, 34 + member.args.Length) + .WithSeverity(DiagnosticSeverity.Error); - await Verify.VerifyAnalyzer(LanguageVersion.CSharp10, source, expected); - } + await Verify.VerifyAnalyzer(source, expected); + } - [Theory] - [MemberData(nameof(MemberSyntaxAndArgs))] - public async void FindWarning_IfHasValidSubclassTheoryDataMemberWithTooManyTypeParameters( - string memberSyntax, - string memberArgs) - { - var source = $@" + [Theory] + [MemberData(nameof(MemberSyntaxAndArgs_WithTheoryDataType), "int, string[], string", DisableDiscoveryEnumeration = true)] + public async void ExtraTypeExistsPastArrayForParamsArray_Triggers( + (string syntax, string args) member, + string theoryDataType) + { + var source = $@" using Xunit; -public class DerivedTheoryData : TheoryData {{ }} +public class DerivedTheoryData : TheoryData {{ }} +public class DerivedTheoryData : TheoryData {{ }} public class TestClass {{ - public static DerivedTheoryData TestData{memberSyntax}new(); + public static {theoryDataType} TestData{member.syntax}new {theoryDataType}(); - [MemberData(nameof(TestData){memberArgs})] - public void TestMethod(int n) {{ }} + [MemberData(nameof(TestData){member.args})] + public void PuzzleOne(int _1, params string[] _2) {{ }} }}"; - var expected = - Verify - .Diagnostic("xUnit1038") - .WithSpan(9, 6, 9, 34 + memberArgs.Length) - .WithSeverity(DiagnosticSeverity.Error); + var expected = + Verify + .Diagnostic("xUnit1038") + .WithSpan(10, 6, 10, 34 + member.args.Length) + .WithSeverity(DiagnosticSeverity.Error); - await Verify.VerifyAnalyzer(LanguageVersion.CSharp10, source, expected); + await Verify.VerifyAnalyzer(source, expected); + } } - [Fact] - public async void FindWarning_WhenExtraTypeExistsPastArrayForParamsArray() + public class X1039_MemberDataTheoryDataTypeArgumentsMustMatchTestMethodParameters_IncompatibleTypes { - var source = @" + public static MatrixTheoryData<(string syntax, string args), string> TypeWithMemberSyntaxAndArgs = + new( + new[] + { + ( " = ", "" ), // Field + ( " => ", "" ), // Property + ( "() => ", "" ), // Method w/o args + ( "(int n) => ", ", 42" ), // Method w/ args + }, + new[] + { + "int", + "System.Exception", + } + ); + + [Fact] + public async void DoesNotFindWarning_WhenPassingMultipleValuesForParamsArray() + { + var source = @" using Xunit; public class TestClass { - public static TheoryData TestData = new TheoryData(); + public static TheoryData TestData = new TheoryData(); [MemberData(nameof(TestData))] public void PuzzleOne(int _1, params string[] _2) { } }"; - var expected = - Verify - .Diagnostic("xUnit1038") - .WithSpan(7, 6, 7, 34) - .WithSeverity(DiagnosticSeverity.Error); - - await Verify.VerifyAnalyzer(source, expected); - } + await Verify.VerifyAnalyzer(source); + } - [Fact] - public async void FindWarning_WhenSubclassedExtraTypeExistsPastArrayForParamsArray() - { - var source = @" + [Fact] + public async void DoesNotFindWarning_WhenPassingArrayForParamsArray() + { + var source = @" using Xunit; -public class DerivedTheoryData : TheoryData { } - public class TestClass { - public static DerivedTheoryData TestData = new DerivedTheoryData(); + public static TheoryData TestData = new TheoryData(); [MemberData(nameof(TestData))] public void PuzzleOne(int _1, params string[] _2) { } }"; - var expected = - Verify - .Diagnostic("xUnit1038") - .WithSpan(9, 6, 9, 34) - .WithSeverity(DiagnosticSeverity.Error); + await Verify.VerifyAnalyzer(source); + } - await Verify.VerifyAnalyzer(source, expected); - } + [Fact] + public async void FindWarning_WithExtraValueNotCompatibleWithParamsArray() + { + var source = @" +using Xunit; - [Theory] - [MemberData(nameof(MemberSyntaxAndArgs))] - public async void FindWarning_IfHasValidTheoryDataMemberWithNotEnoughTypeParameters( - string memberSyntax, - string memberArgs) - { - var source = $@" -public class TestClass {{ - public static Xunit.TheoryData TestData{memberSyntax}new(); +public class TestClass { + public static TheoryData TestData = new TheoryData(); - [Xunit.MemberData(nameof(TestData){memberArgs})] - public void TestMethod(int n, string f) {{ }} -}}"; + [MemberData(nameof(TestData))] + public void PuzzleOne(int _1, params string[] _2) { } +}"; - var expected = - Verify - .Diagnostic("xUnit1037") - .WithSpan(5, 6, 5, 40 + memberArgs.Length) - .WithSeverity(DiagnosticSeverity.Error); + var expected = + Verify + .Diagnostic("xUnit1039") + .WithSpan(8, 42, 8, 50) + .WithSeverity(DiagnosticSeverity.Error) + .WithArguments("int", "TestClass", "TestData", "_2"); - await Verify.VerifyAnalyzer(LanguageVersion.CSharp10, source, expected); - } + await Verify.VerifyAnalyzer(source, expected); + } - [Theory] - [MemberData(nameof(MemberSyntaxAndArgs))] - public async void FindWarning_IfHasValidSubclassedTheoryDataMemberWithNotEnoughTypeParameters( - string memberSyntax, - string memberArgs) - { - var source = $@" + [Theory] + [MemberData(nameof(TypeWithMemberSyntaxAndArgs), DisableDiscoveryEnumeration = true)] + public async void FindWarning_IfHasValidTheoryDataMemberWithIncompatibleTypeParameters( + (string syntax, string args) member, + string type) + { + var source = $@" using Xunit; -public class DerivedTheoryData : TheoryData {{ }} - public class TestClass {{ - public static DerivedTheoryData TestData{memberSyntax}new(); + public static TheoryData<{type}> TestData{member.syntax}new TheoryData<{type}>(); - [Xunit.MemberData(nameof(TestData){memberArgs})] - public void TestMethod(int n, string f) {{ }} + [MemberData(nameof(TestData){member.args})] + public void TestMethod(string f) {{ }} }}"; - var expected = - Verify - .Diagnostic("xUnit1037") - .WithSpan(9, 6, 9, 40 + memberArgs.Length) - .WithSeverity(DiagnosticSeverity.Error); + var expected = + Verify + .Diagnostic("xUnit1039") + .WithSpan(8, 28, 8, 34) + .WithSeverity(DiagnosticSeverity.Error) + .WithArguments(type, "TestClass", "TestData", "f"); - await Verify.VerifyAnalyzer(LanguageVersion.CSharp10, source, expected); + await Verify.VerifyAnalyzer(source, expected); + } } - public static TheoryData TypeWithMemberSyntaxAndArgs() + public class X1040_MemberDataTheoryDataTypeArgumentsMustMatchTestMethodParameters_IncompatibleNullability { - var result = new TheoryData(); - - foreach (var items in MemberSyntaxAndArgs) + public static TheoryData MemberSyntaxAndArgs = new() { - result.Add("int", (string)items[0], (string)items[1]); - result.Add("System.Exception", (string)items[0], (string)items[1]); - } + { " = ", "" }, // Field + { " => ", "" }, // Property + { "() => ", "" }, // Method w/o args + { "(int n) => ", ", 42" }, // Method w/ args + }; - return result; - } + [Theory] + [MemberData(nameof(MemberSyntaxAndArgs))] + public async void ValidTheoryDataMemberWithMismatchedNullability_Triggers( + string memberSyntax, + string memberArgs) + { + var source = $@" +#nullable enable - [Fact] - public async void DoesNotFindWarning_WhenPassingMultipleValuesForParamsArray() - { - var source = @" using Xunit; -public class TestClass { - public static TheoryData TestData = new TheoryData(); +public class TestClass {{ + public static TheoryData TestData{memberSyntax}new TheoryData(); - [MemberData(nameof(TestData))] - public void PuzzleOne(int _1, params string[] _2) { } -}"; + [MemberData(nameof(TestData){memberArgs})] + public void TestMethod(string f) {{ }} +}}"; + + var expected = + Verify + .Diagnostic("xUnit1040") + .WithSpan(10, 28, 10, 34) + .WithSeverity(DiagnosticSeverity.Warning) + .WithArguments("string?", "TestClass", "TestData", "f"); - await Verify.VerifyAnalyzer(source); + await Verify.VerifyAnalyzer(LanguageVersion.CSharp8, source, expected); + } } - [Fact] - public async void DoesNotFindWarning_WhenPassingArrayForParamsArray() + public class X1042_MemberDataTheoryDataIsRecommendedForStronglyTypedAnalysis { - var source = @" + [Fact] + public async void TheoryData_DoesNotTrigger() + { + var source = @" +using System.Collections.Generic; using Xunit; public class TestClass { - public static TheoryData TestData = new TheoryData(); + public static TheoryData Data; - [MemberData(nameof(TestData))] - public void PuzzleOne(int _1, params string[] _2) { } + [MemberData(nameof(Data))] + public void TestMethod(int _) { } }"; - await Verify.VerifyAnalyzer(source); - } + await Verify.VerifyAnalyzer(source); + } - [Fact] - public async void FindWarning_WithExtraValueNotCompatibleWithParamsArray() - { - var source = @" + [Fact] + public async void MatrixTheoryData_DoesNotTrigger() + { + var source = @" +using System.Collections.Generic; using Xunit; public class TestClass { - public static TheoryData TestData = new TheoryData(); + public static MatrixTheoryData Data; - [MemberData(nameof(TestData))] - public void PuzzleOne(int _1, params string[] _2) { } + [MemberData(nameof(Data))] + public void TestMethod(int _1, string _2) { } }"; - var expected = - Verify - .Diagnostic("xUnit1039") - .WithSpan(8, 42, 8, 50) - .WithSeverity(DiagnosticSeverity.Error) - .WithArguments("int", "TestClass", "TestData", "_2"); + await Verify.VerifyAnalyzerV3(source); + } - await Verify.VerifyAnalyzer(source, expected); - } + [Theory] + [InlineData("IEnumerable")] + [InlineData("List")] + [InlineData("ITheoryDataRow[]")] + public async void TheoryDataRow_DoesNotTrigger(string memberType) + { + var source = $@" +using System.Collections.Generic; +using Xunit; +using Xunit.v3; - [Theory] - [MemberData(nameof(TypeWithMemberSyntaxAndArgs))] - public async void FindWarning_IfHasValidTheoryDataMemberWithIncompatibleTypeParameters( - string type, - string memberSyntax, - string memberArgs) - { - var source = @$" public class TestClass {{ - public static Xunit.TheoryData<{type}> TestData{memberSyntax}new(); + public static {memberType} Data; - [Xunit.MemberData(nameof(TestData){memberArgs})] - public void TestMethod(string f) {{ }} + [MemberData(nameof(Data))] + public void TestMethod(int _) {{ }} }}"; - var expected = - Verify - .Diagnostic("xUnit1039") - .WithSpan(6, 28, 6, 34) - .WithSeverity(DiagnosticSeverity.Error) - .WithArguments(type, "TestClass", "TestData", "f"); + await Verify.VerifyAnalyzerV3(source); + } - await Verify.VerifyAnalyzer(LanguageVersion.CSharp10, source, expected); - } + [Theory] + [InlineData("IEnumerable")] + [InlineData("List")] + public async void ValidTypesWhichAreNotTheoryData_Trigger(string memberType) + { + var source = $@" +using System.Collections.Generic; +using Xunit; - [Theory] - [MemberData(nameof(MemberSyntaxAndArgs))] - public async void FindWarning_IfHasValidTheoryDataMemberWithMismatchedNullability( - string memberSyntax, - string memberArgs) - { - var source = $@" -#nullable enable public class TestClass {{ - public static Xunit.TheoryData TestData{memberSyntax}new(); + public static {memberType} Data; - [Xunit.MemberData(nameof(TestData){memberArgs})] - public void TestMethod(int n, string f) {{ }} -}} -#nullable restore"; + [MemberData(nameof(Data))] + public void TestMethod(int _) {{ }} +}}"; - DiagnosticResult[] expected = - { - Verify - .Diagnostic("xUnit1039") - .WithSpan(7, 28, 7, 31) - .WithSeverity(DiagnosticSeverity.Error) - .WithArguments("int?", "TestClass", "TestData", "n"), - Verify - .Diagnostic("xUnit1040") - .WithSpan(7, 35, 7, 41) - .WithSeverity(DiagnosticSeverity.Warning) - .WithArguments("string?", "TestClass", "TestData", "f") - }; + var expected = + Verify + .Diagnostic("xUnit1042") + .WithSpan(8, 6, 8, 30) + .WithSeverity(DiagnosticSeverity.Info); - await Verify.VerifyAnalyzer(LanguageVersion.CSharp10, source, expected); + await Verify.VerifyAnalyzer(source, expected); + } } } diff --git a/src/xunit.analyzers.tests/Fixes/X1000/MemberDataShouldReferenceValidMember_ReturnTypeFixerTests.cs b/src/xunit.analyzers.tests/Fixes/X1000/MemberDataShouldReferenceValidMember_ReturnTypeFixerTests.cs index a67d7f09..96c7c4b5 100644 --- a/src/xunit.analyzers.tests/Fixes/X1000/MemberDataShouldReferenceValidMember_ReturnTypeFixerTests.cs +++ b/src/xunit.analyzers.tests/Fixes/X1000/MemberDataShouldReferenceValidMember_ReturnTypeFixerTests.cs @@ -57,7 +57,7 @@ public class TestClass { public static IEnumerable Data => null; [Theory] - [{|xUnit1042:MemberData(nameof(Data))|}] + [MemberData(nameof(Data))] public void TestMethod(int a) { } }"; diff --git a/src/xunit.analyzers.tests/Utility/CallerArgumentExpressionAttribute.cs b/src/xunit.analyzers.tests/Utility/CallerArgumentExpressionAttribute.cs new file mode 100644 index 00000000..feb37e2c --- /dev/null +++ b/src/xunit.analyzers.tests/Utility/CallerArgumentExpressionAttribute.cs @@ -0,0 +1,18 @@ +// Imported from xUnit.net v3, must be removed when this test project is upgraded + +#if NETFRAMEWORK + +namespace System.Runtime.CompilerServices; + +[AttributeUsage(AttributeTargets.Parameter, Inherited = false)] +internal sealed class CallerArgumentExpressionAttribute : Attribute +{ + public CallerArgumentExpressionAttribute(string parameterName) + { + ParameterName = parameterName; + } + + public string ParameterName { get; } +} + +#endif diff --git a/src/xunit.analyzers.tests/Utility/Guard.cs b/src/xunit.analyzers.tests/Utility/Guard.cs new file mode 100644 index 00000000..d094964a --- /dev/null +++ b/src/xunit.analyzers.tests/Utility/Guard.cs @@ -0,0 +1,290 @@ +// Imported from xUnit.net v3, must be removed when this test project is upgraded + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.IO; +using System.Runtime.CompilerServices; + +namespace Xunit.Internal; + +/// +/// Helper class for guarding value arguments and valid state. +/// +public static class Guard +{ + /// + /// Ensures that an enum value is valid by comparing against a list of valid values. + /// + /// The argument type + /// The value of the argument + /// The list of valid values + /// The name of the argument + /// + public static T ArgumentEnumValid( + T argValue, + HashSet validValues, + [CallerArgumentExpression("argValue")] string? argName = null) + where T : Enum + { + ArgumentNotNull(validValues); + + if (!validValues.Contains(argValue)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "Enum value {0} not in valid set: [{1}]", argValue, string.Join(",", validValues)), argName?.TrimStart('@')); + + return argValue; + } + + /// + /// Ensures that a nullable value type argument is not null. + /// + /// The argument type + /// The value of the argument + /// The name of the argument + /// The argument value as a non-null value + /// Thrown when the argument is null + public static T ArgumentNotNull( + [NotNull] T? argValue, + [CallerArgumentExpression("argValue")] string? argName = null) + where T : struct + { + if (!argValue.HasValue) + throw new ArgumentNullException(argName?.TrimStart('@')); + + return argValue.Value; + } + + /// + /// Ensures that a nullable reference type argument is not null. + /// + /// The argument type + /// The value of the argument + /// The name of the argument + /// The argument value as a non-null value + /// Thrown when the argument is null + public static T ArgumentNotNull( + [NotNull] T? argValue, + [CallerArgumentExpression("argValue")] string? argName = null) + where T : class + { + if (argValue is null) + throw new ArgumentNullException(argName?.TrimStart('@')); + + return argValue; + } + + /// + /// Ensures that a nullable reference type argument is not null. + /// + /// The argument type + /// The exception message to use when the argument is null + /// The value of the argument + /// The name of the argument + /// The argument value as a non-null value + /// Thrown when the argument is null + public static T ArgumentNotNull( + string message, + [NotNull] T? argValue, + string? argName = null) + where T : class + { + if (argValue is null) + throw new ArgumentNullException(argName, message); + + return argValue; + } + + /// + /// Ensures that a nullable reference type argument is not null. + /// + /// The argument type + /// The creator for an exception message to use when the argument is null + /// The value of the argument + /// The name of the argument + /// The argument value as a non-null value + /// Thrown when the argument is null + public static T ArgumentNotNull( + Func messageFunc, + [NotNull] T? argValue, + string? argName = null) + where T : class + { + if (argValue is null) + throw new ArgumentNullException(argName, messageFunc?.Invoke()); + + return argValue; + } + + /// + /// Ensures that a nullable enumerable type argument is not null or empty. + /// + /// The argument type + /// The value of the argument + /// The name of the argument + /// The argument value as a non-null, non-empty value + /// Thrown when the argument is null or empty + public static T ArgumentNotNullOrEmpty( + [NotNull] T? argValue, + [CallerArgumentExpression("argValue")] string? argName = null) + where T : class, IEnumerable + { + ArgumentNotNull(argValue, argName); + + if (!argValue.GetEnumerator().MoveNext()) + throw new ArgumentException("Argument was empty", argName?.TrimStart('@')); + + return argValue; + } + + /// + /// Ensures that a nullable enumerable type argument is not null or empty. + /// + /// The argument type + /// The exception message to use when the argument is null or empty + /// The value of the argument + /// The name of the argument + /// The argument value as a non-null, non-empty value + /// Thrown when the argument is null or empty + public static T ArgumentNotNullOrEmpty( + string message, + [NotNull] T? argValue, + string? argName = null) + where T : class, IEnumerable + { + if (argValue is null || !argValue.GetEnumerator().MoveNext()) + throw new ArgumentException(message, argName); + + return argValue; + } + + /// + /// Ensures that a nullable enumerable type argument is not null or empty. + /// + /// The argument type + /// The creator for an exception message to use when the argument is null or empty + /// The value of the argument + /// The name of the argument + /// The argument value as a non-null, non-empty value + /// Thrown when the argument is null or empty + public static T ArgumentNotNullOrEmpty( + Func messageFunc, + [NotNull] T? argValue, + string? argName = null) + where T : class, IEnumerable + { + if (argValue is null || !argValue.GetEnumerator().MoveNext()) + throw new ArgumentException(messageFunc?.Invoke(), argName); + + return argValue; + } + + /// + /// Ensures that an argument is valid. + /// + /// The exception message to use when the argument is not valid + /// The validity test value + /// The name of the argument + /// The argument value as a non-null value + /// Thrown when the argument is not valid + public static void ArgumentValid( + string message, + bool test, + string? argName = null) + { + if (!test) + throw new ArgumentException(message, argName); + } + + /// + /// Ensures that an argument is valid. + /// + /// The creator for an exception message to use when the argument is not valid + /// The validity test value + /// The name of the argument + /// The argument value as a non-null value + /// Thrown when the argument is not valid + public static void ArgumentValid( + Func messageFunc, + bool test, + string? argName = null) + { + if (!test) + throw new ArgumentException(messageFunc?.Invoke(), argName); + } + + /// + /// Ensures that a filename argument is not null or empty, and that the file exists on disk. + /// + /// The file name value + /// The name of the argument + /// The file name as a non-null value + /// Thrown when the argument is null, empty, or not on disk + public static string FileExists( + [NotNull] string? fileName, + [CallerArgumentExpression("fileName")] string? argName = null) + { + ArgumentNotNullOrEmpty(fileName, argName); + ArgumentValid(() => string.Format(CultureInfo.CurrentCulture, "File not found: {0}", fileName), File.Exists(fileName), argName?.TrimStart('@')); + + return fileName; + } + + /// + /// Ensures that a value is not default value. This is used for values of generic types + /// where nullability is not known. + /// + /// The argument type + /// The value of the argument + /// The name of the argument + /// The argument value as a non-default value + /// Thrown when the argument is default + public static T GenericArgumentNotNull( + [NotNull] T? argValue, + [CallerArgumentExpression("argValue")] string? argName = null) + { + if (argValue is null) + throw new ArgumentNullException(argName?.TrimStart('@')); + + return argValue; + } + + /// + /// Ensure that a value is not null. + /// + /// The value type + /// The exception message to use when the value is not valid + /// The value to test for null + /// The value as a non-null value + /// Thrown when the value is not valid + public static T NotNull( + string message, + [NotNull] T? value) + where T : class + { + if (value is null) + throw new InvalidOperationException(message); + + return value; + } + + /// + /// Ensure that a value is not null. + /// + /// The value type + /// The creator for an exception message to use when the value is not valid + /// The value to test for null + /// The value as a non-null value + /// Thrown when the value is not valid + public static T NotNull( + Func messageFunc, + [NotNull] T? value) + where T : class + { + if (value is null) + throw new InvalidOperationException(messageFunc?.Invoke()); + + return value; + } +} diff --git a/src/xunit.analyzers.tests/Utility/MatrixTheoryData.cs b/src/xunit.analyzers.tests/Utility/MatrixTheoryData.cs new file mode 100644 index 00000000..0329cb4b --- /dev/null +++ b/src/xunit.analyzers.tests/Utility/MatrixTheoryData.cs @@ -0,0 +1,228 @@ +// Imported from xUnit.net v3, must be removed when this test project is upgraded + +using System.Collections.Generic; +using Xunit.Internal; + +namespace Xunit; + +/// +/// Represents theory data which is created from the merging of two data streams by +/// creating a matrix of the data. +/// +/// Type of the first data dimension +/// Type of the second data dimension +public class MatrixTheoryData : TheoryData +{ + /// + /// Initializes a new instance of the class. + /// + /// Data for the first dimension + /// Data for the second dimension + public MatrixTheoryData( + IEnumerable dimension1, + IEnumerable dimension2) + { + Guard.ArgumentNotNull(dimension1); + Guard.ArgumentNotNull(dimension2); + + var data1Empty = true; + var data2Empty = true; + + foreach (var t1 in dimension1) + { + data1Empty = false; + + foreach (var t2 in dimension2) + { + data2Empty = false; + Add(t1, t2); + } + } + + Guard.ArgumentValid("Data dimension cannot be empty", !data1Empty, nameof(dimension1)); + Guard.ArgumentValid("Data dimension cannot be empty", !data2Empty, nameof(dimension2)); + } +} + +/// +/// Represents theory data which is created from the merging of three data streams by +/// creating a matrix of the data. +/// +/// Type of the first data dimension +/// Type of the second data dimension +/// Type of the third data dimension +public class MatrixTheoryData : TheoryData +{ + /// + /// Initializes a new instance of the class. + /// + /// Data for the first dimension + /// Data for the second dimension + /// Data for the third dimension + public MatrixTheoryData( + IEnumerable dimension1, + IEnumerable dimension2, + IEnumerable dimension3) + { + Guard.ArgumentNotNull(dimension1); + Guard.ArgumentNotNull(dimension2); + Guard.ArgumentNotNull(dimension3); + + var data1Empty = true; + var data2Empty = true; + var data3Empty = true; + + foreach (var t1 in dimension1) + { + data1Empty = false; + + foreach (var t2 in dimension2) + { + data2Empty = false; + + foreach (var t3 in dimension3) + { + data3Empty = false; + Add(t1, t2, t3); + } + } + } + + Guard.ArgumentValid("Data dimension cannot be empty", !data1Empty, nameof(dimension1)); + Guard.ArgumentValid("Data dimension cannot be empty", !data2Empty, nameof(dimension2)); + Guard.ArgumentValid("Data dimension cannot be empty", !data3Empty, nameof(dimension3)); + } +} + +/// +/// Represents theory data which is created from the merging of four data streams by +/// creating a matrix of the data. +/// +/// Type of the first data dimension +/// Type of the second data dimension +/// Type of the third data dimension +/// Type of the fourth data dimension +public class MatrixTheoryData : TheoryData +{ + /// + /// Initializes a new instance of the class. + /// + /// Data for the first dimension + /// Data for the second dimension + /// Data for the third dimension + /// Data for the fourth dimension + public MatrixTheoryData( + IEnumerable dimension1, + IEnumerable dimension2, + IEnumerable dimension3, + IEnumerable dimension4) + { + Guard.ArgumentNotNull(dimension1); + Guard.ArgumentNotNull(dimension2); + Guard.ArgumentNotNull(dimension3); + Guard.ArgumentNotNull(dimension4); + + var data1Empty = true; + var data2Empty = true; + var data3Empty = true; + var data4Empty = true; + + foreach (var t1 in dimension1) + { + data1Empty = false; + + foreach (var t2 in dimension2) + { + data2Empty = false; + + foreach (var t3 in dimension3) + { + data3Empty = false; + + foreach (var t4 in dimension4) + { + data4Empty = false; + Add(t1, t2, t3, t4); + } + } + } + } + + Guard.ArgumentValid("Data dimension cannot be empty", !data1Empty, nameof(dimension1)); + Guard.ArgumentValid("Data dimension cannot be empty", !data2Empty, nameof(dimension2)); + Guard.ArgumentValid("Data dimension cannot be empty", !data3Empty, nameof(dimension3)); + Guard.ArgumentValid("Data dimension cannot be empty", !data4Empty, nameof(dimension4)); + } +} + +/// +/// Represents theory data which is created from the merging of five data streams by +/// creating a matrix of the data. +/// +/// Type of the first data dimension +/// Type of the second data dimension +/// Type of the third data dimension +/// Type of the fourth data dimension +/// Type of the fifth data dimension +public class MatrixTheoryData : TheoryData +{ + /// + /// Initializes a new instance of the class. + /// + /// Data for the first dimension + /// Data for the second dimension + /// Data for the third dimension + /// Data for the fourth dimension + /// Data for the fifth dimension + public MatrixTheoryData( + IEnumerable dimension1, + IEnumerable dimension2, + IEnumerable dimension3, + IEnumerable dimension4, + IEnumerable dimension5) + { + Guard.ArgumentNotNull(dimension1); + Guard.ArgumentNotNull(dimension2); + Guard.ArgumentNotNull(dimension3); + Guard.ArgumentNotNull(dimension4); + Guard.ArgumentNotNull(dimension5); + + var data1Empty = true; + var data2Empty = true; + var data3Empty = true; + var data4Empty = true; + var data5Empty = true; + + foreach (var t1 in dimension1) + { + data1Empty = false; + + foreach (var t2 in dimension2) + { + data2Empty = false; + + foreach (var t3 in dimension3) + { + data3Empty = false; + + foreach (var t4 in dimension4) + { + data4Empty = false; + + foreach (var t5 in dimension5) + { + data5Empty = false; + Add(t1, t2, t3, t4, t5); + } + } + } + } + } + + Guard.ArgumentValid("Data dimension cannot be empty", !data1Empty, nameof(dimension1)); + Guard.ArgumentValid("Data dimension cannot be empty", !data2Empty, nameof(dimension2)); + Guard.ArgumentValid("Data dimension cannot be empty", !data3Empty, nameof(dimension3)); + Guard.ArgumentValid("Data dimension cannot be empty", !data4Empty, nameof(dimension4)); + Guard.ArgumentValid("Data dimension cannot be empty", !data5Empty, nameof(dimension5)); + } +} diff --git a/src/xunit.analyzers/X1000/MemberDataShouldReferenceValidMember.cs b/src/xunit.analyzers/X1000/MemberDataShouldReferenceValidMember.cs index 1d4b78bb..2e82f578 100644 --- a/src/xunit.analyzers/X1000/MemberDataShouldReferenceValidMember.cs +++ b/src/xunit.analyzers/X1000/MemberDataShouldReferenceValidMember.cs @@ -140,8 +140,9 @@ public override void AnalyzeCompilation( ReportNonStatic(context, attributeSyntax, memberProperties); // Make sure the member returns a compatible type - bool IsValidMemberReturnType = - VerifyDataSourceReturnType(context, compilation, xunitContext, memberReturnType, memberProperties, attributeSyntax); + var iEnumerableOfTheoryDataRowType = TypeSymbolFactory.IEnumerableOfITheoryDataRow(compilation); + var IsValidMemberReturnType = + VerifyDataSourceReturnType(context, compilation, xunitContext, memberReturnType, memberProperties, attributeSyntax, iEnumerableOfTheoryDataRowType); // Make sure public properties have a public getter if (memberSymbol.Kind == SymbolKind.Property && memberSymbol.DeclaredAccessibility == Accessibility.Public) @@ -156,7 +157,7 @@ public override void AnalyzeCompilation( // If the member does not return TheoryData, gently suggest to the user that TheoryData is better for type safety if (IsTheoryDataType(memberReturnType, theoryDataTypes, out var theoryReturnType)) VerifyTheoryDataUsage(semanticModel, context, testMethod, theoryReturnType, memberName, declaredMemberTypeSymbol, attributeSyntax); - else if (IsValidMemberReturnType) + else if (IsValidMemberReturnType && !IsTheoryDataRowType(memberReturnType, iEnumerableOfTheoryDataRowType)) ReportMemberReturnsTypeUnsafeValue(context, attributeSyntax); // Get the arguments that are to be passed to the method @@ -194,6 +195,28 @@ public override void AnalyzeCompilation( return null; } + public static ISymbol? FindMethodSymbol( + string memberName, + ITypeSymbol? type, + int paramsCount) + { + while (type is not null) + { + var methodSymbol = + type + .GetMembers(memberName) + .OfType() + .FirstOrDefault(x => x.Parameters.Length == paramsCount); + + if (methodSymbol is not null) + return methodSymbol; + + type = type.BaseType; + } + + return null; + } + public static (INamedTypeSymbol? TestClass, ITypeSymbol? MemberClass) GetClassTypesForAttribute( AttributeArgumentListSyntax attributeList, SemanticModel semanticModel, @@ -244,27 +267,10 @@ public static (INamedTypeSymbol? TestClass, ITypeSymbol? MemberClass) GetClassTy return new List { argumentExpression }; } - public static ISymbol? FindMethodSymbol( - string memberName, - ITypeSymbol? type, - int paramsCount) - { - while (type is not null) - { - var methodSymbol = - type - .GetMembers(memberName) - .OfType() - .FirstOrDefault(x => x.Parameters.Length == paramsCount); - - if (methodSymbol is not null) - return methodSymbol; - - type = type.BaseType; - } - - return null; - } + static bool IsTheoryDataRowType( + ITypeSymbol? memberReturnType, + INamedTypeSymbol? iEnumerableOfTheoryDataRowType) => + iEnumerableOfTheoryDataRowType?.IsAssignableFrom(memberReturnType) ?? false; static bool IsTheoryDataType( ITypeSymbol? memberReturnType, @@ -644,10 +650,10 @@ static bool VerifyDataSourceReturnType( XunitContext xunitContext, ITypeSymbol memberType, ImmutableDictionary memberProperties, - AttributeSyntax attributeSyntax) + AttributeSyntax attributeSyntax, + INamedTypeSymbol? iEnumerableOfTheoryDataRowType) { var iEnumerableOfObjectArrayType = TypeSymbolFactory.IEnumerableOfObjectArray(compilation); - var iEnumerableOfTheoryDataRowType = TypeSymbolFactory.IEnumerableOfITheoryDataRow(compilation); var valid = iEnumerableOfObjectArrayType.IsAssignableFrom(memberType); if (!valid && xunitContext.HasV3References && iEnumerableOfTheoryDataRowType is not null) From 0808a623525e2259e3d0103938fee86e04b4a2e9 Mon Sep 17 00:00:00 2001 From: Brad Wilson Date: Sun, 3 Dec 2023 17:54:47 -0800 Subject: [PATCH 10/10] Remove most of the {|xUnit1042|} instances --- ...houldReferenceValidMember_NameOfFixerTests.cs | 8 ++++---- ...otBeUsedForIncompatibleParameterFixerTests.cs | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/xunit.analyzers.tests/Fixes/X1000/MemberDataShouldReferenceValidMember_NameOfFixerTests.cs b/src/xunit.analyzers.tests/Fixes/X1000/MemberDataShouldReferenceValidMember_NameOfFixerTests.cs index 21afe329..021a9227 100644 --- a/src/xunit.analyzers.tests/Fixes/X1000/MemberDataShouldReferenceValidMember_NameOfFixerTests.cs +++ b/src/xunit.analyzers.tests/Fixes/X1000/MemberDataShouldReferenceValidMember_NameOfFixerTests.cs @@ -13,10 +13,10 @@ public async void ConvertStringToNameOf() using Xunit; public class TestClass { - public static IEnumerable DataSource = Array.Empty(); + public static TheoryData DataSource; [Theory] - [{|xUnit1042:MemberData({|xUnit1014:""DataSource""|})|}] + [MemberData({|xUnit1014:""DataSource""|})] public void TestMethod(int a) { } }"; @@ -26,10 +26,10 @@ public void TestMethod(int a) { } using Xunit; public class TestClass { - public static IEnumerable DataSource = Array.Empty(); + public static TheoryData DataSource; [Theory] - [{|xUnit1042:MemberData(nameof(DataSource))|}] + [MemberData(nameof(DataSource))] public void TestMethod(int a) { } }"; diff --git a/src/xunit.analyzers.tests/Fixes/X1000/MemberDataShouldReferenceValidMember_NullShouldNotBeUsedForIncompatibleParameterFixerTests.cs b/src/xunit.analyzers.tests/Fixes/X1000/MemberDataShouldReferenceValidMember_NullShouldNotBeUsedForIncompatibleParameterFixerTests.cs index 1e766fa4..10d200c0 100644 --- a/src/xunit.analyzers.tests/Fixes/X1000/MemberDataShouldReferenceValidMember_NullShouldNotBeUsedForIncompatibleParameterFixerTests.cs +++ b/src/xunit.analyzers.tests/Fixes/X1000/MemberDataShouldReferenceValidMember_NullShouldNotBeUsedForIncompatibleParameterFixerTests.cs @@ -37,29 +37,29 @@ public void TestMethod(int a) { } public async void MakesReferenceParameterNullable() { var before = @" +#nullable enable + using Xunit; -#nullable enable public class TestClass { - public static System.Collections.Generic.IEnumerable TestData(int n, string k) { yield return new object[] { n }; } + public static TheoryData TestData(int n, string k) => new TheoryData { n }; [Theory] - [{|xUnit1042:MemberData(nameof(TestData), 42, {|xUnit1034:null|})|}] + [MemberData(nameof(TestData), 42, {|xUnit1034:null|})] public void TestMethod(int a) { } -#nullable restore }"; var after = @" +#nullable enable + using Xunit; -#nullable enable public class TestClass { - public static System.Collections.Generic.IEnumerable TestData(int n, string? k) { yield return new object[] { n }; } + public static TheoryData TestData(int n, string? k) => new TheoryData { n }; [Theory] - [{|xUnit1042:MemberData(nameof(TestData), 42, null)|}] + [MemberData(nameof(TestData), 42, null)] public void TestMethod(int a) { } -#nullable restore }"; await Verify.VerifyCodeFix(LanguageVersion.CSharp8, before, after, MemberDataShouldReferenceValidMember_NullShouldNotBeUsedForIncompatibleParameterFixer.Key_MakeParameterNullable);