Skip to content

Commit

Permalink
xunit/xunit#3151: xUnit1044/1045/1046/1047 inappropritely trigger wit…
Browse files Browse the repository at this point in the history
…h Guid/Uri for v3 projects
  • Loading branch information
bradwilson committed Jan 31, 2025
1 parent 46d1071 commit f7c41bf
Show file tree
Hide file tree
Showing 7 changed files with 65 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ public class DataSource<T1, T2> : IEnumerable<object[]> {
}

[Theory]
// Serializable via XunitSerializationInfo (v2) or SerializationHelper (v3)
[MemberData(nameof(TheoryDataMembers), "Type")]
[MemberData(nameof(TheoryDataMembers), "Dictionary<string, List<string>>")]
[MemberData(nameof(TheoryDataMembers), "string")]
[MemberData(nameof(TheoryDataMembers), "string[]")]
[MemberData(nameof(TheoryDataMembers), "string[][]")]
Expand Down Expand Up @@ -165,11 +168,6 @@ public class DataSource<T1, T2> : IEnumerable<object[]> {
[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<string, List<string>>")]
#if NET6_0_OR_GREATER
[MemberData(nameof(TheoryDataMembers), "DateOnly")]
[MemberData(nameof(TheoryDataMembers), "DateOnly[]")]
Expand All @@ -180,6 +178,10 @@ public class DataSource<T1, T2> : IEnumerable<object[]> {
[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,
Expand All @@ -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[]")]
Expand Down
17 changes: 15 additions & 2 deletions src/xunit.analyzers/Utility/SerializabilityAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
/// </remarks>
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;
Expand All @@ -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;

Expand Down
6 changes: 6 additions & 0 deletions src/xunit.analyzers/Utility/SerializableTypeSymbols.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public sealed class SerializableTypeSymbols
readonly Lazy<INamedTypeSymbol?> bigInteger;
readonly Lazy<INamedTypeSymbol?> dateOnly;
readonly Lazy<INamedTypeSymbol?> dateTimeOffset;
readonly Lazy<INamedTypeSymbol?> guid;
readonly Lazy<INamedTypeSymbol?> iXunitSerializable;
readonly Lazy<INamedTypeSymbol?> theoryDataBaseType;
readonly Dictionary<int, INamedTypeSymbol> theoryDataTypes;
Expand All @@ -19,6 +20,7 @@ public sealed class SerializableTypeSymbols
readonly Lazy<INamedTypeSymbol?> traitDictionary;
readonly Lazy<INamedTypeSymbol?> type;
readonly Lazy<ImmutableArray<INamedTypeSymbol>> typesWithCustomSerializers;
readonly Lazy<INamedTypeSymbol?> uri;

SerializableTypeSymbols(
Compilation compilation,
Expand All @@ -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<TTheoryDataRow, TRawDataRow>". In either case, getting "TheoryData<T>"
Expand All @@ -60,6 +63,7 @@ public sealed class SerializableTypeSymbols
.WhereNotNull()
.ToImmutableArray();
});
uri = new(() => TypeSymbolFactory.Uri(compilation));

ClassDataAttribute = classDataAttribute;
DataAttribute = dataAttribute;
Expand All @@ -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; }
Expand All @@ -81,6 +86,7 @@ public sealed class SerializableTypeSymbols
public INamedTypeSymbol? TraitDictionary => traitDictionary.Value;
public INamedTypeSymbol? Type => type.Value;
public ImmutableArray<INamedTypeSymbol> TypesWithCustomSerializers => typesWithCustomSerializers.Value;
public INamedTypeSymbol? Uri => uri.Value;

public static SerializableTypeSymbols? Create(
Compilation compilation,
Expand Down
6 changes: 6 additions & 0 deletions src/xunit.analyzers/Utility/TypeSymbolFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down

0 comments on commit f7c41bf

Please sign in to comment.