From d0e74e48e07c1f76acf219ffda86239fa4afd39a Mon Sep 17 00:00:00 2001 From: Brad Wilson Date: Thu, 18 Jul 2024 13:29:11 -0700 Subject: [PATCH] Update xUnit1041 to support [Collection(typeof(T))] and [Collection] --- .../X1000/EnsureFixturesHaveASourceTests.cs | 44 +++++++++------ .../X1000/EnsureFixturesHaveASource.cs | 55 +++++++++++-------- 2 files changed, 61 insertions(+), 38 deletions(-) diff --git a/src/xunit.analyzers.tests/Analyzers/X1000/EnsureFixturesHaveASourceTests.cs b/src/xunit.analyzers.tests/Analyzers/X1000/EnsureFixturesHaveASourceTests.cs index ba1cb0c4..5d262f05 100644 --- a/src/xunit.analyzers.tests/Analyzers/X1000/EnsureFixturesHaveASourceTests.cs +++ b/src/xunit.analyzers.tests/Analyzers/X1000/EnsureFixturesHaveASourceTests.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp; using Xunit; using Verify = CSharpVerifier; @@ -177,31 +178,42 @@ [Fact] public void TestMethod() {{ }} await Verify.VerifyAnalyzer(source); } - [Fact] - public async Task WithInheritedFixture_DoesNotTrigger() + [Theory] + [InlineData("[CollectionDefinition(nameof(TestCollection))]", "[Collection(nameof(TestCollection))]", true)] + [InlineData("", "[Collection(typeof(TestCollection))]", false)] +#if NETCOREAPP && ROSLYN_4_4_OR_GREATER // C# 11 is required for generic attributes + [InlineData("", "[Collection]", false, LanguageVersion.CSharp11)] +#endif + public async Task WithInheritedFixture_DoesNotTrigger( + string collectionDefinition, + string collectionReference, + bool supportedByV2, + LanguageVersion? languageVersion = null) { - var source = /* lang=c#-test */ """ + var source = string.Format(/* lang=c#-test */ """ using Xunit; - public class Fixture { } + public class Fixture {{ }} - [CollectionDefinition("test")] - public class TestCollection : ICollectionFixture { } + {0} + public class TestCollection : ICollectionFixture {{ }} - public abstract class TestContext { - protected TestContext(Fixture fixture) { } - } + public abstract class TestContext {{ + protected TestContext(Fixture fixture) {{ }} + }} - [Collection("test")] - public class TestClass : TestContext { - public TestClass(Fixture fixture) : base(fixture) { } + {1} + public class TestClass : TestContext {{ + public TestClass(Fixture fixture) : base(fixture) {{ }} [Fact] - public void TestMethod() { } - } - """; + public void TestMethod() {{ }} + }} + """, collectionDefinition, collectionReference); - await Verify.VerifyAnalyzer(source); + if (supportedByV2) + await Verify.VerifyAnalyzerV2(languageVersion ?? LanguageVersion.CSharp6, source); + await Verify.VerifyAnalyzerV3(languageVersion ?? LanguageVersion.CSharp6, source); } [Fact] diff --git a/src/xunit.analyzers/X1000/EnsureFixturesHaveASource.cs b/src/xunit.analyzers/X1000/EnsureFixturesHaveASource.cs index bb915915..25b43391 100644 --- a/src/xunit.analyzers/X1000/EnsureFixturesHaveASource.cs +++ b/src/xunit.analyzers/X1000/EnsureFixturesHaveASource.cs @@ -40,14 +40,24 @@ public override void AnalyzeCompilation( // Get the collection name from [Collection], if present var collectionAttributeType = xunitContext.Core.CollectionAttributeType; - string? collectionDefinitionName = null; + object? collectionDefinition = null; - for (var type = namedType; type is not null && collectionDefinitionName is null; type = type.BaseType) - collectionDefinitionName = + for (var type = namedType; type is not null && collectionDefinition is null; type = type.BaseType) + { + var attribute = type .GetAttributes() - .FirstOrDefault(a => a.AttributeClass.IsAssignableFrom(collectionAttributeType)) - ?.ConstructorArguments.FirstOrDefault().Value?.ToString(); + .FirstOrDefault(a => a.AttributeClass.IsAssignableFrom(collectionAttributeType)); + if (attribute is null) + continue; + + if (attribute.AttributeClass?.IsGenericType == true) + // Using [Collection] + collectionDefinition = attribute.AttributeClass.TypeArguments.FirstOrDefault(); + else + // Using [Collection("name"))] or [Collection(typeof(T))] + collectionDefinition = attribute.ConstructorArguments.FirstOrDefault().Value; + } // Need to construct a full set of types we know can be resolved. Start with things // like ITestOutputHelper and ITestContextAccessor (since they're injected by the framework) @@ -67,8 +77,8 @@ public override void AnalyzeCompilation( ); // Add types from IClassFixture<> and ICollectionFixture<> on the collection definition - var collectionFixtureTypes = ImmutableHashSet.Empty; - if (collectionDefinitionName is not null) + var matchingType = collectionDefinition as INamedTypeSymbol; + if (matchingType is null && collectionDefinition is string collectionDefinitionName) { var collectionDefinitionAttributeType = xunitContext.Core.CollectionDefinitionAttributeType; @@ -79,24 +89,25 @@ bool MatchCollectionDefinition(INamedTypeSymbol symbol) => a.ConstructorArguments[0].Value?.ToString() == collectionDefinitionName ); - var matchingType = namedType.ContainingAssembly.FindNamedType(MatchCollectionDefinition); - if (matchingType is not null) + matchingType = namedType.ContainingAssembly.FindNamedType(MatchCollectionDefinition); + } + + if (matchingType is not null) + { + var collectionFixtureType = xunitContext.Core.ICollectionFixtureType?.ConstructUnboundGenericType(); + foreach (var @interface in matchingType.AllInterfaces.Where(i => i.IsGenericType)) { - var collectionFixtureType = xunitContext.Core.ICollectionFixtureType?.ConstructUnboundGenericType(); - foreach (var @interface in matchingType.AllInterfaces.Where(i => i.IsGenericType)) + var unboundGeneric = @interface.ConstructUnboundGenericType(); + if (SymbolEqualityComparer.Default.Equals(classFixtureType, unboundGeneric) + || SymbolEqualityComparer.Default.Equals(collectionFixtureType, unboundGeneric)) { - var unboundGeneric = @interface.ConstructUnboundGenericType(); - if (SymbolEqualityComparer.Default.Equals(classFixtureType, unboundGeneric) - || SymbolEqualityComparer.Default.Equals(collectionFixtureType, unboundGeneric)) + var fixtureTypeSymbol = @interface.TypeArguments.First(); + if (fixtureTypeSymbol is INamedTypeSymbol namedFixtureType) { - var fixtureTypeSymbol = @interface.TypeArguments.First(); - if (fixtureTypeSymbol is INamedTypeSymbol namedFixtureType) - { - if (xunitContext.HasV3References && namedFixtureType.IsGenericType && namedFixtureType.TypeArguments.Any(t => t is ITypeParameterSymbol)) - namedFixtureType = namedFixtureType.ConstructedFrom; - - validConstructorArgumentTypes.Add(namedFixtureType); - } + if (xunitContext.HasV3References && namedFixtureType.IsGenericType && namedFixtureType.TypeArguments.Any(t => t is ITypeParameterSymbol)) + namedFixtureType = namedFixtureType.ConstructedFrom; + + validConstructorArgumentTypes.Add(namedFixtureType); } } }