From 347e9fb7269bdfbb2935f5ca2fb31626b7c63d05 Mon Sep 17 00:00:00 2001 From: Brad Wilson Date: Tue, 21 May 2024 10:32:13 -0700 Subject: [PATCH] Update xUnit1007 to recognize IAsyncEnumerable and ITheoryDataRow in v3 projects --- ...DataAttributeMustPointAtValidClassTests.cs | 71 ++++++++++++++++++- .../Utility/CodeAnalyzerHelper.cs | 2 + .../Utility/Descriptors.xUnit1xxx.cs | 2 +- ...ClassDataAttributeMustPointAtValidClass.cs | 19 ++++- 4 files changed, 89 insertions(+), 5 deletions(-) diff --git a/src/xunit.analyzers.tests/Analyzers/X1000/ClassDataAttributeMustPointAtValidClassTests.cs b/src/xunit.analyzers.tests/Analyzers/X1000/ClassDataAttributeMustPointAtValidClassTests.cs index ea38849a..0efd28f3 100644 --- a/src/xunit.analyzers.tests/Analyzers/X1000/ClassDataAttributeMustPointAtValidClassTests.cs +++ b/src/xunit.analyzers.tests/Analyzers/X1000/ClassDataAttributeMustPointAtValidClassTests.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp; using Xunit; using Verify = CSharpVerifier; @@ -28,6 +29,44 @@ class DataClass: IEnumerable { await Verify.VerifyAnalyzer(new[] { TestMethodSource, dataClassSource }); } + public static TheoryData SuccessCasesV3Data = new() + { + // IAsyncEnumerable + @" +using System.Collections.Generic; +using System.Threading; + +public class DataClass : IAsyncEnumerable { + public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) => null; +}", + // IEnumerable + @" +using System.Collections; +using System.Collections.Generic; +using Xunit; + +class DataClass: IEnumerable { + public IEnumerator GetEnumerator() => null; + IEnumerator IEnumerable.GetEnumerator() => null; +}", + // IAsyncEnumerable + @" +using System.Collections.Generic; +using System.Threading; +using Xunit; + +public class DataClass : IAsyncEnumerable { + public IAsyncEnumerator 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 FailureCases = new() { // Incorrect enumeration type (object instead of object[]) @@ -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"); + var expectedV3 = + Verify + .Diagnostic() + .WithSpan(6, 23, 6, 32) + .WithArguments("DataClass", "IEnumerable, IAsyncEnumerable, IEnumerable, or IAsyncEnumerable"); + + 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 { + public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) => null; +}"; + var expectedV2 = Verify .Diagnostic() .WithSpan(6, 23, 6, 32) - .WithArguments("DataClass"); + .WithArguments("DataClass", "IEnumerable"); - await Verify.VerifyAnalyzer(new[] { TestMethodSource, dataClassSource }, expected); + await Verify.VerifyAnalyzerV2(LanguageVersion.CSharp8, new[] { TestMethodSource, dataClassSource }, expectedV2); + await Verify.VerifyAnalyzerV3(LanguageVersion.CSharp8, new[] { TestMethodSource, dataClassSource }); } } diff --git a/src/xunit.analyzers.tests/Utility/CodeAnalyzerHelper.cs b/src/xunit.analyzers.tests/Utility/CodeAnalyzerHelper.cs index 0a7d7ad1..667089ff 100644 --- a/src/xunit.analyzers.tests/Utility/CodeAnalyzerHelper.cs +++ b/src/xunit.analyzers.tests/Utility/CodeAnalyzerHelper.cs @@ -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"), @@ -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"), diff --git a/src/xunit.analyzers/Utility/Descriptors.xUnit1xxx.cs b/src/xunit.analyzers/Utility/Descriptors.xUnit1xxx.cs index e3551cd0..35948d30 100644 --- a/src/xunit.analyzers/Utility/Descriptors.xUnit1xxx.cs +++ b/src/xunit.analyzers/Utility/Descriptors.xUnit1xxx.cs @@ -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." + "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; } = diff --git a/src/xunit.analyzers/X1000/ClassDataAttributeMustPointAtValidClass.cs b/src/xunit.analyzers/X1000/ClassDataAttributeMustPointAtValidClass.cs index fcecf221..22f048ea 100644 --- a/src/xunit.analyzers/X1000/ClassDataAttributeMustPointAtValidClass.cs +++ b/src/xunit.analyzers/X1000/ClassDataAttributeMustPointAtValidClass.cs @@ -9,6 +9,9 @@ namespace Xunit.Analyzers; [DiagnosticAnalyzer(LanguageNames.CSharp)] public class ClassDataAttributeMustPointAtValidClass : XunitDiagnosticAnalyzer { + const string typesV2 = "IEnumerable"; + const string typesV3 = "IEnumerable, IAsyncEnumerable, IEnumerable, or IAsyncEnumerable"; + public ClassDataAttributeMustPointAtValidClass() : base(Descriptors.X1007_ClassDataAttributeMustPointAtValidClass) { } @@ -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 => { @@ -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); @@ -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);