diff --git a/src/xunit.analyzers.tests/Analyzers/X1000/MemberDataShouldReferenceValidMemberTests.cs b/src/xunit.analyzers.tests/Analyzers/X1000/MemberDataShouldReferenceValidMemberTests.cs index 0f247d77..99ea6f2d 100644 --- a/src/xunit.analyzers.tests/Analyzers/X1000/MemberDataShouldReferenceValidMemberTests.cs +++ b/src/xunit.analyzers.tests/Analyzers/X1000/MemberDataShouldReferenceValidMemberTests.cs @@ -354,8 +354,6 @@ public void SkippedDataRow(int x, string y) {{ }} [InlineData(/* lang=c#-test */ "System.Collections.Generic.IEnumerable")] [InlineData(/* lang=c#-test */ "object[]")] [InlineData(/* lang=c#-test */ "object")] - [InlineData(/* lang=c#-test */ "System.Tuple")] - [InlineData(/* lang=c#-test */ "System.Tuple[]")] public async Task InvalidMemberType_Triggers(string memberType) { var source = string.Format(/* lang=c#-test */ """ @@ -367,11 +365,39 @@ public void TestMethod() {{ }} }} """, memberType); var expectedV2 = Verify.Diagnostic("xUnit1019").WithLocation(0).WithArguments("'System.Collections.Generic.IEnumerable'", memberType); - var expectedV3 = Verify.Diagnostic("xUnit1019").WithLocation(0).WithArguments("'System.Collections.Generic.IEnumerable', 'System.Collections.Generic.IAsyncEnumerable', 'System.Collections.Generic.IEnumerable', or 'System.Collections.Generic.IAsyncEnumerable'", memberType); + var expectedV3 = Verify.Diagnostic("xUnit1019").WithLocation(0).WithArguments("'System.Collections.Generic.IEnumerable', 'System.Collections.Generic.IAsyncEnumerable', 'System.Collections.Generic.IEnumerable', 'System.Collections.Generic.IAsyncEnumerable', 'System.Collections.Generic.IEnumerable', or 'System.Collections.Generic.IAsyncEnumerable'", memberType); await Verify.VerifyAnalyzerV2(source, expectedV2); await Verify.VerifyAnalyzerV3(source, expectedV3); } + + [Fact] + public async Task Tuple_TriggersInV2_DoesNotTriggerInV3() + { + var source = /* lang=c#-test */ """ + #pragma warning disable xUnit1042 + + using System; + using System.Collections.Generic; + using Xunit; + + public class TestClass { + public static IEnumerable<(string, int)> UntypedTupleSource; + public static IEnumerable> TypedTupleSource; + + [{|#0:MemberData(nameof(UntypedTupleSource))|}] + [{|#1:MemberData(nameof(TypedTupleSource))|}] + public void TestMethod(string _1, int _2) { } + } + """; + DiagnosticResult[] expectedV2 = [ + Verify.Diagnostic("xUnit1019").WithLocation(0).WithArguments("'System.Collections.Generic.IEnumerable'", "System.Collections.Generic.IEnumerable<(string, int)>"), + Verify.Diagnostic("xUnit1019").WithLocation(1).WithArguments("'System.Collections.Generic.IEnumerable'", "System.Collections.Generic.IEnumerable>"), + ]; + + await Verify.VerifyAnalyzerV2(LanguageVersion.CSharp7, source, expectedV2); + await Verify.VerifyAnalyzerV3(LanguageVersion.CSharp7, source); + } } public class X1020_MemberDataPropertyMustHaveGetter diff --git a/src/xunit.analyzers/Utility/TypeSymbolFactory.cs b/src/xunit.analyzers/Utility/TypeSymbolFactory.cs index ceeebca2..e2130845 100644 --- a/src/xunit.analyzers/Utility/TypeSymbolFactory.cs +++ b/src/xunit.analyzers/Utility/TypeSymbolFactory.cs @@ -103,6 +103,15 @@ public static class TypeSymbolFactory public static INamedTypeSymbol? IAsyncEnumerableOfT(Compilation compilation) => Guard.ArgumentNotNull(compilation).GetTypeByMetadataName("System.Collections.Generic.IAsyncEnumerable`1"); + public static INamedTypeSymbol? IAsyncEnumerableOfTuple(Compilation compilation) + { + var iTuple = ITuple(compilation); + if (iTuple is null) + return null; + + return IAsyncEnumerableOfT(compilation)?.Construct(iTuple); + } + public static INamedTypeSymbol? IAsyncLifetime(Compilation compilation) => Guard.ArgumentNotNull(compilation).GetTypeByMetadataName(Constants.Types.Xunit.IAsyncLifetime); @@ -142,6 +151,15 @@ public static INamedTypeSymbol IEnumerableOfObjectArray(Compilation compilation) public static INamedTypeSymbol IEnumerableOfT(Compilation compilation) => Guard.ArgumentNotNull(compilation).GetSpecialType(SpecialType.System_Collections_Generic_IEnumerable_T); + public static INamedTypeSymbol? IEnumerableOfTuple(Compilation compilation) + { + var iTuple = ITuple(compilation); + if (iTuple is null) + return null; + + return IEnumerableOfT(compilation).Construct(iTuple); + } + public static INamedTypeSymbol? IMessageSink_V2(Compilation compilation) => Guard.ArgumentNotNull(compilation).GetTypeByMetadataName(Constants.Types.Xunit.IMessageSink_V2); @@ -253,6 +271,9 @@ public static INamedTypeSymbol IReadOnlyCollectionOfT(Compilation compilation) = public static INamedTypeSymbol? ITheoryDataRow_V3(Compilation compilation) => Guard.ArgumentNotNull(compilation).GetTypeByMetadataName(Constants.Types.Xunit.ITheoryDataRow_V3); + public static INamedTypeSymbol? ITuple(Compilation compilation) => + Guard.ArgumentNotNull(compilation).GetTypeByMetadataName("System.Runtime.CompilerServices.ITuple"); + public static INamedTypeSymbol? ITypeInfo_V2(Compilation compilation) => Guard.ArgumentNotNull(compilation).GetTypeByMetadataName(Constants.Types.Xunit.ITypeInfo_V2); diff --git a/src/xunit.analyzers/X1000/MemberDataShouldReferenceValidMember.cs b/src/xunit.analyzers/X1000/MemberDataShouldReferenceValidMember.cs index 9da41b4d..722eb3d1 100644 --- a/src/xunit.analyzers/X1000/MemberDataShouldReferenceValidMember.cs +++ b/src/xunit.analyzers/X1000/MemberDataShouldReferenceValidMember.cs @@ -156,8 +156,10 @@ memberReturnType is INamedTypeSymbol namedMemberReturnType && // Make sure the member returns a compatible type var iEnumerableOfTheoryDataRowType = TypeSymbolFactory.IEnumerableOfITheoryDataRow(compilation); var iAsyncEnumerableOfTheoryDataRowType = TypeSymbolFactory.IAsyncEnumerableOfITheoryDataRow(compilation); + var iEnumerableOfTupleType = TypeSymbolFactory.IEnumerableOfTuple(compilation); + var iAsyncEnumerableOfTupleType = TypeSymbolFactory.IAsyncEnumerableOfTuple(compilation); var IsValidMemberReturnType = - VerifyDataSourceReturnType(context, compilation, xunitContext, memberReturnType, memberProperties, attributeSyntax, iEnumerableOfTheoryDataRowType, iAsyncEnumerableOfTheoryDataRowType); + VerifyDataSourceReturnType(context, compilation, xunitContext, memberReturnType, memberProperties, attributeSyntax, iEnumerableOfTheoryDataRowType, iAsyncEnumerableOfTheoryDataRowType, iEnumerableOfTupleType, iAsyncEnumerableOfTupleType); // Make sure public properties have a public getter if (memberSymbol.Kind == SymbolKind.Property && memberSymbol.DeclaredAccessibility == Accessibility.Public) @@ -381,6 +383,8 @@ static void ReportIncorrectReturnType( INamedTypeSymbol? iAsyncEnumerableOfObjectArrayType, INamedTypeSymbol? iEnumerableOfTheoryDataRowType, INamedTypeSymbol? iAsyncEnumerableOfTheoryDataRowType, + INamedTypeSymbol? iEnumerableOfTupleType, + INamedTypeSymbol? iAsyncEnumerableOfTupleType, AttributeSyntax attribute, ImmutableDictionary memberProperties, ITypeSymbol memberType) @@ -390,14 +394,18 @@ static void ReportIncorrectReturnType( // Only want the extra types when we know ITheoryDataRow is valid if (iAsyncEnumerableOfObjectArrayType is not null && iEnumerableOfTheoryDataRowType is not null && - iAsyncEnumerableOfTheoryDataRowType is not null) + iAsyncEnumerableOfTheoryDataRowType is not null && + iEnumerableOfTupleType is not null && + iAsyncEnumerableOfTupleType is not null) #pragma warning disable RS1035 // The suggested fix is not available in this context validSymbols += string.Format( CultureInfo.CurrentCulture, - ", '{0}', '{1}', or '{2}'", + ", '{0}', '{1}', '{2}', '{3}', or '{4}'", SymbolDisplay.ToDisplayString(iAsyncEnumerableOfObjectArrayType), SymbolDisplay.ToDisplayString(iEnumerableOfTheoryDataRowType), - SymbolDisplay.ToDisplayString(iAsyncEnumerableOfTheoryDataRowType) + SymbolDisplay.ToDisplayString(iAsyncEnumerableOfTheoryDataRowType), + SymbolDisplay.ToDisplayString(iEnumerableOfTupleType), + SymbolDisplay.ToDisplayString(iAsyncEnumerableOfTupleType) ); #pragma warning restore RS1035 @@ -721,7 +729,9 @@ static bool VerifyDataSourceReturnType( ImmutableDictionary memberProperties, AttributeSyntax attributeSyntax, INamedTypeSymbol? iEnumerableOfTheoryDataRowType, - INamedTypeSymbol? iAsyncEnumerableOfTheoryDataRowType) + INamedTypeSymbol? iAsyncEnumerableOfTheoryDataRowType, + INamedTypeSymbol? iEnumerableOfTupleType, + INamedTypeSymbol? iAsyncEnumerableOfTupleType) { var v3 = xunitContext.HasV3References; var iEnumerableOfObjectArrayType = TypeSymbolFactory.IEnumerableOfObjectArray(compilation); @@ -738,6 +748,12 @@ static bool VerifyDataSourceReturnType( if (!valid && v3 && iAsyncEnumerableOfTheoryDataRowType is not null) valid = iAsyncEnumerableOfTheoryDataRowType.IsAssignableFrom(memberType); + if (!valid && v3 && iEnumerableOfTupleType is not null) + valid = iEnumerableOfTupleType.IsAssignableFrom(memberType); + + if (!valid && v3 && iAsyncEnumerableOfTupleType is not null) + valid = iAsyncEnumerableOfTupleType.IsAssignableFrom(memberType); + if (!valid) ReportIncorrectReturnType( context, @@ -745,6 +761,8 @@ static bool VerifyDataSourceReturnType( iAsyncEnumerableOfObjectArrayType, iEnumerableOfTheoryDataRowType, iAsyncEnumerableOfTheoryDataRowType, + iEnumerableOfTupleType, + iAsyncEnumerableOfTupleType, attributeSyntax, memberProperties, memberType