Skip to content

Commit

Permalink
Merge pull request #852 from prochnowc/static-external-model
Browse files Browse the repository at this point in the history
Add support for including types defined in another assembly in static code generation

+semver:feature
  • Loading branch information
EdwardCooke committed Oct 5, 2023
2 parents 1a73db7 + d63f33f commit 0b8f32b
Show file tree
Hide file tree
Showing 9 changed files with 120 additions and 57 deletions.
121 changes: 69 additions & 52 deletions YamlDotNet.Analyzers.StaticGenerator/ClassSyntaxReceiver.cs
Expand Up @@ -44,69 +44,86 @@ public void OnVisitSyntaxNode(GeneratorSyntaxContext context)
if (attributes.Any(attribute => attribute.AttributeClass?.ToDisplayString() == "YamlDotNet.Serialization.YamlStaticContextAttribute"))
{
YamlStaticContextType = classSymbol;

var types =
attributes.Where(attribute => attribute.AttributeClass?.ToDisplayString() == "YamlDotNet.Serialization.YamlSerializableAttribute"
&& attribute.ConstructorArguments.Any(argument => argument.Type?.ToDisplayString() == "System.Type"))
.Select(attribute => attribute.ConstructorArguments.First().Value)
.ToArray();

foreach (var type in types.OfType<INamedTypeSymbol>())
{
AddSerializableClass(type);
}
}

if (classSymbol.GetAttributes().Any(attribute => attribute.AttributeClass?.ToDisplayString() == "YamlDotNet.Serialization.YamlSerializableAttribute"))
if (classSymbol.GetAttributes().Any(attribute => attribute.AttributeClass?.ToDisplayString() == "YamlDotNet.Serialization.YamlSerializableAttribute"
&& attribute.ConstructorArguments.Length == 0))
{
ClassObject classObject;
var className = SanitizeName(classSymbol.GetFullName());
if (Classes.ContainsKey(className))
AddSerializableClass(classSymbol);
}
}
}
}

