Skip to content

Commit

Permalink
Update xUnit1007 to recognize IAsyncEnumerable and ITheoryDataRow in …
Browse files Browse the repository at this point in the history
…v3 projects
  • Loading branch information
bradwilson committed May 21, 2024
1 parent 2100542 commit 347e9fb
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp;
using Xunit;
using Verify = CSharpVerifier<Xunit.Analyzers.ClassDataAttributeMustPointAtValidClass>;

Expand Down Expand Up @@ -28,6 +29,44 @@ class DataClass: IEnumerable<object[]> {
await Verify.VerifyAnalyzer(new[] { TestMethodSource, dataClassSource });
}

public static TheoryData<string> SuccessCasesV3Data = new()
{
// IAsyncEnumerable<object[]>
@"
using System.Collections.Generic;
using System.Threading;
public class DataClass : IAsyncEnumerable<object[]> {
public IAsyncEnumerator<object[]> GetAsyncEnumerator(CancellationToken cancellationToken = default) => null;
}",
// IEnumerable<ITheoryDataRow>
@"
using System.Collections;
using System.Collections.Generic;
using Xunit;
class DataClass: IEnumerable<ITheoryDataRow> {
public IEnumerator<ITheoryDataRow> GetEnumerator() => null;
IEnumerator IEnumerable.GetEnumerator() => null;
}",
// IAsyncEnumerable<ITheoryDataRow>
@"
using System.Collections.Generic;
using System.Threading;
using Xunit;
public class DataClass : IAsyncEnumerable<ITheoryDataRow> {
public IAsyncEnumerator<ITheoryDataRow> GetAsyncEnumerator(CancellationToken cancellationToken = default) => null;
}",
};

[Theory]
[MemberData(nameof(SuccessCasesV3Data))]
public async Task SuccessCases_V3(string dataClassSource)
{
await Verify.VerifyAnalyzerV3(LanguageVersion.CSharp8, new[] { TestMethodSource, dataClassSource });
}

public static TheoryData<string> FailureCases = new()
{
// Incorrect enumeration type (object instead of object[])
Expand Down Expand Up @@ -84,12 +123,38 @@ private DataClass() { }
[MemberData(nameof(FailureCases))]
public async Task FailureCase(string dataClassSource)
{
var expected =
var expectedV2 =
Verify
.Diagnostic()
.WithSpan(6, 23, 6, 32)
.WithArguments("DataClass", "IEnumerable<object[]>");
var expectedV3 =
Verify
.Diagnostic()
.WithSpan(6, 23, 6, 32)
.WithArguments("DataClass", "IEnumerable<object[]>, IAsyncEnumerable<object[]>, IEnumerable<ITheoryDataRow>, or IAsyncEnumerable<ITheoryDataRow>");

await Verify.VerifyAnalyzerV2(new[] { TestMethodSource, dataClassSource }, expectedV2);
await Verify.VerifyAnalyzerV3(new[] { TestMethodSource, dataClassSource }, expectedV3);
}

[Fact]
public async Task IAsyncEnumerableSupportedOnlyInV3()
{
var dataClassSource = @"
using System.Collections.Generic;
using System.Threading;
public class DataClass : IAsyncEnumerable<object[]> {
public IAsyncEnumerator<object[]> GetAsyncEnumerator(CancellationToken cancellationToken = default) => null;
}";
var expectedV2 =
Verify
.Diagnostic()
.WithSpan(6, 23, 6, 32)
.WithArguments("DataClass");
.WithArguments("DataClass", "IEnumerable<object[]>");

await Verify.VerifyAnalyzer(new[] { TestMethodSource, dataClassSource }, expected);
await Verify.VerifyAnalyzerV2(LanguageVersion.CSharp8, new[] { TestMethodSource, dataClassSource }, expectedV2);
await Verify.VerifyAnalyzerV3(LanguageVersion.CSharp8, new[] { TestMethodSource, dataClassSource });
}
}
2 changes: 2 additions & 0 deletions src/xunit.analyzers.tests/Utility/CodeAnalyzerHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ static CodeAnalyzerHelper()

