diff --git a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.Tests/JsonSourceGeneratorTests.cs b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.Tests/JsonSourceGeneratorTests.cs index c8a32bbe05a9f..f1cdd0164c6ec 100644 --- a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.Tests/JsonSourceGeneratorTests.cs +++ b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.Tests/JsonSourceGeneratorTests.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; +using System.Linq; using System.Text.Json.Serialization; using Xunit; @@ -67,11 +68,11 @@ public void TestGeneratedCode() Assert.Equal(expectedCtorParamsExternal, externalTypeTest.CtorParams); // Public and private methods are visible. - List expectedMethodsInternal = new List { "get_PublicIntPropertyPublic", "set_PublicIntPropertyPublic", "get_PublicIntPropertyPrivateSet", "set_PublicIntPropertyPrivateSet", "get_PublicIntPropertyPrivateGet", "set_PublicIntPropertyPrivateGet", "UseFields" }; - Assert.Equal(expectedMethodsInternal, internalTypeTest.Methods); + List expectedMethodsInternal = new List { "get_PublicIntPropertyPrivateGet", "get_PublicIntPropertyPrivateSet", "get_PublicIntPropertyPublic", "set_PublicIntPropertyPrivateGet", "set_PublicIntPropertyPrivateSet", "set_PublicIntPropertyPublic", "UseFields" }; + Assert.Equal(expectedMethodsInternal, internalTypeTest.Methods.OrderBy(s => s).ToList()); - List expectedMethodsExternal = new List { "get_ConverterType", "CreateConverter" }; - Assert.Equal(expectedMethodsExternal, externalTypeTest.Methods); + List expectedMethodsExternal = new List { "CreateConverter", "get_ConverterType" }; + Assert.Equal(expectedMethodsExternal, externalTypeTest.Methods.OrderBy(s => s).ToList()); // Public and private fields are visible. Dictionary expectedFieldsInternal = new Dictionary { { "PublicCharField", "Char" }, { "PrivateStringField", "String" } }; diff --git a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.UnitTests/CompilationHelper.cs b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.UnitTests/CompilationHelper.cs new file mode 100644 index 0000000000000..307bfcbd1d3f1 --- /dev/null +++ b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.UnitTests/CompilationHelper.cs @@ -0,0 +1,54 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Reflection; +using System.Text.Json.Serialization; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Xunit; + +namespace System.Text.Json.SourceGeneration.UnitTests +{ + public class CompilationHelper + { + public static Compilation CreateCompilation(string source) + { + // Bypass System.Runtime error. + Assembly systemRuntimeAssembly = Assembly.Load("System.Runtime, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"); + string systemRuntimeAssemblyPath = systemRuntimeAssembly.Location; + + MetadataReference[] references = new MetadataReference[] { + MetadataReference.CreateFromFile(typeof(object).Assembly.Location), + MetadataReference.CreateFromFile(typeof(Attribute).Assembly.Location), + MetadataReference.CreateFromFile(typeof(JsonSerializableAttribute).Assembly.Location), + MetadataReference.CreateFromFile(typeof(JsonSerializerOptions).Assembly.Location), + MetadataReference.CreateFromFile(typeof(Type).Assembly.Location), + MetadataReference.CreateFromFile(typeof(KeyValuePair).Assembly.Location), + MetadataReference.CreateFromFile(systemRuntimeAssemblyPath), + }; + + return CSharpCompilation.Create( + "TestAssembly", + syntaxTrees: new[] { CSharpSyntaxTree.ParseText(source) }, + references: references, + options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) + ); + } + + private static GeneratorDriver CreateDriver(Compilation compilation, params ISourceGenerator[] generators) + => new CSharpGeneratorDriver( + new CSharpParseOptions(kind: SourceCodeKind.Regular, documentationMode: DocumentationMode.Parse), + ImmutableArray.Create(generators), + ImmutableArray.Empty); + + public static Compilation RunGenerators(Compilation compilation, out ImmutableArray diagnostics, params ISourceGenerator[] generators) + { + CreateDriver(compilation, generators).RunFullGeneration(compilation, out Compilation outCompilation, out diagnostics); + return outCompilation; + } + } +} diff --git a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.UnitTests/JsonSourceGeneratorTests.cs b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.UnitTests/JsonSourceGeneratorTests.cs index ae8cfed1dcfe7..d43a052081bc2 100644 --- a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.UnitTests/JsonSourceGeneratorTests.cs +++ b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.UnitTests/JsonSourceGeneratorTests.cs @@ -2,13 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using System.Reflection; -using System.Text.Json.Serialization; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; using Xunit; namespace System.Text.Json.SourceGeneration.UnitTests @@ -19,7 +15,6 @@ public class GeneratorTests public void TypeDiscoveryPrimitivePOCO() { string source = @" - using System; using System.Text.Json.Serialization; namespace HelloWorld @@ -38,40 +33,42 @@ public class MyType { public void MyMethod() { } public void MySecondMethod() { } + + public void UsePrivates() + { + PrivateDouble = 0; + PrivateChar = ' '; + double d = PrivateDouble; + char c = PrivateChar; + } } }"; - Compilation compilation = CreateCompilation(source); + Compilation compilation = CompilationHelper.CreateCompilation(source); JsonSerializerSourceGenerator generator = new JsonSerializerSourceGenerator(); - Compilation outCompilation = RunGenerators(compilation, out var generatorDiags, generator); + Compilation newCompilation = CompilationHelper.RunGenerators(compilation, out var generatorDiags, generator); + + // Make sure compilation was successful. + Assert.Empty(generatorDiags); + Assert.Empty(newCompilation.GetDiagnostics()); // Check base functionality of found types. Assert.Equal(1, generator.foundTypes.Count); Assert.Equal("HelloWorld.MyType", generator.foundTypes["MyType"].FullName); - // Check for received properties in created type. - string[] expectedPropertyNames = { "PublicPropertyInt", "PublicPropertyString", "PrivatePropertyInt", "PrivatePropertyString" }; - string[] receivedPropertyNames = generator.foundTypes["MyType"].GetProperties().Select(property => property.Name).ToArray(); - Assert.Equal(expectedPropertyNames, receivedPropertyNames); - - // Check for fields in created type. - string[] expectedFieldNames = { "PublicDouble", "PublicChar", "PrivateDouble", "PrivateChar" }; - string[] receivedFieldNames = generator.foundTypes["MyType"].GetFields().Select(field => field.Name).ToArray(); - Assert.Equal(expectedFieldNames, receivedFieldNames); - - // Check for methods in created type. - string[] expectedMethodNames = { "get_PublicPropertyInt", "set_PublicPropertyInt", "get_PublicPropertyString", "set_PublicPropertyString", "get_PrivatePropertyInt", "set_PrivatePropertyInt", "get_PrivatePropertyString", "set_PrivatePropertyString", "MyMethod", "MySecondMethod" }; - string[] receivedMethodNames = generator.foundTypes["MyType"].GetMethods().Select(method => method.Name).ToArray(); - Assert.Equal(expectedMethodNames, receivedMethodNames); + // Check for received fields, properties and methods in created type. + string[] expectedPropertyNames = { "PrivatePropertyInt", "PrivatePropertyString", "PublicPropertyInt", "PublicPropertyString",}; + string[] expectedFieldNames = { "PrivateChar", "PrivateDouble", "PublicChar", "PublicDouble" }; + string[] expectedMethodNames = { "get_PrivatePropertyInt", "get_PrivatePropertyString", "get_PublicPropertyInt", "get_PublicPropertyString", "MyMethod", "MySecondMethod", "set_PrivatePropertyInt", "set_PrivatePropertyString", "set_PublicPropertyInt", "set_PublicPropertyString", "UsePrivates" }; + CheckFieldsPropertiesMethods("MyType", ref generator, expectedFieldNames, expectedPropertyNames, expectedMethodNames); } [Fact] public void TypeDiscoveryPrimitiveTemporaryPOCO() { string source = @" - using System; using System.Text.Json.Serialization; namespace HelloWorld @@ -90,18 +87,28 @@ public class MyType { public void MyMethod() { } public void MySecondMethod() { } + public void UsePrivates() + { + PrivateDouble = 0; + PrivateChar = ' '; + double x = PrivateDouble; + string s = PrivateChar.ToString(); + } } [JsonSerializable(typeof(JsonConverterAttribute))] public class NotMyType { } - }"; - Compilation compilation = CreateCompilation(source); + Compilation compilation = CompilationHelper.CreateCompilation(source); JsonSerializerSourceGenerator generator = new JsonSerializerSourceGenerator(); - Compilation outCompilation = RunGenerators(compilation, out var generatorDiags, generator); + Compilation newCompilation = CompilationHelper.RunGenerators(compilation, out ImmutableArray generatorDiags, generator); + + // Make sure compilation was successful. + Assert.Empty(generatorDiags); + Assert.Empty(newCompilation.GetDiagnostics()); // Check base functionality of found types. Assert.Equal(2, generator.foundTypes.Count); @@ -109,74 +116,104 @@ public class NotMyType { } // Check for MyType. Assert.Equal("HelloWorld.MyType", generator.foundTypes["MyType"].FullName); - // Check for received properties in created type. - string[] expectedPropertyNamesMyType = { "PublicPropertyInt", "PublicPropertyString", "PrivatePropertyInt", "PrivatePropertyString" }; - string[] receivedPropertyNamesMyType = generator.foundTypes["MyType"].GetProperties().Select(property => property.Name).ToArray(); - Assert.Equal(expectedPropertyNamesMyType, receivedPropertyNamesMyType); - - // Check for fields in created type. - string[] expectedFieldNamesMyType = { "PublicDouble", "PublicChar", "PrivateDouble", "PrivateChar" }; - string[] receivedFieldNamesMyType = generator.foundTypes["MyType"].GetFields().Select(field => field.Name).ToArray(); - Assert.Equal(expectedFieldNamesMyType, receivedFieldNamesMyType); - - // Check for methods in created type. - string[] expectedMethodNamesMyType = { "get_PublicPropertyInt", "set_PublicPropertyInt", "get_PublicPropertyString", "set_PublicPropertyString", "get_PrivatePropertyInt", "set_PrivatePropertyInt", "get_PrivatePropertyString", "set_PrivatePropertyString", "MyMethod", "MySecondMethod" }; - string[] receivedMethodNamesMyType = generator.foundTypes["MyType"].GetMethods().Select(method => method.Name).ToArray(); - Assert.Equal(expectedMethodNamesMyType, receivedMethodNamesMyType); + // Check for received fields, properties and methods for MyType. + string[] expectedFieldNamesMyType = { "PrivateChar", "PrivateDouble", "PublicChar", "PublicDouble" }; + string[] expectedPropertyNamesMyType = { "PrivatePropertyInt", "PrivatePropertyString", "PublicPropertyInt", "PublicPropertyString" }; + string[] expectedMethodNamesMyType = { "get_PrivatePropertyInt", "get_PrivatePropertyString", "get_PublicPropertyInt", "get_PublicPropertyString", "MyMethod", "MySecondMethod", "set_PrivatePropertyInt", "set_PrivatePropertyString", "set_PublicPropertyInt", "set_PublicPropertyString", "UsePrivates" }; + CheckFieldsPropertiesMethods("MyType", ref generator, expectedFieldNamesMyType, expectedPropertyNamesMyType, expectedMethodNamesMyType); // Check for NotMyType. Assert.Equal("System.Text.Json.Serialization.JsonConverterAttribute", generator.foundTypes["NotMyType"].FullName); - // Check for received properties in created type. - string[] expectedPropertyNamesNotMyType = { "ConverterType" }; - string[] receivedPropertyNamesNotMyType = generator.foundTypes["NotMyType"].GetProperties().Select(property => property.Name).ToArray(); - Assert.Equal(expectedPropertyNamesNotMyType, receivedPropertyNamesNotMyType); - - // Check for fields in created type. + // Check for received fields, properties and methods for NotMyType. string[] expectedFieldNamesNotMyType = { }; - string[] receivedFieldNamesNotMyType = generator.foundTypes["NotMyType"].GetFields().Select(field => field.Name).ToArray(); - Assert.Equal(expectedFieldNamesNotMyType, receivedFieldNamesNotMyType); - - // Check for methods in created type. - string[] expectedMethodNamesNotMyType = { "get_ConverterType", "CreateConverter" }; - string[] receivedMethodNamesNotMyType = generator.foundTypes["NotMyType"].GetMethods().Select(method => method.Name).ToArray(); - Assert.Equal(expectedMethodNamesNotMyType, receivedMethodNamesNotMyType); + string[] expectedPropertyNamesNotMyType = { "ConverterType" }; + string[] expectedMethodNamesNotMyType = { "CreateConverter", "get_ConverterType" }; + CheckFieldsPropertiesMethods("NotMyType", ref generator, expectedFieldNamesNotMyType, expectedPropertyNamesNotMyType, expectedMethodNamesNotMyType); } - private Compilation CreateCompilation(string source) + [Fact] + public void TypeDiscoveryWithRenamedAttribute() { - // Bypass System.Runtime error. - Assembly systemRuntimeAssembly = Assembly.Load("System.Runtime, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"); - string systemRuntimeAssemblyPath = systemRuntimeAssembly.Location; - - MetadataReference[] references = new MetadataReference[] { - MetadataReference.CreateFromFile(typeof(object).Assembly.Location), - MetadataReference.CreateFromFile(typeof(Attribute).Assembly.Location), - MetadataReference.CreateFromFile(typeof(JsonSerializableAttribute).Assembly.Location), - MetadataReference.CreateFromFile(typeof(JsonSerializerOptions).Assembly.Location), - MetadataReference.CreateFromFile(typeof(Type).Assembly.Location), - MetadataReference.CreateFromFile(typeof(KeyValuePair).Assembly.Location), - MetadataReference.CreateFromFile(systemRuntimeAssemblyPath), - }; - - return CSharpCompilation.Create( - "TestAssembly", - syntaxTrees: new[] { CSharpSyntaxTree.ParseText(source) }, - references: references, - options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) - ); - } + string source = @" + using System.Text.Json.Serialization; + using @JsonSerializable = System.ObsoleteAttribute; + using AliasedAttribute = System.Text.Json.Serialization.JsonSerializableAttribute; + + namespace HelloWorld + { + [AliasedAttribute] + public class MyType { + public int PublicPropertyInt { get; set; } + public string PublicPropertyString { get; set; } + private int PrivatePropertyInt { get; set; } + private string PrivatePropertyString { get; set; } - private GeneratorDriver CreateDriver(Compilation compilation, params ISourceGenerator[] generators) - => new CSharpGeneratorDriver( - new CSharpParseOptions(kind: SourceCodeKind.Regular, documentationMode: DocumentationMode.Parse), - ImmutableArray.Create(generators), - ImmutableArray.Empty); + public double PublicDouble; + public char PublicChar; + private double PrivateDouble; + private char PrivateChar; - private Compilation RunGenerators(Compilation compilation, out ImmutableArray diagnostics, params ISourceGenerator[] generators) + public void MyMethod() { } + public void MySecondMethod() { } + public void UsePrivates() + { + PrivateDouble = 0; + PrivateChar = ' '; + double d = PrivateDouble; + char c = PrivateChar; + } + } + + [AliasedAttribute(typeof(JsonConverterAttribute))] + public class NotMyType { } + + [@JsonSerializable(""Testing"", true)] + public class ShouldNotFind { } + + }"; + + Compilation compilation = CompilationHelper.CreateCompilation(source); + + JsonSerializerSourceGenerator generator = new JsonSerializerSourceGenerator(); + + Compilation newCompilation = CompilationHelper.RunGenerators(compilation, out var generatorDiags, generator); + + // Make sure compilation was successful. + Assert.Empty(generatorDiags); + Assert.Empty(newCompilation.GetDiagnostics()); + + // Check base functionality of found types. + Assert.Equal(2, generator.foundTypes.Count); + + // Check for MyType. + Assert.Equal("HelloWorld.MyType", generator.foundTypes["MyType"].FullName); + + // Check for received fields, properties and methods for MyType. + string[] expectedFieldNamesMyType = { "PrivateChar", "PrivateDouble", "PublicChar", "PublicDouble" }; + string[] expectedPropertyNamesMyType = { "PrivatePropertyInt", "PrivatePropertyString", "PublicPropertyInt", "PublicPropertyString" }; + string[] expectedMethodNamesMyType = { "get_PrivatePropertyInt", "get_PrivatePropertyString", "get_PublicPropertyInt", "get_PublicPropertyString", "MyMethod", "MySecondMethod", "set_PrivatePropertyInt", "set_PrivatePropertyString", "set_PublicPropertyInt", "set_PublicPropertyString", "UsePrivates" }; + CheckFieldsPropertiesMethods("MyType", ref generator, expectedFieldNamesMyType, expectedPropertyNamesMyType, expectedMethodNamesMyType); + + // Check for NotMyType. + Assert.Equal("System.Text.Json.Serialization.JsonConverterAttribute", generator.foundTypes["NotMyType"].FullName); + + // Check for received fields, properties and methods for NotMyType. + string[] expectedFieldNamesNotMyType = { }; + string[] expectedPropertyNamesNotMyType = { "ConverterType" }; + string[] expectedMethodNamesNotMyType = { "CreateConverter", "get_ConverterType" }; + CheckFieldsPropertiesMethods("NotMyType", ref generator, expectedFieldNamesNotMyType, expectedPropertyNamesNotMyType, expectedMethodNamesNotMyType ); + } + + private void CheckFieldsPropertiesMethods(string typeName, ref JsonSerializerSourceGenerator generator, string[] expectedFields, string[] expectedProperties, string[] expectedMethods) { - CreateDriver(compilation, generators).RunFullGeneration(compilation, out Compilation outCompilation, out diagnostics); - return outCompilation; + string[] receivedFields = generator.foundTypes[typeName].GetFields().Select(field => field.Name).OrderBy(s => s).ToArray(); + string[] receivedProperties = generator.foundTypes[typeName].GetProperties().Select(property => property.Name).OrderBy(s => s).ToArray(); + string[] receivedMethods = generator.foundTypes[typeName].GetMethods().Select(method => method.Name).OrderBy(s => s).ToArray(); + + Assert.Equal(expectedFields, receivedFields); + Assert.Equal(expectedProperties, receivedProperties); + Assert.Equal(expectedMethods, receivedMethods); } } } diff --git a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.UnitTests/System.Text.Json.SourceGeneration.UnitTests.csproj b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.UnitTests/System.Text.Json.SourceGeneration.UnitTests.csproj index 4dfa011d1375d..b8646203e16d4 100644 --- a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.UnitTests/System.Text.Json.SourceGeneration.UnitTests.csproj +++ b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.UnitTests/System.Text.Json.SourceGeneration.UnitTests.csproj @@ -1,4 +1,4 @@ - + $(NetCoreAppCurrent);$(NetFrameworkCurrent) @@ -12,6 +12,7 @@ + diff --git a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/JsonSerializableSyntaxReceiver.cs b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/JsonSerializableSyntaxReceiver.cs index 2260a2755dcb2..3b5d2cb8996fe 100644 --- a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/JsonSerializableSyntaxReceiver.cs +++ b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/JsonSerializableSyntaxReceiver.cs @@ -12,42 +12,14 @@ namespace System.Text.Json.SourceGeneration { public class JsonSerializableSyntaxReceiver : ISyntaxReceiver { - public List> ExternalClassTypes = new List>(); - public List> InternalClassTypes = new List>(); + public List TypesWithAttributes = new List(); public void OnVisitSyntaxNode(SyntaxNode syntaxNode) { - // Look for classes or structs for JsonSerializable Attribute. - if (syntaxNode is ClassDeclarationSyntax || syntaxNode is StructDeclarationSyntax) + if(syntaxNode is TypeDeclarationSyntax typeDeclarationNode + && typeDeclarationNode.AttributeLists.Count > 0) { - // Find JsonSerializable Attributes. - IEnumerable? serializableAttributes = null; - AttributeListSyntax attributeList = ((TypeDeclarationSyntax)syntaxNode).AttributeLists.SingleOrDefault(); - if (attributeList != null) - { - serializableAttributes = attributeList.Attributes.Where(node => (node is AttributeSyntax attr && attr.Name.ToString() == "JsonSerializable")).Cast(); - } - - if (serializableAttributes?.Any() == true) - { - // JsonSerializableAttribute has AllowMultiple as False, should only have 1 attribute. - Debug.Assert(serializableAttributes.Count() == 1); - AttributeSyntax attributeNode = serializableAttributes.First(); - - // Check if the attribute is being passed a type. - if (attributeNode.DescendantNodes().Where(node => node is TypeOfExpressionSyntax).Any()) - { - // Get JsonSerializable attribute arguments. - AttributeArgumentSyntax attributeArgumentNode = (AttributeArgumentSyntax)attributeNode.DescendantNodes().Where(node => node is AttributeArgumentSyntax).SingleOrDefault(); - // Get external class token from arguments. - IdentifierNameSyntax externalTypeNode = (IdentifierNameSyntax)attributeArgumentNode?.DescendantNodes().Where(node => node is IdentifierNameSyntax).SingleOrDefault(); - ExternalClassTypes.Add(new KeyValuePair(((TypeDeclarationSyntax)syntaxNode).Identifier.Text, externalTypeNode)); - } - else - { - InternalClassTypes.Add(new KeyValuePair(((TypeDeclarationSyntax)syntaxNode).Identifier.Text, (TypeDeclarationSyntax)syntaxNode)); - } - } + TypesWithAttributes.Add(typeDeclarationNode); } } } diff --git a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/JsonSourceGenerator.cs b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/JsonSourceGenerator.cs index 828f9e16595fd..453fd2ea7b502 100644 --- a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/JsonSourceGenerator.cs +++ b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/JsonSourceGenerator.cs @@ -24,33 +24,49 @@ public class JsonSerializerSourceGenerator : ISourceGenerator public void Execute(SourceGeneratorContext context) { JsonSerializableSyntaxReceiver receiver = (JsonSerializableSyntaxReceiver)context.SyntaxReceiver; - MetadataLoadContext metadataLoadContext = new MetadataLoadContext(context.Compilation); - INamedTypeSymbol namedTypeSymbol; - ITypeSymbol typeSymbol; - IdentifierNameSyntax identifierNameNode; - SemanticModel semanticModel; - Type convertedType; - TypeDeclarationSyntax typeDeclarationNode; + // Filter classes and structs with JsonSerializable attribute semantically. + INamedTypeSymbol jsonSerializableAttributeSymbol = context.Compilation.GetTypeByMetadataName("System.Text.Json.Serialization.JsonSerializableAttribute"); - // Map type name to type objects. - foreach (KeyValuePair entry in receiver.InternalClassTypes) + if (jsonSerializableAttributeSymbol == null) { - typeDeclarationNode = entry.Value; - semanticModel = context.Compilation.GetSemanticModel(typeDeclarationNode.SyntaxTree); - namedTypeSymbol = (INamedTypeSymbol)semanticModel.GetDeclaredSymbol(typeDeclarationNode); - convertedType = new TypeWrapper(namedTypeSymbol, metadataLoadContext); - foundTypes[entry.Key] = convertedType; + return; } - foreach (KeyValuePair entry in receiver.ExternalClassTypes) + // Find classes with JsonSerializable Attributes. + foreach (TypeDeclarationSyntax typeDeclarationNode in receiver.TypesWithAttributes) { - identifierNameNode = entry.Value; - semanticModel = context.Compilation.GetSemanticModel(identifierNameNode.SyntaxTree); - typeSymbol = context.Compilation.GetSemanticModel(identifierNameNode.SyntaxTree).GetTypeInfo(identifierNameNode).ConvertedType; - convertedType = new TypeWrapper(typeSymbol, metadataLoadContext); - foundTypes[entry.Key] = convertedType; + SemanticModel model = context.Compilation.GetSemanticModel(typeDeclarationNode.SyntaxTree); + + // Check if it contains a JsonSerializableAttribute. + INamedTypeSymbol typeSymbol = (INamedTypeSymbol)model.GetDeclaredSymbol(typeDeclarationNode); + if(typeSymbol.GetAttributes().Any(attr => attr.AttributeClass.Equals(jsonSerializableAttributeSymbol, SymbolEqualityComparer.Default))) + { + // JsonSerializableAttribute has AllowMultiple as False, should have a single attribute. + AttributeListSyntax attributeList = typeDeclarationNode.AttributeLists.Single(); + IEnumerable serializableAttributes = attributeList.Attributes.Where(node => node is AttributeSyntax).Cast(); + AttributeSyntax attributeNode = serializableAttributes.First(); + Debug.Assert(serializableAttributes.Count() == 1); + + // Check if the attribute is being passed a type. + if (attributeNode.DescendantNodes().Where(node => node is TypeOfExpressionSyntax).Any()) + { + // Get JsonSerializable attribute arguments. + AttributeArgumentSyntax attributeArgumentNode = (AttributeArgumentSyntax)attributeNode.DescendantNodes().Where(node => node is AttributeArgumentSyntax).SingleOrDefault(); + // Get external class token from arguments. + IdentifierNameSyntax externalTypeNode = (IdentifierNameSyntax)attributeArgumentNode?.DescendantNodes().Where(node => node is IdentifierNameSyntax).SingleOrDefault(); + + // Get non-user owned typeSymbol from IdentifierNameSyntax and add to found types. + ITypeSymbol externalTypeSymbol = model.GetTypeInfo(externalTypeNode).ConvertedType; + foundTypes[typeDeclarationNode.Identifier.Text] = new TypeWrapper(externalTypeSymbol, metadataLoadContext); + } + else + { + // Add user owned type into found types. + foundTypes[typeDeclarationNode.Identifier.Text] = new TypeWrapper(typeSymbol, metadataLoadContext); + } + } } // Create sources for all found types. @@ -98,7 +114,6 @@ public void Execute(SourceGeneratorContext context) member.Clear(); context.AddSource($"{entry.Key}ClassInfo", SourceText.From($@" -using System; using System.Collections.Generic; namespace HelloWorldGenerated