From 93d8a93498963e019e99c10aa24275e1a483811d Mon Sep 17 00:00:00 2001 From: "N. Taylor Mullen" Date: Tue, 22 May 2018 15:55:54 -0700 Subject: [PATCH] Enable serialization of `TagHelperDescriptor`s. - Added ability for TagHelpers to write their own json output. --- .../TagHelperDescriptorJsonConverter.cs | 165 +++++++++++++++++- .../TagHelperDescriptorSerializationTest.cs | 54 ++++++ 2 files changed, 214 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/TagHelperDescriptorJsonConverter.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/TagHelperDescriptorJsonConverter.cs index 72b4f98d24..0d7d59dfc7 100644 --- a/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/TagHelperDescriptorJsonConverter.cs +++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/TagHelperDescriptorJsonConverter.cs @@ -13,8 +13,6 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Serialization { public static readonly TagHelperDescriptorJsonConverter Instance = new TagHelperDescriptorJsonConverter(); - public override bool CanWrite => false; - public override bool CanConvert(Type objectType) { return typeof(TagHelperDescriptor).IsAssignableFrom(objectType); @@ -81,9 +79,166 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Serialization public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { - // We should never get here because CanWrite returns false. - // We want the default serializer to handle TagHelperDescriptor serialization. - throw new NotImplementedException(); + var tagHelper = (TagHelperDescriptor)value; + + writer.WriteStartObject(); + + writer.WritePropertyName(nameof(TagHelperDescriptor.Kind)); + writer.WriteValue(tagHelper.Kind); + + writer.WritePropertyName(nameof(TagHelperDescriptor.Name)); + writer.WriteValue(tagHelper.Name); + + writer.WritePropertyName(nameof(TagHelperDescriptor.AssemblyName)); + writer.WriteValue(tagHelper.AssemblyName); + + writer.WritePropertyName(nameof(TagHelperDescriptor.Documentation)); + writer.WriteValue(tagHelper.Documentation); + + writer.WritePropertyName(nameof(TagHelperDescriptor.TagOutputHint)); + writer.WriteValue(tagHelper.TagOutputHint); + + writer.WritePropertyName(nameof(TagHelperDescriptor.TagMatchingRules)); + writer.WriteStartArray(); + foreach (var ruleDescriptor in tagHelper.TagMatchingRules) + { + WriteTagMatchingRule(writer, ruleDescriptor, serializer); + } + writer.WriteEndArray(); + + writer.WritePropertyName(nameof(TagHelperDescriptor.BoundAttributes)); + writer.WriteStartArray(); + foreach (var boundAttribute in tagHelper.BoundAttributes) + { + WriteBoundAttribute(writer, boundAttribute, serializer); + } + writer.WriteEndArray(); + + writer.WritePropertyName(nameof(TagHelperDescriptor.AllowedChildTags)); + writer.WriteStartArray(); + foreach (var allowedChildTag in tagHelper.AllowedChildTags) + { + WriteAllowedChildTags(writer, allowedChildTag, serializer); + } + writer.WriteEndArray(); + + writer.WritePropertyName(nameof(TagHelperDescriptor.Diagnostics)); + serializer.Serialize(writer, tagHelper.Diagnostics); + + writer.WritePropertyName(nameof(TagHelperDescriptor.Metadata)); + WriteMetadata(writer, tagHelper.Metadata); + + writer.WriteEndObject(); + } + + private void WriteAllowedChildTags(JsonWriter writer, AllowedChildTagDescriptor allowedChildTag, JsonSerializer serializer) + { + writer.WriteStartObject(); + + writer.WritePropertyName(nameof(AllowedChildTagDescriptor.Name)); + writer.WriteValue(allowedChildTag.Name); + + writer.WritePropertyName(nameof(AllowedChildTagDescriptor.DisplayName)); + writer.WriteValue(allowedChildTag.DisplayName); + + writer.WritePropertyName(nameof(AllowedChildTagDescriptor.Diagnostics)); + serializer.Serialize(writer, allowedChildTag.Diagnostics); + + writer.WriteEndObject(); + } + + private void WriteBoundAttribute(JsonWriter writer, BoundAttributeDescriptor boundAttribute, JsonSerializer serializer) + { + writer.WriteStartObject(); + + writer.WritePropertyName(nameof(BoundAttributeDescriptor.Kind)); + writer.WriteValue(boundAttribute.Kind); + + writer.WritePropertyName(nameof(BoundAttributeDescriptor.Name)); + writer.WriteValue(boundAttribute.Name); + + writer.WritePropertyName(nameof(BoundAttributeDescriptor.TypeName)); + writer.WriteValue(boundAttribute.TypeName); + + writer.WritePropertyName(nameof(BoundAttributeDescriptor.IsEnum)); + writer.WriteValue(boundAttribute.IsEnum); + + writer.WritePropertyName(nameof(BoundAttributeDescriptor.IndexerNamePrefix)); + writer.WriteValue(boundAttribute.IndexerNamePrefix); + + writer.WritePropertyName(nameof(BoundAttributeDescriptor.IndexerTypeName)); + writer.WriteValue(boundAttribute.IndexerTypeName); + + writer.WritePropertyName(nameof(BoundAttributeDescriptor.Documentation)); + writer.WriteValue(boundAttribute.Documentation); + + writer.WritePropertyName(nameof(BoundAttributeDescriptor.Diagnostics)); + serializer.Serialize(writer, boundAttribute.Diagnostics); + + writer.WritePropertyName(nameof(BoundAttributeDescriptor.Metadata)); + WriteMetadata(writer, boundAttribute.Metadata); + + writer.WriteEndObject(); + } + + private static void WriteMetadata(JsonWriter writer, IReadOnlyDictionary metadata) + { + writer.WriteStartObject(); + foreach (var kvp in metadata) + { + writer.WritePropertyName(kvp.Key); + writer.WriteValue(kvp.Value); + } + writer.WriteEndObject(); + } + + private void WriteTagMatchingRule(JsonWriter writer, TagMatchingRuleDescriptor ruleDescriptor, JsonSerializer serializer) + { + writer.WriteStartObject(); + + writer.WritePropertyName(nameof(TagMatchingRuleDescriptor.TagName)); + writer.WriteValue(ruleDescriptor.TagName); + + writer.WritePropertyName(nameof(TagMatchingRuleDescriptor.ParentTag)); + writer.WriteValue(ruleDescriptor.ParentTag); + + writer.WritePropertyName(nameof(TagMatchingRuleDescriptor.TagStructure)); + writer.WriteValue(ruleDescriptor.TagStructure); + + writer.WritePropertyName(nameof(TagMatchingRuleDescriptor.Attributes)); + writer.WriteStartArray(); + foreach (var requiredAttribute in ruleDescriptor.Attributes) + { + WriteRequiredAttribute(writer, requiredAttribute, serializer); + } + writer.WriteEndArray(); + + writer.WritePropertyName(nameof(TagMatchingRuleDescriptor.Diagnostics)); + serializer.Serialize(writer, ruleDescriptor.Diagnostics); + + writer.WriteEndObject(); + } + + private void WriteRequiredAttribute(JsonWriter writer, RequiredAttributeDescriptor requiredAttribute, JsonSerializer serializer) + { + writer.WriteStartObject(); + + writer.WritePropertyName(nameof(RequiredAttributeDescriptor.Name)); + writer.WriteValue(requiredAttribute.Name); + + writer.WritePropertyName(nameof(RequiredAttributeDescriptor.NameComparison)); + writer.WriteValue(requiredAttribute.NameComparison); + + writer.WritePropertyName(nameof(RequiredAttributeDescriptor.Value)); + writer.WriteValue(requiredAttribute.Value); + + writer.WritePropertyName(nameof(RequiredAttributeDescriptor.ValueComparison)); + writer.WriteValue(requiredAttribute.ValueComparison); + + writer.WritePropertyName(nameof(RequiredAttributeDescriptor.Diagnostics)); + serializer.Serialize(writer, requiredAttribute.Diagnostics); + + writer.WriteEndObject(); } private void ReadTagMatchingRule(TagMatchingRuleDescriptorBuilder builder, JObject rule, JsonSerializer serializer) diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Serialization/TagHelperDescriptorSerializationTest.cs b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Serialization/TagHelperDescriptorSerializationTest.cs index 9427f77e32..fd4b9ba894 100644 --- a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Serialization/TagHelperDescriptorSerializationTest.cs +++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Serialization/TagHelperDescriptorSerializationTest.cs @@ -7,12 +7,66 @@ using Microsoft.AspNetCore.Mvc.Razor.Extensions; using Microsoft.AspNetCore.Razor.Language; using Microsoft.VisualStudio.LanguageServices.Razor.Serialization; using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; using Xunit; namespace Microsoft.VisualStudio.LanguageServices.Razor { public class TagHelperDescriptorSerializationTest { + [Fact] + public void TagHelperDescriptor_CanReadCamelCasedData() + { + // Arrange + var expectedDescriptor = CreateTagHelperDescriptor( + kind: TagHelperConventions.DefaultKind, + tagName: "tag-name", + typeName: "type name", + assemblyName: "assembly name", + attributes: new Action[] + { + builder => builder + .Name("test-attribute") + .PropertyName("TestAttribute") + .TypeName("string"), + }, + ruleBuilders: new Action[] + { + builder => builder + .RequireAttributeDescriptor(attribute => attribute + .Name("required-attribute-one") + .NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode.PrefixMatch)) + .RequireAttributeDescriptor(attribute => attribute + .Name("required-attribute-two") + .NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode.FullMatch) + .Value("something") + .ValueComparisonMode(RequiredAttributeDescriptor.ValueComparisonMode.PrefixMatch)) + .RequireParentTag("parent-name") + .RequireTagStructure(TagStructure.WithoutEndTag), + }, + configureAction: builder => + { + builder.AllowChildTag("allowed-child-one"); + builder.AddMetadata("foo", "bar"); + }); + var serializerSettings = new JsonSerializerSettings() + { + ContractResolver = new CamelCasePropertyNamesContractResolver(), + Converters = new List + { + TagHelperDescriptorJsonConverter.Instance, + RazorDiagnosticJsonConverter.Instance, + } + }; + var serializedDescriptor = JsonConvert.SerializeObject(expectedDescriptor, serializerSettings); + + // Act + var descriptor = JsonConvert.DeserializeObject(serializedDescriptor, TagHelperDescriptorJsonConverter.Instance, RazorDiagnosticJsonConverter.Instance); + + // Assert + Assert.Equal(expectedDescriptor, descriptor, TagHelperDescriptorComparer.Default); + } + [Fact] public void TagHelperDescriptor_RoundTripsProperly() {