Skip to content

Commit

Permalink
Update xUnit3001 to check for [JsonTypeID] decorated classes
Browse files Browse the repository at this point in the history
  • Loading branch information
bradwilson committed Jul 22, 2024
1 parent 6f1bd59 commit 680e052
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,12 @@ public async Task ImplicitConstructors_DoesNotTrigger(string @interface)
public async Task WrongConstructor_Triggers(string @interface)
{
var v2Source = string.Format(Template, @interface, /* lang=c#-test */ "public Foo(int x) { }", "Xunit.Abstractions");
var v2Expected = VerifyV2.Diagnostic().WithLocation(0).WithArguments("Foo");
var v2Expected = VerifyV2.Diagnostic().WithLocation(0).WithArguments("Foo", "Xunit.Abstractions.IXunitSerializable");

await VerifyV2.VerifyAnalyzerV2(v2Source, v2Expected);

var v3Source = string.Format(Template, @interface, /* lang=c#-test */ "public Foo(int x) { }", "Xunit.Sdk");
var v3Expected = VerifyV3.Diagnostic().WithLocation(0).WithArguments("Foo");
var v3Expected = VerifyV3.Diagnostic().WithLocation(0).WithArguments("Foo", "Xunit.Sdk.IXunitSerializable");

await VerifyV3.VerifyAnalyzerV3(v3Source, v3Expected);
}
Expand All @@ -52,12 +52,12 @@ public async Task WrongConstructor_Triggers(string @interface)
public async Task NonPublicConstructor_Triggers(string @interface)
{
var v2Source = string.Format(Template, @interface, /* lang=c#-test */ "protected Foo() { }", "Xunit.Abstractions");
var v2Expected = VerifyV2.Diagnostic().WithLocation(0).WithArguments("Foo");
var v2Expected = VerifyV2.Diagnostic().WithLocation(0).WithArguments("Foo", "Xunit.Abstractions.IXunitSerializable");

await VerifyV2.VerifyAnalyzerV2(v2Source, v2Expected);

var v3Source = string.Format(Template, @interface, /* lang=c#-test */ "protected Foo() { }", "Xunit.Sdk");
var v3Expected = VerifyV3.Diagnostic().WithLocation(0).WithArguments("Foo");
var v3Expected = VerifyV3.Diagnostic().WithLocation(0).WithArguments("Foo", "Xunit.Sdk.IXunitSerializable");

await VerifyV3.VerifyAnalyzerV3(v3Source, v3Expected);
}
Expand All @@ -70,6 +70,40 @@ public async Task PublicParameterlessConstructor_DoesNotTrigger(string @interfac
await VerifyV3.VerifyAnalyzerV3(string.Format(Template, @interface, "public Foo() { }", "Xunit.Sdk"));
}

[Fact]
public async Task JsonTypeIDAcceptanceTest()
{
var source = /* lang=c#-test */ """
using Xunit.Sdk;

public class NonSerializedClass { }

[JsonTypeID("1")]
public class SerializedWithImplicitCtor { }

[JsonTypeID("2")]
public class SerializedWithExplicitCtor {
public SerializedWithExplicitCtor() { }
}

[JsonTypeID("3")]
public class {|#0:SerializedWithNoMatchingCtor|} {
public SerializedWithNoMatchingCtor(int _) { }
}

[JsonTypeID("4")]
public class {|#1:SerializedWithNonPublicCtor|} {
protected SerializedWithNonPublicCtor() { }
}
""";
var expected = new[] {
VerifyV3.Diagnostic().WithLocation(0).WithArguments("SerializedWithNoMatchingCtor", "Xunit.Sdk.JsonTypeIDAttribute"),
VerifyV3.Diagnostic().WithLocation(1).WithArguments("SerializedWithNonPublicCtor", "Xunit.Sdk.JsonTypeIDAttribute"),
};

await VerifyV3.VerifyAnalyzerV3(source, expected);
}

public class V2Analyzer : SerializableClassMustHaveParameterlessConstructor
{
protected override XunitContext CreateXunitContext(Compilation compilation) =>
Expand Down
4 changes: 2 additions & 2 deletions src/xunit.analyzers/Utility/Descriptors.xUnit3xxx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ public static partial class Descriptors
public static DiagnosticDescriptor X3001_SerializableClassMustHaveParameterlessConstructor { get; } =
Diagnostic(
"xUnit3001",
"Classes that implement Xunit.Abstractions.IXunitSerializable must have a public parameterless constructor",
"Classes that are marked as serializable must have a public parameterless constructor",
Extensibility,
Error,
"Class {0} must have a public parameterless constructor to support Xunit.Abstractions.IXunitSerializable."
"Class {0} must have a public parameterless constructor to support {1}."
);

public static DiagnosticDescriptor X3002_DoNotTestForConcreteTypeOfJsonSerializableTypes { get; } =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ public override void AnalyzeCompilation(
if (namedType.TypeKind != TypeKind.Class)
return;
var isXunitSerializable = xunitContext.Abstractions.IXunitSerializableType?.IsAssignableFrom(namedType) ?? false;
if (!isXunitSerializable)
var serializableTargetDisplay = GetSerializableTargetDisplay(context, xunitContext, namedType);
if (serializableTargetDisplay is null)
return;
var parameterlessCtor = namedType.InstanceConstructors.FirstOrDefault(c => c.Parameters.IsEmpty);
Expand All @@ -37,9 +37,27 @@ public override void AnalyzeCompilation(
Diagnostic.Create(
Descriptors.X3001_SerializableClassMustHaveParameterlessConstructor,
namedType.Locations.First(),
namedType.Name
namedType.Name,
serializableTargetDisplay
)
);
}, SymbolKind.NamedType);
}

static string? GetSerializableTargetDisplay(
SymbolAnalysisContext context,
XunitContext xunitContext,
INamedTypeSymbol namedType)
{
// Types that implement IXunitSerializable
if (xunitContext.Abstractions.IXunitSerializableType?.IsAssignableFrom(namedType) == true)
return xunitContext.Abstractions.IXunitSerializableType.ToDisplayString();

// Types that decorate with [JsonTypeID]
if (xunitContext.V3Core?.JsonTypeIDAttributeType is INamedTypeSymbol jsonTypeIDAttributeType)
if (namedType.GetAttributes().Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, jsonTypeIDAttributeType)))
return jsonTypeIDAttributeType.ToDisplayString();

return null;
}
}

0 comments on commit 680e052

Please sign in to comment.