diff --git a/src/xunit.analyzers.tests/Analyzers/X1000/EnsureFixturesHaveASourceTests.cs b/src/xunit.analyzers.tests/Analyzers/X1000/EnsureFixturesHaveASourceTests.cs index 00bc5ee7..e037031a 100644 --- a/src/xunit.analyzers.tests/Analyzers/X1000/EnsureFixturesHaveASourceTests.cs +++ b/src/xunit.analyzers.tests/Analyzers/X1000/EnsureFixturesHaveASourceTests.cs @@ -186,11 +186,11 @@ public class Fixture { } [CollectionDefinition(""test"")] public class TestCollection : ICollectionFixture { } -[Collection(""test"")] public abstract class TestContext { protected TestContext(Fixture fixture) { } } +[Collection(""test"")] public class TestClass : TestContext { public TestClass(Fixture fixture) : base(fixture) { } @@ -201,6 +201,68 @@ public void TestMethod() { } await Verify.VerifyAnalyzer(source); } + [Fact] + public async void WithGenericFixture_TriggersWithV2_DoesNotTriggerWithV3() + { + var source = @" +using Xunit; + +public class Fixture { } + +[CollectionDefinition(""test"")] +public class TestCollection : ICollectionFixture> { } + +[Collection(""test"")] +public class TestClass { + public TestClass(Fixture fixture) { } + + [Fact] + public void TestMethod() { } +}"; + + var expectedV2 = + Verify + .Diagnostic() + .WithSpan(11, 35, 11, 42) + .WithArguments("fixture"); + + await Verify.VerifyAnalyzerV2(source, expectedV2); + await Verify.VerifyAnalyzerV3(source); + } + + [Fact] + public async void WithInheritedGenericFixture_TriggersWithV2_DoesNotTriggerWithV3() + { + var source = @" +using Xunit; + +public class Fixture { } + +[CollectionDefinition(""test"")] +public class TestCollection : ICollectionFixture> { } + +[Collection(""test"")] +public abstract class TestContext { + protected TestContext(Fixture fixture) { } +} + +public class TestClass : TestContext { + public TestClass(Fixture fixture) : base(fixture) { } + + [Fact] + public void TestMethod() { } +}"; + + var expectedV2 = + Verify + .Diagnostic() + .WithSpan(15, 35, 15, 42) + .WithArguments("fixture"); + + await Verify.VerifyAnalyzerV2(source, expectedV2); + await Verify.VerifyAnalyzerV3(source); + } + [Theory] [InlineData("[Collection(nameof(TestCollection))]", "")] [InlineData("", "[Collection(nameof(TestCollection))]")] diff --git a/src/xunit.analyzers/X1000/EnsureFixturesHaveASource.cs b/src/xunit.analyzers/X1000/EnsureFixturesHaveASource.cs index 0a0d3646..b17f2cc7 100644 --- a/src/xunit.analyzers/X1000/EnsureFixturesHaveASource.cs +++ b/src/xunit.analyzers/X1000/EnsureFixturesHaveASource.cs @@ -91,8 +91,17 @@ bool MatchCollectionDefinition(INamedTypeSymbol symbol) => { var unboundGeneric = @interface.ConstructUnboundGenericType(); if (SymbolEqualityComparer.Default.Equals(classFixtureType, unboundGeneric) - || SymbolEqualityComparer.Default.Equals(collectionFixtureType, unboundGeneric)) - validConstructorArgumentTypes.Add(@interface.TypeArguments.First()); + || SymbolEqualityComparer.Default.Equals(collectionFixtureType, unboundGeneric)) + { + 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); + } + } } } } @@ -108,7 +117,9 @@ bool MatchCollectionDefinition(INamedTypeSymbol symbol) => .Select(a => a.ConstructorArguments[0].Value as ITypeSymbol) ); - foreach (var parameter in ctors[0].Parameters.Where(p => !p.IsOptional && !validConstructorArgumentTypes.Contains(p.Type))) + foreach (var parameter in ctors[0].Parameters.Where(p => !p.IsOptional + && !validConstructorArgumentTypes.Contains(p.Type) + && (xunitContext.HasV2References || p.Type is not INamedTypeSymbol nts || !nts.IsGenericType || !validConstructorArgumentTypes.Contains(nts.ConstructedFrom)))) context.ReportDiagnostic( Diagnostic.Create( Descriptors.X1041_EnsureFixturesHaveASource,