CurrentXunitV2 = defaultAssemblies.AddPackages(
ImmutableArray.Create(
new PackageIdentity("Microsoft.Bcl.AsyncInterfaces", "8.0.0"),
new PackageIdentity("Microsoft.Extensions.Primitives", "8.0.0"),
new PackageIdentity("System.Collections.Immutable", "1.6.0"),
new PackageIdentity("System.Threading.Tasks.Extensions", "4.5.4"),
Expand All @@ -45,6 +46,7 @@ static CodeAnalyzerHelper()

CurrentXunitV2RunnerUtility = defaultAssemblies.AddPackages(
ImmutableArray.Create(
new PackageIdentity("Microsoft.Bcl.AsyncInterfaces", "8.0.0"),
new PackageIdentity("Microsoft.Extensions.Primitives", "8.0.0"),
new PackageIdentity("System.Collections.Immutable", "1.6.0"),
new PackageIdentity("System.Threading.Tasks.Extensions", "4.5.4"),
Expand Down
2 changes: 1 addition & 1 deletion src/xunit.analyzers/Utility/Descriptors.xUnit1xxx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public static partial class Descriptors
"ClassData must point at a valid class",
Usage,
Error,
"ClassData must point at a valid class. The class {0} must be public, not sealed, with an empty constructor, and implement IEnumerable<object[]>."
"ClassData must point at a valid class. The class {0} must be public, not sealed, with an empty constructor, and implement {1}."
);

public static DiagnosticDescriptor X1008_DataAttributeShouldBeUsedOnATheory { get; } =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ namespace Xunit.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class ClassDataAttributeMustPointAtValidClass : XunitDiagnosticAnalyzer
{
const string typesV2 = "IEnumerable<object[]>";
const string typesV3 = "IEnumerable<object[]>, IAsyncEnumerable<object[]>, IEnumerable<ITheoryDataRow>, or IAsyncEnumerable<ITheoryDataRow>";

public ClassDataAttributeMustPointAtValidClass() :
base(Descriptors.X1007_ClassDataAttributeMustPointAtValidClass)
{ }
Expand All @@ -22,6 +25,9 @@ public override void AnalyzeCompilation(

var compilation = context.Compilation;
var iEnumerableOfObjectArray = TypeSymbolFactory.IEnumerableOfObjectArray(compilation);
var iEnumerableOfTheoryDataRow = TypeSymbolFactory.IEnumerableOfITheoryDataRow(compilation);
var iAsyncEnumerableOfObjectArray = TypeSymbolFactory.IAsyncEnumerableOfObjectArray(compilation);
var iAsyncEnumerableOfTheoryDataRow = TypeSymbolFactory.IAsyncEnumerableOfITheoryDataRow(compilation);

context.RegisterSyntaxNodeAction(context =>
{
Expand All @@ -40,6 +46,16 @@ public override void AnalyzeCompilation(
return;

var missingInterface = !iEnumerableOfObjectArray.IsAssignableFrom(classType);
if (xunitContext.HasV3References)
{
if (missingInterface && iEnumerableOfTheoryDataRow is not null)
missingInterface = !iEnumerableOfTheoryDataRow.IsAssignableFrom(classType);
if (missingInterface && iAsyncEnumerableOfObjectArray is not null)
missingInterface = !iAsyncEnumerableOfObjectArray.IsAssignableFrom(classType);
if (missingInterface && iAsyncEnumerableOfTheoryDataRow is not null)
missingInterface = !iAsyncEnumerableOfTheoryDataRow.IsAssignableFrom(classType);
}

var isAbstract = classType.IsAbstract;
var noValidConstructor = !classType.InstanceConstructors.Any(c => c.Parameters.IsEmpty && c.DeclaredAccessibility == Accessibility.Public);

Expand All @@ -48,7 +64,8 @@ public override void AnalyzeCompilation(
Diagnostic.Create(
Descriptors.X1007_ClassDataAttributeMustPointAtValidClass,
argumentExpression.Type.GetLocation(),
classType.Name
classType.Name,
xunitContext.HasV3References ? typesV3 : typesV2
)
);
}, SyntaxKind.Attribute);
Expand Down

0 comments on commit 347e9fb

Please sign in to comment.