Skip to content

Commit 6ef7504

Browse files
authored
Suppress SHARPYAML002 when a converter handles the type (#140)
The source generator SHARPYAML002 diagnostic now checks for converters before reporting unsupported member types. Previously, types like HttpMethod or IPEndPoint would trigger SHARPYAML002 even when a custom YamlConverter<T> was registered via [YamlSourceGenerationOptions] or [YamlConverter] attributes. The validation now skips the diagnostic when: - The member has a [YamlConverter(typeof(...))] attribute - The member type has a [YamlConverter(typeof(...))] attribute - A context-level YamlConverter<T> handles the member type This applies to direct member types, array/list element types, and dictionary value types. Nullable<T> value types are unwrapped before matching against converters. Added 9 tests covering all suppression paths and regression cases.
1 parent def0ca1 commit 6ef7504

2 files changed

Lines changed: 486 additions & 2 deletions

File tree

src/SharpYaml.SourceGenerator/YamlSerializerContextGenerator.cs

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -407,10 +407,23 @@ private static void EmitContext(SourceProductionContext context, Compilation com
407407
continue;
408408
}
409409

410+
// Skip if the member itself has [YamlConverter(typeof(...))] — the converter handles serialization.
411+
if (GetYamlConverterAttributeTypeName(member) is not null)
412+
{
413+
continue;
414+
}
415+
416+
// Skip if the member type is handled by a converter (type-level attribute or context-level converter).
417+
if (IsTypeHandledByConverter(memberType, model.SourceGenerationOptions.ConverterTypes, compilation))
418+
{
419+
continue;
420+
}
421+
410422
if (TryGetArrayElementType(memberType, out var arrayElementType) ||
411423
TryGetSequenceElementType(memberType, out arrayElementType, out _))
412424
{
413-
if (IsKnownScalar(arrayElementType) || indexByType.ContainsKey(arrayElementType))
425+
if (IsKnownScalar(arrayElementType) || indexByType.ContainsKey(arrayElementType) ||
426+
IsTypeHandledByConverter(arrayElementType, model.SourceGenerationOptions.ConverterTypes, compilation))
414427
{
415428
continue;
416429
}
@@ -437,7 +450,8 @@ private static void EmitContext(SourceProductionContext context, Compilation com
437450
continue;
438451
}
439452

440-
if (IsKnownScalar(dictionaryValueType) || indexByType.ContainsKey(dictionaryValueType))
453+
if (IsKnownScalar(dictionaryValueType) || indexByType.ContainsKey(dictionaryValueType) ||
454+
IsTypeHandledByConverter(dictionaryValueType, model.SourceGenerationOptions.ConverterTypes, compilation))
441455
{
442456
continue;
443457
}
@@ -5920,6 +5934,55 @@ private static string GetIgnoreConditionExpression(ISymbol member)
59205934
return null;
59215935
}
59225936

5937+
/// <summary>
5938+
/// Checks whether the given type is handled by a converter — either via a [YamlConverter] attribute
5939+
/// on the type itself, or via a context-level YamlConverter&lt;T&gt; registration.
5940+
/// Nullable&lt;T&gt; value types are unwrapped before checking.
5941+
/// </summary>
5942+
private static bool IsTypeHandledByConverter(
5943+
ITypeSymbol typeToCheck,
5944+
ImmutableArray<ITypeSymbol> converterTypes,
5945+
Compilation compilation)
5946+
{
5947+
// Unwrap Nullable<T> for value types.
5948+
var unwrappedType = typeToCheck;
5949+
if (typeToCheck is INamedTypeSymbol nullable && nullable.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T)
5950+
{
5951+
unwrappedType = nullable.TypeArguments[0];
5952+
}
5953+
5954+
// Check if the type itself has [YamlConverter(typeof(...))].
5955+
if (GetYamlConverterAttributeTypeName(unwrappedType) is not null)
5956+
{
5957+
return true;
5958+
}
5959+
5960+
// Check if a context-level converter handles this type.
5961+
if (!converterTypes.IsDefaultOrEmpty)
5962+
{
5963+
var yamlConverterOfT = compilation.GetTypeByMetadataName("SharpYaml.Serialization.YamlConverter`1");
5964+
if (yamlConverterOfT is not null)
5965+
{
5966+
foreach (var converterType in converterTypes)
5967+
{
5968+
// Walk up the base type chain looking for YamlConverter<T>.
5969+
for (var current = converterType as INamedTypeSymbol; current is not null; current = current.BaseType)
5970+
{
5971+
if (current.IsGenericType &&
5972+
SymbolEqualityComparer.Default.Equals(current.OriginalDefinition, yamlConverterOfT) &&
5973+
current.TypeArguments.Length == 1 &&
5974+
SymbolEqualityComparer.Default.Equals(current.TypeArguments[0], unwrappedType))
5975+
{
5976+
return true;
5977+
}
5978+
}
5979+
}
5980+
}
5981+
}
5982+
5983+
return false;
5984+
}
5985+
59235986
private static ImmutableArray<ISymbol> GetSerializableMembers(INamedTypeSymbol type)
59245987
{
59255988
// Arrays/collections/dictionaries are handled by dedicated generated code paths, not as object graphs.

0 commit comments

Comments
 (0)