Skip to content

Commit e010838

Browse files
committed
#3508: Update xUnit1019 to to not trigger for IEnumerable and IEnumerable<object>
1 parent f735d50 commit e010838

4 files changed

Lines changed: 19 additions & 144 deletions

File tree

src/xunit.analyzers.tests/Analyzers/X1000/X1019_MemberDataShouldReferenceValidMemberTests.cs

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55

66
public class X1019_MemberDataShouldReferenceValidMemberTests
77
{
8-
const string V2AllowedTypes = "'System.Collections.Generic.IEnumerable<object[]>'";
9-
const string V3AllowedTypes = "'System.Collections.Generic.IEnumerable<object[]>', 'System.Collections.Generic.IAsyncEnumerable<object[]>', 'System.Collections.Generic.IEnumerable<Xunit.ITheoryDataRow>', 'System.Collections.Generic.IAsyncEnumerable<Xunit.ITheoryDataRow>', 'System.Collections.Generic.IEnumerable<System.Runtime.CompilerServices.ITuple>', or 'System.Collections.Generic.IAsyncEnumerable<System.Runtime.CompilerServices.ITuple>'";
8+
const string V2AllowedTypes = "'IEnumerable<object[]>'";
9+
const string V3AllowedTypes = "'IEnumerable<object[]>', 'IAsyncEnumerable<object[]>', 'IEnumerable<ITheoryDataRow>', 'IAsyncEnumerable<ITheoryDataRow>', 'IEnumerable<ITuple>', or 'IAsyncEnumerable<ITuple>'";
1010

1111
[Fact]
1212
public async ValueTask V2_and_V3()
@@ -44,6 +44,7 @@ IEnumerator IEnumerable.GetEnumerator()
4444
public class NamedSubtypeForIEnumerableStringArray : NamedTypeForIEnumerableStringArray {}
4545
4646
public class TestClass {
47+
public static IEnumerable NonGenericSource;
4748
public static IEnumerable<object> ObjectSource;
4849
public static object NakedObjectSource;
4950
public static object[] NakedObjectArraySource;
@@ -81,9 +82,10 @@ public class TestClass {
8182
public static Task<IAsyncEnumerable<Tuple<string, int>>> TaskAsyncTypedTupleSource;
8283
public static ValueTask<IAsyncEnumerable<Tuple<string, int>>> ValueTaskAsyncTypedTupleSource;
8384
84-
[{|#0:MemberData(nameof(ObjectSource))|}]
85-
[{|#1:MemberData(nameof(NakedObjectSource))|}]
86-
[{|#2:MemberData(nameof(NakedObjectArraySource))|}]
85+
[MemberData(nameof(NonGenericSource))]
86+
[MemberData(nameof(ObjectSource))]
87+
[{|#0:MemberData(nameof(NakedObjectSource))|}]
88+
[MemberData(nameof(NakedObjectArraySource))]
8789
[MemberData(nameof(NakedObjectMatrixSource))]
8890
8991
[MemberData(nameof(ObjectArraySource))]
@@ -123,9 +125,7 @@ public void TestMethod(string _1, int _2) { }
123125
""";
124126
var expectedV2 = new[] {
125127
// Generally invalid types
126-
Verify.Diagnostic("xUnit1019").WithLocation(0).WithArguments(V2AllowedTypes, $"System.Collections.Generic.IEnumerable<object>"),
127-
Verify.Diagnostic("xUnit1019").WithLocation(1).WithArguments(V2AllowedTypes, $"object"),
128-
Verify.Diagnostic("xUnit1019").WithLocation(2).WithArguments(V2AllowedTypes, $"object[]"),
128+
Verify.Diagnostic("xUnit1019").WithLocation(0).WithArguments(V2AllowedTypes, $"object"),
129129

130130
// v2 does not support tuples, wrapping in Task/ValueTask, and does not support IAsyncEnumerable
131131
Verify.Diagnostic("xUnit1019").WithLocation(10).WithArguments(V2AllowedTypes, $"System.Threading.Tasks.Task<System.Collections.Generic.IEnumerable<object[]>>"),
@@ -156,9 +156,7 @@ public void TestMethod(string _1, int _2) { }
156156
};
157157
var expectedV3 = new[] {
158158
// Generally invalid types
159-
Verify.Diagnostic("xUnit1019").WithLocation(0).WithArguments(V3AllowedTypes, $"System.Collections.Generic.IEnumerable<object>"),
160-
Verify.Diagnostic("xUnit1019").WithLocation(1).WithArguments(V3AllowedTypes, $"object"),
161-
Verify.Diagnostic("xUnit1019").WithLocation(2).WithArguments(V3AllowedTypes, $"object[]"),
159+
Verify.Diagnostic("xUnit1019").WithLocation(0).WithArguments(V3AllowedTypes, $"object"),
162160
};
163161

164162
await Verify.VerifyAnalyzerV2(LanguageVersion.CSharp9, source, expectedV2);

src/xunit.analyzers.tests/Fixes/X1000/X1019_MemberDataShouldReferenceValidMember_ReturnTypeFixerTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ public async ValueTask V2_and_V3()
1515
using Xunit;
1616
1717
public class TestClass {
18-
public static IEnumerable<object> Data1 => null;
19-
public static IEnumerable<object> Data2 => null;
18+
public static IEnumerable<string> Data1 => null;
19+
public static IEnumerable<string> Data2 => null;
2020
2121
[Theory]
2222
[{|xUnit1019:MemberData(nameof(Data1))|}]

src/xunit.analyzers/Utility/TypeSymbolFactory.cs

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -133,15 +133,6 @@ public static INamedTypeSymbol Boolean(Compilation compilation) =>
133133
public static INamedTypeSymbol? IAsyncEnumerableOfT(Compilation compilation) =>
134134
Guard.ArgumentNotNull(compilation).GetTypeByMetadataName("System.Collections.Generic.IAsyncEnumerable`1");
135135

136-
public static INamedTypeSymbol? IAsyncEnumerableOfTuple(Compilation compilation)
137-
{
138-
var iTuple = ITuple(compilation);
139-
if (iTuple is null)
140-
return null;
141-
142-
return IAsyncEnumerableOfT(compilation)?.Construct(iTuple);
143-
}
144-
145136
public static INamedTypeSymbol? IAsyncLifetime(Compilation compilation) =>
146137
Guard.ArgumentNotNull(compilation).GetTypeByMetadataName(Constants.Types.Xunit.IAsyncLifetime);
147138

@@ -202,15 +193,6 @@ public static INamedTypeSymbol IEnumerableOfObjectArray(Compilation compilation)
202193
public static INamedTypeSymbol IEnumerableOfT(Compilation compilation) =>
203194
Guard.ArgumentNotNull(compilation).GetSpecialType(SpecialType.System_Collections_Generic_IEnumerable_T);
204195

205-
public static INamedTypeSymbol? IEnumerableOfTuple(Compilation compilation)
206-
{
207-
var iTuple = ITuple(compilation);
208-
if (iTuple is null)
209-
return null;
210-
211-
return IEnumerableOfT(compilation).Construct(iTuple);
212-
}
213-
214196
public static INamedTypeSymbol? IFormattable(Compilation compilation) =>
215197
Guard.ArgumentNotNull(compilation).GetTypeByMetadataName("System.IFormattable");
216198

src/xunit.analyzers/X1000/MemberDataShouldReferenceValidMember.cs

Lines changed: 8 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -174,10 +174,7 @@ memberReturnType is INamedTypeSymbol namedMemberReturnType &&
174174
// Make sure the member returns a compatible type
175175
var iEnumerableOfTheoryDataRowType = TypeSymbolFactory.IEnumerableOfITheoryDataRow(compilation);
176176
var iAsyncEnumerableOfTheoryDataRowType = TypeSymbolFactory.IAsyncEnumerableOfITheoryDataRow(compilation);
177-
var iEnumerableOfTupleType = TypeSymbolFactory.IEnumerableOfTuple(compilation);
178-
var iAsyncEnumerableOfTupleType = TypeSymbolFactory.IAsyncEnumerableOfTuple(compilation);
179-
var IsValidMemberReturnType =
180-
VerifyDataSourceReturnType(context, compilation, xunitContext, memberReturnType, memberProperties, attributeSyntax, iEnumerableOfTheoryDataRowType, iAsyncEnumerableOfTheoryDataRowType, iEnumerableOfTupleType, iAsyncEnumerableOfTupleType);
177+
var IsValidMemberReturnType = VerifyDataSourceReturnType(context, compilation, xunitContext, memberReturnType, memberProperties, attributeSyntax);
181178

182179
// Make sure public properties have a public getter
183180
if (memberSymbol.Kind == SymbolKind.Property && memberSymbol.DeclaredAccessibility == Accessibility.Public)
@@ -314,42 +311,6 @@ public static ImmutableArray<ISymbol> FindMethodSymbols(
314311
return ImmutableArray<ISymbol>.Empty;
315312
}
316313

317-
static ITypeSymbol? FindTypeArgumentFromIEnumerable(
318-
ITypeSymbol? start,
319-
Compilation compilation)
320-
{
321-
if (start is not INamedTypeSymbol namedStart)
322-
return null;
323-
324-
var iEnumerableOfT = TypeSymbolFactory.IEnumerableOfT(compilation);
325-
if (iEnumerableOfT is null)
326-
return null;
327-
328-
var current = namedStart;
329-
while (current != null)
330-
{
331-
// If this class implements IEnumerable<T>, return T type argument
332-
foreach (var iface in current.AllInterfaces)
333-
if (SymbolEqualityComparer.Default.Equals(iface.OriginalDefinition, iEnumerableOfT))
334-
return iface.TypeArguments.FirstOrDefault();
335-
336-
// Navigate to its Base types to investigate if they implement IEnumerable,
337-
// attempt to resolve the base type from the source syntax (handles `: ValidExamples`).
338-
var declaredBase = ResolveDeclaredBaseTypeFromSyntax(current, compilation) as INamedTypeSymbol;
339-
340-
// Prefer declaredBase when available; otherwise fall back to the BaseType symbol.
341-
var next = declaredBase ?? current.BaseType;
342-
343-
// Stop if we reached object
344-
if (next?.SpecialType == SpecialType.System_Object)
345-
break;
346-
347-
current = next;
348-
}
349-
350-
return null;
351-
}
352-
353314
public static (INamedTypeSymbol? TestClass, ITypeSymbol? MemberClass) GetClassTypesForAttribute(
354315
AttributeArgumentListSyntax attributeList,
355316
SemanticModel semanticModel,
@@ -490,35 +451,16 @@ static void ReportIncorrectMemberType(
490451

491452
static void ReportIncorrectReturnType(
492453
SyntaxNodeAnalysisContext context,
493-
INamedTypeSymbol iEnumerableOfObjectArrayType,
494-
INamedTypeSymbol? iAsyncEnumerableOfObjectArrayType,
495-
INamedTypeSymbol? iEnumerableOfTheoryDataRowType,
496-
INamedTypeSymbol? iAsyncEnumerableOfTheoryDataRowType,
497-
INamedTypeSymbol? iEnumerableOfTupleType,
498-
INamedTypeSymbol? iAsyncEnumerableOfTupleType,
454+
bool v3,
499455
AttributeSyntax attribute,
500456
ImmutableDictionary<string, string?> memberProperties,
501457
ITypeSymbol memberType)
502458
{
503-
var validSymbols = "'" + SymbolDisplay.ToDisplayString(iEnumerableOfObjectArrayType) + "'";
459+
var validSymbols = "'IEnumerable<object[]>'";
504460

505461
// Only want the extra types when we know ITheoryDataRow is valid
506-
if (iAsyncEnumerableOfObjectArrayType is not null &&
507-
iEnumerableOfTheoryDataRowType is not null &&
508-
iAsyncEnumerableOfTheoryDataRowType is not null &&
509-
iEnumerableOfTupleType is not null &&
510-
iAsyncEnumerableOfTupleType is not null)
511-
#pragma warning disable RS1035 // The suggested fix is not available in this context
512-
validSymbols += string.Format(
513-
CultureInfo.CurrentCulture,
514-
", '{0}', '{1}', '{2}', '{3}', or '{4}'",
515-
SymbolDisplay.ToDisplayString(iAsyncEnumerableOfObjectArrayType),
516-
SymbolDisplay.ToDisplayString(iEnumerableOfTheoryDataRowType),
517-
SymbolDisplay.ToDisplayString(iAsyncEnumerableOfTheoryDataRowType),
518-
SymbolDisplay.ToDisplayString(iEnumerableOfTupleType),
519-
SymbolDisplay.ToDisplayString(iAsyncEnumerableOfTupleType)
520-
);
521-
#pragma warning restore RS1035
462+
if (v3)
463+
validSymbols += ", 'IAsyncEnumerable<object[]>', 'IEnumerable<ITheoryDataRow>', 'IAsyncEnumerable<ITheoryDataRow>', 'IEnumerable<ITuple>', or 'IAsyncEnumerable<ITuple>'";
522464

523465
context.ReportDiagnostic(
524466
Diagnostic.Create(
@@ -1039,60 +981,13 @@ static bool VerifyDataSourceReturnType(
1039981
XunitContext xunitContext,
1040982
ITypeSymbol memberType,
1041983
ImmutableDictionary<string, string?> memberProperties,
1042-
AttributeSyntax attributeSyntax,
1043-
INamedTypeSymbol? iEnumerableOfTheoryDataRowType,
1044-
INamedTypeSymbol? iAsyncEnumerableOfTheoryDataRowType,
1045-
INamedTypeSymbol? iEnumerableOfTupleType,
1046-
INamedTypeSymbol? iAsyncEnumerableOfTupleType)
984+
AttributeSyntax attributeSyntax)
1047985
{
1048986
var v3 = xunitContext.HasV3References;
1049-
var iEnumerableOfObjectArrayType = TypeSymbolFactory.IEnumerableOfObjectArray(compilation);
1050-
var iAsyncEnumerableOfObjectArrayType = TypeSymbolFactory.IAsyncEnumerableOfObjectArray(compilation);
1051-
1052-
var valid = iEnumerableOfObjectArrayType.IsAssignableFrom(memberType);
1053-
1054-
// Special-case handling for IEnumerable<T> where T is not object[]. If T is any array type, it is assignable to object[] and therefore valid.
1055-
var memberEnumerableType = memberType.GetEnumerableType();
1056-
if (!valid && memberEnumerableType is not null)
1057-
valid = memberEnumerableType.TypeKind == TypeKind.Array;
1058-
1059-
if (!valid && v3 && iAsyncEnumerableOfObjectArrayType is not null)
1060-
valid = iAsyncEnumerableOfObjectArrayType.IsAssignableFrom(memberType);
1061-
1062-
if (!valid && v3 && iEnumerableOfTheoryDataRowType is not null)
1063-
valid = iEnumerableOfTheoryDataRowType.IsAssignableFrom(memberType);
1064-
1065-
if (!valid && v3 && iAsyncEnumerableOfTheoryDataRowType is not null)
1066-
valid = iAsyncEnumerableOfTheoryDataRowType.IsAssignableFrom(memberType);
1067-
1068-
if (!valid && v3 && iEnumerableOfTupleType is not null)
1069-
valid = iEnumerableOfTupleType.IsAssignableFrom(memberType);
1070-
1071-
if (!valid && v3 && iAsyncEnumerableOfTupleType is not null)
1072-
valid = iAsyncEnumerableOfTupleType.IsAssignableFrom(memberType);
1073-
1074-
// If still invalid and the member is a class, check whether it implements IEnumerable<T> and verify that T is an array type.
1075-
// This ensures the data can be safely converted to IEnumerable<object[]>.
1076-
if (!valid && memberType.TypeKind == TypeKind.Class)
1077-
{
1078-
var resolvedIEnumerableTypeArgument = FindTypeArgumentFromIEnumerable(memberType, compilation);
1079-
if (resolvedIEnumerableTypeArgument is not null)
1080-
valid = (resolvedIEnumerableTypeArgument.TypeKind == TypeKind.Array);
1081-
}
987+
var valid = memberType.IsValidDataSource(v3, compilation);
1082988

1083989
if (!valid)
1084-
ReportIncorrectReturnType(
1085-
context,
1086-
iEnumerableOfObjectArrayType,
1087-
iAsyncEnumerableOfObjectArrayType,
1088-
iEnumerableOfTheoryDataRowType,
1089-
iAsyncEnumerableOfTheoryDataRowType,
1090-
iEnumerableOfTupleType,
1091-
iAsyncEnumerableOfTupleType,
1092-
attributeSyntax,
1093-
memberProperties,
1094-
memberType
1095-
);
990+
ReportIncorrectReturnType(context, v3, attributeSyntax, memberProperties, memberType);
1096991

1097992
return valid;
1098993
}

0 commit comments

Comments
 (0)