Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/project/list-of-diagnostics.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,8 +272,8 @@ The diagnostic id values reserved for .NET Libraries analyzer warnings are `SYSL
| __`SYSLIB1224`__ | Types annotated with JsonSerializableAttribute must be classes deriving from JsonSerializerContext. |
| __`SYSLIB1225`__ | Type includes ref like property, field or constructor parameter. |
| __`SYSLIB1226`__ | 'JsonIgnoreCondition.Always' is not valid on type-level 'JsonIgnoreAttribute' annotations. |
| __`SYSLIB1227`__ | _`SYSLIB1220`-`SYSLIB1229` reserved for System.Text.Json.SourceGeneration._ |
| __`SYSLIB1228`__ | _`SYSLIB1220`-`SYSLIB1229` reserved for System.Text.Json.SourceGeneration._ |
| __`SYSLIB1227`__ | Types annotated with the parameterless JsonSerializableAttribute must be partial. |
| __`SYSLIB1228`__ | Type already contains a member named 'JsonTypeInfo'. |
| __`SYSLIB1229`__ | _`SYSLIB1220`-`SYSLIB1229` reserved for System.Text.Json.SourceGeneration._ |
| __`SYSLIB1230`__ | Deriving from a `GeneratedComInterface`-attributed interface defined in another assembly is not supported. |
| __`SYSLIB1231`__ | _`SYSLIB1230`-`SYSLIB1239` reserved for Microsoft.Interop.ComInterfaceGenerator._ |
Expand Down
12 changes: 12 additions & 0 deletions src/libraries/Common/src/SourceGenerators/TypeRef.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,18 @@ public TypeRef(ITypeSymbol type)
SpecialType = type.OriginalDefinition.SpecialType;
}

/// <summary>
/// Creates a TypeRef for a synthetic type that doesn't exist in the compilation.
/// </summary>
public TypeRef(string name, string fullyQualifiedName)
{
Name = name;
FullyQualifiedName = fullyQualifiedName;
IsValueType = false;
TypeKind = TypeKind.Class;
SpecialType = SpecialType.None;
}

public string Name { get; }

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ namespace System.Text.Json.Serialization
/// Instructs the System.Text.Json source generator to generate source code to help optimize performance
/// when serializing and deserializing instances of the specified type and types in its object graph.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
/// <remarks>
/// This attribute can be applied to a <see cref="JsonSerializerContext"/>-derived class to register types
/// for source generation, or directly to a partial class or struct to enable simplified source generation
/// with an auto-generated context and a static <c>JsonTypeInfo</c> property.
/// </remarks>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true)]

#if BUILDING_SOURCE_GENERATOR
internal
Expand All @@ -28,6 +33,16 @@ sealed class JsonSerializableAttribute : JsonAttribute
public JsonSerializableAttribute(Type type) { }
#pragma warning restore IDE0060

/// <summary>
/// Initializes a new instance of <see cref="JsonSerializableAttribute"/> when applied directly to the type to be serialized.
/// </summary>
/// <remarks>
/// When this parameterless constructor is used on a partial class or struct, the source generator will
/// automatically generate a backing <see cref="JsonSerializerContext"/> and a static <c>JsonTypeInfo</c>
/// property on the annotated type.
/// </remarks>
public JsonSerializableAttribute() { }

/// <summary>
/// The name of the property for the generated <see cref="JsonTypeInfo{T}"/> for
/// the type on the generated, derived <see cref="JsonSerializerContext"/> type.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,22 @@ internal static class DiagnosticDescriptors
category: JsonConstants.SystemTextJsonSourceGenerationName,
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true);

public static DiagnosticDescriptor JsonSerializableTypeMustBePartial { get; } = DiagnosticDescriptorHelper.Create(
id: "SYSLIB1227",
title: new LocalizableResourceString(nameof(SR.JsonSerializableTypeMustBePartialTitle), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)),
messageFormat: new LocalizableResourceString(nameof(SR.JsonSerializableTypeMustBePartialMessageFormat), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)),
category: JsonConstants.SystemTextJsonSourceGenerationName,
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true);

public static DiagnosticDescriptor JsonSerializableTypeHasJsonTypeInfoMember { get; } = DiagnosticDescriptorHelper.Create(
id: "SYSLIB1228",
title: new LocalizableResourceString(nameof(SR.JsonSerializableTypeHasJsonTypeInfoMemberTitle), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)),
messageFormat: new LocalizableResourceString(nameof(SR.JsonSerializableTypeHasJsonTypeInfoMemberMessageFormat), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)),
category: JsonConstants.SystemTextJsonSourceGenerationName,
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true);
}
}
}
98 changes: 98 additions & 0 deletions src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,104 @@ public void Emit(ContextGenerationSpec contextGenerationSpec)
_typeIndex.Clear();
}

/// <summary>
/// Emits the partial type extension with a static JsonTypeInfo property
/// for a type annotated with the parameterless [JsonSerializable] attribute.
/// Also emits the base class declaration for the synthetic backing context.
/// </summary>
public void EmitPocoTypeProperty(PocoTypeGenerationSpec pocoSpec)
{
// 1. Emit the partial type with the static JsonTypeInfo property
var writer = new SourceWriter();

writer.WriteLine("""
// <auto-generated/>

#nullable enable annotations
#nullable disable warnings

// Suppress warnings about [Obsolete] member usage in generated code.
#pragma warning disable CS0612, CS0618

""");

if (pocoSpec.Namespace != null)
{
writer.WriteLine($"namespace {pocoSpec.Namespace}");
writer.WriteLine('{');
writer.Indentation++;
}

// Emit containing type declarations
ImmutableEquatableArray<string> typeDeclarations = pocoSpec.TypeDeclarations;
Debug.Assert(typeDeclarations.Count > 0);

for (int i = typeDeclarations.Count - 1; i > 0; i--)
{
writer.WriteLine(typeDeclarations[i]);
writer.WriteLine('{');
writer.Indentation++;
}

// Emit the partial type declaration with the static property
writer.WriteLine($"""[global::System.CodeDom.Compiler.GeneratedCodeAttribute("{s_assemblyName.Name}", "{s_assemblyName.Version}")]""");
writer.WriteLine(typeDeclarations[0]);
writer.WriteLine('{');
writer.Indentation++;

string fullyQualifiedTypeName = pocoSpec.TypeRef.FullyQualifiedName;

writer.WriteLine($$"""
/// <summary>
/// Gets the generated <see cref="global::System.Text.Json.Serialization.Metadata.JsonTypeInfo{T}"/> for <see cref="{{fullyQualifiedTypeName}}"/>.
/// </summary>
public static global::System.Text.Json.Serialization.Metadata.JsonTypeInfo<{{fullyQualifiedTypeName}}> {{pocoSpec.TypeInfoPropertyName}} =>
{{pocoSpec.GeneratedContextName}}.Default.{{pocoSpec.TypeName}};
""");

// Close all type declarations and namespace
while (writer.Indentation > 0)
{
writer.Indentation--;
writer.WriteLine('}');
}

AddSource($"{pocoSpec.TypeName}.JsonSerializable.g.cs", writer.ToSourceText());

// 2. Emit a separate file that provides the base class declaration for the synthetic context.
// The existing Emit() generates partial class files for the context but none of them
// declare the base class. For real user contexts, the user's source code provides this.
// For our synthetic context, we need this extra file.
var baseWriter = new SourceWriter();

baseWriter.WriteLine("""
// <auto-generated/>

#nullable enable annotations
#nullable disable warnings

""");

if (pocoSpec.Namespace != null)
{
baseWriter.WriteLine($"namespace {pocoSpec.Namespace}");
baseWriter.WriteLine('{');
baseWriter.Indentation++;
}

baseWriter.WriteLine($"internal sealed partial class {pocoSpec.GeneratedContextName} : global::System.Text.Json.Serialization.JsonSerializerContext");
baseWriter.WriteLine('{');
baseWriter.WriteLine('}');

if (pocoSpec.Namespace != null)
{
baseWriter.Indentation--;
baseWriter.WriteLine('}');
}

AddSource($"{pocoSpec.GeneratedContextName}.Base.g.cs", baseWriter.ToSourceText());
}

private static SourceWriter CreateSourceWriterWithContextHeader(ContextGenerationSpec contextSpec, bool isPrimaryContextSourceFile = false, string? interfaceImplementation = null)
{
var writer = new SourceWriter();
Expand Down
Loading
Loading