private void AddSerializableClass(INamedTypeSymbol? classSymbol)
{
ClassObject classObject;
var className = SanitizeName(classSymbol!.GetFullName());
if (Classes.ContainsKey(className))
{
classObject = Classes[className];
}
else
{
classObject = new ClassObject(className, classSymbol!);
Classes[className] = classObject;
}
while (classSymbol != null)
{
var members = classSymbol.GetMembers();
foreach (var member in members)
{
if (member.IsStatic ||
(member.DeclaredAccessibility != Accessibility.Public &&
member.DeclaredAccessibility != Accessibility.Internal) ||
member.GetAttributes().Any(x => x.AttributeClass!.ToDisplayString() == "YamlDotNet.Serialization.YamlIgnoreAttribute"))
{
continue;
}

if (member is IPropertySymbol propertySymbol)
{
classObject.PropertySymbols.Add(propertySymbol);
CheckForSupportedGeneric(propertySymbol.Type);
}
else if (member is IFieldSymbol fieldSymbol)
{
classObject.FieldSymbols.Add(fieldSymbol);
CheckForSupportedGeneric(fieldSymbol.Type);
}
else if (member is IMethodSymbol methodSymbol)
{
var methodAttributes = methodSymbol.GetAttributes();
if (methodAttributes.Any(x => x.AttributeClass!.ToDisplayString() == "YamlDotNet.Serialization.Callbacks.OnDeserializedAttribute"))
{
classObject = Classes[className];
classObject.OnDeserializedMethods.Add(methodSymbol);
}
else
if (methodAttributes.Any(x => x.AttributeClass!.ToDisplayString() == "YamlDotNet.Serialization.Callbacks.OnDeserializingAttribute"))
{
classObject = new ClassObject(className, classSymbol);
Classes[className] = classObject;
classObject.OnDeserializingMethods.Add(methodSymbol);
}
while (classSymbol != null)
if (methodAttributes.Any(x => x.AttributeClass!.ToDisplayString() == "YamlDotNet.Serialization.Callbacks.OnSerializedAttribute"))
{
var members = classSymbol.GetMembers();
foreach (var member in members)
{
if (member.IsStatic ||
(member.DeclaredAccessibility != Accessibility.Public &&
member.DeclaredAccessibility != Accessibility.Internal) ||
member.GetAttributes().Any(x => x.AttributeClass!.ToDisplayString() == "YamlDotNet.Serialization.YamlIgnoreAttribute"))
{
continue;
}

if (member is IPropertySymbol propertySymbol)
{
classObject.PropertySymbols.Add(propertySymbol);
CheckForSupportedGeneric(propertySymbol.Type);
}
else if (member is IFieldSymbol fieldSymbol)
{
classObject.FieldSymbols.Add(fieldSymbol);
CheckForSupportedGeneric(fieldSymbol.Type);
}
else if (member is IMethodSymbol methodSymbol)
{
var methodAttributes = methodSymbol.GetAttributes();
if (methodAttributes.Any(x => x.AttributeClass!.ToDisplayString() == "YamlDotNet.Serialization.Callbacks.OnDeserializedAttribute"))
{
classObject.OnDeserializedMethods.Add(methodSymbol);
}
if (methodAttributes.Any(x => x.AttributeClass!.ToDisplayString() == "YamlDotNet.Serialization.Callbacks.OnDeserializingAttribute"))
{
classObject.OnDeserializingMethods.Add(methodSymbol);
}
if (methodAttributes.Any(x => x.AttributeClass!.ToDisplayString() == "YamlDotNet.Serialization.Callbacks.OnSerializedAttribute"))
{
classObject.OnSerializedMethods.Add(methodSymbol);
}
if (methodAttributes.Any(x => x.AttributeClass!.ToDisplayString() == "YamlDotNet.Serialization.Callbacks.OnSerializingAttribute"))
{
classObject.OnSerializingMethods.Add(methodSymbol);
}
}
}
classSymbol = classSymbol.BaseType;
classObject.OnSerializedMethods.Add(methodSymbol);
}
if (methodAttributes.Any(x => x.AttributeClass!.ToDisplayString() == "YamlDotNet.Serialization.Callbacks.OnSerializingAttribute"))
{
classObject.OnSerializingMethods.Add(methodSymbol);
}
}
}
classSymbol = classSymbol.BaseType;
}
}

Expand Down
Expand Up @@ -21,7 +21,6 @@

using System;
using System.Text;
using System.Xml;
using Microsoft.CodeAnalysis;

namespace YamlDotNet.Analyzers.StaticGenerator
Expand Down
6 changes: 6 additions & 0 deletions YamlDotNet.Core7AoTCompileTest.Model/ExternalModel.cs
@@ -0,0 +1,6 @@
namespace YamlDotNet.Core7AoTCompileTest.Model;

public class ExternalModel
{
public string? Text { get; set; }
}
@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net70</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<Import Project="../build/common.props" />

</Project>
10 changes: 8 additions & 2 deletions YamlDotNet.Core7AoTCompileTest/Program.cs
Expand Up @@ -24,12 +24,14 @@

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using YamlDotNet.Core;
using YamlDotNet.Core7AoTCompileTest.Model;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.Callbacks;

