diff --git a/src/xunit.analyzers.tests/Analyzers/X1000/TheoryDataRowArgumentsShouldBeSerializableTests.cs b/src/xunit.analyzers.tests/Analyzers/X1000/TheoryDataRowArgumentsShouldBeSerializableTests.cs index fc05207c..b106120c 100644 --- a/src/xunit.analyzers.tests/Analyzers/X1000/TheoryDataRowArgumentsShouldBeSerializableTests.cs +++ b/src/xunit.analyzers.tests/Analyzers/X1000/TheoryDataRowArgumentsShouldBeSerializableTests.cs @@ -49,6 +49,8 @@ public void TestMethod() { [InlineData("new Dictionary>()", "Dictionary>")] #if NET6_0_OR_GREATER [InlineData("DateOnly.MinValue", "DateOnly")] + [InlineData("Index.Start", "Index")] + [InlineData("Range.All", "Range")] [InlineData("TimeOnly.MinValue", "TimeOnly")] #endif [InlineData("Guid.Empty", "Guid")] diff --git a/src/xunit.analyzers.tests/Analyzers/X1000/TheoryDataTypeArgumentsShouldBeSerializableTests.cs b/src/xunit.analyzers.tests/Analyzers/X1000/TheoryDataTypeArgumentsShouldBeSerializableTests.cs index 1c99ec88..7c21d80d 100644 --- a/src/xunit.analyzers.tests/Analyzers/X1000/TheoryDataTypeArgumentsShouldBeSerializableTests.cs +++ b/src/xunit.analyzers.tests/Analyzers/X1000/TheoryDataTypeArgumentsShouldBeSerializableTests.cs @@ -207,33 +207,6 @@ public enum SerializableEnumeration {{ Zero }} await Verify.VerifyAnalyzer(source); } - [Theory] - [MemberData(nameof(TheoryDataMembers), "Guid")] - [MemberData(nameof(TheoryDataMembers), "Guid?")] - [MemberData(nameof(TheoryDataMembers), "Uri")] - public async Task GivenTheory_WithTypeOnlySupportedInV3_TriggersInV2_DoesNotTriggerInV3( - string member, - string attribute, - string type) - { - var source = string.Format(/* lang=c#-test */ """ - using System; - using Xunit; - - public class TestClass {{ - {0} - - [Theory] - [{{|#0:{1}|}}] - public void TestMethod({2} parameter) {{ }} - }} - """, member, attribute, type); - var expectedV2 = Verify.Diagnostic(type == "Uri" ? "xUnit1045" : "xUnit1044").WithLocation(0).WithArguments(type); - - await Verify.VerifyAnalyzerV2(source, expectedV2); - await Verify.VerifyAnalyzerV3(source); - } - [Theory] [MemberData(nameof(TheoryDataMembers), "IXunitSerializable")] [MemberData(nameof(TheoryDataMembers), "IXunitSerializable[]")] @@ -434,6 +407,36 @@ public async Task GivenTheory_WithNonSerializableTheoryDataClass_WithDiscoveryEn public sealed class X1044_AvoidUsingTheoryDataTypeArgumentsThatAreNotSerializable : TheoryDataTypeArgumentsShouldBeSerializableTests { + [Theory] + [MemberData(nameof(TheoryDataMembers), "Guid")] + [MemberData(nameof(TheoryDataMembers), "Guid?")] +#if NET6_0_OR_GREATER + [MemberData(nameof(TheoryDataMembers), "Index")] + [MemberData(nameof(TheoryDataMembers), "Range")] +#endif + public async Task GivenTheory_WithTypeOnlySupportedInV3_TriggersInV2_DoesNotTriggerInV3( + string member, + string attribute, + string type) + { + var source = string.Format(/* lang=c#-test */ """ + using System; + using Xunit; + + public class TestClass {{ + {0} + + [Theory] + [{{|#0:{1}|}}] + public void TestMethod({2} parameter) {{ }} + }} + """, member, attribute, type); + var expectedV2 = Verify.Diagnostic("xUnit1044").WithLocation(0).WithArguments(type); + + await Verify.VerifyAnalyzerV2(source, expectedV2); + await Verify.VerifyAnalyzerV3(source); + } + [Theory] [MemberData(nameof(TheoryDataMembers), "Delegate")] [MemberData(nameof(TheoryDataMembers), "Delegate[]")] @@ -492,6 +495,32 @@ public async Task GivenTheory_WithNonSerializableTheoryDataClass_Triggers( public sealed class X1045_AvoidUsingTheoryDataTypeArgumentsThatMightNotBeSerializable : TheoryDataTypeArgumentsShouldBeSerializableTests { + [Theory] + [MemberData(nameof(TheoryDataMembers), "Uri")] + [MemberData(nameof(TheoryDataMembers), "Uri?")] + public async Task GivenTheory_WithTypeOnlySupportedInV3_TriggersInV2_DoesNotTriggerInV3( + string member, + string attribute, + string type) + { + var source = string.Format(/* lang=c#-test */ """ + using System; + using Xunit; + + public class TestClass {{ + {0} + + [Theory] + [{{|#0:{1}|}}] + public void TestMethod({2} parameter) {{ }} + }} + """, member, attribute, type); + var expectedV2 = Verify.Diagnostic("xUnit1045").WithLocation(0).WithArguments(type); + + await Verify.VerifyAnalyzerV2(LanguageVersion.CSharp9, source, expectedV2); + await Verify.VerifyAnalyzerV3(LanguageVersion.CSharp9, source); + } + [Theory] [MemberData(nameof(TheoryDataMembers), "object")] [MemberData(nameof(TheoryDataMembers), "object[]")] diff --git a/src/xunit.analyzers/Utility/SerializabilityAnalyzer.cs b/src/xunit.analyzers/Utility/SerializabilityAnalyzer.cs index 24cfef57..66ec2687 100644 --- a/src/xunit.analyzers/Utility/SerializabilityAnalyzer.cs +++ b/src/xunit.analyzers/Utility/SerializabilityAnalyzer.cs @@ -48,10 +48,10 @@ public Serializability AnalayzeSerializability( if (xunitContext.HasV3References) { - if (type.Equals(typeSymbols.Guid, SymbolEqualityComparer.Default)) - return Serializability.AlwaysSerializable; - - if (type.Equals(typeSymbols.Uri, SymbolEqualityComparer.Default)) + if (type.Equals(typeSymbols.Guid, SymbolEqualityComparer.Default) + || type.Equals(typeSymbols.Index, SymbolEqualityComparer.Default) + || type.Equals(typeSymbols.Range, SymbolEqualityComparer.Default) + || type.Equals(typeSymbols.Uri, SymbolEqualityComparer.Default)) return Serializability.AlwaysSerializable; } diff --git a/src/xunit.analyzers/Utility/SerializableTypeSymbols.cs b/src/xunit.analyzers/Utility/SerializableTypeSymbols.cs index 171b3b35..87b54a8d 100644 --- a/src/xunit.analyzers/Utility/SerializableTypeSymbols.cs +++ b/src/xunit.analyzers/Utility/SerializableTypeSymbols.cs @@ -12,7 +12,9 @@ public sealed class SerializableTypeSymbols readonly Lazy dateOnly; readonly Lazy dateTimeOffset; readonly Lazy guid; + readonly Lazy index; readonly Lazy iXunitSerializable; + readonly Lazy range; readonly Lazy theoryDataBaseType; readonly Dictionary theoryDataTypes; readonly Lazy timeOnly; @@ -37,7 +39,9 @@ public sealed class SerializableTypeSymbols dateOnly = new(() => TypeSymbolFactory.DateOnly(compilation)); dateTimeOffset = new(() => TypeSymbolFactory.DateTimeOffset(compilation)); guid = new(() => TypeSymbolFactory.Guid(compilation)); + index = new(() => TypeSymbolFactory.Index(compilation)); iXunitSerializable = new(() => xunitContext.Common.IXunitSerializableType); + range = new(() => TypeSymbolFactory.Range(compilation)); // For v2 and early versions of v3, the base type is "TheoryData" (non-generic). For later versions // of v3, it's "TheoryDataBase". In either case, getting "TheoryData" // and going up one layer gets us the type we want to be able to search for. @@ -77,8 +81,10 @@ public sealed class SerializableTypeSymbols public INamedTypeSymbol? DateOnly => dateOnly.Value; public INamedTypeSymbol? DateTimeOffset => dateTimeOffset.Value; public INamedTypeSymbol? Guid => guid.Value; + public INamedTypeSymbol? Index => index.Value; public INamedTypeSymbol? IXunitSerializable => iXunitSerializable.Value; public INamedTypeSymbol MemberDataAttribute { get; } + public INamedTypeSymbol? Range => range.Value; public INamedTypeSymbol TheoryAttribute { get; } public INamedTypeSymbol? TheoryDataBaseType => theoryDataBaseType.Value; public INamedTypeSymbol? TimeOnly => timeOnly.Value; diff --git a/src/xunit.analyzers/Utility/TypeSymbolFactory.cs b/src/xunit.analyzers/Utility/TypeSymbolFactory.cs index 2e14be32..5df52a2d 100644 --- a/src/xunit.analyzers/Utility/TypeSymbolFactory.cs +++ b/src/xunit.analyzers/Utility/TypeSymbolFactory.cs @@ -175,6 +175,9 @@ public static INamedTypeSymbol IEnumerableOfT(Compilation compilation) => public static INamedTypeSymbol? IMethodInfo_V2(Compilation compilation) => Guard.ArgumentNotNull(compilation).GetTypeByMetadataName(Constants.Types.Xunit.IMethodInfo_V2); + public static INamedTypeSymbol? Index(Compilation compilation) => + Guard.ArgumentNotNull(compilation).GetTypeByMetadataName("System.Index"); + public static INamedTypeSymbol? InlineDataAttribute(Compilation compilation) => Guard.ArgumentNotNull(compilation).GetTypeByMetadataName(Constants.Types.Xunit.InlineDataAttribute); @@ -316,6 +319,9 @@ public static IArrayTypeSymbol ObjectArray(Compilation compilation) => public static INamedTypeSymbol? OptionalAttribute(Compilation compilation) => Guard.ArgumentNotNull(compilation).GetTypeByMetadataName("System.Runtime.InteropServices.OptionalAttribute"); + public static INamedTypeSymbol? Range(Compilation compilation) => + Guard.ArgumentNotNull(compilation).GetTypeByMetadataName("System.Range"); + public static INamedTypeSymbol? Record(Compilation compilation) => Guard.ArgumentNotNull(compilation).GetTypeByMetadataName(Constants.Types.Xunit.Record);