Skip to content

Commit

Permalink
Json source generation logic (#53)
Browse files Browse the repository at this point in the history
* Initial prototype using dfs and checking for cycles

* Polish debugging and fix tests

* Update tests and change accessibility for variables for sourcegenerator helper

* Add unit tests for diagnostics for users

* Move info logs to only successful generation

* Get rid of cycle check for next pr

* Get rid of stack logic for cycle checks

* Cache property values for typewrapper methods

* Fix nits and move diagnostics to another file

* Change to usage of GenerationClassFrame

* Changed source generated typeclass to be {assemblyname}{typename}

* Use struct instead of class for GenerationClassFrame
  • Loading branch information
kevinwkt authored Aug 20, 2020
1 parent 9e5cf8b commit 8a838e3
Show file tree
Hide file tree
Showing 14 changed files with 1,166 additions and 191 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,88 +5,223 @@
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
using JsonCodeGeneration;
using Xunit;

namespace System.Text.Json.SourceGeneration.Tests
{
[JsonSerializable]
public class Location
{
public int Id { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string PostalCode { get; set; }
public string Name { get; set; }
public string PhoneNumber { get; set; }
public string Country { get; set; }
}

[JsonSerializable]
public class ActiveOrUpcomingEvent
{
public int Id { get; set; }
public string ImageUrl { get; set; }
public string Name { get; set; }
public string CampaignName { get; set; }
public string CampaignManagedOrganizerName { get; set; }
public string Description { get; set; }
public DateTimeOffset StartDate { get; set; }
public DateTimeOffset EndDate { get; set; }
}

[JsonSerializable]
public class CampaignSummaryViewModel
{
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string ImageUrl { get; set; }
public string OrganizationName { get; set; }
public string Headline { get; set; }
}

[JsonSerializable]
public class IndexViewModel
{
public List<ActiveOrUpcomingEvent> ActiveOrUpcomingEvents { get; set; }
public CampaignSummaryViewModel FeaturedCampaign { get; set; }
public bool IsNewAccount { get; set; }
public bool HasFeaturedCampaign => FeaturedCampaign != null;
}

public class JsonSerializerSourceGeneratorTests
{
[JsonSerializable]
public class SampleInternalTest
[Fact]
public static void RoundTripLocation()
{
public char PublicCharField;
private string PrivateStringField;
public int PublicIntPropertyPublic { get; set; }
public int PublicIntPropertyPrivateSet { get; private set; }
public int PublicIntPropertyPrivateGet { private get; set; }
Location expected = CreateLocation();

public SampleInternalTest()
{
PublicCharField = 'a';
PrivateStringField = "privateStringField";
}
string json = JsonSerializer.Serialize(expected, JsonContext.Instance.SystemTextJsonSourceGenerationTestsLocation);
Location obj = JsonSerializer.Deserialize(json, JsonContext.Instance.SystemTextJsonSourceGenerationTestsLocation);

public SampleInternalTest(char c, string s)
{
PublicCharField = c;
PrivateStringField = s;
}
VerifyLocation(expected, obj);
}

private SampleInternalTest(int i)
{
PublicIntPropertyPublic = i;
}
[Fact]
public void RoundTripIndexViewModel()
{
IndexViewModel expected = CreateIndexViewModel();

private void UseFields()
{
string use = PublicCharField.ToString() + PrivateStringField;
}
string json = JsonSerializer.Serialize(expected, JsonContext.Instance.SystemTextJsonSourceGenerationTestsIndexViewModel);
IndexViewModel obj = JsonSerializer.Deserialize(json, JsonContext.Instance.SystemTextJsonSourceGenerationTestsIndexViewModel);

VerifyIndexViewModel(expected, obj);
}

[JsonSerializable(typeof(JsonConverterAttribute))]
public class SampleExternalTest { }
[Fact]
public static void RoundTripCampaignSummaryViewModel()
{
CampaignSummaryViewModel expected = CreateCampaignSummaryViewModel();

string json = JsonSerializer.Serialize(expected, JsonContext.Instance.SystemTextJsonSourceGenerationTestsCampaignSummaryViewModel);
CampaignSummaryViewModel obj = JsonSerializer.Deserialize(json, JsonContext.Instance.SystemTextJsonSourceGenerationTestsCampaignSummaryViewModel);

VerifyCampaignSummaryViewModel(expected, obj);
}

[Fact]
public void TestGeneratedCode()
public static void RoundTripActiveOrUpcomingEvent()
{
var internalTypeTest = new HelloWorldGenerated.SampleInternalTestClassInfo();
var externalTypeTest = new HelloWorldGenerated.SampleExternalTestClassInfo();
ActiveOrUpcomingEvent expected = CreateActiveOrUpcomingEvent();

// Check base class names.
Assert.Equal("SampleInternalTestClassInfo", internalTypeTest.GetClassName());
Assert.Equal("SampleExternalTestClassInfo", externalTypeTest.GetClassName());
string json = JsonSerializer.Serialize(expected, JsonContext.Instance.SystemTextJsonSourceGenerationTestsActiveOrUpcomingEvent);
ActiveOrUpcomingEvent obj = JsonSerializer.Deserialize(json, JsonContext.Instance.SystemTextJsonSourceGenerationTestsActiveOrUpcomingEvent);

// Public and private Ctors are visible.
Assert.Equal(3, internalTypeTest.Ctors.Count);
Assert.Equal(2, externalTypeTest.Ctors.Count);
VerifyActiveOrUpcomingEvent(expected, obj);
}

// Ctor params along with its types are visible.
Dictionary<string, string> expectedCtorParamsInternal = new Dictionary<string, string> { { "c", "Char"}, { "s", "String" }, { "i", "Int32" } };
Assert.Equal(expectedCtorParamsInternal, internalTypeTest.CtorParams);
internal static Location CreateLocation()
{
return new Location
{
Id = 1234,
Address1 = "The Street Name",
Address2 = "20/11",
City = "The City",
State = "The State",
PostalCode = "abc-12",
Name = "Nonexisting",
PhoneNumber = "+0 11 222 333 44",
Country = "The Greatest"
};
}

Dictionary<string, string> expectedCtorParamsExternal = new Dictionary<string, string> { { "converterType", "Type"} };
Assert.Equal(expectedCtorParamsExternal, externalTypeTest.CtorParams);
internal static void VerifyLocation(Location expected, Location obj)
{
Assert.Equal(expected.Address1, obj.Address1);
Assert.Equal(expected.Address2, obj.Address2);
Assert.Equal(expected.City, obj.City);
Assert.Equal(expected.State, obj.State);
Assert.Equal(expected.PostalCode, obj.PostalCode);
Assert.Equal(expected.Name, obj.Name);
Assert.Equal(expected.PhoneNumber, obj.PhoneNumber);
Assert.Equal(expected.Country, obj.Country);
}

internal static ActiveOrUpcomingEvent CreateActiveOrUpcomingEvent()
{
return new ActiveOrUpcomingEvent
{
Id = 10,
CampaignManagedOrganizerName = "Name FamiltyName",
CampaignName = "The very new campaing",
Description = "The .NET Foundation works with Microsoft and the broader industry to increase the exposure of open source projects in the .NET community and the .NET Foundation. The .NET Foundation provides access to these resources to projects and looks to promote the activities of our communities.",
EndDate = DateTime.UtcNow.AddYears(1),
Name = "Just a name",
ImageUrl = "https://www.dotnetfoundation.org/theme/img/carousel/foundation-diagram-content.png",
StartDate = DateTime.UtcNow
};
}

// Public and private methods are visible.
List<string> expectedMethodsInternal = new List<string> { "get_PublicIntPropertyPrivateGet", "get_PublicIntPropertyPrivateSet", "get_PublicIntPropertyPublic", "set_PublicIntPropertyPrivateGet", "set_PublicIntPropertyPrivateSet", "set_PublicIntPropertyPublic", "UseFields" };
Assert.Equal(expectedMethodsInternal, internalTypeTest.Methods.OrderBy(s => s).ToList());
internal static void VerifyActiveOrUpcomingEvent(ActiveOrUpcomingEvent expected, ActiveOrUpcomingEvent obj)
{
Assert.Equal(expected.CampaignManagedOrganizerName, obj.CampaignManagedOrganizerName);
Assert.Equal(expected.CampaignName, obj.CampaignName);
Assert.Equal(expected.Description, obj.Description);
Assert.Equal(expected.EndDate, obj.EndDate);
Assert.Equal(expected.Id, obj.Id);
Assert.Equal(expected.ImageUrl, obj.ImageUrl);
Assert.Equal(expected.Name, obj.Name);
Assert.Equal(expected.StartDate, obj.StartDate);
}

List<string> expectedMethodsExternal = new List<string> { "CreateConverter", "get_ConverterType" };
Assert.Equal(expectedMethodsExternal, externalTypeTest.Methods.OrderBy(s => s).ToList());
internal static CampaignSummaryViewModel CreateCampaignSummaryViewModel()
{
return new CampaignSummaryViewModel
{
Description = "Very nice campaing",
Headline = "The Headline",
Id = 234235,
OrganizationName = "The Company XYZ",
ImageUrl = "https://www.dotnetfoundation.org/theme/img/carousel/foundation-diagram-content.png",
Title = "Promoting Open Source"
};
}

// Public and private fields are visible.
Dictionary<string, string> expectedFieldsInternal = new Dictionary<string, string> { { "PublicCharField", "Char" }, { "PrivateStringField", "String" } };
Assert.Equal(expectedFieldsInternal, internalTypeTest.Fields);
internal static void VerifyCampaignSummaryViewModel(CampaignSummaryViewModel expected, CampaignSummaryViewModel obj)
{
Assert.Equal(expected.Description, obj.Description);
Assert.Equal(expected.Headline, obj.Headline);
Assert.Equal(expected.Id, obj.Id);
Assert.Equal(expected.ImageUrl, obj.ImageUrl);
Assert.Equal(expected.OrganizationName, obj.OrganizationName);
Assert.Equal(expected.Title, obj.Title);
}

Dictionary<string, string> expectedFieldsExternal = new Dictionary<string, string> { };
Assert.Equal(expectedFieldsExternal, externalTypeTest.Fields);
internal static IndexViewModel CreateIndexViewModel()
{
return new IndexViewModel
{
IsNewAccount = false,
FeaturedCampaign = new CampaignSummaryViewModel
{
Description = "Very nice campaing",
Headline = "The Headline",
Id = 234235,
OrganizationName = "The Company XYZ",
ImageUrl = "https://www.dotnetfoundation.org/theme/img/carousel/foundation-diagram-content.png",
Title = "Promoting Open Source"
},
ActiveOrUpcomingEvents = Enumerable.Repeat(
new ActiveOrUpcomingEvent
{
Id = 10,
CampaignManagedOrganizerName = "Name FamiltyName",
CampaignName = "The very new campaing",
Description = "The .NET Foundation works with Microsoft and the broader industry to increase the exposure of open source projects in the .NET community and the .NET Foundation. The .NET Foundation provides access to these resources to projects and looks to promote the activities of our communities.",
EndDate = DateTime.UtcNow.AddYears(1),
Name = "Just a name",
ImageUrl = "https://www.dotnetfoundation.org/theme/img/carousel/foundation-diagram-content.png",
StartDate = DateTime.UtcNow
},
count: 20).ToList()
};
}

// Public properties are visible.
Dictionary<string, string> expectedPropertiesInternal = new Dictionary<string, string> { { "PublicIntPropertyPublic", "Int32" }, { "PublicIntPropertyPrivateSet", "Int32" }, { "PublicIntPropertyPrivateGet", "Int32" } };
Assert.Equal(expectedPropertiesInternal, internalTypeTest.Properties);
internal static void VerifyIndexViewModel(IndexViewModel expected, IndexViewModel obj)
{
Assert.Equal(expected.ActiveOrUpcomingEvents.Count, obj.ActiveOrUpcomingEvents.Count);
for (int i = 0; i < expected.ActiveOrUpcomingEvents.Count; i++)
{
VerifyActiveOrUpcomingEvent(expected.ActiveOrUpcomingEvents[i], obj.ActiveOrUpcomingEvents[i]);
}

Dictionary<string, string> expectedPropertiesExternal = new Dictionary<string, string> { { "ConverterType", "Type"} };
Assert.Equal(expectedPropertiesExternal, externalTypeTest.Properties);
VerifyCampaignSummaryViewModel(expected.FeaturedCampaign, obj.FeaturedCampaign);
Assert.Equal(expected.HasFeaturedCampaign, obj.HasFeaturedCampaign);
Assert.Equal(expected.IsNewAccount, obj.IsNewAccount);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Reflection;
using System.Text.Json.Serialization;
using Microsoft.CodeAnalysis;
Expand Down Expand Up @@ -57,5 +58,78 @@ public static Compilation RunGenerators(Compilation compilation, out ImmutableAr
CreateDriver(compilation, generators).RunFullGeneration(compilation, out Compilation outCompilation, out diagnostics);
return outCompilation;
}

public static byte[] CreateAssemblyImage(Compilation compilation)
{
MemoryStream ms = new MemoryStream();
var emitResult = compilation.Emit(ms);
if (!emitResult.Success)
{
throw new InvalidOperationException();
}
return ms.ToArray();
}

public static Compilation CreateReferencedLocationCompilation()
{
string source = @"
namespace ReferencedAssembly
{
public class Location
{
public int Id { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string PostalCode { get; set; }
public string Name { get; set; }
public string PhoneNumber { get; set; }
public string Country { get; set; }
}
}";

return CreateCompilation(source);
}

public static Compilation CreateCampaignSummaryViewModelCompilation()
{
string source = @"
namespace ReferencedAssembly
{
public class CampaignSummaryViewModel
{
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string ImageUrl { get; set; }
public string OrganizationName { get; set; }
public string Headline { get; set; }
}
}";

return CreateCompilation(source);
}

public static Compilation CreateActiveOrUpcomingEventCompilation()
{
string source = @"
namespace ReferencedAssembly
{
public class ActiveOrUpcomingEvent
{
public int Id { get; set; }
public string ImageUrl { get; set; }
public string Name { get; set; }
public string CampaignName { get; set; }
public string CampaignManagedOrganizerName { get; set; }
public string Description { get; set; }
public DateTimeOffset StartDate { get; set; }
public DateTimeOffset EndDate { get; set; }
}
}";

return CreateCompilation(source);
}
}
}
Loading

0 comments on commit 8a838e3

Please sign in to comment.