string yaml = $@"MyBool: true
string yaml = string.Create(CultureInfo.InvariantCulture, $@"MyBool: true
hi: 1
MyChar: h
MyDateTime: {DateTime.Now}
Expand Down Expand Up @@ -65,7 +67,9 @@
Inherited:
Inherited: hello
NotInherited: world
";
External:
Text: hello
");

var input = new StringReader(yaml);

Expand All @@ -91,6 +95,7 @@
Console.WriteLine("MyUInt64: <{0}>", x.MyUInt64);
Console.WriteLine("Inner == null: <{0}>", x.Inner == null);
Console.WriteLine("Inner.Text: <{0}>", x.Inner?.Text);
Console.WriteLine("External.Text: <{0}>", x.External?.Text);
foreach (var inner in x.InnerArray)
{
Console.WriteLine("InnerArray.Text: <{0}>", inner.Text);
Expand Down Expand Up @@ -188,6 +193,7 @@ public class PrimitiveTypes
public Dictionary<string, string>? MyDictionary { get; set; }
public List<string>? MyList { get; set; }
public Inherited Inherited { get; set; }
public ExternalModel External { get; set; }
}

public class InheritedBase
Expand Down
2 changes: 2 additions & 0 deletions YamlDotNet.Core7AoTCompileTest/StaticAoTContext.cs
Expand Up @@ -19,12 +19,14 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

using YamlDotNet.Core7AoTCompileTest.Model;
using YamlDotNet.Serialization;

namespace YamlDotNet.Core7AoTCompileTest
{
// The rest of this partial class gets generated at build time
[YamlStaticContext]
[YamlSerializable(typeof(ExternalModel))]
public partial class StaticContext : YamlDotNet.Serialization.StaticContext
{
}
Expand Down
Expand Up @@ -25,6 +25,7 @@
<ProjectReference Include="..\YamlDotNet.Analyzers.StaticGenerator\YamlDotNet.Analyzers.StaticGenerator.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
<ProjectReference Include="..\YamlDotNet.Core7AoTCompileTest.Model\YamlDotNet.Core7AoTCompileTest.Model.csproj" />
<ProjectReference Include="..\YamlDotNet\YamlDotNet.csproj" />
</ItemGroup>

Expand Down
6 changes: 6 additions & 0 deletions YamlDotNet.sln
Expand Up @@ -31,6 +31,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YamlDotNet.Core7AoTCompileT
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "YamlDotNet.Samples.Fsharp", "YamlDotNet.Samples.Fsharp\YamlDotNet.Samples.Fsharp.fsproj", "{C047392D-6B20-47CD-9FE6-D0FA326FD262}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YamlDotNet.Core7AoTCompileTest.Model", "YamlDotNet.Core7AoTCompileTest.Model\YamlDotNet.Core7AoTCompileTest.Model.csproj", "{BFE15564-7C2C-47DA-8302-9BCB39B6864B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -69,6 +71,10 @@ Global
{C047392D-6B20-47CD-9FE6-D0FA326FD262}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C047392D-6B20-47CD-9FE6-D0FA326FD262}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C047392D-6B20-47CD-9FE6-D0FA326FD262}.Release|Any CPU.Build.0 = Release|Any CPU
{BFE15564-7C2C-47DA-8302-9BCB39B6864B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BFE15564-7C2C-47DA-8302-9BCB39B6864B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BFE15564-7C2C-47DA-8302-9BCB39B6864B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BFE15564-7C2C-47DA-8302-9BCB39B6864B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
19 changes: 17 additions & 2 deletions YamlDotNet/Serialization/YamlSerializable.cs
Expand Up @@ -24,10 +24,25 @@
namespace YamlDotNet.Serialization
{
/// <summary>
/// Put this attribute on classes that you want the static analyzer to detect and use.
/// Put this attribute either on serializable types or on the <see cref="StaticContext"/> that you want
/// the static analyzer to detect and use.
/// </summary>
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class YamlSerializableAttribute : Attribute
{
/// <summary>
/// Use this constructor if the attribute is placed on a serializable class.
/// </summary>
public YamlSerializableAttribute()
{
}

/// <summary>
/// Use this constructor if the attribute is placed on the <see cref="StaticContext"/>.
/// </summary>
/// <param name="serializableType">The type for which to include static code generation.</param>
public YamlSerializableAttribute(Type serializableType)
{
}
}
}

0 comments on commit 0b8f32b

Please sign in to comment.