Skip to content

Commit 63109b7

Browse files
committed
xunit/xunit#3174: xUnit1003 doesn't detect IDataAttribute
1 parent 40c3dec commit 63109b7

File tree

5 files changed

+63
-4
lines changed

5 files changed

+63
-4
lines changed

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

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Threading.Tasks;
2+
using Microsoft.CodeAnalysis.CSharp;
23
using Xunit;
34
using Verify = CSharpVerifier<Xunit.Analyzers.TheoryMethodMustHaveTestData>;
45

@@ -43,6 +44,48 @@ public void TestMethod3() { }
4344
await Verify.VerifyAnalyzer(source);
4445
}
4546

47+
[Fact]
48+
public async Task TheoryMethodWithCustomDataAttribute_v3_DoesNotTrigger()
49+
{
50+
var source = /* lang=c#-test */ """
51+
using System;
52+
using System.Collections.Generic;
53+
using System.Reflection;
54+
using System.Threading.Tasks;
55+
using Xunit;
56+
using Xunit.Sdk;
57+
using Xunit.v3;
58+
59+
public class TestClass {
60+
[Theory]
61+
[MyData]
62+
public void TestMethod() { }
63+
}
64+
65+
public class MyData : Attribute, IDataAttribute {
66+
public bool? Explicit => throw new NotImplementedException();
67+
public string? Label => throw new NotImplementedException();
68+
public string? Skip => throw new NotImplementedException();
69+
public Type? SkipType => throw new NotImplementedException();
70+
public string? SkipUnless => throw new NotImplementedException();
71+
public string? SkipWhen => throw new NotImplementedException();
72+
public string? TestDisplayName => throw new NotImplementedException();
73+
public int? Timeout => throw new NotImplementedException();
74+
public string[]? Traits => throw new NotImplementedException();
75+
76+
public ValueTask<IReadOnlyCollection<ITheoryDataRow>> GetData(
77+
MethodInfo testMethod,
78+
DisposalTracker disposalTracker) =>
79+
throw new NotImplementedException();
80+
81+
public bool SupportsDiscoveryEnumeration() =>
82+
throw new NotImplementedException();
83+
}
84+
""";
85+
86+
await Verify.VerifyAnalyzerV3(LanguageVersion.CSharp8, source);
87+
}
88+
4689
[Fact]
4790
public async Task TheoryMethodMissingData_Triggers()
4891
{

src/xunit.analyzers/Utility/Constants.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ public static class Xunit
143143
public const string IAttributeInfo_V2 = "Xunit.Abstractions.IAttributeInfo";
144144
public const string IClassFixtureOfT = "Xunit.IClassFixture`1";
145145
public const string ICollectionFixtureOfT = "Xunit.ICollectionFixture`1";
146+
public const string IDataAttribute_V3 = "Xunit.v3.IDataAttribute";
146147
public const string IMessageSink_V2 = "Xunit.Abstractions.IMessageSink";
147148
public const string IMessageSink_V3 = "Xunit.Sdk.IMessageSink";
148149
public const string IMessageSinkMessage_V2 = "Xunit.Abstractions.IMessageSinkMessage";

src/xunit.analyzers/Utility/TypeSymbolFactory.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,9 @@ public static INamedTypeSymbol ICollectionOfT(Compilation compilation) =>
145145
public static INamedTypeSymbol? ICriticalNotifyCompletion(Compilation compilation) =>
146146
Guard.ArgumentNotNull(compilation).GetTypeByMetadataName("System.Runtime.CompilerServices.ICriticalNotifyCompletion");
147147

148+
public static INamedTypeSymbol? IDataAttribute_V3(Compilation compilation) =>
149+
Guard.ArgumentNotNull(compilation).GetTypeByMetadataName(Constants.Types.Xunit.IDataAttribute_V3);
150+
148151
public static INamedTypeSymbol IDisposable(Compilation compilation) =>
149152
Guard.ArgumentNotNull(compilation).GetSpecialType(SpecialType.System_IDisposable);
150153

src/xunit.analyzers/Utility/V3CoreContext.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public class V3CoreContext : ICoreContext
1616
readonly Lazy<INamedTypeSymbol?> lazyFactAttributeType;
1717
readonly Lazy<INamedTypeSymbol?> lazyIClassFixtureType;
1818
readonly Lazy<INamedTypeSymbol?> lazyICollectionFixtureType;
19+
readonly Lazy<INamedTypeSymbol?> lazyIDataAttributeType;
1920
readonly Lazy<INamedTypeSymbol?> lazyInlineDataAttributeType;
2021
readonly Lazy<INamedTypeSymbol?> lazyITestContextAccessorType;
2122
readonly Lazy<INamedTypeSymbol?> lazyITestOutputHelperType;
@@ -39,6 +40,7 @@ public class V3CoreContext : ICoreContext
3940
lazyFactAttributeType = new(() => TypeSymbolFactory.FactAttribute(compilation));
4041
lazyIClassFixtureType = new(() => TypeSymbolFactory.IClassFixureOfT(compilation));
4142
lazyICollectionFixtureType = new(() => TypeSymbolFactory.ICollectionFixtureOfT(compilation));
43+
lazyIDataAttributeType = new(() => TypeSymbolFactory.IDataAttribute_V3(compilation));
4244
lazyInlineDataAttributeType = new(() => TypeSymbolFactory.InlineDataAttribute(compilation));
4345
lazyITestContextAccessorType = new(() => TypeSymbolFactory.ITestContextAccessor_V3(compilation));
4446
lazyITestOutputHelperType = new(() => TypeSymbolFactory.ITestOutputHelper_V3(compilation));
@@ -93,6 +95,12 @@ public class V3CoreContext : ICoreContext
9395
public INamedTypeSymbol? ICollectionFixtureType =>
9496
lazyICollectionFixtureType.Value;
9597

98+
/// <summary>
99+
/// Gets a reference to type <c>IDataAttribute</c>, if available.
100+
/// </summary>
101+
public INamedTypeSymbol? IDataAttributeType =>
102+
lazyIDataAttributeType.Value;
103+
96104
/// <inheritdoc/>
97105
public INamedTypeSymbol? InlineDataAttributeType =>
98106
lazyInlineDataAttributeType.Value;

src/xunit.analyzers/X1000/TheoryMethodMustHaveTestData.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,20 @@ public override void AnalyzeCompilation(
2727
return;
2828

2929
var attributes = symbol.GetAttributes();
30-
if (attributes.ContainsAttributeType(xunitContext.Core.TheoryAttributeType) &&
31-
(attributes.Length == 1 || !attributes.ContainsAttributeType(xunitContext.Core.DataAttributeType)))
32-
{
30+
if (!attributes.ContainsAttributeType(xunitContext.Core.TheoryAttributeType))
31+
return;
32+
33+
var hasData = attributes.ContainsAttributeType(xunitContext.Core.DataAttributeType);
34+
if (!hasData && xunitContext.V3Core?.IDataAttributeType is not null)
35+
hasData = attributes.ContainsAttributeType(xunitContext.V3Core.IDataAttributeType);
36+
37+
if (!hasData)
3338
context.ReportDiagnostic(
3439
Diagnostic.Create(
3540
Descriptors.X1003_TheoryMethodMustHaveTestData,
3641
symbol.Locations.First()
3742
)
3843
);
39-
}
4044
}, SymbolKind.Method);
4145
}
4246
}

0 commit comments

Comments
 (0)