diff --git a/src/xunit.analyzers.tests/Analyzers/X1000/TheoryDataRowArgumentsShouldBeSerializableTests.cs b/src/xunit.analyzers.tests/Analyzers/X1000/TheoryDataRowArgumentsShouldBeSerializableTests.cs index a502c234..fc05207c 100644 --- a/src/xunit.analyzers.tests/Analyzers/X1000/TheoryDataRowArgumentsShouldBeSerializableTests.cs +++ b/src/xunit.analyzers.tests/Analyzers/X1000/TheoryDataRowArgumentsShouldBeSerializableTests.cs @@ -51,6 +51,8 @@ public void TestMethod() { [InlineData("DateOnly.MinValue", "DateOnly")] [InlineData("TimeOnly.MinValue", "TimeOnly")] #endif + [InlineData("Guid.Empty", "Guid")] + [InlineData("new Uri(\"https://xunit.net/\")", "Uri")] public async Task IntrinsicallySerializableValue_DoesNotTrigger( string value, string type) diff --git a/src/xunit.analyzers.tests/Analyzers/X1000/TheoryDataTypeArgumentsShouldBeSerializableTests.cs b/src/xunit.analyzers.tests/Analyzers/X1000/TheoryDataTypeArgumentsShouldBeSerializableTests.cs index 1167a017..1c99ec88 100644 --- a/src/xunit.analyzers.tests/Analyzers/X1000/TheoryDataTypeArgumentsShouldBeSerializableTests.cs +++ b/src/xunit.analyzers.tests/Analyzers/X1000/TheoryDataTypeArgumentsShouldBeSerializableTests.cs @@ -124,6 +124,9 @@ public class DataSource : IEnumerable { } [Theory] + // Serializable via XunitSerializationInfo (v2) or SerializationHelper (v3) + [MemberData(nameof(TheoryDataMembers), "Type")] + [MemberData(nameof(TheoryDataMembers), "Dictionary>")] [MemberData(nameof(TheoryDataMembers), "string")] [MemberData(nameof(TheoryDataMembers), "string[]")] [MemberData(nameof(TheoryDataMembers), "string[][]")] @@ -165,11 +168,6 @@ public class DataSource : IEnumerable { [MemberData(nameof(TheoryDataMembers), "TimeSpan?")] [MemberData(nameof(TheoryDataMembers), "BigInteger")] [MemberData(nameof(TheoryDataMembers), "BigInteger?")] - [MemberData(nameof(TheoryDataMembers), "Type")] - [MemberData(nameof(TheoryDataMembers), "Enum")] - [MemberData(nameof(TheoryDataMembers), "SerializableEnumeration")] - [MemberData(nameof(TheoryDataMembers), "SerializableEnumeration?")] - [MemberData(nameof(TheoryDataMembers), "Dictionary>")] #if NET6_0_OR_GREATER [MemberData(nameof(TheoryDataMembers), "DateOnly")] [MemberData(nameof(TheoryDataMembers), "DateOnly[]")] @@ -180,6 +178,10 @@ public class DataSource : IEnumerable { [MemberData(nameof(TheoryDataMembers), "TimeOnly?")] [MemberData(nameof(TheoryDataMembers), "TimeOnly?[]")] #endif + // Serializable via XunitSerializationInfo (v2) or via built-in IXunitSerializer (v3) + [MemberData(nameof(TheoryDataMembers), "Enum")] + [MemberData(nameof(TheoryDataMembers), "SerializableEnumeration")] + [MemberData(nameof(TheoryDataMembers), "SerializableEnumeration?")] public async Task GivenTheory_WithSerializableTheoryDataMember_DoesNotTrigger( string member, string attribute, @@ -205,6 +207,33 @@ 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[]")] diff --git a/src/xunit.analyzers/Utility/SerializabilityAnalyzer.cs b/src/xunit.analyzers/Utility/SerializabilityAnalyzer.cs index 75262eaa..24cfef57 100644 --- a/src/xunit.analyzers/Utility/SerializabilityAnalyzer.cs +++ b/src/xunit.analyzers/Utility/SerializabilityAnalyzer.cs @@ -13,15 +13,19 @@ public sealed class SerializabilityAnalyzer(SerializableTypeSymbols typeSymbols) /// The logic in this method corresponds to the logic in SerializationHelper.IsSerializable /// and SerializationHelper.Serialize. /// - public Serializability AnalayzeSerializability(ITypeSymbol type) + public Serializability AnalayzeSerializability( + ITypeSymbol type, + XunitContext xunitContext) { + Guard.ArgumentNotNull(xunitContext); + type = type.UnwrapNullable(); if (GetTypeKindSerializability(type.TypeKind) == Serializability.NeverSerializable) return Serializability.NeverSerializable; if (type.TypeKind == TypeKind.Array && type is IArrayTypeSymbol arrayType) - return AnalayzeSerializability(arrayType.ElementType); + return AnalayzeSerializability(arrayType.ElementType, xunitContext); if (typeSymbols.Type.IsAssignableFrom(type)) return Serializability.AlwaysSerializable; @@ -42,6 +46,15 @@ public Serializability AnalayzeSerializability(ITypeSymbol type) || type.Equals(typeSymbols.TimeOnly, SymbolEqualityComparer.Default)) return Serializability.AlwaysSerializable; + if (xunitContext.HasV3References) + { + if (type.Equals(typeSymbols.Guid, SymbolEqualityComparer.Default)) + return Serializability.AlwaysSerializable; + + if (type.Equals(typeSymbols.Uri, SymbolEqualityComparer.Default)) + return Serializability.AlwaysSerializable; + } + if (typeSymbols.TypesWithCustomSerializers.Any(t => t.IsAssignableFrom(type))) return Serializability.AlwaysSerializable; diff --git a/src/xunit.analyzers/Utility/SerializableTypeSymbols.cs b/src/xunit.analyzers/Utility/SerializableTypeSymbols.cs index c7016256..171b3b35 100644 --- a/src/xunit.analyzers/Utility/SerializableTypeSymbols.cs +++ b/src/xunit.analyzers/Utility/SerializableTypeSymbols.cs @@ -11,6 +11,7 @@ public sealed class SerializableTypeSymbols readonly Lazy bigInteger; readonly Lazy dateOnly; readonly Lazy dateTimeOffset; + readonly Lazy guid; readonly Lazy iXunitSerializable; readonly Lazy theoryDataBaseType; readonly Dictionary theoryDataTypes; @@ -19,6 +20,7 @@ public sealed class SerializableTypeSymbols readonly Lazy traitDictionary; readonly Lazy type; readonly Lazy> typesWithCustomSerializers; + readonly Lazy uri; SerializableTypeSymbols( Compilation compilation, @@ -34,6 +36,7 @@ public sealed class SerializableTypeSymbols bigInteger = new(() => TypeSymbolFactory.BigInteger(compilation)); dateOnly = new(() => TypeSymbolFactory.DateOnly(compilation)); dateTimeOffset = new(() => TypeSymbolFactory.DateTimeOffset(compilation)); + guid = new(() => TypeSymbolFactory.Guid(compilation)); iXunitSerializable = new(() => xunitContext.Common.IXunitSerializableType); // 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" @@ -60,6 +63,7 @@ public sealed class SerializableTypeSymbols .WhereNotNull() .ToImmutableArray(); }); + uri = new(() => TypeSymbolFactory.Uri(compilation)); ClassDataAttribute = classDataAttribute; DataAttribute = dataAttribute; @@ -72,6 +76,7 @@ public sealed class SerializableTypeSymbols public INamedTypeSymbol DataAttribute { get; } public INamedTypeSymbol? DateOnly => dateOnly.Value; public INamedTypeSymbol? DateTimeOffset => dateTimeOffset.Value; + public INamedTypeSymbol? Guid => guid.Value; public INamedTypeSymbol? IXunitSerializable => iXunitSerializable.Value; public INamedTypeSymbol MemberDataAttribute { get; } public INamedTypeSymbol TheoryAttribute { get; } @@ -81,6 +86,7 @@ public sealed class SerializableTypeSymbols public INamedTypeSymbol? TraitDictionary => traitDictionary.Value; public INamedTypeSymbol? Type => type.Value; public ImmutableArray TypesWithCustomSerializers => typesWithCustomSerializers.Value; + public INamedTypeSymbol? Uri => uri.Value; public static SerializableTypeSymbols? Create( Compilation compilation, diff --git a/src/xunit.analyzers/Utility/TypeSymbolFactory.cs b/src/xunit.analyzers/Utility/TypeSymbolFactory.cs index e2130845..db3edab6 100644 --- a/src/xunit.analyzers/Utility/TypeSymbolFactory.cs +++ b/src/xunit.analyzers/Utility/TypeSymbolFactory.cs @@ -74,6 +74,9 @@ public static class TypeSymbolFactory int arity = 1) => Guard.ArgumentNotNull(compilation).GetTypeByMetadataName($"System.Func`{ValidateArity(arity, min: 1, max: 17)}"); + public static INamedTypeSymbol? Guid(Compilation compilation) => + Guard.ArgumentNotNull(compilation).GetTypeByMetadataName("System.Guid"); + public static INamedTypeSymbol? IAssemblyInfo_V2(Compilation compilation) => Guard.ArgumentNotNull(compilation).GetTypeByMetadataName(Constants.Types.Xunit.IAssemblyInfo_V2); @@ -406,6 +409,9 @@ static int ValidateArity( throw new ArgumentOutOfRangeException(nameof(arity), $"Arity {arity} must be between {min} and {max}."); } + public static INamedTypeSymbol? Uri(Compilation compilation) => + Guard.ArgumentNotNull(compilation).GetTypeByMetadataName("System.Uri"); + public static INamedTypeSymbol? ValueTask(Compilation compilation) => Guard.ArgumentNotNull(compilation).GetTypeByMetadataName("System.Threading.Tasks.ValueTask"); diff --git a/src/xunit.analyzers/X1000/TheoryDataRowArgumentsShouldBeSerializable.cs b/src/xunit.analyzers/X1000/TheoryDataRowArgumentsShouldBeSerializable.cs index 5635746b..db3f092b 100644 --- a/src/xunit.analyzers/X1000/TheoryDataRowArgumentsShouldBeSerializable.cs +++ b/src/xunit.analyzers/X1000/TheoryDataRowArgumentsShouldBeSerializable.cs @@ -53,7 +53,7 @@ public override void AnalyzeCompilation( if (analyzer.TypeShouldBeIgnored(argumentOperation.Type)) continue; - var serializability = analyzer.AnalayzeSerializability(argumentOperation.Type); + var serializability = analyzer.AnalayzeSerializability(argumentOperation.Type, xunitContext); if (serializability != Serializability.AlwaysSerializable) { diff --git a/src/xunit.analyzers/X1000/TheoryDataTypeArgumentsShouldBeSerializable.cs b/src/xunit.analyzers/X1000/TheoryDataTypeArgumentsShouldBeSerializable.cs index eb960e47..aab5e86e 100644 --- a/src/xunit.analyzers/X1000/TheoryDataTypeArgumentsShouldBeSerializable.cs +++ b/src/xunit.analyzers/X1000/TheoryDataTypeArgumentsShouldBeSerializable.cs @@ -65,7 +65,7 @@ public override void AnalyzeCompilation( if (analyzer.TypeShouldBeIgnored(type)) continue; - var serializability = analyzer.AnalayzeSerializability(type); + var serializability = analyzer.AnalayzeSerializability(type, xunitContext); if (serializability != Serializability.AlwaysSerializable) context.ReportDiagnostic(