diff --git a/CSharpToJsonSchema.sln b/CSharpToJsonSchema.sln index 886671d..c3e83b9 100755 --- a/CSharpToJsonSchema.sln +++ b/CSharpToJsonSchema.sln @@ -40,6 +40,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharpToJsonSchema.UnitTest EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharpToJsonSchema.IntegrationTests", "src\tests\CSharpToJsonSchema.IntegrationTests\CSharpToJsonSchema.IntegrationTests.csproj", "{247C813A-9072-4DF3-B403-B35E3688DB4B}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharpToJsonSchema.AotTests", "src\tests\CSharpToJsonSchema.AotTests\CSharpToJsonSchema.AotTests.csproj", "{6167F915-83EB-42F9-929B-AD4719A55811}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -70,6 +72,10 @@ Global {247C813A-9072-4DF3-B403-B35E3688DB4B}.Debug|Any CPU.Build.0 = Debug|Any CPU {247C813A-9072-4DF3-B403-B35E3688DB4B}.Release|Any CPU.ActiveCfg = Release|Any CPU {247C813A-9072-4DF3-B403-B35E3688DB4B}.Release|Any CPU.Build.0 = Release|Any CPU + {6167F915-83EB-42F9-929B-AD4719A55811}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6167F915-83EB-42F9-929B-AD4719A55811}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6167F915-83EB-42F9-929B-AD4719A55811}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6167F915-83EB-42F9-929B-AD4719A55811}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -82,6 +88,7 @@ Global {8BDEB07E-C8C8-436C-B736-F3C093CB27AD} = {AAA11B78-2764-4520-A97E-46AA7089A588} {8AFCC30C-C59D-498D-BE68-9A328B3E5599} = {AAA11B78-2764-4520-A97E-46AA7089A588} {247C813A-9072-4DF3-B403-B35E3688DB4B} = {AAA11B78-2764-4520-A97E-46AA7089A588} + {6167F915-83EB-42F9-929B-AD4719A55811} = {AAA11B78-2764-4520-A97E-46AA7089A588} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {CED9A020-DBA5-4BE6-8096-75E528648EC1} diff --git a/src/libs/CSharpToJsonSchema.Generators/CSharpToJsonSchema.Generators.csproj b/src/libs/CSharpToJsonSchema.Generators/CSharpToJsonSchema.Generators.csproj index 0bfe7b4..cdd9102 100644 --- a/src/libs/CSharpToJsonSchema.Generators/CSharpToJsonSchema.Generators.csproj +++ b/src/libs/CSharpToJsonSchema.Generators/CSharpToJsonSchema.Generators.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 @@ -7,10 +7,11 @@ true true $(NoWarn);CA1014;CA1031;CA1308 + true - + @@ -22,8 +23,9 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/Conversion/SymbolGenerator.cs b/src/libs/CSharpToJsonSchema.Generators/Conversion/SymbolGenerator.cs new file mode 100644 index 0000000..912eb22 --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/Conversion/SymbolGenerator.cs @@ -0,0 +1,130 @@ +using CSharpToJsonSchema.Generators.JsonGen.Helpers; +using CSharpToJsonSchema.Generators.Models; +using H.Generators.Extensions; + +namespace CSharpToJsonSchema.Generators.Conversion; + +using System; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; + +public static class SymbolGenerator +{ + public static ITypeSymbol? GenerateParameterBasedClassSymbol( + string rootNamespace, + IMethodSymbol methodSymbol, + Compilation originalCompilation) + { + + + // Example: we create a class name + var className = $"{methodSymbol.Name}Args"; + + // Build property declarations based on method parameters + var properties = methodSymbol + .Parameters + .Where(s=>s.Type.Name != "CancellationToken") + .Select(p => + { + + var propertyTypeName = p.Type.ToDisplayString(); // or another approach + var decleration = SyntaxFactory.PropertyDeclaration( + SyntaxFactory.ParseTypeName(propertyTypeName), + p.Name.ToPropertyName()) + .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword)) + .WithAccessorList( + SyntaxFactory.AccessorList( + SyntaxFactory.List(new[] + { + SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration) + .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)), + SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration) + .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)) + }) + ) + ); + if (p.Type.TypeKind == TypeKind.Enum) + { + //decleration = decleration.AddAttributeLists(AttributeList(SingletonSeparatedList(GetConverter(propertyTypeName)))); + } + return decleration; + }) + .ToArray(); + + // Build a class declaration + var classDecl = SyntaxFactory.ClassDeclaration(className) + .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword)) + .AddMembers(properties); + + // We create a compilation unit holding our new class + + + + var namespaceName =rootNamespace; // choose your own + var ns = SyntaxFactory.NamespaceDeclaration(SyntaxFactory.IdentifierName(namespaceName)) + .AddMembers(classDecl); + + var compilationUnit = SyntaxFactory.CompilationUnit() + .AddMembers(ns) // if ns is a NamespaceDeclarationSyntax + .NormalizeWhitespace(); + + var parseOptions = CSharpParseOptions.Default.WithLanguageVersion(originalCompilation.GetLanguageVersion()?? LanguageVersion.Default); + var syntaxTree =CSharpSyntaxTree.Create(compilationUnit,parseOptions); + //CSharpSyntaxTree.Create(ns.NormalizeWhitespace()); + + // Now we need to add this syntax tree to a new or existing compilation + var assemblyName = "TemporaryAssembly"; + var compilation = originalCompilation + .AddSyntaxTrees(syntaxTree); + //.WithAssemblyName(assemblyName); + + + // Get the semantic model for our newly added syntax tree + var semanticModel = compilation.GetSemanticModel(syntaxTree); + + // Find the class syntax node in the syntax tree + var classNode = syntaxTree.GetRoot().DescendantNodes() + .OfType() + .FirstOrDefault(); + + if (classNode == null) return null; + + // Retrieve the ITypeSymbol from the semantic model + var classSymbol = semanticModel.GetDeclaredSymbol(classNode) as ITypeSymbol; + return classSymbol; + } + + public static AttributeSyntax GetConverter(string propertyType) + { + // 1. Create the attribute: [JsonConverter(typeof(StringEnumJsonConverter))] + var converterAttribute = + Attribute( + IdentifierName("System.Text.Json.Serialization.JsonConverterAttribute") + ) + .WithArgumentList( + AttributeArgumentList( + SingletonSeparatedList( + AttributeArgument( + TypeOfExpression( + GenericName( + Identifier("System.Text.Json.Serialization.JsonStringEnumConverter") + ) + .WithTypeArgumentList( + TypeArgumentList( + SingletonSeparatedList( + IdentifierName(propertyType) + ) + ) + ) + ) + ) + ) + ) + ); + return converterAttribute; + + } +} \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/Conversion/ToModels.cs b/src/libs/CSharpToJsonSchema.Generators/Conversion/ToModels.cs index 45a8b2b..068299d 100644 --- a/src/libs/CSharpToJsonSchema.Generators/Conversion/ToModels.cs +++ b/src/libs/CSharpToJsonSchema.Generators/Conversion/ToModels.cs @@ -1,8 +1,8 @@ using System.ComponentModel; -using H.Generators; +using CSharpToJsonSchema.Generators.Models; using Microsoft.CodeAnalysis; -namespace CSharpToJsonSchema.Generators.Core.Conversion; +namespace CSharpToJsonSchema.Generators.Conversion; public static class ToModels { diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/AbstractSyntaxHelper.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/AbstractSyntaxHelper.cs new file mode 100644 index 0000000..1a00825 --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/AbstractSyntaxHelper.cs @@ -0,0 +1,28 @@ +using Microsoft.CodeAnalysis; + +namespace CSharpToJsonSchema.Generators.JsonGen.Base; + +internal abstract class AbstractSyntaxHelper : ISyntaxHelper +{ + public abstract bool IsCaseSensitive { get; } + + public abstract bool IsValidIdentifier(string name); + + public abstract SyntaxToken GetUnqualifiedIdentifierOfName(SyntaxNode name); + + public abstract bool IsAnyNamespaceBlock(SyntaxNode node); + + public abstract bool IsAttribute(SyntaxNode node); + public abstract SyntaxNode GetNameOfAttribute(SyntaxNode node); + + public abstract bool IsAttributeList(SyntaxNode node); + public abstract SeparatedSyntaxList GetAttributesOfAttributeList(SyntaxNode node); + public abstract void AddAttributeTargets(SyntaxNode node, ref ValueListBuilder targets); + + public abstract bool IsLambdaExpression(SyntaxNode node); + + public abstract void AddAliases(SyntaxNode node, ref ValueListBuilder<(string aliasName, string symbolName)> aliases, bool global); + public abstract void AddAliases(CompilationOptions options, ref ValueListBuilder<(string aliasName, string symbolName)> aliases); + + public abstract bool ContainsGlobalAliases(SyntaxNode root); +} \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/CSharpSyntaxHelper.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/CSharpSyntaxHelper.cs new file mode 100644 index 0000000..f7c675e --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/CSharpSyntaxHelper.cs @@ -0,0 +1,126 @@ +using System.Diagnostics; +using CSharpToJsonSchema.Generators.JsonGen.Helpers; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace CSharpToJsonSchema.Generators.JsonGen.Base; + + internal sealed class CSharpSyntaxHelper : AbstractSyntaxHelper + { + public static readonly ISyntaxHelper Instance = new CSharpSyntaxHelper(); + + private CSharpSyntaxHelper() + { + } + + public override bool IsCaseSensitive + => true; + + public override bool IsValidIdentifier(string name) + => SyntaxFacts.IsValidIdentifier(name); + + public override bool IsAnyNamespaceBlock(SyntaxNode node) + => node is BaseNamespaceDeclarationSyntax; + + public override bool IsAttribute(SyntaxNode node) + => node is AttributeSyntax; + + public override SyntaxNode GetNameOfAttribute(SyntaxNode node) + => ((AttributeSyntax)node).Name; + + public override bool IsAttributeList(SyntaxNode node) + => node is AttributeListSyntax; + + public override void AddAttributeTargets(SyntaxNode node, ref ValueListBuilder targets) + { + var attributeList = (AttributeListSyntax)node; + var container = attributeList.Parent; + Debug.Assert(container != null); + + // For fields/events, the attribute applies to all the variables declared. + if (container is FieldDeclarationSyntax field) + { + foreach (var variable in field.Declaration.Variables) + targets.Append(variable); + } + else if (container is EventFieldDeclarationSyntax ev) + { + foreach (var variable in ev.Declaration.Variables) + targets.Append(variable); + } + else + { + targets.Append(container); + } + } + + public override SeparatedSyntaxList GetAttributesOfAttributeList(SyntaxNode node) + => ((AttributeListSyntax)node).Attributes; + + public override bool IsLambdaExpression(SyntaxNode node) + => node is LambdaExpressionSyntax; + + public override SyntaxToken GetUnqualifiedIdentifierOfName(SyntaxNode node) + => RoslynExtensions2.GetUnqualifiedName(((NameSyntax)node).GetUnqualifiedName()).Identifier; + + public override void AddAliases(SyntaxNode node, ref ValueListBuilder<(string aliasName, string symbolName)> aliases, bool global) + { + if (node is CompilationUnitSyntax compilationUnit) + { + AddAliases(compilationUnit.Usings, ref aliases, global); + } + else if (node is BaseNamespaceDeclarationSyntax namespaceDeclaration) + { + AddAliases(namespaceDeclaration.Usings, ref aliases, global); + } + else + { + Debug.Fail("This should not be reachable. Caller already checked we had a compilation unit or namespace."); + } + } + + private static void AddAliases(SyntaxList usings, ref ValueListBuilder<(string aliasName, string symbolName)> aliases, bool global) + { + foreach (var usingDirective in usings) + { + if (usingDirective.Alias is null) + continue; + + if (global != usingDirective.GlobalKeyword.Kind() is SyntaxKind.GlobalKeyword) + continue; + + // We only care about aliases from one name to another name. e.g. `using X = A.B.C;` That's because + // the caller is only interested in finding a fully-qualified-metadata-name to an attribute. + if (usingDirective.Name is null) + continue; + + var aliasName = usingDirective.Alias.Name.Identifier.ValueText; + var symbolName = usingDirective.Name.GetUnqualifiedName().Identifier.ValueText; + aliases.Append((aliasName, symbolName)); + } + } + + public override void AddAliases(CompilationOptions compilation, ref ValueListBuilder<(string aliasName, string symbolName)> aliases) + { + // C# doesn't have global aliases at the compilation level. + return; + } + + public override bool ContainsGlobalAliases(SyntaxNode root) + { + // Global usings can only exist at the compilation-unit level, so no need to dive any deeper than that. + var compilationUnit = (CompilationUnitSyntax)root; + + foreach (var directive in compilationUnit.Usings) + { + if (directive.GlobalKeyword.IsKind(SyntaxKind.GlobalKeyword) && + directive.Alias != null) + { + return true; + } + } + + return false; + } + } \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/CSharpSyntaxUtilities.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/CSharpSyntaxUtilities.cs new file mode 100644 index 0000000..a721570 --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/CSharpSyntaxUtilities.cs @@ -0,0 +1,55 @@ +using System.Globalization; +using CSharpToJsonSchema.Generators.JsonGen.Model; +using Microsoft.CodeAnalysis.CSharp; + +namespace CSharpToJsonSchema.Generators.JsonGen.Base; + +internal static class CSharpSyntaxUtilities +{ + // Standard format for double and single on non-inbox frameworks to ensure value is round-trippable. + public const string DoubleFormatString = "G17"; + public const string SingleFormatString = "G9"; + + // Format a literal in C# format -- works around https://github.com/dotnet/roslyn/issues/58705 + public static string FormatLiteral(object? value, TypeRef type) + { + if (value == null) + { + return $"default({type.FullyQualifiedName})"; + } + + switch (value) + { + case string @string: + return SymbolDisplay.FormatLiteral(@string, quote: true); + case char @char: + return SymbolDisplay.FormatLiteral(@char, quote: true); + case double.NegativeInfinity: + return "double.NegativeInfinity"; + case double.PositiveInfinity: + return "double.PositiveInfinity"; + case double.NaN: + return "double.NaN"; + case double @double: + return $"{@double.ToString(DoubleFormatString, CultureInfo.InvariantCulture)}D"; + case float.NegativeInfinity: + return "float.NegativeInfinity"; + case float.PositiveInfinity: + return "float.PositiveInfinity"; + case float.NaN: + return "float.NaN"; + case float @float: + return $"{@float.ToString(SingleFormatString, CultureInfo.InvariantCulture)}F"; + case decimal @decimal: + // we do not need to specify a format string for decimal as it's default is round-trippable on all frameworks. + return $"{@decimal.ToString(CultureInfo.InvariantCulture)}M"; + case bool @bool: + return @bool ? "true" : "false"; + default: + // Assume this is a number. + return FormatNumber(); + } + + string FormatNumber() => $"({type.FullyQualifiedName})({Convert.ToString(value, CultureInfo.InvariantCulture)})"; + } +} \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/CollectionExtensions.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/CollectionExtensions.cs new file mode 100644 index 0000000..3d0c3d9 --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/CollectionExtensions.cs @@ -0,0 +1,19 @@ +using System.Collections.Immutable; + +namespace CSharpToJsonSchema.Generators.JsonGen.Base; + +public static partial class CollectionExtensions +{ + public static SequenceEqualImmutableArray ToSequenceEqualImmutableArray(this IEnumerable source, IEqualityComparer comparer) + { + return new(source.ToImmutableArray(), comparer); + } + public static SequenceEqualImmutableArray ToSequenceEqualImmutableArray(this IEnumerable source) + { + return new(source.ToImmutableArray()); + } + public static SequenceEqualImmutableArray ToSequenceEqual(this ImmutableArray source) + { + return new(source); + } +} \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/DiagnosticExtensions.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/DiagnosticExtensions.cs new file mode 100644 index 0000000..62ab7c9 --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/DiagnosticExtensions.cs @@ -0,0 +1,215 @@ +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; + +namespace CSharpToJsonSchema.Generators.JsonGen.Base; + + public static class DiagnosticExtensions + { + public static Diagnostic CreateDiagnostic( + this ISymbol symbol, + DiagnosticDescriptor descriptor, + params object[] args) + { + return symbol.Locations.CreateDiagnostic(descriptor, args); + } + + public static Diagnostic CreateDiagnostic( + this ISymbol symbol, + DiagnosticDescriptor descriptor, + ImmutableDictionary properties, + params object[] args) + { + return symbol.Locations.CreateDiagnostic(descriptor, properties, args); + } + + public static Diagnostic CreateDiagnostic( + this AttributeData attributeData, + DiagnosticDescriptor descriptor, + params object[] args) + { + SyntaxReference? syntaxReference = attributeData.ApplicationSyntaxReference; + Location location = syntaxReference is not null + ? syntaxReference.SyntaxTree.GetLocation(syntaxReference.Span) + : Location.None; + + return location.CreateDiagnostic(descriptor, args); + } + + public static Diagnostic CreateDiagnostic( + this AttributeData attributeData, + DiagnosticDescriptor descriptor, + ImmutableDictionary properties, + params object[] args) + { + SyntaxReference? syntaxReference = attributeData.ApplicationSyntaxReference; + Location location = syntaxReference is not null + ? syntaxReference.SyntaxTree.GetLocation(syntaxReference.Span) + : Location.None; + + return location.CreateDiagnostic(descriptor, properties, args); + } + + public static Diagnostic CreateDiagnostic( + this ImmutableArray locations, + DiagnosticDescriptor descriptor, + params object[] args) + { + return CreateDiagnostic(locations, descriptor, properties: null, args); + } + + public static Diagnostic CreateDiagnostic( + this ImmutableArray locations, + DiagnosticDescriptor descriptor, + ImmutableDictionary properties, + params object[] args) + { + Location firstLocation = null; + List additionalLocations = null; + foreach (Location location in locations) + { + if (location.IsInSource) + { + if (firstLocation is null) + { + firstLocation = location; + } + else + { + (additionalLocations ??= new()).Add(location); + } + } + } + + return firstLocation is null ? + Diagnostic.Create(descriptor, Location.None, properties: properties, args) : + Diagnostic.Create(descriptor, firstLocation, additionalLocations: additionalLocations, properties: properties, messageArgs: args); + } + + public static Diagnostic CreateDiagnostic( + this Location location, + DiagnosticDescriptor descriptor, + params object[] args) + { + return Diagnostic.Create( + descriptor, + location: location.IsInSource ? location : Location.None, + messageArgs: args); + } + + public static Diagnostic CreateDiagnostic( + this Location location, + DiagnosticDescriptor descriptor, + ImmutableDictionary properties, + params object[] args) + { + return Diagnostic.Create( + descriptor, + location: location.IsInSource ? location : Location.None, + properties: properties, + messageArgs: args); + } + + public static DiagnosticInfo CreateDiagnosticInfo( + this ISymbol symbol, + DiagnosticDescriptor descriptor, + params object[] args) + { + return symbol.Locations.CreateDiagnosticInfo(descriptor, args); + } + + public static DiagnosticInfo CreateDiagnosticInfo( + this ISymbol symbol, + DiagnosticDescriptor descriptor, + ImmutableDictionary properties, + params object[] args) + { + return symbol.Locations.CreateDiagnosticInfo(descriptor, properties, args); + } + + public static DiagnosticInfo CreateDiagnosticInfo( + this AttributeData attributeData, + DiagnosticDescriptor descriptor, + params object[] args) + { + SyntaxReference? syntaxReference = attributeData.ApplicationSyntaxReference; + Location location = syntaxReference is not null + ? syntaxReference.SyntaxTree.GetLocation(syntaxReference.Span) + : Location.None; + + return location.CreateDiagnosticInfo(descriptor, args); + } + + public static DiagnosticInfo CreateDiagnosticInfo( + this AttributeData attributeData, + DiagnosticDescriptor descriptor, + ImmutableDictionary properties, + params object[] args) + { + SyntaxReference? syntaxReference = attributeData.ApplicationSyntaxReference; + Location location = syntaxReference is not null + ? syntaxReference.SyntaxTree.GetLocation(syntaxReference.Span) + : Location.None; + + return location.CreateDiagnosticInfo(descriptor, properties, args); + } + + public static DiagnosticInfo CreateDiagnosticInfo( + this ImmutableArray locations, + DiagnosticDescriptor descriptor, + params object[] args) + { + return CreateDiagnosticInfo(locations, descriptor, properties: null, args); + } + + public static DiagnosticInfo CreateDiagnosticInfo( + this ImmutableArray locations, + DiagnosticDescriptor descriptor, + ImmutableDictionary properties, + params object[] args) + { + Location firstLocation = null; + List additionalLocations = null; + foreach (Location location in locations) + { + if (location.IsInSource) + { + if (firstLocation is null) + { + firstLocation = location; + } + else + { + (additionalLocations ??= new()).Add(location); + } + } + } + + return firstLocation is null ? + DiagnosticInfo.Create(descriptor, Location.None, properties: properties, args) : + DiagnosticInfo.Create(descriptor, firstLocation, additionalLocations: additionalLocations, properties: properties, messageArgs: args); + } + + public static DiagnosticInfo CreateDiagnosticInfo( + this Location location, + DiagnosticDescriptor descriptor, + params object[] args) + { + return DiagnosticInfo.Create( + descriptor, + location: location.IsInSource ? location : Location.None, + messageArgs: args); + } + + public static DiagnosticInfo CreateDiagnosticInfo( + this Location location, + DiagnosticDescriptor descriptor, + ImmutableDictionary properties, + params object[] args) + { + return DiagnosticInfo.Create( + descriptor, + location: location.IsInSource ? location : Location.None, + properties: properties, + messageArgs: args); + } + } \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/DiagnosticInfo.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/DiagnosticInfo.cs new file mode 100644 index 0000000..3157b24 --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/DiagnosticInfo.cs @@ -0,0 +1,56 @@ +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; + +namespace CSharpToJsonSchema.Generators.JsonGen.Base; + + public sealed record DiagnosticInfo + { + public required DiagnosticDescriptor Descriptor { get; init; } + public required SequenceEqualImmutableArray MessageArgs { get; init; } + public required Location? Location { get; init; } + public required SequenceEqualImmutableArray? AdditionalLocations { get; init; } + public required ValueEqualityImmutableDictionary? Properties { get; init; } + + public Diagnostic ToDiagnostic() => Diagnostic.Create( + Descriptor, + Location, + additionalLocations: AdditionalLocations, + properties: Properties?.Map, + messageArgs: MessageArgs.Array.ToArray()); + + public static DiagnosticInfo Create(DiagnosticDescriptor descriptor, Location location, params object?[] messageArgs) + { + return new DiagnosticInfo() + { + Descriptor = descriptor, + Location = location, + AdditionalLocations = null, + Properties = null, + MessageArgs = messageArgs.Select(o => o?.ToString()).ToSequenceEqualImmutableArray() + }; + } + + public static DiagnosticInfo Create(DiagnosticDescriptor descriptor, Location location, ImmutableDictionary? properties, params object?[] messageArgs) + { + return new DiagnosticInfo() + { + Descriptor = descriptor, + Location = location, + AdditionalLocations = null, + Properties = properties.ToValueEquals(), + MessageArgs = messageArgs.Select(o => o.ToString()).ToSequenceEqualImmutableArray() + }; + } + + public static DiagnosticInfo Create(DiagnosticDescriptor descriptor, Location location, IEnumerable? additionalLocations, ImmutableDictionary? properties, params object?[] messageArgs) + { + return new DiagnosticInfo() + { + Descriptor = descriptor, + Location = location, + AdditionalLocations = (additionalLocations ?? ImmutableArray.Empty).ToSequenceEqualImmutableArray(), + Properties = properties.ToValueEquals(), + MessageArgs = messageArgs.Select(o => o.ToString()).ToSequenceEqualImmutableArray() + }; + } + } \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/GeneratorAttributeSyntaxContext.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/GeneratorAttributeSyntaxContext.cs new file mode 100644 index 0000000..fe4ccb7 --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/GeneratorAttributeSyntaxContext.cs @@ -0,0 +1,47 @@ +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; + +namespace CSharpToJsonSchema.Generators.JsonGen.Base; + +internal readonly struct GeneratorAttributeSyntaxContext +{ + /// + /// The syntax node the attribute is attached to. For example, with [CLSCompliant] class C { } this would + /// the class declaration node. + /// + public SyntaxNode TargetNode { get; } + + /// + /// The symbol that the attribute is attached to. For example, with [CLSCompliant] class C { } this would be + /// the for "C". + /// + public ISymbol TargetSymbol { get; } + + /// + /// Semantic model for the file that is contained within. + /// + public SemanticModel SemanticModel { get; } + + /// + /// s for any matching attributes on . Always non-empty. All + /// these attributes will have an whose fully qualified name metadata + /// name matches the name requested in . + /// + /// To get the entire list of attributes, use on . + /// + /// + public ImmutableArray Attributes { get; } + + + internal GeneratorAttributeSyntaxContext( + SyntaxNode targetNode, + ISymbol targetSymbol, + SemanticModel semanticModel, + ImmutableArray attributes) + { + TargetNode = targetNode; + TargetSymbol = targetSymbol; + SemanticModel = semanticModel; + Attributes = attributes; + } +} \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/GlobalAlias.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/GlobalAlias.cs new file mode 100644 index 0000000..7fc7c95 --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/GlobalAlias.cs @@ -0,0 +1,68 @@ +using System.Collections.Immutable; + +namespace CSharpToJsonSchema.Generators.JsonGen.Base; + +/// +/// Simple wrapper class around an immutable array so we can have the value-semantics needed for the incremental +/// generator to know when a change actually happened and it should run later transform stages. +/// +internal sealed class GlobalAliases : IEquatable +{ + public static readonly GlobalAliases Empty = new(ImmutableArray<(string aliasName, string symbolName)>.Empty); + + public readonly ImmutableArray<(string aliasName, string symbolName)> AliasAndSymbolNames; + + private int _hashCode; + + private GlobalAliases(ImmutableArray<(string aliasName, string symbolName)> aliasAndSymbolNames) + { + AliasAndSymbolNames = aliasAndSymbolNames; + } + + public static GlobalAliases Create(ImmutableArray<(string aliasName, string symbolName)> aliasAndSymbolNames) + { + return aliasAndSymbolNames.IsEmpty ? Empty : new GlobalAliases(aliasAndSymbolNames); + } + + public static GlobalAliases Concat(GlobalAliases ga1, GlobalAliases ga2) + { + if (ga1.AliasAndSymbolNames.Length == 0) + return ga2; + + if (ga2.AliasAndSymbolNames.Length == 0) + return ga1; + + return new(ga1.AliasAndSymbolNames.AddRange(ga2.AliasAndSymbolNames)); + } + + public override int GetHashCode() + { + if (_hashCode == 0) + { + var hashCode = 0; + foreach (var tuple in this.AliasAndSymbolNames) + hashCode = Hash.Combine(tuple.GetHashCode(), hashCode); + + _hashCode = hashCode == 0 ? 1 : hashCode; + } + + return _hashCode; + } + + public override bool Equals(object? obj) + => this.Equals(obj as GlobalAliases); + + public bool Equals(GlobalAliases? aliases) + { + if (aliases is null) + return false; + + if (ReferenceEquals(this, aliases)) + return true; + + if (this.AliasAndSymbolNames == aliases.AliasAndSymbolNames) + return true; + + return this.AliasAndSymbolNames.AsSpan().SequenceEqual(aliases.AliasAndSymbolNames.AsSpan()); + } +} \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/Hash.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/Hash.cs new file mode 100644 index 0000000..f71556f --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/Hash.cs @@ -0,0 +1,15 @@ +namespace CSharpToJsonSchema.Generators.JsonGen.Base; + +internal static class Hash +{ + /// + /// This is how VB Anonymous Types combine hash values for fields. + /// + internal static int Combine(int newKey, int currentKey) + { + return unchecked((currentKey * (int)0xA5555529) + newKey); + } + + // The rest of this file was removed as they were not currently needed in the polyfill of SyntaxValueProvider.ForAttributeWithMetadataName. + // If that changes, they should be added back as necessary. +} \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/HashCode.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/HashCode.cs new file mode 100644 index 0000000..8e43dbf --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/HashCode.cs @@ -0,0 +1,79 @@ +namespace CSharpToJsonSchema.Generators.JsonGen.Base; + + /// + /// Exposes the hashing utilities from Roslyn + /// + public class HashCode + { + public static int Combine(T1 t1, T2 t2) + { + return Hash.Combine(t1 != null ? t1.GetHashCode() : 0, t2 != null ? t2.GetHashCode() : 0); + } + + public static int Combine(T1 t1, T2 t2, T3 t3) + { + int combinedHash = t1 != null ? t1.GetHashCode() : 0; + combinedHash = Hash.Combine(combinedHash, t2 != null ? t2.GetHashCode() : 0); + return Hash.Combine(combinedHash, t3 != null ? t3.GetHashCode() : 0); + } + + public static int Combine(T1 t1, T2 t2, T3 t3, T4 t4) + { + int combinedHash = t1 != null ? t1.GetHashCode() : 0; + combinedHash = Hash.Combine(combinedHash, t2 != null ? t2.GetHashCode() : 0); + combinedHash = Hash.Combine(combinedHash, t3 != null ? t3.GetHashCode() : 0); + return Hash.Combine(combinedHash, t4 != null ? t4.GetHashCode() : 0); + } + + public static int Combine(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5) + { + int combinedHash = t1 != null ? t1.GetHashCode() : 0; + combinedHash = Hash.Combine(combinedHash, t2 != null ? t2.GetHashCode() : 0); + combinedHash = Hash.Combine(combinedHash, t3 != null ? t3.GetHashCode() : 0); + combinedHash = Hash.Combine(combinedHash, t4 != null ? t4.GetHashCode() : 0); + return Hash.Combine(combinedHash, t5 != null ? t5.GetHashCode() : 0); + } + + public static int Combine(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6) + { + int combinedHash = t1 != null ? t1.GetHashCode() : 0; + combinedHash = Hash.Combine(combinedHash, t2 != null ? t2.GetHashCode() : 0); + combinedHash = Hash.Combine(combinedHash, t3 != null ? t3.GetHashCode() : 0); + combinedHash = Hash.Combine(combinedHash, t4 != null ? t4.GetHashCode() : 0); + combinedHash = Hash.Combine(combinedHash, t5 != null ? t5.GetHashCode() : 0); + return Hash.Combine(combinedHash, t6 != null ? t6.GetHashCode() : 0); + } + + public static int Combine(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7) + { + int combinedHash = t1 != null ? t1.GetHashCode() : 0; + combinedHash = Hash.Combine(combinedHash, t2 != null ? t2.GetHashCode() : 0); + combinedHash = Hash.Combine(combinedHash, t3 != null ? t3.GetHashCode() : 0); + combinedHash = Hash.Combine(combinedHash, t4 != null ? t4.GetHashCode() : 0); + combinedHash = Hash.Combine(combinedHash, t5 != null ? t5.GetHashCode() : 0); + combinedHash = Hash.Combine(combinedHash, t6 != null ? t6.GetHashCode() : 0); + return Hash.Combine(combinedHash, t7 != null ? t7.GetHashCode() : 0); + } + + public static int Combine(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8) + { + int combinedHash = t1 != null ? t1.GetHashCode() : 0; + combinedHash = Hash.Combine(combinedHash, t2 != null ? t2.GetHashCode() : 0); + combinedHash = Hash.Combine(combinedHash, t3 != null ? t3.GetHashCode() : 0); + combinedHash = Hash.Combine(combinedHash, t4 != null ? t4.GetHashCode() : 0); + combinedHash = Hash.Combine(combinedHash, t5 != null ? t5.GetHashCode() : 0); + combinedHash = Hash.Combine(combinedHash, t6 != null ? t6.GetHashCode() : 0); + combinedHash = Hash.Combine(combinedHash, t7 != null ? t7.GetHashCode() : 0); + return Hash.Combine(combinedHash, t8 != null ? t8.GetHashCode() : 0); + } + + public static int SequentialValuesHash(IEnumerable values) + { + int hash = 0; + foreach (var value in values) + { + hash = Hash.Combine(hash, value.GetHashCode()); + } + return hash; + } + } \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/ISyntaxHelper.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/ISyntaxHelper.cs new file mode 100644 index 0000000..a19e996 --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/ISyntaxHelper.cs @@ -0,0 +1,32 @@ +using Microsoft.CodeAnalysis; + +namespace CSharpToJsonSchema.Generators.JsonGen.Base; + +internal interface ISyntaxHelper +{ + bool IsCaseSensitive { get; } + + bool IsValidIdentifier(string name); + + bool IsAnyNamespaceBlock(SyntaxNode node); + + bool IsAttributeList(SyntaxNode node); + SeparatedSyntaxList GetAttributesOfAttributeList(SyntaxNode node); + + void AddAttributeTargets(SyntaxNode node, ref ValueListBuilder targets); + + bool IsAttribute(SyntaxNode node); + SyntaxNode GetNameOfAttribute(SyntaxNode node); + + bool IsLambdaExpression(SyntaxNode node); + + SyntaxToken GetUnqualifiedIdentifierOfName(SyntaxNode node); + + /// + /// must be a compilation unit or namespace block. + /// + void AddAliases(SyntaxNode node, ref ValueListBuilder<(string aliasName, string symbolName)> aliases, bool global); + void AddAliases(CompilationOptions options, ref ValueListBuilder<(string aliasName, string symbolName)> aliases); + + bool ContainsGlobalAliases(SyntaxNode root); +} \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/ImmutableEquatableArray.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/ImmutableEquatableArray.cs new file mode 100644 index 0000000..a3c11ec --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/ImmutableEquatableArray.cs @@ -0,0 +1,74 @@ +using System.Collections; +using CSharpToJsonSchema.Generators.JsonGen.Helpers; + +namespace CSharpToJsonSchema.Generators.JsonGen.Base; + +/// +/// Provides an immutable list implementation which implements sequence equality. +/// +public sealed class ImmutableEquatableArray : IEquatable>, IReadOnlyList + where T : IEquatable +{ + public static ImmutableEquatableArray Empty { get; } = new ImmutableEquatableArray(Array.Empty()); + + private readonly T[] _values; + public T this[int index] => _values[index]; + public int Count => _values.Length; + + public ImmutableEquatableArray(IEnumerable values) + => _values = values.ToArray(); + + public bool Equals(ImmutableEquatableArray? other) + => other != null && ((ReadOnlySpan)_values).SequenceEqual(other._values); + + public override bool Equals(object? obj) + => obj is ImmutableEquatableArray other && Equals(other); + + public override int GetHashCode() + { + int hash = 0; + foreach (T value in _values) + { + hash = HashHelpers.Combine(hash, value is null ? 0 : value.GetHashCode()); + } + + return hash; + } + + public Enumerator GetEnumerator() => new Enumerator(_values); + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_values).GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => _values.GetEnumerator(); + + public struct Enumerator + { + private readonly T[] _values; + private int _index; + + internal Enumerator(T[] values) + { + _values = values; + _index = -1; + } + + public bool MoveNext() + { + int newIndex = _index + 1; + + if ((uint)newIndex < (uint)_values.Length) + { + _index = newIndex; + return true; + } + + return false; + } + + public readonly T Current => _values[_index]; + } +} + +internal static class ImmutableEquatableArray +{ + public static ImmutableEquatableArray ToImmutableEquatableArray(this IEnumerable values) where T : IEquatable + => new(values); +} \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/SequenceEqualImmutableArray.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/SequenceEqualImmutableArray.cs new file mode 100644 index 0000000..8ab19bb --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/SequenceEqualImmutableArray.cs @@ -0,0 +1,39 @@ +using System.Collections; +using System.Collections.Immutable; + +namespace CSharpToJsonSchema.Generators.JsonGen.Base; + +/// +/// This method provides a wrapper for an that overrides the equality operation to provide elementwise comparison. +/// The default equality operation for an is reference equality of the underlying array, which is too strict +/// for many scenarios. This wrapper type allows us to use s in our other record types without having to write an Equals method +/// that we may forget to update if we add new elements to the record. +/// +public readonly record struct SequenceEqualImmutableArray(ImmutableArray Array, IEqualityComparer Comparer) : IEnumerable +{ + public SequenceEqualImmutableArray(ImmutableArray array) + : this(array, EqualityComparer.Default) + { + } + + public SequenceEqualImmutableArray Add(T value) + { + return new SequenceEqualImmutableArray(Array.Add(value)); + } + + public T this[int i] { get => Array[i]; } + + public int Length => Array.Length; + public SequenceEqualImmutableArray Insert(int index, T item) + => new SequenceEqualImmutableArray(Array.Insert(index, item), Comparer); + + public override int GetHashCode() => HashCode.SequentialValuesHash(Array); + + public bool Equals(SequenceEqualImmutableArray other) + { + return Array.SequenceEqual(other.Array, Comparer); + } + + public IEnumerator GetEnumerator() => ((IEnumerable)Array).GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)Array).GetEnumerator(); +} \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/SourceWriter.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/SourceWriter.cs new file mode 100644 index 0000000..f14fbf1 --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/SourceWriter.cs @@ -0,0 +1,116 @@ +using System.Diagnostics; +using System.Text; +using Microsoft.CodeAnalysis.Text; + +namespace CSharpToJsonSchema.Generators.JsonGen.Base; + + internal sealed class SourceWriter + { + private const char IndentationChar = ' '; + private const int CharsPerIndentation = 4; + + private readonly StringBuilder _sb = new(); + private int _indentation; + + public int Indentation + { + get => _indentation; + set + { + if (value < 0) + { + Throw(); + static void Throw() => throw new ArgumentOutOfRangeException(nameof(value)); + } + + _indentation = value; + } + } + + public void WriteLine(char value) + { + AddIndentation(); + _sb.Append(value); + _sb.AppendLine(); + } + + public void WriteLine(string text) + { + if (_indentation == 0) + { + _sb.AppendLine(text); + return; + } + + bool isFinalLine; + ReadOnlySpan remainingText = text.AsSpan(); + do + { + ReadOnlySpan nextLine = GetNextLine(ref remainingText, out isFinalLine); + + AddIndentation(); + AppendSpan(_sb, nextLine); + _sb.AppendLine(); + } + while (!isFinalLine); + } + + public void WriteLine() => _sb.AppendLine(); + + public SourceText ToSourceText() + { + Debug.Assert(_indentation == 0 && _sb.Length > 0); + return SourceText.From(_sb.ToString(), Encoding.UTF8); + } + + public void Reset() + { + _sb.Clear(); + _indentation = 0; + } + + private void AddIndentation() + => _sb.Append(IndentationChar, CharsPerIndentation * _indentation); + + private static ReadOnlySpan GetNextLine(ref ReadOnlySpan remainingText, out bool isFinalLine) + { + if (remainingText.IsEmpty) + { + isFinalLine = true; + return default; + } + + ReadOnlySpan next; + ReadOnlySpan rest; + + int lineLength = remainingText.IndexOf('\n'); + if (lineLength == -1) + { + lineLength = remainingText.Length; + isFinalLine = true; + rest = default; + } + else + { + rest = remainingText.Slice(lineLength + 1); + isFinalLine = false; + } + + if ((uint)lineLength > 0 && remainingText[lineLength - 1] == '\r') + { + lineLength--; + } + + next = remainingText.Slice(0, lineLength); + remainingText = rest; + return next; + } + + private static unsafe void AppendSpan(StringBuilder builder, ReadOnlySpan span) + { + fixed (char* ptr = span) + { + builder.Append(ptr, span.Length); + } + } + } \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/SyntaxValueProviderExtensions.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/SyntaxValueProviderExtensions.cs new file mode 100644 index 0000000..f241e28 --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/SyntaxValueProviderExtensions.cs @@ -0,0 +1,279 @@ +using System.Collections.Immutable; +using System.Diagnostics; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace CSharpToJsonSchema.Generators.JsonGen.Base; + +internal static partial class SyntaxValueProviderExtensions +{ +#if false + + // Deviation from roslyn. We do not support attributes that are nested or generic. That's ok as that's not a + // scenario that ever arises in our generators. + + private static readonly char[] s_nestedTypeNameSeparators = new char[] { '+' }; + + private static readonly SymbolDisplayFormat s_metadataDisplayFormat = + SymbolDisplayFormat.QualifiedNameArityFormat.AddCompilerInternalOptions(SymbolDisplayCompilerInternalOptions.UsePlusForNestedTypes); + +#endif + + /// + /// Creates an that can provide a transform over all s if that node has an attribute on it that binds to a with the + /// same fully-qualified metadata as the provided . should be the fully-qualified, metadata name of the attribute, including the + /// Attribute suffix. For example "System.CLSCompliantAttribute for . + /// + /// A function that determines if the given attribute target () should be transformed. Nodes that do not pass this + /// predicate will not have their attributes looked at at all. + /// A function that performs the transform. This will only be passed nodes that return for and which have a matching whose + /// has the same fully qualified, metadata name as . + public static IncrementalValuesProvider ForAttributeWithMetadataName( + this IncrementalValueProvider @this, + IncrementalGeneratorInitializationContext context, + string fullyQualifiedMetadataName, + Func predicate, + Func transform) + { +#if false + + // Deviation from roslyn. We do not support attributes that are nested or generic. That's ok as that's not a + // scenario that ever arises in our generators. + + var metadataName = fullyQualifiedMetadataName.Contains('+') + ? MetadataTypeName.FromFullName(fullyQualifiedMetadataName.Split(s_nestedTypeNameSeparators).Last()) + : MetadataTypeName.FromFullName(fullyQualifiedMetadataName); + + var nodesWithAttributesMatchingSimpleName = @this.ForAttributeWithSimpleName(context, metadataName.UnmangledTypeName, predicate); + +#else + + var lastDotIndex = fullyQualifiedMetadataName.LastIndexOf('.'); + Debug.Assert(lastDotIndex > 0); + var unmangledTypeName = fullyQualifiedMetadataName.Substring(lastDotIndex + 1); + + var nodesWithAttributesMatchingSimpleName = @this.ForAttributeWithSimpleName(context, unmangledTypeName, predicate); + +#endif + + var compilationAndGroupedNodesProvider = nodesWithAttributesMatchingSimpleName + .Combine(context.CompilationProvider) + /*.WithTrackingName("compilationAndGroupedNodes_ForAttributeWithMetadataName")*/; + + var syntaxHelper = CSharpSyntaxHelper.Instance; + var finalProvider = compilationAndGroupedNodesProvider.SelectMany((tuple, cancellationToken) => + { + var ((syntaxTree, syntaxNodes), compilation) = tuple; + Debug.Assert(syntaxNodes.All(n => n.SyntaxTree == syntaxTree)); + + using var result = new ValueListBuilder(Span.Empty); + if (!syntaxNodes.IsEmpty) + { + var semanticModel = compilation.GetSemanticModel(syntaxTree); + + foreach (var targetNode in syntaxNodes) + { + cancellationToken.ThrowIfCancellationRequested(); + + var targetSymbol = + targetNode is ICompilationUnitSyntax compilationUnit ? semanticModel.Compilation.Assembly : + syntaxHelper.IsLambdaExpression(targetNode) ? semanticModel.GetSymbolInfo(targetNode, cancellationToken).Symbol : + semanticModel.GetDeclaredSymbol(targetNode, cancellationToken); + if (targetSymbol is null) + continue; + + var attributes = getMatchingAttributes(targetNode, targetSymbol, fullyQualifiedMetadataName); + if (attributes.Length > 0) + { + result.Append(transform( + new GeneratorAttributeSyntaxContext(targetNode, targetSymbol, semanticModel, attributes), + cancellationToken)); + } + } + } + + return result.AsSpan().ToImmutableArray(); + })/*.WithTrackingName("result_ForAttributeWithMetadataName")*/; + + return finalProvider; + + static ImmutableArray getMatchingAttributes( + SyntaxNode attributeTarget, + ISymbol symbol, + string fullyQualifiedMetadataName) + { + var targetSyntaxTree = attributeTarget.SyntaxTree; + var result = new ValueListBuilder(Span.Empty); + + try + { + addMatchingAttributes(ref result, symbol.GetAttributes()); + addMatchingAttributes(ref result, (symbol as IMethodSymbol)?.GetReturnTypeAttributes()); + + if (symbol is IAssemblySymbol assemblySymbol) + { + foreach (var module in assemblySymbol.Modules) + addMatchingAttributes(ref result, module.GetAttributes()); + } + + return result.AsSpan().ToImmutableArray(); + } + finally + { + result.Dispose(); + } + + void addMatchingAttributes( + ref ValueListBuilder result, + ImmutableArray? attributes) + { + if (!attributes.HasValue) + return; + + foreach (var attribute in attributes.Value) + { + if (attribute.ApplicationSyntaxReference?.SyntaxTree == targetSyntaxTree && + attribute.AttributeClass?.ToDisplayString(/*s_metadataDisplayFormat*/) == fullyQualifiedMetadataName) + { + result.Append(attribute); + } + } + } + } + } + + /// + /// Creates an that can provide a transform over all s if that node has an attribute on it that binds to a with the + /// same fully-qualified metadata as the provided . should be the fully-qualified, metadata name of the attribute, including the + /// Attribute suffix. For example "System.CLSCompliantAttribute for . + /// + /// A function that determines if the given attribute target () should be transformed. Nodes that do not pass this + /// predicate will not have their attributes looked at at all. + /// A function that performs the transform. This will only be passed nodes that return for and which have a matching whose + /// has the same fully qualified, metadata name as . + public static IncrementalValuesProvider ForAttributeWithMetadataName( + this SyntaxValueProvider @this, + IncrementalGeneratorInitializationContext context, + string fullyQualifiedMetadataName, + Func predicate, + Func transform) + { +#if false + + // Deviation from roslyn. We do not support attributes that are nested or generic. That's ok as that's not a + // scenario that ever arises in our generators. + + var metadataName = fullyQualifiedMetadataName.Contains('+') + ? MetadataTypeName.FromFullName(fullyQualifiedMetadataName.Split(s_nestedTypeNameSeparators).Last()) + : MetadataTypeName.FromFullName(fullyQualifiedMetadataName); + + var nodesWithAttributesMatchingSimpleName = @this.ForAttributeWithSimpleName(context, metadataName.UnmangledTypeName, predicate); + +#else + + var lastDotIndex = fullyQualifiedMetadataName.LastIndexOf('.'); + Debug.Assert(lastDotIndex > 0); + var unmangledTypeName = fullyQualifiedMetadataName.Substring(lastDotIndex + 1); + + var nodesWithAttributesMatchingSimpleName = @this.ForAttributeWithSimpleName(context, unmangledTypeName, predicate); + +#endif + + var compilationAndGroupedNodesProvider = nodesWithAttributesMatchingSimpleName + .Combine(context.CompilationProvider) + /*.WithTrackingName("compilationAndGroupedNodes_ForAttributeWithMetadataName")*/; + + var syntaxHelper = CSharpSyntaxHelper.Instance; + var finalProvider = compilationAndGroupedNodesProvider.SelectMany((tuple, cancellationToken) => + { + var ((syntaxTree, syntaxNodes), compilation) = tuple; + Debug.Assert(syntaxNodes.All(n => n.SyntaxTree == syntaxTree)); + + using var result = new ValueListBuilder(Span.Empty); + if (!syntaxNodes.IsEmpty) + { + var semanticModel = compilation.GetSemanticModel(syntaxTree); + + foreach (var targetNode in syntaxNodes) + { + cancellationToken.ThrowIfCancellationRequested(); + + var targetSymbol = + targetNode is ICompilationUnitSyntax compilationUnit ? semanticModel.Compilation.Assembly : + syntaxHelper.IsLambdaExpression(targetNode) ? semanticModel.GetSymbolInfo(targetNode, cancellationToken).Symbol : + semanticModel.GetDeclaredSymbol(targetNode, cancellationToken); + if (targetSymbol is null) + continue; + + var attributes = getMatchingAttributes(targetNode, targetSymbol, fullyQualifiedMetadataName); + if (attributes.Length > 0) + { + result.Append(transform( + new GeneratorAttributeSyntaxContext(targetNode, targetSymbol, semanticModel, attributes), + cancellationToken)); + } + } + } + + return result.AsSpan().ToImmutableArray(); + })/*.WithTrackingName("result_ForAttributeWithMetadataName")*/; + + return finalProvider; + + static ImmutableArray getMatchingAttributes( + SyntaxNode attributeTarget, + ISymbol symbol, + string fullyQualifiedMetadataName) + { + var targetSyntaxTree = attributeTarget.SyntaxTree; + var result = new ValueListBuilder(Span.Empty); + + try + { + addMatchingAttributes(ref result, symbol.GetAttributes()); + addMatchingAttributes(ref result, (symbol as IMethodSymbol)?.GetReturnTypeAttributes()); + + if (symbol is IAssemblySymbol assemblySymbol) + { + foreach (var module in assemblySymbol.Modules) + addMatchingAttributes(ref result, module.GetAttributes()); + } + + return result.AsSpan().ToImmutableArray(); + } + finally + { + result.Dispose(); + } + + void addMatchingAttributes( + ref ValueListBuilder result, + ImmutableArray? attributes) + { + if (!attributes.HasValue) + return; + + foreach (var attribute in attributes.Value) + { + if (attribute.ApplicationSyntaxReference?.SyntaxTree == targetSyntaxTree && + attribute.AttributeClass?.ToDisplayString(/*s_metadataDisplayFormat*/) == fullyQualifiedMetadataName) + { + result.Append(attribute); + } + } + } + } + } +} \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/SyntaxValueProviderExtensions_2.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/SyntaxValueProviderExtensions_2.cs new file mode 100644 index 0000000..d2efa83 --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/SyntaxValueProviderExtensions_2.cs @@ -0,0 +1,579 @@ +using System.Collections.Immutable; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using CSharpToJsonSchema.Generators.JsonGen.Helpers; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Aliases = CSharpToJsonSchema.Generators.JsonGen.Base.ValueListBuilder<(string aliasName, string symbolName)>; +namespace CSharpToJsonSchema.Generators.JsonGen.Base; + +internal static partial class SyntaxValueProviderExtensions +{ + // Normal class as Runtime does not seem to support records currently. + + /// + /// Information computed about a particular tree. Cached so we don't repeatedly recompute this important + /// information each time the incremental pipeline is rerun. + /// + private sealed class SyntaxTreeInfo : IEquatable + { + public readonly SyntaxTree Tree; + public readonly bool ContainsGlobalAliases; + public readonly bool ContainsAttributeList; + + public SyntaxTreeInfo(SyntaxTree tree, bool containsGlobalAliases, bool containsAttributeList) + { + Tree = tree; + ContainsGlobalAliases = containsGlobalAliases; + ContainsAttributeList = containsAttributeList; + } + + public bool Equals(SyntaxTreeInfo? other) + => Tree == other?.Tree; + + public override bool Equals(object? obj) + => this.Equals(obj as SyntaxTreeInfo); + + public override int GetHashCode() + => Tree.GetHashCode(); + } + + /// + /// Caching of syntax-tree to the info we've computed about it. Used because compilations will have thousands of + /// trees, and the incremental pipeline will get called back for *all* of them each time a compilation changes. We + /// do not want to continually recompute this data over and over again each time that happens given that normally + /// only one tree will be different. We also do not want to create an IncrementalValuesProvider that yield this + /// information as that will mean we have a node in the tree that scales with the number of *all syntax trees*, not + /// the number of *relevant syntax trees*. This can lead to huge memory churn keeping track of a high number of + /// trees, most of which are not going to be relevant. + /// + private static readonly ConditionalWeakTable s_treeToInfo = new ConditionalWeakTable(); + +#if false + // Not used in runtime. Pooling is not a pattern here, and we use ValueListBuilder instead. + + private static readonly ObjectPool> s_stackPool = new(static () => new()); +#endif + + /// + /// Returns all syntax nodes of that match if that node has an attribute on it that + /// could possibly bind to the provided . should be the + /// simple, non-qualified, name of the attribute, including the Attribute suffix, and not containing any + /// generics, containing types, or namespaces. For example CLSCompliantAttribute for . + /// This provider understands (Import in Visual Basic) aliases and will find + /// matches even when the attribute references an alias name. For example, given: + /// + /// using XAttribute = System.CLSCompliantAttribute; + /// [X] + /// class C { } + /// + /// Then + /// context.SyntaxProvider.CreateSyntaxProviderForAttribute(nameof(CLSCompliantAttribute), (node, c) => node is ClassDeclarationSyntax) + /// will find the C class. + /// + /// + /// Note: a 'Values'-provider of arrays are returned. Each array provides all the matching nodes from a single . + /// + public static IncrementalValuesProvider<(SyntaxTree tree, ImmutableArray matches)> ForAttributeWithSimpleName( + this SyntaxValueProvider @this, + IncrementalGeneratorInitializationContext context, + string simpleName, + Func predicate) + { + var syntaxHelper = CSharpSyntaxHelper.Instance; + + // Create a provider over all the syntax trees in the compilation. This is better than CreateSyntaxProvider as + // using SyntaxTrees is purely syntax and will not update the incremental node for a tree when another tree is + // changed. CreateSyntaxProvider will have to rerun all incremental nodes since it passes along the + // SemanticModel, and that model is updated whenever any tree changes (since it is tied to the compilation). + var syntaxTreesProvider = context.CompilationProvider + .SelectMany((compilation, cancellationToken) => compilation.SyntaxTrees + .Select(tree => GetTreeInfo(tree, syntaxHelper, cancellationToken)) + .Where(info => info.ContainsGlobalAliases || info.ContainsAttributeList)) + /*.WithTrackingName("compilationUnit_ForAttribute")*/; + + // Create a provider that provides (and updates) the global aliases for any particular file when it is edited. + var individualFileGlobalAliasesProvider = syntaxTreesProvider + .Where(info => info.ContainsGlobalAliases) + .Select((info, cancellationToken) => getGlobalAliasesInCompilationUnit(info.Tree.GetRoot(cancellationToken))) + /*.WithTrackingName("individualFileGlobalAliases_ForAttribute")*/; + + // Create an aggregated view of all global aliases across all files. This should only update when an individual + // file changes its global aliases or a file is added / removed from the compilation + var collectedGlobalAliasesProvider = individualFileGlobalAliasesProvider + .Collect() + .WithComparer(ImmutableArrayValueComparer.Instance) + /*.WithTrackingName("collectedGlobalAliases_ForAttribute")*/; + + var allUpGlobalAliasesProvider = collectedGlobalAliasesProvider + .Select(static (arrays, _) => GlobalAliases.Create(arrays.SelectMany(a => a.AliasAndSymbolNames).ToImmutableArray())) + /*.WithTrackingName("allUpGlobalAliases_ForAttribute")*/; + +#if false + + // C# does not support global aliases from compilation options. So we can just ignore this part. + + // Regenerate our data if the compilation options changed. VB can supply global aliases with compilation options, + // so we have to reanalyze everything if those changed. + var compilationGlobalAliases = _context.CompilationOptionsProvider.Select( + (o, _) => + { + var aliases = Aliases.GetInstance(); + syntaxHelper.AddAliases(o, aliases); + return GlobalAliases.Create(aliases.ToImmutableAndFree()); + }).WithTrackingName("compilationGlobalAliases_ForAttribute"); + + allUpGlobalAliasesProvider = allUpGlobalAliasesProvider + .Combine(compilationGlobalAliases) + .Select((tuple, _) => GlobalAliases.Concat(tuple.Left, tuple.Right)) + .WithTrackingName("allUpIncludingCompilationGlobalAliases_ForAttribute"); + +#endif + + // Combine the two providers so that we reanalyze every file if the global aliases change, or we reanalyze a + // particular file when it's compilation unit changes. + var syntaxTreeAndGlobalAliasesProvider = syntaxTreesProvider + .Where(info => info.ContainsAttributeList) + .Combine(allUpGlobalAliasesProvider) + /*.WithTrackingName("compilationUnitAndGlobalAliases_ForAttribute")*/; + + // For each pair of compilation unit + global aliases, walk the compilation unit + var result = syntaxTreeAndGlobalAliasesProvider + .Select((tuple, c) => (tuple.Left.Tree, GetMatchingNodes(syntaxHelper, tuple.Right, tuple.Left.Tree, simpleName, predicate, c))) + .Where(tuple => tuple.Item2.Length > 0) + /*.WithTrackingName("result_ForAttribute")*/; + + return result; + + static GlobalAliases getGlobalAliasesInCompilationUnit( + SyntaxNode compilationUnit) + { + Debug.Assert(compilationUnit is ICompilationUnitSyntax); + var globalAliases = new Aliases(Span<(string aliasName, string symbolName)>.Empty); + + CSharpSyntaxHelper.Instance.AddAliases(compilationUnit, ref globalAliases, global: true); + + return GlobalAliases.Create(CSharpToJsonSchema.Generators.JsonGen.Helpers.RoslynExtensions2.ToImmutableArray(globalAliases.AsSpan())); + } + } + + + /// + /// Returns all syntax nodes of that match if that node has an attribute on it that + /// could possibly bind to the provided . should be the + /// simple, non-qualified, name of the attribute, including the Attribute suffix, and not containing any + /// generics, containing types, or namespaces. For example CLSCompliantAttribute for . + /// This provider understands (Import in Visual Basic) aliases and will find + /// matches even when the attribute references an alias name. For example, given: + /// + /// using XAttribute = System.CLSCompliantAttribute; + /// [X] + /// class C { } + /// + /// Then + /// context.SyntaxProvider.CreateSyntaxProviderForAttribute(nameof(CLSCompliantAttribute), (node, c) => node is ClassDeclarationSyntax) + /// will find the C class. + /// + /// + /// Note: a 'Values'-provider of arrays are returned. Each array provides all the matching nodes from a single . + /// + public static IncrementalValuesProvider<(SyntaxTree tree, ImmutableArray matches)> ForAttributeWithSimpleName( + this IncrementalValueProvider @this, + IncrementalGeneratorInitializationContext context, + string simpleName, + Func predicate) + { + var syntaxHelper = CSharpSyntaxHelper.Instance; + + // Create a provider over all the syntax trees in the compilation. This is better than CreateSyntaxProvider as + // using SyntaxTrees is purely syntax and will not update the incremental node for a tree when another tree is + // changed. CreateSyntaxProvider will have to rerun all incremental nodes since it passes along the + // SemanticModel, and that model is updated whenever any tree changes (since it is tied to the compilation). + var syntaxTreesProvider = context.CompilationProvider + .SelectMany((compilation, cancellationToken) => compilation.SyntaxTrees + .Select(tree => GetTreeInfo(tree, syntaxHelper, cancellationToken)) + .Where(info => info.ContainsGlobalAliases || info.ContainsAttributeList)) + /*.WithTrackingName("compilationUnit_ForAttribute")*/; + + // Create a provider that provides (and updates) the global aliases for any particular file when it is edited. + var individualFileGlobalAliasesProvider = syntaxTreesProvider + .Where(info => info.ContainsGlobalAliases) + .Select((info, cancellationToken) => getGlobalAliasesInCompilationUnit(info.Tree.GetRoot(cancellationToken))) + /*.WithTrackingName("individualFileGlobalAliases_ForAttribute")*/; + + // Create an aggregated view of all global aliases across all files. This should only update when an individual + // file changes its global aliases or a file is added / removed from the compilation + var collectedGlobalAliasesProvider = individualFileGlobalAliasesProvider + .Collect() + .WithComparer(ImmutableArrayValueComparer.Instance) + /*.WithTrackingName("collectedGlobalAliases_ForAttribute")*/; + + var allUpGlobalAliasesProvider = collectedGlobalAliasesProvider + .Select(static (arrays, _) => GlobalAliases.Create(arrays.SelectMany(a => a.AliasAndSymbolNames).ToImmutableArray())) + /*.WithTrackingName("allUpGlobalAliases_ForAttribute")*/; + +#if false + + // C# does not support global aliases from compilation options. So we can just ignore this part. + + // Regenerate our data if the compilation options changed. VB can supply global aliases with compilation options, + // so we have to reanalyze everything if those changed. + var compilationGlobalAliases = _context.CompilationOptionsProvider.Select( + (o, _) => + { + var aliases = Aliases.GetInstance(); + syntaxHelper.AddAliases(o, aliases); + return GlobalAliases.Create(aliases.ToImmutableAndFree()); + }).WithTrackingName("compilationGlobalAliases_ForAttribute"); + + allUpGlobalAliasesProvider = allUpGlobalAliasesProvider + .Combine(compilationGlobalAliases) + .Select((tuple, _) => GlobalAliases.Concat(tuple.Left, tuple.Right)) + .WithTrackingName("allUpIncludingCompilationGlobalAliases_ForAttribute"); + +#endif + + // Combine the two providers so that we reanalyze every file if the global aliases change, or we reanalyze a + // particular file when it's compilation unit changes. + var syntaxTreeAndGlobalAliasesProvider = syntaxTreesProvider + .Where(info => info.ContainsAttributeList) + .Combine(allUpGlobalAliasesProvider) + /*.WithTrackingName("compilationUnitAndGlobalAliases_ForAttribute")*/; + + // For each pair of compilation unit + global aliases, walk the compilation unit + var result = syntaxTreeAndGlobalAliasesProvider + .Select((tuple, c) => (tuple.Left.Tree, GetMatchingNodes(syntaxHelper, tuple.Right, tuple.Left.Tree, simpleName, predicate, c))) + .Where(tuple => tuple.Item2.Length > 0) + /*.WithTrackingName("result_ForAttribute")*/; + + return result; + + static GlobalAliases getGlobalAliasesInCompilationUnit( + SyntaxNode compilationUnit) + { + Debug.Assert(compilationUnit is ICompilationUnitSyntax); + var globalAliases = new Aliases(Span<(string aliasName, string symbolName)>.Empty); + + CSharpSyntaxHelper.Instance.AddAliases(compilationUnit, ref globalAliases, global: true); + + return GlobalAliases.Create(CSharpToJsonSchema.Generators.JsonGen.Helpers.RoslynExtensions2.ToImmutableArray(globalAliases.AsSpan())); + } + } + + private static SyntaxTreeInfo GetTreeInfo( + SyntaxTree tree, ISyntaxHelper syntaxHelper, CancellationToken cancellationToken) + { + // prevent captures for the case where the item is in the tree. + return s_treeToInfo.TryGetValue(tree, out var info) + ? info + : computeTreeInfo(); + + SyntaxTreeInfo computeTreeInfo() + { + var root = tree.GetRoot(cancellationToken); + var containsGlobalAliases = syntaxHelper.ContainsGlobalAliases(root); + var containsAttributeList = ContainsAttributeList(root); + + var info = new SyntaxTreeInfo(tree, containsGlobalAliases, containsAttributeList); + return s_treeToInfo.GetValue(tree, _ => info); + } + } + + private static ImmutableArray GetMatchingNodes( + ISyntaxHelper syntaxHelper, + GlobalAliases globalAliases, + SyntaxTree syntaxTree, + string name, + Func predicate, + CancellationToken cancellationToken) + { + var compilationUnit = syntaxTree.GetRoot(cancellationToken); + Debug.Assert(compilationUnit is ICompilationUnitSyntax); + + var isCaseSensitive = syntaxHelper.IsCaseSensitive; + var comparison = isCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; + + // As we walk down the compilation unit and nested namespaces, we may encounter additional using aliases local + // to this file. Keep track of them so we can determine if they would allow an attribute in code to bind to the + // attribute being searched for. + var localAliases = new Aliases(Span<(string, string)>.Empty); + var nameHasAttributeSuffix = name.HasAttributeSuffix(isCaseSensitive); + + // Used to ensure that as we recurse through alias names to see if they could bind to attributeName that we + // don't get into cycles. + + var seenNames = new ValueListBuilder(Span.Empty); + var results = new ValueListBuilder(Span.Empty); + var attributeTargets = new ValueListBuilder(Span.Empty); + + try + { + processCompilationUnit(compilationUnit, ref localAliases, ref seenNames, ref results, ref attributeTargets); + + if (results.Length == 0) + return ImmutableArray.Empty; + + return results.AsSpan().ToArray().Distinct().ToImmutableArray(); + } + finally + { + attributeTargets.Dispose(); + results.Dispose(); + seenNames.Dispose(); + } + + void processCompilationUnit( + SyntaxNode compilationUnit, + ref Aliases localAliases, + ref ValueListBuilder seenNames, + ref ValueListBuilder results, + ref ValueListBuilder attributeTargets) + { + cancellationToken.ThrowIfCancellationRequested(); + + syntaxHelper.AddAliases(compilationUnit, ref localAliases, global: false); + + processCompilationOrNamespaceMembers(compilationUnit, ref localAliases, ref seenNames, ref results, ref attributeTargets); + } + + void processCompilationOrNamespaceMembers( + SyntaxNode node, + ref Aliases localAliases, + ref ValueListBuilder seenNames, + ref ValueListBuilder results, + ref ValueListBuilder attributeTargets) + { + cancellationToken.ThrowIfCancellationRequested(); + + foreach (var child in node.ChildNodesAndTokens()) + { + if (child.IsNode) + { + var childNode = child.AsNode()!; + if (syntaxHelper.IsAnyNamespaceBlock(childNode)) + processNamespaceBlock(childNode, ref localAliases, ref seenNames, ref results, ref attributeTargets); + else + processMember(childNode, ref localAliases, ref seenNames, ref results, ref attributeTargets); + } + } + } + + void processNamespaceBlock( + SyntaxNode namespaceBlock, + ref Aliases localAliases, + ref ValueListBuilder seenNames, + ref ValueListBuilder results, + ref ValueListBuilder attributeTargets) + { + cancellationToken.ThrowIfCancellationRequested(); + + var localAliasCount = localAliases.Length; + syntaxHelper.AddAliases(namespaceBlock, ref localAliases, global: false); + + processCompilationOrNamespaceMembers( + namespaceBlock, ref localAliases, ref seenNames, ref results, ref attributeTargets); + + // after recursing into this namespace, dump any local aliases we added from this namespace decl itself. + localAliases.Length = localAliasCount; + } + + void processMember( + SyntaxNode member, + ref Aliases localAliases, + ref ValueListBuilder seenNames, + ref ValueListBuilder results, + ref ValueListBuilder attributeTargets) + { + cancellationToken.ThrowIfCancellationRequested(); + + // nodes can be arbitrarily deep. Use an explicit stack over recursion to prevent a stack-overflow. + var nodeStack = new ValueListBuilder(Span.Empty); + nodeStack.Append(member); + + try + { + while (nodeStack.Length > 0) + { + var node = nodeStack.Pop(); + + if (syntaxHelper.IsAttributeList(node)) + { + foreach (var attribute in syntaxHelper.GetAttributesOfAttributeList(node)) + { + // Have to lookup both with the name in the attribute, as well as adding the 'Attribute' suffix. + // e.g. if there is [X] then we have to lookup with X and with XAttribute. + var simpleAttributeName = syntaxHelper.GetUnqualifiedIdentifierOfName( + syntaxHelper.GetNameOfAttribute(attribute)).ValueText; + if (matchesAttributeName(ref localAliases, ref seenNames, simpleAttributeName, withAttributeSuffix: false) || + matchesAttributeName(ref localAliases, ref seenNames, simpleAttributeName, withAttributeSuffix: true)) + { + attributeTargets.Length = 0; + syntaxHelper.AddAttributeTargets(node, ref attributeTargets); + + foreach (var target in attributeTargets.AsSpan()) + { + if (predicate(target, cancellationToken)) + results.Append(target); + } + + break; + } + } + + // attributes can't have attributes inside of them. so no need to recurse when we're done. + } + else + { + // For any other node, just keep recursing deeper to see if we can find an attribute. Note: we cannot + // terminate the search anywhere as attributes may be found on things like local functions, and that + // means having to dive deep into statements and expressions. + var childNodesAndTokens = node.ChildNodesAndTokens(); + + // Avoid performance issue in ChildSyntaxList when iterating the child list in reverse + // (see https://github.com/dotnet/roslyn/issues/66475) by iterating forward first to + // ensure child nodes are realized. + foreach (var childNode in childNodesAndTokens) + { + } + + foreach (var child in childNodesAndTokens.Reverse()) + { + if (child.IsNode) + nodeStack.Append(child.AsNode()!); + } + } + + } + } + finally + { + nodeStack.Dispose(); + } + } + + // Checks if `name` is equal to `matchAgainst`. if `withAttributeSuffix` is true, then + // will check if `name` + "Attribute" is equal to `matchAgainst` + bool matchesName(string name, string matchAgainst, bool withAttributeSuffix) + { + if (withAttributeSuffix) + { + return name.Length + "Attribute".Length == matchAgainst.Length && + matchAgainst.HasAttributeSuffix(isCaseSensitive) && + matchAgainst.StartsWith(name, comparison); + } + else + { + return name.Equals(matchAgainst, comparison); + } + } + + bool matchesAttributeName( + ref Aliases localAliases, + ref ValueListBuilder seenNames, + string currentAttributeName, + bool withAttributeSuffix) + { + // If the names match, we're done. + if (withAttributeSuffix) + { + if (nameHasAttributeSuffix && + matchesName(currentAttributeName, name, withAttributeSuffix)) + { + return true; + } + } + else + { + if (matchesName(currentAttributeName, name, withAttributeSuffix: false)) + return true; + } + + // Otherwise, keep searching through aliases. Check that this is the first time seeing this name so we + // don't infinite recurse in error code where aliases reference each other. + // + // note: as we recurse up the aliases, we do not want to add the attribute suffix anymore. aliases must + // reference the actual real name of the symbol they are aliasing. + foreach (var seenName in seenNames.AsSpan()) + { + if (seenName == currentAttributeName) + return false; + } + + seenNames.Append(currentAttributeName); + + foreach (var (aliasName, symbolName) in localAliases.AsSpan()) + { + // see if user wrote `[SomeAlias]`. If so, if we find a `using SomeAlias = ...` recurse using the + // ... name portion to see if it might bind to the attr name the caller is searching for. + if (matchesName(currentAttributeName, aliasName, withAttributeSuffix) && + matchesAttributeName(ref localAliases, ref seenNames, symbolName, withAttributeSuffix: false)) + { + return true; + } + } + + foreach (var (aliasName, symbolName) in globalAliases.AliasAndSymbolNames) + { + if (matchesName(currentAttributeName, aliasName, withAttributeSuffix) && + matchesAttributeName(ref localAliases, ref seenNames, symbolName, withAttributeSuffix: false)) + { + return true; + } + } + + seenNames.Pop(); + return false; + } + } + + private static bool ContainsAttributeList(SyntaxNode node) + { + if (node.IsKind(SyntaxKind.AttributeList)) + return true; + + foreach (SyntaxNodeOrToken child in node.ChildNodesAndTokens()) + { + if (child.IsToken) + continue; + + SyntaxNode? childNode = child.AsNode()!; + if (ContainsAttributeList(childNode)) + return true; + } + + return false; + } + private sealed class ImmutableArrayValueComparer : IEqualityComparer> + { + public static readonly IEqualityComparer> Instance = new ImmutableArrayValueComparer(); + + public bool Equals(ImmutableArray x, ImmutableArray y) + => x.SequenceEqual(y, EqualityComparer.Default); + + public int GetHashCode(ImmutableArray obj) + { + var hashCode = 0; + foreach (var value in obj) + hashCode = Hash.Combine(hashCode, EqualityComparer.Default.GetHashCode(value!)); + + return hashCode; + } + } +} + +/// +/// These public methods are required by RegexWriter. +/// +internal ref partial struct ValueListBuilder +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T Pop() + { + _pos--; + return _span[_pos]; + } +} \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/ValueEqualityImmutableDictionary.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/ValueEqualityImmutableDictionary.cs new file mode 100644 index 0000000..60bec9f --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/ValueEqualityImmutableDictionary.cs @@ -0,0 +1,57 @@ +using System.Collections; +using System.Collections.Immutable; + +namespace CSharpToJsonSchema.Generators.JsonGen.Base; + + public record struct ValueEqualityImmutableDictionary(ImmutableDictionary Map) : IDictionary + { + // Since this is immutable, we can cache the hash, which requires sorting and is expensive to calculate + private int? _hash = null; + + public bool Equals(ValueEqualityImmutableDictionary other) + { + if (Count != other.Count) + { + return false; + } + + foreach (var kvp in this) + { + if (!other.TryGetValue(kvp.Key, out var value) || !kvp.Value.Equals(value)) + { + return false; + } + } + return true; + } + + public override int GetHashCode() + { + if (_hash.HasValue) + return _hash.Value; + + _hash = 0; + foreach (var kvp in Map.ToImmutableArray().Sort()) + { + _hash = HashCode.Combine(_hash, kvp.Key, kvp.Value); + } + return _hash.Value; + } + + public U this[T key] { get => ((IDictionary)Map)[key]; set => ((IDictionary)Map)[key] = value; } + public ICollection Keys => ((IDictionary)Map).Keys; + public ICollection Values => ((IDictionary)Map).Values; + public int Count => Map.Count; + public bool IsReadOnly => ((ICollection>)Map).IsReadOnly; + public bool Contains(KeyValuePair item) => Map.Contains(item); + public bool ContainsKey(T key) => Map.ContainsKey(key); + public void CopyTo(KeyValuePair[] array, int arrayIndex) => ((ICollection>)Map).CopyTo(array, arrayIndex); + public IEnumerator> GetEnumerator() => ((IEnumerable>)Map).GetEnumerator(); + public bool Remove(T key) => ((IDictionary)Map).Remove(key); + public bool Remove(KeyValuePair item) => ((ICollection>)Map).Remove(item); + public bool TryGetValue(T key, out U value) => Map.TryGetValue(key, out value); + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)Map).GetEnumerator(); + public void Add(T key, U value) => ((IDictionary)Map).Add(key, value); + public void Add(KeyValuePair item) => ((ICollection>)Map).Add(item); + public void Clear() => ((ICollection>)Map).Clear(); + } \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/ValueEqualityImmutableDictionaryHelperExtensions.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/ValueEqualityImmutableDictionaryHelperExtensions.cs new file mode 100644 index 0000000..9b7cb09 --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/ValueEqualityImmutableDictionaryHelperExtensions.cs @@ -0,0 +1,19 @@ +using System.Collections.Immutable; + +namespace CSharpToJsonSchema.Generators.JsonGen.Base; + +public static class ValueEqualityImmutableDictionaryHelperExtensions +{ + public static ValueEqualityImmutableDictionary ToValueEqualityImmutableDictionary( + this IEnumerable source, + Func keySelector, + Func valueSelector) + { + return new ValueEqualityImmutableDictionary(source.ToImmutableDictionary(keySelector, valueSelector)); + } + public static ValueEqualityImmutableDictionary ToValueEquals(this ImmutableDictionary source) + { + return new ValueEqualityImmutableDictionary(source); + } + +} \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/ValueListBuilder.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/ValueListBuilder.cs new file mode 100644 index 0000000..bd5017c --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Base/ValueListBuilder.cs @@ -0,0 +1,203 @@ +using System.Buffers; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace CSharpToJsonSchema.Generators.JsonGen.Base; + +internal ref partial struct ValueListBuilder +{ + private Span _span; + private T[]? _arrayFromPool; + private int _pos; + + public ValueListBuilder(Span scratchBuffer) + { + _span = scratchBuffer!; + } + + public ValueListBuilder(int capacity) + { + Grow(capacity); + } + + public int Length + { + get => _pos; + set + { + Debug.Assert(value >= 0); + Debug.Assert(value <= _span.Length); + _pos = value; + } + } + + public ref T this[int index] + { + get + { + Debug.Assert(index < _pos); + return ref _span[index]; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Append(T item) + { + int pos = _pos; + + // Workaround for https://github.com/dotnet/runtime/issues/72004 + Span span = _span; + if ((uint)pos < (uint)span.Length) + { + span[pos] = item; + _pos = pos + 1; + } + else + { + AddWithResize(item); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Append(scoped ReadOnlySpan source) + { + int pos = _pos; + Span span = _span; + if (source.Length == 1 && (uint)pos < (uint)span.Length) + { + span[pos] = source[0]; + _pos = pos + 1; + } + else + { + AppendMultiChar(source); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void AppendMultiChar(scoped ReadOnlySpan source) + { + if ((uint)(_pos + source.Length) > (uint)_span.Length) + { + Grow(_span.Length - _pos + source.Length); + } + + source.CopyTo(_span.Slice(_pos)); + _pos += source.Length; + } + + public void Insert(int index, scoped ReadOnlySpan source) + { + Debug.Assert(index == 0, "Implementation currently only supports index == 0"); + + if ((uint)(_pos + source.Length) > (uint)_span.Length) + { + Grow(source.Length); + } + + _span.Slice(0, _pos).CopyTo(_span.Slice(source.Length)); + source.CopyTo(_span); + _pos += source.Length; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span AppendSpan(int length) + { + Debug.Assert(length >= 0); + + int pos = _pos; + Span span = _span; + if ((ulong)(uint)pos + (ulong)(uint)length <= (ulong)(uint)span.Length) // same guard condition as in Span.Slice on 64-bit + { + _pos = pos + length; + return span.Slice(pos, length); + } + else + { + return AppendSpanWithGrow(length); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private Span AppendSpanWithGrow(int length) + { + int pos = _pos; + Grow(_span.Length - pos + length); + _pos += length; + return _span.Slice(pos, length); + } + + // Hide uncommon path + [MethodImpl(MethodImplOptions.NoInlining)] + private void AddWithResize(T item) + { + Debug.Assert(_pos == _span.Length); + int pos = _pos; + Grow(1); + _span[pos] = item; + _pos = pos + 1; + } + + public ReadOnlySpan AsSpan() + { + return _span.Slice(0, _pos); + } + + public bool TryCopyTo(Span destination, out int itemsWritten) + { + if (_span.Slice(0, _pos).TryCopyTo(destination)) + { + itemsWritten = _pos; + return true; + } + + itemsWritten = 0; + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Dispose() + { + T[]? toReturn = _arrayFromPool; + if (toReturn != null) + { + _arrayFromPool = null; + ArrayPool.Shared.Return(toReturn); + } + } + + // Note that consuming implementations depend on the list only growing if it's absolutely + // required. If the list is already large enough to hold the additional items be added, + // it must not grow. The list is used in a number of places where the reference is checked + // and it's expected to match the initial reference provided to the constructor if that + // span was sufficiently large. + private void Grow(int additionalCapacityRequired = 1) + { + const int ArrayMaxLength = 0x7FFFFFC7; // same as Array.MaxLength + + // Double the size of the span. If it's currently empty, default to size 4, + // although it'll be increased in Rent to the pool's minimum bucket size. + int nextCapacity = Math.Max(_span.Length != 0 ? _span.Length * 2 : 4, _span.Length + additionalCapacityRequired); + + // If the computed doubled capacity exceeds the possible length of an array, then we + // want to downgrade to either the maximum array length if that's large enough to hold + // an additional item, or the current length + 1 if it's larger than the max length, in + // which case it'll result in an OOM when calling Rent below. In the exceedingly rare + // case where _span.Length is already int.MaxValue (in which case it couldn't be a managed + // array), just use that same value again and let it OOM in Rent as well. + if ((uint)nextCapacity > ArrayMaxLength) + { + nextCapacity = Math.Max(Math.Max(_span.Length + 1, ArrayMaxLength), _span.Length); + } + + T[] array = ArrayPool.Shared.Rent(nextCapacity); + _span.CopyTo(array); + + T[]? toReturn = _arrayFromPool; + _span = _arrayFromPool = array; + if (toReturn != null) + { + ArrayPool.Shared.Return(toReturn); + } + } +} \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Helpers/DiagnosticHelper.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Helpers/DiagnosticHelper.cs new file mode 100644 index 0000000..2852fab --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Helpers/DiagnosticHelper.cs @@ -0,0 +1,22 @@ +using Microsoft.CodeAnalysis; + +namespace CSharpToJsonSchema.Generators.JsonGen.Helpers +{ + internal static partial class DiagnosticDescriptorHelper + { + public static DiagnosticDescriptor Create( + string id, + LocalizableString title, + LocalizableString messageFormat, + string category, + DiagnosticSeverity defaultSeverity, + bool isEnabledByDefault, + LocalizableString? description = null, + params string[] customTags) + { + string helpLink = $"https://learn.microsoft.com/dotnet/fundamentals/syslib-diagnostics/{id.ToLowerInvariant()}"; + + return new DiagnosticDescriptor(id, title, messageFormat, category, defaultSeverity, isEnabledByDefault, description, helpLink, customTags); + } + } +} \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Helpers/HashHelpers.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Helpers/HashHelpers.cs new file mode 100644 index 0000000..e4b1134 --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Helpers/HashHelpers.cs @@ -0,0 +1,117 @@ +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace CSharpToJsonSchema.Generators.JsonGen.Helpers; + + internal static partial class HashHelpers + { + public const uint HashCollisionThreshold = 100; + + // This is the maximum prime smaller than Array.MaxLength. + public const int MaxPrimeArrayLength = 0x7FFFFFC3; + + public const int HashPrime = 101; + + // Table of prime numbers to use as hash table sizes. + // A typical resize algorithm would pick the smallest prime number in this array + // that is larger than twice the previous capacity. + // Suppose our Hashtable currently has capacity x and enough elements are added + // such that a resize needs to occur. Resizing first computes 2x then finds the + // first prime in the table greater than 2x, i.e. if primes are ordered + // p_1, p_2, ..., p_i, ..., it finds p_n such that p_n-1 < 2x < p_n. + // Doubling is important for preserving the asymptotic complexity of the + // hashtable operations such as add. Having a prime guarantees that double + // hashing does not lead to infinite loops. IE, your hash function will be + // h1(key) + i*h2(key), 0 <= i < size. h2 and the size must be relatively prime. + // We prefer the low computation costs of higher prime numbers over the increased + // memory allocation of a fixed prime number i.e. when right sizing a HashSet. + internal static ReadOnlySpan Primes => + [ + 3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919, + 1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591, + 17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437, + 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263, + 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369 + ]; + + public static bool IsPrime(int candidate) + { + if ((candidate & 1) != 0) + { + int limit = (int)Math.Sqrt(candidate); + for (int divisor = 3; divisor <= limit; divisor += 2) + { + if ((candidate % divisor) == 0) + return false; + } + return true; + } + return candidate == 2; + } + + public static int GetPrime(int min) + { + if (min < 0) + throw new ArgumentException("Capacity overflow: minimum value cannot be less than zero."); + + foreach (int prime in Primes) + { + if (prime >= min) + return prime; + } + + // Outside of our predefined table. Compute the hard way. + for (int i = (min | 1); i < int.MaxValue; i += 2) + { + if (IsPrime(i) && ((i - 1) % HashPrime != 0)) + return i; + } + return min; + } + + // Returns size of hashtable to grow to. + public static int ExpandPrime(int oldSize) + { + int newSize = 2 * oldSize; + + // Allow the hashtables to grow to maximum possible size (~2G elements) before encountering capacity overflow. + // Note that this check works even when _items.Length overflowed thanks to the (uint) cast + if ((uint)newSize > MaxPrimeArrayLength && MaxPrimeArrayLength > oldSize) + { + Debug.Assert(MaxPrimeArrayLength == GetPrime(MaxPrimeArrayLength), "Invalid MaxPrimeArrayLength"); + return MaxPrimeArrayLength; + } + + return GetPrime(newSize); + } + + /// Returns approximate reciprocal of the divisor: ceil(2**64 / divisor). + /// This should only be used on 64-bit. + public static ulong GetFastModMultiplier(uint divisor) => + ulong.MaxValue / divisor + 1; + + /// Performs a mod operation using the multiplier pre-computed with . + /// This should only be used on 64-bit. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint FastMod(uint value, uint divisor, ulong multiplier) + { + // We use modified Daniel Lemire's fastmod algorithm (https://github.com/dotnet/runtime/pull/406), + // which allows to avoid the long multiplication if the divisor is less than 2**31. + Debug.Assert(divisor <= int.MaxValue); + + // This is equivalent of (uint)Math.BigMul(multiplier * value, divisor, out _). This version + // is faster than BigMul currently because we only need the high bits. + uint highbits = (uint)(((((multiplier * value) >> 32) + 1) * divisor) >> 32); + + Debug.Assert(highbits == value % divisor); + return highbits; + } + + public static int Combine(int h1, int h2) + { + // RyuJIT optimizes this to use the ROL instruction + // Related GitHub pull request: https://github.com/dotnet/coreclr/pull/1830 + uint rol5 = ((uint)h1 << 5) | ((uint)h1 >> 27); + return ((int)rol5 + h1) ^ h2; + } + } \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Helpers/JsonHelpers.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Helpers/JsonHelpers.cs new file mode 100644 index 0000000..7735e1a --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Helpers/JsonHelpers.cs @@ -0,0 +1,171 @@ +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using CSharpToJsonSchema.Generators.JsonGen.System.Text.Json; + +namespace CSharpToJsonSchema.Generators.JsonGen.Helpers; + + + internal static partial class JsonHelpers + { +#if !NET + /// + /// netstandard/netfx polyfill for Dictionary.TryAdd + /// + public static bool TryAdd(this Dictionary dictionary, TKey key, TValue value) where TKey : notnull + { + if (!dictionary.ContainsKey(key)) + { + dictionary[key] = value; + return true; + } + + return false; + } + + /// + /// netstandard/netfx polyfill for Queue.TryDequeue + /// + public static bool TryDequeue(this Queue queue, [NotNullWhen(true)] out T? result) + { + if (queue.Count > 0) + { + result = queue.Dequeue(); + return true; + } + + result = default; + return false; + } +#endif + + internal static bool RequiresSpecialNumberHandlingOnWrite(JsonNumberHandling? handling) + { + return handling != null + ? (handling.Value & (JsonNumberHandling.WriteAsString | JsonNumberHandling.AllowNamedFloatingPointLiterals)) != 0 + : false; + } + + /// + /// Provides an in-place, stable sorting implementation for List. + /// + internal static void StableSortByKey(this List items, Func keySelector) + where TKey : unmanaged, IComparable + { +#if NET + Span span = CollectionsMarshal.AsSpan(items); + + // Tuples implement lexical ordering OOTB which can be used to encode stable sorting + // using the actual key as the first element and index as the second element. + const int StackallocThreshold = 32; + Span<(TKey, int)> keys = span.Length <= StackallocThreshold + ? (stackalloc (TKey, int)[StackallocThreshold]).Slice(0, span.Length) + : new (TKey, int)[span.Length]; + + for (int i = 0; i < keys.Length; i++) + { + keys[i] = (keySelector(span[i]), i); + } + + MemoryExtensions.Sort(keys, span); +#else + T[] arrayCopy = items.ToArray(); + (TKey, int)[] keys = new (TKey, int)[arrayCopy.Length]; + for (int i = 0; i < keys.Length; i++) + { + keys[i] = (keySelector(arrayCopy[i]), i); + } + + Array.Sort(keys, arrayCopy); + items.Clear(); + items.AddRange(arrayCopy); +#endif + } + + /// + /// Traverses a DAG and returns its nodes applying topological sorting to the result. + /// + public static T[] TraverseGraphWithTopologicalSort(T entryNode, Func> getChildren, IEqualityComparer? comparer = null) + where T : notnull + { + comparer ??= EqualityComparer.Default; + + // Implements Kahn's algorithm. + // Step 1. Traverse and build the graph, labeling each node with an integer. + + var nodes = new List { entryNode }; // the integer-to-node mapping + var nodeIndex = new Dictionary(comparer) { [entryNode] = 0 }; // the node-to-integer mapping + var adjacency = new List(); // the growable adjacency matrix + var childlessQueue = new Queue(); // the queue of nodes without children or whose children have been visited + + for (int i = 0; i < nodes.Count; i++) + { + T next = nodes[i]; + ICollection children = getChildren(next); + int count = children.Count; + + if (count == 0) + { + adjacency.Add(null); // can use null in this row of the adjacency matrix. + childlessQueue.Enqueue(i); + continue; + } + + var adjacencyRow = new bool[Math.Max(nodes.Count, count)]; + foreach (T childNode in children) + { + if (!nodeIndex.TryGetValue(childNode, out int index)) + { + // this is the first time we're encountering this node. + // Assign it an index and append it to the maps. + + index = nodes.Count; + nodeIndex.Add(childNode, index); + nodes.Add(childNode); + } + + // Grow the adjacency row as appropriate. + if (index >= adjacencyRow.Length) + { + Array.Resize(ref adjacencyRow, index + 1); + } + + // Set the relevant bit in the adjacency row. + adjacencyRow[index] = true; + } + + // Append the row to the adjacency matrix. + adjacency.Add(adjacencyRow); + } + + Debug.Assert(childlessQueue.Count > 0, "The graph contains cycles."); + + // Step 2. Build the sorted array, walking from the nodes without children upward. + var sortedNodes = new T[nodes.Count]; + int idx = sortedNodes.Length; + + do + { + int nextIndex = childlessQueue.Dequeue(); + sortedNodes[--idx] = nodes[nextIndex]; + + // Iterate over the adjacency matrix, removing any occurrence of nextIndex. + for (int i = 0; i < adjacency.Count; i++) + { + if (adjacency[i] is { } childMap && nextIndex < childMap.Length && childMap[nextIndex]) + { + childMap[nextIndex] = false; + + if (childMap.AsSpan().IndexOf(true) == -1) + { + // nextIndex was the last child removed from i, add to queue. + childlessQueue.Enqueue(i); + } + } + } + + } while (childlessQueue.Count > 0); + + Debug.Assert(idx == 0, "should have populated the entire sortedNodes array."); + return sortedNodes; + } + } \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Helpers/KnownTypeSymbols.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Helpers/KnownTypeSymbols.cs new file mode 100644 index 0000000..21ed68e --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Helpers/KnownTypeSymbols.cs @@ -0,0 +1,372 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Immutable; +using System.Collections.ObjectModel; +using System.Reflection; +using Microsoft.CodeAnalysis; + +//using Microsoft.CodeAnalysis.DotnetRuntime.Extensions; + +namespace CSharpToJsonSchema.Generators.JsonGen.Helpers +{ + internal sealed class KnownTypeSymbols + { + public KnownTypeSymbols(Compilation compilation) + => Compilation = compilation; + + public Compilation Compilation { get; } + + // Caches a set of types with built-in converter support. Populated by the Parser class. + public HashSet? BuiltInSupportTypes { get; set; } + + public INamedTypeSymbol? IListOfTType => GetOrResolveType(typeof(IList<>), ref _IListOfTType); + private Option _IListOfTType; + + public INamedTypeSymbol? ICollectionOfTType => GetOrResolveType(typeof(ICollection<>), ref _ICollectionOfTType); + private Option _ICollectionOfTType; + + public INamedTypeSymbol? IEnumerableType => GetOrResolveType(typeof(IEnumerable), ref _IEnumerableType); + private Option _IEnumerableType; + + public INamedTypeSymbol? IEnumerableOfTType => GetOrResolveType(typeof(IEnumerable<>), ref _IEnumerableOfTType); + private Option _IEnumerableOfTType; + + public INamedTypeSymbol? ListOfTType => GetOrResolveType(typeof(List<>), ref _ListOfTType); + private Option _ListOfTType; + + public INamedTypeSymbol? DictionaryOfTKeyTValueType => GetOrResolveType(typeof(Dictionary<,>), ref _DictionaryOfTKeyTValueType); + private Option _DictionaryOfTKeyTValueType; + + public INamedTypeSymbol? IAsyncEnumerableOfTType => GetOrResolveType("System.Collections.Generic.IAsyncEnumerable`1", ref _AsyncEnumerableOfTType); + private Option _AsyncEnumerableOfTType; + + public INamedTypeSymbol? IDictionaryOfTKeyTValueType => GetOrResolveType(typeof(IDictionary<,>), ref _IDictionaryOfTKeyTValueType); + private Option _IDictionaryOfTKeyTValueType; + + public INamedTypeSymbol? IReadonlyDictionaryOfTKeyTValueType => GetOrResolveType(typeof(IReadOnlyDictionary<,>), ref _IReadonlyDictionaryOfTKeyTValueType); + private Option _IReadonlyDictionaryOfTKeyTValueType; + + public INamedTypeSymbol? ISetOfTType => GetOrResolveType(typeof(ISet<>), ref _ISetOfTType); + private Option _ISetOfTType; + + public INamedTypeSymbol? StackOfTType => GetOrResolveType(typeof(Stack<>), ref _StackOfTType); + private Option _StackOfTType; + + public INamedTypeSymbol? QueueOfTType => GetOrResolveType(typeof(Queue<>), ref _QueueOfTType); + private Option _QueueOfTType; + + public INamedTypeSymbol? ConcurrentStackType => GetOrResolveType(typeof(ConcurrentStack<>), ref _ConcurrentStackType); + private Option _ConcurrentStackType; + + public INamedTypeSymbol? ConcurrentQueueType => GetOrResolveType(typeof(ConcurrentQueue<>), ref _ConcurrentQueueType); + private Option _ConcurrentQueueType; + + public INamedTypeSymbol? IDictionaryType => GetOrResolveType(typeof(IDictionary), ref _IDictionaryType); + private Option _IDictionaryType; + + public INamedTypeSymbol? IListType => GetOrResolveType(typeof(IList), ref _IListType); + private Option _IListType; + + public INamedTypeSymbol? StackType => GetOrResolveType(typeof(Stack), ref _StackType); + private Option _StackType; + + public INamedTypeSymbol? QueueType => GetOrResolveType(typeof(Queue), ref _QueueType); + private Option _QueueType; + + public INamedTypeSymbol? KeyValuePair => GetOrResolveType(typeof(KeyValuePair<,>), ref _KeyValuePair); + private Option _KeyValuePair; + + public INamedTypeSymbol? ImmutableArrayType => GetOrResolveType(typeof(ImmutableArray<>), ref _ImmutableArrayType); + private Option _ImmutableArrayType; + + public INamedTypeSymbol? ImmutableListType => GetOrResolveType(typeof(ImmutableList<>), ref _ImmutableListType); + private Option _ImmutableListType; + + public INamedTypeSymbol? IImmutableListType => GetOrResolveType(typeof(IImmutableList<>), ref _IImmutableListType); + private Option _IImmutableListType; + + public INamedTypeSymbol? ImmutableStackType => GetOrResolveType(typeof(ImmutableStack<>), ref _ImmutableStackType); + private Option _ImmutableStackType; + + public INamedTypeSymbol? IImmutableStackType => GetOrResolveType(typeof(IImmutableStack<>), ref _IImmutableStackType); + private Option _IImmutableStackType; + + public INamedTypeSymbol? ImmutableQueueType => GetOrResolveType(typeof(ImmutableQueue<>), ref _ImmutableQueueType); + private Option _ImmutableQueueType; + + public INamedTypeSymbol? IImmutableQueueType => GetOrResolveType(typeof(IImmutableQueue<>), ref _IImmutableQueueType); + private Option _IImmutableQueueType; + + public INamedTypeSymbol? ImmutableSortedType => GetOrResolveType(typeof(ImmutableSortedSet<>), ref _ImmutableSortedType); + private Option _ImmutableSortedType; + + public INamedTypeSymbol? ImmutableHashSetType => GetOrResolveType(typeof(ImmutableHashSet<>), ref _ImmutableHashSetType); + private Option _ImmutableHashSetType; + + public INamedTypeSymbol? IImmutableSetType => GetOrResolveType(typeof(IImmutableSet<>), ref _IImmutableSetType); + private Option _IImmutableSetType; + + public INamedTypeSymbol? ImmutableDictionaryType => GetOrResolveType(typeof(ImmutableDictionary<,>), ref _ImmutableDictionaryType); + private Option _ImmutableDictionaryType; + + public INamedTypeSymbol? ImmutableSortedDictionaryType => GetOrResolveType(typeof(ImmutableSortedDictionary<,>), ref _ImmutableSortedDictionaryType); + private Option _ImmutableSortedDictionaryType; + + public INamedTypeSymbol? IImmutableDictionaryType => GetOrResolveType(typeof(IImmutableDictionary<,>), ref _IImmutableDictionaryType); + private Option _IImmutableDictionaryType; + + public INamedTypeSymbol? KeyedCollectionType => GetOrResolveType(typeof(KeyedCollection<,>), ref _KeyedCollectionType); + private Option _KeyedCollectionType; + + public INamedTypeSymbol ObjectType => _ObjectType ??= Compilation.GetSpecialType(SpecialType.System_Object); + private INamedTypeSymbol? _ObjectType; + + public INamedTypeSymbol StringType => _StringType ??= Compilation.GetSpecialType(SpecialType.System_String); + private INamedTypeSymbol? _StringType; + + public INamedTypeSymbol? DateTimeOffsetType => GetOrResolveType(typeof(DateTimeOffset), ref _DateTimeOffsetType); + private Option _DateTimeOffsetType; + + public INamedTypeSymbol? TimeSpanType => GetOrResolveType(typeof(TimeSpan), ref _TimeSpanType); + private Option _TimeSpanType; + + public INamedTypeSymbol? DateOnlyType => GetOrResolveType("System.DateOnly", ref _DateOnlyType); + private Option _DateOnlyType; + + public INamedTypeSymbol? TimeOnlyType => GetOrResolveType("System.TimeOnly", ref _TimeOnlyType); + private Option _TimeOnlyType; + + public INamedTypeSymbol? Int128Type => GetOrResolveType("System.Int128", ref _Int128Type); + private Option _Int128Type; + + public INamedTypeSymbol? UInt128Type => GetOrResolveType("System.UInt128", ref _UInt128Type); + private Option _UInt128Type; + + public INamedTypeSymbol? HalfType => GetOrResolveType("System.Half", ref _HalfType); + private Option _HalfType; + + public IArrayTypeSymbol? ByteArrayType => _ByteArrayType.HasValue + ? _ByteArrayType.Value + : (_ByteArrayType = new(Compilation.CreateArrayTypeSymbol(Compilation.GetSpecialType(SpecialType.System_Byte), rank: 1))).Value; + + private Option _ByteArrayType; + + public INamedTypeSymbol? MemoryByteType => _MemoryByteType.HasValue + ? _MemoryByteType.Value + : (_MemoryByteType = new(MemoryType?.Construct(Compilation.GetSpecialType(SpecialType.System_Byte)))).Value; + + private Option _MemoryByteType; + + public INamedTypeSymbol? ReadOnlyMemoryByteType => _ReadOnlyMemoryByteType.HasValue + ? _ReadOnlyMemoryByteType.Value + : (_ReadOnlyMemoryByteType = new(ReadOnlyMemoryType?.Construct(Compilation.GetSpecialType(SpecialType.System_Byte)))).Value; + + private Option _ReadOnlyMemoryByteType; + + public INamedTypeSymbol? GuidType => GetOrResolveType(typeof(Guid), ref _GuidType); + private Option _GuidType; + + public INamedTypeSymbol? UriType => GetOrResolveType(typeof(Uri), ref _UriType); + private Option _UriType; + + public INamedTypeSymbol? VersionType => GetOrResolveType(typeof(Version), ref _VersionType); + private Option _VersionType; + + // System.Text.Json types + public INamedTypeSymbol? JsonConverterType => GetOrResolveType("System.Text.Json.Serialization.JsonConverter", ref _JsonConverterType); + private Option _JsonConverterType; + + public INamedTypeSymbol? JsonSerializerContextType => GetOrResolveType("System.Text.Json.Serialization.JsonSerializerContext", ref _JsonSerializerContextType); + private Option _JsonSerializerContextType; + + public INamedTypeSymbol? JsonSerializableAttributeType => GetOrResolveType("System.Text.Json.Serialization.JsonSerializableAttribute", ref _JsonSerializableAttributeType); + private Option _JsonSerializableAttributeType; + + public INamedTypeSymbol? JsonDocumentType => GetOrResolveType("System.Text.Json.JsonDocument", ref _JsonDocumentType); + private Option _JsonDocumentType; + + public INamedTypeSymbol? JsonElementType => GetOrResolveType("System.Text.Json.JsonElement", ref _JsonElementType); + private Option _JsonElementType; + + public INamedTypeSymbol? JsonNodeType => GetOrResolveType("System.Text.Json.Nodes.JsonNode", ref _JsonNodeType); + private Option _JsonNodeType; + + public INamedTypeSymbol? JsonValueType => GetOrResolveType("System.Text.Json.Nodes.JsonValue", ref _JsonValueType); + private Option _JsonValueType; + + public INamedTypeSymbol? JsonObjectType => GetOrResolveType("System.Text.Json.Nodes.JsonObject", ref _JsonObjectType); + private Option _JsonObjectType; + + public INamedTypeSymbol? JsonArrayType => GetOrResolveType("System.Text.Json.Nodes.JsonArray", ref _JsonArrayType); + private Option _JsonArrayType; + + // System.Text.Json attributes + public INamedTypeSymbol? JsonConverterAttributeType => GetOrResolveType("System.Text.Json.Serialization.JsonConverterAttribute", ref _JsonConverterAttributeType); + private Option _JsonConverterAttributeType; + + public INamedTypeSymbol? JsonDerivedTypeAttributeType => GetOrResolveType("System.Text.Json.Serialization.JsonDerivedTypeAttribute", ref _JsonDerivedTypeAttributeType); + private Option _JsonDerivedTypeAttributeType; + + public INamedTypeSymbol? JsonNumberHandlingAttributeType => GetOrResolveType("System.Text.Json.Serialization.JsonNumberHandlingAttribute", ref _JsonNumberHandlingAttributeType); + private Option _JsonNumberHandlingAttributeType; + + public INamedTypeSymbol? JsonObjectCreationHandlingAttributeType => GetOrResolveType("System.Text.Json.Serialization.JsonObjectCreationHandlingAttribute", ref _JsonObjectCreationHandlingAttributeType); + private Option _JsonObjectCreationHandlingAttributeType; + + public INamedTypeSymbol? JsonSourceGenerationOptionsAttributeType => GetOrResolveType("System.Text.Json.Serialization.JsonSourceGenerationOptionsAttribute", ref _JsonSourceGenerationOptionsAttributeType); + private Option _JsonSourceGenerationOptionsAttributeType; + + public INamedTypeSymbol? JsonUnmappedMemberHandlingAttributeType => GetOrResolveType("System.Text.Json.Serialization.JsonUnmappedMemberHandlingAttribute", ref _JsonUnmappedMemberHandlingAttributeType); + private Option _JsonUnmappedMemberHandlingAttributeType; + + public INamedTypeSymbol? JsonConstructorAttributeType => GetOrResolveType("System.Text.Json.Serialization.JsonConstructorAttribute", ref _JsonConstructorAttributeType); + private Option _JsonConstructorAttributeType; + + public INamedTypeSymbol? SetsRequiredMembersAttributeType => GetOrResolveType("System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute", ref _SetsRequiredMembersAttributeType); + private Option _SetsRequiredMembersAttributeType; + + public INamedTypeSymbol? JsonStringEnumConverterType => GetOrResolveType("System.Text.Json.Serialization.JsonStringEnumConverter", ref _JsonStringEnumConverterType); + private Option _JsonStringEnumConverterType; + + public INamedTypeSymbol? JsonStringEnumConverterOfTType => GetOrResolveType("System.Text.Json.Serialization.JsonStringEnumConverter`1", ref _JsonStringEnumConverterOfTType); + private Option _JsonStringEnumConverterOfTType; + + public INamedTypeSymbol? IJsonOnSerializingType => GetOrResolveType(JsonConstants.IJsonOnSerializingFullName, ref _IJsonOnSerializingType); + private Option _IJsonOnSerializingType; + + public INamedTypeSymbol? IJsonOnSerializedType => GetOrResolveType(JsonConstants.IJsonOnSerializedFullName, ref _IJsonOnSerializedType); + private Option _IJsonOnSerializedType; + + // Unsupported types + public INamedTypeSymbol? DelegateType => _DelegateType ??= Compilation.GetSpecialType(SpecialType.System_Delegate); + private INamedTypeSymbol? _DelegateType; + + public INamedTypeSymbol? MemberInfoType => GetOrResolveType(typeof(MemberInfo), ref _MemberInfoType); + private Option _MemberInfoType; + + // public INamedTypeSymbol? SerializationInfoType => GetOrResolveType(typeof(System.Runtime.Serialization.SerializationInfo), ref _SerializationInfoType); + // private Option _SerializationInfoType; + + public INamedTypeSymbol? IntPtrType => GetOrResolveType(typeof(IntPtr), ref _IntPtrType); + private Option _IntPtrType; + + public INamedTypeSymbol? UIntPtrType => GetOrResolveType(typeof(UIntPtr), ref _UIntPtrType); + private Option _UIntPtrType; + + public INamedTypeSymbol? MemoryType => GetOrResolveType(typeof(Memory<>), ref _MemoryType); + private Option _MemoryType; + + public INamedTypeSymbol? ReadOnlyMemoryType => GetOrResolveType(typeof(ReadOnlyMemory<>), ref _ReadOnlyMemoryType); + private Option _ReadOnlyMemoryType; + + public bool IsImmutableEnumerableType(ITypeSymbol type, out string? factoryTypeFullName) + { + if (type is not INamedTypeSymbol { IsGenericType: true, ConstructedFrom: INamedTypeSymbol genericTypeDef }) + { + factoryTypeFullName = null; + return false; + } + + SymbolEqualityComparer cmp = SymbolEqualityComparer.Default; + if (cmp.Equals(genericTypeDef, ImmutableArrayType)) + { + factoryTypeFullName = typeof(ImmutableArray).FullName; + return true; + } + + if (cmp.Equals(genericTypeDef, ImmutableListType) || + cmp.Equals(genericTypeDef, IImmutableListType)) + { + factoryTypeFullName = typeof(ImmutableList).FullName; + return true; + } + + if (cmp.Equals(genericTypeDef, ImmutableStackType) || + cmp.Equals(genericTypeDef, IImmutableStackType)) + { + factoryTypeFullName = typeof(ImmutableStack).FullName; + return true; + } + + if (cmp.Equals(genericTypeDef, ImmutableQueueType) || + cmp.Equals(genericTypeDef, IImmutableQueueType)) + { + factoryTypeFullName = typeof(ImmutableQueue).FullName; + return true; + } + + if (cmp.Equals(genericTypeDef, ImmutableHashSetType) || + cmp.Equals(genericTypeDef, IImmutableSetType)) + { + factoryTypeFullName = typeof(ImmutableHashSet).FullName; + return true; + } + + if (cmp.Equals(genericTypeDef, ImmutableSortedType)) + { + factoryTypeFullName = typeof(ImmutableSortedSet).FullName; + return true; + } + + factoryTypeFullName = null; + return false; + } + + public bool IsImmutableDictionaryType(ITypeSymbol type, out string? factoryTypeFullName) + { + if (type is not INamedTypeSymbol { IsGenericType: true, ConstructedFrom: INamedTypeSymbol genericTypeDef }) + { + factoryTypeFullName = null; + return false; + } + + SymbolEqualityComparer cmp = SymbolEqualityComparer.Default; + + if (cmp.Equals(genericTypeDef, ImmutableDictionaryType) || + cmp.Equals(genericTypeDef, IImmutableDictionaryType)) + { + factoryTypeFullName = typeof(ImmutableDictionary).FullName; + return true; + } + + if (cmp.Equals(genericTypeDef, ImmutableSortedDictionaryType)) + { + factoryTypeFullName = typeof(ImmutableSortedDictionary).FullName; + return true; + } + + factoryTypeFullName = null; + return false; + } + + private INamedTypeSymbol? GetOrResolveType(Type type, ref Option field) + => GetOrResolveType(type.FullName!, ref field); + + private INamedTypeSymbol? GetOrResolveType(string fullyQualifiedName, ref Option field) + { + if (field.HasValue) + { + return field.Value; + } + + INamedTypeSymbol? type = Compilation.GetBestTypeByMetadataName(fullyQualifiedName); + field = new(type); + return type; + } + + private readonly struct Option + { + public readonly bool HasValue; + public readonly T Value; + + public Option(T value) + { + HasValue = true; + Value = value; + } + } + } +} diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Helpers/RoslynExtension2.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Helpers/RoslynExtension2.cs new file mode 100644 index 0000000..b27d485 --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Helpers/RoslynExtension2.cs @@ -0,0 +1,176 @@ +// using System.Collections.Immutable; +// using Microsoft.CodeAnalysis; +// using Microsoft.CodeAnalysis.CSharp.Syntax; +// +// namespace CSharpToJsonSchema.Generators.JsonGen.Helpers; +// +// internal static partial class RoslynExtensions +// { +// // Copied from: https://github.com/dotnet/roslyn/blob/main/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/CompilationExtensions.cs +// /// +// /// Gets a type by its metadata name to use for code analysis within a . This method +// /// attempts to find the "best" symbol to use for code analysis, which is the symbol matching the first of the +// /// following rules. +// /// +// /// +// /// +// /// If only one type with the given name is found within the compilation and its referenced assemblies, that +// /// type is returned regardless of accessibility. +// /// +// /// +// /// If the current defines the symbol, that symbol is returned. +// /// +// /// +// /// If exactly one referenced assembly defines the symbol in a manner that makes it visible to the current +// /// , that symbol is returned. +// /// +// /// +// /// Otherwise, this method returns . +// /// +// /// +// /// +// /// The to consider for analysis. +// /// The fully-qualified metadata type name to find. +// /// The symbol to use for code analysis; otherwise, . +// public static INamedTypeSymbol? GetBestTypeByMetadataName(this Compilation compilation, string fullyQualifiedMetadataName) +// { +// // Try to get the unique type with this name, ignoring accessibility +// var type = compilation.GetTypeByMetadataName(fullyQualifiedMetadataName); +// +// // Otherwise, try to get the unique type with this name originally defined in 'compilation' +// type ??= compilation.Assembly.GetTypeByMetadataName(fullyQualifiedMetadataName); +// +// // Otherwise, try to get the unique accessible type with this name from a reference +// if (type is null) +// { +// foreach (var module in compilation.Assembly.Modules) +// { +// foreach (var referencedAssembly in module.ReferencedAssemblySymbols) +// { +// var currentType = referencedAssembly.GetTypeByMetadataName(fullyQualifiedMetadataName); +// if (currentType is null) +// continue; +// +// switch (currentType.GetResultantVisibility()) +// { +// case SymbolVisibility.Public: +// case SymbolVisibility.Internal when referencedAssembly.GivesAccessTo(compilation.Assembly): +// break; +// +// default: +// continue; +// } +// +// if (type is object) +// { +// // Multiple visible types with the same metadata name are present +// return null; +// } +// +// type = currentType; +// } +// } +// } +// +// return type; +// } +// +// /// +// /// A thin wrapper over , +// /// but taking the type itself rather than the fully-qualified metadata type name. +// /// +// /// The to consider for analysis. +// /// The type to find. +// /// +// public static INamedTypeSymbol? GetBestTypeByMetadataName(this Compilation compilation, Type type) => +// type.IsArray || type.FullName is null +// ? throw new ArgumentException("The input type must correspond to a named type symbol.") +// : GetBestTypeByMetadataName(compilation, type.FullName); +// +// // copied from https://github.com/dotnet/roslyn/blob/main/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ISymbolExtensions.cs +// private static SymbolVisibility GetResultantVisibility(this ISymbol symbol) +// { +// // Start by assuming it's visible. +// SymbolVisibility visibility = SymbolVisibility.Public; +// +// switch (symbol.Kind) +// { +// case SymbolKind.Alias: +// // Aliases are uber private. They're only visible in the same file that they +// // were declared in. +// return SymbolVisibility.Private; +// +// case SymbolKind.Parameter: +// // Parameters are only as visible as their containing symbol +// return GetResultantVisibility(symbol.ContainingSymbol); +// +// case SymbolKind.TypeParameter: +// // Type Parameters are private. +// return SymbolVisibility.Private; +// } +// +// while (symbol != null && symbol.Kind != SymbolKind.Namespace) +// { +// switch (symbol.DeclaredAccessibility) +// { +// // If we see anything private, then the symbol is private. +// case Accessibility.NotApplicable: +// case Accessibility.Private: +// return SymbolVisibility.Private; +// +// // If we see anything internal, then knock it down from public to +// // internal. +// case Accessibility.Internal: +// case Accessibility.ProtectedAndInternal: +// visibility = SymbolVisibility.Internal; +// break; +// +// // For anything else (Public, Protected, ProtectedOrInternal), the +// // symbol stays at the level we've gotten so far. +// } +// +// symbol = symbol.ContainingSymbol; +// } +// +// return visibility; +// } +// +// // Copied from: https://github.com/dotnet/roslyn/blob/main/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SymbolVisibility.cs +// #pragma warning disable CA1027 // Mark enums with FlagsAttribute +// private enum SymbolVisibility +// #pragma warning restore CA1027 // Mark enums with FlagsAttribute +// { +// Public = 0, +// Internal = 1, +// Private = 2, +// Friend = Internal, +// } +// +// internal static bool HasAttributeSuffix(this string name, bool isCaseSensitive) +// { +// const string AttributeSuffix = "Attribute"; +// +// var comparison = isCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; +// return name.Length > AttributeSuffix.Length && name.EndsWith(AttributeSuffix, comparison); +// } +// +// public static ImmutableArray ToImmutableArray(this ReadOnlySpan span) +// { +// switch (span.Length) +// { +// case 0: return ImmutableArray.Empty; +// case 1: return ImmutableArray.Create(span[0]); +// case 2: return ImmutableArray.Create(span[0], span[1]); +// case 3: return ImmutableArray.Create(span[0], span[1], span[2]); +// case 4: return ImmutableArray.Create(span[0], span[1], span[2], span[3]); +// default: +// var builder = ImmutableArray.CreateBuilder(span.Length); +// foreach (var item in span) +// builder.Add(item); +// +// return builder.MoveToImmutable(); +// } +// } +// +// +// } \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Helpers/RoslynExtensions.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Helpers/RoslynExtensions.cs new file mode 100644 index 0000000..3073398 --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Helpers/RoslynExtensions.cs @@ -0,0 +1,389 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Immutable; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace CSharpToJsonSchema.Generators.JsonGen.Helpers +{ + internal static partial class RoslynExtensions2 + { + public static SimpleNameSyntax GetUnqualifiedName(this NameSyntax name) + => name switch + { + AliasQualifiedNameSyntax alias => alias.Name, + QualifiedNameSyntax qualified => qualified.Right, + SimpleNameSyntax simple => simple, + _ => throw new InvalidOperationException("Unreachable"), + }; + public static LanguageVersion? GetLanguageVersion(this Compilation compilation) + => compilation is CSharpCompilation csc ? csc.LanguageVersion : null; + + public static INamedTypeSymbol? GetBestTypeByMetadataName(this Compilation compilation, Type type) + { + Debug.Assert(!type.IsArray, "Resolution logic only capable of handling named types."); + Debug.Assert(type.FullName != null); + return compilation.GetBestTypeByMetadataName(type.FullName); + } + + public static Location? GetLocation(this ISymbol typeSymbol) + => typeSymbol.Locations.Length > 0 ? typeSymbol.Locations[0] : null; + + public static Location? GetLocation(this AttributeData attributeData) + { + SyntaxReference? reference = attributeData.ApplicationSyntaxReference; + return reference?.SyntaxTree.GetLocation(reference.Span); + } + + /// + /// Returns true if the specified location is contained in one of the syntax trees in the compilation. + /// + public static bool ContainsLocation(this Compilation compilation, Location location) + => location.SourceTree != null && compilation.ContainsSyntaxTree(location.SourceTree); + + /// + /// Removes any type metadata that is erased at compile time, such as NRT annotations and tuple labels. + /// + public static ITypeSymbol EraseCompileTimeMetadata(this Compilation compilation, ITypeSymbol type) + { + if (type.NullableAnnotation is NullableAnnotation.Annotated) + { + type = type.WithNullableAnnotation(NullableAnnotation.None); + } + + if (type is IArrayTypeSymbol arrayType) + { + ITypeSymbol elementType = compilation.EraseCompileTimeMetadata(arrayType.ElementType); + return compilation.CreateArrayTypeSymbol(elementType, arrayType.Rank); + } + + if (type is INamedTypeSymbol namedType) + { + if (namedType.IsTupleType) + { + if (namedType.TupleElements.Length < 2) + { + return type; + } + + ImmutableArray erasedElements = namedType.TupleElements + .Select(e => compilation.EraseCompileTimeMetadata(e.Type)) + .ToImmutableArray(); + + type = compilation.CreateTupleTypeSymbol(erasedElements); + } + else if (namedType.IsGenericType) + { + if (namedType.IsUnboundGenericType) + { + return namedType; + } + + ImmutableArray typeArguments = namedType.TypeArguments; + INamedTypeSymbol? containingType = namedType.ContainingType; + + if (containingType?.IsGenericType == true) + { + containingType = (INamedTypeSymbol)compilation.EraseCompileTimeMetadata(containingType); + type = namedType = containingType.GetTypeMembers().First(t => t.Name == namedType.Name && t.Arity == namedType.Arity); + } + + if (typeArguments.Length > 0) + { + ITypeSymbol[] erasedTypeArgs = typeArguments + .Select(compilation.EraseCompileTimeMetadata) + .ToArray(); + + type = namedType.ConstructedFrom.Construct(erasedTypeArgs); + } + } + } + + return type; + } + + public static bool CanUseDefaultConstructorForDeserialization(this ITypeSymbol type, out IMethodSymbol? constructorInfo) + { + if (type.IsAbstract || type.TypeKind is TypeKind.Interface || type is not INamedTypeSymbol namedType) + { + constructorInfo = null; + return false; + } + + constructorInfo = namedType.GetExplicitlyDeclaredInstanceConstructors().FirstOrDefault(ctor => ctor.DeclaredAccessibility is Accessibility.Public && ctor.Parameters.Length == 0); + return constructorInfo != null || type.IsValueType; + } + + public static IEnumerable GetExplicitlyDeclaredInstanceConstructors(this INamedTypeSymbol type) + => type.Constructors.Where(ctor => !ctor.IsStatic && !(ctor.IsImplicitlyDeclared && type.IsValueType && ctor.Parameters.Length == 0)); + + public static bool ContainsAttribute(this ISymbol memberInfo, INamedTypeSymbol? attributeType) + => attributeType != null && memberInfo.GetAttributes().Any(attr => SymbolEqualityComparer.Default.Equals(attr.AttributeClass, attributeType)); + + public static bool IsVirtual(this ISymbol symbol) + => symbol.IsVirtual || symbol.IsOverride || symbol.IsAbstract; + + public static bool IsAssignableFrom(this ITypeSymbol? baseType, ITypeSymbol? type) + { + if (baseType is null || type is null) + { + return false; + } + + if (baseType.TypeKind is TypeKind.Interface) + { + if (type.AllInterfaces.Contains(baseType, SymbolEqualityComparer.Default)) + { + return true; + } + } + + for (INamedTypeSymbol? current = type as INamedTypeSymbol; current != null; current = current.BaseType) + { + if (SymbolEqualityComparer.Default.Equals(baseType, current)) + { + return true; + } + } + + return false; + } + + public static INamedTypeSymbol? GetCompatibleGenericBaseType(this ITypeSymbol type, INamedTypeSymbol? baseType) + { + if (baseType is null) + { + return null; + } + + Debug.Assert(baseType.IsGenericTypeDefinition()); + + if (baseType.TypeKind is TypeKind.Interface) + { + foreach (INamedTypeSymbol interfaceType in type.AllInterfaces) + { + if (IsMatchingGenericType(interfaceType, baseType)) + { + return interfaceType; + } + } + } + + for (INamedTypeSymbol? current = type as INamedTypeSymbol; current != null; current = current.BaseType) + { + if (IsMatchingGenericType(current, baseType)) + { + return current; + } + } + + return null; + + static bool IsMatchingGenericType(INamedTypeSymbol candidate, INamedTypeSymbol baseType) + { + return candidate.IsGenericType && SymbolEqualityComparer.Default.Equals(candidate.ConstructedFrom, baseType); + } + } + + public static bool IsGenericTypeDefinition(this ITypeSymbol type) + => type is INamedTypeSymbol { IsGenericType: true } namedType && SymbolEqualityComparer.Default.Equals(namedType, namedType.ConstructedFrom); + + public static bool IsNumberType(this ITypeSymbol type) + { + return type.SpecialType is + SpecialType.System_SByte or SpecialType.System_Int16 or SpecialType.System_Int32 or SpecialType.System_Int64 or + SpecialType.System_Byte or SpecialType.System_UInt16 or SpecialType.System_UInt32 or SpecialType.System_UInt64 or + SpecialType.System_Single or SpecialType.System_Double or SpecialType.System_Decimal; + } + + public static bool IsNullableType(this ITypeSymbol type) + => !type.IsValueType || type.OriginalDefinition.SpecialType is SpecialType.System_Nullable_T; + + public static bool IsNullableValueType(this ITypeSymbol type, [NotNullWhen(true)] out ITypeSymbol? elementType) + { + if (type.IsValueType && type is INamedTypeSymbol { OriginalDefinition.SpecialType: SpecialType.System_Nullable_T }) + { + elementType = ((INamedTypeSymbol)type).TypeArguments[0]; + return true; + } + + elementType = null; + return false; + } + + public static ITypeSymbol GetMemberType(this ISymbol member) + { + Debug.Assert(member is IFieldSymbol or IPropertySymbol); + return member is IFieldSymbol fs ? fs.Type : ((IPropertySymbol)member).Type; + } + + public static bool IsOverriddenOrShadowedBy(this ISymbol member, ISymbol otherMember) + { + Debug.Assert(member is IFieldSymbol or IPropertySymbol); + Debug.Assert(otherMember is IFieldSymbol or IPropertySymbol); + return member.Name == otherMember.Name && member.ContainingType.IsAssignableFrom(otherMember.ContainingType); + } + + public static bool MemberNameNeedsAtSign(this ISymbol symbol) + => SyntaxFacts.GetKeywordKind(symbol.Name) != SyntaxKind.None || SyntaxFacts.GetContextualKeywordKind(symbol.Name) != SyntaxKind.None; + + public static INamedTypeSymbol[] GetSortedTypeHierarchy(this ITypeSymbol type) + { + if (type is not INamedTypeSymbol namedType) + { + return Array.Empty(); + } + + if (type.TypeKind != TypeKind.Interface) + { + var list = new List(); + for (INamedTypeSymbol? current = namedType; current != null; current = current.BaseType) + { + list.Add(current); + } + + return list.ToArray(); + } + else + { + // Interface hierarchies support multiple inheritance. + // For consistency with class hierarchy resolution order, + // sort topologically from most derived to least derived. + return JsonHelpers.TraverseGraphWithTopologicalSort(namedType, static t => t.AllInterfaces, SymbolEqualityComparer.Default); + } + } + + /// + /// Returns the kind keyword corresponding to the specified declaration syntax node. + /// + public static string GetTypeKindKeyword(this TypeDeclarationSyntax typeDeclaration) + { + switch (typeDeclaration.Kind()) + { + case SyntaxKind.ClassDeclaration: + return "class"; + case SyntaxKind.InterfaceDeclaration: + return "interface"; + case SyntaxKind.StructDeclaration: + return "struct"; + case SyntaxKind.RecordDeclaration: + return "record"; + case SyntaxKind.RecordStructDeclaration: + return "record struct"; + case SyntaxKind.EnumDeclaration: + return "enum"; + case SyntaxKind.DelegateDeclaration: + return "delegate"; + default: + Debug.Fail("unexpected syntax kind"); + return null; + } + } + + public static void ResolveNullabilityAnnotations(this IFieldSymbol field, out bool isGetterNonNullable, out bool isSetterNonNullable) + { + if (field.Type.IsNullableType()) + { + // Because System.Text.Json cannot distinguish between nullable and non-nullable type parameters, + // (e.g. the same metadata is being used for both KeyValuePair and KeyValuePair), + // we derive nullability annotations from the original definition of the field and not its instantiation. + // This preserves compatibility with the capabilities of the reflection-based NullabilityInfo reader. + field = field.OriginalDefinition; + + isGetterNonNullable = IsOutputTypeNonNullable(field, field.Type); + isSetterNonNullable = IsInputTypeNonNullable(field, field.Type); + } + else + { + isGetterNonNullable = isSetterNonNullable = false; + } + } + + public static void ResolveNullabilityAnnotations(this IPropertySymbol property, out bool isGetterNonNullable, out bool isSetterNonNullable) + { + if (property.Type.IsNullableType()) + { + // Because System.Text.Json cannot distinguish between nullable and non-nullable type parameters, + // (e.g. the same metadata is being used for both KeyValuePair and KeyValuePair), + // we derive nullability annotations from the original definition of the field and not its instantiation. + // This preserves compatibility with the capabilities of the reflection-based NullabilityInfo reader. + property = property.OriginalDefinition; + + isGetterNonNullable = property.GetMethod != null && IsOutputTypeNonNullable(property, property.Type); + isSetterNonNullable = property.SetMethod != null && IsInputTypeNonNullable(property, property.Type); + } + else + { + isGetterNonNullable = isSetterNonNullable = false; + } + } + + public static bool IsNullable(this IParameterSymbol parameter) + { + if (parameter.Type.IsNullableType()) + { + // Because System.Text.Json cannot distinguish between nullable and non-nullable type parameters, + // (e.g. the same metadata is being used for both KeyValuePair and KeyValuePair), + // we derive nullability annotations from the original definition of the field and not its instantiation. + // This preserves compatibility with the capabilities of the reflection-based NullabilityInfo reader. + parameter = parameter.OriginalDefinition; + return !IsInputTypeNonNullable(parameter, parameter.Type); + } + + return false; + } + + private static bool IsOutputTypeNonNullable(this ISymbol symbol, ITypeSymbol returnType) + { + if (symbol.HasCodeAnalysisAttribute("MaybeNullAttribute")) + { + return false; + } + + if (symbol.HasCodeAnalysisAttribute("NotNullAttribute")) + { + return true; + } + + if (returnType is ITypeParameterSymbol { HasNotNullConstraint: false }) + { + return false; + } + + return returnType.NullableAnnotation is NullableAnnotation.NotAnnotated; + } + + private static bool IsInputTypeNonNullable(this ISymbol symbol, ITypeSymbol inputType) + { + Debug.Assert(inputType.IsNullableType()); + + if (symbol.HasCodeAnalysisAttribute("AllowNullAttribute")) + { + return false; + } + + if (symbol.HasCodeAnalysisAttribute("DisallowNullAttribute")) + { + return true; + } + + if (inputType is ITypeParameterSymbol { HasNotNullConstraint: false }) + { + return false; + } + + return inputType.NullableAnnotation is NullableAnnotation.NotAnnotated; + } + + private static bool HasCodeAnalysisAttribute(this ISymbol symbol, string attributeName) + { + return symbol.GetAttributes().Any(attr => + attr.AttributeClass?.Name == attributeName && + attr.AttributeClass.ContainingNamespace.ToDisplayString() == "System.Diagnostics.CodeAnalysis"); + } + } +} diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Helpers/RoslynExtensions_2.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Helpers/RoslynExtensions_2.cs new file mode 100644 index 0000000..1972f37 --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Helpers/RoslynExtensions_2.cs @@ -0,0 +1,173 @@ +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace CSharpToJsonSchema.Generators.JsonGen.Helpers; + + internal static partial class RoslynExtensions2 + { + // Copied from: https://github.com/dotnet/roslyn/blob/main/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/CompilationExtensions.cs + /// + /// Gets a type by its metadata name to use for code analysis within a . This method + /// attempts to find the "best" symbol to use for code analysis, which is the symbol matching the first of the + /// following rules. + /// + /// + /// + /// If only one type with the given name is found within the compilation and its referenced assemblies, that + /// type is returned regardless of accessibility. + /// + /// + /// If the current defines the symbol, that symbol is returned. + /// + /// + /// If exactly one referenced assembly defines the symbol in a manner that makes it visible to the current + /// , that symbol is returned. + /// + /// + /// Otherwise, this method returns . + /// + /// + /// + /// The to consider for analysis. + /// The fully-qualified metadata type name to find. + /// The symbol to use for code analysis; otherwise, . + public static INamedTypeSymbol? GetBestTypeByMetadataName(this Compilation compilation, string fullyQualifiedMetadataName) + { + // Try to get the unique type with this name, ignoring accessibility + var type = compilation.GetTypeByMetadataName(fullyQualifiedMetadataName); + + // Otherwise, try to get the unique type with this name originally defined in 'compilation' + type ??= compilation.Assembly.GetTypeByMetadataName(fullyQualifiedMetadataName); + + // Otherwise, try to get the unique accessible type with this name from a reference + if (type is null) + { + foreach (var module in compilation.Assembly.Modules) + { + foreach (var referencedAssembly in module.ReferencedAssemblySymbols) + { + var currentType = referencedAssembly.GetTypeByMetadataName(fullyQualifiedMetadataName); + if (currentType is null) + continue; + + switch (currentType.GetResultantVisibility()) + { + case SymbolVisibility.Public: + case SymbolVisibility.Internal when referencedAssembly.GivesAccessTo(compilation.Assembly): + break; + + default: + continue; + } + + if (type is object) + { + // Multiple visible types with the same metadata name are present + return null; + } + + type = currentType; + } + } + } + + return type; + } + + + + // copied from https://github.com/dotnet/roslyn/blob/main/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ISymbolExtensions.cs + private static SymbolVisibility GetResultantVisibility(this ISymbol symbol) + { + // Start by assuming it's visible. + SymbolVisibility visibility = SymbolVisibility.Public; + + switch (symbol.Kind) + { + case SymbolKind.Alias: + // Aliases are uber private. They're only visible in the same file that they + // were declared in. + return SymbolVisibility.Private; + + case SymbolKind.Parameter: + // Parameters are only as visible as their containing symbol + return GetResultantVisibility(symbol.ContainingSymbol); + + case SymbolKind.TypeParameter: + // Type Parameters are private. + return SymbolVisibility.Private; + } + + while (symbol != null && symbol.Kind != SymbolKind.Namespace) + { + switch (symbol.DeclaredAccessibility) + { + // If we see anything private, then the symbol is private. + case Accessibility.NotApplicable: + case Accessibility.Private: + return SymbolVisibility.Private; + + // If we see anything internal, then knock it down from public to + // internal. + case Accessibility.Internal: + case Accessibility.ProtectedAndInternal: + visibility = SymbolVisibility.Internal; + break; + + // For anything else (Public, Protected, ProtectedOrInternal), the + // symbol stays at the level we've gotten so far. + } + + symbol = symbol.ContainingSymbol; + } + + return visibility; + } + + // Copied from: https://github.com/dotnet/roslyn/blob/main/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SymbolVisibility.cs +#pragma warning disable CA1027 // Mark enums with FlagsAttribute + private enum SymbolVisibility +#pragma warning restore CA1027 // Mark enums with FlagsAttribute + { + Public = 0, + Internal = 1, + Private = 2, + Friend = Internal, + } + + internal static bool HasAttributeSuffix(this string name, bool isCaseSensitive) + { + const string AttributeSuffix = "Attribute"; + + var comparison = isCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; + return name.Length > AttributeSuffix.Length && name.EndsWith(AttributeSuffix, comparison); + } + + public static ImmutableArray ToImmutableArray(this ReadOnlySpan span) + { + switch (span.Length) + { + case 0: return ImmutableArray.Empty; + case 1: return ImmutableArray.Create(span[0]); + case 2: return ImmutableArray.Create(span[0], span[1]); + case 3: return ImmutableArray.Create(span[0], span[1], span[2]); + case 4: return ImmutableArray.Create(span[0], span[1], span[2], span[3]); + default: + var builder = ImmutableArray.CreateBuilder(span.Length); + foreach (var item in span) + builder.Add(item); + + return builder.MoveToImmutable(); + } + } + + // public static SimpleNameSyntax GetUnqualifiedName(this NameSyntax name) + // => name switch + // { + // AliasQualifiedNameSyntax alias => alias.Name, + // QualifiedNameSyntax qualified => qualified.Right, + // SimpleNameSyntax simple => simple, + // _ => throw new InvalidOperationException("Unreachable"), + // }; + } \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Helpers/SourceGeneratorHelpers.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Helpers/SourceGeneratorHelpers.cs new file mode 100644 index 0000000..e093e42 --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Helpers/SourceGeneratorHelpers.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace CSharpToJsonSchema.Generators.JsonGen.Helpers +{ + internal static class SourceGeneratorHelpers + { + private static readonly char[] s_enumSeparator = new char[] { ',' }; + + public static string FormatEnumLiteral(string fullyQualifiedName, TEnum value) where TEnum : struct, Enum + { + IEnumerable values = value.ToString().Split(s_enumSeparator, StringSplitOptions.RemoveEmptyEntries) + .Select(name => name.Trim()) + .Select(name => + int.TryParse(name, out _) + ? $"({fullyQualifiedName})({name})" + : $"{fullyQualifiedName}.{name}"); + + return string.Join(" | ", values); + } + } +} diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Helpers/TypeModelHelper.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Helpers/TypeModelHelper.cs new file mode 100644 index 0000000..972612c --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Helpers/TypeModelHelper.cs @@ -0,0 +1,33 @@ +using Microsoft.CodeAnalysis; + +namespace CSharpToJsonSchema.Generators.JsonGen.Helpers; + +internal static class TypeModelHelper +{ + public static List? GetAllTypeArgumentsInScope(this INamedTypeSymbol type) + { + if (!type.IsGenericType) + { + return null; + } + + List? args = null; + TraverseContainingTypes(type); + return args; + + void TraverseContainingTypes(INamedTypeSymbol current) + { + if (current.ContainingType is INamedTypeSymbol parent) + { + TraverseContainingTypes(parent); + } + + if (!current.TypeArguments.IsEmpty) + { + (args ??= new()).AddRange(current.TypeArguments); + } + } + } + + public static string GetFullyQualifiedName(this ITypeSymbol type) => type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); +} \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/JsonConstants.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/JsonConstants.cs new file mode 100644 index 0000000..ba7bdbb --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/JsonConstants.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace CSharpToJsonSchema.Generators.JsonGen +{ + internal static partial class JsonConstants + { + public const string SystemTextJsonSourceGenerationName = "System.Text.Json.SourceGeneration"; + + public const string IJsonOnSerializedFullName = "System.Text.Json.Serialization.IJsonOnSerialized"; + public const string IJsonOnSerializingFullName = "System.Text.Json.Serialization.IJsonOnSerializing"; + } +} diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/JsonSourceGenerator.DiagnosticDescriptors.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/JsonSourceGenerator.DiagnosticDescriptors.cs new file mode 100644 index 0000000..69820b3 --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/JsonSourceGenerator.DiagnosticDescriptors.cs @@ -0,0 +1,136 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using CSharpToJsonSchema.Generators.JsonGen.Helpers; +using Microsoft.CodeAnalysis; + +namespace CSharpToJsonSchema.Generators.JsonGen +{ + public sealed partial class JsonSourceGenerator + { + internal static class DiagnosticDescriptors + { + // Must be kept in sync with https://github.com/dotnet/runtime/blob/main/docs/project/list-of-diagnostics.md + + public static DiagnosticDescriptor TypeNotSupported { get; } = DiagnosticDescriptorHelper.Create( + id: "SYSLIB1030", + title: "Type is not supported", + messageFormat: "The type {0} is not supported for JSON serialization.", + category: JsonConstants.SystemTextJsonSourceGenerationName, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + public static DiagnosticDescriptor DuplicateTypeName { get; } = DiagnosticDescriptorHelper.Create( + id: "SYSLIB1031", + title: "Duplicate type name detected", + messageFormat: "The type name {0} is defined multiple times.", + category: JsonConstants.SystemTextJsonSourceGenerationName, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + public static DiagnosticDescriptor ContextClassesMustBePartial { get; } = DiagnosticDescriptorHelper.Create( + id: "SYSLIB1032", + title: "Context class must be partial", + messageFormat: "The context class {0} must be declared as partial.", + category: JsonConstants.SystemTextJsonSourceGenerationName, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + public static DiagnosticDescriptor MultipleJsonConstructorAttribute { get; } = DiagnosticDescriptorHelper.Create( + id: "SYSLIB1033", + title: "Multiple JSON constructor attributes", + messageFormat: "Multiple constructors in {0} cannot have the [JsonConstructor] attribute.", + category: JsonConstants.SystemTextJsonSourceGenerationName, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public static DiagnosticDescriptor JsonStringEnumConverterNotSupportedInAot { get; } = DiagnosticDescriptorHelper.Create( + id: "SYSLIB1034", + title: "JsonStringEnumConverter is not supported in AOT", + messageFormat: "Using JsonStringEnumConverter is not supported in environments with Ahead-Of-Time (AOT) compilation.", + category: JsonConstants.SystemTextJsonSourceGenerationName, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + public static DiagnosticDescriptor MultipleJsonExtensionDataAttribute { get; } = DiagnosticDescriptorHelper.Create( + id: "SYSLIB1035", + title: "Multiple JsonExtensionData attributes", + messageFormat: "The type {0} cannot have multiple [JsonExtensionData] properties.", + category: JsonConstants.SystemTextJsonSourceGenerationName, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public static DiagnosticDescriptor DataExtensionPropertyInvalid { get; } = DiagnosticDescriptorHelper.Create( + id: "SYSLIB1036", + title: "Invalid JsonExtensionData property", + messageFormat: "The property {0} attributed with [JsonExtensionData] must be of type IDictionary or IDictionary.", + category: JsonConstants.SystemTextJsonSourceGenerationName, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public static DiagnosticDescriptor InaccessibleJsonIncludePropertiesNotSupported { get; } = DiagnosticDescriptorHelper.Create( + id: "SYSLIB1038", + title: "Inaccessible properties with [JsonInclude] are not supported", + messageFormat: "The property {0} marked with [JsonInclude] must be accessible.", + category: JsonConstants.SystemTextJsonSourceGenerationName, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + public static DiagnosticDescriptor PolymorphismNotSupported { get; } = DiagnosticDescriptorHelper.Create( + id: "SYSLIB1039", + title: "Polymorphism is not supported", + messageFormat: "Polymorphism for type {0} is not supported for JSON serialization.", + category: JsonConstants.SystemTextJsonSourceGenerationName, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + public static DiagnosticDescriptor JsonConverterAttributeInvalidType { get; } = DiagnosticDescriptorHelper.Create( + id: "SYSLIB1220", + title: "Invalid type in JsonConverter attribute", + messageFormat: "The type {0} specified in [JsonConverter] is invalid or not supported.", + category: JsonConstants.SystemTextJsonSourceGenerationName, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + public static DiagnosticDescriptor JsonUnsupportedLanguageVersion { get; } = DiagnosticDescriptorHelper.Create( + id: "SYSLIB1221", + title: "Unsupported C# language version", + messageFormat: "The language version does not support the required features for JSON source generation.", + category: JsonConstants.SystemTextJsonSourceGenerationName, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public static DiagnosticDescriptor JsonConstructorInaccessible { get; } = DiagnosticDescriptorHelper.Create( + id: "SYSLIB1222", + title: "Inaccessible JSON constructor", + messageFormat: "The constructor marked with [JsonConstructor] in {0} is inaccessible.", + category: JsonConstants.SystemTextJsonSourceGenerationName, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + public static DiagnosticDescriptor DerivedJsonConverterAttributesNotSupported { get; } = DiagnosticDescriptorHelper.Create( + id: "SYSLIB1223", + title: "Derived JsonConverter attributes are not supported", + messageFormat: "Derived attributes from [JsonConverter] in type {0} are not supported.", + category: JsonConstants.SystemTextJsonSourceGenerationName, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + public static DiagnosticDescriptor JsonSerializableAttributeOnNonContextType { get; } = DiagnosticDescriptorHelper.Create( + id: "SYSLIB1224", + title: "JsonSerializable attribute applied to non-context type", + messageFormat: "[JsonSerializable] can only be applied to a source generation context type.", + category: JsonConstants.SystemTextJsonSourceGenerationName, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + public static DiagnosticDescriptor TypeContainsRefLikeMember { get; } = DiagnosticDescriptorHelper.Create( + id: "SYSLIB1225", + title: "Type contains ref-like members", + messageFormat: "The type {0} cannot contain ref-like members for JSON serialization.", + category: JsonConstants.SystemTextJsonSourceGenerationName, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); + } + } +} diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/JsonSourceGenerator.Emitter.ExceptionMessages.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/JsonSourceGenerator.Emitter.ExceptionMessages.cs new file mode 100644 index 0000000..e9f25be --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/JsonSourceGenerator.Emitter.ExceptionMessages.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace CSharpToJsonSchema.Generators.JsonGen +{ + public sealed partial class JsonSourceGenerator + { + private sealed partial class Emitter + { + /// + /// Unlike sourcegen warnings, exception messages should not be localized so we keep them in source. + /// + private static class ExceptionMessages + { + public const string InaccessibleJsonIncludePropertiesNotSupported = + "The member '{0}.{1}' has been annotated with the JsonIncludeAttribute but is not visible to the source generator."; + + public const string IncompatibleConverterType = + "The converter '{0}' is not compatible with the type '{1}'."; + + public const string InitOnlyPropertySetterNotSupported = + "Setting init-only properties is not supported in source generation mode."; + + public const string InvalidJsonConverterFactoryOutput = + "The converter '{0}' cannot return null or a JsonConverterFactory instance."; + + public const string InvalidSerializablePropertyConfiguration = + "Invalid serializable-property configuration specified for type '{0}'. For more information, see 'JsonSourceGenerationMode.Serialization'."; + + public const string PropertyGetterDisallowNull = + "The property or field '{0}' on type '{1}' doesn't allow getting null values. Consider updating its nullability annotation."; + }; + } + } +} diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/JsonSourceGenerator.Emitter.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/JsonSourceGenerator.Emitter.cs new file mode 100644 index 0000000..4e229fc --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/JsonSourceGenerator.Emitter.cs @@ -0,0 +1,1534 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Reflection; +using System.Text; +using CSharpToJsonSchema.Generators.JsonGen.Base; +using CSharpToJsonSchema.Generators.JsonGen.Helpers; +using CSharpToJsonSchema.Generators.JsonGen.Model; +using CSharpToJsonSchema.Generators.JsonGen.System.Text.Json; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Text; + +namespace CSharpToJsonSchema.Generators.JsonGen +{ + public sealed partial class JsonSourceGenerator + { + private sealed partial class Emitter + { + // Literals in generated source + private const string CreateValueInfoMethodName = "CreateValueInfo"; + private const string CtorParamInitMethodNameSuffix = "CtorParamInit"; + private const string DefaultOptionsStaticVarName = "s_defaultOptions"; + private const string InstanceMemberBindingFlagsVariableName = "InstanceMemberBindingFlags"; + private const string OriginatingResolverPropertyName = "OriginatingResolver"; + private const string InfoVarName = "info"; + private const string NumberHandlingPropName = "NumberHandling"; + private const string UnmappedMemberHandlingPropName = "UnmappedMemberHandling"; + private const string PreferredPropertyObjectCreationHandlingPropName = "PreferredPropertyObjectCreationHandling"; + private const string ObjectCreatorPropName = "ObjectCreator"; + private const string OptionsInstanceVariableName = "Options"; + private const string JsonTypeInfoLocalVariableName = "jsonTypeInfo"; + private const string PropInitMethodNameSuffix = "PropInit"; + private const string TryGetTypeInfoForRuntimeCustomConverterMethodName = "TryGetTypeInfoForRuntimeCustomConverter"; + private const string ExpandConverterMethodName = "ExpandConverter"; + private const string GetConverterForNullablePropertyMethodName = "GetConverterForNullableProperty"; + private const string SerializeHandlerPropName = "SerializeHandler"; + private const string OptionsLocalVariableName = "options"; + private const string ValueVarName = "value"; + private const string WriterVarName = "writer"; + private const string PreserveReferenceHandlerPropertyName = "Preserve"; + private const string IgnoreCyclesReferenceHandlerPropertyName = "IgnoreCycles"; + + private static readonly AssemblyName s_assemblyName = typeof(Emitter).Assembly.GetName(); + + // global::fully.qualified.name for referenced types + private const string InvalidOperationExceptionTypeRef = "global::System.InvalidOperationException"; + private const string JsonExceptionTypeRef = "global::System.Text.Json.JsonException"; + private const string TypeTypeRef = "global::System.Type"; + private const string UnsafeTypeRef = "global::System.Runtime.CompilerServices.Unsafe"; + private const string EqualityComparerTypeRef = "global::System.Collections.Generic.EqualityComparer"; + private const string KeyValuePairTypeRef = "global::System.Collections.Generic.KeyValuePair"; + private const string JsonEncodedTextTypeRef = "global::System.Text.Json.JsonEncodedText"; + private const string JsonNamingPolicyTypeRef = "global::System.Text.Json.JsonNamingPolicy"; + private const string JsonSerializerTypeRef = "global::System.Text.Json.JsonSerializer"; + private const string JsonSerializerOptionsTypeRef = "global::System.Text.Json.JsonSerializerOptions"; + private const string JsonSerializerContextTypeRef = "global::System.Text.Json.Serialization.JsonSerializerContext"; + private const string Utf8JsonWriterTypeRef = "global::System.Text.Json.Utf8JsonWriter"; + private const string JsonCommentHandlingTypeRef = "global::System.Text.Json.JsonCommentHandling"; + private const string JsonConverterTypeRef = "global::System.Text.Json.Serialization.JsonConverter"; + private const string JsonConverterFactoryTypeRef = "global::System.Text.Json.Serialization.JsonConverterFactory"; + private const string JsonCollectionInfoValuesTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonCollectionInfoValues"; + private const string JsonIgnoreConditionTypeRef = "global::System.Text.Json.Serialization.JsonIgnoreCondition"; + private const string JsonSerializerDefaultsTypeRef = "global::System.Text.Json.JsonSerializerDefaults"; + private const string JsonNumberHandlingTypeRef = "global::System.Text.Json.Serialization.JsonNumberHandling"; + private const string JsonObjectCreationHandlingTypeRef = "global::System.Text.Json.Serialization.JsonObjectCreationHandling"; + private const string JsonUnmappedMemberHandlingTypeRef = "global::System.Text.Json.Serialization.JsonUnmappedMemberHandling"; + private const string JsonUnknownTypeHandlingTypeRef = "global::System.Text.Json.Serialization.JsonUnknownTypeHandling"; + private const string JsonMetadataServicesTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonMetadataServices"; + private const string JsonObjectInfoValuesTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonObjectInfoValues"; + private const string JsonParameterInfoValuesTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonParameterInfoValues"; + private const string JsonPropertyInfoTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonPropertyInfo"; + private const string JsonPropertyInfoValuesTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonPropertyInfoValues"; + private const string JsonTypeInfoTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonTypeInfo"; + private const string JsonTypeInfoResolverTypeRef = "global::System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver"; + private const string ReferenceHandlerTypeRef = "global::System.Text.Json.Serialization.ReferenceHandler"; + private const string EmptyTypeArray = "global::System.Array.Empty()"; + + /// + /// Contains an index from TypeRef to TypeGenerationSpec for the current ContextGenerationSpec. + /// + private readonly Dictionary _typeIndex = new(); + + /// + /// Cache of property names (statically determined) found across the type graph of the JsonSerializerContext. + /// The dictionary Key is the JSON property name, and the Value is the variable name which is the same as the property + /// name except for cases where special characters are used with [JsonPropertyName]. + /// + private readonly Dictionary _propertyNames = new(); + + /// + /// Indicates that the type graph contains a nullable property with a design-time custom converter declaration. + /// + private bool _emitGetConverterForNullablePropertyMethod; + + /// + /// The SourceText emit implementation filled by the individual Roslyn versions. + /// + private partial void AddSource(string hintName, SourceText sourceText); + + public void Emit(ContextGenerationSpec contextGenerationSpec) + { + Debug.Assert(_typeIndex.Count == 0); + Debug.Assert(_propertyNames.Count == 0); + Debug.Assert(!_emitGetConverterForNullablePropertyMethod); + + foreach (TypeGenerationSpec spec in contextGenerationSpec.GeneratedTypes) + { + if(_typeIndex.TryAdd(spec.TypeRef, spec)) + continue; + //_typeIndex.Add(spec.TypeRef, spec); + } + + foreach (TypeGenerationSpec typeGenerationSpec in contextGenerationSpec.GeneratedTypes) + { + SourceText? sourceText = GenerateTypeInfo(contextGenerationSpec, typeGenerationSpec); + if (sourceText != null) + { + AddSource($"{contextGenerationSpec.ContextType.Name}.{typeGenerationSpec.TypeInfoPropertyName}.g.cs", sourceText); + } + } + + string contextName = contextGenerationSpec.ContextType.Name; + + // Add root context implementation. + AddSource($"{contextName}.g.cs", GetRootJsonContextImplementation(contextGenerationSpec, _emitGetConverterForNullablePropertyMethod)); + + // Add GetJsonTypeInfo override implementation. + AddSource($"{contextName}.GetJsonTypeInfo.g.cs", GetGetTypeInfoImplementation(contextGenerationSpec)); + + // Add property name initialization. + AddSource($"{contextName}.PropertyNames.g.cs", GetPropertyNameInitialization(contextGenerationSpec)); + + _emitGetConverterForNullablePropertyMethod = false; + _propertyNames.Clear(); + _typeIndex.Clear(); + } + + private static SourceWriter CreateSourceWriterWithContextHeader(ContextGenerationSpec contextSpec, bool isPrimaryContextSourceFile = false, string? interfaceImplementation = null) + { + var writer = new SourceWriter(); + + writer.WriteLine(""" + // + + #nullable enable annotations + #nullable disable warnings + + // Suppress warnings about [Obsolete] member usage in generated code. + #pragma warning disable CS0612, CS0618 + + """); + + if (contextSpec.Namespace != null) + { + writer.WriteLine($"namespace {contextSpec.Namespace}"); + writer.WriteLine('{'); + writer.Indentation++; + } + + ImmutableEquatableArray contextClasses = contextSpec.ContextClassDeclarations; + Debug.Assert(contextClasses.Count > 0); + + // Emit any containing classes first. + for (int i = contextClasses.Count - 1; i > 0; i--) + { + writer.WriteLine(contextClasses[i]); + writer.WriteLine('{'); + writer.Indentation++; + } + + if (isPrimaryContextSourceFile) + { + // Annotate context class with the GeneratedCodeAttribute + writer.WriteLine($"""[global::System.CodeDom.Compiler.GeneratedCodeAttribute("{s_assemblyName.Name}", "{s_assemblyName.Version}")]"""); + } + + // Emit the JsonSerializerContext class declaration + writer.WriteLine($"{contextClasses[0]}{(interfaceImplementation is null ? "" : " : " + interfaceImplementation)}"); + writer.WriteLine('{'); + writer.Indentation++; + + return writer; + } + + private static SourceText CompleteSourceFileAndReturnText(SourceWriter writer) + { + while (writer.Indentation > 0) + { + writer.Indentation--; + writer.WriteLine('}'); + } + + return writer.ToSourceText(); + } + + private SourceText? GenerateTypeInfo(ContextGenerationSpec contextSpec, TypeGenerationSpec typeGenerationSpec) + { + switch (typeGenerationSpec.ClassType) + { + case ClassType.BuiltInSupportType: + return GenerateForTypeWithBuiltInConverter(contextSpec, typeGenerationSpec); + + case ClassType.TypeWithDesignTimeProvidedCustomConverter: + return GenerateForTypeWithCustomConverter(contextSpec, typeGenerationSpec); + + case ClassType.Nullable: + return GenerateForNullable(contextSpec, typeGenerationSpec); + + case ClassType.Enum: + return GenerateForEnum(contextSpec, typeGenerationSpec); + + case ClassType.Enumerable: + case ClassType.Dictionary: + return GenerateForCollection(contextSpec, typeGenerationSpec); + + case ClassType.Object: + if(contextSpec.ContextType.Name == "CancellationToken") + return null; // Do not emit a source file for the type. + return GenerateForObject(contextSpec, typeGenerationSpec); + + case ClassType.UnsupportedType: + return GenerateForUnsupportedType(contextSpec, typeGenerationSpec); + + case ClassType.TypeUnsupportedBySourceGen: + return null; // Do not emit a source file for the type. + + default: + Debug.Fail($"Unexpected class type {typeGenerationSpec.ClassType}"); + return null; + } + } + + private static SourceText GenerateForTypeWithBuiltInConverter(ContextGenerationSpec contextSpec, TypeGenerationSpec typeMetadata) + { + SourceWriter writer = CreateSourceWriterWithContextHeader(contextSpec); + + string typeFQN = typeMetadata.TypeRef.FullyQualifiedName; + string typeInfoPropertyName = typeMetadata.TypeInfoPropertyName; + + GenerateTypeInfoFactoryHeader(writer, typeMetadata); + writer.WriteLine($""" + {JsonTypeInfoLocalVariableName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeFQN)}({OptionsLocalVariableName}, {JsonMetadataServicesTypeRef}.{typeInfoPropertyName}Converter); + """); + + GenerateTypeInfoFactoryFooter(writer); + + return CompleteSourceFileAndReturnText(writer); + } + + private static SourceText GenerateForTypeWithCustomConverter(ContextGenerationSpec contextSpec, TypeGenerationSpec typeMetadata) + { + Debug.Assert(typeMetadata.ConverterType != null); + + SourceWriter writer = CreateSourceWriterWithContextHeader(contextSpec); + + string typeFQN = typeMetadata.TypeRef.FullyQualifiedName; + string converterFQN = typeMetadata.ConverterType.FullyQualifiedName; + + GenerateTypeInfoFactoryHeader(writer, typeMetadata); + + writer.WriteLine($""" + {JsonConverterTypeRef} converter = {ExpandConverterMethodName}(typeof({typeFQN}), new {converterFQN}(), {OptionsLocalVariableName}); + {JsonTypeInfoLocalVariableName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeFQN)} ({OptionsLocalVariableName}, converter); + """); + + GenerateTypeInfoFactoryFooter(writer); + + return CompleteSourceFileAndReturnText(writer); + } + + private static SourceText GenerateForNullable(ContextGenerationSpec contextSpec, TypeGenerationSpec typeMetadata) + { + Debug.Assert(typeMetadata.NullableUnderlyingType != null); + + SourceWriter writer = CreateSourceWriterWithContextHeader(contextSpec); + + string typeFQN = typeMetadata.TypeRef.FullyQualifiedName; + string underlyingTypeFQN = typeMetadata.NullableUnderlyingType.FullyQualifiedName; + + GenerateTypeInfoFactoryHeader(writer, typeMetadata); + + writer.WriteLine($$""" + {{JsonConverterTypeRef}} converter = {{JsonMetadataServicesTypeRef}}.GetNullableConverter<{{underlyingTypeFQN}}>({{OptionsLocalVariableName}}); + {{JsonTypeInfoLocalVariableName}} = {{JsonMetadataServicesTypeRef}}.{{GetCreateValueInfoMethodRef(typeFQN)}}({{OptionsLocalVariableName}}, converter); + """); + + GenerateTypeInfoFactoryFooter(writer); + + return CompleteSourceFileAndReturnText(writer); + } + + private static SourceText GenerateForUnsupportedType(ContextGenerationSpec contextSpec, TypeGenerationSpec typeMetadata) + { + SourceWriter writer = CreateSourceWriterWithContextHeader(contextSpec); + + string typeFQN = typeMetadata.TypeRef.FullyQualifiedName; + + GenerateTypeInfoFactoryHeader(writer, typeMetadata); + writer.WriteLine($""" + {JsonTypeInfoLocalVariableName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeFQN)}({OptionsLocalVariableName}, {JsonMetadataServicesTypeRef}.GetUnsupportedTypeConverter<{typeFQN}>()); + """); + + GenerateTypeInfoFactoryFooter(writer); + + return CompleteSourceFileAndReturnText(writer); + } + + private static SourceText GenerateForEnum(ContextGenerationSpec contextSpec, TypeGenerationSpec typeMetadata) + { + SourceWriter writer = CreateSourceWriterWithContextHeader(contextSpec); + + string typeFQN = typeMetadata.TypeRef.FullyQualifiedName; + + GenerateTypeInfoFactoryHeader(writer, typeMetadata); + writer.WriteLine($""" + {JsonTypeInfoLocalVariableName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeFQN)}({OptionsLocalVariableName}, {JsonMetadataServicesTypeRef}.GetEnumConverter<{typeFQN}>({OptionsLocalVariableName})); + """); + + GenerateTypeInfoFactoryFooter(writer); + + return CompleteSourceFileAndReturnText(writer); + } + + private SourceText GenerateForCollection(ContextGenerationSpec contextSpec, TypeGenerationSpec typeGenerationSpec) + { + SourceWriter writer = CreateSourceWriterWithContextHeader(contextSpec); + + // Key metadata + TypeRef? collectionKeyType = typeGenerationSpec.CollectionKeyType; + Debug.Assert(!(typeGenerationSpec.ClassType == ClassType.Dictionary && collectionKeyType == null)); + string? keyTypeFQN = collectionKeyType?.FullyQualifiedName; + + // Value metadata + TypeRef? collectionValueType = typeGenerationSpec.CollectionValueType; + Debug.Assert(collectionValueType != null); + string valueTypeFQN = collectionValueType.FullyQualifiedName; + + CollectionType collectionType = typeGenerationSpec.CollectionType; + + string? serializeMethodName = ShouldGenerateSerializationLogic(typeGenerationSpec) + ? $"{typeGenerationSpec.TypeInfoPropertyName}{SerializeHandlerPropName}" + : null; + + string typeFQN = typeGenerationSpec.TypeRef.FullyQualifiedName; + string createCollectionInfoMethodName = GetCollectionInfoMethodName(collectionType); + string createCollectionMethodExpr; + + switch (collectionType) + { + case CollectionType.Array: + case CollectionType.MemoryOfT: + case CollectionType.ReadOnlyMemoryOfT: + createCollectionMethodExpr = $"{createCollectionInfoMethodName}<{valueTypeFQN}>({OptionsLocalVariableName}, {InfoVarName})"; + break; + case CollectionType.IEnumerable: + case CollectionType.IDictionary: + case CollectionType.IList: + createCollectionMethodExpr = $"{createCollectionInfoMethodName}<{typeFQN}>({OptionsLocalVariableName}, {InfoVarName})"; + break; + case CollectionType.Stack: + case CollectionType.Queue: + string addMethod = collectionType == CollectionType.Stack ? "Push" : "Enqueue"; + string addFuncNamedArg = $"(collection, {ValueVarName}) => collection.{addMethod}({ValueVarName})"; + createCollectionMethodExpr = $"{createCollectionInfoMethodName}<{typeFQN}>({OptionsLocalVariableName}, {InfoVarName}, addFunc: {addFuncNamedArg})"; + break; + case CollectionType.ImmutableEnumerable: + createCollectionMethodExpr = $"{createCollectionInfoMethodName}<{typeFQN}, {valueTypeFQN}>({OptionsLocalVariableName}, {InfoVarName}, createRangeFunc: {typeGenerationSpec.ImmutableCollectionFactoryMethod})"; + break; + case CollectionType.Dictionary: + case CollectionType.IDictionaryOfTKeyTValue: + case CollectionType.IReadOnlyDictionary: + Debug.Assert(keyTypeFQN != null); + createCollectionMethodExpr = $"{createCollectionInfoMethodName}<{typeFQN}, {keyTypeFQN!}, {valueTypeFQN}>({OptionsLocalVariableName}, {InfoVarName})"; + break; + case CollectionType.ImmutableDictionary: + Debug.Assert(keyTypeFQN != null); + createCollectionMethodExpr = $"{createCollectionInfoMethodName}<{typeFQN}, {keyTypeFQN!}, {valueTypeFQN}>({OptionsLocalVariableName}, {InfoVarName}, createRangeFunc: {typeGenerationSpec.ImmutableCollectionFactoryMethod})"; + break; + default: + createCollectionMethodExpr = $"{createCollectionInfoMethodName}<{typeFQN}, {valueTypeFQN}>({OptionsLocalVariableName}, {InfoVarName})"; + break; + } + + GenerateTypeInfoFactoryHeader(writer, typeGenerationSpec); + + writer.WriteLine($$""" + var {{InfoVarName}} = new {{JsonCollectionInfoValuesTypeRef}}<{{typeFQN}}> + { + {{ObjectCreatorPropName}} = {{FormatDefaultConstructorExpr(typeGenerationSpec)}}, + {{SerializeHandlerPropName}} = {{serializeMethodName ?? "null"}} + }; + + {{JsonTypeInfoLocalVariableName}} = {{JsonMetadataServicesTypeRef}}.{{createCollectionMethodExpr}}; + {{JsonTypeInfoLocalVariableName}}.{{NumberHandlingPropName}} = {{FormatNumberHandling(typeGenerationSpec.NumberHandling)}}; + """); + + GenerateTypeInfoFactoryFooter(writer); + + if (serializeMethodName != null) + { + writer.WriteLine(); + + if (typeGenerationSpec.ClassType == ClassType.Enumerable) + { + GenerateFastPathFuncForEnumerable(writer, serializeMethodName, typeGenerationSpec); + } + else + { + GenerateFastPathFuncForDictionary(writer, serializeMethodName, typeGenerationSpec); + } + } + + return CompleteSourceFileAndReturnText(writer); + } + + private void GenerateFastPathFuncForEnumerable(SourceWriter writer, string serializeMethodName, TypeGenerationSpec typeGenerationSpec) + { + Debug.Assert(typeGenerationSpec.CollectionValueType != null); + TypeGenerationSpec valueTypeGenerationSpec = _typeIndex[typeGenerationSpec.CollectionValueType]; + + GenerateFastPathFuncHeader(writer, typeGenerationSpec, serializeMethodName); + + writer.WriteLine($"{WriterVarName}.WriteStartArray();"); + writer.WriteLine(); + + string getCurrentElementExpr; + const string elementVarName = "element"; + switch (typeGenerationSpec.CollectionType) + { + case CollectionType.Array: + writer.WriteLine($"for (int i = 0; i < {ValueVarName}.Length; i++)"); + getCurrentElementExpr = $"{ValueVarName}[i]"; + break; + + case CollectionType.MemoryOfT: + case CollectionType.ReadOnlyMemoryOfT: + writer.WriteLine($"foreach ({valueTypeGenerationSpec.TypeRef.FullyQualifiedName} {elementVarName} in {ValueVarName}.Span)"); + getCurrentElementExpr = elementVarName; + break; + + case CollectionType.IListOfT: + case CollectionType.List: + case CollectionType.IList: + writer.WriteLine($"for (int i = 0; i < {ValueVarName}.Count; i++)"); + getCurrentElementExpr = $"{ValueVarName}[i]"; + break; + + default: + writer.WriteLine($"foreach ({valueTypeGenerationSpec.TypeRef.FullyQualifiedName} {elementVarName} in {ValueVarName})"); + getCurrentElementExpr = elementVarName; + break; + }; + + writer.WriteLine('{'); + writer.Indentation++; + + GenerateSerializeValueStatement(writer, valueTypeGenerationSpec, getCurrentElementExpr); + + writer.Indentation--; + writer.WriteLine('}'); + + writer.WriteLine(); + writer.WriteLine($"{WriterVarName}.WriteEndArray();"); + + writer.Indentation--; + writer.WriteLine('}'); + } + + private void GenerateFastPathFuncForDictionary(SourceWriter writer, string serializeMethodName, TypeGenerationSpec typeGenerationSpec) + { + Debug.Assert(typeGenerationSpec.CollectionKeyType != null); + Debug.Assert(typeGenerationSpec.CollectionValueType != null); + + TypeRef keyType = typeGenerationSpec.CollectionKeyType; + TypeGenerationSpec valueTypeGenerationSpec = _typeIndex[typeGenerationSpec.CollectionValueType]; + + GenerateFastPathFuncHeader(writer, typeGenerationSpec, serializeMethodName); + + writer.WriteLine($"{WriterVarName}.WriteStartObject();"); + writer.WriteLine(); + + writer.WriteLine($"foreach ({KeyValuePairTypeRef}<{keyType.FullyQualifiedName}, {valueTypeGenerationSpec.TypeRef.FullyQualifiedName}> entry in {ValueVarName})"); + writer.WriteLine('{'); + writer.Indentation++; + + GenerateSerializePropertyStatement(writer, valueTypeGenerationSpec, propertyNameExpr: "entry.Key", valueExpr: "entry.Value"); + + writer.Indentation--; + writer.WriteLine('}'); + + writer.WriteLine(); + writer.WriteLine($"{WriterVarName}.WriteEndObject();"); + + writer.Indentation--; + writer.WriteLine('}'); + } + + private SourceText GenerateForObject(ContextGenerationSpec contextSpec, TypeGenerationSpec typeMetadata) + { + SourceWriter writer = CreateSourceWriterWithContextHeader(contextSpec); + + string typeFriendlyName = typeMetadata.TypeInfoPropertyName; + ObjectConstructionStrategy constructionStrategy = typeMetadata.ConstructionStrategy; + + string creatorInvocation = FormatDefaultConstructorExpr(typeMetadata); + string parameterizedCreatorInvocation = constructionStrategy == ObjectConstructionStrategy.ParameterizedConstructor + ? GetParameterizedCtorInvocationFunc(typeMetadata) + : "null"; + + string? propInitMethodName = null; + string? propInitAdapterFunc = null; + string? constructorInfoFactoryFunc = null; + string? ctorParamMetadataInitMethodName = null; + string? serializeMethodName = null; + + if (ShouldGenerateMetadata(typeMetadata)) + { + propInitMethodName = $"{typeFriendlyName}{PropInitMethodNameSuffix}"; + propInitAdapterFunc = $"_ => {propInitMethodName}({OptionsLocalVariableName})"; + + if (constructionStrategy is ObjectConstructionStrategy.ParameterizedConstructor) + { + ctorParamMetadataInitMethodName = $"{typeFriendlyName}{CtorParamInitMethodNameSuffix}"; + } + + if (constructionStrategy is ObjectConstructionStrategy.ParameterlessConstructor + or ObjectConstructionStrategy.ParameterizedConstructor) + { + string argTypes = typeMetadata.CtorParamGenSpecs.Count == 0 + ? EmptyTypeArray + : $$"""new[] {{{string.Join(", ", typeMetadata.CtorParamGenSpecs.Select(p => $"typeof({p.ParameterType.FullyQualifiedName})"))}}}"""; + + constructorInfoFactoryFunc = $"static () => typeof({typeMetadata.TypeRef.FullyQualifiedName}).GetConstructor({InstanceMemberBindingFlagsVariableName}, binder: null, {argTypes}, modifiers: null)"; + } + } + + if (ShouldGenerateSerializationLogic(typeMetadata)) + { + serializeMethodName = $"{typeFriendlyName}{SerializeHandlerPropName}"; + } + + const string ObjectInfoVarName = "objectInfo"; + string genericArg = typeMetadata.TypeRef.FullyQualifiedName; + + GenerateTypeInfoFactoryHeader(writer, typeMetadata); + + writer.WriteLine($$""" + var {{ObjectInfoVarName}} = new {{JsonObjectInfoValuesTypeRef}}<{{genericArg}}> + { + {{ObjectCreatorPropName}} = {{creatorInvocation}}, + ObjectWithParameterizedConstructorCreator = {{parameterizedCreatorInvocation}}, + PropertyMetadataInitializer = {{propInitAdapterFunc ?? "null"}}, + ConstructorParameterMetadataInitializer = {{ctorParamMetadataInitMethodName ?? "null"}}, + ConstructorAttributeProviderFactory = {{constructorInfoFactoryFunc ?? "null"}}, + {{SerializeHandlerPropName}} = {{serializeMethodName ?? "null"}}, + }; + + {{JsonTypeInfoLocalVariableName}} = {{JsonMetadataServicesTypeRef}}.CreateObjectInfo<{{typeMetadata.TypeRef.FullyQualifiedName}}>({{OptionsLocalVariableName}}, {{ObjectInfoVarName}}); + {{JsonTypeInfoLocalVariableName}}.{{NumberHandlingPropName}} = {{FormatNumberHandling(typeMetadata.NumberHandling)}}; + """); + + if (typeMetadata is { UnmappedMemberHandling: not null } or { PreferredPropertyObjectCreationHandling: not null }) + { + writer.WriteLine(); + + if (typeMetadata.UnmappedMemberHandling != null) + { + writer.WriteLine($"{JsonTypeInfoLocalVariableName}.{UnmappedMemberHandlingPropName} = {FormatUnmappedMemberHandling(typeMetadata.UnmappedMemberHandling.Value)};"); + } + + if (typeMetadata.PreferredPropertyObjectCreationHandling != null) + { + writer.WriteLine($"{JsonTypeInfoLocalVariableName}.{PreferredPropertyObjectCreationHandlingPropName} = {FormatObjectCreationHandling(typeMetadata.PreferredPropertyObjectCreationHandling.Value)};"); + } + } + + GenerateTypeInfoFactoryFooter(writer); + + if (propInitMethodName != null) + { + writer.WriteLine(); + GeneratePropMetadataInitFunc(writer, propInitMethodName, typeMetadata); + } + + if (serializeMethodName != null) + { + writer.WriteLine(); + GenerateFastPathFuncForObject(writer, contextSpec, serializeMethodName, typeMetadata); + } + + if (ctorParamMetadataInitMethodName != null) + { + writer.WriteLine(); + GenerateCtorParamMetadataInitFunc(writer, ctorParamMetadataInitMethodName, typeMetadata); + } + + writer.Indentation--; + writer.WriteLine('}'); + + return CompleteSourceFileAndReturnText(writer); + } + + private void GeneratePropMetadataInitFunc(SourceWriter writer, string propInitMethodName, TypeGenerationSpec typeGenerationSpec) + { + ImmutableEquatableArray properties = typeGenerationSpec.PropertyGenSpecs; + + writer.WriteLine($"private static {JsonPropertyInfoTypeRef}[] {propInitMethodName}({JsonSerializerOptionsTypeRef} {OptionsLocalVariableName})"); + writer.WriteLine('{'); + writer.Indentation++; + + writer.WriteLine($"var properties = new {JsonPropertyInfoTypeRef}[{properties.Count}];"); + writer.WriteLine(); + + for (int i = 0; i < properties.Count; i++) + { + PropertyGenerationSpec property = properties[i]; + string propertyName = property.NameSpecifiedInSourceCode; + string declaringTypeFQN = property.DeclaringType.FullyQualifiedName; + string propertyTypeFQN = property.PropertyType.FullyQualifiedName; + + string getterValue = property switch + { + { DefaultIgnoreCondition: JsonIgnoreCondition.Always } => "null", + { CanUseGetter: true } => $"static obj => (({declaringTypeFQN})obj).{propertyName}", + { CanUseGetter: false, HasJsonInclude: true } + => $"""static _ => throw new {InvalidOperationExceptionTypeRef}("{string.Format(ExceptionMessages.InaccessibleJsonIncludePropertiesNotSupported, typeGenerationSpec.TypeRef.Name, propertyName)}")""", + _ => "null" + }; + + string setterValue = property switch + { + { DefaultIgnoreCondition: JsonIgnoreCondition.Always } => "null", + { CanUseSetter: true, IsInitOnlySetter: true } + => $"""static (obj, value) => throw new {InvalidOperationExceptionTypeRef}("{ExceptionMessages.InitOnlyPropertySetterNotSupported}")""", + { CanUseSetter: true } when typeGenerationSpec.TypeRef.IsValueType + => $"""static (obj, value) => {UnsafeTypeRef}.Unbox<{declaringTypeFQN}>(obj).{propertyName} = value!""", + { CanUseSetter: true } + => $"""static (obj, value) => (({declaringTypeFQN})obj).{propertyName} = value!""", + { CanUseSetter: false, HasJsonInclude: true } + => $"""static (obj, value) => throw new {InvalidOperationExceptionTypeRef}("{string.Format(ExceptionMessages.InaccessibleJsonIncludePropertiesNotSupported, typeGenerationSpec.TypeRef.Name, property.MemberName)}")""", + _ => "null", + }; + + string ignoreConditionNamedArg = property.DefaultIgnoreCondition.HasValue + ? $"{JsonIgnoreConditionTypeRef}.{property.DefaultIgnoreCondition.Value}" + : "null"; + + string? converterInstantiationExpr = null; + if (property.ConverterType != null) + { + string converterFQN = property.ConverterType.FullyQualifiedName; + TypeRef? nullableUnderlyingType = _typeIndex[property.PropertyType].NullableUnderlyingType; + _emitGetConverterForNullablePropertyMethod |= nullableUnderlyingType != null; + + converterInstantiationExpr = nullableUnderlyingType != null + ? $"{GetConverterForNullablePropertyMethodName}<{nullableUnderlyingType.FullyQualifiedName}>(new {converterFQN}(), {OptionsLocalVariableName})" + : $"({JsonConverterTypeRef}<{propertyTypeFQN}>){ExpandConverterMethodName}(typeof({propertyTypeFQN}), new {converterFQN}(), {OptionsLocalVariableName})"; + } + + string attributeProviderFactoryExpr = property.IsProperty + ? $"typeof({property.DeclaringType.FullyQualifiedName}).GetProperty({FormatStringLiteral(property.MemberName)}, {InstanceMemberBindingFlagsVariableName}, null, typeof({property.PropertyType.FullyQualifiedName}), {EmptyTypeArray}, null)" + : $"typeof({property.DeclaringType.FullyQualifiedName}).GetField({FormatStringLiteral(property.MemberName)}, {InstanceMemberBindingFlagsVariableName})"; + + writer.WriteLine($$""" + var {{InfoVarName}}{{i}} = new {{JsonPropertyInfoValuesTypeRef}}<{{propertyTypeFQN}}> + { + IsProperty = {{FormatBoolLiteral(property.IsProperty)}}, + IsPublic = {{FormatBoolLiteral(property.IsPublic)}}, + IsVirtual = {{FormatBoolLiteral(property.IsVirtual)}}, + DeclaringType = typeof({{property.DeclaringType.FullyQualifiedName}}), + Converter = {{converterInstantiationExpr ?? "null"}}, + Getter = {{getterValue}}, + Setter = {{setterValue}}, + IgnoreCondition = {{ignoreConditionNamedArg}}, + HasJsonInclude = {{FormatBoolLiteral(property.HasJsonInclude)}}, + IsExtensionData = {{FormatBoolLiteral(property.IsExtensionData)}}, + NumberHandling = {{FormatNumberHandling(property.NumberHandling)}}, + PropertyName = {{FormatStringLiteral(property.MemberName)}}, + JsonPropertyName = {{FormatStringLiteral(property.JsonPropertyName)}}, + AttributeProviderFactory = static () => {{attributeProviderFactoryExpr}}, + }; + + properties[{{i}}] = {{JsonMetadataServicesTypeRef}}.CreatePropertyInfo<{{propertyTypeFQN}}>({{OptionsLocalVariableName}}, {{InfoVarName}}{{i}}); + """); + + if (property.HasJsonRequiredAttribute || + (property.IsRequired && !typeGenerationSpec.ConstructorSetsRequiredParameters)) + { + writer.WriteLine($"properties[{i}].IsRequired = true;"); + } + + if (property.ObjectCreationHandling != null) + { + writer.WriteLine($"properties[{i}].ObjectCreationHandling = {FormatObjectCreationHandling(property.ObjectCreationHandling.Value)};"); + } + + if (property.Order != 0) + { + writer.WriteLine($"properties[{i}].Order = {property.Order};"); + } + + if (property.IsGetterNonNullableAnnotation) + { + writer.WriteLine($"properties[{i}].IsGetNullable = false;"); + } + if (property.IsSetterNonNullableAnnotation) + { + writer.WriteLine($"properties[{i}].IsSetNullable = false;"); + } + + writer.WriteLine(); + } + + writer.WriteLine($"return properties;"); + writer.Indentation--; + writer.WriteLine('}'); + } + + private static void GenerateCtorParamMetadataInitFunc(SourceWriter writer, string ctorParamMetadataInitMethodName, TypeGenerationSpec typeGenerationSpec) + { + ImmutableEquatableArray parameters = typeGenerationSpec.CtorParamGenSpecs; + ImmutableEquatableArray propertyInitializers = typeGenerationSpec.PropertyInitializerSpecs; + int paramCount = parameters.Count + propertyInitializers.Count(propInit => !propInit.MatchesConstructorParameter); + Debug.Assert(paramCount > 0); + + writer.WriteLine($"private static {JsonParameterInfoValuesTypeRef}[] {ctorParamMetadataInitMethodName}() => new {JsonParameterInfoValuesTypeRef}[]"); + writer.WriteLine('{'); + writer.Indentation++; + + int i = 0; + foreach (ParameterGenerationSpec spec in parameters) + { + writer.WriteLine($$""" + new() + { + Name = {{FormatStringLiteral(spec.Name)}}, + ParameterType = typeof({{spec.ParameterType.FullyQualifiedName}}), + Position = {{spec.ParameterIndex}}, + HasDefaultValue = {{FormatBoolLiteral(spec.HasDefaultValue)}}, + DefaultValue = {{(spec.HasDefaultValue ? CSharpSyntaxUtilities.FormatLiteral(spec.DefaultValue, spec.ParameterType) : "null")}}, + IsNullable = {{FormatBoolLiteral(spec.IsNullable)}}, + }, + """); + + if (++i < paramCount) + { + writer.WriteLine(); + } + } + + foreach (PropertyInitializerGenerationSpec spec in propertyInitializers) + { + if (spec.MatchesConstructorParameter) + { + continue; + } + + writer.WriteLine($$""" + new() + { + Name = {{FormatStringLiteral(spec.Name)}}, + ParameterType = typeof({{spec.ParameterType.FullyQualifiedName}}), + Position = {{spec.ParameterIndex}}, + IsNullable = {{FormatBoolLiteral(spec.IsNullable)}}, + IsMemberInitializer = true, + }, + """); + + if (++i < paramCount) + { + writer.WriteLine(); + } + } + + writer.Indentation--; + writer.WriteLine("};"); + } + + private void GenerateFastPathFuncForObject(SourceWriter writer, ContextGenerationSpec contextSpec, string serializeMethodName, TypeGenerationSpec typeGenSpec) + { + if (typeGenSpec.FastPathPropertyIndices is null) + { + // Type uses configuration that doesn't support fast-path: emit a stub that just throws. + GenerateFastPathFuncHeader(writer, typeGenSpec, serializeMethodName, skipNullCheck: true); + + string exceptionMessage = string.Format(ExceptionMessages.InvalidSerializablePropertyConfiguration, typeGenSpec.TypeRef.FullyQualifiedName); + writer.WriteLine($"""throw new {InvalidOperationExceptionTypeRef}("{exceptionMessage}");"""); + writer.Indentation--; + writer.WriteLine('}'); + return; + } + + GenerateFastPathFuncHeader(writer, typeGenSpec, serializeMethodName); + + if (typeGenSpec.ImplementsIJsonOnSerializing) + { + writer.WriteLine($"((global::{JsonConstants.IJsonOnSerializingFullName}){ValueVarName}).OnSerializing();"); + writer.WriteLine(); + } + + writer.WriteLine($"{WriterVarName}.WriteStartObject();"); + writer.WriteLine(); + + bool generateDisallowNullThrowHelper = false; + + // Provide generation logic for each prop. + foreach (int i in typeGenSpec.FastPathPropertyIndices) + { + PropertyGenerationSpec propertyGenSpec = typeGenSpec.PropertyGenSpecs[i]; + + if (!propertyGenSpec.ShouldIncludePropertyForFastPath(contextSpec)) + { + continue; + } + + TypeGenerationSpec propertyTypeSpec = _typeIndex[propertyGenSpec.PropertyType]; + + if (propertyTypeSpec.ClassType is ClassType.TypeUnsupportedBySourceGen) + { + continue; + } + + string effectiveJsonPropertyName = propertyGenSpec.EffectiveJsonPropertyName; + string propertyNameFieldName = propertyGenSpec.PropertyNameFieldName; + + // Add the property names to the context-wide cache; we'll generate the source to initialize them at the end of generation. + Debug.Assert(!_propertyNames.TryGetValue(effectiveJsonPropertyName, out string? existingName) || existingName == propertyNameFieldName); + _propertyNames.TryAdd(effectiveJsonPropertyName, propertyNameFieldName); + + SerializedValueCheckType defaultCheckType = GetCheckType(contextSpec, propertyGenSpec); + + // For properties whose declared type differs from that of the serialized type + // perform an explicit cast -- this is to account for hidden properties or diamond ambiguity. + string? objectExpr = propertyGenSpec.DeclaringType != typeGenSpec.TypeRef + ? $"(({propertyGenSpec.DeclaringType.FullyQualifiedName}){ValueVarName})" + : ValueVarName; + + string propValueExpr; + if (defaultCheckType != SerializedValueCheckType.None) + { + // Use temporary variable to evaluate property value only once + string localVariableName = $"__value_{propertyGenSpec.NameSpecifiedInSourceCode.TrimStart('@')}"; + writer.WriteLine($"{propertyGenSpec.PropertyType.FullyQualifiedName} {localVariableName} = {objectExpr}.{propertyGenSpec.NameSpecifiedInSourceCode};"); + propValueExpr = localVariableName; + } + else + { + propValueExpr = $"{objectExpr}.{propertyGenSpec.NameSpecifiedInSourceCode}"; + } + + switch (defaultCheckType) + { + case SerializedValueCheckType.IgnoreWhenNull: + writer.WriteLine($"if ({propValueExpr} is not null)"); + writer.WriteLine('{'); + writer.Indentation++; + + GenerateSerializePropertyStatement(writer, propertyTypeSpec, propertyNameFieldName, propValueExpr); + + writer.Indentation--; + writer.WriteLine('}'); + break; + + case SerializedValueCheckType.IgnoreWhenDefault: + writer.WriteLine($"if (!{EqualityComparerTypeRef}<{propertyGenSpec.PropertyType.FullyQualifiedName}>.Default.Equals(default, {propValueExpr}))"); + writer.WriteLine('{'); + writer.Indentation++; + + GenerateSerializePropertyStatement(writer, propertyTypeSpec, propertyNameFieldName, propValueExpr); + + writer.Indentation--; + writer.WriteLine('}'); + break; + + case SerializedValueCheckType.DisallowNull: + writer.WriteLine($$""" + if ({{propValueExpr}} is null) + { + ThrowPropertyNullException({{FormatStringLiteral(propertyGenSpec.EffectiveJsonPropertyName)}}); + } + + """); + + GenerateSerializePropertyStatement(writer, propertyTypeSpec, propertyNameFieldName, propValueExpr); + generateDisallowNullThrowHelper = true; + break; + + default: + GenerateSerializePropertyStatement(writer, propertyTypeSpec, propertyNameFieldName, propValueExpr); + break; + } + } + + // End method logic. + writer.WriteLine(); + writer.WriteLine($"{WriterVarName}.WriteEndObject();"); + + if (typeGenSpec.ImplementsIJsonOnSerialized) + { + writer.WriteLine(); + writer.WriteLine($"((global::{JsonConstants.IJsonOnSerializedFullName}){ValueVarName}).OnSerialized();"); + } + + if (generateDisallowNullThrowHelper) + { + writer.WriteLine(); + writer.WriteLine($$""" + static void ThrowPropertyNullException(string propertyName) + { + throw new {{JsonExceptionTypeRef}}(string.Format("{{ExceptionMessages.PropertyGetterDisallowNull}}", propertyName, {{FormatStringLiteral(typeGenSpec.TypeRef.Name)}})); + } + """); + } + + writer.Indentation--; + writer.WriteLine('}'); + } + + private static string GetParameterizedCtorInvocationFunc(TypeGenerationSpec typeGenerationSpec) + { + ImmutableEquatableArray parameters = typeGenerationSpec.CtorParamGenSpecs; + ImmutableEquatableArray propertyInitializers = typeGenerationSpec.PropertyInitializerSpecs; + + const string ArgsVarName = "args"; + + StringBuilder sb = new($"static {ArgsVarName} => new {typeGenerationSpec.TypeRef.FullyQualifiedName}("); + + if (parameters.Count > 0) + { + foreach (ParameterGenerationSpec param in parameters) + { + int index = param.ParameterIndex; + sb.Append($"{GetParamUnboxing(param.ParameterType, index)}, "); + } + + sb.Length -= 2; // delete the last ", " token + } + + sb.Append(')'); + + if (propertyInitializers.Count > 0) + { + sb.Append("{ "); + foreach (PropertyInitializerGenerationSpec property in propertyInitializers) + { + sb.Append($"{property.Name} = {GetParamUnboxing(property.ParameterType, property.ParameterIndex)}, "); + } + + sb.Length -= 2; // delete the last ", " token + sb.Append(" }"); + } + + return sb.ToString(); + + static string GetParamUnboxing(TypeRef type, int index) + => $"({type.FullyQualifiedName}){ArgsVarName}[{index}]"; + } + + private static string? GetPrimitiveWriterMethod(TypeGenerationSpec type) + { + return type.PrimitiveTypeKind switch + { + JsonPrimitiveTypeKind.Number => "WriteNumber", + JsonPrimitiveTypeKind.String or JsonPrimitiveTypeKind.Char => "WriteString", + JsonPrimitiveTypeKind.Boolean => "WriteBoolean", + JsonPrimitiveTypeKind.ByteArray => "WriteBase64String", + _ => null + }; + } + + private static void GenerateFastPathFuncHeader(SourceWriter writer, TypeGenerationSpec typeGenSpec, string methodName, bool skipNullCheck = false) + { + // fast path serializers for reference types always support null inputs. + string valueTypeRef = typeGenSpec.TypeRef.IsValueType + ? typeGenSpec.TypeRef.FullyQualifiedName + : typeGenSpec.TypeRef.FullyQualifiedName + "?"; + + writer.WriteLine($$""" + // Intentionally not a static method because we create a delegate to it. Invoking delegates to instance + // methods is almost as fast as virtual calls. Static methods need to go through a shuffle thunk. + private void {{methodName}}({{Utf8JsonWriterTypeRef}} {{WriterVarName}}, {{valueTypeRef}} {{ValueVarName}}) + { + """); + + writer.Indentation++; + + if (!skipNullCheck && typeGenSpec.TypeRef.CanBeNull) + { + writer.WriteLine($$""" + if ({{ValueVarName}} is null) + { + {{WriterVarName}}.WriteNullValue(); + return; + } + + """); + } + } + + private static void GenerateSerializeValueStatement(SourceWriter writer, TypeGenerationSpec typeSpec, string valueExpr) + { + if (GetPrimitiveWriterMethod(typeSpec) is string primitiveWriterMethod) + { + if (typeSpec.PrimitiveTypeKind is JsonPrimitiveTypeKind.Char) + { + writer.WriteLine($"{WriterVarName}.{primitiveWriterMethod}Value({valueExpr}.ToString());"); + } + else + { + writer.WriteLine($"{WriterVarName}.{primitiveWriterMethod}Value({valueExpr});"); + } + } + else + { + if (ShouldGenerateSerializationLogic(typeSpec)) + { + writer.WriteLine($"{typeSpec.TypeInfoPropertyName}{SerializeHandlerPropName}({WriterVarName}, {valueExpr});"); + } + else + { + writer.WriteLine($"{JsonSerializerTypeRef}.Serialize({WriterVarName}, {valueExpr}, {typeSpec.TypeInfoPropertyName});"); + } + } + } + + private static void GenerateSerializePropertyStatement(SourceWriter writer, TypeGenerationSpec typeSpec, string propertyNameExpr, string valueExpr) + { + if (GetPrimitiveWriterMethod(typeSpec) is string primitiveWriterMethod) + { + if (typeSpec.PrimitiveTypeKind is JsonPrimitiveTypeKind.Char) + { + writer.WriteLine($"{WriterVarName}.{primitiveWriterMethod}({propertyNameExpr}, {valueExpr}.ToString());"); + } + else + { + writer.WriteLine($"{WriterVarName}.{primitiveWriterMethod}({propertyNameExpr}, {valueExpr});"); + } + } + else + { + writer.WriteLine($"{WriterVarName}.WritePropertyName({propertyNameExpr});"); + + if (ShouldGenerateSerializationLogic(typeSpec)) + { + writer.WriteLine($"{typeSpec.TypeInfoPropertyName}{SerializeHandlerPropName}({WriterVarName}, {valueExpr});"); + } + else + { + writer.WriteLine($"{JsonSerializerTypeRef}.Serialize({WriterVarName}, {valueExpr}, {typeSpec.TypeInfoPropertyName});"); + } + } + } + + private enum SerializedValueCheckType + { + None, + IgnoreWhenNull, + IgnoreWhenDefault, + DisallowNull, + } + + private static SerializedValueCheckType GetCheckType(ContextGenerationSpec contextSpec, PropertyGenerationSpec propertySpec) + { + return (propertySpec.DefaultIgnoreCondition ?? contextSpec.GeneratedOptionsSpec?.DefaultIgnoreCondition) switch + { + JsonIgnoreCondition.WhenWritingNull => propertySpec.PropertyType.CanBeNull ? SerializedValueCheckType.IgnoreWhenNull : SerializedValueCheckType.None, + JsonIgnoreCondition.WhenWritingDefault => propertySpec.PropertyType.CanBeNull ? SerializedValueCheckType.IgnoreWhenNull : SerializedValueCheckType.IgnoreWhenDefault, + _ when propertySpec.IsGetterNonNullableAnnotation && contextSpec.GeneratedOptionsSpec?.RespectNullableAnnotations is true => SerializedValueCheckType.DisallowNull, + _ => SerializedValueCheckType.None, + }; + } + + private static void GenerateTypeInfoFactoryHeader(SourceWriter writer, TypeGenerationSpec typeMetadata) + { + string typeFQN = typeMetadata.TypeRef.FullyQualifiedName; + string typeInfoPropertyName = typeMetadata.TypeInfoPropertyName; + string typeInfoFQN = $"{JsonTypeInfoTypeRef}<{typeFQN}>"; + + writer.WriteLine($$""" + private {{typeInfoFQN}}? _{{typeInfoPropertyName}}; + + /// + /// Defines the source generated JSON serialization contract metadata for a given type. + /// + #nullable disable annotations // Marking the property type as nullable-oblivious. + public {{typeInfoFQN}} {{typeInfoPropertyName}} + #nullable enable annotations + { + get => _{{typeInfoPropertyName}} ??= ({{typeInfoFQN}}){{OptionsInstanceVariableName}}.GetTypeInfo(typeof({{typeFQN}})); + } + + private {{typeInfoFQN}} {{CreateTypeInfoMethodName(typeMetadata)}}({{JsonSerializerOptionsTypeRef}} {{OptionsLocalVariableName}}) + { + if (!{{TryGetTypeInfoForRuntimeCustomConverterMethodName}}<{{typeFQN}}>({{OptionsLocalVariableName}}, out {{typeInfoFQN}} {{JsonTypeInfoLocalVariableName}})) + { + """); + + writer.Indentation += 2; + } + + private static void GenerateTypeInfoFactoryFooter(SourceWriter writer) + { + writer.Indentation -= 2; + + // NB OriginatingResolver should be the last property set by the source generator. + writer.WriteLine($$""" + } + + {{JsonTypeInfoLocalVariableName}}.{{OriginatingResolverPropertyName}} = this; + return {{JsonTypeInfoLocalVariableName}}; + } + """); + } + + private static SourceText GetRootJsonContextImplementation(ContextGenerationSpec contextSpec, bool emitGetConverterForNullablePropertyMethod) + { + string contextTypeRef = contextSpec.ContextType.FullyQualifiedName; + string contextTypeName = contextSpec.ContextType.Name; + + if (contextSpec.ContextType.TypeKind == TypeKind.Interface) + { + var newName = contextTypeName.Substring(1)+"ExtensionsJsonSerializerContext"; + var oldName = contextTypeName; + + contextTypeRef = contextTypeRef.Remove(contextTypeRef.LastIndexOf(".")); + + contextTypeRef = $"{contextTypeRef}.{newName}"; + contextTypeName = newName; + } + int backTickIndex = contextTypeName.IndexOf('`'); + if (backTickIndex != -1) + { + contextTypeName = contextTypeName.Substring(0, backTickIndex); + } + + SourceWriter writer = CreateSourceWriterWithContextHeader(contextSpec, isPrimaryContextSourceFile: true); + + GetLogicForDefaultSerializerOptionsInit(contextSpec.GeneratedOptionsSpec, writer); + + writer.WriteLine($""" + + private const global::System.Reflection.BindingFlags {InstanceMemberBindingFlagsVariableName} = + global::System.Reflection.BindingFlags.Instance | + global::System.Reflection.BindingFlags.Public | + global::System.Reflection.BindingFlags.NonPublic; + + """); + + writer.WriteLine($$""" + /// + /// The default associated with a default instance. + /// + public static {{contextTypeRef}} Default { get; } = new {{contextTypeRef}}(new {{JsonSerializerOptionsTypeRef}}({{DefaultOptionsStaticVarName}})); + + /// + /// The source-generated options associated with this context. + /// + protected override {{JsonSerializerOptionsTypeRef}}? GeneratedSerializerOptions { get; } = {{DefaultOptionsStaticVarName}}; + + /// + public {{contextTypeName}}() : base(null) + { + } + + /// + public {{contextTypeName}}({{JsonSerializerOptionsTypeRef}} {{OptionsLocalVariableName}}) : base({{OptionsLocalVariableName}}) + { + } + """); + + writer.WriteLine(); + + GenerateConverterHelpers(writer, emitGetConverterForNullablePropertyMethod); + + return CompleteSourceFileAndReturnText(writer); + } + + private static void GetLogicForDefaultSerializerOptionsInit(SourceGenerationOptionsSpec? optionsSpec, SourceWriter writer) + { + const string DefaultOptionsFieldDecl = $"private readonly static {JsonSerializerOptionsTypeRef} {DefaultOptionsStaticVarName}"; + + if (optionsSpec is null) + { + writer.WriteLine($"{DefaultOptionsFieldDecl} = new();"); + return; + } + + if (optionsSpec.Defaults is JsonSerializerDefaults defaults) + { + writer.WriteLine($"{DefaultOptionsFieldDecl} = new({FormatJsonSerializerDefaults(defaults)})"); + } + else + { + writer.WriteLine($"{DefaultOptionsFieldDecl} = new()"); + } + + writer.WriteLine('{'); + writer.Indentation++; + + if (optionsSpec.AllowOutOfOrderMetadataProperties is bool allowOutOfOrderMetadataProperties) + writer.WriteLine($"AllowOutOfOrderMetadataProperties = {FormatBoolLiteral(allowOutOfOrderMetadataProperties)},"); + + if (optionsSpec.AllowTrailingCommas is bool allowTrailingCommas) + writer.WriteLine($"AllowTrailingCommas = {FormatBoolLiteral(allowTrailingCommas)},"); + + if (optionsSpec.Converters is { Count: > 0 } converters) + { + writer.WriteLine("Converters ="); + writer.WriteLine('{'); + writer.Indentation++; + + foreach (TypeRef converter in converters) + { + writer.WriteLine($"new {converter.FullyQualifiedName}(),"); + } + + writer.Indentation--; + writer.WriteLine("},"); + } + + if (optionsSpec.DefaultBufferSize is int defaultBufferSize) + writer.WriteLine($"DefaultBufferSize = {defaultBufferSize},"); + + if (optionsSpec.DefaultIgnoreCondition is JsonIgnoreCondition defaultIgnoreCondition) + writer.WriteLine($"DefaultIgnoreCondition = {FormatIgnoreCondition(defaultIgnoreCondition)},"); + + if (optionsSpec.DictionaryKeyPolicy is JsonKnownNamingPolicy dictionaryKeyPolicy) + writer.WriteLine($"DictionaryKeyPolicy = {FormatNamingPolicy(dictionaryKeyPolicy)},"); + + if (optionsSpec.RespectNullableAnnotations is bool respectNullableAnnotations) + writer.WriteLine($"RespectNullableAnnotations = {FormatBoolLiteral(respectNullableAnnotations)},"); + + if (optionsSpec.RespectRequiredConstructorParameters is bool respectRequiredConstructorParameters) + writer.WriteLine($"RespectRequiredConstructorParameters = {FormatBoolLiteral(respectRequiredConstructorParameters)},"); + + if (optionsSpec.IgnoreReadOnlyFields is bool ignoreReadOnlyFields) + writer.WriteLine($"IgnoreReadOnlyFields = {FormatBoolLiteral(ignoreReadOnlyFields)},"); + + if (optionsSpec.IgnoreReadOnlyProperties is bool ignoreReadOnlyProperties) + writer.WriteLine($"IgnoreReadOnlyProperties = {FormatBoolLiteral(ignoreReadOnlyProperties)},"); + + if (optionsSpec.IncludeFields is bool includeFields) + writer.WriteLine($"IncludeFields = {FormatBoolLiteral(includeFields)},"); + + if (optionsSpec.MaxDepth is int maxDepth) + writer.WriteLine($"MaxDepth = {maxDepth},"); + + if (optionsSpec.NewLine is string newLine) + writer.WriteLine($"NewLine = {FormatStringLiteral(newLine)},"); + + if (optionsSpec.NumberHandling is JsonNumberHandling numberHandling) + writer.WriteLine($"NumberHandling = {FormatNumberHandling(numberHandling)},"); + + if (optionsSpec.PreferredObjectCreationHandling is JsonObjectCreationHandling preferredObjectCreationHandling) + writer.WriteLine($"PreferredObjectCreationHandling = {FormatObjectCreationHandling(preferredObjectCreationHandling)},"); + + if (optionsSpec.PropertyNameCaseInsensitive is bool propertyNameCaseInsensitive) + writer.WriteLine($"PropertyNameCaseInsensitive = {FormatBoolLiteral(propertyNameCaseInsensitive)},"); + + if (optionsSpec.PropertyNamingPolicy is JsonKnownNamingPolicy propertyNamingPolicy) + writer.WriteLine($"PropertyNamingPolicy = {FormatNamingPolicy(propertyNamingPolicy)},"); + + if (optionsSpec.ReadCommentHandling is JsonCommentHandling readCommentHandling) + writer.WriteLine($"ReadCommentHandling = {FormatCommentHandling(readCommentHandling)},"); + + if (optionsSpec.ReferenceHandler is JsonKnownReferenceHandler referenceHandler) + writer.WriteLine($"ReferenceHandler = {FormatReferenceHandler(referenceHandler)},"); + + if (optionsSpec.UnknownTypeHandling is JsonUnknownTypeHandling unknownTypeHandling) + writer.WriteLine($"UnknownTypeHandling = {FormatUnknownTypeHandling(unknownTypeHandling)},"); + + if (optionsSpec.UnmappedMemberHandling is JsonUnmappedMemberHandling unmappedMemberHandling) + writer.WriteLine($"UnmappedMemberHandling = {FormatUnmappedMemberHandling(unmappedMemberHandling)},"); + + if (optionsSpec.WriteIndented is bool writeIndented) + writer.WriteLine($"WriteIndented = {FormatBoolLiteral(writeIndented)},"); + + if (optionsSpec.IndentCharacter is char indentCharacter) + writer.WriteLine($"IndentCharacter = {FormatCharLiteral(indentCharacter)},"); + + if (optionsSpec.IndentSize is int indentSize) + writer.WriteLine($"IndentSize = {indentSize},"); + + writer.Indentation--; + writer.WriteLine("};"); + + static string FormatNamingPolicy(JsonKnownNamingPolicy knownNamingPolicy) + { + string? policyName = knownNamingPolicy switch + { + JsonKnownNamingPolicy.CamelCase => "CamelCase", + JsonKnownNamingPolicy.SnakeCaseLower => "SnakeCaseLower", + JsonKnownNamingPolicy.SnakeCaseUpper => "SnakeCaseUpper", + JsonKnownNamingPolicy.KebabCaseLower => "KebabCaseLower", + JsonKnownNamingPolicy.KebabCaseUpper => "KebabCaseUpper", + _ => null, + }; + + return policyName != null + ? $"{JsonNamingPolicyTypeRef}.{policyName}" + : "null"; + } + + static string FormatReferenceHandler(JsonKnownReferenceHandler referenceHandler) + { + string? referenceHandlerName = referenceHandler switch + { + JsonKnownReferenceHandler.Preserve => PreserveReferenceHandlerPropertyName, + JsonKnownReferenceHandler.IgnoreCycles => IgnoreCyclesReferenceHandlerPropertyName, + _ => null, + }; + + return referenceHandlerName != null + ? $"{ReferenceHandlerTypeRef}.{referenceHandlerName}" + : "null"; + } + } + + private static void GenerateConverterHelpers(SourceWriter writer, bool emitGetConverterForNullablePropertyMethod) + { + // The generic type parameter could capture type parameters from containing types, + // so use a name that is unlikely to be used. + const string TypeParameter = "TJsonMetadataType"; + + writer.WriteLine($$""" + private static bool {{TryGetTypeInfoForRuntimeCustomConverterMethodName}}<{{TypeParameter}}>({{JsonSerializerOptionsTypeRef}} options, out {{JsonTypeInfoTypeRef}}<{{TypeParameter}}> jsonTypeInfo) + { + {{JsonConverterTypeRef}}? converter = GetRuntimeConverterForType(typeof({{TypeParameter}}), options); + if (converter != null) + { + jsonTypeInfo = {{JsonMetadataServicesTypeRef}}.{{CreateValueInfoMethodName}}<{{TypeParameter}}>(options, converter); + return true; + } + + jsonTypeInfo = null; + return false; + } + + private static {{JsonConverterTypeRef}}? GetRuntimeConverterForType({{TypeTypeRef}} type, {{JsonSerializerOptionsTypeRef}} options) + { + for (int i = 0; i < options.Converters.Count; i++) + { + {{JsonConverterTypeRef}}? converter = options.Converters[i]; + if (converter?.CanConvert(type) == true) + { + return {{ExpandConverterMethodName}}(type, converter, options, validateCanConvert: false); + } + } + + return null; + } + + private static {{JsonConverterTypeRef}} {{ExpandConverterMethodName}}({{TypeTypeRef}} type, {{JsonConverterTypeRef}} converter, {{JsonSerializerOptionsTypeRef}} options, bool validateCanConvert = true) + { + if (validateCanConvert && !converter.CanConvert(type)) + { + throw new {{InvalidOperationExceptionTypeRef}}(string.Format("{{ExceptionMessages.IncompatibleConverterType}}", converter.GetType(), type)); + } + + if (converter is {{JsonConverterFactoryTypeRef}} factory) + { + converter = factory.CreateConverter(type, options); + if (converter is null || converter is {{JsonConverterFactoryTypeRef}}) + { + throw new {{InvalidOperationExceptionTypeRef}}(string.Format("{{ExceptionMessages.InvalidJsonConverterFactoryOutput}}", factory.GetType())); + } + } + + return converter; + } + """); + + if (emitGetConverterForNullablePropertyMethod) + { + writer.WriteLine($$""" + + private static {{JsonConverterTypeRef}}<{{TypeParameter}}?> {{GetConverterForNullablePropertyMethodName}}<{{TypeParameter}}>({{JsonConverterTypeRef}} converter, {{JsonSerializerOptionsTypeRef}} options) + where {{TypeParameter}} : struct + { + if (converter.CanConvert(typeof({{TypeParameter}}?))) + { + return ({{JsonConverterTypeRef}}<{{TypeParameter}}?>){{ExpandConverterMethodName}}(typeof({{TypeParameter}}?), converter, options, validateCanConvert: false); + } + + converter = {{ExpandConverterMethodName}}(typeof({{TypeParameter}}), converter, options); + {{JsonTypeInfoTypeRef}}<{{TypeParameter}}> typeInfo = {{JsonMetadataServicesTypeRef}}.{{CreateValueInfoMethodName}}<{{TypeParameter}}>(options, converter); + return {{JsonMetadataServicesTypeRef}}.GetNullableConverter<{{TypeParameter}}>(typeInfo); + } + """); + } + } + + private static SourceText GetGetTypeInfoImplementation(ContextGenerationSpec contextSpec) + { + SourceWriter writer = CreateSourceWriterWithContextHeader(contextSpec, interfaceImplementation: JsonTypeInfoResolverTypeRef); + + // JsonSerializerContext.GetTypeInfo override -- returns cached metadata via JsonSerializerOptions + writer.WriteLine($$""" + /// + public override {{JsonTypeInfoTypeRef}}? GetTypeInfo({{TypeTypeRef}} type) + { + {{OptionsInstanceVariableName}}.TryGetTypeInfo(type, out {{JsonTypeInfoTypeRef}}? typeInfo); + return typeInfo; + } + """); + + writer.WriteLine(); + + // Explicit IJsonTypeInfoResolver implementation -- the source of truth for metadata resolution + writer.WriteLine($"{JsonTypeInfoTypeRef}? {JsonTypeInfoResolverTypeRef}.GetTypeInfo({TypeTypeRef} type, {JsonSerializerOptionsTypeRef} {OptionsLocalVariableName})"); + writer.WriteLine('{'); + writer.Indentation++; + + foreach (TypeGenerationSpec metadata in contextSpec.GeneratedTypes) + { + if (metadata.ClassType != ClassType.TypeUnsupportedBySourceGen) + { + writer.WriteLine($$""" + if (type == typeof({{metadata.TypeRef.FullyQualifiedName}})) + { + return {{CreateTypeInfoMethodName(metadata)}}({{OptionsLocalVariableName}}); + } + """); + } + } + + writer.WriteLine("return null;"); + + writer.Indentation--; + writer.WriteLine('}'); + + return CompleteSourceFileAndReturnText(writer); + } + + private SourceText GetPropertyNameInitialization(ContextGenerationSpec contextSpec) + { + SourceWriter writer = CreateSourceWriterWithContextHeader(contextSpec); + + foreach (KeyValuePair name_varName_pair in _propertyNames) + { + writer.WriteLine($$"""private static readonly {{JsonEncodedTextTypeRef}} {{name_varName_pair.Value}} = {{JsonEncodedTextTypeRef}}.Encode({{FormatStringLiteral(name_varName_pair.Key)}});"""); + } + + return CompleteSourceFileAndReturnText(writer); + } + + private static string FormatNumberHandling(JsonNumberHandling? numberHandling) + => numberHandling.HasValue + ? SourceGeneratorHelpers.FormatEnumLiteral(JsonNumberHandlingTypeRef, numberHandling.Value) + : "null"; + + private static string FormatObjectCreationHandling(JsonObjectCreationHandling creationHandling) + => SourceGeneratorHelpers.FormatEnumLiteral(JsonObjectCreationHandlingTypeRef, creationHandling); + + private static string FormatUnmappedMemberHandling(JsonUnmappedMemberHandling unmappedMemberHandling) + => SourceGeneratorHelpers.FormatEnumLiteral(JsonUnmappedMemberHandlingTypeRef, unmappedMemberHandling); + + private static string FormatCommentHandling(JsonCommentHandling commentHandling) + => SourceGeneratorHelpers.FormatEnumLiteral(JsonCommentHandlingTypeRef, commentHandling); + + private static string FormatUnknownTypeHandling(JsonUnknownTypeHandling commentHandling) + => SourceGeneratorHelpers.FormatEnumLiteral(JsonUnknownTypeHandlingTypeRef, commentHandling); + + private static string FormatIgnoreCondition(JsonIgnoreCondition ignoreCondition) + => SourceGeneratorHelpers.FormatEnumLiteral(JsonIgnoreConditionTypeRef, ignoreCondition); + + private static string FormatJsonSerializerDefaults(JsonSerializerDefaults defaults) + => SourceGeneratorHelpers.FormatEnumLiteral(JsonSerializerDefaultsTypeRef, defaults); + + private static string GetCreateValueInfoMethodRef(string typeCompilableName) => $"{CreateValueInfoMethodName}<{typeCompilableName}>"; + + private static string FormatBoolLiteral(bool value) => value ? "true" : "false"; + private static string FormatStringLiteral(string? value) => value is null ? "null" : SymbolDisplay.FormatLiteral(value, quote: true); + private static string FormatCharLiteral(char value) => SymbolDisplay.FormatLiteral(value, quote: true); + + /// + /// Method used to generate JsonTypeInfo given options instance + /// + private static string CreateTypeInfoMethodName(TypeGenerationSpec typeSpec) + => $"Create_{typeSpec.TypeInfoPropertyName}"; + + + private static string FormatDefaultConstructorExpr(TypeGenerationSpec typeSpec) + { + return typeSpec switch + { + { RuntimeTypeRef: TypeRef runtimeType } => $"() => new {runtimeType.FullyQualifiedName}()", + { IsValueTuple: true } => $"() => default({typeSpec.TypeRef.FullyQualifiedName})", + { ConstructionStrategy: ObjectConstructionStrategy.ParameterlessConstructor } => $"() => new {typeSpec.TypeRef.FullyQualifiedName}()", + _ => "null", + }; + } + + private static string GetCollectionInfoMethodName(CollectionType collectionType) + { + return collectionType switch + { + CollectionType.Array => "CreateArrayInfo", + CollectionType.List => "CreateListInfo", + CollectionType.IListOfT or CollectionType.IList => "CreateIListInfo", + CollectionType.ICollectionOfT => "CreateICollectionInfo", + CollectionType.IEnumerableOfT or CollectionType.IEnumerable => "CreateIEnumerableInfo", + CollectionType.StackOfT or CollectionType.Stack => "CreateStackInfo", + CollectionType.QueueOfT or CollectionType.Queue => "CreateQueueInfo", + CollectionType.ConcurrentStack => "CreateConcurrentStackInfo", + CollectionType.ConcurrentQueue => "CreateConcurrentQueueInfo", + CollectionType.ImmutableEnumerable => "CreateImmutableEnumerableInfo", + CollectionType.IAsyncEnumerableOfT => "CreateIAsyncEnumerableInfo", + CollectionType.MemoryOfT => "CreateMemoryInfo", + CollectionType.ReadOnlyMemoryOfT => "CreateReadOnlyMemoryInfo", + CollectionType.ISet => "CreateISetInfo", + + CollectionType.Dictionary => "CreateDictionaryInfo", + CollectionType.IDictionaryOfTKeyTValue or CollectionType.IDictionary => "CreateIDictionaryInfo", + CollectionType.IReadOnlyDictionary => "CreateIReadOnlyDictionaryInfo", + CollectionType.ImmutableDictionary => "CreateImmutableDictionaryInfo", + + _ => throw new Exception(), + }; + } + + private static bool ShouldGenerateMetadata(TypeGenerationSpec typeSpec) + => IsGenerationModeSpecified(typeSpec, JsonSourceGenerationMode.Metadata); + + private static bool ShouldGenerateSerializationLogic(TypeGenerationSpec typeSpec) + => IsGenerationModeSpecified(typeSpec, JsonSourceGenerationMode.Serialization) && typeSpec.IsFastPathSupported(); + + private static bool IsGenerationModeSpecified(TypeGenerationSpec typeSpec, JsonSourceGenerationMode mode) + => typeSpec.GenerationMode == JsonSourceGenerationMode.Default || (mode & typeSpec.GenerationMode) != 0; + } + } +} diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/JsonSourceGenerator.Parser.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/JsonSourceGenerator.Parser.cs new file mode 100644 index 0000000..afdbb84 --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/JsonSourceGenerator.Parser.cs @@ -0,0 +1,2394 @@ +using System.Collections.Immutable; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Text; +using CSharpToJsonSchema.Generators.JsonGen.Base; +using CSharpToJsonSchema.Generators.JsonGen.Helpers; + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +using CSharpToJsonSchema.Generators.JsonGen.Model; +using CSharpToJsonSchema.Generators.JsonGen.System.Text.Json; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Text; +using CSharpToJsonSchema.Generators.Conversion; +using CSharpToJsonSchema.Generators.JsonGen.Base; +using CSharpToJsonSchema.Generators.JsonGen.Helpers; +using CSharpToJsonSchema.Generators.JsonGen.Model; +using CSharpToJsonSchema.Generators.JsonGen.System.Text.Json; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace CSharpToJsonSchema.Generators.JsonGen +{ + public sealed partial class JsonSourceGenerator + { + // The source generator requires NRT and init-only property support. + private const LanguageVersion MinimumSupportedLanguageVersion = LanguageVersion.CSharp9; + + private sealed class Parser + { + private const string SystemTextJsonNamespace = "System.Text.Json"; + + private const string JsonExtensionDataAttributeFullName = + "System.Text.Json.Serialization.JsonExtensionDataAttribute"; + + private const string JsonIgnoreAttributeFullName = "System.Text.Json.Serialization.JsonIgnoreAttribute"; + private const string JsonIgnoreConditionFullName = "System.Text.Json.Serialization.JsonIgnoreCondition"; + private const string JsonIncludeAttributeFullName = "System.Text.Json.Serialization.JsonIncludeAttribute"; + + private const string JsonNumberHandlingAttributeFullName = + "System.Text.Json.Serialization.JsonNumberHandlingAttribute"; + + private const string JsonObjectCreationHandlingAttributeFullName = + "System.Text.Json.Serialization.JsonObjectCreationHandlingAttribute"; + + private const string JsonPropertyNameAttributeFullName = + "System.Text.Json.Serialization.JsonPropertyNameAttribute"; + + private const string JsonPropertyOrderAttributeFullName = + "System.Text.Json.Serialization.JsonPropertyOrderAttribute"; + + private const string JsonRequiredAttributeFullName = "System.Text.Json.Serialization.JsonRequiredAttribute"; + + internal const string JsonSerializableAttributeFullName = + "System.Text.Json.Serialization.JsonSerializableAttribute"; + + private readonly KnownTypeSymbols _knownSymbols; + private readonly bool _compilationContainsCoreJsonTypes; + + // Keeps track of generated context type names + private readonly HashSet<(string ContextName, string TypeName)> _generatedContextAndTypeNames = new(); + + private readonly HashSet _builtInSupportTypes; + private readonly Queue _typesToGenerate = new(); +#pragma warning disable RS1024 // Compare symbols correctly https://github.com/dotnet/roslyn-analyzers/issues/5804 + private readonly Dictionary _generatedTypes = + new(SymbolEqualityComparer.Default); +#pragma warning restore + + public List Diagnostics { get; } = new(); + private Location? _contextClassLocation; + + public void ReportDiagnostic(DiagnosticDescriptor descriptor, Location? location, + params object?[]? messageArgs) + { + // Debug.Assert(_contextClassLocation != null); + // + // if (location is null || !_knownSymbols.Compilation.ContainsLocation(location)) + // { + // // If location is null or is a location outside of the current compilation, fall back to the location of the context class. + // location = _contextClassLocation; + // } + // + // Diagnostics.Add(DiagnosticInfo.Create(descriptor, location, messageArgs)); + } + + public Parser(KnownTypeSymbols knownSymbols) + { + _knownSymbols = knownSymbols; + _compilationContainsCoreJsonTypes = + knownSymbols.JsonSerializerContextType != null && + knownSymbols.JsonSerializableAttributeType != null && + knownSymbols.JsonSourceGenerationOptionsAttributeType != null && + knownSymbols.JsonConverterType != null; + + _builtInSupportTypes = (knownSymbols.BuiltInSupportTypes ??= CreateBuiltInSupportTypeSet(knownSymbols)); + } + + public ContextGenerationSpec? ParseContextGenerationSpec(InterfaceDeclarationSyntax contextClassDeclaration, + SemanticModel semanticModel, CancellationToken cancellationToken) + { + if (!_compilationContainsCoreJsonTypes) + { + return null; + } + + Debug.Assert(_knownSymbols.JsonSerializerContextType != null); + + // Ensure context-scoped metadata caches are empty. + Debug.Assert(_typesToGenerate.Count == 0); + Debug.Assert(_generatedTypes.Count == 0); + Debug.Assert(_contextClassLocation is null); + + INamedTypeSymbol? contextTypeSymbol = + semanticModel.GetDeclaredSymbol(contextClassDeclaration, cancellationToken); + Debug.Assert(contextTypeSymbol != null); + + _contextClassLocation = contextTypeSymbol.GetLocation(); + Debug.Assert(_contextClassLocation is not null); + + // if (!_knownSymbols.JsonSerializerContextType.IsAssignableFrom(contextTypeSymbol)) + // { + // ReportDiagnostic(DiagnosticDescriptors.JsonSerializableAttributeOnNonContextType, _contextClassLocation, contextTypeSymbol.ToDisplayString()); + // return null; + // } + + ParseJsonSerializerContextAttributes(contextTypeSymbol, + semanticModel.Compilation, + out List? rootSerializableTypes, + out SourceGenerationOptionsSpec? options ); + + if (rootSerializableTypes is null) + { + // No types were annotated with JsonSerializableAttribute. + // Can only be reached if a [JsonSerializable(null)] declaration has been made. + // Do not emit a diagnostic since a NRT warning will also be emitted. + return null; + } + + Debug.Assert(rootSerializableTypes.Count > 0); + + LanguageVersion? langVersion = _knownSymbols.Compilation.GetLanguageVersion(); + if (langVersion is null or < MinimumSupportedLanguageVersion) + { + // Unsupported lang version should be the first (and only) diagnostic emitted by the generator. + ReportDiagnostic( + global::CSharpToJsonSchema.Generators.JsonGen.JsonSourceGenerator.DiagnosticDescriptors + .JsonUnsupportedLanguageVersion, _contextClassLocation, langVersion?.ToDisplayString(), + MinimumSupportedLanguageVersion.ToDisplayString()); + return null; + } + + if (!TryGetNestedTypeDeclarations(contextClassDeclaration, semanticModel, cancellationToken, + out List? classDeclarationList)) + { + // Class or one of its containing types is not partial so we can't add to it. + ReportDiagnostic( + global::CSharpToJsonSchema.Generators.JsonGen.JsonSourceGenerator.DiagnosticDescriptors + .ContextClassesMustBePartial, _contextClassLocation, contextTypeSymbol.Name); + return null; + } + + // Enqueue attribute data for spec generation + foreach (TypeToGenerate rootSerializableType in rootSerializableTypes) + { + _typesToGenerate.Enqueue(rootSerializableType); + } + + // Walk the transitive type graph generating specs for every encountered type. + while (_typesToGenerate.Count > 0) + { + cancellationToken.ThrowIfCancellationRequested(); + TypeToGenerate typeToGenerate = _typesToGenerate.Dequeue(); + if (!_generatedTypes.ContainsKey(typeToGenerate.Type)) + { + TypeGenerationSpec spec = ParseTypeGenerationSpec(typeToGenerate, contextTypeSymbol, options); + _generatedTypes.Add(typeToGenerate.Type, spec); + } + } + + Debug.Assert(_generatedTypes.Count > 0); + + ContextGenerationSpec contextGenSpec = new() + { + ContextType = new(contextTypeSymbol), + GeneratedTypes = _generatedTypes.Values.OrderBy(t => t.TypeRef.FullyQualifiedName) + .ToImmutableEquatableArray(), + Namespace = contextTypeSymbol.ContainingNamespace is { IsGlobalNamespace: false } ns + ? ns.ToDisplayString() + : null, + ContextClassDeclarations = classDeclarationList.ToImmutableEquatableArray(), + GeneratedOptionsSpec = options, + }; + + // Clear the caches of generated metadata between the processing of context classes. + _generatedTypes.Clear(); + _typesToGenerate.Clear(); + _contextClassLocation = null; + return contextGenSpec; + } + public ContextGenerationSpec? ParseContextGenerationSpec(ClassDeclarationSyntax contextClassDeclaration, + SemanticModel semanticModel, CancellationToken cancellationToken) + { + if (!_compilationContainsCoreJsonTypes) + { + return null; + } + + Debug.Assert(_knownSymbols.JsonSerializerContextType != null); + + // Ensure context-scoped metadata caches are empty. + Debug.Assert(_typesToGenerate.Count == 0); + Debug.Assert(_generatedTypes.Count == 0); + Debug.Assert(_contextClassLocation is null); + + INamedTypeSymbol? contextTypeSymbol = + semanticModel.GetDeclaredSymbol(contextClassDeclaration, cancellationToken); + Debug.Assert(contextTypeSymbol != null); + + _contextClassLocation = contextTypeSymbol.GetLocation(); + Debug.Assert(_contextClassLocation is not null); + + // if (!_knownSymbols.JsonSerializerContextType.IsAssignableFrom(contextTypeSymbol)) + // { + // ReportDiagnostic(DiagnosticDescriptors.JsonSerializableAttributeOnNonContextType, _contextClassLocation, contextTypeSymbol.ToDisplayString()); + // return null; + // } + + ParseJsonSerializerContextAttributes(contextTypeSymbol, + out List? rootSerializableTypes, + out SourceGenerationOptionsSpec? options); + + if (rootSerializableTypes is null) + { + // No types were annotated with JsonSerializableAttribute. + // Can only be reached if a [JsonSerializable(null)] declaration has been made. + // Do not emit a diagnostic since a NRT warning will also be emitted. + return null; + } + + Debug.Assert(rootSerializableTypes.Count > 0); + + LanguageVersion? langVersion = _knownSymbols.Compilation.GetLanguageVersion(); + if (langVersion is null or < MinimumSupportedLanguageVersion) + { + // Unsupported lang version should be the first (and only) diagnostic emitted by the generator. + ReportDiagnostic( + global::CSharpToJsonSchema.Generators.JsonGen.JsonSourceGenerator.DiagnosticDescriptors + .JsonUnsupportedLanguageVersion, _contextClassLocation, langVersion?.ToDisplayString(), + MinimumSupportedLanguageVersion.ToDisplayString()); + return null; + } + + if (!TryGetNestedTypeDeclarations(contextClassDeclaration, semanticModel, cancellationToken, + out List? classDeclarationList)) + { + // Class or one of its containing types is not partial so we can't add to it. + ReportDiagnostic( + global::CSharpToJsonSchema.Generators.JsonGen.JsonSourceGenerator.DiagnosticDescriptors + .ContextClassesMustBePartial, _contextClassLocation, contextTypeSymbol.Name); + return null; + } + + // Enqueue attribute data for spec generation + foreach (TypeToGenerate rootSerializableType in rootSerializableTypes) + { + _typesToGenerate.Enqueue(rootSerializableType); + } + + // Walk the transitive type graph generating specs for every encountered type. + while (_typesToGenerate.Count > 0) + { + cancellationToken.ThrowIfCancellationRequested(); + TypeToGenerate typeToGenerate = _typesToGenerate.Dequeue(); + if (!_generatedTypes.ContainsKey(typeToGenerate.Type)) + { + TypeGenerationSpec spec = ParseTypeGenerationSpec(typeToGenerate, contextTypeSymbol, options); + _generatedTypes.Add(typeToGenerate.Type, spec); + } + } + + Debug.Assert(_generatedTypes.Count > 0); + + ContextGenerationSpec contextGenSpec = new() + { + ContextType = new(contextTypeSymbol), + GeneratedTypes = _generatedTypes.Values.OrderBy(t => t.TypeRef.FullyQualifiedName) + .ToImmutableEquatableArray(), + Namespace = contextTypeSymbol.ContainingNamespace is { IsGlobalNamespace: false } ns + ? ns.ToDisplayString() + : null, + ContextClassDeclarations = classDeclarationList.ToImmutableEquatableArray(), + GeneratedOptionsSpec = options, + }; + + // Clear the caches of generated metadata between the processing of context classes. + _generatedTypes.Clear(); + _typesToGenerate.Clear(); + _contextClassLocation = null; + return contextGenSpec; + } + private static bool TryGetNestedTypeDeclarations(InterfaceDeclarationSyntax contextClassSyntax, + SemanticModel semanticModel, CancellationToken cancellationToken, + [NotNullWhen(true)] out List? typeDeclarations) + { + typeDeclarations = null; + + for (TypeDeclarationSyntax? currentType = contextClassSyntax; + currentType != null; + currentType = currentType.Parent as TypeDeclarationSyntax) + { + StringBuilder stringBuilder = new(); + bool isPartialType = false; + bool isInterface = currentType is InterfaceDeclarationSyntax; + foreach (SyntaxToken modifier in currentType.Modifiers) + { + stringBuilder.Append(modifier.Text); + stringBuilder.Append(' '); + isPartialType |= modifier.IsKind(SyntaxKind.PartialKeyword); + } + + if (!isPartialType && !isInterface) + { + typeDeclarations = null; + return false; + } + + var kind = currentType.GetTypeKindKeyword(); + if(isInterface) + kind = "partial class"; + + stringBuilder.Append(kind); + stringBuilder.Append(' '); + + INamedTypeSymbol? typeSymbol = semanticModel.GetDeclaredSymbol(currentType, cancellationToken); + Debug.Assert(typeSymbol != null); + + string typeName = (typeSymbol.TypeKind == TypeKind.Interface)? typeSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat).Substring(1)+"ExtensionsJsonSerializerContext":typeSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat); + stringBuilder.Append(typeName); + + (typeDeclarations ??= new()).Add(stringBuilder.ToString()); + } + + Debug.Assert(typeDeclarations?.Count > 0); + return true; + } + + // private static bool TryGetNestedTypeDeclarations(InterfaceDeclarationSyntax contextClassSyntax, + // SemanticModel semanticModel, CancellationToken cancellationToken, + // [NotNullWhen(true)] out List? typeDeclarations) + // { + // typeDeclarations = null; + // + // for (TypeDeclarationSyntax? currentType = contextClassSyntax; + // currentType != null; + // currentType = currentType.Parent as TypeDeclarationSyntax) + // { + // StringBuilder stringBuilder = new(); + // bool isPartialType = false; + // + // foreach (SyntaxToken modifier in currentType.Modifiers) + // { + // stringBuilder.Append(modifier.Text); + // stringBuilder.Append(' '); + // isPartialType |= modifier.IsKind(SyntaxKind.PartialKeyword); + // } + // + // if (!isPartialType) + // { + // typeDeclarations = null; + // return false; + // } + // + // stringBuilder.Append(currentType.GetTypeKindKeyword()); + // stringBuilder.Append(' '); + // + // INamedTypeSymbol? typeSymbol = semanticModel.GetDeclaredSymbol(currentType, cancellationToken); + // Debug.Assert(typeSymbol != null); + // + // string typeName = typeSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)+"JsonSerializerContext"; + // stringBuilder.Append(typeName); + // + // (typeDeclarations ??= new()).Add(stringBuilder.ToString()); + // } + // + // Debug.Assert(typeDeclarations?.Count > 0); + // return true; + // } + private static bool TryGetNestedTypeDeclarations(ClassDeclarationSyntax contextClassSyntax, + SemanticModel semanticModel, CancellationToken cancellationToken, + [NotNullWhen(true)] out List? typeDeclarations) + { + typeDeclarations = null; + + for (TypeDeclarationSyntax? currentType = contextClassSyntax; + currentType != null; + currentType = currentType.Parent as TypeDeclarationSyntax) + { + StringBuilder stringBuilder = new(); + bool isPartialType = false; + + foreach (SyntaxToken modifier in currentType.Modifiers) + { + stringBuilder.Append(modifier.Text); + stringBuilder.Append(' '); + isPartialType |= modifier.IsKind(SyntaxKind.PartialKeyword); + } + + if (!isPartialType) + { + typeDeclarations = null; + return false; + } + + stringBuilder.Append(currentType.GetTypeKindKeyword()); + stringBuilder.Append(' '); + + INamedTypeSymbol? typeSymbol = semanticModel.GetDeclaredSymbol(currentType, cancellationToken); + Debug.Assert(typeSymbol != null); + + string typeName = typeSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)+"JsonSerializerContext"; + stringBuilder.Append(typeName); + + (typeDeclarations ??= new()).Add(stringBuilder.ToString()); + } + + Debug.Assert(typeDeclarations?.Count > 0); + return true; + } + + private TypeRef EnqueueType(ITypeSymbol type, JsonSourceGenerationMode? generationMode) + { + // Trim compile-time erased metadata such as tuple labels and NRT annotations. + type = _knownSymbols.Compilation.EraseCompileTimeMetadata(type); + + if (_generatedTypes.TryGetValue(type, out TypeGenerationSpec? spec)) + { + return spec.TypeRef; + } + + _typesToGenerate.Enqueue(new TypeToGenerate + { + Type = type, + Mode = generationMode, + TypeInfoPropertyName = null, + Location = type.GetLocation(), + AttributeLocation = null, + }); + + return new TypeRef(type); + } + private void ParseJsonSerializerContextAttributes( + + INamedTypeSymbol contextClassSymbol, + out List? rootSerializableTypes, + out SourceGenerationOptionsSpec? options) + { + Debug.Assert(_knownSymbols.JsonSerializableAttributeType != null); + Debug.Assert(_knownSymbols.JsonSourceGenerationOptionsAttributeType != null); + + rootSerializableTypes = null; + options = null; + + foreach (AttributeData attributeData in contextClassSymbol.GetAttributes()) + { + INamedTypeSymbol? attributeClass = attributeData.AttributeClass; + + if (SymbolEqualityComparer.Default.Equals(attributeClass, _knownSymbols.JsonSerializableAttributeType)) + { + TypeToGenerate? typeToGenerate = ParseJsonSerializableAttribute(attributeData); + if (typeToGenerate is null) + { + continue; + } + + (rootSerializableTypes ??= new()).Add(typeToGenerate.Value); + } + else if (SymbolEqualityComparer.Default.Equals(attributeClass, _knownSymbols.JsonSourceGenerationOptionsAttributeType)) + { + options = ParseJsonSourceGenerationOptionsAttribute(contextClassSymbol, attributeData); + } + } + + if (contextClassSymbol.TypeKind == TypeKind.Interface) + { + + // + } + } + + private void ParseJsonSerializerContextAttributes( + + INamedTypeSymbol contextClassSymbol, + Compilation compilation, + out List? rootSerializableTypes, + out SourceGenerationOptionsSpec? options) + { + Debug.Assert(_knownSymbols.JsonSerializableAttributeType != null); + Debug.Assert(_knownSymbols.JsonSourceGenerationOptionsAttributeType != null); + + rootSerializableTypes = null; + options = null; + + foreach (AttributeData attributeData in contextClassSymbol.GetAttributes()) + { + INamedTypeSymbol? attributeClass = attributeData.AttributeClass; + + if (SymbolEqualityComparer.Default.Equals(attributeClass, _knownSymbols.JsonSerializableAttributeType)) + { + TypeToGenerate? typeToGenerate = ParseJsonSerializableAttribute(attributeData); + if (typeToGenerate is null) + { + continue; + } + + (rootSerializableTypes ??= new()).Add(typeToGenerate.Value); + } + else if (SymbolEqualityComparer.Default.Equals(attributeClass, _knownSymbols.JsonSourceGenerationOptionsAttributeType)) + { + options = ParseJsonSourceGenerationOptionsAttribute(contextClassSymbol, attributeData); + } + } + + if (contextClassSymbol.TypeKind == TypeKind.Interface) + { + + foreach (var member in contextClassSymbol.GetMembers().OfType()) + { + var type = SymbolGenerator.GenerateParameterBasedClassSymbol( + contextClassSymbol.ContainingNamespace.ToDisplayString(), member, compilation); + + var typeToGenerate = new TypeToGenerate + { + Type = type, + Mode = null, + TypeInfoPropertyName = $"{member.Name}Args", + Location = type.GetLocation(), + AttributeLocation = null + }; + (rootSerializableTypes ??= new()).Add(typeToGenerate); + + if(member.ReturnsVoid || member.ReturnType.MetadataName == "Task")//, || member.ReturnsByRefReadonly) + continue; + if (member.ReturnType.BaseType?.Name == "Task") + { + var types = (member.ReturnType as INamedTypeSymbol).TypeArguments[0]; + var typeToGenerate3 = new TypeToGenerate + { + Type = types, + Mode = null, + TypeInfoPropertyName = $"{types.Name}", + Location = types.GetLocation(), + AttributeLocation = null + }; + (rootSerializableTypes ??= new()).Add(typeToGenerate3); + continue; + } + + var typeToGenerate2 = new TypeToGenerate + { + Type = member.ReturnType, + Mode = null, + TypeInfoPropertyName = $"{member.ReturnType.Name}", + Location = member.GetLocation(), + AttributeLocation = null + }; + (rootSerializableTypes ??= new()).Add(typeToGenerate2); + + } + + } + + options = new SourceGenerationOptionsSpec + { + GenerationMode = JsonSourceGenerationMode.Default, + Defaults = JsonSerializerDefaults.Web, + AllowOutOfOrderMetadataProperties = true, + AllowTrailingCommas = false, + DefaultBufferSize = 1024, + Converters = null, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + DictionaryKeyPolicy = null, + RespectNullableAnnotations = true, + RespectRequiredConstructorParameters = false, + IgnoreReadOnlyFields = false, + IgnoreReadOnlyProperties = false, + IncludeFields = false, + MaxDepth = 64, + NewLine = "\n", + NumberHandling = JsonNumberHandling.Strict, + PreferredObjectCreationHandling = JsonObjectCreationHandling.Replace, + PropertyNameCaseInsensitive = true, + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, + ReadCommentHandling = JsonCommentHandling.Disallow, + ReferenceHandler = null, + UnknownTypeHandling = JsonUnknownTypeHandling.JsonElement, + UnmappedMemberHandling = JsonUnmappedMemberHandling.Skip, + UseStringEnumConverter = true, + WriteIndented = false, + IndentCharacter = ' ', + IndentSize = 2, + }; + } + // private void ParseJsonSerializerContextAttributes( + // INamedTypeSymbol contextClassSymbol, + // out List? rootSerializableTypes, + // out SourceGenerationOptionsSpec? options) + // { + // Debug.Assert(_knownSymbols.JsonSerializableAttributeType != null); + // Debug.Assert(_knownSymbols.JsonSourceGenerationOptionsAttributeType != null); + // + // rootSerializableTypes = null; + // options = null; + // + // // foreach (AttributeData attributeData in contextClassSymbol.GetAttributes()) + // // { + // // INamedTypeSymbol? attributeClass = attributeData.AttributeClass; + // // + // // if (SymbolEqualityComparer.Default.Equals(attributeClass, + // // _knownSymbols.JsonSerializableAttributeType)) + // // { + // // TypeToGenerate? typeToGenerate = ParseJsonSerializableAttribute(attributeData); + // // if (typeToGenerate is null) + // // { + // // continue; + // // } + // // + // // (rootSerializableTypes ??= new()).Add(typeToGenerate.Value); + // // } + // // else if (SymbolEqualityComparer.Default.Equals(attributeClass, + // // _knownSymbols.JsonSourceGenerationOptionsAttributeType)) + // // { + // // options = ParseJsonSourceGenerationOptionsAttribute(contextClassSymbol, attributeData); + // // } + // // } + // + // rootSerializableTypes ??= new(); + // + // if (contextClassSymbol.TypeKind == TypeKind.Interface) + // { + // foreach (var symbol in contextClassSymbol.GetMembers().OfType()) + // { + // var symbol.Parameters[0].Type; + // } + // } + // else + // { + // rootSerializableTypes.Add(new TypeToGenerate() + // { + // Type = contextClassSymbol, + // Mode = null, + // TypeInfoPropertyName = contextClassSymbol.Name, + // Location = contextClassSymbol.Locations.FirstOrDefault(), + // AttributeLocation = null + // }); + // } + // + // // // Extract all properties of contextClassSymbol and add them to rootSerializableTypes + // // foreach (var member in contextClassSymbol.GetMembers()) + // // { + // // if (member is IPropertySymbol propertySymbol) + // // { + // // var typeToGenerate = new TypeToGenerate + // // { + // // Type = propertySymbol.Type, + // // Mode = null, + // // TypeInfoPropertyName = propertySymbol.Name, + // // Location = propertySymbol.Locations.FirstOrDefault(), + // // AttributeLocation = null + // // }; + // // (rootSerializableTypes ??= new()).Add(typeToGenerate); + // // } + // // } + // } + + private SourceGenerationOptionsSpec ParseJsonSourceGenerationOptionsAttribute(INamedTypeSymbol contextType, + AttributeData attributeData) + { + JsonSourceGenerationMode? generationMode = null; + List? converters = null; + JsonSerializerDefaults? defaults = null; + bool? allowOutOfOrderMetadataProperties = null; + bool? allowTrailingCommas = null; + int? defaultBufferSize = null; + JsonIgnoreCondition? defaultIgnoreCondition = null; + JsonKnownNamingPolicy? dictionaryKeyPolicy = null; + bool? respectNullableAnnotations = null; + bool? ignoreReadOnlyFields = null; + bool? respectRequiredConstructorParameters = null; + bool? ignoreReadOnlyProperties = null; + bool? includeFields = null; + int? maxDepth = null; + string? newLine = null; + JsonNumberHandling? numberHandling = null; + JsonObjectCreationHandling? preferredObjectCreationHandling = null; + bool? propertyNameCaseInsensitive = null; + JsonKnownNamingPolicy? propertyNamingPolicy = null; + JsonCommentHandling? readCommentHandling = null; + JsonKnownReferenceHandler? referenceHandler = null; + JsonUnknownTypeHandling? unknownTypeHandling = null; + JsonUnmappedMemberHandling? unmappedMemberHandling = null; + bool? useStringEnumConverter = null; + bool? writeIndented = null; + char? indentCharacter = null; + int? indentSize = null; + + if (attributeData.ConstructorArguments.Length > 0) + { + Debug.Assert(attributeData.ConstructorArguments.Length == 1 & + attributeData.ConstructorArguments[0].Type?.Name is nameof(JsonSerializerDefaults)); + defaults = (JsonSerializerDefaults)attributeData.ConstructorArguments[0].Value!; + } + + foreach (KeyValuePair namedArg in attributeData.NamedArguments) + { + switch (namedArg.Key) + { + case nameof(JsonSourceGenerationOptionsAttribute.AllowOutOfOrderMetadataProperties): + allowOutOfOrderMetadataProperties = (bool)namedArg.Value.Value!; + break; + + case nameof(JsonSourceGenerationOptionsAttribute.AllowTrailingCommas): + allowTrailingCommas = (bool)namedArg.Value.Value!; + break; + + case nameof(JsonSourceGenerationOptionsAttribute.Converters): + converters = new List(); + foreach (TypedConstant element in namedArg.Value.Values) + { + var converterType = (ITypeSymbol?)element.Value; + TypeRef? typeRef = GetConverterTypeFromAttribute(contextType, converterType, + contextType, attributeData); + if (typeRef != null) + { + converters.Add(typeRef); + } + } + + break; + + case nameof(JsonSourceGenerationOptionsAttribute.DefaultBufferSize): + defaultBufferSize = (int)namedArg.Value.Value!; + break; + + case nameof(JsonSourceGenerationOptionsAttribute.DefaultIgnoreCondition): + defaultIgnoreCondition = (JsonIgnoreCondition)namedArg.Value.Value!; + break; + + case nameof(JsonSourceGenerationOptionsAttribute.DictionaryKeyPolicy): + dictionaryKeyPolicy = (JsonKnownNamingPolicy)namedArg.Value.Value!; + break; + + case nameof(JsonSourceGenerationOptionsAttribute.RespectNullableAnnotations): + respectNullableAnnotations = (bool)namedArg.Value.Value!; + break; + + case nameof(JsonSourceGenerationOptionsAttribute.RespectRequiredConstructorParameters): + respectRequiredConstructorParameters = (bool)namedArg.Value.Value!; + break; + + case nameof(JsonSourceGenerationOptionsAttribute.IgnoreReadOnlyFields): + ignoreReadOnlyFields = (bool)namedArg.Value.Value!; + break; + + case nameof(JsonSourceGenerationOptionsAttribute.IgnoreReadOnlyProperties): + ignoreReadOnlyProperties = (bool)namedArg.Value.Value!; + break; + + case nameof(JsonSourceGenerationOptionsAttribute.IncludeFields): + includeFields = (bool)namedArg.Value.Value!; + break; + + case nameof(JsonSourceGenerationOptionsAttribute.MaxDepth): + maxDepth = (int)namedArg.Value.Value!; + break; + + case nameof(JsonSourceGenerationOptionsAttribute.NewLine): + newLine = (string)namedArg.Value.Value!; + break; + + case nameof(JsonSourceGenerationOptionsAttribute.NumberHandling): + numberHandling = (JsonNumberHandling)namedArg.Value.Value!; + break; + + case nameof(JsonSourceGenerationOptionsAttribute.PreferredObjectCreationHandling): + preferredObjectCreationHandling = (JsonObjectCreationHandling)namedArg.Value.Value!; + break; + + case nameof(JsonSourceGenerationOptionsAttribute.PropertyNameCaseInsensitive): + propertyNameCaseInsensitive = (bool)namedArg.Value.Value!; + break; + + case nameof(JsonSourceGenerationOptionsAttribute.PropertyNamingPolicy): + propertyNamingPolicy = (JsonKnownNamingPolicy)namedArg.Value.Value!; + break; + + case nameof(JsonSourceGenerationOptionsAttribute.ReadCommentHandling): + readCommentHandling = (JsonCommentHandling)namedArg.Value.Value!; + break; + + case nameof(JsonSourceGenerationOptionsAttribute.ReferenceHandler): + referenceHandler = (JsonKnownReferenceHandler)namedArg.Value.Value!; + break; + + case nameof(JsonSourceGenerationOptionsAttribute.UnknownTypeHandling): + unknownTypeHandling = (JsonUnknownTypeHandling)namedArg.Value.Value!; + break; + + case nameof(JsonSourceGenerationOptionsAttribute.UnmappedMemberHandling): + unmappedMemberHandling = (JsonUnmappedMemberHandling)namedArg.Value.Value!; + break; + + case nameof(JsonSourceGenerationOptionsAttribute.UseStringEnumConverter): + useStringEnumConverter = (bool)namedArg.Value.Value!; + break; + + case nameof(JsonSourceGenerationOptionsAttribute.WriteIndented): + writeIndented = (bool)namedArg.Value.Value!; + break; + + case nameof(JsonSourceGenerationOptionsAttribute.IndentCharacter): + indentCharacter = (char)namedArg.Value.Value!; + break; + + case nameof(JsonSourceGenerationOptionsAttribute.IndentSize): + indentSize = (int)namedArg.Value.Value!; + break; + + case nameof(JsonSourceGenerationOptionsAttribute.GenerationMode): + generationMode = (JsonSourceGenerationMode)namedArg.Value.Value!; + break; + + default: + throw new InvalidOperationException(); + } + } + + return new SourceGenerationOptionsSpec + { + GenerationMode = generationMode, + Defaults = defaults, + AllowOutOfOrderMetadataProperties = allowOutOfOrderMetadataProperties, + AllowTrailingCommas = allowTrailingCommas, + DefaultBufferSize = defaultBufferSize, + Converters = converters?.ToImmutableEquatableArray(), + DefaultIgnoreCondition = defaultIgnoreCondition, + DictionaryKeyPolicy = dictionaryKeyPolicy, + RespectNullableAnnotations = respectNullableAnnotations, + RespectRequiredConstructorParameters = respectRequiredConstructorParameters, + IgnoreReadOnlyFields = ignoreReadOnlyFields, + IgnoreReadOnlyProperties = ignoreReadOnlyProperties, + IncludeFields = includeFields, + MaxDepth = maxDepth, + NewLine = newLine, + NumberHandling = numberHandling, + PreferredObjectCreationHandling = preferredObjectCreationHandling, + PropertyNameCaseInsensitive = propertyNameCaseInsensitive, + PropertyNamingPolicy = propertyNamingPolicy, + ReadCommentHandling = readCommentHandling, + ReferenceHandler = referenceHandler, + UnknownTypeHandling = unknownTypeHandling, + UnmappedMemberHandling = unmappedMemberHandling, + UseStringEnumConverter = useStringEnumConverter, + WriteIndented = writeIndented, + IndentCharacter = indentCharacter, + IndentSize = indentSize, + }; + } + + private TypeToGenerate? ParseJsonSerializableAttribute(AttributeData attributeData) + { + Debug.Assert(attributeData.ConstructorArguments.Length == 1); + var typeSymbol = (ITypeSymbol?)attributeData.ConstructorArguments[0].Value; + if (typeSymbol is null) + { + return null; + } + + JsonSourceGenerationMode? generationMode = null; + string? typeInfoPropertyName = null; + + foreach (KeyValuePair namedArg in attributeData.NamedArguments) + { + switch (namedArg.Key) + { + case nameof(JsonSerializableAttribute.TypeInfoPropertyName): + typeInfoPropertyName = (string)namedArg.Value.Value!; + break; + case nameof(JsonSerializableAttribute.GenerationMode): + generationMode = (JsonSourceGenerationMode)namedArg.Value.Value!; + break; + default: + throw new InvalidOperationException(); + } + } + + Location? location = typeSymbol.GetLocation(); + Location? attributeLocation = attributeData.GetLocation(); + Debug.Assert(attributeLocation != null); + + if (location is null || !_knownSymbols.Compilation.ContainsLocation(location)) + { + // For symbols located outside the compilation, fall back to attribute location instead. + location = attributeLocation; + } + + return new TypeToGenerate + { + Type = _knownSymbols.Compilation.EraseCompileTimeMetadata(typeSymbol), + Mode = generationMode, + TypeInfoPropertyName = typeInfoPropertyName, + Location = location, + AttributeLocation = attributeLocation, + }; + } + + private TypeGenerationSpec ParseTypeGenerationSpec(in TypeToGenerate typeToGenerate, + INamedTypeSymbol contextType, SourceGenerationOptionsSpec? options) + { + Debug.Assert(IsSymbolAccessibleWithin(typeToGenerate.Type, within: contextType), + "should not generate metadata for inaccessible types."); + + ITypeSymbol type = typeToGenerate.Type; + + ClassType classType; + JsonPrimitiveTypeKind? primitiveTypeKind = GetPrimitiveTypeKind(type); + TypeRef? collectionKeyType = null; + TypeRef? collectionValueType = null; + TypeRef? nullableUnderlyingType = null; + bool hasExtensionDataProperty = false; + TypeRef? runtimeTypeRef = null; + List? propertySpecs = null; + List? fastPathPropertyIndices = null; + ObjectConstructionStrategy constructionStrategy = default; + bool constructorSetsRequiredMembers = false; + ParameterGenerationSpec[]? ctorParamSpecs = null; + List? propertyInitializerSpecs = null; + CollectionType collectionType = CollectionType.NotApplicable; + string? immutableCollectionFactoryTypeFullName = null; + bool implementsIJsonOnSerialized = false; + bool implementsIJsonOnSerializing = false; + + ProcessTypeCustomAttributes(typeToGenerate, contextType, + out JsonNumberHandling? numberHandling, + out JsonUnmappedMemberHandling? unmappedMemberHandling, + out JsonObjectCreationHandling? preferredPropertyObjectCreationHandling, + out bool foundJsonConverterAttribute, + out TypeRef? customConverterType, + out bool isPolymorphic); + + if (type is { IsRefLikeType: true } or INamedTypeSymbol { IsUnboundGenericType: true } + or IErrorTypeSymbol) + { + classType = ClassType.TypeUnsupportedBySourceGen; + } + else if (foundJsonConverterAttribute) + { + classType = customConverterType != null + ? ClassType.TypeWithDesignTimeProvidedCustomConverter + : ClassType.TypeUnsupportedBySourceGen; + } + else if (IsBuiltInSupportType(type)) + { + classType = ClassType.BuiltInSupportType; + } + else if (IsUnsupportedType(type)) + { + classType = ClassType.UnsupportedType; + } + else if (type.IsNullableValueType(out ITypeSymbol? underlyingType)) + { + classType = ClassType.Nullable; + nullableUnderlyingType = EnqueueType(underlyingType, typeToGenerate.Mode); + } + else if (type.TypeKind is TypeKind.Enum) + { + if (options?.UseStringEnumConverter == true) + { + Debug.Assert(_knownSymbols.JsonStringEnumConverterOfTType != null); + INamedTypeSymbol converterSymbol = _knownSymbols.JsonStringEnumConverterOfTType.Construct(type); + + customConverterType = new TypeRef(converterSymbol); + classType = ClassType.TypeWithDesignTimeProvidedCustomConverter; + } + else + { + classType = ClassType.Enum; + } + } + else if (TryResolveCollectionType(type, + out ITypeSymbol? valueType, + out ITypeSymbol? keyType, + out collectionType, + out immutableCollectionFactoryTypeFullName, + out bool needsRuntimeType)) + { + if (!IsSymbolAccessibleWithin(valueType, within: contextType) || + (keyType != null && !IsSymbolAccessibleWithin(keyType, within: contextType))) + { + classType = ClassType.UnsupportedType; + immutableCollectionFactoryTypeFullName = null; + collectionType = default; + } + else if (valueType.IsRefLikeType || keyType?.IsRefLikeType is true) + { + classType = ClassType.TypeUnsupportedBySourceGen; + immutableCollectionFactoryTypeFullName = null; + collectionType = default; + } + else + { + if (type.CanUseDefaultConstructorForDeserialization(out IMethodSymbol? defaultCtor)) + { + constructionStrategy = ObjectConstructionStrategy.ParameterlessConstructor; + constructorSetsRequiredMembers = + defaultCtor?.ContainsAttribute(_knownSymbols.SetsRequiredMembersAttributeType) == true; + } + + classType = keyType != null ? ClassType.Dictionary : ClassType.Enumerable; + collectionValueType = EnqueueType(valueType, typeToGenerate.Mode); + + if (keyType != null) + { + collectionKeyType = EnqueueType(keyType, typeToGenerate.Mode); + + if (needsRuntimeType) + { + runtimeTypeRef = GetDictionaryTypeRef(keyType, valueType); + } + } + } + } + else + { + bool useDefaultCtorInAnnotatedStructs = + type.GetCompatibleGenericBaseType(_knownSymbols.KeyValuePair) is null; + + if (!TryGetDeserializationConstructor(type, useDefaultCtorInAnnotatedStructs, + out IMethodSymbol? constructor)) + { + ReportDiagnostic( + global::CSharpToJsonSchema.Generators.JsonGen.JsonSourceGenerator.DiagnosticDescriptors + .MultipleJsonConstructorAttribute, typeToGenerate.Location, type.ToDisplayString()); + } + // else if (constructor != null && !IsSymbolAccessibleWithin(constructor, within: contextType)) + // { + // ReportDiagnostic( + // global::CSharpToJsonSchema.Generators.JsonGen.JsonSourceGenerator.DiagnosticDescriptors + // .JsonConstructorInaccessible, typeToGenerate.Location, type.ToDisplayString()); + // constructor = null; + // } + + classType = ClassType.Object; + + implementsIJsonOnSerializing = _knownSymbols.IJsonOnSerializingType.IsAssignableFrom(type); + implementsIJsonOnSerialized = _knownSymbols.IJsonOnSerializedType.IsAssignableFrom(type); + + ctorParamSpecs = ParseConstructorParameters(typeToGenerate, constructor, out constructionStrategy, + out constructorSetsRequiredMembers); + propertySpecs = ParsePropertyGenerationSpecs(contextType, typeToGenerate, options, + out hasExtensionDataProperty, out fastPathPropertyIndices); + propertyInitializerSpecs = ParsePropertyInitializers(ctorParamSpecs, propertySpecs, + constructorSetsRequiredMembers, ref constructionStrategy); + } + + var typeRef = new TypeRef(type); + string typeInfoPropertyName = typeToGenerate.TypeInfoPropertyName ?? GetTypeInfoPropertyName(type); + + if (classType is ClassType.TypeUnsupportedBySourceGen) + { + ReportDiagnostic( + global::CSharpToJsonSchema.Generators.JsonGen.JsonSourceGenerator.DiagnosticDescriptors + .TypeNotSupported, typeToGenerate.AttributeLocation ?? typeToGenerate.Location, + type.ToDisplayString()); + } + + if (!_generatedContextAndTypeNames.Add((contextType.Name, typeInfoPropertyName))) + { + // The context name/property name combination will result in a conflict in generated types. + // Workaround for https://github.com/dotnet/roslyn/issues/54185 by keeping track of the file names we've used. + ReportDiagnostic( + global::CSharpToJsonSchema.Generators.JsonGen.JsonSourceGenerator.DiagnosticDescriptors + .DuplicateTypeName, typeToGenerate.AttributeLocation ?? _contextClassLocation, + typeInfoPropertyName); + classType = ClassType.TypeUnsupportedBySourceGen; + } + + return new TypeGenerationSpec + { + TypeRef = typeRef, + TypeInfoPropertyName = typeInfoPropertyName, + GenerationMode = typeToGenerate.Mode ?? options?.GenerationMode ?? JsonSourceGenerationMode.Default, + ClassType = classType, + PrimitiveTypeKind = primitiveTypeKind, + IsPolymorphic = isPolymorphic, + NumberHandling = numberHandling, + UnmappedMemberHandling = unmappedMemberHandling, + PreferredPropertyObjectCreationHandling = preferredPropertyObjectCreationHandling, + PropertyGenSpecs = propertySpecs?.ToImmutableEquatableArray() ?? + ImmutableEquatableArray.Empty, + FastPathPropertyIndices = fastPathPropertyIndices?.ToImmutableEquatableArray(), + PropertyInitializerSpecs = propertyInitializerSpecs?.ToImmutableEquatableArray() ?? + ImmutableEquatableArray.Empty, + CtorParamGenSpecs = ctorParamSpecs?.ToImmutableEquatableArray() ?? + ImmutableEquatableArray.Empty, + CollectionType = collectionType, + CollectionKeyType = collectionKeyType, + CollectionValueType = collectionValueType, + ConstructionStrategy = constructionStrategy, + ConstructorSetsRequiredParameters = constructorSetsRequiredMembers, + NullableUnderlyingType = nullableUnderlyingType, + RuntimeTypeRef = runtimeTypeRef, + IsValueTuple = type.IsTupleType, + HasExtensionDataPropertyType = hasExtensionDataProperty, + ConverterType = customConverterType, + ImplementsIJsonOnSerialized = implementsIJsonOnSerialized, + ImplementsIJsonOnSerializing = implementsIJsonOnSerializing, + ImmutableCollectionFactoryMethod = + DetermineImmutableCollectionFactoryMethod(immutableCollectionFactoryTypeFullName), + }; + } + + private void ProcessTypeCustomAttributes( + in TypeToGenerate typeToGenerate, + INamedTypeSymbol contextType, + out JsonNumberHandling? numberHandling, + out JsonUnmappedMemberHandling? unmappedMemberHandling, + out JsonObjectCreationHandling? objectCreationHandling, + out bool foundJsonConverterAttribute, + out TypeRef? customConverterType, + out bool isPolymorphic) + { + numberHandling = null; + unmappedMemberHandling = null; + objectCreationHandling = null; + customConverterType = null; + foundJsonConverterAttribute = false; + isPolymorphic = false; + + foreach (AttributeData attributeData in typeToGenerate.Type.GetAttributes()) + { + INamedTypeSymbol? attributeType = attributeData.AttributeClass; + + if (SymbolEqualityComparer.Default.Equals(attributeType, + _knownSymbols.JsonNumberHandlingAttributeType)) + { + numberHandling = (JsonNumberHandling)attributeData.ConstructorArguments[0].Value!; + continue; + } + else if (SymbolEqualityComparer.Default.Equals(attributeType, + _knownSymbols.JsonUnmappedMemberHandlingAttributeType)) + { + unmappedMemberHandling = + (JsonUnmappedMemberHandling)attributeData.ConstructorArguments[0].Value!; + continue; + } + else if (SymbolEqualityComparer.Default.Equals(attributeType, + _knownSymbols.JsonObjectCreationHandlingAttributeType)) + { + objectCreationHandling = + (JsonObjectCreationHandling)attributeData.ConstructorArguments[0].Value!; + continue; + } + else if (!foundJsonConverterAttribute && + _knownSymbols.JsonConverterAttributeType.IsAssignableFrom(attributeType)) + { + customConverterType = + GetConverterTypeFromJsonConverterAttribute(contextType, typeToGenerate.Type, attributeData); + foundJsonConverterAttribute = true; + } + + if (SymbolEqualityComparer.Default.Equals(attributeType, + _knownSymbols.JsonDerivedTypeAttributeType)) + { + Debug.Assert(attributeData.ConstructorArguments.Length > 0); + var derivedType = (ITypeSymbol)attributeData.ConstructorArguments[0].Value!; + EnqueueType(derivedType, typeToGenerate.Mode); + + if (!isPolymorphic && typeToGenerate.Mode == JsonSourceGenerationMode.Serialization) + { + ReportDiagnostic( + global::CSharpToJsonSchema.Generators.JsonGen.JsonSourceGenerator.DiagnosticDescriptors + .PolymorphismNotSupported, typeToGenerate.Location, + typeToGenerate.Type.ToDisplayString()); + } + + isPolymorphic = true; + } + } + } + + private bool TryResolveCollectionType( + ITypeSymbol type, + [NotNullWhen(true)] out ITypeSymbol? valueType, + out ITypeSymbol? keyType, + out CollectionType collectionType, + out string? immutableCollectionFactoryTypeFullName, + out bool needsRuntimeType) + { + INamedTypeSymbol? actualTypeToConvert; + valueType = null; + keyType = null; + collectionType = default; + immutableCollectionFactoryTypeFullName = null; + needsRuntimeType = false; + + if (SymbolEqualityComparer.Default.Equals(type.OriginalDefinition, _knownSymbols.MemoryType)) + { + Debug.Assert(!SymbolEqualityComparer.Default.Equals(type, _knownSymbols.MemoryByteType)); + valueType = ((INamedTypeSymbol)type).TypeArguments[0]; + collectionType = CollectionType.MemoryOfT; + return true; + } + + if (SymbolEqualityComparer.Default.Equals(type.OriginalDefinition, _knownSymbols.ReadOnlyMemoryType)) + { + Debug.Assert(!SymbolEqualityComparer.Default.Equals(type, _knownSymbols.ReadOnlyMemoryByteType)); + valueType = ((INamedTypeSymbol)type).TypeArguments[0]; + collectionType = CollectionType.ReadOnlyMemoryOfT; + return true; + } + + // IAsyncEnumerable takes precedence over IEnumerable. + if (type.GetCompatibleGenericBaseType(_knownSymbols.IAsyncEnumerableOfTType) is INamedTypeSymbol + iAsyncEnumerableType) + { + valueType = iAsyncEnumerableType.TypeArguments[0]; + collectionType = CollectionType.IAsyncEnumerableOfT; + return true; + } + + if (!_knownSymbols.IEnumerableType.IsAssignableFrom(type)) + { + // Type is not IEnumerable and therefore not a collection type + return false; + } + + if (type is IArrayTypeSymbol arraySymbol) + { + Debug.Assert(arraySymbol.Rank == 1, "multi-dimensional arrays should have been handled earlier."); + collectionType = CollectionType.Array; + valueType = arraySymbol.ElementType; + } + else if ((actualTypeToConvert = type.GetCompatibleGenericBaseType(_knownSymbols.KeyedCollectionType)) != + null) + { + collectionType = CollectionType.ICollectionOfT; + valueType = actualTypeToConvert.TypeArguments[1]; + } + else if ((actualTypeToConvert = type.GetCompatibleGenericBaseType(_knownSymbols.ListOfTType)) != null) + { + collectionType = CollectionType.List; + valueType = actualTypeToConvert.TypeArguments[0]; + } + else if ((actualTypeToConvert = + type.GetCompatibleGenericBaseType(_knownSymbols.DictionaryOfTKeyTValueType)) != null) + { + collectionType = CollectionType.Dictionary; + keyType = actualTypeToConvert.TypeArguments[0]; + valueType = actualTypeToConvert.TypeArguments[1]; + } + else if (_knownSymbols.IsImmutableDictionaryType(type, out immutableCollectionFactoryTypeFullName)) + { + collectionType = CollectionType.ImmutableDictionary; + ImmutableArray genericArgs = ((INamedTypeSymbol)type).TypeArguments; + keyType = genericArgs[0]; + valueType = genericArgs[1]; + } + else if ((actualTypeToConvert = + type.GetCompatibleGenericBaseType(_knownSymbols.IDictionaryOfTKeyTValueType)) != null) + { + collectionType = CollectionType.IDictionaryOfTKeyTValue; + keyType = actualTypeToConvert.TypeArguments[0]; + valueType = actualTypeToConvert.TypeArguments[1]; + needsRuntimeType = SymbolEqualityComparer.Default.Equals(type, actualTypeToConvert); + } + else if ((actualTypeToConvert = + type.GetCompatibleGenericBaseType(_knownSymbols.IReadonlyDictionaryOfTKeyTValueType)) != + null) + { + collectionType = CollectionType.IReadOnlyDictionary; + keyType = actualTypeToConvert.TypeArguments[0]; + valueType = actualTypeToConvert.TypeArguments[1]; + needsRuntimeType = SymbolEqualityComparer.Default.Equals(type, actualTypeToConvert); + } + else if (_knownSymbols.IsImmutableEnumerableType(type, out immutableCollectionFactoryTypeFullName)) + { + collectionType = CollectionType.ImmutableEnumerable; + valueType = ((INamedTypeSymbol)type).TypeArguments[0]; + } + else if ((actualTypeToConvert = type.GetCompatibleGenericBaseType(_knownSymbols.IListOfTType)) != null) + { + collectionType = CollectionType.IListOfT; + valueType = actualTypeToConvert.TypeArguments[0]; + } + else if ((actualTypeToConvert = type.GetCompatibleGenericBaseType(_knownSymbols.ISetOfTType)) != null) + { + collectionType = CollectionType.ISet; + valueType = actualTypeToConvert.TypeArguments[0]; + } + else if ((actualTypeToConvert = type.GetCompatibleGenericBaseType(_knownSymbols.ICollectionOfTType)) != + null) + { + collectionType = CollectionType.ICollectionOfT; + valueType = actualTypeToConvert.TypeArguments[0]; + } + else if ((actualTypeToConvert = type.GetCompatibleGenericBaseType(_knownSymbols.StackOfTType)) != null) + { + collectionType = CollectionType.StackOfT; + valueType = actualTypeToConvert.TypeArguments[0]; + } + else if ((actualTypeToConvert = type.GetCompatibleGenericBaseType(_knownSymbols.QueueOfTType)) != null) + { + collectionType = CollectionType.QueueOfT; + valueType = actualTypeToConvert.TypeArguments[0]; + } + else if ((actualTypeToConvert = type.GetCompatibleGenericBaseType(_knownSymbols.ConcurrentStackType)) != + null) + { + collectionType = CollectionType.ConcurrentStack; + valueType = actualTypeToConvert.TypeArguments[0]; + } + else if ((actualTypeToConvert = type.GetCompatibleGenericBaseType(_knownSymbols.ConcurrentQueueType)) != + null) + { + collectionType = CollectionType.ConcurrentQueue; + valueType = actualTypeToConvert.TypeArguments[0]; + } + else if ((actualTypeToConvert = type.GetCompatibleGenericBaseType(_knownSymbols.IEnumerableOfTType)) != + null) + { + collectionType = CollectionType.IEnumerableOfT; + valueType = actualTypeToConvert.TypeArguments[0]; + } + else if (_knownSymbols.IDictionaryType.IsAssignableFrom(type)) + { + collectionType = CollectionType.IDictionary; + keyType = _knownSymbols.StringType; + valueType = _knownSymbols.ObjectType; + needsRuntimeType = SymbolEqualityComparer.Default.Equals(type, actualTypeToConvert); + } + else if (_knownSymbols.IListType.IsAssignableFrom(type)) + { + collectionType = CollectionType.IList; + valueType = _knownSymbols.ObjectType; + } + else if (_knownSymbols.StackType.IsAssignableFrom(type)) + { + collectionType = CollectionType.Stack; + valueType = _knownSymbols.ObjectType; + } + else if (_knownSymbols.QueueType.IsAssignableFrom(type)) + { + collectionType = CollectionType.Queue; + valueType = _knownSymbols.ObjectType; + } + else + { + collectionType = CollectionType.IEnumerable; + valueType = _knownSymbols.ObjectType; + } + + return true; + } + + private TypeRef? GetDictionaryTypeRef(ITypeSymbol keyType, ITypeSymbol valueType) + { + INamedTypeSymbol? dictionary = _knownSymbols.DictionaryOfTKeyTValueType?.Construct(keyType, valueType); + return dictionary is null ? null : new TypeRef(dictionary); + } + + private List ParsePropertyGenerationSpecs( + INamedTypeSymbol contextType, + in TypeToGenerate typeToGenerate, + SourceGenerationOptionsSpec? options, + out bool hasExtensionDataProperty, + out List? fastPathPropertyIndices) + { + Location? typeLocation = typeToGenerate.Location; + List properties = new(); + PropertyHierarchyResolutionState state = new(options); + hasExtensionDataProperty = false; + + // Walk the type hierarchy starting from the current type up to the base type(s) + foreach (INamedTypeSymbol currentType in typeToGenerate.Type.GetSortedTypeHierarchy()) + { + if(currentType.Name == "CancellationToken") + continue; + var declaringTypeRef = new TypeRef(currentType); + ImmutableArray members = currentType.GetMembers(); + + foreach (IPropertySymbol propertyInfo in members.OfType()) + { + // Skip if: + if ( + // property is static or an indexer + propertyInfo.IsStatic || propertyInfo.Parameters.Length > 0 || + // It is overridden by a derived property + PropertyIsOverriddenAndIgnored(propertyInfo, state.IgnoredMembers)) + { + continue; + } + + AddMember( + declaringTypeRef, + memberType: propertyInfo.Type, + memberInfo: propertyInfo, + typeToGenerate.Mode, + ref state, + ref hasExtensionDataProperty); + } + + foreach (IFieldSymbol fieldInfo in members.OfType()) + { + // Skip if : + if ( + // it is a static field, constant + fieldInfo.IsStatic || fieldInfo.IsConst || + // it is a compiler-generated backing field + fieldInfo.AssociatedSymbol != null || + // symbol represents an explicitly named tuple element + fieldInfo.IsExplicitlyNamedTupleElement) + { + continue; + } + + AddMember( + declaringTypeRef, + memberType: fieldInfo.Type, + memberInfo: fieldInfo, + typeToGenerate.Mode, + ref state, + ref hasExtensionDataProperty); + } + } + + if (state.IsPropertyOrderSpecified) + { + state.Properties.StableSortByKey(i => properties[i].Order); + } + + fastPathPropertyIndices = state.HasInvalidConfigurationForFastPath ? null : state.Properties; + return properties; + + void AddMember( + TypeRef declaringTypeRef, + ITypeSymbol memberType, + ISymbol memberInfo, + JsonSourceGenerationMode? generationMode, + ref PropertyHierarchyResolutionState state, + ref bool hasExtensionDataProperty) + { + PropertyGenerationSpec? propertySpec = ParsePropertyGenerationSpec( + contextType, + declaringTypeRef, + typeLocation, + memberType, + memberInfo, + ref hasExtensionDataProperty, + generationMode, + options); + + if (propertySpec is null) + { + // ignored invalid property + return; + } + + AddPropertyWithConflictResolution(propertySpec, memberInfo, propertyIndex: properties.Count, + ref state); + properties.Add(propertySpec); + + // In case of JsonInclude fail if either: + // 1. the getter is not accessible by the source generator or + // 2. neither getter or setter methods are public. + if (propertySpec.HasJsonInclude && (!propertySpec.CanUseGetter || !propertySpec.IsPublic)) + { + state.HasInvalidConfigurationForFastPath = true; + } + } + + bool PropertyIsOverriddenAndIgnored(IPropertySymbol property, + Dictionary? ignoredMembers) + { + return property.IsVirtual() && + ignoredMembers?.TryGetValue(property.Name, out ISymbol? ignoredMember) == true && + ignoredMember.IsVirtual() && + SymbolEqualityComparer.Default.Equals(property.Type, ignoredMember.GetMemberType()); + } + } + + private ref struct PropertyHierarchyResolutionState(SourceGenerationOptionsSpec? options) + { + public readonly List Properties = new(); + + public Dictionary AddedProperties = + new(options?.PropertyNameCaseInsensitive == true + ? StringComparer.OrdinalIgnoreCase + : StringComparer.Ordinal); + + public Dictionary? IgnoredMembers; + public bool IsPropertyOrderSpecified; + public bool HasInvalidConfigurationForFastPath; + } + + /// + /// Runs the property name conflict resolution algorithm for the fast-path serializer + /// using the compile-time specified naming policies. Note that the metadata serializer + /// does not consult these results since its own run-time configuration can be different. + /// Instead the same algorithm is executed at run-time within the JsonTypeInfo infrastructure. + /// + private static void AddPropertyWithConflictResolution( + PropertyGenerationSpec propertySpec, + ISymbol memberInfo, + int propertyIndex, + ref PropertyHierarchyResolutionState state) + { + // Algorithm should be kept in sync with the runtime equivalent in JsonTypeInfo.cs + string memberName = propertySpec.MemberName; + + if (state.AddedProperties.TryAdd(propertySpec.EffectiveJsonPropertyName, + (propertySpec, memberInfo, state.Properties.Count))) + { + state.Properties.Add(propertyIndex); + state.IsPropertyOrderSpecified |= propertySpec.Order != 0; + } + else + { + // The JsonPropertyNameAttribute or naming policy resulted in a collision. + (PropertyGenerationSpec other, ISymbol otherSymbol, int index) = + state.AddedProperties[propertySpec.EffectiveJsonPropertyName]; + + if (other.DefaultIgnoreCondition == JsonIgnoreCondition.Always) + { + // Overwrite previously cached property since it has [JsonIgnore]. + state.AddedProperties[propertySpec.EffectiveJsonPropertyName] = + (propertySpec, memberInfo, index); + state.Properties[index] = propertyIndex; + state.IsPropertyOrderSpecified |= propertySpec.Order != 0; + } + else + { + bool ignoreCurrentProperty = + // Does the current property have `JsonIgnoreAttribute`? + propertySpec.DefaultIgnoreCondition == JsonIgnoreCondition.Always || + // Is the current property hidden by the previously cached property + // (with `new` keyword, or by overriding)? + memberInfo.IsOverriddenOrShadowedBy(otherSymbol) || + // Was a property with the same CLR name ignored? That property hid the current property, + // thus, if it was ignored, the current property should be ignored too. + (state.IgnoredMembers?.TryGetValue(memberName, out ISymbol? ignored) == true && + memberInfo.IsOverriddenOrShadowedBy(ignored)); + + if (!ignoreCurrentProperty) + { + // Property name conflict cannot be reconciled + // Signal the fast-path generator to emit a throwing stub method. + state.HasInvalidConfigurationForFastPath = true; + } + } + } + + if (propertySpec.DefaultIgnoreCondition == JsonIgnoreCondition.Always) + { + (state.IgnoredMembers ??= new())[memberName] = memberInfo; + } + } + + private bool IsValidDataExtensionPropertyType(ITypeSymbol type) + { + if (SymbolEqualityComparer.Default.Equals(type, _knownSymbols.JsonObjectType)) + { + return true; + } + + INamedTypeSymbol? actualDictionaryType = + type.GetCompatibleGenericBaseType(_knownSymbols.IDictionaryOfTKeyTValueType); + if (actualDictionaryType == null) + { + return false; + } + + return SymbolEqualityComparer.Default.Equals(actualDictionaryType.TypeArguments[0], + _knownSymbols.StringType) && + (SymbolEqualityComparer.Default.Equals(actualDictionaryType.TypeArguments[1], + _knownSymbols.ObjectType) || + SymbolEqualityComparer.Default.Equals(actualDictionaryType.TypeArguments[1], + _knownSymbols.JsonElementType)); + } + + private PropertyGenerationSpec? ParsePropertyGenerationSpec( + INamedTypeSymbol contextType, + TypeRef declaringType, + Location? typeLocation, + ITypeSymbol memberType, + ISymbol memberInfo, + ref bool typeHasExtensionDataProperty, + JsonSourceGenerationMode? generationMode, + SourceGenerationOptionsSpec? options) + { + Debug.Assert(memberInfo is IFieldSymbol or IPropertySymbol); + + ProcessMemberCustomAttributes( + contextType, + memberInfo, + out bool hasJsonInclude, + out string? jsonPropertyName, + out JsonIgnoreCondition? ignoreCondition, + out JsonNumberHandling? numberHandling, + out JsonObjectCreationHandling? objectCreationHandling, + out TypeRef? converterType, + out int order, + out bool isExtensionData, + out bool hasJsonRequiredAttribute); + + ProcessMember( + contextType, + memberInfo, + hasJsonInclude, + out bool isReadOnly, + out bool isAccessible, + out bool isRequired, + out bool canUseGetter, + out bool canUseSetter, + out bool hasJsonIncludeButIsInaccessible, + out bool setterIsInitOnly, + out bool isGetterNonNullable, + out bool isSetterNonNullable); + + if (hasJsonIncludeButIsInaccessible) + { + ReportDiagnostic( + global::CSharpToJsonSchema.Generators.JsonGen.JsonSourceGenerator.DiagnosticDescriptors + .InaccessibleJsonIncludePropertiesNotSupported, memberInfo.GetLocation(), + declaringType.Name, memberInfo.Name); + } + + if (isExtensionData) + { + if (typeHasExtensionDataProperty) + { + ReportDiagnostic( + global::CSharpToJsonSchema.Generators.JsonGen.JsonSourceGenerator.DiagnosticDescriptors + .MultipleJsonExtensionDataAttribute, typeLocation, declaringType.Name); + } + + if (!IsValidDataExtensionPropertyType(memberType)) + { + ReportDiagnostic( + global::CSharpToJsonSchema.Generators.JsonGen.JsonSourceGenerator.DiagnosticDescriptors + .DataExtensionPropertyInvalid, memberInfo.GetLocation(), declaringType.Name, + memberInfo.Name); + } + + typeHasExtensionDataProperty = true; + } + + if ((!canUseGetter && !canUseSetter && !hasJsonIncludeButIsInaccessible) || + !IsSymbolAccessibleWithin(memberType, within: contextType)) + { + // Skip the member if either of the two conditions hold + // 1. Member has no accessible getters or setters (but is not marked with JsonIncludeAttribute since we need to throw a runtime exception) OR + // 2. The member type is not accessible within the generated context. + return null; + } + + if (memberType.IsRefLikeType) + { + // Skip all ref-like members and emit a diagnostic unless the property is being explicitly ignored. + if (ignoreCondition is not JsonIgnoreCondition.Always) + { + ReportDiagnostic( + global::CSharpToJsonSchema.Generators.JsonGen.JsonSourceGenerator.DiagnosticDescriptors + .TypeContainsRefLikeMember, memberInfo.GetLocation(), declaringType.Name, + memberInfo.Name); + } + + return null; + } + + string effectiveJsonPropertyName = + DetermineEffectiveJsonPropertyName(memberInfo.Name, jsonPropertyName, options); + string propertyNameFieldName = DeterminePropertyNameFieldName(effectiveJsonPropertyName); + + // Enqueue the property type for generation, unless the member is ignored. + TypeRef propertyTypeRef = ignoreCondition != JsonIgnoreCondition.Always + ? EnqueueType(memberType, generationMode) + : new TypeRef(memberType); + + return new PropertyGenerationSpec + { + NameSpecifiedInSourceCode = + memberInfo.MemberNameNeedsAtSign() ? "@" + memberInfo.Name : memberInfo.Name, + MemberName = memberInfo.Name, + IsProperty = memberInfo is IPropertySymbol, + IsPublic = isAccessible, + IsVirtual = memberInfo.IsVirtual(), + JsonPropertyName = jsonPropertyName, + EffectiveJsonPropertyName = effectiveJsonPropertyName, + PropertyNameFieldName = propertyNameFieldName, + IsReadOnly = isReadOnly, + IsRequired = isRequired, + HasJsonRequiredAttribute = hasJsonRequiredAttribute, + IsInitOnlySetter = setterIsInitOnly, + CanUseGetter = canUseGetter, + CanUseSetter = canUseSetter, + DefaultIgnoreCondition = ignoreCondition, + NumberHandling = numberHandling, + ObjectCreationHandling = objectCreationHandling, + Order = order, + HasJsonInclude = hasJsonInclude, + IsExtensionData = isExtensionData, + PropertyType = propertyTypeRef, + DeclaringType = declaringType, + ConverterType = converterType, + IsGetterNonNullableAnnotation = isGetterNonNullable, + IsSetterNonNullableAnnotation = isSetterNonNullable, + }; + } + + private void ProcessMemberCustomAttributes( + INamedTypeSymbol contextType, + ISymbol memberInfo, + out bool hasJsonInclude, + out string? jsonPropertyName, + out JsonIgnoreCondition? ignoreCondition, + out JsonNumberHandling? numberHandling, + out JsonObjectCreationHandling? objectCreationHandling, + out TypeRef? converterType, + out int order, + out bool isExtensionData, + out bool hasJsonRequiredAttribute) + { + Debug.Assert(memberInfo is IFieldSymbol or IPropertySymbol); + + hasJsonInclude = false; + jsonPropertyName = null; + ignoreCondition = default; + numberHandling = default; + objectCreationHandling = default; + converterType = null; + order = 0; + isExtensionData = false; + hasJsonRequiredAttribute = false; + + foreach (AttributeData attributeData in memberInfo.GetAttributes()) + { + INamedTypeSymbol? attributeType = attributeData.AttributeClass; + + if (attributeType is null) + { + continue; + } + + if (converterType is null && + _knownSymbols.JsonConverterAttributeType.IsAssignableFrom(attributeType)) + { + converterType = + GetConverterTypeFromJsonConverterAttribute(contextType, memberInfo, attributeData); + } + else if (attributeType.ContainingAssembly.Name == SystemTextJsonNamespace) + { + switch (attributeType.ToDisplayString()) + { + case JsonIgnoreAttributeFullName: + { + ImmutableArray> namedArgs = + attributeData.NamedArguments; + + if (namedArgs.Length == 0) + { + ignoreCondition = JsonIgnoreCondition.Always; + } + else if (namedArgs.Length == 1 && + namedArgs[0].Value.Type?.ToDisplayString() == JsonIgnoreConditionFullName) + { + ignoreCondition = (JsonIgnoreCondition)namedArgs[0].Value.Value!; + } + + break; + } + case JsonIncludeAttributeFullName: + { + hasJsonInclude = true; + break; + } + case JsonNumberHandlingAttributeFullName: + { + ImmutableArray ctorArgs = attributeData.ConstructorArguments; + numberHandling = (JsonNumberHandling)ctorArgs[0].Value!; + break; + } + case JsonObjectCreationHandlingAttributeFullName: + { + ImmutableArray ctorArgs = attributeData.ConstructorArguments; + objectCreationHandling = (JsonObjectCreationHandling)ctorArgs[0].Value!; + break; + } + case JsonPropertyNameAttributeFullName: + { + ImmutableArray ctorArgs = attributeData.ConstructorArguments; + jsonPropertyName = (string)ctorArgs[0].Value!; + // Null check here is done at runtime within JsonSerializer. + break; + } + case JsonPropertyOrderAttributeFullName: + { + ImmutableArray ctorArgs = attributeData.ConstructorArguments; + order = (int)ctorArgs[0].Value!; + break; + } + case JsonExtensionDataAttributeFullName: + { + isExtensionData = true; + break; + } + case JsonRequiredAttributeFullName: + { + hasJsonRequiredAttribute = true; + break; + } + } + } + } + } + + private void ProcessMember( + INamedTypeSymbol contextType, + ISymbol memberInfo, + bool hasJsonInclude, + out bool isReadOnly, + out bool isAccessible, + out bool isRequired, + out bool canUseGetter, + out bool canUseSetter, + out bool hasJsonIncludeButIsInaccessible, + out bool isSetterInitOnly, + out bool isGetterNonNullable, + out bool isSetterNonNullable) + { + isAccessible = false; + isReadOnly = false; + isRequired = false; + canUseGetter = false; + canUseSetter = false; + hasJsonIncludeButIsInaccessible = false; + isSetterInitOnly = false; + isGetterNonNullable = false; + isSetterNonNullable = false; + + switch (memberInfo) + { + case IPropertySymbol propertyInfo: +#if ROSLYN4_4_OR_GREATER + isRequired = propertyInfo.IsRequired; +#endif + if (propertyInfo.GetMethod is { } getMethod) + { + if (getMethod.DeclaredAccessibility is Accessibility.Public) + { + isAccessible = true; + canUseGetter = true; + } + else if (IsSymbolAccessibleWithin(getMethod, within: contextType)) + { + isAccessible = true; + canUseGetter = hasJsonInclude; + } + else + { + hasJsonIncludeButIsInaccessible = hasJsonInclude; + } + } + + if (propertyInfo.SetMethod is { } setMethod) + { + isSetterInitOnly = setMethod.IsInitOnly; + + if (setMethod.DeclaredAccessibility is Accessibility.Public) + { + isAccessible = true; + canUseSetter = true; + } + else if (IsSymbolAccessibleWithin(setMethod, within: contextType)) + { + isAccessible = true; + canUseSetter = hasJsonInclude; + } + else + { + hasJsonIncludeButIsInaccessible = hasJsonInclude; + } + } + else + { + isReadOnly = true; + } + + propertyInfo.ResolveNullabilityAnnotations(out isGetterNonNullable, out isSetterNonNullable); + break; + case IFieldSymbol fieldInfo: + isReadOnly = fieldInfo.IsReadOnly; +#if ROSLYN4_4_OR_GREATER + isRequired = fieldInfo.IsRequired; +#endif + if (fieldInfo.DeclaredAccessibility is Accessibility.Public) + { + isAccessible = true; + canUseGetter = true; + canUseSetter = !isReadOnly; + } + else if (IsSymbolAccessibleWithin(fieldInfo, within: contextType)) + { + isAccessible = true; + canUseGetter = hasJsonInclude; + canUseSetter = hasJsonInclude && !isReadOnly; + } + else + { + hasJsonIncludeButIsInaccessible = hasJsonInclude; + } + + fieldInfo.ResolveNullabilityAnnotations(out isGetterNonNullable, out isSetterNonNullable); + break; + default: + Debug.Fail("Method given an invalid symbol type."); + break; + } + } + + private ParameterGenerationSpec[]? ParseConstructorParameters( + in TypeToGenerate typeToGenerate, + IMethodSymbol? constructor, + out ObjectConstructionStrategy constructionStrategy, + out bool constructorSetsRequiredMembers) + { + ITypeSymbol type = typeToGenerate.Type; + + if ((constructor is null && !type.IsValueType) || type.IsAbstract) + { + constructionStrategy = ObjectConstructionStrategy.NotApplicable; + constructorSetsRequiredMembers = false; + return null; + } + + ParameterGenerationSpec[] constructorParameters; + int paramCount = constructor?.Parameters.Length ?? 0; + constructorSetsRequiredMembers = + constructor?.ContainsAttribute(_knownSymbols.SetsRequiredMembersAttributeType) == true; + + if (paramCount == 0) + { + constructionStrategy = ObjectConstructionStrategy.ParameterlessConstructor; + constructorParameters = []; + } + else + { + Debug.Assert(constructor != null); + + constructionStrategy = ObjectConstructionStrategy.ParameterizedConstructor; + constructorParameters = new ParameterGenerationSpec[paramCount]; + + for (int i = 0; i < paramCount; i++) + { + IParameterSymbol parameterInfo = constructor.Parameters[i]; + + if (parameterInfo.Type.IsRefLikeType) + { + ReportDiagnostic( + global::CSharpToJsonSchema.Generators.JsonGen.JsonSourceGenerator.DiagnosticDescriptors + .TypeContainsRefLikeMember, parameterInfo.GetLocation(), type.Name, + parameterInfo.Name); + constructionStrategy = ObjectConstructionStrategy.NotApplicable; + continue; + } + + TypeRef parameterTypeRef = EnqueueType(parameterInfo.Type, typeToGenerate.Mode); + + constructorParameters[i] = new ParameterGenerationSpec + { + ParameterType = parameterTypeRef, + Name = parameterInfo.Name, + HasDefaultValue = parameterInfo.HasExplicitDefaultValue, + DefaultValue = parameterInfo.HasExplicitDefaultValue + ? parameterInfo.ExplicitDefaultValue + : null, + ParameterIndex = i, + IsNullable = parameterInfo.IsNullable(), + }; + } + } + + return constructionStrategy is ObjectConstructionStrategy.NotApplicable ? null : constructorParameters; + } + + private List? ParsePropertyInitializers( + ParameterGenerationSpec[]? constructorParameters, + List? properties, + bool constructorSetsRequiredMembers, + ref ObjectConstructionStrategy constructionStrategy) + { + if (constructionStrategy is ObjectConstructionStrategy.NotApplicable || properties is null) + { + return null; + } + + HashSet? memberInitializerNames = null; + List? propertyInitializers = null; + int paramCount = constructorParameters?.Length ?? 0; + + // Determine potential init-only or required properties that need to be part of the constructor delegate signature. + foreach (PropertyGenerationSpec property in properties) + { + if (!property.CanUseSetter) + { + continue; + } + + if ((property.IsRequired && !constructorSetsRequiredMembers) || property.IsInitOnlySetter) + { + if (!(memberInitializerNames ??= new()).Add(property.MemberName)) + { + // We've already added another member initializer with the same name to our spec list. + // Duplicates can occur here because the provided list of properties includes shadowed members. + // This is because we generate metadata for *all* members, including shadowed or ignored ones, + // since we need to re-run the deduplication algorithm taking run-time configuration into account. + // This is a simple deduplication that keeps the first result for each member name -- + // this should be fine since the properties are listed from most derived to least derived order, + // so the second instance of a member name is always shadowed by the first. + continue; + } + + ParameterGenerationSpec? matchingConstructorParameter = + GetMatchingConstructorParameter(property, constructorParameters); + + if (property.IsRequired || matchingConstructorParameter is null) + { + constructionStrategy = ObjectConstructionStrategy.ParameterizedConstructor; + + var propertyInitializer = new PropertyInitializerGenerationSpec + { + Name = property.MemberName, + ParameterType = property.PropertyType, + MatchesConstructorParameter = matchingConstructorParameter is not null, + ParameterIndex = matchingConstructorParameter?.ParameterIndex ?? paramCount++, + IsNullable = property.PropertyType.CanBeNull && !property.IsSetterNonNullableAnnotation, + }; + + (propertyInitializers ??= new()).Add(propertyInitializer); + } + + static ParameterGenerationSpec? GetMatchingConstructorParameter(PropertyGenerationSpec propSpec, + ParameterGenerationSpec[]? paramGenSpecs) + { + return paramGenSpecs?.FirstOrDefault(MatchesConstructorParameter); + + bool MatchesConstructorParameter(ParameterGenerationSpec paramSpec) + => propSpec.MemberName.Equals(paramSpec.Name, StringComparison.OrdinalIgnoreCase); + } + } + } + + return propertyInitializers; + } + + private TypeRef? GetConverterTypeFromJsonConverterAttribute(INamedTypeSymbol contextType, + ISymbol declaringSymbol, AttributeData attributeData) + { + Debug.Assert(_knownSymbols.JsonConverterAttributeType.IsAssignableFrom(attributeData.AttributeClass)); + + if (!SymbolEqualityComparer.Default.Equals(attributeData.AttributeClass, + _knownSymbols.JsonConverterAttributeType)) + { + ReportDiagnostic( + global::CSharpToJsonSchema.Generators.JsonGen.JsonSourceGenerator.DiagnosticDescriptors + .DerivedJsonConverterAttributesNotSupported, attributeData.GetLocation(), + attributeData.AttributeClass!.ToDisplayString()); + return null; + } + + Debug.Assert(attributeData.ConstructorArguments.Length == 1 && + attributeData.ConstructorArguments[0].Value is null or ITypeSymbol); + var converterType = (ITypeSymbol?)attributeData.ConstructorArguments[0].Value; + return GetConverterTypeFromAttribute(contextType, converterType, declaringSymbol, attributeData); + } + + private TypeRef? GetConverterTypeFromAttribute(INamedTypeSymbol contextType, ITypeSymbol? converterType, + ISymbol declaringSymbol, AttributeData attributeData) + { + if (converterType is not INamedTypeSymbol namedConverterType || + !_knownSymbols.JsonConverterType.IsAssignableFrom(namedConverterType) || + !namedConverterType.Constructors.Any(c => + c.Parameters.Length == 0 && IsSymbolAccessibleWithin(c, within: contextType))) + { + ReportDiagnostic( + global::CSharpToJsonSchema.Generators.JsonGen.JsonSourceGenerator.DiagnosticDescriptors + .JsonConverterAttributeInvalidType, attributeData.GetLocation(), + converterType?.ToDisplayString() ?? "null", declaringSymbol.ToDisplayString()); + return null; + } + + if (_knownSymbols.JsonStringEnumConverterType.IsAssignableFrom(converterType)) + { + ReportDiagnostic( + global::CSharpToJsonSchema.Generators.JsonGen.JsonSourceGenerator.DiagnosticDescriptors + .JsonStringEnumConverterNotSupportedInAot, attributeData.GetLocation(), + declaringSymbol.ToDisplayString()); + } + + return new TypeRef(converterType); + } + + private static string DetermineEffectiveJsonPropertyName(string propertyName, string? jsonPropertyName, + SourceGenerationOptionsSpec? options) + { + if (propertyName != null) + { + return ToCamelCase(propertyName); + } + + // JsonNamingPolicy? instance = options?.GetEffectivePropertyNamingPolicy() switch + // { + // JsonKnownNamingPolicy.CamelCase => JsonNamingPolicy.CamelCase, + // JsonKnownNamingPolicy.SnakeCaseLower => JsonNamingPolicy.SnakeCaseLower, + // JsonKnownNamingPolicy.SnakeCaseUpper => JsonNamingPolicy.SnakeCaseUpper, + // JsonKnownNamingPolicy.KebabCaseLower => JsonNamingPolicy.KebabCaseLower, + // JsonKnownNamingPolicy.KebabCaseUpper => JsonNamingPolicy.KebabCaseUpper, + // _ => null, + // }; + + //return instance?.ConvertName(propertyName) ?? propertyName; + + return propertyName; + } + + public static string ToCamelCase(string str) + { + if (!string.IsNullOrEmpty(str) && str.Length > 1) + { + return char.ToLowerInvariant(str[0]) + str.Substring(1); + } + + return str.ToLowerInvariant(); + } + + private static string? DetermineImmutableCollectionFactoryMethod( + string? immutableCollectionFactoryTypeFullName) + { + return immutableCollectionFactoryTypeFullName is not null + ? $"global::{immutableCollectionFactoryTypeFullName}.CreateRange" + : null; + } + + private static string DeterminePropertyNameFieldName(string effectiveJsonPropertyName) + { + const string PropName = "PropName_"; + + // Use a different prefix to avoid possible collisions with "PropName_" in + // the rare case there is a C# property in a hex format. + const string EncodedPropName = "EncodedPropName_"; + + if (SyntaxFacts.IsValidIdentifier(effectiveJsonPropertyName)) + { + return PropName + effectiveJsonPropertyName; + } + + // Encode the string to a byte[] and then convert to hexadecimal. + // To make the generated code more readable, we could use a different strategy in the future + // such as including the full class name + the CLR property name when there are duplicates, + // but that will create unnecessary JsonEncodedText properties. + byte[] utf8Json = Encoding.UTF8.GetBytes(effectiveJsonPropertyName); + + StringBuilder sb = new StringBuilder( + EncodedPropName, + capacity: EncodedPropName.Length + utf8Json.Length * 2); + + for (int i = 0; i < utf8Json.Length; i++) + { + sb.Append(utf8Json[i].ToString("X2")); // X2 is hex format + } + + return sb.ToString(); + } + + private JsonPrimitiveTypeKind? GetPrimitiveTypeKind(ITypeSymbol type) + { + if (type.IsNumberType()) + { + return JsonPrimitiveTypeKind.Number; + } + + if (type.SpecialType is SpecialType.System_Boolean) + { + return JsonPrimitiveTypeKind.Boolean; + } + + if (type.SpecialType is SpecialType.System_Char) + { + return JsonPrimitiveTypeKind.Char; + } + + SymbolEqualityComparer cmp = SymbolEqualityComparer.Default; + + if (type.SpecialType is SpecialType.System_String or SpecialType.System_DateTime || + cmp.Equals(type, _knownSymbols.DateTimeOffsetType) || cmp.Equals(type, _knownSymbols.GuidType)) + { + return JsonPrimitiveTypeKind.String; + } + + if (cmp.Equals(type, _knownSymbols.ByteArrayType)) + { + return JsonPrimitiveTypeKind.ByteArray; + } + + return null; + } + + private static string GetTypeInfoPropertyName(ITypeSymbol type) + { + if (type is IArrayTypeSymbol arrayType) + { + int rank = arrayType.Rank; + string suffix = rank == 1 ? "Array" : $"Array{rank}D"; // Array, Array2D, Array3D, ... + return GetTypeInfoPropertyName(arrayType.ElementType) + suffix; + } + + if (type is not INamedTypeSymbol namedType || !namedType.IsGenericType) + { + return type.Name; + } + + StringBuilder sb = new(); + + string name = namedType.Name; + + sb.Append(name); + + if (namedType.GetAllTypeArgumentsInScope() is List typeArgsInScope) + { + foreach (ITypeSymbol genericArg in typeArgsInScope) + { + sb.Append(GetTypeInfoPropertyName(genericArg)); + } + } + + return sb.ToString(); + } + + private bool TryGetDeserializationConstructor( + ITypeSymbol type, + bool useDefaultCtorInAnnotatedStructs, + out IMethodSymbol? deserializationCtor) + { + IMethodSymbol? ctorWithAttribute = null; + IMethodSymbol? publicParameterlessCtor = null; + IMethodSymbol? lonePublicCtor = null; + + if (type is not INamedTypeSymbol namedType) + { + deserializationCtor = null; + return false; + } + + IMethodSymbol[] publicCtors = namedType.GetExplicitlyDeclaredInstanceConstructors() + .Where(ctor => ctor.DeclaredAccessibility is Accessibility.Public).ToArray(); + + if (publicCtors.Length == 1) + { + lonePublicCtor = publicCtors[0]; + } + + foreach (IMethodSymbol constructor in publicCtors) + { + if (constructor.ContainsAttribute(_knownSymbols.JsonConstructorAttributeType)) + { + if (ctorWithAttribute != null) + { + deserializationCtor = null; + return false; + } + + ctorWithAttribute = constructor; + } + else if (constructor.Parameters.Length == 0) + { + publicParameterlessCtor = constructor; + } + } + + // Search for non-public ctors with [JsonConstructor]. + foreach (IMethodSymbol constructor in namedType.GetExplicitlyDeclaredInstanceConstructors() + .Where(ctor => ctor.DeclaredAccessibility is not Accessibility.Public)) + { + if (constructor.ContainsAttribute(_knownSymbols.JsonConstructorAttributeType)) + { + if (ctorWithAttribute != null) + { + deserializationCtor = null; + return false; + } + + ctorWithAttribute = constructor; + } + } + + // Structs will use default constructor if attribute isn't used. + if (useDefaultCtorInAnnotatedStructs && type.IsValueType && ctorWithAttribute == null) + { + deserializationCtor = null; + return true; + } + + deserializationCtor = ctorWithAttribute ?? publicParameterlessCtor ?? lonePublicCtor; + return true; + } + + private bool IsSymbolAccessibleWithin(ISymbol symbol, INamedTypeSymbol within) + => true;// symbol.Name.EndsWith("Args") || _knownSymbols.Compilation.IsSymbolAccessibleWithin(symbol, within); + + private bool IsUnsupportedType(ITypeSymbol type) + { + return + // SymbolEqualityComparer.Default.Equals(_knownSymbols.SerializationInfoType, type) || + SymbolEqualityComparer.Default.Equals(_knownSymbols.IntPtrType, type) || + SymbolEqualityComparer.Default.Equals(_knownSymbols.UIntPtrType, type) || + _knownSymbols.MemberInfoType.IsAssignableFrom(type) || + _knownSymbols.DelegateType.IsAssignableFrom(type) || + type is IArrayTypeSymbol { Rank: > 1 }; + } + + private bool IsBuiltInSupportType(ITypeSymbol type) + { + return type.SpecialType is + SpecialType.System_Boolean or + SpecialType.System_Char or + SpecialType.System_DateTime or + SpecialType.System_String or + SpecialType.System_Object || + type.IsNumberType() || + _builtInSupportTypes.Contains(type); + } + + private static HashSet CreateBuiltInSupportTypeSet(KnownTypeSymbols knownSymbols) + { +#pragma warning disable RS1024 // Compare symbols correctly https://github.com/dotnet/roslyn-analyzers/issues/5804 + HashSet builtInSupportTypes = new(SymbolEqualityComparer.Default); +#pragma warning restore + + AddTypeIfNotNull(knownSymbols.ByteArrayType); + AddTypeIfNotNull(knownSymbols.MemoryByteType); + AddTypeIfNotNull(knownSymbols.ReadOnlyMemoryByteType); + AddTypeIfNotNull(knownSymbols.TimeSpanType); + AddTypeIfNotNull(knownSymbols.DateTimeOffsetType); + AddTypeIfNotNull(knownSymbols.DateOnlyType); + AddTypeIfNotNull(knownSymbols.TimeOnlyType); + AddTypeIfNotNull(knownSymbols.Int128Type); + AddTypeIfNotNull(knownSymbols.UInt128Type); + AddTypeIfNotNull(knownSymbols.HalfType); + AddTypeIfNotNull(knownSymbols.GuidType); + AddTypeIfNotNull(knownSymbols.UriType); + AddTypeIfNotNull(knownSymbols.VersionType); + + AddTypeIfNotNull(knownSymbols.JsonArrayType); + AddTypeIfNotNull(knownSymbols.JsonElementType); + AddTypeIfNotNull(knownSymbols.JsonNodeType); + AddTypeIfNotNull(knownSymbols.JsonObjectType); + AddTypeIfNotNull(knownSymbols.JsonValueType); + AddTypeIfNotNull(knownSymbols.JsonDocumentType); + + return builtInSupportTypes; + + void AddTypeIfNotNull(ITypeSymbol? type) + { + if (type != null) + { + builtInSupportTypes.Add(type); + } + } + } + + private readonly struct TypeToGenerate + { + public required ITypeSymbol Type { get; init; } + public required JsonSourceGenerationMode? Mode { get; init; } + public required string? TypeInfoPropertyName { get; init; } + public required Location? Location { get; init; } + public required Location? AttributeLocation { get; init; } + } + } + } +} \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/JsonSourceGenerator.Roslyn4.0.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/JsonSourceGenerator.Roslyn4.0.cs new file mode 100644 index 0000000..c8309e1 --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/JsonSourceGenerator.Roslyn4.0.cs @@ -0,0 +1,140 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Immutable; +using CSharpToJsonSchema.Generators.JsonGen.Base; +using CSharpToJsonSchema.Generators.JsonGen.Helpers; +using CSharpToJsonSchema.Generators.JsonGen.Model; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; +#if !ROSLYN4_4_OR_GREATER +#endif + +namespace CSharpToJsonSchema.Generators.JsonGen +{ + /// + /// Generates source code to optimize serialization and deserialization with JsonSerializer. + /// + [Generator] + public sealed partial class JsonSourceGenerator + { + //private const string JsonSerializableAttributeFullName = "CSharpToJsonSchema.Generators.JsonGen.System.Text.Json.JsonSerializableAttribute"; + private const string JsonSerializableAttributeFullName = "CSharpToJsonSchema.GenerateJsonSchemaAttribute"; +#if ROSLYN4_4_OR_GREATER + public const string SourceGenerationSpecTrackingName = "SourceGenerationSpec"; +#endif + + public void Initialize2(IncrementalGeneratorInitializationContext context) + { +#if LAUNCH_DEBUGGER + System.Diagnostics.Debugger.Launch(); +#endif + IncrementalValueProvider knownTypeSymbols = context.CompilationProvider + .Select((compilation, _) => new KnownTypeSymbols(compilation)); + + IncrementalValuesProvider<(Model.ContextGenerationSpec?, ImmutableEquatableArray)> + contextGenerationSpecs = IncrementalValueProviderExtensions.Combine( + context.CompilationProvider + .ForAttributeWithMetadataName<(InterfaceDeclarationSyntax ContextClass, SemanticModel + SemanticModel)>( +#if !ROSLYN4_4_OR_GREATER + context, +#endif + JsonSerializableAttributeFullName, + (node, _) => node is InterfaceDeclarationSyntax, + (context, _) => + (ContextClass: (InterfaceDeclarationSyntax)context.TargetNode, context.SemanticModel)), + knownTypeSymbols) + .Select(static (tuple, cancellationToken) => + { + Parser parser = new(tuple.Right); + Model.ContextGenerationSpec? contextGenerationSpec = + parser.ParseContextGenerationSpec(tuple.Left.ContextClass, tuple.Left.SemanticModel, + cancellationToken); + ImmutableEquatableArray diagnostics = + parser.Diagnostics.ToImmutableEquatableArray(); + return (contextGenerationSpec, diagnostics); + }) +#if ROSLYN4_4_OR_GREATER + .WithTrackingName(SourceGenerationSpecTrackingName) +#endif + ; + + context.RegisterSourceOutput(contextGenerationSpecs, ReportDiagnosticsAndEmitSource); + } + + public void Initialize(IncrementalGeneratorInitializationContext context) + { +#if LAUNCH_DEBUGGER + System.Diagnostics.Debugger.Launch(); +#endif + IncrementalValueProvider knownTypeSymbols = context.CompilationProvider + .Select((compilation, _) => new KnownTypeSymbols(compilation)); + + IncrementalValuesProvider<(Model.ContextGenerationSpec?, ImmutableEquatableArray)> + contextGenerationSpecs = IncrementalValueProviderExtensions.Combine( + context.SyntaxProvider + .ForAttributeWithMetadataName<(ClassDeclarationSyntax ContextClass, SemanticModel + SemanticModel)>( +#if !ROSLYN4_4_OR_GREATER + context, +#endif + JsonSerializableAttributeFullName, + (node, _) => node is ClassDeclarationSyntax, + (context, _) => + (ContextClass: (ClassDeclarationSyntax)context.TargetNode, context.SemanticModel)), + knownTypeSymbols) + .Select(static (tuple, cancellationToken) => + { + Parser parser = new(tuple.Right); + Model.ContextGenerationSpec? contextGenerationSpec = + parser.ParseContextGenerationSpec(tuple.Left.ContextClass, tuple.Left.SemanticModel, + cancellationToken); + ImmutableEquatableArray diagnostics = + parser.Diagnostics.ToImmutableEquatableArray(); + return (contextGenerationSpec, diagnostics); + }) +#if ROSLYN4_4_OR_GREATER + .WithTrackingName(SourceGenerationSpecTrackingName) +#endif + ; + + context.RegisterSourceOutput(contextGenerationSpecs, ReportDiagnosticsAndEmitSource); + } + + private void ReportDiagnosticsAndEmitSource(SourceProductionContext sourceProductionContext, (Model.ContextGenerationSpec? ContextGenerationSpec, ImmutableEquatableArray Diagnostics) input) + { + // // Report any diagnostics ahead of emitting. + // foreach (DiagnosticInfo diagnostic in input.Diagnostics) + // { + // sourceProductionContext.ReportDiagnostic(diagnostic.CreateDiagnostic()); + // } + + if (input.ContextGenerationSpec is null) + { + return; + } + + OnSourceEmitting?.Invoke(ImmutableArray.Create(input.ContextGenerationSpec)); + Emitter emitter = new(sourceProductionContext); + emitter.Emit(input.ContextGenerationSpec); + } + + /// + /// Instrumentation helper for unit tests. + /// + public Action>? OnSourceEmitting { get; init; } + + private partial class Emitter + { + private readonly SourceProductionContext _context; + + public Emitter(SourceProductionContext context) + => _context = context; + + private partial void AddSource(string hintName, SourceText sourceText) + => _context.AddSource(hintName, sourceText); + } + } +} diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Model/ClassType.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Model/ClassType.cs new file mode 100644 index 0000000..e5414aa --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Model/ClassType.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace CSharpToJsonSchema.Generators.JsonGen.Model +{ + public enum ClassType + { + /// + /// Types that are not supported at all and will be warned on/skipped by the source generator. + /// + TypeUnsupportedBySourceGen = 0, + Object = 1, + BuiltInSupportType = 2, + /// + /// Known types such as System.Type and System.IntPtr that throw NotSupportedException at runtime. + /// + UnsupportedType = 3, + TypeWithDesignTimeProvidedCustomConverter = 4, + Enumerable = 5, + Dictionary = 6, + Nullable = 7, + Enum = 8 + } +} diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Model/CollectionType.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Model/CollectionType.cs new file mode 100644 index 0000000..71e2c11 --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Model/CollectionType.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace CSharpToJsonSchema.Generators.JsonGen.Model +{ + public enum CollectionType + { + NotApplicable, + // Dictionary types + IDictionary, + Dictionary, + ImmutableDictionary, + IDictionaryOfTKeyTValue, + IReadOnlyDictionary, + // Non-dictionary types + Array, + List, + IEnumerable, + IList, + IListOfT, + ISet, + ICollectionOfT, + StackOfT, + QueueOfT, + ConcurrentStack, + ConcurrentQueue, + IAsyncEnumerableOfT, + IEnumerableOfT, + Stack, + Queue, + ImmutableEnumerable, + MemoryOfT, + ReadOnlyMemoryOfT + } +} diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Model/ContextGenerationSpec.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Model/ContextGenerationSpec.cs new file mode 100644 index 0000000..2e9a75a --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Model/ContextGenerationSpec.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using CSharpToJsonSchema.Generators.JsonGen.Base; + +namespace CSharpToJsonSchema.Generators.JsonGen.Model +{ + /// + /// Represents the set of input types and options needed to provide an + /// implementation for a user-provided JsonSerializerContext-derived type. + /// + /// + /// Type needs to be cacheable as a Roslyn incremental value so it must be + /// + /// 1) immutable and + /// 2) implement structural (pointwise) equality comparison. + /// + /// We can get these properties for free provided that we + /// + /// a) define the type as an immutable C# record and + /// b) ensure all nested members are also immutable and implement structural equality. + /// + /// When adding new members to the type, please ensure that these properties + /// are satisfied otherwise we risk breaking incremental caching in the source generator! + /// + [DebuggerDisplay("ContextType = {ContextType.Name}")] + public sealed record ContextGenerationSpec + { + public required TypeRef ContextType { get; init; } + + public required ImmutableEquatableArray GeneratedTypes { get; init; } + + public required string? Namespace { get; init; } + + public required ImmutableEquatableArray ContextClassDeclarations { get; init; } + + public required SourceGenerationOptionsSpec? GeneratedOptionsSpec { get; init; } + } +} diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Model/JsonPrimitiveTypeKind.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Model/JsonPrimitiveTypeKind.cs new file mode 100644 index 0000000..62f6705 --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Model/JsonPrimitiveTypeKind.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace CSharpToJsonSchema.Generators.JsonGen.Model +{ + public enum JsonPrimitiveTypeKind + { + String, + Boolean, + ByteArray, + Char, + Number + } +} diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Model/ObjectConstructionStrategy.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Model/ObjectConstructionStrategy.cs new file mode 100644 index 0000000..be67ce4 --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Model/ObjectConstructionStrategy.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace CSharpToJsonSchema.Generators.JsonGen.Model +{ + /// + /// Indicates which kind of constructor an object is to be created with. + /// + public enum ObjectConstructionStrategy + { + /// + /// Object is abstract or an interface. + /// + NotApplicable = 0, + /// + /// Object should be created with a parameterless constructor. + /// + ParameterlessConstructor = 1, + /// + /// Object should be created with a parameterized constructor. + /// + ParameterizedConstructor = 2, + } +} diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Model/ParameterGenerationSpec.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Model/ParameterGenerationSpec.cs new file mode 100644 index 0000000..7e37ce2 --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Model/ParameterGenerationSpec.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace CSharpToJsonSchema.Generators.JsonGen.Model +{ + /// + /// Models a constructor parameter for a generated type. + /// + /// + /// Type needs to be cacheable as a Roslyn incremental value so it must be + /// + /// 1) immutable and + /// 2) implement structural (pointwise) equality comparison. + /// + /// We can get these properties for free provided that we + /// + /// a) define the type as an immutable C# record and + /// b) ensure all nested members are also immutable and implement structural equality. + /// + /// When adding new members to the type, please ensure that these properties + /// are satisfied otherwise we risk breaking incremental caching in the source generator! + /// + public sealed record ParameterGenerationSpec + { + public required TypeRef ParameterType { get; init; } + public required string Name { get; init; } + public required bool HasDefaultValue { get; init; } + + // The default value of a constructor parameter can only be a constant + // so it always satisfies the structural equality requirement for the record. + public required object? DefaultValue { get; init; } + public required int ParameterIndex { get; init; } + public required bool IsNullable { get; init; } + } +} diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Model/PropertyGenerationSpec.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Model/PropertyGenerationSpec.cs new file mode 100644 index 0000000..8a8d67d --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Model/PropertyGenerationSpec.cs @@ -0,0 +1,196 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using CSharpToJsonSchema.Generators.JsonGen.System.Text.Json; + +namespace CSharpToJsonSchema.Generators.JsonGen.Model +{ + /// + /// Models a property for a generated type. + /// + /// + /// Type needs to be cacheable as a Roslyn incremental value so it must be + /// + /// 1) immutable and + /// 2) implement structural (pointwise) equality comparison. + /// + /// We can get these properties for free provided that we + /// + /// a) define the type as an immutable C# record and + /// b) ensure all nested members are also immutable and implement structural equality. + /// + /// When adding new members to the type, please ensure that these properties + /// are satisfied otherwise we risk breaking incremental caching in the source generator! + /// + [DebuggerDisplay("Name = {MemberName}, Type = {PropertyType.Name}")] + public sealed record PropertyGenerationSpec + { + /// + /// The exact name specified in the source code. This might be different + /// from the because source code might be decorated + /// with '@' for reserved keywords, e.g. public string @event { get; set; } + /// + public required string NameSpecifiedInSourceCode { get; init; } + + public required string MemberName { get; init; } + + /// + /// Is this a property or a field? + /// + public required bool IsProperty { get; init; } + + /// + /// If representing a property, returns true if either the getter or setter are public. + /// + public required bool IsPublic { get; init; } + + public required bool IsVirtual { get; init; } + + /// + /// The property name specified via JsonPropertyNameAttribute, if available. + /// + public required string? JsonPropertyName { get; init; } + + /// + /// The pre-determined JSON property name, accounting for + /// specified ahead-of-time via . + /// Only used in fast-path serialization logic. + /// + public required string EffectiveJsonPropertyName { get; init; } + + /// + /// The field identifier used for storing JsonEncodedText for use by the fast-path serializer. + /// + public required string PropertyNameFieldName { get; init; } + + /// + /// Whether the property has a set method. + /// + public required bool IsReadOnly { get; init; } + + /// + /// Whether the property is marked `required`. + /// + public required bool IsRequired { get; init; } + + /// + /// The property is marked with JsonRequiredAttribute. + /// + public required bool HasJsonRequiredAttribute { get; init; } + + /// + /// Whether the property has an init-only set method. + /// + public required bool IsInitOnlySetter { get; init; } + + /// + /// Whether the property has a public or internal (only usable when JsonIncludeAttribute is specified) + /// getter that can be referenced in generated source code. + /// + public required bool CanUseGetter { get; init; } + + /// + /// Whether the property has a public or internal (only usable when JsonIncludeAttribute is specified) + /// setter that can be referenced in generated source code. + /// + public required bool CanUseSetter { get; init; } + + /// + /// Whether the property getter returns a nullable type with a non-nullable annotation. + /// + public required bool IsGetterNonNullableAnnotation { get; init; } + + /// + /// Whether the property setter accepts a nullable type with a non-nullable annotation. + /// + public required bool IsSetterNonNullableAnnotation { get; init; } + + /// + /// The for the property. + /// + public required JsonIgnoreCondition? DefaultIgnoreCondition { get; init; } + + /// + /// The for the property. + /// + public required JsonNumberHandling? NumberHandling { get; init; } + + /// + /// The for the property. + /// + public required JsonObjectCreationHandling? ObjectCreationHandling { get; init; } + + /// + /// The serialization order of the property. + /// + public required int Order { get; init; } + + /// + /// Whether the property has the JsonIncludeAttribute. If so, non-public accessors can be used for (de)serialziation. + /// + public required bool HasJsonInclude { get; init; } + + /// + /// Whether the property has the JsonExtensionDataAttribute. + /// + public required bool IsExtensionData { get; init; } + + /// + /// Gets a reference to the property type. + /// + public required TypeRef PropertyType { get; init; } + + /// + /// Gets a reference to the declaring type of the property. + /// + public required TypeRef DeclaringType { get; init; } + + /// + /// Design-time specified custom converter type. + /// + public required TypeRef? ConverterType { get; init; } + + /// + /// Determines if the specified property should be included in the fast-path method body. + /// + public bool ShouldIncludePropertyForFastPath(ContextGenerationSpec contextSpec) + { + // Discard ignored properties + if (DefaultIgnoreCondition is JsonIgnoreCondition.Always) + { + return false; + } + + // Discard properties without getters + if (!CanUseGetter) + { + return false; + } + + // Discard fields when JsonInclude or IncludeFields aren't enabled. + if (!IsProperty && !HasJsonInclude && contextSpec.GeneratedOptionsSpec?.IncludeFields != true) + { + return false; + } + + // Ignore read-only properties/fields if enabled in configuration. + if (IsReadOnly) + { + if (IsProperty) + { + if (contextSpec.GeneratedOptionsSpec?.IgnoreReadOnlyProperties == true) + { + return false; + } + } + else if (contextSpec.GeneratedOptionsSpec?.IgnoreReadOnlyFields == true) + { + return false; + } + } + + return true; + } + } +} diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Model/PropertyInitializerGenerationSpec.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Model/PropertyInitializerGenerationSpec.cs new file mode 100644 index 0000000..37c04dd --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Model/PropertyInitializerGenerationSpec.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace CSharpToJsonSchema.Generators.JsonGen.Model +{ + /// + /// Models a member initializer for a generated type. + /// + /// + /// Type needs to be cacheable as a Roslyn incremental value so it must be + /// + /// 1) immutable and + /// 2) implement structural (pointwise) equality comparison. + /// + /// We can get these properties for free provided that we + /// + /// a) define the type as an immutable C# record and + /// b) ensure all nested members are also immutable and implement structural equality. + /// + /// When adding new members to the type, please ensure that these properties + /// are satisfied otherwise we risk breaking incremental caching in the source generator! + /// + public sealed record PropertyInitializerGenerationSpec + { + public required string Name { get; init; } + + public required TypeRef ParameterType { get; init; } + + public required int ParameterIndex { get; init; } + + public required bool MatchesConstructorParameter { get; init; } + + public required bool IsNullable { get; init; } + } +} diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Model/SourceGenerationOptionsSpec.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Model/SourceGenerationOptionsSpec.cs new file mode 100644 index 0000000..abef883 --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Model/SourceGenerationOptionsSpec.cs @@ -0,0 +1,74 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using CSharpToJsonSchema.Generators.JsonGen.Base; +using CSharpToJsonSchema.Generators.JsonGen.System.Text.Json; + +namespace CSharpToJsonSchema.Generators.JsonGen.Model +{ + /// + /// Models compile-time configuration of . + /// Properties are made nullable to model the presence or absence of a given configuration. + /// + public sealed record SourceGenerationOptionsSpec + { + public required JsonSourceGenerationMode? GenerationMode { get; init; } + + public required JsonSerializerDefaults? Defaults { get; init; } + + public required bool? AllowOutOfOrderMetadataProperties { get; init; } + + public required bool? AllowTrailingCommas { get; init; } + + public required ImmutableEquatableArray? Converters { get; init; } + + public required int? DefaultBufferSize { get; init; } + + public required JsonIgnoreCondition? DefaultIgnoreCondition { get; init; } + + public required JsonKnownNamingPolicy? DictionaryKeyPolicy { get; init; } + + public required bool? RespectNullableAnnotations { get; init; } + + public required bool? RespectRequiredConstructorParameters { get; init; } + + public required bool? IgnoreReadOnlyFields { get; init; } + + public required bool? IgnoreReadOnlyProperties { get; init; } + + public required bool? IncludeFields { get; init; } + + public required int? MaxDepth { get; init; } + + public required string? NewLine { get; init; } + + public required JsonNumberHandling? NumberHandling { get; init; } + + public required JsonObjectCreationHandling? PreferredObjectCreationHandling { get; init; } + + public required bool? PropertyNameCaseInsensitive { get; init; } + + public required JsonKnownNamingPolicy? PropertyNamingPolicy { get; init; } + + public required JsonCommentHandling? ReadCommentHandling { get; init; } + + public required JsonKnownReferenceHandler? ReferenceHandler { get; init; } + + public required JsonUnknownTypeHandling? UnknownTypeHandling { get; init; } + + public required JsonUnmappedMemberHandling? UnmappedMemberHandling { get; init; } + + public required bool? UseStringEnumConverter { get; init; } + + public required bool? WriteIndented { get; init; } + + public required char? IndentCharacter { get; init; } + + public required int? IndentSize { get; init; } + + // public JsonKnownNamingPolicy? GetEffectivePropertyNamingPolicy() + // => PropertyNamingPolicy ?? (Defaults is JsonSerializerDefaults.Web ? JsonKnownNamingPolicy.CamelCase : null); + public JsonKnownNamingPolicy? GetEffectivePropertyNamingPolicy() + => PropertyNamingPolicy ?? (Defaults is JsonSerializerDefaults.Web ? JsonKnownNamingPolicy.CamelCase : null); + } +} diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Model/TypeGenerationSpec.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Model/TypeGenerationSpec.cs new file mode 100644 index 0000000..bc09649 --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Model/TypeGenerationSpec.cs @@ -0,0 +1,146 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using CSharpToJsonSchema.Generators.JsonGen.Base; +using CSharpToJsonSchema.Generators.JsonGen.Helpers; +using CSharpToJsonSchema.Generators.JsonGen.System.Text.Json; +using Microsoft.CodeAnalysis; + +namespace CSharpToJsonSchema.Generators.JsonGen.Model +{ + /// + /// Models a generated type. + /// + /// + /// Type needs to be cacheable as a Roslyn incremental value so it must be + /// + /// 1) immutable and + /// 2) implement structural (pointwise) equality comparison. + /// + /// We can get these properties for free provided that we + /// + /// a) define the type as an immutable C# record and + /// b) ensure all nested members are also immutable and implement structural equality. + /// + /// When adding new members to the type, please ensure that these properties + /// are satisfied otherwise we risk breaking incremental caching in the source generator! + /// + [DebuggerDisplay("Type = {TypeRef.Name}, ClassType = {ClassType}")] + public sealed record TypeGenerationSpec + { + /// + /// The type being generated. + /// + public required TypeRef TypeRef { get; init; } + + /// + /// The name of the public JsonTypeInfo<T> property for this type on the generated context class. + /// For example, if the context class is named MyJsonContext, and the value of this property is JsonMessage; + /// then users will call MyJsonContext.JsonMessage to access generated metadata for the type. + /// + public required string TypeInfoPropertyName { get; init; } + + public required JsonSourceGenerationMode GenerationMode { get; init; } + + public required JsonPrimitiveTypeKind? PrimitiveTypeKind { get; init; } + + public required ClassType ClassType { get; init; } + + public required bool ImplementsIJsonOnSerialized { get; init; } + public required bool ImplementsIJsonOnSerializing { get; init; } + + public required bool IsPolymorphic { get; init; } + + public required bool IsValueTuple { get; init; } + + public required JsonNumberHandling? NumberHandling { get; init; } + public required JsonUnmappedMemberHandling? UnmappedMemberHandling { get; init; } + public required JsonObjectCreationHandling? PreferredPropertyObjectCreationHandling { get; init; } + + /// + /// List of all properties without conflict resolution or sorting to be generated for the metadata-based serializer. + /// + public required ImmutableEquatableArray PropertyGenSpecs { get; init; } + + /// + /// List of properties with compile-time conflict resolution and sorting to be generated for the fast-path serializer. + /// Contains indices pointing to . A value in the case of object types + /// indicates that a naming conflict was found and that an exception throwing stub should be emitted in the fast-path method. + /// + public required ImmutableEquatableArray? FastPathPropertyIndices { get; init; } + + public required ImmutableEquatableArray CtorParamGenSpecs { get; init; } + + public required ImmutableEquatableArray PropertyInitializerSpecs { get; init; } + + public required CollectionType CollectionType { get; init; } + + public required TypeRef? CollectionKeyType { get; init; } + + public required TypeRef? CollectionValueType { get; init; } + + public required ObjectConstructionStrategy ConstructionStrategy { get; init; } + + public required bool ConstructorSetsRequiredParameters { get; init; } + + public required TypeRef? NullableUnderlyingType { get; init; } + + /// + /// Supports deserialization of extension data dictionaries typed as I[ReadOnly]Dictionary<string, object/JsonElement>. + /// Specifies a concrete type to instantiate, which would be Dictionary<string, object/JsonElement>. + /// + public required TypeRef? RuntimeTypeRef { get; init; } + + public required bool HasExtensionDataPropertyType { get; init; } + + public required TypeRef? ConverterType { get; init; } + + public required string? ImmutableCollectionFactoryMethod { get; init; } + + public bool IsFastPathSupported() + { + if (IsPolymorphic) + { + return false; + } + + if (JsonHelpers.RequiresSpecialNumberHandlingOnWrite(NumberHandling)) + { + return false; + } + + switch (ClassType) + { + case ClassType.Object: + if (HasExtensionDataPropertyType) + { + return false; + } + + foreach (PropertyGenerationSpec property in PropertyGenSpecs) + { + if (property.PropertyType.SpecialType is SpecialType.System_Object || + property.NumberHandling != null || + property.ConverterType != null) + { + return false; + } + } + + return true; + + case ClassType.Enumerable: + return CollectionType != CollectionType.IAsyncEnumerableOfT && + CollectionValueType!.SpecialType is not SpecialType.System_Object; + + case ClassType.Dictionary: + return CollectionKeyType!.SpecialType is SpecialType.System_String && + CollectionValueType!.SpecialType is not SpecialType.System_Object; + + default: + return false; + } + } + } +} diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Model/TypeRef.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Model/TypeRef.cs new file mode 100644 index 0000000..caffca6 --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Model/TypeRef.cs @@ -0,0 +1,38 @@ +using System.Diagnostics; +using CSharpToJsonSchema.Generators.JsonGen.Helpers; +using Microsoft.CodeAnalysis; + +namespace CSharpToJsonSchema.Generators.JsonGen.Model; + +/// +/// An equatable value representing type identity. +/// +[DebuggerDisplay("Name = {Name}")] +public sealed class TypeRef : IEquatable +{ + public TypeRef(ITypeSymbol type) + { + Name = type.Name; + FullyQualifiedName = type.GetFullyQualifiedName(); + IsValueType = type.IsValueType; + TypeKind = type.TypeKind; + SpecialType = type.OriginalDefinition.SpecialType; + } + + public string Name { get; } + + /// + /// Fully qualified assembly name, prefixed with "global::", e.g. global::System.Numerics.BigInteger. + /// + public string FullyQualifiedName { get; } + + public bool IsValueType { get; } + public TypeKind TypeKind { get; } + public SpecialType SpecialType { get; } + + public bool CanBeNull => !IsValueType || SpecialType is SpecialType.System_Nullable_T; + + public bool Equals(TypeRef? other) => other != null && FullyQualifiedName == other.FullyQualifiedName; + public override bool Equals(object? obj) => Equals(obj as TypeRef); + public override int GetHashCode() => FullyQualifiedName.GetHashCode(); +} \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Resources/Strings.resx b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Resources/Strings.resx new file mode 100644 index 0000000..5c2d069 --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Resources/Strings.resx @@ -0,0 +1,216 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + There are multiple types named '{0}'. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. + + + Duplicate type name. + + + Did not generate serialization metadata for type '{0}'. + + + Did not generate serialization metadata for type. + + + Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation. + + + Derived 'JsonSerializerContext' types and all containing types must be partial. + + + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. + + + Type has multiple constructors annotated with JsonConstructorAttribute. + + + Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'. + + + Type has multiple members annotated with JsonExtensionDataAttribute. + + + The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'. + + + Data extension property type invalid. + + + Deserialization of init-only properties is currently not supported in source generation mode. + + + The type '{0}' defines init-only properties, deserialization of which is currently not supported in source generation mode. + + + Inaccessible properties annotated with the JsonIncludeAttribute are not supported in source generation mode. + + + The member '{0}.{1}' has been annotated with the JsonIncludeAttribute but is not visible to the source generator. + + + 'JsonDerivedTypeAttribute' is not supported in 'JsonSourceGenerationMode.Serialization'. + + + Type '{0}' is annotated with 'JsonDerivedTypeAttribute' which is not supported in 'JsonSourceGenerationMode.Serialization'. + + + The non-generic 'JsonStringEnumConverter' requires dynamic code. + + + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. + + + The 'JsonConverterAttribute.Type' contains an invalid or inaccessible argument. + + + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. + + + Attributes deriving from JsonConverterAttribute are not supported by the source generator. + + + The custom attribute '{0}' deriving from JsonConverterAttribute is not supported by the source generator. + + + C# language version not supported by the source generator. + + + The System.Text.Json source generator is not available in C# {0}. Please use language version {1} or greater. + + + Constructor annotated with JsonConstructorAttribute is inaccessible. + + + The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. + + + Types annotated with JsonSerializableAttribute must be classes deriving from JsonSerializerContext. + + + The type '{0}' has been annotated with JsonSerializableAttribute but does not derive from JsonSerializerContext. No source code will be generated. + + + Type includes ref like property, field or constructor parameter. + + + The type '{0}' includes the ref like property, field or constructor parameter '{1}'. No source code will be generated for the property, field or constructor. + + diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Resources/xlf/Strings.cs.xlf b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Resources/xlf/Strings.cs.xlf new file mode 100644 index 0000000..49ede1a --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Resources/xlf/Strings.cs.xlf @@ -0,0 +1,167 @@ + + + + + + Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation. + Odvozený typ JsonSerializerContext {0} určuje typy serializovatelné na JSON. Typ a všechny obsahující typy musí být částečné, aby se zahájilo generování zdroje. + + + + Derived 'JsonSerializerContext' types and all containing types must be partial. + Odvozené typy JsonSerializerContext a všechny obsahující typy musí být částečné. + + + + The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'. + Vlastnost rozšíření dat {0}.{1} není platná. Musí implementovat IDictionary<string, JsonElement> nebo IDictionary<string, object>, nebo být JsonObject. + + + + Data extension property type invalid. + Typ vlastnosti rozšíření dat není platný + + + + The custom attribute '{0}' deriving from JsonConverterAttribute is not supported by the source generator. + Generátor zdroje nepodporuje vlastní atribut „{0}“ odvozený od atributu JsonConverterAttribute. + + + + Attributes deriving from JsonConverterAttribute are not supported by the source generator. + Zdrojový generátor nepodporuje atributy odvozené od atributu JsonConverterAttribute. + + + + There are multiple types named '{0}'. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. + Existuje více typů s názvem „{0}“. Zdroj byl vygenerován pro první zjištěný zdroj. K vyřešení této kolize použijte JsonSerializableAttribute.TypeInfoPropertyName. + + + + Duplicate type name. + Duplicitní název typu + + + + Type '{0}' is annotated with 'JsonDerivedTypeAttribute' which is not supported in 'JsonSourceGenerationMode.Serialization'. + Typ {0} je anotován atributem JsonDerivedTypeAttribute, který se v JsonSourceGenerationMode.Serialization nepodporuje. + + + + 'JsonDerivedTypeAttribute' is not supported in 'JsonSourceGenerationMode.Serialization'. + Atribut JsonDerivedTypeAttribute se v JsonSourceGenerationMode.Serialization nepodporuje. + + + + The member '{0}.{1}' has been annotated with the JsonIncludeAttribute but is not visible to the source generator. + Člen {0}.{1} má anotaci od JsonIncludeAttribute, ale není pro zdrojový generátor viditelný. + + + + Inaccessible properties annotated with the JsonIncludeAttribute are not supported in source generation mode. + Nepřístupné vlastnosti anotované s JsonIncludeAttribute se v režimu generování zdroje nepodporují. + + + + The type '{0}' defines init-only properties, deserialization of which is currently not supported in source generation mode. + Typ {0} definuje vlastnosti pouze pro inicializaci, jejichž deserializace se v režimu generování zdroje v současnosti nepodporuje. + + + + Deserialization of init-only properties is currently not supported in source generation mode. + Deserializace vlastností pouze pro inicializaci se v současnosti v režimu generování zdroje nepodporuje. + + + + The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. + Konstruktor typu {0} byl opatřen poznámkou s atributem JsonConstructorAttribute, ale zdrojový generátor k němu nemá přístup. + + + + Constructor annotated with JsonConstructorAttribute is inaccessible. + Konstruktor anotovaný atributem JsonConstructorAttribute je nepřístupný. + + + + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. + Typ JsonConverterAttribute {0} specifikovaný u členu {1} není typem konvertoru nebo neobsahuje přístupný konstruktor bez parametrů. + + + + The 'JsonConverterAttribute.Type' contains an invalid or inaccessible argument. + JsonConverterAttribute.Type obsahuje neplatný nebo nepřístupný argument. + + + + The type '{0}' has been annotated with JsonSerializableAttribute but does not derive from JsonSerializerContext. No source code will be generated. + Typ „{0}“ byl anotován atributem JsonSerializableAttribute, ale není odvozen od třídy JsonSerializerContext. Nebude vygenerován žádný zdrojový kód. + + + + Types annotated with JsonSerializableAttribute must be classes deriving from JsonSerializerContext. + Typy anotované atributem JsonSerializableAttribute musí být třídy odvozené od třídy JsonSerializerContext. + + + + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. + Člen {0} byl opatřen poznámkou jsonStringEnumConverter, což není v nativním AOT podporováno. Zvažte použití obecného objektu JsonStringEnumConverter<TEnum>. + + + + The non-generic 'JsonStringEnumConverter' requires dynamic code. + Neobecný objekt JsonStringEnumConverter vyžaduje dynamický kód. + + + + The System.Text.Json source generator is not available in C# {0}. Please use language version {1} or greater. + Zdrojový generátor System.Text.Json není k dispozici v jazyce C# {0}. Použijte prosím jazykovou verzi {1} nebo vyšší. + + + + C# language version not supported by the source generator. + Zdrojový generátor nepodporuje jazykovou verzi jazyka C#. + + + + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. + Typ {0} má více konstruktorů anotovaných s JsonConstructorAttribute. + + + + Type has multiple constructors annotated with JsonConstructorAttribute. + Typ obsahuje více konstruktorů anotovaných s JsonConstructorAttribute. + + + + Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'. + Typ {0} obsahuje více členů s komentářem JsonExtensionDataAttribute. + + + + Type has multiple members annotated with JsonExtensionDataAttribute. + Typ obsahuje více členů s komentářem JsonExtensionDataAttribute + + + + The type '{0}' includes the ref like property, field or constructor parameter '{1}'. No source code will be generated for the property, field or constructor. + Typ {0} zahrnuje ref-like parametr vlastnosti, pole nebo konstruktoru {1}. Pro vlastnost, pole nebo konstruktor se nevygeneruje žádný zdrojový kód. + + + + Type includes ref like property, field or constructor parameter. + Typ zahrnuje ref-like parametr vlastnosti, pole nebo konstruktoru. + + + + Did not generate serialization metadata for type '{0}'. + Nevygenerovala se metadata serializace pro typ {0}. + + + + Did not generate serialization metadata for type. + Nevygenerovala se metadata serializace pro typ + + + + + \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Resources/xlf/Strings.de.xlf b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Resources/xlf/Strings.de.xlf new file mode 100644 index 0000000..35bfee5 --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Resources/xlf/Strings.de.xlf @@ -0,0 +1,167 @@ + + + + + + Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation. + Der abgeleitete JsonSerializerContext-Typ "{0}" gibt serialisierbare JSON-Typen an. Der Typ und alle enthaltenden Typen müssen partiell sein, um die Quellgenerierung zu starten. + + + + Derived 'JsonSerializerContext' types and all containing types must be partial. + Abgeleitete JsonSerializerContext-Typen und alle enthaltenden Typen müssen partiell sein. + + + + The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'. + Die Datenerweiterungseigenschaft „{0}.{1}“ ist ungültig. Sie muss „IDictionary<string, JsonElement>“ oder „IDictionary<string, object>“ oder „JsonObject“ implementieren. + + + + Data extension property type invalid. + Der Typ der Datenerweiterungseigenschaft ist ungültig. + + + + The custom attribute '{0}' deriving from JsonConverterAttribute is not supported by the source generator. + Das von JsonConverterAttribute abgeleitete benutzerdefinierte Attribut "{0}" wird vom Quellgenerator nicht unterstützt. + + + + Attributes deriving from JsonConverterAttribute are not supported by the source generator. + Von JsonConverterAttribute abgeleitete Attribute werden vom Quellgenerator nicht unterstützt. + + + + There are multiple types named '{0}'. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. + Es gibt mehrere Typen mit dem Namen "{0}". Die Quelle wurde für die erste erkannte Quelle generiert. Verwenden Sie "JsonSerializableAttribute.TypeInfoPropertyName", um diesen Konflikt zu beheben. + + + + Duplicate type name. + Doppelter Typname + + + + Type '{0}' is annotated with 'JsonDerivedTypeAttribute' which is not supported in 'JsonSourceGenerationMode.Serialization'. + Der Typ „{0}“ ist mit „JsonDerivedTypeAttribute“ versehen, was in „JsonSourceGenerationMode.Serialization“ nicht unterstützt wird. + + + + 'JsonDerivedTypeAttribute' is not supported in 'JsonSourceGenerationMode.Serialization'. + „JsonDerivedTypeAttribute“ wird in „JsonSourceGenerationMode.Serialization“ nicht unterstützt. + + + + The member '{0}.{1}' has been annotated with the JsonIncludeAttribute but is not visible to the source generator. + Der Member "{0}. {1}" wurde mit dem JsonIncludeAttribute versehen, ist jedoch für den Quellgenerator nicht sichtbar. + + + + Inaccessible properties annotated with the JsonIncludeAttribute are not supported in source generation mode. + Nicht zugängliche Eigenschaften, die mit dem JsonIncludeAttribute versehen sind, werden im Quellgenerierungsmodus nicht unterstützt. + + + + The type '{0}' defines init-only properties, deserialization of which is currently not supported in source generation mode. + Der Typ "{0}" definiert nur init-Eigenschaften, deren Deserialisierung im Quellgenerierungsmodus derzeit nicht unterstützt wird. + + + + Deserialization of init-only properties is currently not supported in source generation mode. + Die Deserialisierung von reinen init-Eigenschaften wird im Quellgenerierungsmodus derzeit nicht unterstützt. + + + + The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. + Der Konstruktor für den Typ "{0}" wurde mit dem Kommentar "JsonConstructorAttribute" versehen, aber der Quellgenerator kann nicht darauf zugreifen. + + + + Constructor annotated with JsonConstructorAttribute is inaccessible. + Auf den Konstruktor mit dem Kommentar "JsonConstructorAttribute" kann nicht zugegriffen werden. + + + + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. + Der für den Member "{1}" angegebene JsonConverterAttribute-Typ "{0}" ist kein Konvertertyp oder enthält keinen parameterlosen Konstruktor, auf den zugegriffen werden kann. + + + + The 'JsonConverterAttribute.Type' contains an invalid or inaccessible argument. + "JsonConverterAttribute.Type" enthält ein ungültiges oder nicht zugängliches Argument. + + + + The type '{0}' has been annotated with JsonSerializableAttribute but does not derive from JsonSerializerContext. No source code will be generated. + Der Typ "{0}" wurde mit JsonSerializableAttribute kommentiert, ist aber nicht von JsonSerializerContext abgeleitet. Es wird kein Quellcode generiert. + + + + Types annotated with JsonSerializableAttribute must be classes deriving from JsonSerializerContext. + Mit JsonSerializableAttribute kommentierte Typen müssen Klassen sein, die von JsonSerializerContext abgeleitet werden. + + + + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. + Der Member "{0}" wurde mit "JsonStringEnumConverter" kommentiert, was in nativem AOT nicht unterstützt wird. Erwägen Sie stattdessen die Verwendung des generischen "JsonStringEnumConverter<TEnum>". + + + + The non-generic 'JsonStringEnumConverter' requires dynamic code. + Der nicht generische "JsonStringEnumConverter" erfordert dynamischen Code. + + + + The System.Text.Json source generator is not available in C# {0}. Please use language version {1} or greater. + Der System.Text.Json-Quellgenerator ist in C# {0} nicht verfügbar. Verwenden Sie die Sprachversion {1} oder höher. + + + + C# language version not supported by the source generator. + Die C#-Sprachversion wird vom Quellgenerator nicht unterstützt. + + + + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. + Typ "{0}" weist mehrere Konstruktoren mit dem Kommentar "JsonConstructorAttribute" auf. + + + + Type has multiple constructors annotated with JsonConstructorAttribute. + Der Typ weist mehrere Konstruktoren mit dem Kommentar JsonConstructorAttribute auf. + + + + Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'. + Typ „{0}“ enthält mehrere Elemente, die mit dem „JsonExtensionDataAttribute“ versehen sind. + + + + Type has multiple members annotated with JsonExtensionDataAttribute. + Der Typ enthält mehrere Elemente, die mit dem JsonExtensionDataAttribute versehen sind. + + + + The type '{0}' includes the ref like property, field or constructor parameter '{1}'. No source code will be generated for the property, field or constructor. + Der Typ „{0}“ enthält die Verweise wie Eigenschaft, Feld oder Konstruktorparameter „{1}“. Für die Eigenschaft, das Feld oder den Konstruktor wird kein Quellcode generiert. + + + + Type includes ref like property, field or constructor parameter. + Der Typ enthält Verweise wie Eigenschaft, Feld oder Konstruktorparameter. + + + + Did not generate serialization metadata for type '{0}'. + Die Serialisierungsmetadaten für den Typ "{0}" wurden nicht generiert. + + + + Did not generate serialization metadata for type. + Serialisierungsmetadaten für den Typ wurden nicht generiert + + + + + \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Resources/xlf/Strings.es.xlf b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Resources/xlf/Strings.es.xlf new file mode 100644 index 0000000..3fdbb1b --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Resources/xlf/Strings.es.xlf @@ -0,0 +1,167 @@ + + + + + + Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation. + El tipo derivado "JsonSerializerContext" "{0}" especifica tipos de JSON serializables. El tipo y todos los tipos que contienen deben hacerse parciales para iniciar la generación de origen. + + + + Derived 'JsonSerializerContext' types and all containing types must be partial. + Los tipos derivados "JsonSerializerContext" y todos los tipos que contienen deben ser parciales. + + + + The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'. + La propiedad de extensión de datos '{0}.{1}' no es válida. Debe implementar 'IDictionary<string, JsonElement>' o 'IDictionary<string, object>', o ser 'JsonObject'. + + + + Data extension property type invalid. + Tipo de propiedad de extensión de datos no válido. + + + + The custom attribute '{0}' deriving from JsonConverterAttribute is not supported by the source generator. + El generador de origen no admite el atributo personalizado '{0}' derivado de JsonConverterAttribute. + + + + Attributes deriving from JsonConverterAttribute are not supported by the source generator. + El generador de origen no admite los atributos derivados de JsonConverterAttribute. + + + + There are multiple types named '{0}'. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. + Hay varios tipos denominados "{0}". El origen se generó para el primero detectado. Use "JsonSerializableAttribute.TypeInfoPropertyName" para resolver esta colisión. + + + + Duplicate type name. + Nombre de tipo duplicado. + + + + Type '{0}' is annotated with 'JsonDerivedTypeAttribute' which is not supported in 'JsonSourceGenerationMode.Serialization'. + El tipo \"{0}\" está anotado con \"JsonDerivedTypeAttribute\", que no se admite en \"JsonSourceGenerationMode.Serialization\". + + + + 'JsonDerivedTypeAttribute' is not supported in 'JsonSourceGenerationMode.Serialization'. + \"JsonDerivedTypeAttribute\" no se admite en \"JsonSourceGenerationMode.Serialization\". + + + + The member '{0}.{1}' has been annotated with the JsonIncludeAttribute but is not visible to the source generator. + El miembro '{0}.{1}' se ha anotado con JsonIncludeAttribute, pero no es visible para el generador de origen. + + + + Inaccessible properties annotated with the JsonIncludeAttribute are not supported in source generation mode. + Las propiedades inaccesibles anotadas con JsonIncludeAttribute no se admiten en el modo de generación de origen. + + + + The type '{0}' defines init-only properties, deserialization of which is currently not supported in source generation mode. + El tipo '{0}' define propiedades de solo inicialización, pero la deserialización no es compatible actualmente con el modo de generación de origen. + + + + Deserialization of init-only properties is currently not supported in source generation mode. + Actualmente no se admite la deserialización de propiedades de solo inicialización en el modo de generación de origen. + + + + The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. + El constructor del tipo '{0}' se ha anotado con JsonConstructorAttribute, pero el generador de origen no puede acceder a él. + + + + Constructor annotated with JsonConstructorAttribute is inaccessible. + No se puede acceder al constructor anotado con JsonConstructorAttribute. + + + + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. + El tipo “JsonConverterAttribute” “{0}” especificado en el miembro “{1}” no es un tipo de convertidor o no contiene un constructor sin parámetros accesible. + + + + The 'JsonConverterAttribute.Type' contains an invalid or inaccessible argument. + “JsonConverterAttribute.Type” contiene un argumento no válido o inaccesible. + + + + The type '{0}' has been annotated with JsonSerializableAttribute but does not derive from JsonSerializerContext. No source code will be generated. + El tipo "{0}" se ha anotado con JsonSerializableAttribute, pero no se deriva de JsonSerializerContext. No se generará ningún código fuente. + + + + Types annotated with JsonSerializableAttribute must be classes deriving from JsonSerializerContext. + Los tipos anotados con JsonSerializableAttribute deben ser clases derivadas de JsonSerializerContext. + + + + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. + El miembro '{0}' se ha anotado con 'JsonStringEnumConverter', que no se admite en AOT nativo. Considere la posibilidad de usar el elemento genérico "JsonStringEnumConverter<TEnum>" en su lugar. + + + + The non-generic 'JsonStringEnumConverter' requires dynamic code. + El elemento 'JsonStringEnumConverter' no genérico requiere código dinámico. + + + + The System.Text.Json source generator is not available in C# {0}. Please use language version {1} or greater. + El generador de origen System.Text.Json no está disponible en C# {0}. Use la versión de idioma {1} o superior. + + + + C# language version not supported by the source generator. + La versión del idioma C# no es compatible con el generador de origen. + + + + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. + El tipo '{0}' tiene varios constructores anotados con 'JsonConstructorAttribute'. + + + + Type has multiple constructors annotated with JsonConstructorAttribute. + El tipo tiene varios constructores anotados con JsonConstructorAttribute. + + + + Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'. + El tipo '{0}' tiene varios miembros anotados con 'JsonExtensionDataAttribute'. + + + + Type has multiple members annotated with JsonExtensionDataAttribute. + El tipo tiene varios miembros anotados con JsonExtensionDataAttribute. + + + + The type '{0}' includes the ref like property, field or constructor parameter '{1}'. No source code will be generated for the property, field or constructor. + El tipo "{0}" incluye una referencia como propiedad, campo o parámetro de constructor "{1}". No se generará código fuente para la propiedad, campo o constructor. + + + + Type includes ref like property, field or constructor parameter. + El tipo incluye una referencia como propiedad, campo o parámetro de constructor. + + + + Did not generate serialization metadata for type '{0}'. + No generó metadatos de serialización para el tipo '{0}". + + + + Did not generate serialization metadata for type. + No generó metadatos de serialización de metadatos para el tipo. + + + + + \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Resources/xlf/Strings.fr.xlf b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Resources/xlf/Strings.fr.xlf new file mode 100644 index 0000000..1704ce4 --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Resources/xlf/Strings.fr.xlf @@ -0,0 +1,167 @@ + + + + + + Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation. + Le type 'JsonSerializerContext' dérivé '{0}' spécifie des types Sérialisables JSON. Le type et tous les types contenants doivent être rendus partiels pour lancer la génération de la source. + + + + Derived 'JsonSerializerContext' types and all containing types must be partial. + Les types dérivés 'JsonSerializerContext' et tous les types conteneurs doivent être partiels. + + + + The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'. + La propriété d'extension de données '{0}.{1}' n'est pas valide. Elle doit mettre en œuvre 'IDictionary<string, JsonElement>' ou 'IDictionary<string, object>', ou être 'JsonObject'. + + + + Data extension property type invalid. + Type de propriété d’extension de données non valide + + + + The custom attribute '{0}' deriving from JsonConverterAttribute is not supported by the source generator. + L'attribut personnalisé '{0}' dérivant de JsonConverterAttribute n'est pas pris en charge par le générateur source. + + + + Attributes deriving from JsonConverterAttribute are not supported by the source generator. + Les attributs dérivant de JsonConverterAttribute ne sont pas pris en charge par le générateur de source. + + + + There are multiple types named '{0}'. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. + Il existe plusieurs types nommés '{0}'. La source a été générée pour le premier détecté. Utilisez 'JsonSerializableAttribute.TypeInfoPropertyName' pour résoudre cette collision. + + + + Duplicate type name. + Nom de type dupliqué. + + + + Type '{0}' is annotated with 'JsonDerivedTypeAttribute' which is not supported in 'JsonSourceGenerationMode.Serialization'. + Le type « {0} » est annoté avec « JsonDerivedTypeAttribute », ce qui n’est pas pris en charge dans « JsonSourceGenerationMode.Serialization ». + + + + 'JsonDerivedTypeAttribute' is not supported in 'JsonSourceGenerationMode.Serialization'. + « JsonDerivedTypeAttribute » n’est pas pris en charge dans « JsonSourceGenerationMode.Serialization ». + + + + The member '{0}.{1}' has been annotated with the JsonIncludeAttribute but is not visible to the source generator. + Le membre '{0}.{1}' a été annoté avec JsonIncludeAttribute mais n’est pas visible pour le générateur source. + + + + Inaccessible properties annotated with the JsonIncludeAttribute are not supported in source generation mode. + Les propriétés inaccessibles annotées avec JsonIncludeAttribute ne sont pas prises en charge en mode de génération de source. + + + + The type '{0}' defines init-only properties, deserialization of which is currently not supported in source generation mode. + Le type' {0}' définit des propriétés init uniquement, dont la désérialisation n'est actuellement pas prise en charge en mode de génération de source. + + + + Deserialization of init-only properties is currently not supported in source generation mode. + La désérialisation des propriétés d’initialisation uniquement n’est actuellement pas prise en charge en mode de génération de source. + + + + The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. + Le constructeur sur le type '{0}' a été annoté avec JsonConstructorAttribute mais n'est pas accessible par le générateur source. + + + + Constructor annotated with JsonConstructorAttribute is inaccessible. + Le constructeur annoté avec JsonConstructorAttribute est inaccessible. + + + + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. + Le type 'JsonConverterAttribute' '{0}' spécifié sur le membre '{1}' n’est pas un type convertisseur ou ne contient pas de constructeur sans paramètre accessible. + + + + The 'JsonConverterAttribute.Type' contains an invalid or inaccessible argument. + 'JsonConverterAttribute.Type' contient un argument non valide ou inaccessible. + + + + The type '{0}' has been annotated with JsonSerializableAttribute but does not derive from JsonSerializerContext. No source code will be generated. + Le type '{0}' a été annoté avec l'attribut JsonSerializable mais ne dérive pas de JsonSerializerContext. Aucun code source ne sera généré. + + + + Types annotated with JsonSerializableAttribute must be classes deriving from JsonSerializerContext. + Les types annotés avec l'attribut JsonSerializable doivent être des classes dérivant de JsonSerializerContext. + + + + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. + Le membre '{0}' a été annoté avec 'JsonStringEnumConverter', ce qui n’est pas pris en charge dans AOT natif. Utilisez plutôt le générique 'JsonStringEnumConverter<TEnum>'. + + + + The non-generic 'JsonStringEnumConverter' requires dynamic code. + Le 'JsonStringEnumConverter' non générique requiert du code dynamique. + + + + The System.Text.Json source generator is not available in C# {0}. Please use language version {1} or greater. + Le générateur de source System.Text.Json n'est pas disponible en C# « {0} ». Veuillez utiliser la version linguistique {1} ou supérieure. + + + + C# language version not supported by the source generator. + Version du langage C# non prise en charge par le générateur de source. + + + + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. + Le type' {0} 'a plusieurs constructeurs annotés avec’JsonConstructorAttribute'. + + + + Type has multiple constructors annotated with JsonConstructorAttribute. + Le type a plusieurs constructeurs annotés avec JsonConstructorAttribute. + + + + Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'. + Le type '{0}' comporte plusieurs membres annotés avec JsonExtensionDataAttribute. + + + + Type has multiple members annotated with JsonExtensionDataAttribute. + Le type comporte plusieurs membres annotés avec JsonExtensionDataAttribute. + + + + The type '{0}' includes the ref like property, field or constructor parameter '{1}'. No source code will be generated for the property, field or constructor. + Le type « {0} » inclut le ref comme propriété, champ ou paramètre de constructeur « {1} ». Aucun code source ne sera généré pour la propriété, le champ ou le constructeur. + + + + Type includes ref like property, field or constructor parameter. + Le type comprend des éléments tels que des propriétés, des champs ou des paramètres de construction. + + + + Did not generate serialization metadata for type '{0}'. + Les métadonnées de sérialisation pour le type « {0} » n’ont pas été générées. + + + + Did not generate serialization metadata for type. + Les métadonnées de sérialisation pour le type n’ont pas été générées. + + + + + \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Resources/xlf/Strings.it.xlf b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Resources/xlf/Strings.it.xlf new file mode 100644 index 0000000..c44dbd4 --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Resources/xlf/Strings.it.xlf @@ -0,0 +1,167 @@ + + + + + + Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation. + Il tipo 'JsonSerializerContext 'derivato '{0}' specifica i tipi serializzabili JSON. Il tipo e tutti i tipi contenenti devono essere parziali per iniziare la generazione dell'origine. + + + + Derived 'JsonSerializerContext' types and all containing types must be partial. + I tipi derivati 'JsonSerializerContext' e tutti i tipi contenenti devono essere parziali. + + + + The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'. + La proprietà '{0}.{1}' dell'estensione dati non è valida. Deve implementare 'IDictionary<string, JsonElement>' o 'IDictionary<String, Object>' o 'JsonObject '. + + + + Data extension property type invalid. + Il tipo di proprietà dell'estensione dati non è valido. + + + + The custom attribute '{0}' deriving from JsonConverterAttribute is not supported by the source generator. + L'attributo personalizzato '{0}' che deriva da JsonConverterAttribute non è supportato dal generatore di origine. + + + + Attributes deriving from JsonConverterAttribute are not supported by the source generator. + Gli attributi che derivano da JsonConverterAttribute non sono supportati dal generatore di origine. + + + + There are multiple types named '{0}'. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. + Sono presenti più tipi denominati '{0}'. L'origine è stata generata per il primo tipo rilevato. Per risolvere questa collisione, usare 'JsonSerializableAttribute.TypeInfoPropertyName'. + + + + Duplicate type name. + Nome di tipo duplicato. + + + + Type '{0}' is annotated with 'JsonDerivedTypeAttribute' which is not supported in 'JsonSourceGenerationMode.Serialization'. + Il tipo '{0}' è annotato con 'JsonDerivedTypeAttribute' che non è supportato in 'JsonSourceGenerationMode.Serialization'. + + + + 'JsonDerivedTypeAttribute' is not supported in 'JsonSourceGenerationMode.Serialization'. + 'JsonDerivedTypeAttribute' non è supportato in 'JsonSourceGenerationMode.Serialization'. + + + + The member '{0}.{1}' has been annotated with the JsonIncludeAttribute but is not visible to the source generator. + Il membro ' {0}.{1}' è stato annotato con JsonIncludeAttribute ma non è visibile al generatore di origine. + + + + Inaccessible properties annotated with the JsonIncludeAttribute are not supported in source generation mode. + Le proprietà inaccessibili annotate con JsonIncludeAttribute non sono supportate nella modalità di generazione di origine. + + + + The type '{0}' defines init-only properties, deserialization of which is currently not supported in source generation mode. + Il tipo '{0}' definisce le proprietà di sola inizializzazione, la cui deserializzazione al momento non è supportata nella modalità di generazione di origine. + + + + Deserialization of init-only properties is currently not supported in source generation mode. + La deserializzazione delle proprietà di sola inizializzazione al momento non è supportata nella modalità di generazione di origine. + + + + The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. + Il costruttore nel tipo '{0}' è stato annotato con JsonConstructorAttribute ma non è accessibile dal generatore di origine. + + + + Constructor annotated with JsonConstructorAttribute is inaccessible. + Il costruttore annotato con JsonConstructorAttribute non è accessibile. + + + + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. + Il tipo 'JsonConverterAttribute' '{0}' specificato nel membro '{1}' non è un tipo di convertitore o non contiene un costruttore senza parametri accessibile. + + + + The 'JsonConverterAttribute.Type' contains an invalid or inaccessible argument. + 'JsonConverterAttribute.Type' contiene un argomento non valido o inaccessibile. + + + + The type '{0}' has been annotated with JsonSerializableAttribute but does not derive from JsonSerializerContext. No source code will be generated. + Il tipo '{0}' è stato annotato con JsonSerializableAttribute ma non deriva da JsonSerializerContext. Non verrà generato alcun codice sorgente. + + + + Types annotated with JsonSerializableAttribute must be classes deriving from JsonSerializerContext. + I tipi annotati con JsonSerializableAttribute devono essere classi che derivano da JsonSerializerContext. + + + + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. + Il membro '{0}' è stato annotato con 'JsonStringEnumConverter' che non è supportato in AOT nativo. Provare a usare il generico 'JsonStringEnumConverter<TEnum>'. + + + + The non-generic 'JsonStringEnumConverter' requires dynamic code. + L'elemento 'JsonStringEnumConverter' non generico richiede un codice dinamico. + + + + The System.Text.Json source generator is not available in C# {0}. Please use language version {1} or greater. + Il generatore di origine System.Text.Json non è disponibile in C# {0}. Usare la versione del linguaggio {1} o successiva. + + + + C# language version not supported by the source generator. + Versione del linguaggio C# non supportata dal generatore di origine. + + + + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. + Il tipo '{0}' contiene più costruttori che presentano l'annotazione 'JsonConstructorAttribute'. + + + + Type has multiple constructors annotated with JsonConstructorAttribute. + Il tipo contiene più costruttori che presentano l'annotazione JsonConstructorAttribute. + + + + Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'. + Nel tipo '{0}' sono presenti più membri annotati con 'JsonExtensionDataAttribute'. + + + + Type has multiple members annotated with JsonExtensionDataAttribute. + Nel tipo sono presenti più membri annotati con JsonExtensionDataAttribute. + + + + The type '{0}' includes the ref like property, field or constructor parameter '{1}'. No source code will be generated for the property, field or constructor. + Il tipo '{0}' include un riferimento come la proprietà, il campo o il parametro del costruttore '{1}'. Non verrà generato codice sorgente per la proprietà, il campo o il costruttore. + + + + Type includes ref like property, field or constructor parameter. + Il tipo include un riferimento come la proprietà, il campo o il parametro del costruttore. + + + + Did not generate serialization metadata for type '{0}'. + Non sono stati generati metadati di serializzazione per il tipo '{0}'. + + + + Did not generate serialization metadata for type. + Non sono stati generati metadati di serializzazione per il tipo. + + + + + \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Resources/xlf/Strings.ja.xlf b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Resources/xlf/Strings.ja.xlf new file mode 100644 index 0000000..ef07d13 --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Resources/xlf/Strings.ja.xlf @@ -0,0 +1,167 @@ + + + + + + Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation. + 派生した 'JsonSerializerContext'型 '{0}' は、JSON シリアル化可能な型を指定します。ソース生成を開始するには、型と含まれているすべての型を部分的にする必要があります。 + + + + Derived 'JsonSerializerContext' types and all containing types must be partial. + 派生した 'JsonSerializerContext' 型と含まれているすべての型は部分的である必要があります。 + + + + The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'. + データ拡張プロパティ '{0}.{1}' が無効です。これには、'IDictionary<string, JsonElement>'、'IDictionary<string, object>'、または 'JsonObject' を実装する必要があります。 + + + + Data extension property type invalid. + データ拡張プロパティの型が無効です。 + + + + The custom attribute '{0}' deriving from JsonConverterAttribute is not supported by the source generator. + JsonConverterAttribute から派生するカスタム属性 '{0}' は、ソース ジェネレーターではサポートされていません。 + + + + Attributes deriving from JsonConverterAttribute are not supported by the source generator. + JsonConverterAttribute から派生する属性は、ソース ジェネレーターではサポートされていません。 + + + + There are multiple types named '{0}'. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. + '{0}' と名前が付けられた種類が複数あります。最初に検出されたものに対してソースが生成されました。この問題を解決するには、'JsonSerializableAttribute.TypeInfoPropertyName' を使用します。 + + + + Duplicate type name. + 重複した種類名。 + + + + Type '{0}' is annotated with 'JsonDerivedTypeAttribute' which is not supported in 'JsonSourceGenerationMode.Serialization'. + 型 '{0}' は 'JsonDerivedTypeAttribute' の注釈が付けられていますが、これは 'JsonSourceGenerationMode.Serialization' でサポートされていません。 + + + + 'JsonDerivedTypeAttribute' is not supported in 'JsonSourceGenerationMode.Serialization'. + 'JsonDerivedTypeAttribute' は 'JsonSourceGenerationMode.Serialization' ではサポートされていません。 + + + + The member '{0}.{1}' has been annotated with the JsonIncludeAttribute but is not visible to the source generator. + メンバー '{0}.{1}' には、JsonIncludeAttribute で注釈が付けられていますが、ソース ジェネレーターには表示されません。 + + + + Inaccessible properties annotated with the JsonIncludeAttribute are not supported in source generation mode. + JsonIncludeAttribute で注釈が付けられたアクセスできないプロパティは、ソース生成モードではサポートされていません。 + + + + The type '{0}' defines init-only properties, deserialization of which is currently not supported in source generation mode. + 型 '{0}' は、ソース生成モードでは現在サポートされていない init-only プロパティの逆シリアル化を定義します。 + + + + Deserialization of init-only properties is currently not supported in source generation mode. + 現在、ソース生成モードでは init-only プロパティの逆シリアル化はサポートされていません。 + + + + The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. + 型 '{0}' のコンストラクターには JsonConstructorAttribute で注釈が付けられますが、ソース ジェネレーターからアクセスすることはできません。 + + + + Constructor annotated with JsonConstructorAttribute is inaccessible. + JsonConstructorAttribute で注釈が付けられたコンストラクターにアクセスできません。 + + + + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. + メンバー '{1}' で指定されている 'JsonConverterAttribute' 型 '{0}' はコンバーター型ではないか、アクセス可能なパラメーターなしのコンストラクターを含んでいません。 + + + + The 'JsonConverterAttribute.Type' contains an invalid or inaccessible argument. + 'JsonConverterAttribute.Type' に無効な、またはアクセスできない引数が含まれています。 + + + + The type '{0}' has been annotated with JsonSerializableAttribute but does not derive from JsonSerializerContext. No source code will be generated. + 型 '{0}' は JsonSerializableAttribute で注釈が付けられていますが、JsonSerializerContext から派生したものではありません。ソース コードは生成されません。 + + + + Types annotated with JsonSerializableAttribute must be classes deriving from JsonSerializerContext. + JsonSerializableAttribute で注釈が付けられた型は、JsonSerializerContext から派生するクラスである必要があります。 + + + + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. + メンバー '{0}' には、ネイティブ AOT ではサポートされていない 'JsonStringEnumConverter' の注釈が付けられています。 代わりに汎用の 'JsonStringEnumConverter<TEnum>' を使用することを検討してください。 + + + + The non-generic 'JsonStringEnumConverter' requires dynamic code. + 非ジェネリック 'JsonStringEnumConverter' には動的コードが必要です。 + + + + The System.Text.Json source generator is not available in C# {0}. Please use language version {1} or greater. + System.Text.Json ソース ジェネレーターは、C# {0}では使用できません。言語バージョン {1} 以上を使用してください。 + + + + C# language version not supported by the source generator. + ソース ジェネレーターでサポートされていない C# 言語バージョン。 + + + + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. + 型 '{0}' には、'JsonConstructorAttribute' で注釈が付けられた複数のコンストラクターがあります。 + + + + Type has multiple constructors annotated with JsonConstructorAttribute. + 型には、JsonConstructorAttribute で注釈が付けられた複数のコンストラクターがあります。 + + + + Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'. + 型 '{0}' には、'JsonExtensionDataAttribute' に注釈が付けられた複数のメンバーが含まれます。 + + + + Type has multiple members annotated with JsonExtensionDataAttribute. + 型には、'JsonExtensionDataAttribute' に注釈が付けられた複数のメンバーが含まれます。 + + + + The type '{0}' includes the ref like property, field or constructor parameter '{1}'. No source code will be generated for the property, field or constructor. + 型 '{0}' には、プロパティ、フィールド、コンストラクター パラメーター '{1}' などの ref が含まれます。プロパティ、フィールド、またはコンストラクターのソース コードは生成されません。 + + + + Type includes ref like property, field or constructor parameter. + 型には、プロパティ、フィールド、コンストラクター パラメーターなどの ref が含まれます。 + + + + Did not generate serialization metadata for type '{0}'. + '{0}'型 のシリアル化メタデータを生成ませんでした。 + + + + Did not generate serialization metadata for type. + 型のシリアル化メタデータが生成されません。 + + + + + \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Resources/xlf/Strings.ko.xlf b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Resources/xlf/Strings.ko.xlf new file mode 100644 index 0000000..615789e --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Resources/xlf/Strings.ko.xlf @@ -0,0 +1,167 @@ + + + + + + Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation. + 파생된 'JsonSerializerContext' 유형 '{0}'은(는) JSON 직렬화 가능한 유형을 지정합니다. 소스 생성을 위해 유형 및 모든 포함 유형을 부분적으로 만들어야 합니다. + + + + Derived 'JsonSerializerContext' types and all containing types must be partial. + 파생된 'JsonSerializerContext' 형식과 포함하는 모든 형식은 부분이어야 합니다. + + + + The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'. + 데이터 확장 속성 '{0}.{1}'이 잘못되었습니다. 'IDictionary<string, JsonElement>'나 'IDictionary<string, object>' 를 구현하거나 'JsonObject'여야 합니다. + + + + Data extension property type invalid. + 데이터 확장 속성 형식이 잘못되었습니다. + + + + The custom attribute '{0}' deriving from JsonConverterAttribute is not supported by the source generator. + JsonConverterAttribute에서 파생된 사용자 지정 속성 '{0}'은(는) 원본 생성기에서 지원되지 않습니다. + + + + Attributes deriving from JsonConverterAttribute are not supported by the source generator. + JsonConverterAttribute에서 파생되는 특성은 소스 생성기에서 지원되지 않습니다. + + + + There are multiple types named '{0}'. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. + 이름이 '{0}'인 형식이 여러 개 있습니다. 처음 검색된 것에 대한 원본이 생성되었습니다. 이 충돌을 해결하려면 'JsonSerializableAttribute.TypeInfoPropertyName'을 사용하세요. + + + + Duplicate type name. + 중복된 형식 이름입니다. + + + + Type '{0}' is annotated with 'JsonDerivedTypeAttribute' which is not supported in 'JsonSourceGenerationMode.Serialization'. + '{0} 형식은 'JsonSourceGenerationMode.Serialization'에서 지원되지 않는 'JsonDerivedTypeAttribute'로 주석이 추가되었습니다. + + + + 'JsonDerivedTypeAttribute' is not supported in 'JsonSourceGenerationMode.Serialization'. + 'JsonSourceGenerationMode.Serialization'에서는 'JsonDerivedTypeAttribute'가 지원되지 않습니다. + + + + The member '{0}.{1}' has been annotated with the JsonIncludeAttribute but is not visible to the source generator. + 멤버 '{0}.{1}'이(가) JsonIncludeAttribute로 주석 처리되었지만 원본 생성기에는 표시되지 않습니다. + + + + Inaccessible properties annotated with the JsonIncludeAttribute are not supported in source generation mode. + JsonIncludeAttribute로 주석 처리된 액세스할 수 없는 속성은 원본 생성 모드에서 지원되지 않습니다. + + + + The type '{0}' defines init-only properties, deserialization of which is currently not supported in source generation mode. + '{0}' 유형은 초기화 전용 속성을 정의하며, 이 속성의 역직렬화는 현재 원본 생성 모드에서 지원되지 않습니다. + + + + Deserialization of init-only properties is currently not supported in source generation mode. + 초기화 전용 속성의 역직렬화는 현재 원본 생성 모드에서 지원되지 않습니다. + + + + The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. + '{0}' 형식의 생성자에 JsonConstructorAttribute로 주석이 추가되었지만 원본 생성기에서 액세스할 수 없습니다. + + + + Constructor annotated with JsonConstructorAttribute is inaccessible. + JsonConstructorAttribute로 주석이 추가된 생성자에 액세스할 수 없습니다. + + + + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. + '{1}' 멤버에 지정된 'JsonConverterAttribute' 형식 '{0}'이(가) 변환기 형식이 아니거나 액세스 가능한 매개 변수가 없는 생성자를 포함하지 않습니다. + + + + The 'JsonConverterAttribute.Type' contains an invalid or inaccessible argument. + 'JsonConverterAttribute.Type'에 잘못되었거나 액세스할 수 없는 인수가 포함되어 있습니다. + + + + The type '{0}' has been annotated with JsonSerializableAttribute but does not derive from JsonSerializerContext. No source code will be generated. + '{0}' 형식에 JsonSerializableAttribute 주석이 추가되었지만 JsonSerializerContext에서 파생되지 않았습니다. 소스 코드가 생성되지 않습니다. + + + + Types annotated with JsonSerializableAttribute must be classes deriving from JsonSerializerContext. + JsonSerializableAttribute 주석이 추가된 형식은 JsonSerializerContext에서 파생된 클래스여야 합니다. + + + + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. + '{0}' 멤버에 네이티브 AOT에서 지원되지 않는 'JsonStringEnumConverter'로 주석이 달렸습니다. 대신 제네릭 'JsonStringEnumConverter<TEnum>'을 사용해 보세요. + + + + The non-generic 'JsonStringEnumConverter' requires dynamic code. + 제네릭이 아닌 'JsonStringEnumConverter'에는 동적 코드가 필요합니다. + + + + The System.Text.Json source generator is not available in C# {0}. Please use language version {1} or greater. + System.Text.Json 원본 생성기는 C# {0}에서 사용할 수 없습니다. {1} 이상의 언어 버전을 사용하세요. + + + + C# language version not supported by the source generator. + 원본 생성기에서 지원되지 않는 C# 언어 버전입니다. + + + + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. + '{0}' 형식에 'JsonConstructorAttribute'로 주석이 추가된 여러 생성자가 있습니다. + + + + Type has multiple constructors annotated with JsonConstructorAttribute. + 해당 형식에 JsonConstructorAttribute로 주석이 추가된 여러 생성자가 있습니다. + + + + Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'. + '{0}' 형식에 JsonExtensionDataAttribute로 주석이 추가 된 멤버가 여러 개 있습니다. + + + + Type has multiple members annotated with JsonExtensionDataAttribute. + 형식에 JsonExtensionDataAttribute로 주석이 추가 된 멤버가 여러 개 있습니다. + + + + The type '{0}' includes the ref like property, field or constructor parameter '{1}'. No source code will be generated for the property, field or constructor. + '{0}' 형식에는 속성, 필드 또는 생성자 매개 변수 '{1}'와 같은 ref가 포함됩니다. 속성, 필드 또는 생성자에 대한 소스 코드가 생성되지 않습니다. + + + + Type includes ref like property, field or constructor parameter. + 형식에는 속성, 필드 또는 생성자 매개 변수와 같은 ref가 포함됩니다. + + + + Did not generate serialization metadata for type '{0}'. + '{0}' 형식에 대한 직렬화 메타데이터가 생성되지 않았습니다. + + + + Did not generate serialization metadata for type. + 형식에 대한 직렬화 메타데이터가 생성되지 않았습니다. + + + + + \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Resources/xlf/Strings.pl.xlf b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Resources/xlf/Strings.pl.xlf new file mode 100644 index 0000000..ef4fd7d --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Resources/xlf/Strings.pl.xlf @@ -0,0 +1,167 @@ + + + + + + Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation. + Pochodny typ "JsonSerializerContext" "{0}" określa typy możliwe do serializacji w notacji JSON. Typ i wszystkie zawierające typy muszą być częściowe, aby uruchomić generowanie źródła. + + + + Derived 'JsonSerializerContext' types and all containing types must be partial. + Pochodne typy "JsonSerializerContext" i wszystkich zawierające typy muszą być częściowe. + + + + The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'. + Właściwość rozszerzenia danych "{0}.{1}" jest nieprawidłowa. Musi ona zaimplementować ciąg "IDictionary<string, JsonElement>" lub "IDictionary<string, object>" albo być obiektem "JsonObject". + + + + Data extension property type invalid. + Nieprawidłowy typ właściwości rozszerzenia danych. + + + + The custom attribute '{0}' deriving from JsonConverterAttribute is not supported by the source generator. + Generator źródłowy nie obsługuje atrybutu niestandardowego „{0}” pochodzącego od atrybutu JsonConverterAttribute. + + + + Attributes deriving from JsonConverterAttribute are not supported by the source generator. + Atrybuty pochodzące z atrybutu JsonConverterAttribute nie są obsługiwane przez generator źródła. + + + + There are multiple types named '{0}'. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. + Istnieje wiele typów o nazwie „{0}”. Wygenerowano źródło dla pierwszego wykrytego elementu. Aby rozwiązać tę kolizję, użyj elementu „JsonSerializableAttribute. TypeInfoPropertyName”. + + + + Duplicate type name. + Zduplikowana nazwa typu. + + + + Type '{0}' is annotated with 'JsonDerivedTypeAttribute' which is not supported in 'JsonSourceGenerationMode.Serialization'. + Typ „{0}” ma adnotację „JsonDerivedTypeAttribute”, która nie jest obsługiwana w elemecie „JsonSourceGenerationMode.Serialization”. + + + + 'JsonDerivedTypeAttribute' is not supported in 'JsonSourceGenerationMode.Serialization'. + Atrybut „JsonDerivedTypeAttribute” nie jest obsługiwany w elemecie „JsonSourceGenerationMode.Serialization”. + + + + The member '{0}.{1}' has been annotated with the JsonIncludeAttribute but is not visible to the source generator. + Składowa "{0}. {1}" jest adnotowana za pomocą atrybutu JsonIncludeAttribute, ale nie jest widoczna dla generatora źródła. + + + + Inaccessible properties annotated with the JsonIncludeAttribute are not supported in source generation mode. + Niedostępne właściwości adnotowane za pomocą atrybutu JsonIncludeAttribute nie są obsługiwane w trybie generowania źródła. + + + + The type '{0}' defines init-only properties, deserialization of which is currently not supported in source generation mode. + Typ "{0}" określa właściwości tylko do inicjowania, deserializację, która obecnie nie jest obsługiwana w trybie generowania źródła. + + + + Deserialization of init-only properties is currently not supported in source generation mode. + Deserializacja właściwości tylko do inicjowania nie jest obecnie obsługiwana w trybie generowania źródła. + + + + The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. + W przypadku konstruktora w zakresie typu „{0}” dokonano adnotacji przy użyciu atrybutu JsonConstructorAttribute, ale nie jest on dostępny dla generatora źródła. + + + + Constructor annotated with JsonConstructorAttribute is inaccessible. + Konstruktor, w przypadku którego dokonano adnotacji za pomocą atrybutu JsonConstructorAttribute, jest niedostępny. + + + + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. + Typ „{0}” „JsonConverterAttribute” określony w przypadku składowej „{1}” nie jest typem konwertera lub nie zawiera dostępnego konstruktora bez parametrów. + + + + The 'JsonConverterAttribute.Type' contains an invalid or inaccessible argument. + Typ „JsonConverterAttribute.Type” zawiera nieprawidłowy lub niedostępny argument. + + + + The type '{0}' has been annotated with JsonSerializableAttribute but does not derive from JsonSerializerContext. No source code will be generated. + Typ „{0}” ma adnotacje z atrybutem JsonSerializableAttribute, ale nie pochodzi od elementu JsonSerializerContext. Nie zostanie wygenerowany żaden kod źródłowy. + + + + Types annotated with JsonSerializableAttribute must be classes deriving from JsonSerializerContext. + Typy z adnotacjami JsonSerializableAttribute muszą być klasami pochodzącymi z elementu JsonSerializerContext. + + + + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. + Element członkowski '{0}' został opatrzony adnotacją 'JsonStringEnumConverter', która nie jest obsługiwana w natywnym AOT. Zamiast tego należy rozważyć użycie ogólnego konwertera „JsonStringEnumConverter<TEnum>”. + + + + The non-generic 'JsonStringEnumConverter' requires dynamic code. + Nieogólny element „JsonStringEnumConverter” wymaga dynamicznego kodu. + + + + The System.Text.Json source generator is not available in C# {0}. Please use language version {1} or greater. + Generator źródła System.Text.Json nie jest dostępny w języku C# {0}. Użyj wersji językowej {1} lub nowszej. + + + + C# language version not supported by the source generator. + Wersja języka C# nie jest obsługiwana przez generator źródła. + + + + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. + Typ "{0}" ma wiele konstruktorów z adnotacją "JsonConstructorAttribute". + + + + Type has multiple constructors annotated with JsonConstructorAttribute. + Typ ma wiele konstruktorów z adnotacją JsonConstructorAttribute. + + + + Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'. + Typ "{0}" ma wiele składowych opatrzonych adnotacjami za pomocą atrybutu "JsonExtensionDataAttribute". + + + + Type has multiple members annotated with JsonExtensionDataAttribute. + Typ ma wiele składowych opatrzonych adnotacjami za pomocą atrybutu JsonExtensionDataAttribute. + + + + The type '{0}' includes the ref like property, field or constructor parameter '{1}'. No source code will be generated for the property, field or constructor. + Typ „{0}” zawiera parametr ref, taki jak właściwość, pole lub konstruktor „{1}”. Nie zostanie wygenerowany kod źródłowy dla właściwości, pola lub konstruktora. + + + + Type includes ref like property, field or constructor parameter. + Typ zawiera parametr właściwości, pola lub konstruktora typu ref. + + + + Did not generate serialization metadata for type '{0}'. + Nie wygenerowano metadanych serializacji dla typu „{0}”. + + + + Did not generate serialization metadata for type. + Nie wygenerowano metadanych serializacji dla typu. + + + + + \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Resources/xlf/Strings.pt-BR.xlf b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Resources/xlf/Strings.pt-BR.xlf new file mode 100644 index 0000000..3a76db6 --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Resources/xlf/Strings.pt-BR.xlf @@ -0,0 +1,167 @@ + + + + + + Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation. + O tipo 'JsonSerializerContext' derivado '{0}' especifica tipos serializáveis por JSON. O tipo e todos os tipos de contenção devem ser feitos parcialmente para iniciar a geração de origem. + + + + Derived 'JsonSerializerContext' types and all containing types must be partial. + Os tipos derivados de 'JsonSerializerContext' e todos os tipos contidos devem ser parciais. + + + + The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'. + A propriedade da extensão de dados '{0}.{1}' é inválida. Deve implementar 'IDictionary<string, JsonElement>' ou 'IDictionary<string, object>', ou ter 'JsonObject'. + + + + Data extension property type invalid. + Tipo de propriedade de extensão de dados inválido. + + + + The custom attribute '{0}' deriving from JsonConverterAttribute is not supported by the source generator. + O atributo customizado '{0}' derivado de JsonConverterAttribute não tem suporte pelo gerador de origem. + + + + Attributes deriving from JsonConverterAttribute are not supported by the source generator. + Os atributos derivados de JsonConverterAttribute não têm suporte pelo gerador de origem. + + + + There are multiple types named '{0}'. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. + Existem vários tipos chamados '{0}'. A fonte foi gerada para o primeiro detectado. Use 'JsonSerializableAttribute.TypeInfoPropertyName' para resolver essa colisão. + + + + Duplicate type name. + Nome de tipo duplicado. + + + + Type '{0}' is annotated with 'JsonDerivedTypeAttribute' which is not supported in 'JsonSourceGenerationMode.Serialization'. + O tipo '{0}' é anotado com 'JsonDerivedTypeAttribute' que não tem suporte em 'JsonSourceGenerationMode.Serialization'. + + + + 'JsonDerivedTypeAttribute' is not supported in 'JsonSourceGenerationMode.Serialization'. + 'JsonDerivedTypeAttribute' não tem suporte em 'JsonSourceGenerationMode.Serialization'. + + + + The member '{0}.{1}' has been annotated with the JsonIncludeAttribute but is not visible to the source generator. + O membro '{0}.{1}' foi anotado com o JsonIncludeAttribute, mas não é visível para o gerador de origem. + + + + Inaccessible properties annotated with the JsonIncludeAttribute are not supported in source generation mode. + Propriedades inacessíveis anotadas com JsonIncludeAttribute não são suportadas no modo de geração de origem. + + + + The type '{0}' defines init-only properties, deserialization of which is currently not supported in source generation mode. + O tipo '{0}' define propriedades apenas de inicialização, a desserialização das quais atualmente não é suportada no modo de geração de origem. + + + + Deserialization of init-only properties is currently not supported in source generation mode. + A desserialização de propriedades apenas de inicialização não é atualmente suportada no modo de geração de origem. + + + + The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. + O construtor do tipo '{0}' foi anotado com JsonConstructorAttribute, mas não pode ser acessado pelo gerador de origem. + + + + Constructor annotated with JsonConstructorAttribute is inaccessible. + O construtor anotado com JsonConstructorAttribute está inacessível. + + + + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. + O tipo "JsonConverterAttribute" "{0}" especificado no membro "{1}" não é um tipo de conversor ou não contém um construtor sem parâmetros acessível. + + + + The 'JsonConverterAttribute.Type' contains an invalid or inaccessible argument. + O "JsonConverterAttribute.Type" contém um argumento inválido ou inacessível. + + + + The type '{0}' has been annotated with JsonSerializableAttribute but does not derive from JsonSerializerContext. No source code will be generated. + O tipo '{0}' foi anotado com JsonSerializableAttribute, mas não deriva de JsonSerializerContext. Nenhum código-fonte será gerado. + + + + Types annotated with JsonSerializableAttribute must be classes deriving from JsonSerializerContext. + Tipos anotados com JsonSerializable Attribute devem ser classes derivadas de JsonSerializerContext. + + + + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. + O membro "{0}" foi anotado com "JsonStringEnumConverter" que não tem suporte na AOT nativa. Considere usar o genérico "JsonStringEnumConverter<TEnum>". + + + + The non-generic 'JsonStringEnumConverter' requires dynamic code. + O "JsonStringEnumConverter" não genérico requer código dinâmico. + + + + The System.Text.Json source generator is not available in C# {0}. Please use language version {1} or greater. + O gerador de origem System.Text.Json não está disponível em C# {0}. Use a versão do idioma {1} ou superior. + + + + C# language version not supported by the source generator. + Versão da linguagem C# não suportada pelo gerador de origem. + + + + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. + O tipo '{0}' tem vários construtores anotados com 'JsonConstructorAttribute'. + + + + Type has multiple constructors annotated with JsonConstructorAttribute. + O tipo tem vários construtores anotados com JsonConstructorAttribute. + + + + Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'. + Tipo '{0}' tem vários membros anotados com 'JsonExtensionDataAttribute'. + + + + Type has multiple members annotated with JsonExtensionDataAttribute. + Tipo tem vários membros anotados com JsonExtensionDataAttribute. + + + + The type '{0}' includes the ref like property, field or constructor parameter '{1}'. No source code will be generated for the property, field or constructor. + O tipo "{0}" inclui a propriedade ref like, campo ou parâmetro de construtor "{1}". Nenhum código-fonte será gerado para a propriedade, campo ou construtor. + + + + Type includes ref like property, field or constructor parameter. + O tipo inclui propriedade ref like, campo ou parâmetro de construtor. + + + + Did not generate serialization metadata for type '{0}'. + Não gerou metadados de serialização para o tipo '{0}'. + + + + Did not generate serialization metadata for type. + Não gerou metadados de serialização para o tipo. + + + + + \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Resources/xlf/Strings.ru.xlf b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Resources/xlf/Strings.ru.xlf new file mode 100644 index 0000000..b40f423 --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Resources/xlf/Strings.ru.xlf @@ -0,0 +1,167 @@ + + + + + + Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation. + Производный тип "JsonSerializerContext"{0}' указывает сериализуемые типы в формате JSON. Тип и все содержащие типы необходимо сделать частичными для начала генерации источника. + + + + Derived 'JsonSerializerContext' types and all containing types must be partial. + Производные типы "JsonSerializerContext" и все содержащие типы должны быть частичными. + + + + The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'. + Свойство расширения данных "{0}.{1}" недопустимо. Оно должен реализовывать строку "IDictionary<string, JsonElement>" или "IDictionary<string, Object>" либо являться "JsonObject". + + + + Data extension property type invalid. + Недопустимый тип свойства расширения данных. + + + + The custom attribute '{0}' deriving from JsonConverterAttribute is not supported by the source generator. + Пользовательский атрибут "{0}", производный от JsonConverterAttribute, не поддерживается генератором исходного кода. + + + + Attributes deriving from JsonConverterAttribute are not supported by the source generator. + Атрибуты, производные от JsonConverterAttribute, не поддерживаются генератором исходного кода. + + + + There are multiple types named '{0}'. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. + Существует несколько типов с именем "{0}". Исходный код сформирован для первого обнаруженного типа. Используйте JsonSerializableAttribute.TypeInfoPropertyName для устранения этого конфликта. + + + + Duplicate type name. + Дублирующееся имя типа. + + + + Type '{0}' is annotated with 'JsonDerivedTypeAttribute' which is not supported in 'JsonSourceGenerationMode.Serialization'. + Тип '{0}' аннотирован атрибутом \"JsonDerivedTypeAttribute\", который не поддерживается в \"JsonSourceGenerationMode.Serialization\". + + + + 'JsonDerivedTypeAttribute' is not supported in 'JsonSourceGenerationMode.Serialization'. + Атрибут JsonDerivedTypeAttribute не поддерживается в \"JsonSourceGenerationMode.Serialization\". + + + + The member '{0}.{1}' has been annotated with the JsonIncludeAttribute but is not visible to the source generator. + Элемент "{0}.{1}" аннотирован с использованием класса JsonIncludeAttribute, но генератор исходного кода не обнаруживает этот элемент. + + + + Inaccessible properties annotated with the JsonIncludeAttribute are not supported in source generation mode. + Недоступные свойства, аннотированные с использованием класса JsonIncludeAttribute, не поддерживаются в режиме создания исходного кода. + + + + The type '{0}' defines init-only properties, deserialization of which is currently not supported in source generation mode. + Тип "{0}" определяет свойства, предназначенные только для инициализации. Их десериализация сейчас не поддерживается в режиме создания исходного кода. + + + + Deserialization of init-only properties is currently not supported in source generation mode. + Десериализация свойств, предназначенных только для инициализации, сейчас не поддерживается в режиме создания исходного кода. + + + + The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. + Конструктор для типа "{0}" аннотирован с использованием JsonConstructorAttribute, но недоступен для генератора источника. + + + + Constructor annotated with JsonConstructorAttribute is inaccessible. + Конструктор, аннотированный с использованием JsonConstructorAttribute, недоступен. + + + + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. + Тип "JsonConverterAttribute" "{0}", указанный в элементе "{1}", не является типом преобразователя или не содержит доступного конструктора без параметров. + + + + The 'JsonConverterAttribute.Type' contains an invalid or inaccessible argument. + Аргумент "JsonConverterAttribute.Type" содержит недопустимый или недоступный аргумент. + + + + The type '{0}' has been annotated with JsonSerializableAttribute but does not derive from JsonSerializerContext. No source code will be generated. + Тип "{0}" помечен атрибутом JsonSerializableAttribute, но не является производным от JsonSerializerContext. Исходный код не будет создан. + + + + Types annotated with JsonSerializableAttribute must be classes deriving from JsonSerializerContext. + Типы, аннотированные атрибутом JsonSerializableAttribute, должны быть классами, производными от JsonSerializerContext. + + + + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. + Элемент "{0}" содержит примечание JsonStringEnumConverter, что не поддерживается в собственном AOT. Вместо этого рассмотрите возможность использовать универсальный параметр JsonStringEnumConverter<TEnum>. + + + + The non-generic 'JsonStringEnumConverter' requires dynamic code. + Для неуниверсального параметра JsonStringEnumConverter требуется динамический код. + + + + The System.Text.Json source generator is not available in C# {0}. Please use language version {1} or greater. + Генератор исходного кода System.Text.Json не доступен в C# {0}. Используйте языковую версию {1} или выше. + + + + C# language version not supported by the source generator. + Версия языка C# не поддерживается генератором исходного кода. + + + + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. + Тип "{0}" имеет несколько конструкторов, аннотированных с использованием JsonConstructorAttribute. + + + + Type has multiple constructors annotated with JsonConstructorAttribute. + Тип имеет несколько конструкторов, аннотированных с использованием JsonConstructorAttribute. + + + + Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'. + Тип "{0}" содержит несколько элементов, помеченных с помощью "JsonExtensionDataAttribute". + + + + Type has multiple members annotated with JsonExtensionDataAttribute. + Тип содержит несколько элементов, помеченных с помощью JsonExtensionDataAttribute. + + + + The type '{0}' includes the ref like property, field or constructor parameter '{1}'. No source code will be generated for the property, field or constructor. + Тип "{0}" содержит ссылку, например свойство, поле или параметр конструктора "{1}". Для свойства, поля или конструктора не будет создан исходный код. + + + + Type includes ref like property, field or constructor parameter. + Тип содержит ссылку, например свойство, поле или параметр конструктора. + + + + Did not generate serialization metadata for type '{0}'. + Метаданные сериализации для типа "{0}" не сформированы. + + + + Did not generate serialization metadata for type. + Метаданные сериализации для типа не сформированы. + + + + + \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Resources/xlf/Strings.tr.xlf b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Resources/xlf/Strings.tr.xlf new file mode 100644 index 0000000..6c843de --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Resources/xlf/Strings.tr.xlf @@ -0,0 +1,167 @@ + + + + + + Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation. + Türetilmiş 'JsonSerializerContext' türü '{0}' JSON ile seri hale getirilebilir türleri belirtir. kaynak oluşturmayı başlatmak için bu türün ve bu türü içeren tüm türlerin kısmi yapılması gerekir. + + + + Derived 'JsonSerializerContext' types and all containing types must be partial. + Türetilmiş 'JsonSerializerContext' türleri ve bunları içeren tüm türler kısmi olmalıdır. + + + + The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'. + '{0}.{1}' veri uzantısı özelliği geçersiz. 'IDictionary<string, JsonElement>' veya 'IDictionary<string, object>' uygulamalı ya da 'JsonObject' olmalıdır. + + + + Data extension property type invalid. + Veri uzantısı özellik türü geçersiz. + + + + The custom attribute '{0}' deriving from JsonConverterAttribute is not supported by the source generator. + JsonConverterAttribute’tan türetilen özel öznitelik '{0}' kaynak oluşturucu tarafından desteklenmiyor. + + + + Attributes deriving from JsonConverterAttribute are not supported by the source generator. + JsonConverterAttribute’tan türetilen öznitelikler kaynak oluşturucu tarafından desteklenmiyor. + + + + There are multiple types named '{0}'. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. + '{0}' adını taşıyan birden çok tür var. Kaynak, algılanan ilk tür için oluşturuldu. Bu çarpışmayı çözmek için 'JsonSerializableAttribute.TypeInfoPropertyName' özelliğini kullanın. + + + + Duplicate type name. + Yinelenen tür adı. + + + + Type '{0}' is annotated with 'JsonDerivedTypeAttribute' which is not supported in 'JsonSourceGenerationMode.Serialization'. + '{0}' türü, 'JsonSourceGenerationMode.Serialization' içinde desteklenmeyen 'JsonDerivedTypeAttribute' ile ek açıklama içeriyor. + + + + 'JsonDerivedTypeAttribute' is not supported in 'JsonSourceGenerationMode.Serialization'. + 'JsonSourceGenerationMode.Serialization' içinde 'JsonDerivedTypeAttribute' desteklenmiyor. + + + + The member '{0}.{1}' has been annotated with the JsonIncludeAttribute but is not visible to the source generator. + '{0}.{1}' üyesine JsonIncludeAttribute notu eklendi ancak bu üye kaynak oluşturucu tarafından görülmüyor. + + + + Inaccessible properties annotated with the JsonIncludeAttribute are not supported in source generation mode. + JsonIncludeAttribute notu eklenmiş erişilemeyen özellikler kaynak oluşturma modunda desteklenmiyor. + + + + The type '{0}' defines init-only properties, deserialization of which is currently not supported in source generation mode. + ‘{0}’ türü, seri durumdan çıkarılması şu anda kaynak oluşturma modunda desteklenmeyen yalnızca başlangıç özelliklerini tanımlar. + + + + Deserialization of init-only properties is currently not supported in source generation mode. + Yalnızca başlangıç özelliklerini seri durumdan çıkarma şu anda kaynak oluşturma modunda desteklenmiyor. + + + + The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. + '{0}' türündeki oluşturucuya JsonConstructorAttribute ile açıklama eklenmiş ancak kaynak oluşturucu tarafından erişilebilir değil. + + + + Constructor annotated with JsonConstructorAttribute is inaccessible. + JsonConstructorAttribute ile açıklama eklenmiş oluşturucuya erişilemiyor. + + + + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. + '{1}' üyesi üzerinde belirtilen 'JsonConverterAttribute' '{0}' türü dönüştürücü türü değil veya erişilebilir parametresiz bir oluşturucu içermiyor. + + + + The 'JsonConverterAttribute.Type' contains an invalid or inaccessible argument. + 'JsonConverterAttribute.Type' geçersiz veya erişilemeyen bir bağımsız değişken içeriyor. + + + + The type '{0}' has been annotated with JsonSerializableAttribute but does not derive from JsonSerializerContext. No source code will be generated. + '{0}' türü, JsonSerializableAttribute ile ek açıklama eklenmiş ancak JsonSerializerContext'ten türetmiyor. Kaynak kodu oluşturulmayacak. + + + + Types annotated with JsonSerializableAttribute must be classes deriving from JsonSerializerContext. + JsonSerializableAttribute ile not eklenen türler, JsonSerializerContext'ten türetilen sınıflar olmalıdır. + + + + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. + '{0}' adlı üyeye yerel AOT’de desteklenmeyen 'JsonStringEnumConverter' parametresi eklendi. bunun yerine genel 'JsonStringEnumConverter<TEnum>' parametresini kullanmayı deneyin. + + + + The non-generic 'JsonStringEnumConverter' requires dynamic code. + Genel olmayan 'JsonStringEnumConverter' parametresi dinamik kod gerektirir. + + + + The System.Text.Json source generator is not available in C# {0}. Please use language version {1} or greater. + System.Text.Json kaynak oluşturucusu C# {0}'ta mevcut değildir. Lütfen {1} dil sürümünü veya daha üstünü kullanın. + + + + C# language version not supported by the source generator. + C# dil sürümü kaynak oluşturucu tarafından desteklenmiyor. + + + + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. + '{0}' türünün 'JsonConstructorAttribute' ile açıklanan birden çok oluşturucusu var. + + + + Type has multiple constructors annotated with JsonConstructorAttribute. + Türün JsonConstructorAttribute ile açıklanan birden çok oluşturucusu var. + + + + Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'. + '{0}' türü, JsonExtensionDataAttribute ile ilişkili birden çok üyeye sahip. + + + + Type has multiple members annotated with JsonExtensionDataAttribute. + Tür, JsonExtensionDataAttribute ile açıklanan birden çok üyeye sahip. + + + + The type '{0}' includes the ref like property, field or constructor parameter '{1}'. No source code will be generated for the property, field or constructor. + '{0}' türü; özellik, alan veya oluşturucu parametresi '{1}' gibi başvuru içeriyor. Özellik, alan veya oluşturucu için kaynak kodu üretilmeyecek. + + + + Type includes ref like property, field or constructor parameter. + Tür; özellik, alan veya oluşturucu parametresi gibi başvuru içeriyor. + + + + Did not generate serialization metadata for type '{0}'. + '{0}' türü için serileştirme meta verileri oluşturulmadı. + + + + Did not generate serialization metadata for type. + Tür için serileştirme meta verileri oluşturulmadı. + + + + + \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Resources/xlf/Strings.zh-Hans.xlf b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Resources/xlf/Strings.zh-Hans.xlf new file mode 100644 index 0000000..b5b714c --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Resources/xlf/Strings.zh-Hans.xlf @@ -0,0 +1,167 @@ + + + + + + Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation. + 派生的 “JsonSerializerContext” 类型“{0}”指定 JSON-serializable 类型。该类型和所有包含类型必须设为部分,以启动源生成。 + + + + Derived 'JsonSerializerContext' types and all containing types must be partial. + 派生的 “JsonSerializerContext” 类型以及所有包含类型必须是部分。 + + + + The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'. + 数据扩展属性“{0}.{1}”无效。它必须实现“IDictionary<string, JsonElement>”或“IDictionary<string, object>”,或为 “JsonObject”。 + + + + Data extension property type invalid. + 数据扩展属性类型无效。 + + + + The custom attribute '{0}' deriving from JsonConverterAttribute is not supported by the source generator. + 源生成器不支持从 JsonConverterAttribute 派生的自定义属性“{0}”。 + + + + Attributes deriving from JsonConverterAttribute are not supported by the source generator. + 源生成器不支持从 JsonConverterAttribute 派生的属性。 + + + + There are multiple types named '{0}'. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. + 有多个名为 {0} 的类型。已为第一个检测到类型的生成源。请使用“JsonSerializableAttribute.TypeInfoPropertyName”以解决此冲突。 + + + + Duplicate type name. + 重复的类型名称。 + + + + Type '{0}' is annotated with 'JsonDerivedTypeAttribute' which is not supported in 'JsonSourceGenerationMode.Serialization'. + 类型 \"{0}\" 使用 \"JsonSourceGenerationMode.Serialization\" 中不支持的 \"JsonDerivedTypeAttribute\" 进行了批注。 + + + + 'JsonDerivedTypeAttribute' is not supported in 'JsonSourceGenerationMode.Serialization'. + \"JsonSourceGenerationMode.Serialization\" 中不支持 \"JsonDerivedTypeAttribute\"。 + + + + The member '{0}.{1}' has been annotated with the JsonIncludeAttribute but is not visible to the source generator. + 已使用 JsonIncludeAttribute 注释成员“{0}.{1}”,但对源生成器不可见。 + + + + Inaccessible properties annotated with the JsonIncludeAttribute are not supported in source generation mode. + 源生成模式不支持使用 JsonIncludeAttribute 注释的不可访问属性。 + + + + The type '{0}' defines init-only properties, deserialization of which is currently not supported in source generation mode. + 类型“{0}”定义仅初始化属性,源生成模式当前不支持其反序列化。 + + + + Deserialization of init-only properties is currently not supported in source generation mode. + 源生成模式当前不支持仅初始化属性的反序列化。 + + + + The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. + 类型“{0}”上的构造函数已使用 JsonConstructorAttribute 进行批注,但源生成器无法访问该构造函数。 + + + + Constructor annotated with JsonConstructorAttribute is inaccessible. + 无法访问使用 JsonConstructorAttribute 批注的构造函数。 + + + + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. + 在成员 "{1}" 上指定的 "JsonConverterAttribute" 类型 "{0}" 不是转换器类型或不包含可访问的无参数构造函数。 + + + + The 'JsonConverterAttribute.Type' contains an invalid or inaccessible argument. + "JsonConverterAttribute.Type" 包含无效或不可访问的参数。 + + + + The type '{0}' has been annotated with JsonSerializableAttribute but does not derive from JsonSerializerContext. No source code will be generated. + 类型“{0}”已使用 JsonSerializableAttribute 进行批注,但不是从 JsonSerializerContext 派生的。不会生成源代码。 + + + + Types annotated with JsonSerializableAttribute must be classes deriving from JsonSerializerContext. + 使用 JsonSerializableAttribute 批注的类型必须是派生自 JsonSerializerContext 的类。 + + + + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. + 成员“{0}”已使用本机 AOT 中不支持的 "JsonStringEnumConverter" 进行批注。请改为考虑使用泛型 "JsonStringEnumConverter<TEnum>"。 + + + + The non-generic 'JsonStringEnumConverter' requires dynamic code. + 非泛型 "JsonStringEnumConverter" 需要动态代码。 + + + + The System.Text.Json source generator is not available in C# {0}. Please use language version {1} or greater. + System.Text.Json 源生成器在 C# {0} 中不可用。请使用{1}或更高版本的语言版本。 + + + + C# language version not supported by the source generator. + 源生成器不支持 C# 语言版本。 + + + + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. + 类型“{0}”具有用 “JsonConstructorAttribute” 批注的多个构造函数。 + + + + Type has multiple constructors annotated with JsonConstructorAttribute. + 类型具有用 JsonConstructorAttribute 批注的多个构造函数。 + + + + Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'. + 类型“{0}”具有多个带有 “JsonExtensionDataAttribute” 注释的成员。 + + + + Type has multiple members annotated with JsonExtensionDataAttribute. + 类型具有多个带有 JsonExtensionDataAttribute 注释的成员。 + + + + The type '{0}' includes the ref like property, field or constructor parameter '{1}'. No source code will be generated for the property, field or constructor. + 类型“{0}”包括属性、字段或构造函数参数“{1}”等引用。不会为属性、字段或构造函数生成源代码。 + + + + Type includes ref like property, field or constructor parameter. + 类型包括属性、字段或构造函数参数等引用。 + + + + Did not generate serialization metadata for type '{0}'. + 未生成类型 '{0}' 的序列化元数据。 + + + + Did not generate serialization metadata for type. + 未生成类型的序列化元数据。 + + + + + \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/Resources/xlf/Strings.zh-Hant.xlf b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Resources/xlf/Strings.zh-Hant.xlf new file mode 100644 index 0000000..ac3fd35 --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/Resources/xlf/Strings.zh-Hant.xlf @@ -0,0 +1,167 @@ + + + + + + Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation. + 衍生的 'JsonSerializerContext' 型別 '{0}' 指定了 JSON 可序列化類型。類型和所有包含類型必須設為部份,才可開始產生原始檔。 + + + + Derived 'JsonSerializerContext' types and all containing types must be partial. + 衍生的 'JsonSerializerContext' 類型和所有包含類型必須是部份。 + + + + The data extension property '{0}.{1}' is invalid. It must implement 'IDictionary<string, JsonElement>' or 'IDictionary<string, object>', or be 'JsonObject'. + 資料延伸屬性 '{0}.{1}' 無效。它必須實作 'IDictionary<string, JsonElement>' 或 'IDictionary<string, object>',或者為 'JsonObject'。 + + + + Data extension property type invalid. + 資料延伸屬性類型無效。 + + + + The custom attribute '{0}' deriving from JsonConverterAttribute is not supported by the source generator. + 來源產生器不支援衍生自 JsonConverterAttribute 的自訂屬性 '{0}'。 + + + + Attributes deriving from JsonConverterAttribute are not supported by the source generator. + 來源產生器不支援衍生自 JsonConverterAttribute 的屬性。 + + + + There are multiple types named '{0}'. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. + 有多個名為 '{0}' 的類型。已為偵測到的第一個項目產生來源。請使用 'JsonSerializableAttribute.TypeInfoPropertyName' 解決此衝突。 + + + + Duplicate type name. + 重複類型名稱。 + + + + Type '{0}' is annotated with 'JsonDerivedTypeAttribute' which is not supported in 'JsonSourceGenerationMode.Serialization'. + 類型 '{0}' 使用 'JsonSourceGenerationMode.Serialization' 不支援的 'JsonDerivedTypeAttribute' 進行註釋。 + + + + 'JsonDerivedTypeAttribute' is not supported in 'JsonSourceGenerationMode.Serialization'. + 'JsonSourceGenerationMode.Serialization' 中不支援 'JsonDerivedTypeAttribute'。 + + + + The member '{0}.{1}' has been annotated with the JsonIncludeAttribute but is not visible to the source generator. + 成員 '{0}.{1}' 已經以 JsonIncludeAttribute 標註,但對來源產生器是不可見的。 + + + + Inaccessible properties annotated with the JsonIncludeAttribute are not supported in source generation mode. + 來源產生模式不支援以 JsonIncludeAttribute 標註的無法存取屬性。 + + + + The type '{0}' defines init-only properties, deserialization of which is currently not supported in source generation mode. + 來源產生模式目前不支援類型 '{0}' 定義之 init-only 屬性的還原序列化。 + + + + Deserialization of init-only properties is currently not supported in source generation mode. + 來源產生模式目前不支援 init-only 屬性的還原序列化。 + + + + The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. + 類型 '{0}' 上的建構函式已使用 JsonConstructorAttribute 標註,但無法供來源產生器存取。 + + + + Constructor annotated with JsonConstructorAttribute is inaccessible. + 無法存取使用 JsonConstructorAttribute 標註的建構函式。 + + + + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. + 成員 '{1}' 上指定的 'JsonConverterAttribute' 類型 '{0}' 不是轉換器類型,或不包含可存取的無參數建構函式。 + + + + The 'JsonConverterAttribute.Type' contains an invalid or inaccessible argument. + 'JsonConverterAttribute.Type' 包含無效或無法存取的引數。 + + + + The type '{0}' has been annotated with JsonSerializableAttribute but does not derive from JsonSerializerContext. No source code will be generated. + 類型 '{0}' 已使用 JsonSerializableAttribute 標註,但並非衍生自 JsonSerializerCoNtext。將不會產生原始程式碼。 + + + + Types annotated with JsonSerializableAttribute must be classes deriving from JsonSerializerContext. + 以 JsonSerializableAttribute 標註的類型必須為衍生自 JsonSerializerCoNtext 的類別。 + + + + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. + 成員 '{0}' 已使用原生 AOT 不支援的 'JsonStringEnumConverter' 加上標註。請考慮改用一般 'JsonStringEnumConverter<TEnum>'。 + + + + The non-generic 'JsonStringEnumConverter' requires dynamic code. + 非一般 'JsonStringEnumConverter' 需要動態程式碼。 + + + + The System.Text.Json source generator is not available in C# {0}. Please use language version {1} or greater. + C# {0} 中無法使用 System.Text.Json 來源產生器。請使用 {1} 或更新的語言版本。 + + + + C# language version not supported by the source generator. + 來源產生器不支援 C# 語言版本。 + + + + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. + 類型 '{0}' 包含多個以 'JsonConstructorAttribute' 註解的建構函式。 + + + + Type has multiple constructors annotated with JsonConstructorAttribute. + 類型包含多個以 JsonConstructorAttribute 註解的建構函式。 + + + + Type '{0}' has multiple members annotated with 'JsonExtensionDataAttribute'. + 類型 '{0}' 有使用 'JsonExtensionDataAttribute' 標註的多個成員。 + + + + Type has multiple members annotated with JsonExtensionDataAttribute. + 類型具有使用 JsonExtensionDataAttribute 標註的多個成員。 + + + + The type '{0}' includes the ref like property, field or constructor parameter '{1}'. No source code will be generated for the property, field or constructor. + 類型 '{0}' 包含 ref,例如屬性、欄位或建構函式參數 '{1}'。不會針對屬性、欄位或建構函式產生原始程式碼。 + + + + Type includes ref like property, field or constructor parameter. + 類型包含 ref,例如屬性、欄位或建構函式參數。 + + + + Did not generate serialization metadata for type '{0}'. + 未產生類型 '{0}' 的序列化中繼資料。 + + + + Did not generate serialization metadata for type. + 未產生類型的序列化中繼資料。 + + + + + \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/System.Text.Json/Flags.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/System.Text.Json/Flags.cs new file mode 100644 index 0000000..598d2ef --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/System.Text.Json/Flags.cs @@ -0,0 +1,262 @@ +namespace CSharpToJsonSchema.Generators.JsonGen.System.Text.Json; + + +/// +/// The generation mode for the System.Text.Json source generator. +/// +[Flags] +public enum JsonSourceGenerationMode +{ + /// + /// When specified on , indicates that both type-metadata initialization logic + /// and optimized serialization logic should be generated for all types. When specified on , + /// indicates that the setting on should be used. + /// + Default = 0, + + /// + /// Instructs the JSON source generator to generate type-metadata initialization logic. + /// + /// + /// This mode supports all features. + /// + Metadata = 1, + + /// + /// Instructs the JSON source generator to generate optimized serialization logic. + /// + /// + /// This mode supports only a subset of features. + /// + Serialization = 2 +} + + /// + /// Determines how handles numbers when serializing and deserializing. + /// + /// The behavior of and is not defined by the JSON specification. Altering the default number handling can potentially produce JSON that cannot be parsed by other JSON implementations. + /// + /// + [Flags] + public enum JsonNumberHandling + { + /// + /// Numbers will only be read from tokens and will only be written as JSON numbers (without quotes). + /// + Strict = 0x0, + + /// + /// Numbers can be read from tokens. + /// Does not prevent numbers from being read from token. + /// Strings that have escaped characters will be unescaped before reading. + /// Leading or trailing trivia within the string token, including whitespace, is not allowed. + /// + AllowReadingFromString = 0x1, + + /// + /// Numbers will be written as JSON strings (with quotes), not as JSON numbers. + /// + /// This behavior is not defined by the JSON specification. Altering the default number handling can potentially produce JSON that cannot be parsed by other JSON implementations. + /// + /// + WriteAsString = 0x2, + + /// + /// The "NaN", "Infinity", and "-Infinity" tokens can be read as + /// floating-point constants, and the and values for these + /// constants (such as and ) + /// will be written as their corresponding JSON string representations. + /// Strings that have escaped characters will be unescaped before reading. + /// Leading or trailing trivia within the string token, including whitespace, is not allowed. + /// + /// This behavior is not defined by the JSON specification. Altering the default number handling can potentially produce JSON that cannot be parsed by other JSON implementations. + /// + /// + AllowNamedFloatingPointLiterals = 0x4 + } + + /// + /// Determines how deserialization will handle object creation for fields or properties. + /// + public enum JsonObjectCreationHandling + { + /// + /// A new instance will always be created when deserializing a field or property. + /// + Replace = 0, + + /// + /// Attempt to populate any instances already found on a deserialized field or property. + /// + Populate = 1, + } + + /// + /// Determines how handles JSON properties that + /// cannot be mapped to a specific .NET member when deserializing object types. + /// + public enum JsonUnmappedMemberHandling + { + /// + /// Silently skips any unmapped properties. This is the default behavior. + /// + Skip = 0, + + /// + /// Throws an exception when an unmapped property is encountered. + /// + Disallow = 1, + } + + /// + /// This enum defines the various ways the can deal with comments. + /// + public enum JsonCommentHandling : byte + { + /// + /// By default, do no allow comments within the JSON input. + /// Comments are treated as invalid JSON if found and a + /// is thrown. + /// + Disallow = 0, + /// + /// Allow comments within the JSON input and ignore them. + /// The will behave as if no comments were present. + /// + Skip = 1, + /// + /// Allow comments within the JSON input and treat them as valid tokens. + /// While reading, the caller will be able to access the comment values. + /// + Allow = 2, + } + + /// + /// Defines how deserializing a type declared as an is handled during deserialization. + /// + public enum JsonUnknownTypeHandling + { + /// + /// A type declared as is deserialized as a . + /// + JsonElement = 0, + /// + /// A type declared as is deserialized as a . + /// + JsonNode = 1 + } + + /// + /// When specified on , + /// determines when properties and fields across the type graph are ignored. + /// When specified on , controls whether + /// a property or field is ignored during serialization and deserialization. This option + /// overrides the setting on . + /// + public enum JsonIgnoreCondition + { + /// + /// Property is never ignored during serialization or deserialization. + /// + Never = 0, + /// + /// Property is always ignored during serialization and deserialization. + /// + Always = 1, + /// + /// If the value is the default, the property is ignored during serialization. + /// This is applied to both reference and value-type properties and fields. + /// + WhenWritingDefault = 2, + /// + /// If the value is , the property is ignored during serialization. + /// This is applied only to reference-type properties and fields. + /// + WhenWritingNull = 3, + /// + /// Property is ignored during serialization + /// + WhenWriting = 4, + /// + /// Property is ignored during deserialization + /// + WhenReading = 5, + } + + /// + /// Signifies what default options are used by . + /// + public enum JsonSerializerDefaults + { + /// + /// Specifies that general-purpose values should be used. These are the same settings applied if a isn't specified. + /// + /// + /// This option implies that property names are treated as case-sensitive and that "PascalCase" name formatting should be employed. + /// + General = 0, + /// + /// Specifies that values should be used more appropriate to web-based scenarios. + /// + /// + /// This option implies that property names are treated as case-insensitive and that "camelCase" name formatting should be employed. + /// + Web = 1 + } + + /// + /// The to be used at run time. + /// + public enum JsonKnownReferenceHandler + { + /// + /// Specifies that circular references should throw exceptions. + /// + Unspecified = 0, + + /// + /// Specifies that the built-in be used to handle references. + /// + Preserve = 1, + + /// + /// Specifies that the built-in be used to ignore cyclic references. + /// + IgnoreCycles = 2, + } + + /// + /// The to be used at run time. + /// + public enum JsonKnownNamingPolicy + { + /// + /// Specifies that JSON property names should not be converted. + /// + Unspecified = 0, + + /// + /// Specifies that the built-in be used to convert JSON property names. + /// + CamelCase = 1, + + /// + /// Specifies that the built-in be used to convert JSON property names. + /// + SnakeCaseLower = 2, + + /// + /// Specifies that the built-in be used to convert JSON property names. + /// + SnakeCaseUpper = 3, + + /// + /// Specifies that the built-in be used to convert JSON property names. + /// + KebabCaseLower = 4, + + /// + /// Specifies that the built-in be used to convert JSON property names. + /// + KebabCaseUpper = 5 + } \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/System.Text.Json/JsonSerializableAttribute.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/System.Text.Json/JsonSerializableAttribute.cs new file mode 100644 index 0000000..89548a2 --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/System.Text.Json/JsonSerializableAttribute.cs @@ -0,0 +1,27 @@ +namespace CSharpToJsonSchema.Generators.JsonGen.System.Text.Json; + +sealed class JsonSerializableAttribute : JsonAttribute +{ +#pragma warning disable IDE0060 + /// + /// Initializes a new instance of with the specified type. + /// + /// The type to generate source code for. + public JsonSerializableAttribute(Type type) { } +#pragma warning restore IDE0060 + + /// + /// The name of the property for the generated for + /// the type on the generated, derived type. + /// + /// + /// Useful to resolve a name collision with another type in the compilation closure. + /// + public string? TypeInfoPropertyName { get; set; } + + /// + /// Determines what the source generator should generate for the type. If the value is , + /// then the setting specified on will be used. + /// + public JsonSourceGenerationMode GenerationMode { get; set; } +} \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonGen/System.Text.Json/JsonSourceGenerationAttribute.cs b/src/libs/CSharpToJsonSchema.Generators/JsonGen/System.Text.Json/JsonSourceGenerationAttribute.cs new file mode 100644 index 0000000..3fd87c5 --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/JsonGen/System.Text.Json/JsonSourceGenerationAttribute.cs @@ -0,0 +1,182 @@ +namespace CSharpToJsonSchema.Generators.JsonGen.System.Text.Json; + +/// +/// Specifies compile-time source generator configuration when applied to class declarations. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] +#if BUILDING_SOURCE_GENERATOR + internal +#else +public +#endif + sealed class JsonSourceGenerationOptionsAttribute : JsonAttribute +{ + /// + /// Constructs a new instance. + /// + public JsonSourceGenerationOptionsAttribute() + { + } + + /// + /// Constructs a new instance with a predefined set of options determined by the specified . + /// + /// The to reason about. + /// Invalid parameter. + public JsonSourceGenerationOptionsAttribute(JsonSerializerDefaults defaults) + { + // Constructor kept in sync with equivalent overload in JsonSerializerOptions + + if (defaults is JsonSerializerDefaults.Web) + { + PropertyNameCaseInsensitive = true; + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase; + NumberHandling = JsonNumberHandling.AllowReadingFromString; + } + else if (defaults is not JsonSerializerDefaults.General) + { + throw new ArgumentOutOfRangeException(nameof(defaults)); + } + } + + /// + /// Specifies the default value of when set. + /// + public bool AllowOutOfOrderMetadataProperties { get; set; } + + /// + /// Specifies the default value of when set. + /// + public bool AllowTrailingCommas { get; set; } + + /// + /// Specifies the default value of when set. + /// + public Type[]? Converters { get; set; } + + /// + /// Specifies the default value of when set. + /// + public int DefaultBufferSize { get; set; } + + /// + /// Specifies the default value of when set. + /// + public JsonIgnoreCondition DefaultIgnoreCondition { get; set; } + + /// + /// Specifies the default value of when set. + /// + public JsonKnownNamingPolicy DictionaryKeyPolicy { get; set; } + + /// + /// Specifies the default value of when set. + /// + public bool IgnoreReadOnlyFields { get; set; } + + /// + /// Specifies the default value of when set. + /// + public bool IgnoreReadOnlyProperties { get; set; } + + /// + /// Specifies the default value of when set. + /// + public bool IncludeFields { get; set; } + + /// + /// Specifies the default value of when set. + /// + public int MaxDepth { get; set; } + + /// + /// Specifies the default value of when set. + /// + public JsonNumberHandling NumberHandling { get; set; } + + /// + /// Specifies the default value of when set. + /// + public JsonObjectCreationHandling PreferredObjectCreationHandling { get; set; } + + /// + /// Specifies the default value of when set. + /// + public bool PropertyNameCaseInsensitive { get; set; } + + /// + /// Specifies the default value of when set. + /// + public JsonKnownNamingPolicy PropertyNamingPolicy { get; set; } + + /// + /// Specifies the default value of when set. + /// + public JsonCommentHandling ReadCommentHandling { get; set; } + + /// + /// Specifies the default value of when set. + /// + public JsonKnownReferenceHandler ReferenceHandler { get; set; } + + /// + /// Specifies the default value of when set. + /// + public bool RespectNullableAnnotations { get; set; } + + /// + /// Specifies the default value of when set. + /// + public bool RespectRequiredConstructorParameters { get; set; } + + /// + /// Specifies the default value of when set. + /// + public JsonUnknownTypeHandling UnknownTypeHandling { get; set; } + + /// + /// Specifies the default value of when set. + /// + public JsonUnmappedMemberHandling UnmappedMemberHandling { get; set; } + + /// + /// Specifies the default value of when set. + /// + public bool WriteIndented { get; set; } + + /// + /// Specifies the default value of when set. + /// + public char IndentCharacter { get; set; } + + /// + /// Specifies the default value of when set. + /// + public int IndentSize { get; set; } + + /// + /// Specifies the default source generation mode for type declarations that don't set a . + /// + public JsonSourceGenerationMode GenerationMode { get; set; } + + /// + /// Instructs the source generator to default to + /// instead of numeric serialization for all enum types encountered in its type graph. + /// + public bool UseStringEnumConverter { get; set; } + + /// + /// Specifies the default value of when set. + /// + public string? NewLine { get; set; } +} + +#if BUILDING_SOURCE_GENERATOR + internal +#else +public +#endif + abstract class JsonAttribute : Attribute +{ +} + diff --git a/src/libs/CSharpToJsonSchema.Generators/JsonSchemaGenerator.cs b/src/libs/CSharpToJsonSchema.Generators/JsonSchemaGenerator.cs index b7b95fa..7f3fb88 100755 --- a/src/libs/CSharpToJsonSchema.Generators/JsonSchemaGenerator.cs +++ b/src/libs/CSharpToJsonSchema.Generators/JsonSchemaGenerator.cs @@ -1,8 +1,11 @@ +using CSharpToJsonSchema.Generators.Conversion; +using CSharpToJsonSchema.Generators.JsonGen; using H.Generators; using H.Generators.Extensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; -using CSharpToJsonSchema.Generators.Core.Conversion; +using CSharpToJsonSchema.Generators.Models; +using Microsoft.CodeAnalysis.Text; namespace CSharpToJsonSchema.Generators; @@ -25,14 +28,19 @@ public void Initialize(IncrementalGeneratorInitializationContext context) .ForAttributeWithMetadataName("CSharpToJsonSchema.GenerateJsonSchemaAttribute") .SelectManyAllAttributesOfCurrentInterfaceSyntax() .SelectAndReportExceptions(PrepareData, context, Id); - + attributes .SelectAndReportExceptions(AsTools, context, Id) .AddSource(context); + attributes .SelectAndReportExceptions(AsCalls, context, Id) .AddSource(context); + + var generator = new JsonSourceGenerator(); + generator.Initialize2(context); } + private static InterfaceData PrepareData( (SemanticModel SemanticModel, AttributeData AttributeData, InterfaceDeclarationSyntax InterfaceSyntax, INamedTypeSymbol InterfaceSymbol) tuple) @@ -41,20 +49,28 @@ private static InterfaceData PrepareData( return interfaceSymbol.PrepareData(attributeData); } - + private static FileWithName AsTools(InterfaceData @interface) { return new FileWithName( Name: $"{@interface.Name}.Tools.generated.cs", Text: Sources.GenerateClientImplementation(@interface)); } - + private static FileWithName AsCalls(InterfaceData @interface) { return new FileWithName( Name: $"{@interface.Name}.Calls.generated.cs", Text: Sources.GenerateCalls(@interface)); } + + + + public static (string hintName, SourceText sourceText) AsCalls2(InterfaceData @interface) + { + return ($"{@interface.Name}.Calls.generated.cs", SourceText.From(Sources.GenerateCalls(@interface))); + } + #endregion } \ No newline at end of file diff --git a/src/libs/CSharpToJsonSchema.Generators/Models/ClassData.cs b/src/libs/CSharpToJsonSchema.Generators/Models/ClassData.cs new file mode 100644 index 0000000..f18de4d --- /dev/null +++ b/src/libs/CSharpToJsonSchema.Generators/Models/ClassData.cs @@ -0,0 +1,25 @@ +using Microsoft.CodeAnalysis; + +namespace CSharpToJsonSchema.Generators.Models; + +public sealed class ClassData +{ + public INamedTypeSymbol ClassSymbol { get; } + + public ClassData(INamedTypeSymbol classSymbol) + { + ClassSymbol = classSymbol; + } + + /// + /// The name of the class where JSON type info will be generated. + /// + public string Name => ClassSymbol.Name; + + /// + /// The namespace of the class. + /// + public string Namespace => ClassSymbol.ContainingNamespace.IsGlobalNamespace + ? string.Empty + : ClassSymbol.ContainingNamespace.ToString(); +} diff --git a/src/libs/CSharpToJsonSchema.Generators/Models/InterfaceData.cs b/src/libs/CSharpToJsonSchema.Generators/Models/InterfaceData.cs index 2f78ed9..71ca4c2 100644 --- a/src/libs/CSharpToJsonSchema.Generators/Models/InterfaceData.cs +++ b/src/libs/CSharpToJsonSchema.Generators/Models/InterfaceData.cs @@ -1,4 +1,4 @@ -namespace H.Generators; +namespace CSharpToJsonSchema.Generators.Models; public readonly record struct InterfaceData( string Namespace, diff --git a/src/libs/CSharpToJsonSchema.Generators/Models/MethodData.cs b/src/libs/CSharpToJsonSchema.Generators/Models/MethodData.cs index 1b5d0c0..6938daf 100644 --- a/src/libs/CSharpToJsonSchema.Generators/Models/MethodData.cs +++ b/src/libs/CSharpToJsonSchema.Generators/Models/MethodData.cs @@ -1,6 +1,4 @@ -using CSharpToJsonSchema.Generators.Core; - -namespace H.Generators; +namespace CSharpToJsonSchema.Generators.Models; public readonly record struct MethodData( string Name, diff --git a/src/libs/CSharpToJsonSchema.Generators/Models/OpenApiSchema.cs b/src/libs/CSharpToJsonSchema.Generators/Models/OpenApiSchema.cs index f99f682..1861ce5 100644 --- a/src/libs/CSharpToJsonSchema.Generators/Models/OpenApiSchema.cs +++ b/src/libs/CSharpToJsonSchema.Generators/Models/OpenApiSchema.cs @@ -1,4 +1,4 @@ -namespace CSharpToJsonSchema.Generators.Core; +namespace CSharpToJsonSchema.Generators.Models; public readonly record struct OpenApiSchema( string Name, diff --git a/src/libs/CSharpToJsonSchema.Generators/Sources.Calls.cs b/src/libs/CSharpToJsonSchema.Generators/Sources.Calls.cs index adc6d01..aa3e214 100755 --- a/src/libs/CSharpToJsonSchema.Generators/Sources.Calls.cs +++ b/src/libs/CSharpToJsonSchema.Generators/Sources.Calls.cs @@ -1,3 +1,4 @@ +using CSharpToJsonSchema.Generators.Models; using H.Generators; using H.Generators.Extensions; @@ -8,13 +9,10 @@ internal static partial class Sources public static string GenerateCalls(InterfaceData @interface) { var extensionsClassName = @interface.Name.Substring(startIndex: 1) + "Extensions"; - - return @$"#nullable enable + var res = @$"#nullable enable namespace {@interface.Namespace} {{ - public static partial class {extensionsClassName} - {{ {@interface.Methods.Select(static method => $@" public class {method.Name}Args {{ @@ -22,6 +20,11 @@ public class {method.Name}Args }} ").Inject()} + public static partial class {extensionsClassName} + {{ + + + public static global::System.Collections.Generic.IReadOnlyDictionary>> AsCalls(this {@interface.Name} service) {{ return new global::System.Collections.Generic.Dictionary>> @@ -58,17 +61,37 @@ public class {method.Name}Args }} {@interface.Methods.Select(method => $@" - public static {extensionsClassName}.{method.Name}Args As{method.Name}Args( + public static {method.Name}Args As{method.Name}Args( this {@interface.Name} functions, string json) {{ + #if NET6_0_OR_GREATER + if(global::System.Text.Json.JsonSerializer.IsReflectionEnabledByDefault) + {{ + return + global::System.Text.Json.JsonSerializer.Deserialize<{method.Name}Args>(json, new global::System.Text.Json.JsonSerializerOptions + {{ + PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, + Converters = {{{{ new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }}}} + }}) ?? + throw new global::System.InvalidOperationException(""Could not deserialize JSON.""); + + }} + else + {{ + return global::System.Text.Json.JsonSerializer.Deserialize(json, global::{@interface.Namespace}.{extensionsClassName}JsonSerializerContext.Default.{method.Name}Args) ?? + throw new global::System.InvalidOperationException(""Could not deserialize JSON.""); + + }} + #else return - global::System.Text.Json.JsonSerializer.Deserialize<{extensionsClassName}.{method.Name}Args>(json, new global::System.Text.Json.JsonSerializerOptions + global::System.Text.Json.JsonSerializer.Deserialize<{method.Name}Args>(json, new global::System.Text.Json.JsonSerializerOptions {{ PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, Converters = {{{{ new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }}}} }}) ?? throw new global::System.InvalidOperationException(""Could not deserialize JSON.""); + #endif }} ").Inject()} @@ -78,11 +101,27 @@ public class {method.Name}Args var args = functions.As{method.Name}Args(json); var jsonResult = functions.{method.Name}({string.Join(", ", method.Parameters.Properties.Select(static parameter => $@"args.{parameter.Name.ToPropertyName()}"))}); - return global::System.Text.Json.JsonSerializer.Serialize(jsonResult, new global::System.Text.Json.JsonSerializerOptions + #if NET6_0_OR_GREATER + if(global::System.Text.Json.JsonSerializer.IsReflectionEnabledByDefault) {{ - PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, - Converters = {{ new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }}, - }}); + return global::System.Text.Json.JsonSerializer.Serialize(jsonResult, new global::System.Text.Json.JsonSerializerOptions + {{ + PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, + Converters = {{ new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }}, + }}); + }} + else + {{ + return global::System.Text.Json.JsonSerializer.Serialize(jsonResult, global::{@interface.Namespace}.{extensionsClassName}JsonSerializerContext.Default.GetTypeInfo(jsonResult.GetType())); + }} + #else + return global::System.Text.Json.JsonSerializer.Serialize(jsonResult, new global::System.Text.Json.JsonSerializerOptions + {{ + PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, + Converters = {{ new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }}, + }}); + #endif + }} ").Inject()} @@ -104,12 +143,29 @@ public class {method.Name}Args var jsonResult = await functions.{method.Name}({string.Join(", ", method.Parameters.Properties .Select(static parameter => $@"args.{parameter.Name.ToPropertyName()}").Append("cancellationToken"))}); + #if NET6_0_OR_GREATER + if(global::System.Text.Json.JsonSerializer.IsReflectionEnabledByDefault) + {{ + return global::System.Text.Json.JsonSerializer.Serialize(jsonResult, new global::System.Text.Json.JsonSerializerOptions + {{ + PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, + Converters = {{ new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }}, + }}); + }} + else + {{ + return global::System.Text.Json.JsonSerializer.Serialize(jsonResult, global::{@interface.Namespace}.{extensionsClassName}JsonSerializerContext.Default.GetTypeInfo(jsonResult.GetType())); + }} + #else return global::System.Text.Json.JsonSerializer.Serialize(jsonResult, new global::System.Text.Json.JsonSerializerOptions {{ PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, Converters = {{ new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }}, }}); + #endif + }} + ").Inject()} {@interface.Methods.Where(static x => x is { IsAsync: true, IsVoid: true }).Select(method => $@" @@ -137,6 +193,13 @@ public class {method.Name}Args return await func(argumentsAsJson, cancellationToken); }} }} + + public partial class {extensionsClassName}JsonSerializerContext: global::System.Text.Json.Serialization.JsonSerializerContext + {{ + + }} }}"; + + return res; } } diff --git a/src/libs/CSharpToJsonSchema.Generators/Sources.Tools.cs b/src/libs/CSharpToJsonSchema.Generators/Sources.Tools.cs index 50205d9..bcefbc1 100755 --- a/src/libs/CSharpToJsonSchema.Generators/Sources.Tools.cs +++ b/src/libs/CSharpToJsonSchema.Generators/Sources.Tools.cs @@ -1,6 +1,6 @@ -using H.Generators; +using CSharpToJsonSchema.Generators.Models; +using H.Generators; using H.Generators.Extensions; -using CSharpToJsonSchema.Generators.Core; namespace CSharpToJsonSchema.Generators; diff --git a/src/libs/CSharpToJsonSchema/GenerateJsonTypeInfoAttribute.cs b/src/libs/CSharpToJsonSchema/GenerateJsonTypeInfoAttribute.cs new file mode 100644 index 0000000..0baece7 --- /dev/null +++ b/src/libs/CSharpToJsonSchema/GenerateJsonTypeInfoAttribute.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CSharpToJsonSchema +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Class, Inherited = false, AllowMultiple = true)] + public sealed class GenerateJsonTypeInfoAttribute : global::System.Attribute + { +#pragma warning disable IDE0060 + /// + /// Initializes a new instance of with the specified type. + /// + /// The type to generate source code for. + public GenerateJsonTypeInfoAttribute(Type type) { } +#pragma warning restore IDE0060 + + /// + /// The name of the property for the generated for + /// the type on the generated, derived type. + /// + /// + /// Useful to resolve a name collision with another type in the compilation closure. + /// + public string? TypeInfoPropertyName { get; set; } + + /// + /// Determines what the source generator should generate for the type. If the value is , + /// then the setting specified on will be used. + /// + // public JsonSourceGenerationMode GenerationMode { get; set; } + } +} diff --git a/src/tests/CSharpToJsonSchema.AotTests/CSharpToJsonSchema.AotTests.csproj b/src/tests/CSharpToJsonSchema.AotTests/CSharpToJsonSchema.AotTests.csproj new file mode 100644 index 0000000..409fda3 --- /dev/null +++ b/src/tests/CSharpToJsonSchema.AotTests/CSharpToJsonSchema.AotTests.csproj @@ -0,0 +1,29 @@ + + + + net9.0 + enable + enable + false + false + + + + + + + + + + + + + + + + + + + + + diff --git a/src/tests/CSharpToJsonSchema.AotTests/JsonSerializationTests.cs b/src/tests/CSharpToJsonSchema.AotTests/JsonSerializationTests.cs new file mode 100644 index 0000000..9a5e5b1 --- /dev/null +++ b/src/tests/CSharpToJsonSchema.AotTests/JsonSerializationTests.cs @@ -0,0 +1,120 @@ +using System.Text.Json; +using CSharpToJsonSchema.AotTests; +using CSharpToJsonSchema.IntegrationTests.Services; +using FluentAssertions; + +namespace CSharpToJsonSchema.IntegrationTests; + +public class JsonSerializationTests +{ + [Fact] + public Task ShouldDeserializeWithJsonTypeInfo() + { + var args = new ComplexClassSerializerTools() + { + Name = "Example Name", + Age = 30, + IsActive = true, + CreatedAt = DateTime.UtcNow, + Tags = new List { "tag1", "tag2", "tag3" }, + Metadata = new Dictionary + { + { "Key1", "Value1" }, + { "Key2", 12345 }, + { "Key3", true } + }, + Details = new ComplexClassSerializerTools.NestedClass + { + Description = "Nested description", + Value = 99.99, + Numbers = new List { 1, 2, 3, 4, 5 } + } + }; + + var serialized = JsonSerializer.Serialize(args, WeatherToolsExtensionsJsonSerializerContext.Default.ComplexClassSerializerTools); + serialized.Should().NotBeNullOrEmpty(); + + var deserialized = JsonSerializer.Deserialize(serialized, + WeatherToolsExtensionsJsonSerializerContext.Default.ComplexClassSerializerTools); + + deserialized.Should().NotBeNull(); + deserialized!.Name.Should().Be(args.Name); + deserialized.Age.Should().Be(args.Age); + deserialized.IsActive.Should().Be(args.IsActive); + deserialized.CreatedAt.Should().BeCloseTo(args.CreatedAt, TimeSpan.FromSeconds(1)); // Accounting for serialization precision + deserialized.Tags.Should().BeEquivalentTo(args.Tags); + //foreach (var key in args.Metadata.Keys) + //{ + // deserialized.Metadata[key].Should().Be(args.Metadata[key]); + //} + + + deserialized.Details.Should().NotBeNull(); + deserialized.Details!.Description.Should().Be(args.Details.Description); + deserialized.Details.Value.Should().Be(args.Details.Value); + deserialized.Details.Numbers.Should().BeEquivalentTo(args.Details.Numbers); + + + return Task.CompletedTask; + } + + [Fact] + public async Task ShouldWorkWithService() + { + var service = new WeatherService(); + var calls = service.AsCalls(); + + var args = new ComplexClassSerializerTools() + { + Name = "Example Name", + Age = 30, + IsActive = true, + CreatedAt = DateTime.UtcNow, + Tags = new List { "tag1", "tag2", "tag3" }, + Metadata = new Dictionary + { + { "Key1", "Value1" }, + { "Key2", 12345 }, + { "Key3", true } + }, + Details = new ComplexClassSerializerTools.NestedClass + { + Description = "Nested description", + Value = 99.99, + Numbers = new List { 1, 2, 3, 4, 5 } + } + }; + + var args2 = new GetComplexDataTypeArgs(); + args2.Unit = Unit.Fahrenheit; + args2.WeatherToTest = new Weather() + { + Description = "description", + Location = "Location", + Temperature = 25, + Unit = Unit.Celsius + }; + var serialize = JsonSerializer.Serialize(args2, WeatherToolsExtensionsJsonSerializerContext.Default.GetComplexDataTypeArgs); + + var dx = await calls["GetComplexDataType"].Invoke(serialize, default); + + var deserialized = JsonSerializer.Deserialize(dx, WeatherToolsExtensionsJsonSerializerContext.Default.ComplexClassSerializerTools); + deserialized.Should().NotBeNull(); + deserialized!.Name.Should().Be(args.Name); + deserialized.Age.Should().Be(args.Age); + deserialized.IsActive.Should().Be(args.IsActive); + deserialized.CreatedAt.Should().BeCloseTo(args.CreatedAt, TimeSpan.FromSeconds(1)); // Accounting for serialization precision + deserialized.Tags.Should().BeEquivalentTo(args.Tags); + //foreach (var key in args.Metadata.Keys) + //{ + // deserialized.Metadata[key].Should().Be(args.Metadata[key]); + //} + + + deserialized.Details.Should().NotBeNull(); + deserialized.Details!.Description.Should().Be(args.Details.Description); + deserialized.Details.Value.Should().Be(args.Details.Value); + deserialized.Details.Numbers.Should().BeEquivalentTo(args.Details.Numbers); + + } +} \ No newline at end of file diff --git a/src/tests/CSharpToJsonSchema.AotTests/Services/ComplexClass.cs b/src/tests/CSharpToJsonSchema.AotTests/Services/ComplexClass.cs new file mode 100644 index 0000000..21ff02c --- /dev/null +++ b/src/tests/CSharpToJsonSchema.AotTests/Services/ComplexClass.cs @@ -0,0 +1,26 @@ +using System.Text.Json.Serialization; + +namespace CSharpToJsonSchema.AotTests; + +public partial class ComplexClassSerializerTools +{ + public string Name { get; set; } + public int Age { get; set; } + public bool IsActive { get; set; } + public DateTime CreatedAt { get; set; } + public List Tags { get; set; } + public Dictionary Metadata { get; set; } + public NestedClass Details { get; set; } + + public class NestedClass + { + public string Description { get; set; } + public double Value { get; set; } + public List Numbers { get; set; } + } +} + +//public partial class ComplexClassSerializerToolsJsonSerializerContext : JsonSerializerContext +//{ + +//} \ No newline at end of file diff --git a/src/tests/CSharpToJsonSchema.AotTests/Services/WeatherTools.cs b/src/tests/CSharpToJsonSchema.AotTests/Services/WeatherTools.cs new file mode 100644 index 0000000..df45476 --- /dev/null +++ b/src/tests/CSharpToJsonSchema.AotTests/Services/WeatherTools.cs @@ -0,0 +1,92 @@ +using CSharpToJsonSchema.AotTests; + +namespace CSharpToJsonSchema.IntegrationTests.Services; + +// ReSharper disable RedundantUsingDirective +using System.Threading; +using System.Threading.Tasks; +using DescriptionAttribute = System.ComponentModel.DescriptionAttribute; + +public enum Unit +{ + Celsius, + Fahrenheit, +} + +public class Weather +{ + public string Location { get; set; } = string.Empty; + public double Temperature { get; set; } + public Unit Unit { get; set; } + public string Description { get; set; } = string.Empty; +} + +[GenerateJsonSchema] +public interface IWeatherTools +{ + [Description("Get the current weather in a given location")] + public Weather GetCurrentWeather( + [Description("The city and state, e.g. San Francisco, CA")] string location, + Unit unit = Unit.Celsius); + + [Description("Get the current weather in a given location")] + public Task GetCurrentWeatherAsync( + [Description("The city and state, e.g. San Francisco, CA")] string location, + Unit unit = Unit.Celsius, + CancellationToken cancellationToken = default); + + [Description("Get Complex Data type")] + public Task GetComplexDataType( + [Description("The city and state, e.g. San Francisco, CA")] Weather weatherToTest, + Unit unit = Unit.Celsius, + CancellationToken cancellationToken = default); +} + +public class WeatherService : IWeatherTools +{ + public Weather GetCurrentWeather(string location, Unit unit = Unit.Celsius) + { + return new Weather + { + Location = location, + Temperature = 22.0, + Unit = unit, + Description = "Sunny", + }; + } + + public Task GetCurrentWeatherAsync(string location, Unit unit = Unit.Celsius, CancellationToken cancellationToken = default) + { + return Task.FromResult(new Weather + { + Location = location, + Temperature = 22.0, + Unit = unit, + Description = "Sunny", + }); + } + + public async Task GetComplexDataType(Weather weatherName, Unit unit = Unit.Celsius, CancellationToken cancellationToken = default) + { + return new ComplexClassSerializerTools() + { + Name = "Example Name", + Age = 30, + IsActive = true, + CreatedAt = DateTime.UtcNow, + Tags = new List { "tag1", "tag2", "tag3" }, + Metadata = new Dictionary + { + { "Key1", "Value1" }, + { "Key2", 12345 }, + { "Key3", true } + }, + Details = new ComplexClassSerializerTools.NestedClass + { + Description = "Nested description", + Value = 99.99, + Numbers = new List { 1, 2, 3, 4, 5 } + } + }; + } +} \ No newline at end of file diff --git a/src/tests/CSharpToJsonSchema.IntegrationTests/CSharpToJsonSchema.IntegrationTests.csproj b/src/tests/CSharpToJsonSchema.IntegrationTests/CSharpToJsonSchema.IntegrationTests.csproj index b1d91f4..10392a9 100644 --- a/src/tests/CSharpToJsonSchema.IntegrationTests/CSharpToJsonSchema.IntegrationTests.csproj +++ b/src/tests/CSharpToJsonSchema.IntegrationTests/CSharpToJsonSchema.IntegrationTests.csproj @@ -2,6 +2,7 @@ net9.0 + diff --git a/src/tests/CSharpToJsonSchema.IntegrationTests/ComplexClassSerializerTools.cs b/src/tests/CSharpToJsonSchema.IntegrationTests/ComplexClassSerializerTools.cs new file mode 100644 index 0000000..bacba38 --- /dev/null +++ b/src/tests/CSharpToJsonSchema.IntegrationTests/ComplexClassSerializerTools.cs @@ -0,0 +1,23 @@ +using System.Text.Json.Serialization; + +namespace CSharpToJsonSchema.IntegrationTests; + + +public partial class ComplexClassSerializerTools +{ + public string Name { get; set; } + public int Age { get; set; } + public bool IsActive { get; set; } + public DateTime CreatedAt { get; set; } + public List Tags { get; set; } + public Dictionary Metadata { get; set; } + public NestedClass Details { get; set; } + + public class NestedClass + { + public string Description { get; set; } + public double Value { get; set; } + public List Numbers { get; set; } + } +} + diff --git a/src/tests/CSharpToJsonSchema.IntegrationTests/VariousTypesTools.cs b/src/tests/CSharpToJsonSchema.IntegrationTests/VariousTypesTools.cs index 838ddfe..b0a2572 100755 --- a/src/tests/CSharpToJsonSchema.IntegrationTests/VariousTypesTools.cs +++ b/src/tests/CSharpToJsonSchema.IntegrationTests/VariousTypesTools.cs @@ -10,7 +10,7 @@ namespace CSharpToJsonSchema.IntegrationTests; public interface IVariousTypesTools { [Description("Get the current weather in a given location")] - public bool GetCurrentWeather( + public bool GetCurrentWeather3( long parameter1, int parameter2, double parameter3, @@ -34,7 +34,7 @@ public bool GetCurrentWeather( public class VariousTypesService : IVariousTypesTools { - public bool GetCurrentWeather( + public bool GetCurrentWeather3( long parameter1, int parameter2, double parameter3, diff --git a/src/tests/CSharpToJsonSchema.IntegrationTests/WeatherStrictTools.cs b/src/tests/CSharpToJsonSchema.IntegrationTests/WeatherStrictTools.cs index 6d07c32..0fbca1e 100755 --- a/src/tests/CSharpToJsonSchema.IntegrationTests/WeatherStrictTools.cs +++ b/src/tests/CSharpToJsonSchema.IntegrationTests/WeatherStrictTools.cs @@ -23,12 +23,12 @@ public class Weather2 public interface IWeatherStrictTools { [Description("Get the current weather in a given location")] - public Weather2 GetCurrentWeather( + public Weather2 GetCurrentWeather2( [Description("The city and state, e.g. San Francisco, CA")] string location, Unit2 unit); [Description("Get the current weather in a given location")] - public Task GetCurrentWeatherAsync( + public Task GetCurrentWeatherAsync2( [Description("The city and state, e.g. San Francisco, CA")] string location, Unit2 unit, CancellationToken cancellationToken = default); @@ -36,7 +36,7 @@ public Task GetCurrentWeatherAsync( public class WeatherStrictService : IWeatherStrictTools { - public Weather2 GetCurrentWeather(string location, Unit2 unit) + public Weather2 GetCurrentWeather2(string location, Unit2 unit) { return new Weather2 { @@ -47,7 +47,7 @@ public Weather2 GetCurrentWeather(string location, Unit2 unit) }; } - public Task GetCurrentWeatherAsync(string location, Unit2 unit, CancellationToken cancellationToken = default) + public Task GetCurrentWeatherAsync2(string location, Unit2 unit, CancellationToken cancellationToken = default) { return Task.FromResult(new Weather2 { diff --git a/src/tests/CSharpToJsonSchema.SnapshotTests/CSharpToJsonSchema.SnapshotTests.csproj b/src/tests/CSharpToJsonSchema.SnapshotTests/CSharpToJsonSchema.SnapshotTests.csproj index f188196..e2b1c46 100644 --- a/src/tests/CSharpToJsonSchema.SnapshotTests/CSharpToJsonSchema.SnapshotTests.csproj +++ b/src/tests/CSharpToJsonSchema.SnapshotTests/CSharpToJsonSchema.SnapshotTests.csproj @@ -1,4 +1,4 @@ - + net9.0 @@ -19,8 +19,8 @@ - - + + @@ -40,4 +40,8 @@ + + + + diff --git a/src/tests/CSharpToJsonSchema.SnapshotTests/SnapshotTests.cs b/src/tests/CSharpToJsonSchema.SnapshotTests/SnapshotTests.cs index 484a83c..e7cfad0 100755 --- a/src/tests/CSharpToJsonSchema.SnapshotTests/SnapshotTests.cs +++ b/src/tests/CSharpToJsonSchema.SnapshotTests/SnapshotTests.cs @@ -3,6 +3,7 @@ namespace CSharpToJsonSchema.SnapshotTests; [TestClass] public class ToolTests : VerifyBase { + [TestMethod] public Task Weather() { diff --git a/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.Boolean.g.verified.cs b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.Boolean.g.verified.cs new file mode 100644 index 0000000..c12002c --- /dev/null +++ b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.Boolean.g.verified.cs @@ -0,0 +1,37 @@ +//HintName: IVariousTypesTools.Boolean.g.cs +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +namespace CSharpToJsonSchema.IntegrationTests +{ + public partial class VariousTypesToolsExtensionsJsonSerializerContext + { + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo? _Boolean; + + /// + /// Defines the source generated JSON serialization contract metadata for a given type. + /// + #nullable disable annotations // Marking the property type as nullable-oblivious. + public global::System.Text.Json.Serialization.Metadata.JsonTypeInfo Boolean + #nullable enable annotations + { + get => _Boolean ??= (global::System.Text.Json.Serialization.Metadata.JsonTypeInfo)Options.GetTypeInfo(typeof(bool)); + } + + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo Create_Boolean(global::System.Text.Json.JsonSerializerOptions options) + { + if (!TryGetTypeInfoForRuntimeCustomConverter(options, out global::System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo)) + { + jsonTypeInfo = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreateValueInfo(options, global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.BooleanConverter); + } + + jsonTypeInfo.OriginatingResolver = this; + return jsonTypeInfo; + } + } +} diff --git a/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.Calls.generated.verified.cs b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.Calls.generated.verified.cs index 70ca68e..efc47f9 100644 --- a/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.Calls.generated.verified.cs +++ b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.Calls.generated.verified.cs @@ -3,9 +3,7 @@ namespace CSharpToJsonSchema.IntegrationTests { - public static partial class VariousTypesToolsExtensions - { - public class GetCurrentWeatherArgs + public class GetCurrentWeather3Args { public long Parameter1 { get; set; } public int Parameter2 { get; set; } @@ -36,13 +34,18 @@ public class GetValueAsyncArgs } + public static partial class VariousTypesToolsExtensions + { + + + public static global::System.Collections.Generic.IReadOnlyDictionary>> AsCalls(this IVariousTypesTools service) { return new global::System.Collections.Generic.Dictionary>> { - ["GetCurrentWeather"] = (json, _) => + ["GetCurrentWeather3"] = (json, _) => { - return global::System.Threading.Tasks.Task.FromResult(service.CallGetCurrentWeather(json)); + return global::System.Threading.Tasks.Task.FromResult(service.CallGetCurrentWeather3(json)); }, ["GetValue"] = (json, _) => @@ -68,81 +71,197 @@ public class GetValueAsyncArgs }; } - public static VariousTypesToolsExtensions.GetCurrentWeatherArgs AsGetCurrentWeatherArgs( + public static GetCurrentWeather3Args AsGetCurrentWeather3Args( this IVariousTypesTools functions, string json) { + #if NET6_0_OR_GREATER + if(global::System.Text.Json.JsonSerializer.IsReflectionEnabledByDefault) + { + return + global::System.Text.Json.JsonSerializer.Deserialize(json, new global::System.Text.Json.JsonSerializerOptions + { + PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, + Converters = {{ new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }} + }) ?? + throw new global::System.InvalidOperationException("Could not deserialize JSON."); + + } + else + { + return global::System.Text.Json.JsonSerializer.Deserialize(json, global::CSharpToJsonSchema.IntegrationTests.VariousTypesToolsExtensionsJsonSerializerContext.Default.GetCurrentWeather3Args) ?? + throw new global::System.InvalidOperationException("Could not deserialize JSON."); + + } + #else return - global::System.Text.Json.JsonSerializer.Deserialize(json, new global::System.Text.Json.JsonSerializerOptions + global::System.Text.Json.JsonSerializer.Deserialize(json, new global::System.Text.Json.JsonSerializerOptions { PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, Converters = {{ new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }} }) ?? throw new global::System.InvalidOperationException("Could not deserialize JSON."); + #endif } - public static VariousTypesToolsExtensions.SetValueArgs AsSetValueArgs( + public static SetValueArgs AsSetValueArgs( this IVariousTypesTools functions, string json) { + #if NET6_0_OR_GREATER + if(global::System.Text.Json.JsonSerializer.IsReflectionEnabledByDefault) + { + return + global::System.Text.Json.JsonSerializer.Deserialize(json, new global::System.Text.Json.JsonSerializerOptions + { + PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, + Converters = {{ new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }} + }) ?? + throw new global::System.InvalidOperationException("Could not deserialize JSON."); + + } + else + { + return global::System.Text.Json.JsonSerializer.Deserialize(json, global::CSharpToJsonSchema.IntegrationTests.VariousTypesToolsExtensionsJsonSerializerContext.Default.SetValueArgs) ?? + throw new global::System.InvalidOperationException("Could not deserialize JSON."); + + } + #else return - global::System.Text.Json.JsonSerializer.Deserialize(json, new global::System.Text.Json.JsonSerializerOptions + global::System.Text.Json.JsonSerializer.Deserialize(json, new global::System.Text.Json.JsonSerializerOptions { PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, Converters = {{ new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }} }) ?? throw new global::System.InvalidOperationException("Could not deserialize JSON."); + #endif } - public static VariousTypesToolsExtensions.GetValueArgs AsGetValueArgs( + public static GetValueArgs AsGetValueArgs( this IVariousTypesTools functions, string json) { + #if NET6_0_OR_GREATER + if(global::System.Text.Json.JsonSerializer.IsReflectionEnabledByDefault) + { + return + global::System.Text.Json.JsonSerializer.Deserialize(json, new global::System.Text.Json.JsonSerializerOptions + { + PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, + Converters = {{ new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }} + }) ?? + throw new global::System.InvalidOperationException("Could not deserialize JSON."); + + } + else + { + return global::System.Text.Json.JsonSerializer.Deserialize(json, global::CSharpToJsonSchema.IntegrationTests.VariousTypesToolsExtensionsJsonSerializerContext.Default.GetValueArgs) ?? + throw new global::System.InvalidOperationException("Could not deserialize JSON."); + + } + #else return - global::System.Text.Json.JsonSerializer.Deserialize(json, new global::System.Text.Json.JsonSerializerOptions + global::System.Text.Json.JsonSerializer.Deserialize(json, new global::System.Text.Json.JsonSerializerOptions { PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, Converters = {{ new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }} }) ?? throw new global::System.InvalidOperationException("Could not deserialize JSON."); + #endif } - public static VariousTypesToolsExtensions.SetValueAsyncArgs AsSetValueAsyncArgs( + public static SetValueAsyncArgs AsSetValueAsyncArgs( this IVariousTypesTools functions, string json) { + #if NET6_0_OR_GREATER + if(global::System.Text.Json.JsonSerializer.IsReflectionEnabledByDefault) + { + return + global::System.Text.Json.JsonSerializer.Deserialize(json, new global::System.Text.Json.JsonSerializerOptions + { + PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, + Converters = {{ new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }} + }) ?? + throw new global::System.InvalidOperationException("Could not deserialize JSON."); + + } + else + { + return global::System.Text.Json.JsonSerializer.Deserialize(json, global::CSharpToJsonSchema.IntegrationTests.VariousTypesToolsExtensionsJsonSerializerContext.Default.SetValueAsyncArgs) ?? + throw new global::System.InvalidOperationException("Could not deserialize JSON."); + + } + #else return - global::System.Text.Json.JsonSerializer.Deserialize(json, new global::System.Text.Json.JsonSerializerOptions + global::System.Text.Json.JsonSerializer.Deserialize(json, new global::System.Text.Json.JsonSerializerOptions { PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, Converters = {{ new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }} }) ?? throw new global::System.InvalidOperationException("Could not deserialize JSON."); + #endif } - public static VariousTypesToolsExtensions.GetValueAsyncArgs AsGetValueAsyncArgs( + public static GetValueAsyncArgs AsGetValueAsyncArgs( this IVariousTypesTools functions, string json) { + #if NET6_0_OR_GREATER + if(global::System.Text.Json.JsonSerializer.IsReflectionEnabledByDefault) + { + return + global::System.Text.Json.JsonSerializer.Deserialize(json, new global::System.Text.Json.JsonSerializerOptions + { + PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, + Converters = {{ new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }} + }) ?? + throw new global::System.InvalidOperationException("Could not deserialize JSON."); + + } + else + { + return global::System.Text.Json.JsonSerializer.Deserialize(json, global::CSharpToJsonSchema.IntegrationTests.VariousTypesToolsExtensionsJsonSerializerContext.Default.GetValueAsyncArgs) ?? + throw new global::System.InvalidOperationException("Could not deserialize JSON."); + + } + #else return - global::System.Text.Json.JsonSerializer.Deserialize(json, new global::System.Text.Json.JsonSerializerOptions + global::System.Text.Json.JsonSerializer.Deserialize(json, new global::System.Text.Json.JsonSerializerOptions { PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, Converters = {{ new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }} }) ?? throw new global::System.InvalidOperationException("Could not deserialize JSON."); + #endif } - public static string CallGetCurrentWeather(this IVariousTypesTools functions, string json) + public static string CallGetCurrentWeather3(this IVariousTypesTools functions, string json) { - var args = functions.AsGetCurrentWeatherArgs(json); - var jsonResult = functions.GetCurrentWeather(args.Parameter1, args.Parameter2, args.Parameter3, args.Parameter4, args.Parameter5, args.DateTime, args.Date); + var args = functions.AsGetCurrentWeather3Args(json); + var jsonResult = functions.GetCurrentWeather3(args.Parameter1, args.Parameter2, args.Parameter3, args.Parameter4, args.Parameter5, args.DateTime, args.Date); - return global::System.Text.Json.JsonSerializer.Serialize(jsonResult, new global::System.Text.Json.JsonSerializerOptions + #if NET6_0_OR_GREATER + if(global::System.Text.Json.JsonSerializer.IsReflectionEnabledByDefault) { - PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, - Converters = { new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }, - }); + return global::System.Text.Json.JsonSerializer.Serialize(jsonResult, new global::System.Text.Json.JsonSerializerOptions + { + PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, + Converters = { new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }, + }); + } + else + { + return global::System.Text.Json.JsonSerializer.Serialize(jsonResult, global::CSharpToJsonSchema.IntegrationTests.VariousTypesToolsExtensionsJsonSerializerContext.Default.GetTypeInfo(jsonResult.GetType())); + } + #else + return global::System.Text.Json.JsonSerializer.Serialize(jsonResult, new global::System.Text.Json.JsonSerializerOptions + { + PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, + Converters = { new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }, + }); + #endif + } public static string CallGetValue(this IVariousTypesTools functions, string json) @@ -150,11 +269,27 @@ public static string CallGetValue(this IVariousTypesTools functions, string json var args = functions.AsGetValueArgs(json); var jsonResult = functions.GetValue(); - return global::System.Text.Json.JsonSerializer.Serialize(jsonResult, new global::System.Text.Json.JsonSerializerOptions + #if NET6_0_OR_GREATER + if(global::System.Text.Json.JsonSerializer.IsReflectionEnabledByDefault) { - PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, - Converters = { new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }, - }); + return global::System.Text.Json.JsonSerializer.Serialize(jsonResult, new global::System.Text.Json.JsonSerializerOptions + { + PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, + Converters = { new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }, + }); + } + else + { + return global::System.Text.Json.JsonSerializer.Serialize(jsonResult, global::CSharpToJsonSchema.IntegrationTests.VariousTypesToolsExtensionsJsonSerializerContext.Default.GetTypeInfo(jsonResult.GetType())); + } + #else + return global::System.Text.Json.JsonSerializer.Serialize(jsonResult, new global::System.Text.Json.JsonSerializerOptions + { + PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, + Converters = { new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }, + }); + #endif + } public static void CallSetValue(this IVariousTypesTools functions, string json) @@ -171,12 +306,29 @@ public static void CallSetValue(this IVariousTypesTools functions, string json) var args = functions.AsGetValueAsyncArgs(json); var jsonResult = await functions.GetValueAsync(cancellationToken); + #if NET6_0_OR_GREATER + if(global::System.Text.Json.JsonSerializer.IsReflectionEnabledByDefault) + { + return global::System.Text.Json.JsonSerializer.Serialize(jsonResult, new global::System.Text.Json.JsonSerializerOptions + { + PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, + Converters = { new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }, + }); + } + else + { + return global::System.Text.Json.JsonSerializer.Serialize(jsonResult, global::CSharpToJsonSchema.IntegrationTests.VariousTypesToolsExtensionsJsonSerializerContext.Default.GetTypeInfo(jsonResult.GetType())); + } + #else return global::System.Text.Json.JsonSerializer.Serialize(jsonResult, new global::System.Text.Json.JsonSerializerOptions { PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, Converters = { new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }, }); + #endif + } + public static async global::System.Threading.Tasks.Task CallSetValueAsync( this IVariousTypesTools functions, @@ -201,4 +353,9 @@ public static void CallSetValue(this IVariousTypesTools functions, string json) return await func(argumentsAsJson, cancellationToken); } } + + public partial class VariousTypesToolsExtensionsJsonSerializerContext: global::System.Text.Json.Serialization.JsonSerializerContext + { + + } } \ No newline at end of file diff --git a/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.DateOnly.g.verified.cs b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.DateOnly.g.verified.cs new file mode 100644 index 0000000..b10c67b --- /dev/null +++ b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.DateOnly.g.verified.cs @@ -0,0 +1,37 @@ +//HintName: IVariousTypesTools.DateOnly.g.cs +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +namespace CSharpToJsonSchema.IntegrationTests +{ + public partial class VariousTypesToolsExtensionsJsonSerializerContext + { + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo? _DateOnly; + + /// + /// Defines the source generated JSON serialization contract metadata for a given type. + /// + #nullable disable annotations // Marking the property type as nullable-oblivious. + public global::System.Text.Json.Serialization.Metadata.JsonTypeInfo DateOnly + #nullable enable annotations + { + get => _DateOnly ??= (global::System.Text.Json.Serialization.Metadata.JsonTypeInfo)Options.GetTypeInfo(typeof(global::System.DateOnly)); + } + + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo Create_DateOnly(global::System.Text.Json.JsonSerializerOptions options) + { + if (!TryGetTypeInfoForRuntimeCustomConverter(options, out global::System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo)) + { + jsonTypeInfo = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreateValueInfo(options, global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.DateOnlyConverter); + } + + jsonTypeInfo.OriginatingResolver = this; + return jsonTypeInfo; + } + } +} diff --git a/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.DateTime.g.verified.cs b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.DateTime.g.verified.cs new file mode 100644 index 0000000..23a8cf3 --- /dev/null +++ b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.DateTime.g.verified.cs @@ -0,0 +1,37 @@ +//HintName: IVariousTypesTools.DateTime.g.cs +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +namespace CSharpToJsonSchema.IntegrationTests +{ + public partial class VariousTypesToolsExtensionsJsonSerializerContext + { + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo? _DateTime; + + /// + /// Defines the source generated JSON serialization contract metadata for a given type. + /// + #nullable disable annotations // Marking the property type as nullable-oblivious. + public global::System.Text.Json.Serialization.Metadata.JsonTypeInfo DateTime + #nullable enable annotations + { + get => _DateTime ??= (global::System.Text.Json.Serialization.Metadata.JsonTypeInfo)Options.GetTypeInfo(typeof(global::System.DateTime)); + } + + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo Create_DateTime(global::System.Text.Json.JsonSerializerOptions options) + { + if (!TryGetTypeInfoForRuntimeCustomConverter(options, out global::System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo)) + { + jsonTypeInfo = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreateValueInfo(options, global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.DateTimeConverter); + } + + jsonTypeInfo.OriginatingResolver = this; + return jsonTypeInfo; + } + } +} diff --git a/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.Double.g.verified.cs b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.Double.g.verified.cs new file mode 100644 index 0000000..0e044a3 --- /dev/null +++ b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.Double.g.verified.cs @@ -0,0 +1,37 @@ +//HintName: IVariousTypesTools.Double.g.cs +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +namespace CSharpToJsonSchema.IntegrationTests +{ + public partial class VariousTypesToolsExtensionsJsonSerializerContext + { + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo? _Double; + + /// + /// Defines the source generated JSON serialization contract metadata for a given type. + /// + #nullable disable annotations // Marking the property type as nullable-oblivious. + public global::System.Text.Json.Serialization.Metadata.JsonTypeInfo Double + #nullable enable annotations + { + get => _Double ??= (global::System.Text.Json.Serialization.Metadata.JsonTypeInfo)Options.GetTypeInfo(typeof(double)); + } + + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo Create_Double(global::System.Text.Json.JsonSerializerOptions options) + { + if (!TryGetTypeInfoForRuntimeCustomConverter(options, out global::System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo)) + { + jsonTypeInfo = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreateValueInfo(options, global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.DoubleConverter); + } + + jsonTypeInfo.OriginatingResolver = this; + return jsonTypeInfo; + } + } +} diff --git a/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.GetCurrentWeather3Args.g.verified.cs b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.GetCurrentWeather3Args.g.verified.cs new file mode 100644 index 0000000..0bf5111 --- /dev/null +++ b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.GetCurrentWeather3Args.g.verified.cs @@ -0,0 +1,219 @@ +//HintName: IVariousTypesTools.GetCurrentWeather3Args.g.cs +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +namespace CSharpToJsonSchema.IntegrationTests +{ + public partial class VariousTypesToolsExtensionsJsonSerializerContext + { + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo? _GetCurrentWeather3Args; + + /// + /// Defines the source generated JSON serialization contract metadata for a given type. + /// + #nullable disable annotations // Marking the property type as nullable-oblivious. + public global::System.Text.Json.Serialization.Metadata.JsonTypeInfo GetCurrentWeather3Args + #nullable enable annotations + { + get => _GetCurrentWeather3Args ??= (global::System.Text.Json.Serialization.Metadata.JsonTypeInfo)Options.GetTypeInfo(typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather3Args)); + } + + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo Create_GetCurrentWeather3Args(global::System.Text.Json.JsonSerializerOptions options) + { + if (!TryGetTypeInfoForRuntimeCustomConverter(options, out global::System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo)) + { + var objectInfo = new global::System.Text.Json.Serialization.Metadata.JsonObjectInfoValues + { + ObjectCreator = () => new global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather3Args(), + ObjectWithParameterizedConstructorCreator = null, + PropertyMetadataInitializer = _ => GetCurrentWeather3ArgsPropInit(options), + ConstructorParameterMetadataInitializer = null, + ConstructorAttributeProviderFactory = static () => typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather3Args).GetConstructor(InstanceMemberBindingFlags, binder: null, global::System.Array.Empty(), modifiers: null), + SerializeHandler = GetCurrentWeather3ArgsSerializeHandler, + }; + + jsonTypeInfo = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreateObjectInfo(options, objectInfo); + jsonTypeInfo.NumberHandling = null; + } + + jsonTypeInfo.OriginatingResolver = this; + return jsonTypeInfo; + } + + private static global::System.Text.Json.Serialization.Metadata.JsonPropertyInfo[] GetCurrentWeather3ArgsPropInit(global::System.Text.Json.JsonSerializerOptions options) + { + var properties = new global::System.Text.Json.Serialization.Metadata.JsonPropertyInfo[7]; + + var info0 = new global::System.Text.Json.Serialization.Metadata.JsonPropertyInfoValues + { + IsProperty = true, + IsPublic = true, + IsVirtual = false, + DeclaringType = typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather3Args), + Converter = null, + Getter = static obj => ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather3Args)obj).Parameter1, + Setter = static (obj, value) => ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather3Args)obj).Parameter1 = value!, + IgnoreCondition = null, + HasJsonInclude = false, + IsExtensionData = false, + NumberHandling = null, + PropertyName = "Parameter1", + JsonPropertyName = null, + AttributeProviderFactory = static () => typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather3Args).GetProperty("Parameter1", InstanceMemberBindingFlags, null, typeof(long), global::System.Array.Empty(), null), + }; + + properties[0] = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreatePropertyInfo(options, info0); + + var info1 = new global::System.Text.Json.Serialization.Metadata.JsonPropertyInfoValues + { + IsProperty = true, + IsPublic = true, + IsVirtual = false, + DeclaringType = typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather3Args), + Converter = null, + Getter = static obj => ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather3Args)obj).Parameter2, + Setter = static (obj, value) => ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather3Args)obj).Parameter2 = value!, + IgnoreCondition = null, + HasJsonInclude = false, + IsExtensionData = false, + NumberHandling = null, + PropertyName = "Parameter2", + JsonPropertyName = null, + AttributeProviderFactory = static () => typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather3Args).GetProperty("Parameter2", InstanceMemberBindingFlags, null, typeof(int), global::System.Array.Empty(), null), + }; + + properties[1] = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreatePropertyInfo(options, info1); + + var info2 = new global::System.Text.Json.Serialization.Metadata.JsonPropertyInfoValues + { + IsProperty = true, + IsPublic = true, + IsVirtual = false, + DeclaringType = typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather3Args), + Converter = null, + Getter = static obj => ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather3Args)obj).Parameter3, + Setter = static (obj, value) => ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather3Args)obj).Parameter3 = value!, + IgnoreCondition = null, + HasJsonInclude = false, + IsExtensionData = false, + NumberHandling = null, + PropertyName = "Parameter3", + JsonPropertyName = null, + AttributeProviderFactory = static () => typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather3Args).GetProperty("Parameter3", InstanceMemberBindingFlags, null, typeof(double), global::System.Array.Empty(), null), + }; + + properties[2] = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreatePropertyInfo(options, info2); + + var info3 = new global::System.Text.Json.Serialization.Metadata.JsonPropertyInfoValues + { + IsProperty = true, + IsPublic = true, + IsVirtual = false, + DeclaringType = typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather3Args), + Converter = null, + Getter = static obj => ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather3Args)obj).Parameter4, + Setter = static (obj, value) => ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather3Args)obj).Parameter4 = value!, + IgnoreCondition = null, + HasJsonInclude = false, + IsExtensionData = false, + NumberHandling = null, + PropertyName = "Parameter4", + JsonPropertyName = null, + AttributeProviderFactory = static () => typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather3Args).GetProperty("Parameter4", InstanceMemberBindingFlags, null, typeof(float), global::System.Array.Empty(), null), + }; + + properties[3] = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreatePropertyInfo(options, info3); + + var info4 = new global::System.Text.Json.Serialization.Metadata.JsonPropertyInfoValues + { + IsProperty = true, + IsPublic = true, + IsVirtual = false, + DeclaringType = typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather3Args), + Converter = null, + Getter = static obj => ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather3Args)obj).Parameter5, + Setter = static (obj, value) => ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather3Args)obj).Parameter5 = value!, + IgnoreCondition = null, + HasJsonInclude = false, + IsExtensionData = false, + NumberHandling = null, + PropertyName = "Parameter5", + JsonPropertyName = null, + AttributeProviderFactory = static () => typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather3Args).GetProperty("Parameter5", InstanceMemberBindingFlags, null, typeof(bool), global::System.Array.Empty(), null), + }; + + properties[4] = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreatePropertyInfo(options, info4); + + var info5 = new global::System.Text.Json.Serialization.Metadata.JsonPropertyInfoValues + { + IsProperty = true, + IsPublic = true, + IsVirtual = false, + DeclaringType = typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather3Args), + Converter = null, + Getter = static obj => ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather3Args)obj).DateTime, + Setter = static (obj, value) => ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather3Args)obj).DateTime = value!, + IgnoreCondition = null, + HasJsonInclude = false, + IsExtensionData = false, + NumberHandling = null, + PropertyName = "DateTime", + JsonPropertyName = null, + AttributeProviderFactory = static () => typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather3Args).GetProperty("DateTime", InstanceMemberBindingFlags, null, typeof(global::System.DateTime), global::System.Array.Empty(), null), + }; + + properties[5] = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreatePropertyInfo(options, info5); + + var info6 = new global::System.Text.Json.Serialization.Metadata.JsonPropertyInfoValues + { + IsProperty = true, + IsPublic = true, + IsVirtual = false, + DeclaringType = typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather3Args), + Converter = null, + Getter = static obj => ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather3Args)obj).Date, + Setter = static (obj, value) => ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather3Args)obj).Date = value!, + IgnoreCondition = null, + HasJsonInclude = false, + IsExtensionData = false, + NumberHandling = null, + PropertyName = "Date", + JsonPropertyName = null, + AttributeProviderFactory = static () => typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather3Args).GetProperty("Date", InstanceMemberBindingFlags, null, typeof(global::System.DateOnly), global::System.Array.Empty(), null), + }; + + properties[6] = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreatePropertyInfo(options, info6); + + return properties; + } + + // Intentionally not a static method because we create a delegate to it. Invoking delegates to instance + // methods is almost as fast as virtual calls. Static methods need to go through a shuffle thunk. + private void GetCurrentWeather3ArgsSerializeHandler(global::System.Text.Json.Utf8JsonWriter writer, global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather3Args? value) + { + if (value is null) + { + writer.WriteNullValue(); + return; + } + + writer.WriteStartObject(); + + writer.WriteNumber(PropName_parameter1, ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather3Args)value).Parameter1); + writer.WriteNumber(PropName_parameter2, ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather3Args)value).Parameter2); + writer.WriteNumber(PropName_parameter3, ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather3Args)value).Parameter3); + writer.WriteNumber(PropName_parameter4, ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather3Args)value).Parameter4); + writer.WriteBoolean(PropName_parameter5, ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather3Args)value).Parameter5); + writer.WriteString(PropName_dateTime, ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather3Args)value).DateTime); + writer.WritePropertyName(PropName_date); + global::System.Text.Json.JsonSerializer.Serialize(writer, ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather3Args)value).Date, DateOnly); + + writer.WriteEndObject(); + } + } +} diff --git a/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.GetJsonTypeInfo.g.verified.cs b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.GetJsonTypeInfo.g.verified.cs new file mode 100644 index 0000000..1edd614 --- /dev/null +++ b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.GetJsonTypeInfo.g.verified.cs @@ -0,0 +1,74 @@ +//HintName: IVariousTypesTools.GetJsonTypeInfo.g.cs +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +namespace CSharpToJsonSchema.IntegrationTests +{ + public partial class VariousTypesToolsExtensionsJsonSerializerContext : global::System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver + { + /// + public override global::System.Text.Json.Serialization.Metadata.JsonTypeInfo? GetTypeInfo(global::System.Type type) + { + Options.TryGetTypeInfo(type, out global::System.Text.Json.Serialization.Metadata.JsonTypeInfo? typeInfo); + return typeInfo; + } + + global::System.Text.Json.Serialization.Metadata.JsonTypeInfo? global::System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver.GetTypeInfo(global::System.Type type, global::System.Text.Json.JsonSerializerOptions options) + { + if (type == typeof(bool)) + { + return Create_Boolean(options); + } + if (type == typeof(double)) + { + return Create_Double(options); + } + if (type == typeof(float)) + { + return Create_Single(options); + } + if (type == typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather3Args)) + { + return Create_GetCurrentWeather3Args(options); + } + if (type == typeof(global::CSharpToJsonSchema.IntegrationTests.GetValueArgs)) + { + return Create_GetValueArgs(options); + } + if (type == typeof(global::CSharpToJsonSchema.IntegrationTests.GetValueAsyncArgs)) + { + return Create_GetValueAsyncArgs(options); + } + if (type == typeof(global::CSharpToJsonSchema.IntegrationTests.SetValueArgs)) + { + return Create_SetValueArgs(options); + } + if (type == typeof(global::CSharpToJsonSchema.IntegrationTests.SetValueAsyncArgs)) + { + return Create_SetValueAsyncArgs(options); + } + if (type == typeof(global::System.DateOnly)) + { + return Create_DateOnly(options); + } + if (type == typeof(global::System.DateTime)) + { + return Create_DateTime(options); + } + if (type == typeof(int)) + { + return Create_Int32(options); + } + if (type == typeof(long)) + { + return Create_Int64(options); + } + return null; + } + } +} diff --git a/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.GetValueArgs.g.verified.cs b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.GetValueArgs.g.verified.cs new file mode 100644 index 0000000..32bb28a --- /dev/null +++ b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.GetValueArgs.g.verified.cs @@ -0,0 +1,71 @@ +//HintName: IVariousTypesTools.GetValueArgs.g.cs +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +namespace CSharpToJsonSchema.IntegrationTests +{ + public partial class VariousTypesToolsExtensionsJsonSerializerContext + { + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo? _GetValueArgs; + + /// + /// Defines the source generated JSON serialization contract metadata for a given type. + /// + #nullable disable annotations // Marking the property type as nullable-oblivious. + public global::System.Text.Json.Serialization.Metadata.JsonTypeInfo GetValueArgs + #nullable enable annotations + { + get => _GetValueArgs ??= (global::System.Text.Json.Serialization.Metadata.JsonTypeInfo)Options.GetTypeInfo(typeof(global::CSharpToJsonSchema.IntegrationTests.GetValueArgs)); + } + + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo Create_GetValueArgs(global::System.Text.Json.JsonSerializerOptions options) + { + if (!TryGetTypeInfoForRuntimeCustomConverter(options, out global::System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo)) + { + var objectInfo = new global::System.Text.Json.Serialization.Metadata.JsonObjectInfoValues + { + ObjectCreator = () => new global::CSharpToJsonSchema.IntegrationTests.GetValueArgs(), + ObjectWithParameterizedConstructorCreator = null, + PropertyMetadataInitializer = _ => GetValueArgsPropInit(options), + ConstructorParameterMetadataInitializer = null, + ConstructorAttributeProviderFactory = static () => typeof(global::CSharpToJsonSchema.IntegrationTests.GetValueArgs).GetConstructor(InstanceMemberBindingFlags, binder: null, global::System.Array.Empty(), modifiers: null), + SerializeHandler = GetValueArgsSerializeHandler, + }; + + jsonTypeInfo = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreateObjectInfo(options, objectInfo); + jsonTypeInfo.NumberHandling = null; + } + + jsonTypeInfo.OriginatingResolver = this; + return jsonTypeInfo; + } + + private static global::System.Text.Json.Serialization.Metadata.JsonPropertyInfo[] GetValueArgsPropInit(global::System.Text.Json.JsonSerializerOptions options) + { + var properties = new global::System.Text.Json.Serialization.Metadata.JsonPropertyInfo[0]; + + return properties; + } + + // Intentionally not a static method because we create a delegate to it. Invoking delegates to instance + // methods is almost as fast as virtual calls. Static methods need to go through a shuffle thunk. + private void GetValueArgsSerializeHandler(global::System.Text.Json.Utf8JsonWriter writer, global::CSharpToJsonSchema.IntegrationTests.GetValueArgs? value) + { + if (value is null) + { + writer.WriteNullValue(); + return; + } + + writer.WriteStartObject(); + + + writer.WriteEndObject(); + } + } +} diff --git a/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.GetValueAsyncArgs.g.verified.cs b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.GetValueAsyncArgs.g.verified.cs new file mode 100644 index 0000000..54c8114 --- /dev/null +++ b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.GetValueAsyncArgs.g.verified.cs @@ -0,0 +1,71 @@ +//HintName: IVariousTypesTools.GetValueAsyncArgs.g.cs +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +namespace CSharpToJsonSchema.IntegrationTests +{ + public partial class VariousTypesToolsExtensionsJsonSerializerContext + { + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo? _GetValueAsyncArgs; + + /// + /// Defines the source generated JSON serialization contract metadata for a given type. + /// + #nullable disable annotations // Marking the property type as nullable-oblivious. + public global::System.Text.Json.Serialization.Metadata.JsonTypeInfo GetValueAsyncArgs + #nullable enable annotations + { + get => _GetValueAsyncArgs ??= (global::System.Text.Json.Serialization.Metadata.JsonTypeInfo)Options.GetTypeInfo(typeof(global::CSharpToJsonSchema.IntegrationTests.GetValueAsyncArgs)); + } + + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo Create_GetValueAsyncArgs(global::System.Text.Json.JsonSerializerOptions options) + { + if (!TryGetTypeInfoForRuntimeCustomConverter(options, out global::System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo)) + { + var objectInfo = new global::System.Text.Json.Serialization.Metadata.JsonObjectInfoValues + { + ObjectCreator = () => new global::CSharpToJsonSchema.IntegrationTests.GetValueAsyncArgs(), + ObjectWithParameterizedConstructorCreator = null, + PropertyMetadataInitializer = _ => GetValueAsyncArgsPropInit(options), + ConstructorParameterMetadataInitializer = null, + ConstructorAttributeProviderFactory = static () => typeof(global::CSharpToJsonSchema.IntegrationTests.GetValueAsyncArgs).GetConstructor(InstanceMemberBindingFlags, binder: null, global::System.Array.Empty(), modifiers: null), + SerializeHandler = GetValueAsyncArgsSerializeHandler, + }; + + jsonTypeInfo = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreateObjectInfo(options, objectInfo); + jsonTypeInfo.NumberHandling = null; + } + + jsonTypeInfo.OriginatingResolver = this; + return jsonTypeInfo; + } + + private static global::System.Text.Json.Serialization.Metadata.JsonPropertyInfo[] GetValueAsyncArgsPropInit(global::System.Text.Json.JsonSerializerOptions options) + { + var properties = new global::System.Text.Json.Serialization.Metadata.JsonPropertyInfo[0]; + + return properties; + } + + // Intentionally not a static method because we create a delegate to it. Invoking delegates to instance + // methods is almost as fast as virtual calls. Static methods need to go through a shuffle thunk. + private void GetValueAsyncArgsSerializeHandler(global::System.Text.Json.Utf8JsonWriter writer, global::CSharpToJsonSchema.IntegrationTests.GetValueAsyncArgs? value) + { + if (value is null) + { + writer.WriteNullValue(); + return; + } + + writer.WriteStartObject(); + + + writer.WriteEndObject(); + } + } +} diff --git a/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.Int32.g.verified.cs b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.Int32.g.verified.cs new file mode 100644 index 0000000..91818c8 --- /dev/null +++ b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.Int32.g.verified.cs @@ -0,0 +1,37 @@ +//HintName: IVariousTypesTools.Int32.g.cs +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +namespace CSharpToJsonSchema.IntegrationTests +{ + public partial class VariousTypesToolsExtensionsJsonSerializerContext + { + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo? _Int32; + + /// + /// Defines the source generated JSON serialization contract metadata for a given type. + /// + #nullable disable annotations // Marking the property type as nullable-oblivious. + public global::System.Text.Json.Serialization.Metadata.JsonTypeInfo Int32 + #nullable enable annotations + { + get => _Int32 ??= (global::System.Text.Json.Serialization.Metadata.JsonTypeInfo)Options.GetTypeInfo(typeof(int)); + } + + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo Create_Int32(global::System.Text.Json.JsonSerializerOptions options) + { + if (!TryGetTypeInfoForRuntimeCustomConverter(options, out global::System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo)) + { + jsonTypeInfo = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreateValueInfo(options, global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.Int32Converter); + } + + jsonTypeInfo.OriginatingResolver = this; + return jsonTypeInfo; + } + } +} diff --git a/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.Int64.g.verified.cs b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.Int64.g.verified.cs new file mode 100644 index 0000000..2263c42 --- /dev/null +++ b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.Int64.g.verified.cs @@ -0,0 +1,37 @@ +//HintName: IVariousTypesTools.Int64.g.cs +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +namespace CSharpToJsonSchema.IntegrationTests +{ + public partial class VariousTypesToolsExtensionsJsonSerializerContext + { + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo? _Int64; + + /// + /// Defines the source generated JSON serialization contract metadata for a given type. + /// + #nullable disable annotations // Marking the property type as nullable-oblivious. + public global::System.Text.Json.Serialization.Metadata.JsonTypeInfo Int64 + #nullable enable annotations + { + get => _Int64 ??= (global::System.Text.Json.Serialization.Metadata.JsonTypeInfo)Options.GetTypeInfo(typeof(long)); + } + + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo Create_Int64(global::System.Text.Json.JsonSerializerOptions options) + { + if (!TryGetTypeInfoForRuntimeCustomConverter(options, out global::System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo)) + { + jsonTypeInfo = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreateValueInfo(options, global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.Int64Converter); + } + + jsonTypeInfo.OriginatingResolver = this; + return jsonTypeInfo; + } + } +} diff --git a/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.PropertyNames.g.verified.cs b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.PropertyNames.g.verified.cs new file mode 100644 index 0000000..d34ba5a --- /dev/null +++ b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.PropertyNames.g.verified.cs @@ -0,0 +1,23 @@ +//HintName: IVariousTypesTools.PropertyNames.g.cs +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +namespace CSharpToJsonSchema.IntegrationTests +{ + public partial class VariousTypesToolsExtensionsJsonSerializerContext + { + private static readonly global::System.Text.Json.JsonEncodedText PropName_parameter1 = global::System.Text.Json.JsonEncodedText.Encode("parameter1"); + private static readonly global::System.Text.Json.JsonEncodedText PropName_parameter2 = global::System.Text.Json.JsonEncodedText.Encode("parameter2"); + private static readonly global::System.Text.Json.JsonEncodedText PropName_parameter3 = global::System.Text.Json.JsonEncodedText.Encode("parameter3"); + private static readonly global::System.Text.Json.JsonEncodedText PropName_parameter4 = global::System.Text.Json.JsonEncodedText.Encode("parameter4"); + private static readonly global::System.Text.Json.JsonEncodedText PropName_parameter5 = global::System.Text.Json.JsonEncodedText.Encode("parameter5"); + private static readonly global::System.Text.Json.JsonEncodedText PropName_dateTime = global::System.Text.Json.JsonEncodedText.Encode("dateTime"); + private static readonly global::System.Text.Json.JsonEncodedText PropName_date = global::System.Text.Json.JsonEncodedText.Encode("date"); + private static readonly global::System.Text.Json.JsonEncodedText PropName_value = global::System.Text.Json.JsonEncodedText.Encode("value"); + } +} diff --git a/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.SetValueArgs.g.verified.cs b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.SetValueArgs.g.verified.cs new file mode 100644 index 0000000..d83f8a2 --- /dev/null +++ b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.SetValueArgs.g.verified.cs @@ -0,0 +1,92 @@ +//HintName: IVariousTypesTools.SetValueArgs.g.cs +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +namespace CSharpToJsonSchema.IntegrationTests +{ + public partial class VariousTypesToolsExtensionsJsonSerializerContext + { + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo? _SetValueArgs; + + /// + /// Defines the source generated JSON serialization contract metadata for a given type. + /// + #nullable disable annotations // Marking the property type as nullable-oblivious. + public global::System.Text.Json.Serialization.Metadata.JsonTypeInfo SetValueArgs + #nullable enable annotations + { + get => _SetValueArgs ??= (global::System.Text.Json.Serialization.Metadata.JsonTypeInfo)Options.GetTypeInfo(typeof(global::CSharpToJsonSchema.IntegrationTests.SetValueArgs)); + } + + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo Create_SetValueArgs(global::System.Text.Json.JsonSerializerOptions options) + { + if (!TryGetTypeInfoForRuntimeCustomConverter(options, out global::System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo)) + { + var objectInfo = new global::System.Text.Json.Serialization.Metadata.JsonObjectInfoValues + { + ObjectCreator = () => new global::CSharpToJsonSchema.IntegrationTests.SetValueArgs(), + ObjectWithParameterizedConstructorCreator = null, + PropertyMetadataInitializer = _ => SetValueArgsPropInit(options), + ConstructorParameterMetadataInitializer = null, + ConstructorAttributeProviderFactory = static () => typeof(global::CSharpToJsonSchema.IntegrationTests.SetValueArgs).GetConstructor(InstanceMemberBindingFlags, binder: null, global::System.Array.Empty(), modifiers: null), + SerializeHandler = SetValueArgsSerializeHandler, + }; + + jsonTypeInfo = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreateObjectInfo(options, objectInfo); + jsonTypeInfo.NumberHandling = null; + } + + jsonTypeInfo.OriginatingResolver = this; + return jsonTypeInfo; + } + + private static global::System.Text.Json.Serialization.Metadata.JsonPropertyInfo[] SetValueArgsPropInit(global::System.Text.Json.JsonSerializerOptions options) + { + var properties = new global::System.Text.Json.Serialization.Metadata.JsonPropertyInfo[1]; + + var info0 = new global::System.Text.Json.Serialization.Metadata.JsonPropertyInfoValues + { + IsProperty = true, + IsPublic = true, + IsVirtual = false, + DeclaringType = typeof(global::CSharpToJsonSchema.IntegrationTests.SetValueArgs), + Converter = null, + Getter = static obj => ((global::CSharpToJsonSchema.IntegrationTests.SetValueArgs)obj).Value, + Setter = static (obj, value) => ((global::CSharpToJsonSchema.IntegrationTests.SetValueArgs)obj).Value = value!, + IgnoreCondition = null, + HasJsonInclude = false, + IsExtensionData = false, + NumberHandling = null, + PropertyName = "Value", + JsonPropertyName = null, + AttributeProviderFactory = static () => typeof(global::CSharpToJsonSchema.IntegrationTests.SetValueArgs).GetProperty("Value", InstanceMemberBindingFlags, null, typeof(int), global::System.Array.Empty(), null), + }; + + properties[0] = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreatePropertyInfo(options, info0); + + return properties; + } + + // Intentionally not a static method because we create a delegate to it. Invoking delegates to instance + // methods is almost as fast as virtual calls. Static methods need to go through a shuffle thunk. + private void SetValueArgsSerializeHandler(global::System.Text.Json.Utf8JsonWriter writer, global::CSharpToJsonSchema.IntegrationTests.SetValueArgs? value) + { + if (value is null) + { + writer.WriteNullValue(); + return; + } + + writer.WriteStartObject(); + + writer.WriteNumber(PropName_value, ((global::CSharpToJsonSchema.IntegrationTests.SetValueArgs)value).Value); + + writer.WriteEndObject(); + } + } +} diff --git a/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.SetValueAsyncArgs.g.verified.cs b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.SetValueAsyncArgs.g.verified.cs new file mode 100644 index 0000000..26a1458 --- /dev/null +++ b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.SetValueAsyncArgs.g.verified.cs @@ -0,0 +1,92 @@ +//HintName: IVariousTypesTools.SetValueAsyncArgs.g.cs +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +namespace CSharpToJsonSchema.IntegrationTests +{ + public partial class VariousTypesToolsExtensionsJsonSerializerContext + { + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo? _SetValueAsyncArgs; + + /// + /// Defines the source generated JSON serialization contract metadata for a given type. + /// + #nullable disable annotations // Marking the property type as nullable-oblivious. + public global::System.Text.Json.Serialization.Metadata.JsonTypeInfo SetValueAsyncArgs + #nullable enable annotations + { + get => _SetValueAsyncArgs ??= (global::System.Text.Json.Serialization.Metadata.JsonTypeInfo)Options.GetTypeInfo(typeof(global::CSharpToJsonSchema.IntegrationTests.SetValueAsyncArgs)); + } + + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo Create_SetValueAsyncArgs(global::System.Text.Json.JsonSerializerOptions options) + { + if (!TryGetTypeInfoForRuntimeCustomConverter(options, out global::System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo)) + { + var objectInfo = new global::System.Text.Json.Serialization.Metadata.JsonObjectInfoValues + { + ObjectCreator = () => new global::CSharpToJsonSchema.IntegrationTests.SetValueAsyncArgs(), + ObjectWithParameterizedConstructorCreator = null, + PropertyMetadataInitializer = _ => SetValueAsyncArgsPropInit(options), + ConstructorParameterMetadataInitializer = null, + ConstructorAttributeProviderFactory = static () => typeof(global::CSharpToJsonSchema.IntegrationTests.SetValueAsyncArgs).GetConstructor(InstanceMemberBindingFlags, binder: null, global::System.Array.Empty(), modifiers: null), + SerializeHandler = SetValueAsyncArgsSerializeHandler, + }; + + jsonTypeInfo = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreateObjectInfo(options, objectInfo); + jsonTypeInfo.NumberHandling = null; + } + + jsonTypeInfo.OriginatingResolver = this; + return jsonTypeInfo; + } + + private static global::System.Text.Json.Serialization.Metadata.JsonPropertyInfo[] SetValueAsyncArgsPropInit(global::System.Text.Json.JsonSerializerOptions options) + { + var properties = new global::System.Text.Json.Serialization.Metadata.JsonPropertyInfo[1]; + + var info0 = new global::System.Text.Json.Serialization.Metadata.JsonPropertyInfoValues + { + IsProperty = true, + IsPublic = true, + IsVirtual = false, + DeclaringType = typeof(global::CSharpToJsonSchema.IntegrationTests.SetValueAsyncArgs), + Converter = null, + Getter = static obj => ((global::CSharpToJsonSchema.IntegrationTests.SetValueAsyncArgs)obj).Value, + Setter = static (obj, value) => ((global::CSharpToJsonSchema.IntegrationTests.SetValueAsyncArgs)obj).Value = value!, + IgnoreCondition = null, + HasJsonInclude = false, + IsExtensionData = false, + NumberHandling = null, + PropertyName = "Value", + JsonPropertyName = null, + AttributeProviderFactory = static () => typeof(global::CSharpToJsonSchema.IntegrationTests.SetValueAsyncArgs).GetProperty("Value", InstanceMemberBindingFlags, null, typeof(int), global::System.Array.Empty(), null), + }; + + properties[0] = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreatePropertyInfo(options, info0); + + return properties; + } + + // Intentionally not a static method because we create a delegate to it. Invoking delegates to instance + // methods is almost as fast as virtual calls. Static methods need to go through a shuffle thunk. + private void SetValueAsyncArgsSerializeHandler(global::System.Text.Json.Utf8JsonWriter writer, global::CSharpToJsonSchema.IntegrationTests.SetValueAsyncArgs? value) + { + if (value is null) + { + writer.WriteNullValue(); + return; + } + + writer.WriteStartObject(); + + writer.WriteNumber(PropName_value, ((global::CSharpToJsonSchema.IntegrationTests.SetValueAsyncArgs)value).Value); + + writer.WriteEndObject(); + } + } +} diff --git a/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.Single.g.verified.cs b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.Single.g.verified.cs new file mode 100644 index 0000000..60b90d8 --- /dev/null +++ b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.Single.g.verified.cs @@ -0,0 +1,37 @@ +//HintName: IVariousTypesTools.Single.g.cs +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +namespace CSharpToJsonSchema.IntegrationTests +{ + public partial class VariousTypesToolsExtensionsJsonSerializerContext + { + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo? _Single; + + /// + /// Defines the source generated JSON serialization contract metadata for a given type. + /// + #nullable disable annotations // Marking the property type as nullable-oblivious. + public global::System.Text.Json.Serialization.Metadata.JsonTypeInfo Single + #nullable enable annotations + { + get => _Single ??= (global::System.Text.Json.Serialization.Metadata.JsonTypeInfo)Options.GetTypeInfo(typeof(float)); + } + + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo Create_Single(global::System.Text.Json.JsonSerializerOptions options) + { + if (!TryGetTypeInfoForRuntimeCustomConverter(options, out global::System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo)) + { + jsonTypeInfo = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreateValueInfo(options, global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.SingleConverter); + } + + jsonTypeInfo.OriginatingResolver = this; + return jsonTypeInfo; + } + } +} diff --git a/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.Tools.generated.verified.cs b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.Tools.generated.verified.cs index 961ccc1..875f719 100644 --- a/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.Tools.generated.verified.cs +++ b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.Tools.generated.verified.cs @@ -12,7 +12,7 @@ public static partial class VariousTypesToolsExtensions { new global::CSharpToJsonSchema.Tool { - Name = "GetCurrentWeather", + Name = "GetCurrentWeather3", Description = "Get the current weather in a given location", Strict = false, Parameters = new global::CSharpToJsonSchema.OpenApiSchema diff --git a/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.g.verified.cs b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.g.verified.cs new file mode 100644 index 0000000..5032b6d --- /dev/null +++ b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.VariousTypes#IVariousTypesTools.g.verified.cs @@ -0,0 +1,111 @@ +//HintName: IVariousTypesTools.g.cs +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +namespace CSharpToJsonSchema.IntegrationTests +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("CSharpToJsonSchema.Generators", "3.0.0.0")] + public partial class VariousTypesToolsExtensionsJsonSerializerContext + { + private readonly static global::System.Text.Json.JsonSerializerOptions s_defaultOptions = new(global::System.Text.Json.JsonSerializerDefaults.Web) + { + AllowOutOfOrderMetadataProperties = true, + AllowTrailingCommas = false, + DefaultBufferSize = 1024, + DefaultIgnoreCondition = global::System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull, + RespectNullableAnnotations = true, + RespectRequiredConstructorParameters = false, + IgnoreReadOnlyFields = false, + IgnoreReadOnlyProperties = false, + IncludeFields = false, + MaxDepth = 64, + NewLine = "\n", + NumberHandling = global::System.Text.Json.Serialization.JsonNumberHandling.Strict, + PreferredObjectCreationHandling = global::System.Text.Json.Serialization.JsonObjectCreationHandling.Replace, + PropertyNameCaseInsensitive = true, + PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, + ReadCommentHandling = global::System.Text.Json.JsonCommentHandling.Disallow, + UnknownTypeHandling = global::System.Text.Json.Serialization.JsonUnknownTypeHandling.JsonElement, + UnmappedMemberHandling = global::System.Text.Json.Serialization.JsonUnmappedMemberHandling.Skip, + WriteIndented = false, + IndentCharacter = ' ', + IndentSize = 2, + }; + + private const global::System.Reflection.BindingFlags InstanceMemberBindingFlags = + global::System.Reflection.BindingFlags.Instance | + global::System.Reflection.BindingFlags.Public | + global::System.Reflection.BindingFlags.NonPublic; + + /// + /// The default associated with a default instance. + /// + public static global::CSharpToJsonSchema.IntegrationTests.VariousTypesToolsExtensionsJsonSerializerContext Default { get; } = new global::CSharpToJsonSchema.IntegrationTests.VariousTypesToolsExtensionsJsonSerializerContext(new global::System.Text.Json.JsonSerializerOptions(s_defaultOptions)); + + /// + /// The source-generated options associated with this context. + /// + protected override global::System.Text.Json.JsonSerializerOptions? GeneratedSerializerOptions { get; } = s_defaultOptions; + + /// + public VariousTypesToolsExtensionsJsonSerializerContext() : base(null) + { + } + + /// + public VariousTypesToolsExtensionsJsonSerializerContext(global::System.Text.Json.JsonSerializerOptions options) : base(options) + { + } + + private static bool TryGetTypeInfoForRuntimeCustomConverter(global::System.Text.Json.JsonSerializerOptions options, out global::System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo) + { + global::System.Text.Json.Serialization.JsonConverter? converter = GetRuntimeConverterForType(typeof(TJsonMetadataType), options); + if (converter != null) + { + jsonTypeInfo = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreateValueInfo(options, converter); + return true; + } + + jsonTypeInfo = null; + return false; + } + + private static global::System.Text.Json.Serialization.JsonConverter? GetRuntimeConverterForType(global::System.Type type, global::System.Text.Json.JsonSerializerOptions options) + { + for (int i = 0; i < options.Converters.Count; i++) + { + global::System.Text.Json.Serialization.JsonConverter? converter = options.Converters[i]; + if (converter?.CanConvert(type) == true) + { + return ExpandConverter(type, converter, options, validateCanConvert: false); + } + } + + return null; + } + + private static global::System.Text.Json.Serialization.JsonConverter ExpandConverter(global::System.Type type, global::System.Text.Json.Serialization.JsonConverter converter, global::System.Text.Json.JsonSerializerOptions options, bool validateCanConvert = true) + { + if (validateCanConvert && !converter.CanConvert(type)) + { + throw new global::System.InvalidOperationException(string.Format("The converter '{0}' is not compatible with the type '{1}'.", converter.GetType(), type)); + } + + if (converter is global::System.Text.Json.Serialization.JsonConverterFactory factory) + { + converter = factory.CreateConverter(type, options); + if (converter is null || converter is global::System.Text.Json.Serialization.JsonConverterFactory) + { + throw new global::System.InvalidOperationException(string.Format("The converter '{0}' cannot return null or a JsonConverterFactory instance.", factory.GetType())); + } + } + + return converter; + } + } +} diff --git a/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.Weather#IWeatherTools.Calls.generated.verified.cs b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.Weather#IWeatherTools.Calls.generated.verified.cs index bf61772..6eafd44 100644 --- a/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.Weather#IWeatherTools.Calls.generated.verified.cs +++ b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.Weather#IWeatherTools.Calls.generated.verified.cs @@ -3,8 +3,6 @@ namespace CSharpToJsonSchema.IntegrationTests { - public static partial class WeatherToolsExtensions - { public class GetCurrentWeatherArgs { public string Location { get; set; } = string.Empty; @@ -17,6 +15,11 @@ public class GetCurrentWeatherAsyncArgs public global::CSharpToJsonSchema.IntegrationTests.Unit Unit { get; set; } } + public static partial class WeatherToolsExtensions + { + + + public static global::System.Collections.Generic.IReadOnlyDictionary>> AsCalls(this IWeatherTools service) { return new global::System.Collections.Generic.Dictionary>> @@ -34,30 +37,70 @@ public class GetCurrentWeatherAsyncArgs }; } - public static WeatherToolsExtensions.GetCurrentWeatherArgs AsGetCurrentWeatherArgs( + public static GetCurrentWeatherArgs AsGetCurrentWeatherArgs( this IWeatherTools functions, string json) { + #if NET6_0_OR_GREATER + if(global::System.Text.Json.JsonSerializer.IsReflectionEnabledByDefault) + { + return + global::System.Text.Json.JsonSerializer.Deserialize(json, new global::System.Text.Json.JsonSerializerOptions + { + PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, + Converters = {{ new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }} + }) ?? + throw new global::System.InvalidOperationException("Could not deserialize JSON."); + + } + else + { + return global::System.Text.Json.JsonSerializer.Deserialize(json, global::CSharpToJsonSchema.IntegrationTests.WeatherToolsExtensionsJsonSerializerContext.Default.GetCurrentWeatherArgs) ?? + throw new global::System.InvalidOperationException("Could not deserialize JSON."); + + } + #else return - global::System.Text.Json.JsonSerializer.Deserialize(json, new global::System.Text.Json.JsonSerializerOptions + global::System.Text.Json.JsonSerializer.Deserialize(json, new global::System.Text.Json.JsonSerializerOptions { PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, Converters = {{ new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }} }) ?? throw new global::System.InvalidOperationException("Could not deserialize JSON."); + #endif } - public static WeatherToolsExtensions.GetCurrentWeatherAsyncArgs AsGetCurrentWeatherAsyncArgs( + public static GetCurrentWeatherAsyncArgs AsGetCurrentWeatherAsyncArgs( this IWeatherTools functions, string json) { + #if NET6_0_OR_GREATER + if(global::System.Text.Json.JsonSerializer.IsReflectionEnabledByDefault) + { + return + global::System.Text.Json.JsonSerializer.Deserialize(json, new global::System.Text.Json.JsonSerializerOptions + { + PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, + Converters = {{ new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }} + }) ?? + throw new global::System.InvalidOperationException("Could not deserialize JSON."); + + } + else + { + return global::System.Text.Json.JsonSerializer.Deserialize(json, global::CSharpToJsonSchema.IntegrationTests.WeatherToolsExtensionsJsonSerializerContext.Default.GetCurrentWeatherAsyncArgs) ?? + throw new global::System.InvalidOperationException("Could not deserialize JSON."); + + } + #else return - global::System.Text.Json.JsonSerializer.Deserialize(json, new global::System.Text.Json.JsonSerializerOptions + global::System.Text.Json.JsonSerializer.Deserialize(json, new global::System.Text.Json.JsonSerializerOptions { PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, Converters = {{ new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }} }) ?? throw new global::System.InvalidOperationException("Could not deserialize JSON."); + #endif } public static string CallGetCurrentWeather(this IWeatherTools functions, string json) @@ -65,11 +108,27 @@ public static string CallGetCurrentWeather(this IWeatherTools functions, string var args = functions.AsGetCurrentWeatherArgs(json); var jsonResult = functions.GetCurrentWeather(args.Location, args.Unit); - return global::System.Text.Json.JsonSerializer.Serialize(jsonResult, new global::System.Text.Json.JsonSerializerOptions + #if NET6_0_OR_GREATER + if(global::System.Text.Json.JsonSerializer.IsReflectionEnabledByDefault) { - PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, - Converters = { new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }, - }); + return global::System.Text.Json.JsonSerializer.Serialize(jsonResult, new global::System.Text.Json.JsonSerializerOptions + { + PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, + Converters = { new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }, + }); + } + else + { + return global::System.Text.Json.JsonSerializer.Serialize(jsonResult, global::CSharpToJsonSchema.IntegrationTests.WeatherToolsExtensionsJsonSerializerContext.Default.GetTypeInfo(jsonResult.GetType())); + } + #else + return global::System.Text.Json.JsonSerializer.Serialize(jsonResult, new global::System.Text.Json.JsonSerializerOptions + { + PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, + Converters = { new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }, + }); + #endif + } @@ -82,12 +141,29 @@ public static string CallGetCurrentWeather(this IWeatherTools functions, string var args = functions.AsGetCurrentWeatherAsyncArgs(json); var jsonResult = await functions.GetCurrentWeatherAsync(args.Location, args.Unit, cancellationToken); + #if NET6_0_OR_GREATER + if(global::System.Text.Json.JsonSerializer.IsReflectionEnabledByDefault) + { + return global::System.Text.Json.JsonSerializer.Serialize(jsonResult, new global::System.Text.Json.JsonSerializerOptions + { + PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, + Converters = { new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }, + }); + } + else + { + return global::System.Text.Json.JsonSerializer.Serialize(jsonResult, global::CSharpToJsonSchema.IntegrationTests.WeatherToolsExtensionsJsonSerializerContext.Default.GetTypeInfo(jsonResult.GetType())); + } + #else return global::System.Text.Json.JsonSerializer.Serialize(jsonResult, new global::System.Text.Json.JsonSerializerOptions { PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, Converters = { new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }, }); + #endif + } + @@ -103,4 +179,9 @@ public static string CallGetCurrentWeather(this IWeatherTools functions, string return await func(argumentsAsJson, cancellationToken); } } + + public partial class WeatherToolsExtensionsJsonSerializerContext: global::System.Text.Json.Serialization.JsonSerializerContext + { + + } } \ No newline at end of file diff --git a/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.Weather#IWeatherTools.Double.g.verified.cs b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.Weather#IWeatherTools.Double.g.verified.cs new file mode 100644 index 0000000..2347275 --- /dev/null +++ b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.Weather#IWeatherTools.Double.g.verified.cs @@ -0,0 +1,37 @@ +//HintName: IWeatherTools.Double.g.cs +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +namespace CSharpToJsonSchema.IntegrationTests +{ + public partial class WeatherToolsExtensionsJsonSerializerContext + { + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo? _Double; + + /// + /// Defines the source generated JSON serialization contract metadata for a given type. + /// + #nullable disable annotations // Marking the property type as nullable-oblivious. + public global::System.Text.Json.Serialization.Metadata.JsonTypeInfo Double + #nullable enable annotations + { + get => _Double ??= (global::System.Text.Json.Serialization.Metadata.JsonTypeInfo)Options.GetTypeInfo(typeof(double)); + } + + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo Create_Double(global::System.Text.Json.JsonSerializerOptions options) + { + if (!TryGetTypeInfoForRuntimeCustomConverter(options, out global::System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo)) + { + jsonTypeInfo = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreateValueInfo(options, global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.DoubleConverter); + } + + jsonTypeInfo.OriginatingResolver = this; + return jsonTypeInfo; + } + } +} diff --git a/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.Weather#IWeatherTools.GetCurrentWeatherArgs.g.verified.cs b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.Weather#IWeatherTools.GetCurrentWeatherArgs.g.verified.cs new file mode 100644 index 0000000..ddadc1f --- /dev/null +++ b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.Weather#IWeatherTools.GetCurrentWeatherArgs.g.verified.cs @@ -0,0 +1,118 @@ +//HintName: IWeatherTools.GetCurrentWeatherArgs.g.cs +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +namespace CSharpToJsonSchema.IntegrationTests +{ + public partial class WeatherToolsExtensionsJsonSerializerContext + { + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo? _GetCurrentWeatherArgs; + + /// + /// Defines the source generated JSON serialization contract metadata for a given type. + /// + #nullable disable annotations // Marking the property type as nullable-oblivious. + public global::System.Text.Json.Serialization.Metadata.JsonTypeInfo GetCurrentWeatherArgs + #nullable enable annotations + { + get => _GetCurrentWeatherArgs ??= (global::System.Text.Json.Serialization.Metadata.JsonTypeInfo)Options.GetTypeInfo(typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherArgs)); + } + + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo Create_GetCurrentWeatherArgs(global::System.Text.Json.JsonSerializerOptions options) + { + if (!TryGetTypeInfoForRuntimeCustomConverter(options, out global::System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo)) + { + var objectInfo = new global::System.Text.Json.Serialization.Metadata.JsonObjectInfoValues + { + ObjectCreator = () => new global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherArgs(), + ObjectWithParameterizedConstructorCreator = null, + PropertyMetadataInitializer = _ => GetCurrentWeatherArgsPropInit(options), + ConstructorParameterMetadataInitializer = null, + ConstructorAttributeProviderFactory = static () => typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherArgs).GetConstructor(InstanceMemberBindingFlags, binder: null, global::System.Array.Empty(), modifiers: null), + SerializeHandler = GetCurrentWeatherArgsSerializeHandler, + }; + + jsonTypeInfo = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreateObjectInfo(options, objectInfo); + jsonTypeInfo.NumberHandling = null; + } + + jsonTypeInfo.OriginatingResolver = this; + return jsonTypeInfo; + } + + private static global::System.Text.Json.Serialization.Metadata.JsonPropertyInfo[] GetCurrentWeatherArgsPropInit(global::System.Text.Json.JsonSerializerOptions options) + { + var properties = new global::System.Text.Json.Serialization.Metadata.JsonPropertyInfo[2]; + + var info0 = new global::System.Text.Json.Serialization.Metadata.JsonPropertyInfoValues + { + IsProperty = true, + IsPublic = true, + IsVirtual = false, + DeclaringType = typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherArgs), + Converter = null, + Getter = static obj => ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherArgs)obj).Location, + Setter = static (obj, value) => ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherArgs)obj).Location = value!, + IgnoreCondition = null, + HasJsonInclude = false, + IsExtensionData = false, + NumberHandling = null, + PropertyName = "Location", + JsonPropertyName = null, + AttributeProviderFactory = static () => typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherArgs).GetProperty("Location", InstanceMemberBindingFlags, null, typeof(string), global::System.Array.Empty(), null), + }; + + properties[0] = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreatePropertyInfo(options, info0); + + var info1 = new global::System.Text.Json.Serialization.Metadata.JsonPropertyInfoValues + { + IsProperty = true, + IsPublic = true, + IsVirtual = false, + DeclaringType = typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherArgs), + Converter = null, + Getter = static obj => ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherArgs)obj).Unit, + Setter = static (obj, value) => ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherArgs)obj).Unit = value!, + IgnoreCondition = null, + HasJsonInclude = false, + IsExtensionData = false, + NumberHandling = null, + PropertyName = "Unit", + JsonPropertyName = null, + AttributeProviderFactory = static () => typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherArgs).GetProperty("Unit", InstanceMemberBindingFlags, null, typeof(global::CSharpToJsonSchema.IntegrationTests.Unit), global::System.Array.Empty(), null), + }; + + properties[1] = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreatePropertyInfo(options, info1); + + return properties; + } + + // Intentionally not a static method because we create a delegate to it. Invoking delegates to instance + // methods is almost as fast as virtual calls. Static methods need to go through a shuffle thunk. + private void GetCurrentWeatherArgsSerializeHandler(global::System.Text.Json.Utf8JsonWriter writer, global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherArgs? value) + { + if (value is null) + { + writer.WriteNullValue(); + return; + } + + writer.WriteStartObject(); + + string __value_Location = ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherArgs)value).Location; + if (__value_Location is not null) + { + writer.WriteString(PropName_location, __value_Location); + } + writer.WritePropertyName(PropName_unit); + global::System.Text.Json.JsonSerializer.Serialize(writer, ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherArgs)value).Unit, Unit); + + writer.WriteEndObject(); + } + } +} diff --git a/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.Weather#IWeatherTools.GetCurrentWeatherAsyncArgs.g.verified.cs b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.Weather#IWeatherTools.GetCurrentWeatherAsyncArgs.g.verified.cs new file mode 100644 index 0000000..0ed9822 --- /dev/null +++ b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.Weather#IWeatherTools.GetCurrentWeatherAsyncArgs.g.verified.cs @@ -0,0 +1,118 @@ +//HintName: IWeatherTools.GetCurrentWeatherAsyncArgs.g.cs +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +namespace CSharpToJsonSchema.IntegrationTests +{ + public partial class WeatherToolsExtensionsJsonSerializerContext + { + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo? _GetCurrentWeatherAsyncArgs; + + /// + /// Defines the source generated JSON serialization contract metadata for a given type. + /// + #nullable disable annotations // Marking the property type as nullable-oblivious. + public global::System.Text.Json.Serialization.Metadata.JsonTypeInfo GetCurrentWeatherAsyncArgs + #nullable enable annotations + { + get => _GetCurrentWeatherAsyncArgs ??= (global::System.Text.Json.Serialization.Metadata.JsonTypeInfo)Options.GetTypeInfo(typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherAsyncArgs)); + } + + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo Create_GetCurrentWeatherAsyncArgs(global::System.Text.Json.JsonSerializerOptions options) + { + if (!TryGetTypeInfoForRuntimeCustomConverter(options, out global::System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo)) + { + var objectInfo = new global::System.Text.Json.Serialization.Metadata.JsonObjectInfoValues + { + ObjectCreator = () => new global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherAsyncArgs(), + ObjectWithParameterizedConstructorCreator = null, + PropertyMetadataInitializer = _ => GetCurrentWeatherAsyncArgsPropInit(options), + ConstructorParameterMetadataInitializer = null, + ConstructorAttributeProviderFactory = static () => typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherAsyncArgs).GetConstructor(InstanceMemberBindingFlags, binder: null, global::System.Array.Empty(), modifiers: null), + SerializeHandler = GetCurrentWeatherAsyncArgsSerializeHandler, + }; + + jsonTypeInfo = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreateObjectInfo(options, objectInfo); + jsonTypeInfo.NumberHandling = null; + } + + jsonTypeInfo.OriginatingResolver = this; + return jsonTypeInfo; + } + + private static global::System.Text.Json.Serialization.Metadata.JsonPropertyInfo[] GetCurrentWeatherAsyncArgsPropInit(global::System.Text.Json.JsonSerializerOptions options) + { + var properties = new global::System.Text.Json.Serialization.Metadata.JsonPropertyInfo[2]; + + var info0 = new global::System.Text.Json.Serialization.Metadata.JsonPropertyInfoValues + { + IsProperty = true, + IsPublic = true, + IsVirtual = false, + DeclaringType = typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherAsyncArgs), + Converter = null, + Getter = static obj => ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherAsyncArgs)obj).Location, + Setter = static (obj, value) => ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherAsyncArgs)obj).Location = value!, + IgnoreCondition = null, + HasJsonInclude = false, + IsExtensionData = false, + NumberHandling = null, + PropertyName = "Location", + JsonPropertyName = null, + AttributeProviderFactory = static () => typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherAsyncArgs).GetProperty("Location", InstanceMemberBindingFlags, null, typeof(string), global::System.Array.Empty(), null), + }; + + properties[0] = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreatePropertyInfo(options, info0); + + var info1 = new global::System.Text.Json.Serialization.Metadata.JsonPropertyInfoValues + { + IsProperty = true, + IsPublic = true, + IsVirtual = false, + DeclaringType = typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherAsyncArgs), + Converter = null, + Getter = static obj => ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherAsyncArgs)obj).Unit, + Setter = static (obj, value) => ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherAsyncArgs)obj).Unit = value!, + IgnoreCondition = null, + HasJsonInclude = false, + IsExtensionData = false, + NumberHandling = null, + PropertyName = "Unit", + JsonPropertyName = null, + AttributeProviderFactory = static () => typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherAsyncArgs).GetProperty("Unit", InstanceMemberBindingFlags, null, typeof(global::CSharpToJsonSchema.IntegrationTests.Unit), global::System.Array.Empty(), null), + }; + + properties[1] = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreatePropertyInfo(options, info1); + + return properties; + } + + // Intentionally not a static method because we create a delegate to it. Invoking delegates to instance + // methods is almost as fast as virtual calls. Static methods need to go through a shuffle thunk. + private void GetCurrentWeatherAsyncArgsSerializeHandler(global::System.Text.Json.Utf8JsonWriter writer, global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherAsyncArgs? value) + { + if (value is null) + { + writer.WriteNullValue(); + return; + } + + writer.WriteStartObject(); + + string __value_Location = ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherAsyncArgs)value).Location; + if (__value_Location is not null) + { + writer.WriteString(PropName_location, __value_Location); + } + writer.WritePropertyName(PropName_unit); + global::System.Text.Json.JsonSerializer.Serialize(writer, ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherAsyncArgs)value).Unit, Unit); + + writer.WriteEndObject(); + } + } +} diff --git a/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.Weather#IWeatherTools.GetJsonTypeInfo.g.verified.cs b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.Weather#IWeatherTools.GetJsonTypeInfo.g.verified.cs new file mode 100644 index 0000000..6e04e4e --- /dev/null +++ b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.Weather#IWeatherTools.GetJsonTypeInfo.g.verified.cs @@ -0,0 +1,50 @@ +//HintName: IWeatherTools.GetJsonTypeInfo.g.cs +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +namespace CSharpToJsonSchema.IntegrationTests +{ + public partial class WeatherToolsExtensionsJsonSerializerContext : global::System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver + { + /// + public override global::System.Text.Json.Serialization.Metadata.JsonTypeInfo? GetTypeInfo(global::System.Type type) + { + Options.TryGetTypeInfo(type, out global::System.Text.Json.Serialization.Metadata.JsonTypeInfo? typeInfo); + return typeInfo; + } + + global::System.Text.Json.Serialization.Metadata.JsonTypeInfo? global::System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver.GetTypeInfo(global::System.Type type, global::System.Text.Json.JsonSerializerOptions options) + { + if (type == typeof(double)) + { + return Create_Double(options); + } + if (type == typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherArgs)) + { + return Create_GetCurrentWeatherArgs(options); + } + if (type == typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherAsyncArgs)) + { + return Create_GetCurrentWeatherAsyncArgs(options); + } + if (type == typeof(global::CSharpToJsonSchema.IntegrationTests.Unit)) + { + return Create_Unit(options); + } + if (type == typeof(global::CSharpToJsonSchema.IntegrationTests.Weather)) + { + return Create_Weather(options); + } + if (type == typeof(string)) + { + return Create_String(options); + } + return null; + } + } +} diff --git a/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.Weather#IWeatherTools.PropertyNames.g.verified.cs b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.Weather#IWeatherTools.PropertyNames.g.verified.cs new file mode 100644 index 0000000..f05a09c --- /dev/null +++ b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.Weather#IWeatherTools.PropertyNames.g.verified.cs @@ -0,0 +1,19 @@ +//HintName: IWeatherTools.PropertyNames.g.cs +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +namespace CSharpToJsonSchema.IntegrationTests +{ + public partial class WeatherToolsExtensionsJsonSerializerContext + { + private static readonly global::System.Text.Json.JsonEncodedText PropName_location = global::System.Text.Json.JsonEncodedText.Encode("location"); + private static readonly global::System.Text.Json.JsonEncodedText PropName_unit = global::System.Text.Json.JsonEncodedText.Encode("unit"); + private static readonly global::System.Text.Json.JsonEncodedText PropName_temperature = global::System.Text.Json.JsonEncodedText.Encode("temperature"); + private static readonly global::System.Text.Json.JsonEncodedText PropName_description = global::System.Text.Json.JsonEncodedText.Encode("description"); + } +} diff --git a/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.Weather#IWeatherTools.String.g.verified.cs b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.Weather#IWeatherTools.String.g.verified.cs new file mode 100644 index 0000000..56f2b3d --- /dev/null +++ b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.Weather#IWeatherTools.String.g.verified.cs @@ -0,0 +1,37 @@ +//HintName: IWeatherTools.String.g.cs +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +namespace CSharpToJsonSchema.IntegrationTests +{ + public partial class WeatherToolsExtensionsJsonSerializerContext + { + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo? _String; + + /// + /// Defines the source generated JSON serialization contract metadata for a given type. + /// + #nullable disable annotations // Marking the property type as nullable-oblivious. + public global::System.Text.Json.Serialization.Metadata.JsonTypeInfo String + #nullable enable annotations + { + get => _String ??= (global::System.Text.Json.Serialization.Metadata.JsonTypeInfo)Options.GetTypeInfo(typeof(string)); + } + + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo Create_String(global::System.Text.Json.JsonSerializerOptions options) + { + if (!TryGetTypeInfoForRuntimeCustomConverter(options, out global::System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo)) + { + jsonTypeInfo = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreateValueInfo(options, global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.StringConverter); + } + + jsonTypeInfo.OriginatingResolver = this; + return jsonTypeInfo; + } + } +} diff --git a/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.Weather#IWeatherTools.Unit.g.verified.cs b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.Weather#IWeatherTools.Unit.g.verified.cs new file mode 100644 index 0000000..7850732 --- /dev/null +++ b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.Weather#IWeatherTools.Unit.g.verified.cs @@ -0,0 +1,38 @@ +//HintName: IWeatherTools.Unit.g.cs +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +namespace CSharpToJsonSchema.IntegrationTests +{ + public partial class WeatherToolsExtensionsJsonSerializerContext + { + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo? _Unit; + + /// + /// Defines the source generated JSON serialization contract metadata for a given type. + /// + #nullable disable annotations // Marking the property type as nullable-oblivious. + public global::System.Text.Json.Serialization.Metadata.JsonTypeInfo Unit + #nullable enable annotations + { + get => _Unit ??= (global::System.Text.Json.Serialization.Metadata.JsonTypeInfo)Options.GetTypeInfo(typeof(global::CSharpToJsonSchema.IntegrationTests.Unit)); + } + + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo Create_Unit(global::System.Text.Json.JsonSerializerOptions options) + { + if (!TryGetTypeInfoForRuntimeCustomConverter(options, out global::System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo)) + { + global::System.Text.Json.Serialization.JsonConverter converter = ExpandConverter(typeof(global::CSharpToJsonSchema.IntegrationTests.Unit), new global::System.Text.Json.Serialization.JsonStringEnumConverter(), options); + jsonTypeInfo = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreateValueInfo (options, converter); + } + + jsonTypeInfo.OriginatingResolver = this; + return jsonTypeInfo; + } + } +} diff --git a/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.Weather#IWeatherTools.Weather.g.verified.cs b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.Weather#IWeatherTools.Weather.g.verified.cs new file mode 100644 index 0000000..830d339 --- /dev/null +++ b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.Weather#IWeatherTools.Weather.g.verified.cs @@ -0,0 +1,164 @@ +//HintName: IWeatherTools.Weather.g.cs +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +namespace CSharpToJsonSchema.IntegrationTests +{ + public partial class WeatherToolsExtensionsJsonSerializerContext + { + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo? _Weather; + + /// + /// Defines the source generated JSON serialization contract metadata for a given type. + /// + #nullable disable annotations // Marking the property type as nullable-oblivious. + public global::System.Text.Json.Serialization.Metadata.JsonTypeInfo Weather + #nullable enable annotations + { + get => _Weather ??= (global::System.Text.Json.Serialization.Metadata.JsonTypeInfo)Options.GetTypeInfo(typeof(global::CSharpToJsonSchema.IntegrationTests.Weather)); + } + + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo Create_Weather(global::System.Text.Json.JsonSerializerOptions options) + { + if (!TryGetTypeInfoForRuntimeCustomConverter(options, out global::System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo)) + { + var objectInfo = new global::System.Text.Json.Serialization.Metadata.JsonObjectInfoValues + { + ObjectCreator = () => new global::CSharpToJsonSchema.IntegrationTests.Weather(), + ObjectWithParameterizedConstructorCreator = null, + PropertyMetadataInitializer = _ => WeatherPropInit(options), + ConstructorParameterMetadataInitializer = null, + ConstructorAttributeProviderFactory = static () => typeof(global::CSharpToJsonSchema.IntegrationTests.Weather).GetConstructor(InstanceMemberBindingFlags, binder: null, global::System.Array.Empty(), modifiers: null), + SerializeHandler = WeatherSerializeHandler, + }; + + jsonTypeInfo = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreateObjectInfo(options, objectInfo); + jsonTypeInfo.NumberHandling = null; + } + + jsonTypeInfo.OriginatingResolver = this; + return jsonTypeInfo; + } + + private static global::System.Text.Json.Serialization.Metadata.JsonPropertyInfo[] WeatherPropInit(global::System.Text.Json.JsonSerializerOptions options) + { + var properties = new global::System.Text.Json.Serialization.Metadata.JsonPropertyInfo[4]; + + var info0 = new global::System.Text.Json.Serialization.Metadata.JsonPropertyInfoValues + { + IsProperty = true, + IsPublic = true, + IsVirtual = false, + DeclaringType = typeof(global::CSharpToJsonSchema.IntegrationTests.Weather), + Converter = null, + Getter = static obj => ((global::CSharpToJsonSchema.IntegrationTests.Weather)obj).Location, + Setter = static (obj, value) => ((global::CSharpToJsonSchema.IntegrationTests.Weather)obj).Location = value!, + IgnoreCondition = null, + HasJsonInclude = false, + IsExtensionData = false, + NumberHandling = null, + PropertyName = "Location", + JsonPropertyName = null, + AttributeProviderFactory = static () => typeof(global::CSharpToJsonSchema.IntegrationTests.Weather).GetProperty("Location", InstanceMemberBindingFlags, null, typeof(string), global::System.Array.Empty(), null), + }; + + properties[0] = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreatePropertyInfo(options, info0); + + var info1 = new global::System.Text.Json.Serialization.Metadata.JsonPropertyInfoValues + { + IsProperty = true, + IsPublic = true, + IsVirtual = false, + DeclaringType = typeof(global::CSharpToJsonSchema.IntegrationTests.Weather), + Converter = null, + Getter = static obj => ((global::CSharpToJsonSchema.IntegrationTests.Weather)obj).Temperature, + Setter = static (obj, value) => ((global::CSharpToJsonSchema.IntegrationTests.Weather)obj).Temperature = value!, + IgnoreCondition = null, + HasJsonInclude = false, + IsExtensionData = false, + NumberHandling = null, + PropertyName = "Temperature", + JsonPropertyName = null, + AttributeProviderFactory = static () => typeof(global::CSharpToJsonSchema.IntegrationTests.Weather).GetProperty("Temperature", InstanceMemberBindingFlags, null, typeof(double), global::System.Array.Empty(), null), + }; + + properties[1] = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreatePropertyInfo(options, info1); + + var info2 = new global::System.Text.Json.Serialization.Metadata.JsonPropertyInfoValues + { + IsProperty = true, + IsPublic = true, + IsVirtual = false, + DeclaringType = typeof(global::CSharpToJsonSchema.IntegrationTests.Weather), + Converter = null, + Getter = static obj => ((global::CSharpToJsonSchema.IntegrationTests.Weather)obj).Unit, + Setter = static (obj, value) => ((global::CSharpToJsonSchema.IntegrationTests.Weather)obj).Unit = value!, + IgnoreCondition = null, + HasJsonInclude = false, + IsExtensionData = false, + NumberHandling = null, + PropertyName = "Unit", + JsonPropertyName = null, + AttributeProviderFactory = static () => typeof(global::CSharpToJsonSchema.IntegrationTests.Weather).GetProperty("Unit", InstanceMemberBindingFlags, null, typeof(global::CSharpToJsonSchema.IntegrationTests.Unit), global::System.Array.Empty(), null), + }; + + properties[2] = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreatePropertyInfo(options, info2); + + var info3 = new global::System.Text.Json.Serialization.Metadata.JsonPropertyInfoValues + { + IsProperty = true, + IsPublic = true, + IsVirtual = false, + DeclaringType = typeof(global::CSharpToJsonSchema.IntegrationTests.Weather), + Converter = null, + Getter = static obj => ((global::CSharpToJsonSchema.IntegrationTests.Weather)obj).Description, + Setter = static (obj, value) => ((global::CSharpToJsonSchema.IntegrationTests.Weather)obj).Description = value!, + IgnoreCondition = null, + HasJsonInclude = false, + IsExtensionData = false, + NumberHandling = null, + PropertyName = "Description", + JsonPropertyName = null, + AttributeProviderFactory = static () => typeof(global::CSharpToJsonSchema.IntegrationTests.Weather).GetProperty("Description", InstanceMemberBindingFlags, null, typeof(string), global::System.Array.Empty(), null), + }; + + properties[3] = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreatePropertyInfo(options, info3); + + return properties; + } + + // Intentionally not a static method because we create a delegate to it. Invoking delegates to instance + // methods is almost as fast as virtual calls. Static methods need to go through a shuffle thunk. + private void WeatherSerializeHandler(global::System.Text.Json.Utf8JsonWriter writer, global::CSharpToJsonSchema.IntegrationTests.Weather? value) + { + if (value is null) + { + writer.WriteNullValue(); + return; + } + + writer.WriteStartObject(); + + string __value_Location = ((global::CSharpToJsonSchema.IntegrationTests.Weather)value).Location; + if (__value_Location is not null) + { + writer.WriteString(PropName_location, __value_Location); + } + writer.WriteNumber(PropName_temperature, ((global::CSharpToJsonSchema.IntegrationTests.Weather)value).Temperature); + writer.WritePropertyName(PropName_unit); + global::System.Text.Json.JsonSerializer.Serialize(writer, ((global::CSharpToJsonSchema.IntegrationTests.Weather)value).Unit, Unit); + string __value_Description = ((global::CSharpToJsonSchema.IntegrationTests.Weather)value).Description; + if (__value_Description is not null) + { + writer.WriteString(PropName_description, __value_Description); + } + + writer.WriteEndObject(); + } + } +} diff --git a/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.Weather#IWeatherTools.g.verified.cs b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.Weather#IWeatherTools.g.verified.cs new file mode 100644 index 0000000..e0160a5 --- /dev/null +++ b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.Weather#IWeatherTools.g.verified.cs @@ -0,0 +1,111 @@ +//HintName: IWeatherTools.g.cs +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +namespace CSharpToJsonSchema.IntegrationTests +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("CSharpToJsonSchema.Generators", "3.0.0.0")] + public partial class WeatherToolsExtensionsJsonSerializerContext + { + private readonly static global::System.Text.Json.JsonSerializerOptions s_defaultOptions = new(global::System.Text.Json.JsonSerializerDefaults.Web) + { + AllowOutOfOrderMetadataProperties = true, + AllowTrailingCommas = false, + DefaultBufferSize = 1024, + DefaultIgnoreCondition = global::System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull, + RespectNullableAnnotations = true, + RespectRequiredConstructorParameters = false, + IgnoreReadOnlyFields = false, + IgnoreReadOnlyProperties = false, + IncludeFields = false, + MaxDepth = 64, + NewLine = "\n", + NumberHandling = global::System.Text.Json.Serialization.JsonNumberHandling.Strict, + PreferredObjectCreationHandling = global::System.Text.Json.Serialization.JsonObjectCreationHandling.Replace, + PropertyNameCaseInsensitive = true, + PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, + ReadCommentHandling = global::System.Text.Json.JsonCommentHandling.Disallow, + UnknownTypeHandling = global::System.Text.Json.Serialization.JsonUnknownTypeHandling.JsonElement, + UnmappedMemberHandling = global::System.Text.Json.Serialization.JsonUnmappedMemberHandling.Skip, + WriteIndented = false, + IndentCharacter = ' ', + IndentSize = 2, + }; + + private const global::System.Reflection.BindingFlags InstanceMemberBindingFlags = + global::System.Reflection.BindingFlags.Instance | + global::System.Reflection.BindingFlags.Public | + global::System.Reflection.BindingFlags.NonPublic; + + /// + /// The default associated with a default instance. + /// + public static global::CSharpToJsonSchema.IntegrationTests.WeatherToolsExtensionsJsonSerializerContext Default { get; } = new global::CSharpToJsonSchema.IntegrationTests.WeatherToolsExtensionsJsonSerializerContext(new global::System.Text.Json.JsonSerializerOptions(s_defaultOptions)); + + /// + /// The source-generated options associated with this context. + /// + protected override global::System.Text.Json.JsonSerializerOptions? GeneratedSerializerOptions { get; } = s_defaultOptions; + + /// + public WeatherToolsExtensionsJsonSerializerContext() : base(null) + { + } + + /// + public WeatherToolsExtensionsJsonSerializerContext(global::System.Text.Json.JsonSerializerOptions options) : base(options) + { + } + + private static bool TryGetTypeInfoForRuntimeCustomConverter(global::System.Text.Json.JsonSerializerOptions options, out global::System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo) + { + global::System.Text.Json.Serialization.JsonConverter? converter = GetRuntimeConverterForType(typeof(TJsonMetadataType), options); + if (converter != null) + { + jsonTypeInfo = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreateValueInfo(options, converter); + return true; + } + + jsonTypeInfo = null; + return false; + } + + private static global::System.Text.Json.Serialization.JsonConverter? GetRuntimeConverterForType(global::System.Type type, global::System.Text.Json.JsonSerializerOptions options) + { + for (int i = 0; i < options.Converters.Count; i++) + { + global::System.Text.Json.Serialization.JsonConverter? converter = options.Converters[i]; + if (converter?.CanConvert(type) == true) + { + return ExpandConverter(type, converter, options, validateCanConvert: false); + } + } + + return null; + } + + private static global::System.Text.Json.Serialization.JsonConverter ExpandConverter(global::System.Type type, global::System.Text.Json.Serialization.JsonConverter converter, global::System.Text.Json.JsonSerializerOptions options, bool validateCanConvert = true) + { + if (validateCanConvert && !converter.CanConvert(type)) + { + throw new global::System.InvalidOperationException(string.Format("The converter '{0}' is not compatible with the type '{1}'.", converter.GetType(), type)); + } + + if (converter is global::System.Text.Json.Serialization.JsonConverterFactory factory) + { + converter = factory.CreateConverter(type, options); + if (converter is null || converter is global::System.Text.Json.Serialization.JsonConverterFactory) + { + throw new global::System.InvalidOperationException(string.Format("The converter '{0}' cannot return null or a JsonConverterFactory instance.", factory.GetType())); + } + } + + return converter; + } + } +} diff --git a/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.WeatherStrict#IWeatherStrictTools.Calls.generated.verified.cs b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.WeatherStrict#IWeatherStrictTools.Calls.generated.verified.cs index 0800e0f..069f2f0 100644 --- a/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.WeatherStrict#IWeatherStrictTools.Calls.generated.verified.cs +++ b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.WeatherStrict#IWeatherStrictTools.Calls.generated.verified.cs @@ -3,91 +3,167 @@ namespace CSharpToJsonSchema.IntegrationTests { - public static partial class WeatherStrictToolsExtensions - { - public class GetCurrentWeatherArgs + public class GetCurrentWeather2Args { public string Location { get; set; } = string.Empty; public global::CSharpToJsonSchema.IntegrationTests.Unit2 Unit { get; set; } } - public class GetCurrentWeatherAsyncArgs + public class GetCurrentWeatherAsync2Args { public string Location { get; set; } = string.Empty; public global::CSharpToJsonSchema.IntegrationTests.Unit2 Unit { get; set; } } + public static partial class WeatherStrictToolsExtensions + { + + + public static global::System.Collections.Generic.IReadOnlyDictionary>> AsCalls(this IWeatherStrictTools service) { return new global::System.Collections.Generic.Dictionary>> { - ["GetCurrentWeather"] = (json, _) => + ["GetCurrentWeather2"] = (json, _) => { - return global::System.Threading.Tasks.Task.FromResult(service.CallGetCurrentWeather(json)); + return global::System.Threading.Tasks.Task.FromResult(service.CallGetCurrentWeather2(json)); }, - ["GetCurrentWeatherAsync"] = async (json, cancellationToken) => + ["GetCurrentWeatherAsync2"] = async (json, cancellationToken) => { - return await service.CallGetCurrentWeatherAsync(json, cancellationToken); + return await service.CallGetCurrentWeatherAsync2(json, cancellationToken); }, }; } - public static WeatherStrictToolsExtensions.GetCurrentWeatherArgs AsGetCurrentWeatherArgs( + public static GetCurrentWeather2Args AsGetCurrentWeather2Args( this IWeatherStrictTools functions, string json) { + #if NET6_0_OR_GREATER + if(global::System.Text.Json.JsonSerializer.IsReflectionEnabledByDefault) + { + return + global::System.Text.Json.JsonSerializer.Deserialize(json, new global::System.Text.Json.JsonSerializerOptions + { + PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, + Converters = {{ new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }} + }) ?? + throw new global::System.InvalidOperationException("Could not deserialize JSON."); + + } + else + { + return global::System.Text.Json.JsonSerializer.Deserialize(json, global::CSharpToJsonSchema.IntegrationTests.WeatherStrictToolsExtensionsJsonSerializerContext.Default.GetCurrentWeather2Args) ?? + throw new global::System.InvalidOperationException("Could not deserialize JSON."); + + } + #else return - global::System.Text.Json.JsonSerializer.Deserialize(json, new global::System.Text.Json.JsonSerializerOptions + global::System.Text.Json.JsonSerializer.Deserialize(json, new global::System.Text.Json.JsonSerializerOptions { PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, Converters = {{ new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }} }) ?? throw new global::System.InvalidOperationException("Could not deserialize JSON."); + #endif } - public static WeatherStrictToolsExtensions.GetCurrentWeatherAsyncArgs AsGetCurrentWeatherAsyncArgs( + public static GetCurrentWeatherAsync2Args AsGetCurrentWeatherAsync2Args( this IWeatherStrictTools functions, string json) { + #if NET6_0_OR_GREATER + if(global::System.Text.Json.JsonSerializer.IsReflectionEnabledByDefault) + { + return + global::System.Text.Json.JsonSerializer.Deserialize(json, new global::System.Text.Json.JsonSerializerOptions + { + PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, + Converters = {{ new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }} + }) ?? + throw new global::System.InvalidOperationException("Could not deserialize JSON."); + + } + else + { + return global::System.Text.Json.JsonSerializer.Deserialize(json, global::CSharpToJsonSchema.IntegrationTests.WeatherStrictToolsExtensionsJsonSerializerContext.Default.GetCurrentWeatherAsync2Args) ?? + throw new global::System.InvalidOperationException("Could not deserialize JSON."); + + } + #else return - global::System.Text.Json.JsonSerializer.Deserialize(json, new global::System.Text.Json.JsonSerializerOptions + global::System.Text.Json.JsonSerializer.Deserialize(json, new global::System.Text.Json.JsonSerializerOptions { PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, Converters = {{ new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }} }) ?? throw new global::System.InvalidOperationException("Could not deserialize JSON."); + #endif } - public static string CallGetCurrentWeather(this IWeatherStrictTools functions, string json) + public static string CallGetCurrentWeather2(this IWeatherStrictTools functions, string json) { - var args = functions.AsGetCurrentWeatherArgs(json); - var jsonResult = functions.GetCurrentWeather(args.Location, args.Unit); + var args = functions.AsGetCurrentWeather2Args(json); + var jsonResult = functions.GetCurrentWeather2(args.Location, args.Unit); - return global::System.Text.Json.JsonSerializer.Serialize(jsonResult, new global::System.Text.Json.JsonSerializerOptions + #if NET6_0_OR_GREATER + if(global::System.Text.Json.JsonSerializer.IsReflectionEnabledByDefault) { - PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, - Converters = { new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }, - }); + return global::System.Text.Json.JsonSerializer.Serialize(jsonResult, new global::System.Text.Json.JsonSerializerOptions + { + PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, + Converters = { new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }, + }); + } + else + { + return global::System.Text.Json.JsonSerializer.Serialize(jsonResult, global::CSharpToJsonSchema.IntegrationTests.WeatherStrictToolsExtensionsJsonSerializerContext.Default.GetTypeInfo(jsonResult.GetType())); + } + #else + return global::System.Text.Json.JsonSerializer.Serialize(jsonResult, new global::System.Text.Json.JsonSerializerOptions + { + PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, + Converters = { new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }, + }); + #endif + } - public static async global::System.Threading.Tasks.Task CallGetCurrentWeatherAsync( + public static async global::System.Threading.Tasks.Task CallGetCurrentWeatherAsync2( this IWeatherStrictTools functions, string json, global::System.Threading.CancellationToken cancellationToken = default) { - var args = functions.AsGetCurrentWeatherAsyncArgs(json); - var jsonResult = await functions.GetCurrentWeatherAsync(args.Location, args.Unit, cancellationToken); + var args = functions.AsGetCurrentWeatherAsync2Args(json); + var jsonResult = await functions.GetCurrentWeatherAsync2(args.Location, args.Unit, cancellationToken); + #if NET6_0_OR_GREATER + if(global::System.Text.Json.JsonSerializer.IsReflectionEnabledByDefault) + { + return global::System.Text.Json.JsonSerializer.Serialize(jsonResult, new global::System.Text.Json.JsonSerializerOptions + { + PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, + Converters = { new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }, + }); + } + else + { + return global::System.Text.Json.JsonSerializer.Serialize(jsonResult, global::CSharpToJsonSchema.IntegrationTests.WeatherStrictToolsExtensionsJsonSerializerContext.Default.GetTypeInfo(jsonResult.GetType())); + } + #else return global::System.Text.Json.JsonSerializer.Serialize(jsonResult, new global::System.Text.Json.JsonSerializerOptions { PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, Converters = { new global::System.Text.Json.Serialization.JsonStringEnumConverter(global::System.Text.Json.JsonNamingPolicy.CamelCase) }, }); + #endif + } + @@ -103,4 +179,9 @@ public static string CallGetCurrentWeather(this IWeatherStrictTools functions, s return await func(argumentsAsJson, cancellationToken); } } + + public partial class WeatherStrictToolsExtensionsJsonSerializerContext: global::System.Text.Json.Serialization.JsonSerializerContext + { + + } } \ No newline at end of file diff --git a/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.WeatherStrict#IWeatherStrictTools.Double.g.verified.cs b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.WeatherStrict#IWeatherStrictTools.Double.g.verified.cs new file mode 100644 index 0000000..452fe78 --- /dev/null +++ b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.WeatherStrict#IWeatherStrictTools.Double.g.verified.cs @@ -0,0 +1,37 @@ +//HintName: IWeatherStrictTools.Double.g.cs +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +namespace CSharpToJsonSchema.IntegrationTests +{ + public partial class WeatherStrictToolsExtensionsJsonSerializerContext + { + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo? _Double; + + /// + /// Defines the source generated JSON serialization contract metadata for a given type. + /// + #nullable disable annotations // Marking the property type as nullable-oblivious. + public global::System.Text.Json.Serialization.Metadata.JsonTypeInfo Double + #nullable enable annotations + { + get => _Double ??= (global::System.Text.Json.Serialization.Metadata.JsonTypeInfo)Options.GetTypeInfo(typeof(double)); + } + + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo Create_Double(global::System.Text.Json.JsonSerializerOptions options) + { + if (!TryGetTypeInfoForRuntimeCustomConverter(options, out global::System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo)) + { + jsonTypeInfo = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreateValueInfo(options, global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.DoubleConverter); + } + + jsonTypeInfo.OriginatingResolver = this; + return jsonTypeInfo; + } + } +} diff --git a/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.WeatherStrict#IWeatherStrictTools.GetCurrentWeather2Args.g.verified.cs b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.WeatherStrict#IWeatherStrictTools.GetCurrentWeather2Args.g.verified.cs new file mode 100644 index 0000000..2d4e52f --- /dev/null +++ b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.WeatherStrict#IWeatherStrictTools.GetCurrentWeather2Args.g.verified.cs @@ -0,0 +1,118 @@ +//HintName: IWeatherStrictTools.GetCurrentWeather2Args.g.cs +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +namespace CSharpToJsonSchema.IntegrationTests +{ + public partial class WeatherStrictToolsExtensionsJsonSerializerContext + { + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo? _GetCurrentWeather2Args; + + /// + /// Defines the source generated JSON serialization contract metadata for a given type. + /// + #nullable disable annotations // Marking the property type as nullable-oblivious. + public global::System.Text.Json.Serialization.Metadata.JsonTypeInfo GetCurrentWeather2Args + #nullable enable annotations + { + get => _GetCurrentWeather2Args ??= (global::System.Text.Json.Serialization.Metadata.JsonTypeInfo)Options.GetTypeInfo(typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather2Args)); + } + + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo Create_GetCurrentWeather2Args(global::System.Text.Json.JsonSerializerOptions options) + { + if (!TryGetTypeInfoForRuntimeCustomConverter(options, out global::System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo)) + { + var objectInfo = new global::System.Text.Json.Serialization.Metadata.JsonObjectInfoValues + { + ObjectCreator = () => new global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather2Args(), + ObjectWithParameterizedConstructorCreator = null, + PropertyMetadataInitializer = _ => GetCurrentWeather2ArgsPropInit(options), + ConstructorParameterMetadataInitializer = null, + ConstructorAttributeProviderFactory = static () => typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather2Args).GetConstructor(InstanceMemberBindingFlags, binder: null, global::System.Array.Empty(), modifiers: null), + SerializeHandler = GetCurrentWeather2ArgsSerializeHandler, + }; + + jsonTypeInfo = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreateObjectInfo(options, objectInfo); + jsonTypeInfo.NumberHandling = null; + } + + jsonTypeInfo.OriginatingResolver = this; + return jsonTypeInfo; + } + + private static global::System.Text.Json.Serialization.Metadata.JsonPropertyInfo[] GetCurrentWeather2ArgsPropInit(global::System.Text.Json.JsonSerializerOptions options) + { + var properties = new global::System.Text.Json.Serialization.Metadata.JsonPropertyInfo[2]; + + var info0 = new global::System.Text.Json.Serialization.Metadata.JsonPropertyInfoValues + { + IsProperty = true, + IsPublic = true, + IsVirtual = false, + DeclaringType = typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather2Args), + Converter = null, + Getter = static obj => ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather2Args)obj).Location, + Setter = static (obj, value) => ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather2Args)obj).Location = value!, + IgnoreCondition = null, + HasJsonInclude = false, + IsExtensionData = false, + NumberHandling = null, + PropertyName = "Location", + JsonPropertyName = null, + AttributeProviderFactory = static () => typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather2Args).GetProperty("Location", InstanceMemberBindingFlags, null, typeof(string), global::System.Array.Empty(), null), + }; + + properties[0] = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreatePropertyInfo(options, info0); + + var info1 = new global::System.Text.Json.Serialization.Metadata.JsonPropertyInfoValues + { + IsProperty = true, + IsPublic = true, + IsVirtual = false, + DeclaringType = typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather2Args), + Converter = null, + Getter = static obj => ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather2Args)obj).Unit, + Setter = static (obj, value) => ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather2Args)obj).Unit = value!, + IgnoreCondition = null, + HasJsonInclude = false, + IsExtensionData = false, + NumberHandling = null, + PropertyName = "Unit", + JsonPropertyName = null, + AttributeProviderFactory = static () => typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather2Args).GetProperty("Unit", InstanceMemberBindingFlags, null, typeof(global::CSharpToJsonSchema.IntegrationTests.Unit2), global::System.Array.Empty(), null), + }; + + properties[1] = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreatePropertyInfo(options, info1); + + return properties; + } + + // Intentionally not a static method because we create a delegate to it. Invoking delegates to instance + // methods is almost as fast as virtual calls. Static methods need to go through a shuffle thunk. + private void GetCurrentWeather2ArgsSerializeHandler(global::System.Text.Json.Utf8JsonWriter writer, global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather2Args? value) + { + if (value is null) + { + writer.WriteNullValue(); + return; + } + + writer.WriteStartObject(); + + string __value_Location = ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather2Args)value).Location; + if (__value_Location is not null) + { + writer.WriteString(PropName_location, __value_Location); + } + writer.WritePropertyName(PropName_unit); + global::System.Text.Json.JsonSerializer.Serialize(writer, ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather2Args)value).Unit, Unit2); + + writer.WriteEndObject(); + } + } +} diff --git a/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.WeatherStrict#IWeatherStrictTools.GetCurrentWeatherAsync2Args.g.verified.cs b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.WeatherStrict#IWeatherStrictTools.GetCurrentWeatherAsync2Args.g.verified.cs new file mode 100644 index 0000000..715c33e --- /dev/null +++ b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.WeatherStrict#IWeatherStrictTools.GetCurrentWeatherAsync2Args.g.verified.cs @@ -0,0 +1,118 @@ +//HintName: IWeatherStrictTools.GetCurrentWeatherAsync2Args.g.cs +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +namespace CSharpToJsonSchema.IntegrationTests +{ + public partial class WeatherStrictToolsExtensionsJsonSerializerContext + { + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo? _GetCurrentWeatherAsync2Args; + + /// + /// Defines the source generated JSON serialization contract metadata for a given type. + /// + #nullable disable annotations // Marking the property type as nullable-oblivious. + public global::System.Text.Json.Serialization.Metadata.JsonTypeInfo GetCurrentWeatherAsync2Args + #nullable enable annotations + { + get => _GetCurrentWeatherAsync2Args ??= (global::System.Text.Json.Serialization.Metadata.JsonTypeInfo)Options.GetTypeInfo(typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherAsync2Args)); + } + + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo Create_GetCurrentWeatherAsync2Args(global::System.Text.Json.JsonSerializerOptions options) + { + if (!TryGetTypeInfoForRuntimeCustomConverter(options, out global::System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo)) + { + var objectInfo = new global::System.Text.Json.Serialization.Metadata.JsonObjectInfoValues + { + ObjectCreator = () => new global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherAsync2Args(), + ObjectWithParameterizedConstructorCreator = null, + PropertyMetadataInitializer = _ => GetCurrentWeatherAsync2ArgsPropInit(options), + ConstructorParameterMetadataInitializer = null, + ConstructorAttributeProviderFactory = static () => typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherAsync2Args).GetConstructor(InstanceMemberBindingFlags, binder: null, global::System.Array.Empty(), modifiers: null), + SerializeHandler = GetCurrentWeatherAsync2ArgsSerializeHandler, + }; + + jsonTypeInfo = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreateObjectInfo(options, objectInfo); + jsonTypeInfo.NumberHandling = null; + } + + jsonTypeInfo.OriginatingResolver = this; + return jsonTypeInfo; + } + + private static global::System.Text.Json.Serialization.Metadata.JsonPropertyInfo[] GetCurrentWeatherAsync2ArgsPropInit(global::System.Text.Json.JsonSerializerOptions options) + { + var properties = new global::System.Text.Json.Serialization.Metadata.JsonPropertyInfo[2]; + + var info0 = new global::System.Text.Json.Serialization.Metadata.JsonPropertyInfoValues + { + IsProperty = true, + IsPublic = true, + IsVirtual = false, + DeclaringType = typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherAsync2Args), + Converter = null, + Getter = static obj => ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherAsync2Args)obj).Location, + Setter = static (obj, value) => ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherAsync2Args)obj).Location = value!, + IgnoreCondition = null, + HasJsonInclude = false, + IsExtensionData = false, + NumberHandling = null, + PropertyName = "Location", + JsonPropertyName = null, + AttributeProviderFactory = static () => typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherAsync2Args).GetProperty("Location", InstanceMemberBindingFlags, null, typeof(string), global::System.Array.Empty(), null), + }; + + properties[0] = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreatePropertyInfo(options, info0); + + var info1 = new global::System.Text.Json.Serialization.Metadata.JsonPropertyInfoValues + { + IsProperty = true, + IsPublic = true, + IsVirtual = false, + DeclaringType = typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherAsync2Args), + Converter = null, + Getter = static obj => ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherAsync2Args)obj).Unit, + Setter = static (obj, value) => ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherAsync2Args)obj).Unit = value!, + IgnoreCondition = null, + HasJsonInclude = false, + IsExtensionData = false, + NumberHandling = null, + PropertyName = "Unit", + JsonPropertyName = null, + AttributeProviderFactory = static () => typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherAsync2Args).GetProperty("Unit", InstanceMemberBindingFlags, null, typeof(global::CSharpToJsonSchema.IntegrationTests.Unit2), global::System.Array.Empty(), null), + }; + + properties[1] = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreatePropertyInfo(options, info1); + + return properties; + } + + // Intentionally not a static method because we create a delegate to it. Invoking delegates to instance + // methods is almost as fast as virtual calls. Static methods need to go through a shuffle thunk. + private void GetCurrentWeatherAsync2ArgsSerializeHandler(global::System.Text.Json.Utf8JsonWriter writer, global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherAsync2Args? value) + { + if (value is null) + { + writer.WriteNullValue(); + return; + } + + writer.WriteStartObject(); + + string __value_Location = ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherAsync2Args)value).Location; + if (__value_Location is not null) + { + writer.WriteString(PropName_location, __value_Location); + } + writer.WritePropertyName(PropName_unit); + global::System.Text.Json.JsonSerializer.Serialize(writer, ((global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherAsync2Args)value).Unit, Unit2); + + writer.WriteEndObject(); + } + } +} diff --git a/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.WeatherStrict#IWeatherStrictTools.GetJsonTypeInfo.g.verified.cs b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.WeatherStrict#IWeatherStrictTools.GetJsonTypeInfo.g.verified.cs new file mode 100644 index 0000000..1b7617b --- /dev/null +++ b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.WeatherStrict#IWeatherStrictTools.GetJsonTypeInfo.g.verified.cs @@ -0,0 +1,50 @@ +//HintName: IWeatherStrictTools.GetJsonTypeInfo.g.cs +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +namespace CSharpToJsonSchema.IntegrationTests +{ + public partial class WeatherStrictToolsExtensionsJsonSerializerContext : global::System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver + { + /// + public override global::System.Text.Json.Serialization.Metadata.JsonTypeInfo? GetTypeInfo(global::System.Type type) + { + Options.TryGetTypeInfo(type, out global::System.Text.Json.Serialization.Metadata.JsonTypeInfo? typeInfo); + return typeInfo; + } + + global::System.Text.Json.Serialization.Metadata.JsonTypeInfo? global::System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver.GetTypeInfo(global::System.Type type, global::System.Text.Json.JsonSerializerOptions options) + { + if (type == typeof(double)) + { + return Create_Double(options); + } + if (type == typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeather2Args)) + { + return Create_GetCurrentWeather2Args(options); + } + if (type == typeof(global::CSharpToJsonSchema.IntegrationTests.GetCurrentWeatherAsync2Args)) + { + return Create_GetCurrentWeatherAsync2Args(options); + } + if (type == typeof(global::CSharpToJsonSchema.IntegrationTests.Unit2)) + { + return Create_Unit2(options); + } + if (type == typeof(global::CSharpToJsonSchema.IntegrationTests.Weather2)) + { + return Create_Weather2(options); + } + if (type == typeof(string)) + { + return Create_String(options); + } + return null; + } + } +} diff --git a/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.WeatherStrict#IWeatherStrictTools.PropertyNames.g.verified.cs b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.WeatherStrict#IWeatherStrictTools.PropertyNames.g.verified.cs new file mode 100644 index 0000000..a8edc96 --- /dev/null +++ b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.WeatherStrict#IWeatherStrictTools.PropertyNames.g.verified.cs @@ -0,0 +1,19 @@ +//HintName: IWeatherStrictTools.PropertyNames.g.cs +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +namespace CSharpToJsonSchema.IntegrationTests +{ + public partial class WeatherStrictToolsExtensionsJsonSerializerContext + { + private static readonly global::System.Text.Json.JsonEncodedText PropName_location = global::System.Text.Json.JsonEncodedText.Encode("location"); + private static readonly global::System.Text.Json.JsonEncodedText PropName_unit = global::System.Text.Json.JsonEncodedText.Encode("unit"); + private static readonly global::System.Text.Json.JsonEncodedText PropName_temperature = global::System.Text.Json.JsonEncodedText.Encode("temperature"); + private static readonly global::System.Text.Json.JsonEncodedText PropName_description = global::System.Text.Json.JsonEncodedText.Encode("description"); + } +} diff --git a/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.WeatherStrict#IWeatherStrictTools.String.g.verified.cs b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.WeatherStrict#IWeatherStrictTools.String.g.verified.cs new file mode 100644 index 0000000..004080e --- /dev/null +++ b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.WeatherStrict#IWeatherStrictTools.String.g.verified.cs @@ -0,0 +1,37 @@ +//HintName: IWeatherStrictTools.String.g.cs +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +namespace CSharpToJsonSchema.IntegrationTests +{ + public partial class WeatherStrictToolsExtensionsJsonSerializerContext + { + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo? _String; + + /// + /// Defines the source generated JSON serialization contract metadata for a given type. + /// + #nullable disable annotations // Marking the property type as nullable-oblivious. + public global::System.Text.Json.Serialization.Metadata.JsonTypeInfo String + #nullable enable annotations + { + get => _String ??= (global::System.Text.Json.Serialization.Metadata.JsonTypeInfo)Options.GetTypeInfo(typeof(string)); + } + + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo Create_String(global::System.Text.Json.JsonSerializerOptions options) + { + if (!TryGetTypeInfoForRuntimeCustomConverter(options, out global::System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo)) + { + jsonTypeInfo = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreateValueInfo(options, global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.StringConverter); + } + + jsonTypeInfo.OriginatingResolver = this; + return jsonTypeInfo; + } + } +} diff --git a/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.WeatherStrict#IWeatherStrictTools.Tools.generated.verified.cs b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.WeatherStrict#IWeatherStrictTools.Tools.generated.verified.cs index 9b55ce7..94b0e86 100644 --- a/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.WeatherStrict#IWeatherStrictTools.Tools.generated.verified.cs +++ b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.WeatherStrict#IWeatherStrictTools.Tools.generated.verified.cs @@ -12,7 +12,7 @@ public static partial class WeatherStrictToolsExtensions { new global::CSharpToJsonSchema.Tool { - Name = "GetCurrentWeather", + Name = "GetCurrentWeather2", Description = "Get the current weather in a given location", Strict = true, Parameters = new global::CSharpToJsonSchema.OpenApiSchema @@ -39,7 +39,7 @@ public static partial class WeatherStrictToolsExtensions new global::CSharpToJsonSchema.Tool { - Name = "GetCurrentWeatherAsync", + Name = "GetCurrentWeatherAsync2", Description = "Get the current weather in a given location", Strict = true, Parameters = new global::CSharpToJsonSchema.OpenApiSchema diff --git a/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.WeatherStrict#IWeatherStrictTools.Unit2.g.verified.cs b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.WeatherStrict#IWeatherStrictTools.Unit2.g.verified.cs new file mode 100644 index 0000000..c3526ac --- /dev/null +++ b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.WeatherStrict#IWeatherStrictTools.Unit2.g.verified.cs @@ -0,0 +1,38 @@ +//HintName: IWeatherStrictTools.Unit2.g.cs +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +namespace CSharpToJsonSchema.IntegrationTests +{ + public partial class WeatherStrictToolsExtensionsJsonSerializerContext + { + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo? _Unit2; + + /// + /// Defines the source generated JSON serialization contract metadata for a given type. + /// + #nullable disable annotations // Marking the property type as nullable-oblivious. + public global::System.Text.Json.Serialization.Metadata.JsonTypeInfo Unit2 + #nullable enable annotations + { + get => _Unit2 ??= (global::System.Text.Json.Serialization.Metadata.JsonTypeInfo)Options.GetTypeInfo(typeof(global::CSharpToJsonSchema.IntegrationTests.Unit2)); + } + + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo Create_Unit2(global::System.Text.Json.JsonSerializerOptions options) + { + if (!TryGetTypeInfoForRuntimeCustomConverter(options, out global::System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo)) + { + global::System.Text.Json.Serialization.JsonConverter converter = ExpandConverter(typeof(global::CSharpToJsonSchema.IntegrationTests.Unit2), new global::System.Text.Json.Serialization.JsonStringEnumConverter(), options); + jsonTypeInfo = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreateValueInfo (options, converter); + } + + jsonTypeInfo.OriginatingResolver = this; + return jsonTypeInfo; + } + } +} diff --git a/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.WeatherStrict#IWeatherStrictTools.Weather2.g.verified.cs b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.WeatherStrict#IWeatherStrictTools.Weather2.g.verified.cs new file mode 100644 index 0000000..56c793b --- /dev/null +++ b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.WeatherStrict#IWeatherStrictTools.Weather2.g.verified.cs @@ -0,0 +1,164 @@ +//HintName: IWeatherStrictTools.Weather2.g.cs +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +namespace CSharpToJsonSchema.IntegrationTests +{ + public partial class WeatherStrictToolsExtensionsJsonSerializerContext + { + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo? _Weather2; + + /// + /// Defines the source generated JSON serialization contract metadata for a given type. + /// + #nullable disable annotations // Marking the property type as nullable-oblivious. + public global::System.Text.Json.Serialization.Metadata.JsonTypeInfo Weather2 + #nullable enable annotations + { + get => _Weather2 ??= (global::System.Text.Json.Serialization.Metadata.JsonTypeInfo)Options.GetTypeInfo(typeof(global::CSharpToJsonSchema.IntegrationTests.Weather2)); + } + + private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo Create_Weather2(global::System.Text.Json.JsonSerializerOptions options) + { + if (!TryGetTypeInfoForRuntimeCustomConverter(options, out global::System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo)) + { + var objectInfo = new global::System.Text.Json.Serialization.Metadata.JsonObjectInfoValues + { + ObjectCreator = () => new global::CSharpToJsonSchema.IntegrationTests.Weather2(), + ObjectWithParameterizedConstructorCreator = null, + PropertyMetadataInitializer = _ => Weather2PropInit(options), + ConstructorParameterMetadataInitializer = null, + ConstructorAttributeProviderFactory = static () => typeof(global::CSharpToJsonSchema.IntegrationTests.Weather2).GetConstructor(InstanceMemberBindingFlags, binder: null, global::System.Array.Empty(), modifiers: null), + SerializeHandler = Weather2SerializeHandler, + }; + + jsonTypeInfo = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreateObjectInfo(options, objectInfo); + jsonTypeInfo.NumberHandling = null; + } + + jsonTypeInfo.OriginatingResolver = this; + return jsonTypeInfo; + } + + private static global::System.Text.Json.Serialization.Metadata.JsonPropertyInfo[] Weather2PropInit(global::System.Text.Json.JsonSerializerOptions options) + { + var properties = new global::System.Text.Json.Serialization.Metadata.JsonPropertyInfo[4]; + + var info0 = new global::System.Text.Json.Serialization.Metadata.JsonPropertyInfoValues + { + IsProperty = true, + IsPublic = true, + IsVirtual = false, + DeclaringType = typeof(global::CSharpToJsonSchema.IntegrationTests.Weather2), + Converter = null, + Getter = static obj => ((global::CSharpToJsonSchema.IntegrationTests.Weather2)obj).Location, + Setter = static (obj, value) => ((global::CSharpToJsonSchema.IntegrationTests.Weather2)obj).Location = value!, + IgnoreCondition = null, + HasJsonInclude = false, + IsExtensionData = false, + NumberHandling = null, + PropertyName = "Location", + JsonPropertyName = null, + AttributeProviderFactory = static () => typeof(global::CSharpToJsonSchema.IntegrationTests.Weather2).GetProperty("Location", InstanceMemberBindingFlags, null, typeof(string), global::System.Array.Empty(), null), + }; + + properties[0] = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreatePropertyInfo(options, info0); + + var info1 = new global::System.Text.Json.Serialization.Metadata.JsonPropertyInfoValues + { + IsProperty = true, + IsPublic = true, + IsVirtual = false, + DeclaringType = typeof(global::CSharpToJsonSchema.IntegrationTests.Weather2), + Converter = null, + Getter = static obj => ((global::CSharpToJsonSchema.IntegrationTests.Weather2)obj).Temperature, + Setter = static (obj, value) => ((global::CSharpToJsonSchema.IntegrationTests.Weather2)obj).Temperature = value!, + IgnoreCondition = null, + HasJsonInclude = false, + IsExtensionData = false, + NumberHandling = null, + PropertyName = "Temperature", + JsonPropertyName = null, + AttributeProviderFactory = static () => typeof(global::CSharpToJsonSchema.IntegrationTests.Weather2).GetProperty("Temperature", InstanceMemberBindingFlags, null, typeof(double), global::System.Array.Empty(), null), + }; + + properties[1] = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreatePropertyInfo(options, info1); + + var info2 = new global::System.Text.Json.Serialization.Metadata.JsonPropertyInfoValues + { + IsProperty = true, + IsPublic = true, + IsVirtual = false, + DeclaringType = typeof(global::CSharpToJsonSchema.IntegrationTests.Weather2), + Converter = null, + Getter = static obj => ((global::CSharpToJsonSchema.IntegrationTests.Weather2)obj).Unit, + Setter = static (obj, value) => ((global::CSharpToJsonSchema.IntegrationTests.Weather2)obj).Unit = value!, + IgnoreCondition = null, + HasJsonInclude = false, + IsExtensionData = false, + NumberHandling = null, + PropertyName = "Unit", + JsonPropertyName = null, + AttributeProviderFactory = static () => typeof(global::CSharpToJsonSchema.IntegrationTests.Weather2).GetProperty("Unit", InstanceMemberBindingFlags, null, typeof(global::CSharpToJsonSchema.IntegrationTests.Unit2), global::System.Array.Empty(), null), + }; + + properties[2] = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreatePropertyInfo(options, info2); + + var info3 = new global::System.Text.Json.Serialization.Metadata.JsonPropertyInfoValues + { + IsProperty = true, + IsPublic = true, + IsVirtual = false, + DeclaringType = typeof(global::CSharpToJsonSchema.IntegrationTests.Weather2), + Converter = null, + Getter = static obj => ((global::CSharpToJsonSchema.IntegrationTests.Weather2)obj).Description, + Setter = static (obj, value) => ((global::CSharpToJsonSchema.IntegrationTests.Weather2)obj).Description = value!, + IgnoreCondition = null, + HasJsonInclude = false, + IsExtensionData = false, + NumberHandling = null, + PropertyName = "Description", + JsonPropertyName = null, + AttributeProviderFactory = static () => typeof(global::CSharpToJsonSchema.IntegrationTests.Weather2).GetProperty("Description", InstanceMemberBindingFlags, null, typeof(string), global::System.Array.Empty(), null), + }; + + properties[3] = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreatePropertyInfo(options, info3); + + return properties; + } + + // Intentionally not a static method because we create a delegate to it. Invoking delegates to instance + // methods is almost as fast as virtual calls. Static methods need to go through a shuffle thunk. + private void Weather2SerializeHandler(global::System.Text.Json.Utf8JsonWriter writer, global::CSharpToJsonSchema.IntegrationTests.Weather2? value) + { + if (value is null) + { + writer.WriteNullValue(); + return; + } + + writer.WriteStartObject(); + + string __value_Location = ((global::CSharpToJsonSchema.IntegrationTests.Weather2)value).Location; + if (__value_Location is not null) + { + writer.WriteString(PropName_location, __value_Location); + } + writer.WriteNumber(PropName_temperature, ((global::CSharpToJsonSchema.IntegrationTests.Weather2)value).Temperature); + writer.WritePropertyName(PropName_unit); + global::System.Text.Json.JsonSerializer.Serialize(writer, ((global::CSharpToJsonSchema.IntegrationTests.Weather2)value).Unit, Unit2); + string __value_Description = ((global::CSharpToJsonSchema.IntegrationTests.Weather2)value).Description; + if (__value_Description is not null) + { + writer.WriteString(PropName_description, __value_Description); + } + + writer.WriteEndObject(); + } + } +} diff --git a/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.WeatherStrict#IWeatherStrictTools.g.verified.cs b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.WeatherStrict#IWeatherStrictTools.g.verified.cs new file mode 100644 index 0000000..5869fdd --- /dev/null +++ b/src/tests/CSharpToJsonSchema.SnapshotTests/Snapshots/ToolTests.WeatherStrict#IWeatherStrictTools.g.verified.cs @@ -0,0 +1,111 @@ +//HintName: IWeatherStrictTools.g.cs +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +namespace CSharpToJsonSchema.IntegrationTests +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("CSharpToJsonSchema.Generators", "3.0.0.0")] + public partial class WeatherStrictToolsExtensionsJsonSerializerContext + { + private readonly static global::System.Text.Json.JsonSerializerOptions s_defaultOptions = new(global::System.Text.Json.JsonSerializerDefaults.Web) + { + AllowOutOfOrderMetadataProperties = true, + AllowTrailingCommas = false, + DefaultBufferSize = 1024, + DefaultIgnoreCondition = global::System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull, + RespectNullableAnnotations = true, + RespectRequiredConstructorParameters = false, + IgnoreReadOnlyFields = false, + IgnoreReadOnlyProperties = false, + IncludeFields = false, + MaxDepth = 64, + NewLine = "\n", + NumberHandling = global::System.Text.Json.Serialization.JsonNumberHandling.Strict, + PreferredObjectCreationHandling = global::System.Text.Json.Serialization.JsonObjectCreationHandling.Replace, + PropertyNameCaseInsensitive = true, + PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase, + ReadCommentHandling = global::System.Text.Json.JsonCommentHandling.Disallow, + UnknownTypeHandling = global::System.Text.Json.Serialization.JsonUnknownTypeHandling.JsonElement, + UnmappedMemberHandling = global::System.Text.Json.Serialization.JsonUnmappedMemberHandling.Skip, + WriteIndented = false, + IndentCharacter = ' ', + IndentSize = 2, + }; + + private const global::System.Reflection.BindingFlags InstanceMemberBindingFlags = + global::System.Reflection.BindingFlags.Instance | + global::System.Reflection.BindingFlags.Public | + global::System.Reflection.BindingFlags.NonPublic; + + /// + /// The default associated with a default instance. + /// + public static global::CSharpToJsonSchema.IntegrationTests.WeatherStrictToolsExtensionsJsonSerializerContext Default { get; } = new global::CSharpToJsonSchema.IntegrationTests.WeatherStrictToolsExtensionsJsonSerializerContext(new global::System.Text.Json.JsonSerializerOptions(s_defaultOptions)); + + /// + /// The source-generated options associated with this context. + /// + protected override global::System.Text.Json.JsonSerializerOptions? GeneratedSerializerOptions { get; } = s_defaultOptions; + + /// + public WeatherStrictToolsExtensionsJsonSerializerContext() : base(null) + { + } + + /// + public WeatherStrictToolsExtensionsJsonSerializerContext(global::System.Text.Json.JsonSerializerOptions options) : base(options) + { + } + + private static bool TryGetTypeInfoForRuntimeCustomConverter(global::System.Text.Json.JsonSerializerOptions options, out global::System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo) + { + global::System.Text.Json.Serialization.JsonConverter? converter = GetRuntimeConverterForType(typeof(TJsonMetadataType), options); + if (converter != null) + { + jsonTypeInfo = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreateValueInfo(options, converter); + return true; + } + + jsonTypeInfo = null; + return false; + } + + private static global::System.Text.Json.Serialization.JsonConverter? GetRuntimeConverterForType(global::System.Type type, global::System.Text.Json.JsonSerializerOptions options) + { + for (int i = 0; i < options.Converters.Count; i++) + { + global::System.Text.Json.Serialization.JsonConverter? converter = options.Converters[i]; + if (converter?.CanConvert(type) == true) + { + return ExpandConverter(type, converter, options, validateCanConvert: false); + } + } + + return null; + } + + private static global::System.Text.Json.Serialization.JsonConverter ExpandConverter(global::System.Type type, global::System.Text.Json.Serialization.JsonConverter converter, global::System.Text.Json.JsonSerializerOptions options, bool validateCanConvert = true) + { + if (validateCanConvert && !converter.CanConvert(type)) + { + throw new global::System.InvalidOperationException(string.Format("The converter '{0}' is not compatible with the type '{1}'.", converter.GetType(), type)); + } + + if (converter is global::System.Text.Json.Serialization.JsonConverterFactory factory) + { + converter = factory.CreateConverter(type, options); + if (converter is null || converter is global::System.Text.Json.Serialization.JsonConverterFactory) + { + throw new global::System.InvalidOperationException(string.Format("The converter '{0}' cannot return null or a JsonConverterFactory instance.", factory.GetType())); + } + } + + return converter; + } + } +} diff --git a/src/tests/CSharpToJsonSchema.SnapshotTests/TestHelper.cs b/src/tests/CSharpToJsonSchema.SnapshotTests/TestHelper.cs index 302384e..81bbfe4 100755 --- a/src/tests/CSharpToJsonSchema.SnapshotTests/TestHelper.cs +++ b/src/tests/CSharpToJsonSchema.SnapshotTests/TestHelper.cs @@ -36,9 +36,9 @@ await Task.WhenAll( //.AutoVerify() .UseDirectory("Snapshots") .UseTextForParameters("Diagnostics"), + verifier .Verify(driver) - .AutoVerify() .UseDirectory("Snapshots")); } } \ No newline at end of file