diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/BoundAttributeDescriptorComparer.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/BoundAttributeDescriptorComparer.cs index 54154a7aaa..4b3ae0503c 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/BoundAttributeDescriptorComparer.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/BoundAttributeDescriptorComparer.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -56,10 +56,20 @@ namespace Microsoft.AspNetCore.Razor.Language } var hash = HashCodeCombiner.Start(); - hash.Add(descriptor.Kind); + hash.Add(descriptor.Kind, StringComparer.Ordinal); hash.Add(descriptor.Name, StringComparer.Ordinal); + if (descriptor.BoundAttributeParameters != null) + { + for (var i = 0; i < descriptor.BoundAttributeParameters.Count; i++) + { + hash.Add(descriptor.BoundAttributeParameters[i]); + } + } + + hash.Add(descriptor.Metadata.Count); + return hash.CombinedHash; } } -} \ No newline at end of file +} diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/BoundAttributeParameterDescriptorComparer.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/BoundAttributeParameterDescriptorComparer.cs index 6cbc01017c..8896c341d3 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/BoundAttributeParameterDescriptorComparer.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/BoundAttributeParameterDescriptorComparer.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -51,8 +51,10 @@ namespace Microsoft.AspNetCore.Razor.Language } var hash = HashCodeCombiner.Start(); - hash.Add(descriptor.Kind); + hash.Add(descriptor.Kind, StringComparer.Ordinal); hash.Add(descriptor.Name, StringComparer.Ordinal); + hash.Add(descriptor.TypeName, StringComparer.Ordinal); + hash.Add(descriptor.Metadata?.Count); return hash.CombinedHash; } diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DirectiveDescriptorComparer.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DirectiveDescriptorComparer.cs index c3a9590231..42bff25217 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DirectiveDescriptorComparer.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DirectiveDescriptorComparer.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -39,11 +39,19 @@ namespace Microsoft.AspNetCore.Razor.Language throw new ArgumentNullException(nameof(descriptor)); } - var hashCodeCombiner = HashCodeCombiner.Start(); - hashCodeCombiner.Add(descriptor.Directive, StringComparer.Ordinal); - hashCodeCombiner.Add(descriptor.Kind); + var hash = HashCodeCombiner.Start(); + hash.Add(descriptor.Directive, StringComparer.Ordinal); + hash.Add(descriptor.Kind); - return hashCodeCombiner.CombinedHash; + if (descriptor.Tokens != null) + { + for (var i = 0; i < descriptor.Tokens.Count; i++) + { + hash.Add(descriptor.Tokens[i]); + } + } + + return hash.CombinedHash; } } } diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DirectiveTokenDescriptorComparer.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DirectiveTokenDescriptorComparer.cs index 4ec1ba70d6..8d3d4bc24e 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DirectiveTokenDescriptorComparer.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DirectiveTokenDescriptorComparer.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Microsoft.AspNetCore.Razor.Language.csproj b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Microsoft.AspNetCore.Razor.Language.csproj index fbda167142..38d03b2a1d 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Microsoft.AspNetCore.Razor.Language.csproj +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Microsoft.AspNetCore.Razor.Language.csproj @@ -1,4 +1,4 @@ - + Razor is a markup syntax for adding server-side logic to web pages. This package contains the Razor parser and code generation infrastructure. diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RequiredAttributeDescriptorComparer.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RequiredAttributeDescriptorComparer.cs index 2883797e4f..6467803ee9 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RequiredAttributeDescriptorComparer.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RequiredAttributeDescriptorComparer.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -57,6 +57,7 @@ namespace Microsoft.AspNetCore.Razor.Language var hash = HashCodeCombiner.Start(); hash.Add(descriptor.Name, StringComparer.Ordinal); + hash.Add(descriptor.Value, StringComparer.Ordinal); return hash.CombinedHash; } diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/TagHelperDescriptorComparer.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/TagHelperDescriptorComparer.cs index a078fc0630..275ee11cea 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/TagHelperDescriptorComparer.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/TagHelperDescriptorComparer.cs @@ -120,8 +120,50 @@ namespace Microsoft.AspNetCore.Razor.Language hash.Add(descriptor.Kind, StringComparer.Ordinal); hash.Add(descriptor.AssemblyName, StringComparer.Ordinal); hash.Add(descriptor.Name, StringComparer.Ordinal); + hash.Add(descriptor.DisplayName, StringComparer.Ordinal); + hash.Add(descriptor.CaseSensitive ? 1 : 0); + + if (descriptor.BoundAttributes != null) + { + for (var i = 0; i < descriptor.BoundAttributes.Count; i++) + { + hash.Add(descriptor.BoundAttributes[i]); + } + } + + if (descriptor.TagMatchingRules != null) + { + for (var i = 0; i < descriptor.TagMatchingRules.Count; i++) + { + hash.Add(descriptor.TagMatchingRules[i]); + } + } + + if (descriptor.AllowedChildTags != null) + { + for (var i = 0; i < descriptor.AllowedChildTags.Count; i++) + { + hash.Add(descriptor.AllowedChildTags[i]); + } + } + + if (descriptor.Diagnostics != null) + { + for (var i = 0; i < descriptor.Diagnostics.Count; i++) + { + hash.Add(descriptor.Diagnostics[i]); + } + } + + if (descriptor.Metadata != null) + { + foreach (var kvp in descriptor.Metadata) + { + hash.Add(kvp.Value, StringComparer.Ordinal); + } + } return hash.CombinedHash; } } -} \ No newline at end of file +} diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/TagMatchingRuleDescriptorComparer.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/TagMatchingRuleDescriptorComparer.cs index 4ed8805e92..1b9a2a7589 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/TagMatchingRuleDescriptorComparer.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/TagMatchingRuleDescriptorComparer.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -48,8 +48,17 @@ namespace Microsoft.AspNetCore.Razor.Language var hash = HashCodeCombiner.Start(); hash.Add(rule.TagName, StringComparer.Ordinal); + hash.Add(rule.ParentTag, StringComparer.Ordinal); + + if (rule.Attributes != null) + { + for (var i = 0; i < rule.Attributes.Count; ++i) + { + hash.Add(rule.Attributes[i]); + } + } return hash.CombinedHash; } } -} \ No newline at end of file +} diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/test/Microsoft.AspNetCore.Razor.Language.Test.csproj b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/Microsoft.AspNetCore.Razor.Language.Test.csproj index f60d341f39..d90b125693 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/test/Microsoft.AspNetCore.Razor.Language.Test.csproj +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/Microsoft.AspNetCore.Razor.Language.Test.csproj @@ -13,15 +13,31 @@ false - - - - - + + + + Shared\TagHelperDescriptorJsonConverter.cs + + + Shared\RazorDiagnosticJsonConverter.cs + + + Shared\JsonReaderExtensions.cs + + + TestFiles\taghelpers.json + + + + + + + + diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TagHelperDescriptorComparerTest.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TagHelperDescriptorComparerTest.cs new file mode 100644 index 0000000000..d2302ef4e4 --- /dev/null +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TagHelperDescriptorComparerTest.cs @@ -0,0 +1,208 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Microsoft.CodeAnalysis.Razor.Serialization; +using Newtonsoft.Json; +using Xunit; + +namespace Microsoft.AspNetCore.Razor.Language +{ + public class TagHelperDescriptorComparerTest + { + private static readonly TestFile TagHelpersTestFile = TestFile.Create("TestFiles/taghelpers.json", typeof(TagHelperDescriptorComparerTest)); + + [Fact] + public void GetHashCode_SameTagHelperDescriptors_HashCodeMatches() + { + // Arrange + var descriptor1 = CreateTagHelperDescriptor( + tagName: "input", + typeName: "InputTagHelper", + assemblyName: "TestAssembly", + attributes: new Action[] + { + builder => builder + .Name("value") + .PropertyName("FooProp") + .TypeName("System.String"), + }); + var descriptor2 = CreateTagHelperDescriptor( + tagName: "input", + typeName: "InputTagHelper", + assemblyName: "TestAssembly", + attributes: new Action[] + { + builder => builder + .Name("value") + .PropertyName("FooProp") + .TypeName("System.String"), + }); + + // Act + var hashCode1 = descriptor1.GetHashCode(); + var hashCode2 = descriptor2.GetHashCode(); + + // Assert + Assert.Equal(hashCode1, hashCode2); + } + + [Fact] + public void GetHashCode_FQNAndNameTagHelperDescriptors_HashCodeDoesNotMatch() + { + // Arrange + var descriptorName = CreateTagHelperDescriptor( + tagName: "input", + typeName: "InputTagHelper", + assemblyName: "TestAssembly", + tagMatchingRuleName: "Input", + attributes: new Action[] + { + builder => builder + .Name("value") + .PropertyName("FooProp") + .TypeName("System.String"), + }); + var descriptorFQN = CreateTagHelperDescriptor( + tagName: "input", + typeName: "InputTagHelper", + assemblyName: "TestAssembly", + tagMatchingRuleName: "Microsoft.AspNetCore.Components.Forms.Input", + attributes: new Action[] + { + builder => builder + .Name("value") + .PropertyName("FooProp") + .TypeName("System.String"), + }); + + // Act + var hashCodeName = descriptorName.GetHashCode(); + var hashCodeFQN = descriptorFQN.GetHashCode(); + + // Assert + Assert.NotEqual(hashCodeName, hashCodeFQN); + } + + [Fact] + public void GetHashCode_DifferentTagHelperDescriptors_HashCodeDoesNotMatch() + { + // Arrange + var counterTagHelper = CreateTagHelperDescriptor( + tagName: "Counter", + typeName: "CounterTagHelper", + assemblyName: "Components.Component", + tagMatchingRuleName: "Input", + attributes: new Action[] + { + builder => builder + .Name("IncrementBy") + .PropertyName("IncrementBy") + .TypeName("System.Int32"), + }); + var inputTagHelper = CreateTagHelperDescriptor( + tagName: "input", + typeName: "InputTagHelper", + assemblyName: "TestAssembly", + tagMatchingRuleName: "Microsoft.AspNetCore.Components.Forms.Input", + attributes: new Action[] + { + builder => builder + .Name("value") + .PropertyName("FooProp") + .TypeName("System.String"), + }); + + // Act + var hashCodeCounter = counterTagHelper.GetHashCode(); + var hashCodeInput = inputTagHelper.GetHashCode(); + + // Assert + Assert.NotEqual(hashCodeCounter, hashCodeInput); + } + + [Fact] + public void GetHashCode_AllTagHelpers_NoHashCodeCollisions() + { + // Arrange + var tagHelpers = ReadTagHelpers(TagHelpersTestFile.OpenRead()); + + // Act + var hashes = new HashSet(tagHelpers.Select(t => t.GetHashCode())); + + // Assert + Assert.Equal(hashes.Count(), tagHelpers.Count); + } + + [Fact] + public void GetHashCode_DuplicateTagHelpers_NoHashCodeCollisions() + { + // Arrange + var tagHelpers = new List(); + var tagHelpersPerBatch = -1; + + // Reads 5 copies of the TagHelpers (with 5x references) + // This ensures we don't have any dependencies on reference based GetHashCode + for (var i = 0; i < 5; ++i) + { + var tagHelpersBatch = ReadTagHelpers(TagHelpersTestFile.OpenRead()); + tagHelpers.AddRange(tagHelpersBatch); + tagHelpersPerBatch = tagHelpersBatch.Count; + } + + // Act + var hashes = new HashSet(tagHelpers.Select(t => t.GetHashCode())); + + // Assert + // Only 1 batch of taghelpers should remain after we filter by hash + Assert.Equal(hashes.Count(), tagHelpersPerBatch); + } + + private static TagHelperDescriptor CreateTagHelperDescriptor( + string tagName, + string typeName, + string assemblyName, + string tagMatchingRuleName = null, + IEnumerable> attributes = null) + { + var builder = TagHelperDescriptorBuilder.Create(typeName, assemblyName) as DefaultTagHelperDescriptorBuilder; + builder.TypeName(typeName); + + if (attributes != null) + { + foreach (var attributeBuilder in attributes) + { + builder.BoundAttributeDescriptor(attributeBuilder); + } + } + + builder.TagMatchingRuleDescriptor(ruleBuilder => ruleBuilder.RequireTagName(tagMatchingRuleName ?? tagName)); + + var descriptor = builder.Build(); + + return descriptor; + } + + private IReadOnlyList ReadTagHelpers(Stream stream) + { + var serializer = new JsonSerializer(); + serializer.Converters.Add(new RazorDiagnosticJsonConverter()); + serializer.Converters.Add(new TagHelperDescriptorJsonConverter()); + + IReadOnlyList result; + + using var streamReader = new StreamReader(stream); + using (var reader = new JsonTextReader(streamReader)) + { + result = serializer.Deserialize>(reader); + } + + stream.Dispose(); + + return result; + } + } +} diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/DiscoverCommand.cs b/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/DiscoverCommand.cs index 1c96b83e85..7ac1ea23c7 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/DiscoverCommand.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/DiscoverCommand.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -13,7 +13,6 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Razor; using Microsoft.CodeAnalysis.Razor.Serialization; using Microsoft.Extensions.CommandLineUtils; -using Microsoft.VisualStudio.LanguageServices.Razor.Serialization; using Newtonsoft.Json; namespace Microsoft.AspNetCore.Razor.Tools @@ -223,4 +222,4 @@ namespace Microsoft.AspNetCore.Razor.Tools } } } -} \ No newline at end of file +} diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/GenerateCommand.cs b/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/GenerateCommand.cs index 658b9d2263..229d62a040 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/GenerateCommand.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/GenerateCommand.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -12,7 +12,6 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Razor; using Microsoft.CodeAnalysis.Razor.Serialization; using Microsoft.Extensions.CommandLineUtils; -using Microsoft.VisualStudio.LanguageServices.Razor.Serialization; using Newtonsoft.Json; namespace Microsoft.AspNetCore.Razor.Tools diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/rzc.csproj b/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/rzc.csproj index 0e4954984b..590e785d0e 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/rzc.csproj +++ b/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/rzc.csproj @@ -1,4 +1,4 @@ - + Razor is a markup syntax for adding server-side logic to web pages. This assembly contains infrastructure supporting Razor MSBuild integration. @@ -33,6 +33,9 @@ Shared\RazorDiagnosticJsonConverter.cs + + Shared\JsonReaderExtensions.cs + diff --git a/src/Razor/perf/Microsoft.AspNetCore.Razor.Performance/Microsoft.AspNetCore.Razor.Performance.csproj b/src/Razor/perf/Microsoft.AspNetCore.Razor.Performance/Microsoft.AspNetCore.Razor.Performance.csproj index 31cc0f16bd..75bf91f96d 100644 --- a/src/Razor/perf/Microsoft.AspNetCore.Razor.Performance/Microsoft.AspNetCore.Razor.Performance.csproj +++ b/src/Razor/perf/Microsoft.AspNetCore.Razor.Performance/Microsoft.AspNetCore.Razor.Performance.csproj @@ -1,4 +1,4 @@ - + netcoreapp2.1 @@ -16,15 +16,22 @@ - - - + Shared\TagHelperDescriptorJsonConverter.cs Shared\RazorDiagnosticJsonConverter.cs + + Shared\JsonReaderExtensions.cs + + + + + taghelpers.json + + diff --git a/src/Razor/perf/Microsoft.AspNetCore.Razor.Performance/RazorTagHelperParsingBenchmark.cs b/src/Razor/perf/Microsoft.AspNetCore.Razor.Performance/RazorTagHelperParsingBenchmark.cs index 56e80f62fb..89e3c291f7 100644 --- a/src/Razor/perf/Microsoft.AspNetCore.Razor.Performance/RazorTagHelperParsingBenchmark.cs +++ b/src/Razor/perf/Microsoft.AspNetCore.Razor.Performance/RazorTagHelperParsingBenchmark.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -8,7 +8,6 @@ using BenchmarkDotNet.Attributes; using Microsoft.AspNetCore.Mvc.Razor.Extensions; using Microsoft.AspNetCore.Razor.Language; using Microsoft.CodeAnalysis.Razor.Serialization; -using Microsoft.VisualStudio.LanguageServices.Razor.Serialization; using Newtonsoft.Json; using static Microsoft.AspNetCore.Razor.Language.DefaultRazorTagHelperBinderPhase; diff --git a/src/Shared/RazorShared/JsonReaderExtensions.cs b/src/Shared/RazorShared/JsonReaderExtensions.cs new file mode 100644 index 0000000000..2cf37b12d5 --- /dev/null +++ b/src/Shared/RazorShared/JsonReaderExtensions.cs @@ -0,0 +1,58 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Newtonsoft.Json; + +namespace Microsoft.CodeAnalysis.Razor.Serialization +{ + internal static class JsonReaderExtensions + { + public static bool ReadTokenAndAdvance(this JsonReader reader, JsonToken expectedTokenType, out object value) + { + value = reader.Value; + return reader.TokenType == expectedTokenType && reader.Read(); + } + + public static void ReadProperties(this JsonReader reader, Action onProperty) + { + while (reader.Read()) + { + switch (reader.TokenType) + { + case JsonToken.PropertyName: + var propertyName = reader.Value.ToString(); + onProperty(propertyName); + break; + case JsonToken.EndObject: + return; + } + } + } + + public static string ReadNextStringProperty(this JsonReader reader, string propertyName) + { + while (reader.Read()) + { + switch (reader.TokenType) + { + case JsonToken.PropertyName: + Debug.Assert(reader.Value.ToString() == propertyName); + if (reader.Read()) + { + var value = (string)reader.Value; + return value; + } + else + { + return null; + } + } + } + + throw new JsonSerializationException($"Could not find string property '{propertyName}'."); + } + } +} diff --git a/src/Shared/RazorShared/RazorDiagnosticJsonConverter.cs b/src/Shared/RazorShared/RazorDiagnosticJsonConverter.cs index 4bfba23eed..3d34a8e344 100644 --- a/src/Shared/RazorShared/RazorDiagnosticJsonConverter.cs +++ b/src/Shared/RazorShared/RazorDiagnosticJsonConverter.cs @@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Razor.Language; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -namespace Microsoft.VisualStudio.LanguageServices.Razor.Serialization +namespace Microsoft.CodeAnalysis.Razor.Serialization { internal class RazorDiagnosticJsonConverter : JsonConverter { diff --git a/src/Shared/RazorShared/TagHelperDescriptorJsonConverter.cs b/src/Shared/RazorShared/TagHelperDescriptorJsonConverter.cs index 3e610c08c7..85763bf559 100644 --- a/src/Shared/RazorShared/TagHelperDescriptorJsonConverter.cs +++ b/src/Shared/RazorShared/TagHelperDescriptorJsonConverter.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using Microsoft.AspNetCore.Razor.Language; using Newtonsoft.Json; -using Newtonsoft.Json.Linq; namespace Microsoft.CodeAnalysis.Razor.Serialization { @@ -25,56 +24,54 @@ namespace Microsoft.CodeAnalysis.Razor.Serialization return null; } - var descriptor = JObject.Load(reader); - var descriptorKind = descriptor[nameof(TagHelperDescriptor.Kind)].Value(); - var typeName = descriptor[nameof(TagHelperDescriptor.Name)].Value(); - var assemblyName = descriptor[nameof(TagHelperDescriptor.AssemblyName)].Value(); - var tagMatchingRules = descriptor[nameof(TagHelperDescriptor.TagMatchingRules)].Value(); - var boundAttributes = descriptor[nameof(TagHelperDescriptor.BoundAttributes)].Value(); - var childTags = descriptor[nameof(TagHelperDescriptor.AllowedChildTags)].Value(); - var documentation = descriptor[nameof(TagHelperDescriptor.Documentation)].Value(); - var tagOutputHint = descriptor[nameof(TagHelperDescriptor.TagOutputHint)].Value(); - var caseSensitive = descriptor[nameof(TagHelperDescriptor.CaseSensitive)].Value(); - var diagnostics = descriptor[nameof(TagHelperDescriptor.Diagnostics)].Value(); - var metadata = descriptor[nameof(TagHelperDescriptor.Metadata)].Value(); - + // Required tokens (order matters) + var descriptorKind = reader.ReadNextStringProperty(nameof(TagHelperDescriptor.Kind)); + var typeName = reader.ReadNextStringProperty(nameof(TagHelperDescriptor.Name)); + var assemblyName = reader.ReadNextStringProperty(nameof(TagHelperDescriptor.AssemblyName)); var builder = TagHelperDescriptorBuilder.Create(descriptorKind, typeName, assemblyName); - builder.Documentation = documentation; - builder.TagOutputHint = tagOutputHint; - builder.CaseSensitive = caseSensitive; - - foreach (var tagMatchingRule in tagMatchingRules) + reader.ReadProperties(propertyName => { - var rule = tagMatchingRule.Value(); - builder.TagMatchingRule(b => ReadTagMatchingRule(b, rule, serializer)); - } - - foreach (var boundAttribute in boundAttributes) - { - var attribute = boundAttribute.Value(); - builder.BindAttribute(b => ReadBoundAttribute(b, attribute, serializer)); - } - - foreach (var childTag in childTags) - { - var tag = childTag.Value(); - builder.AllowChildTag(childTagBuilder => ReadAllowedChildTag(childTagBuilder, tag, serializer)); - } - - foreach (var diagnostic in diagnostics) - { - var diagnosticReader = diagnostic.CreateReader(); - var diagnosticObject = serializer.Deserialize(diagnosticReader); - builder.Diagnostics.Add(diagnosticObject); - } - - var metadataReader = metadata.CreateReader(); - var metadataValue = serializer.Deserialize>(metadataReader); - foreach (var item in metadataValue) - { - builder.Metadata[item.Key] = item.Value; - } + switch (propertyName) + { + case nameof(TagHelperDescriptor.Documentation): + if (reader.Read()) + { + var documentation = (string)reader.Value; + builder.Documentation = documentation; + } + break; + case nameof(TagHelperDescriptor.TagOutputHint): + if (reader.Read()) + { + var tagOutputHint = (string)reader.Value; + builder.TagOutputHint = tagOutputHint; + } + break; + case nameof(TagHelperDescriptor.CaseSensitive): + if (reader.Read()) + { + var caseSensitive = (bool)reader.Value; + builder.CaseSensitive = caseSensitive; + } + break; + case nameof(TagHelperDescriptor.TagMatchingRules): + ReadTagMatchingRules(reader, builder); + break; + case nameof(TagHelperDescriptor.BoundAttributes): + ReadBoundAttributes(reader, builder); + break; + case nameof(TagHelperDescriptor.AllowedChildTags): + ReadAllowedChildTags(reader, builder); + break; + case nameof(TagHelperDescriptor.Diagnostics): + ReadDiagnostics(reader, builder.Diagnostics); + break; + case nameof(TagHelperDescriptor.Metadata): + ReadMetadata(reader, builder.Metadata); + break; + } + }); return builder.Build(); } @@ -94,11 +91,17 @@ namespace Microsoft.CodeAnalysis.Razor.Serialization writer.WritePropertyName(nameof(TagHelperDescriptor.AssemblyName)); writer.WriteValue(tagHelper.AssemblyName); - writer.WritePropertyName(nameof(TagHelperDescriptor.Documentation)); - writer.WriteValue(tagHelper.Documentation); + if (tagHelper.Documentation != null) + { + writer.WritePropertyName(nameof(TagHelperDescriptor.Documentation)); + writer.WriteValue(tagHelper.Documentation); + } - writer.WritePropertyName(nameof(TagHelperDescriptor.TagOutputHint)); - writer.WriteValue(tagHelper.TagOutputHint); + if (tagHelper.TagOutputHint != null) + { + writer.WritePropertyName(nameof(TagHelperDescriptor.TagOutputHint)); + writer.WriteValue(tagHelper.TagOutputHint); + } writer.WritePropertyName(nameof(TagHelperDescriptor.CaseSensitive)); writer.WriteValue(tagHelper.CaseSensitive); @@ -111,24 +114,33 @@ namespace Microsoft.CodeAnalysis.Razor.Serialization } writer.WriteEndArray(); - writer.WritePropertyName(nameof(TagHelperDescriptor.BoundAttributes)); - writer.WriteStartArray(); - foreach (var boundAttribute in tagHelper.BoundAttributes) + if (tagHelper.BoundAttributes != null && tagHelper.BoundAttributes.Count > 0) { - WriteBoundAttribute(writer, boundAttribute, serializer); + writer.WritePropertyName(nameof(TagHelperDescriptor.BoundAttributes)); + writer.WriteStartArray(); + foreach (var boundAttribute in tagHelper.BoundAttributes) + { + WriteBoundAttribute(writer, boundAttribute, serializer); + } + writer.WriteEndArray(); } - writer.WriteEndArray(); - writer.WritePropertyName(nameof(TagHelperDescriptor.AllowedChildTags)); - writer.WriteStartArray(); - foreach (var allowedChildTag in tagHelper.AllowedChildTags) + if (tagHelper.AllowedChildTags != null && tagHelper.AllowedChildTags.Count > 0) { - WriteAllowedChildTags(writer, allowedChildTag, serializer); + writer.WritePropertyName(nameof(TagHelperDescriptor.AllowedChildTags)); + writer.WriteStartArray(); + foreach (var allowedChildTag in tagHelper.AllowedChildTags) + { + WriteAllowedChildTags(writer, allowedChildTag, serializer); + } + writer.WriteEndArray(); } - writer.WriteEndArray(); - writer.WritePropertyName(nameof(TagHelperDescriptor.Diagnostics)); - serializer.Serialize(writer, tagHelper.Diagnostics); + if (tagHelper.Diagnostics != null && tagHelper.Diagnostics.Count > 0) + { + writer.WritePropertyName(nameof(TagHelperDescriptor.Diagnostics)); + serializer.Serialize(writer, tagHelper.Diagnostics); + } writer.WritePropertyName(nameof(TagHelperDescriptor.Metadata)); WriteMetadata(writer, tagHelper.Metadata); @@ -165,31 +177,49 @@ namespace Microsoft.CodeAnalysis.Razor.Serialization writer.WritePropertyName(nameof(BoundAttributeDescriptor.TypeName)); writer.WriteValue(boundAttribute.TypeName); - writer.WritePropertyName(nameof(BoundAttributeDescriptor.IsEnum)); - writer.WriteValue(boundAttribute.IsEnum); + if (boundAttribute.IsEnum) + { + writer.WritePropertyName(nameof(BoundAttributeDescriptor.IsEnum)); + writer.WriteValue(boundAttribute.IsEnum); + } - writer.WritePropertyName(nameof(BoundAttributeDescriptor.IndexerNamePrefix)); - writer.WriteValue(boundAttribute.IndexerNamePrefix); + if (boundAttribute.IndexerNamePrefix != null) + { + writer.WritePropertyName(nameof(BoundAttributeDescriptor.IndexerNamePrefix)); + writer.WriteValue(boundAttribute.IndexerNamePrefix); + } - writer.WritePropertyName(nameof(BoundAttributeDescriptor.IndexerTypeName)); - writer.WriteValue(boundAttribute.IndexerTypeName); + if (boundAttribute.IndexerTypeName != null) + { + writer.WritePropertyName(nameof(BoundAttributeDescriptor.IndexerTypeName)); + writer.WriteValue(boundAttribute.IndexerTypeName); + } - writer.WritePropertyName(nameof(BoundAttributeDescriptor.Documentation)); - writer.WriteValue(boundAttribute.Documentation); + if (boundAttribute.Documentation != null) + { + writer.WritePropertyName(nameof(BoundAttributeDescriptor.Documentation)); + writer.WriteValue(boundAttribute.Documentation); + } - writer.WritePropertyName(nameof(BoundAttributeDescriptor.Diagnostics)); - serializer.Serialize(writer, boundAttribute.Diagnostics); + if (boundAttribute.Diagnostics != null && boundAttribute.Diagnostics.Count > 0) + { + writer.WritePropertyName(nameof(BoundAttributeDescriptor.Diagnostics)); + serializer.Serialize(writer, boundAttribute.Diagnostics); + } writer.WritePropertyName(nameof(BoundAttributeDescriptor.Metadata)); WriteMetadata(writer, boundAttribute.Metadata); - writer.WritePropertyName(nameof(BoundAttributeDescriptor.BoundAttributeParameters)); - writer.WriteStartArray(); - foreach (var boundAttributeParameter in boundAttribute.BoundAttributeParameters) + if (boundAttribute.BoundAttributeParameters != null && boundAttribute.BoundAttributeParameters.Count > 0) { - WriteBoundAttributeParameter(writer, boundAttributeParameter, serializer); + writer.WritePropertyName(nameof(BoundAttributeDescriptor.BoundAttributeParameters)); + writer.WriteStartArray(); + foreach (var boundAttributeParameter in boundAttribute.BoundAttributeParameters) + { + WriteBoundAttributeParameter(writer, boundAttributeParameter, serializer); + } + writer.WriteEndArray(); } - writer.WriteEndArray(); writer.WriteEndObject(); } @@ -198,23 +228,29 @@ namespace Microsoft.CodeAnalysis.Razor.Serialization { writer.WriteStartObject(); - writer.WritePropertyName(nameof(BoundAttributeParameterDescriptor.Kind)); - writer.WriteValue(boundAttributeParameter.Kind); - writer.WritePropertyName(nameof(BoundAttributeParameterDescriptor.Name)); writer.WriteValue(boundAttributeParameter.Name); writer.WritePropertyName(nameof(BoundAttributeParameterDescriptor.TypeName)); writer.WriteValue(boundAttributeParameter.TypeName); - writer.WritePropertyName(nameof(BoundAttributeParameterDescriptor.IsEnum)); - writer.WriteValue(boundAttributeParameter.IsEnum); + if (boundAttributeParameter.IsEnum != default) + { + writer.WritePropertyName(nameof(BoundAttributeParameterDescriptor.IsEnum)); + writer.WriteValue(boundAttributeParameter.IsEnum); + } - writer.WritePropertyName(nameof(BoundAttributeParameterDescriptor.Documentation)); - writer.WriteValue(boundAttributeParameter.Documentation); + if (boundAttributeParameter.Documentation != null) + { + writer.WritePropertyName(nameof(BoundAttributeParameterDescriptor.Documentation)); + writer.WriteValue(boundAttributeParameter.Documentation); + } - writer.WritePropertyName(nameof(BoundAttributeParameterDescriptor.Diagnostics)); - serializer.Serialize(writer, boundAttributeParameter.Diagnostics); + if (boundAttributeParameter.Diagnostics != null && boundAttributeParameter.Diagnostics.Count > 0) + { + writer.WritePropertyName(nameof(BoundAttributeParameterDescriptor.Diagnostics)); + serializer.Serialize(writer, boundAttributeParameter.Diagnostics); + } writer.WritePropertyName(nameof(BoundAttributeParameterDescriptor.Metadata)); WriteMetadata(writer, boundAttributeParameter.Metadata); @@ -240,22 +276,34 @@ namespace Microsoft.CodeAnalysis.Razor.Serialization 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) + if (ruleDescriptor.ParentTag != null) { - WriteRequiredAttribute(writer, requiredAttribute, serializer); + writer.WritePropertyName(nameof(TagMatchingRuleDescriptor.ParentTag)); + writer.WriteValue(ruleDescriptor.ParentTag); } - writer.WriteEndArray(); - writer.WritePropertyName(nameof(TagMatchingRuleDescriptor.Diagnostics)); - serializer.Serialize(writer, ruleDescriptor.Diagnostics); + if (ruleDescriptor.TagStructure != default) + { + writer.WritePropertyName(nameof(TagMatchingRuleDescriptor.TagStructure)); + writer.WriteValue(ruleDescriptor.TagStructure); + } + + if (ruleDescriptor.Attributes != null && ruleDescriptor.Attributes.Count > 0) + { + writer.WritePropertyName(nameof(TagMatchingRuleDescriptor.Attributes)); + writer.WriteStartArray(); + foreach (var requiredAttribute in ruleDescriptor.Attributes) + { + WriteRequiredAttribute(writer, requiredAttribute, serializer); + } + writer.WriteEndArray(); + } + + if (ruleDescriptor.Diagnostics != null && ruleDescriptor.Diagnostics.Count > 0) + { + writer.WritePropertyName(nameof(TagMatchingRuleDescriptor.Diagnostics)); + serializer.Serialize(writer, ruleDescriptor.Diagnostics); + } writer.WriteEndObject(); } @@ -267,176 +315,538 @@ namespace Microsoft.CodeAnalysis.Razor.Serialization writer.WritePropertyName(nameof(RequiredAttributeDescriptor.Name)); writer.WriteValue(requiredAttribute.Name); - writer.WritePropertyName(nameof(RequiredAttributeDescriptor.NameComparison)); - writer.WriteValue(requiredAttribute.NameComparison); + if (requiredAttribute.NameComparison != default) + { + writer.WritePropertyName(nameof(RequiredAttributeDescriptor.NameComparison)); + writer.WriteValue(requiredAttribute.NameComparison); + } - writer.WritePropertyName(nameof(RequiredAttributeDescriptor.Value)); - writer.WriteValue(requiredAttribute.Value); + if (requiredAttribute.Value != null) + { + writer.WritePropertyName(nameof(RequiredAttributeDescriptor.Value)); + writer.WriteValue(requiredAttribute.Value); + } - writer.WritePropertyName(nameof(RequiredAttributeDescriptor.ValueComparison)); - writer.WriteValue(requiredAttribute.ValueComparison); + if (requiredAttribute.ValueComparison != default) + { + writer.WritePropertyName(nameof(RequiredAttributeDescriptor.ValueComparison)); + writer.WriteValue(requiredAttribute.ValueComparison); + } - writer.WritePropertyName(nameof(RequiredAttributeDescriptor.Diagnostics)); - serializer.Serialize(writer, requiredAttribute.Diagnostics); + if (requiredAttribute.Diagnostics != null && requiredAttribute.Diagnostics.Count > 0) + { + writer.WritePropertyName(nameof(RequiredAttributeDescriptor.Diagnostics)); + serializer.Serialize(writer, requiredAttribute.Diagnostics); + } - writer.WritePropertyName(nameof(RequiredAttributeDescriptor.Metadata)); - WriteMetadata(writer, requiredAttribute.Metadata); + if (requiredAttribute.Metadata != null && requiredAttribute.Metadata.Count > 0) + { + writer.WritePropertyName(nameof(RequiredAttributeDescriptor.Metadata)); + WriteMetadata(writer, requiredAttribute.Metadata); + } writer.WriteEndObject(); } - private static void ReadTagMatchingRule(TagMatchingRuleDescriptorBuilder builder, JObject rule, JsonSerializer serializer) + private static void ReadBoundAttributes(JsonReader reader, TagHelperDescriptorBuilder builder) { - var tagName = rule[nameof(TagMatchingRuleDescriptor.TagName)].Value(); - var attributes = rule[nameof(TagMatchingRuleDescriptor.Attributes)].Value(); - var parentTag = rule[nameof(TagMatchingRuleDescriptor.ParentTag)].Value(); - var tagStructure = rule[nameof(TagMatchingRuleDescriptor.TagStructure)].Value(); - var diagnostics = rule[nameof(TagMatchingRuleDescriptor.Diagnostics)].Value(); - - builder.TagName = tagName; - builder.ParentTag = parentTag; - builder.TagStructure = (TagStructure)tagStructure; - - foreach (var attribute in attributes) + if (!reader.Read()) { - var attibuteValue = attribute.Value(); - builder.Attribute(b => ReadRequiredAttribute(b, attibuteValue, serializer)); + return; } - foreach (var diagnostic in diagnostics) + if (reader.TokenType != JsonToken.StartArray) { - var diagnosticReader = diagnostic.CreateReader(); - var diagnosticObject = serializer.Deserialize(diagnosticReader); - builder.Diagnostics.Add(diagnosticObject); + return; } + + do + { + ReadBoundAttribute(reader, builder); + } while (reader.TokenType != JsonToken.EndArray); } - private static void ReadRequiredAttribute(RequiredAttributeDescriptorBuilder builder, JObject attribute, JsonSerializer serializer) + private static void ReadBoundAttribute(JsonReader reader, TagHelperDescriptorBuilder builder) { - var name = attribute[nameof(RequiredAttributeDescriptor.Name)].Value(); - var nameComparison = attribute[nameof(RequiredAttributeDescriptor.NameComparison)].Value(); - var value = attribute[nameof(RequiredAttributeDescriptor.Value)].Value(); - var valueComparison = attribute[nameof(RequiredAttributeDescriptor.ValueComparison)].Value(); - var diagnostics = attribute[nameof(RequiredAttributeDescriptor.Diagnostics)].Value(); - var metadata = attribute[nameof(RequiredAttributeDescriptor.Metadata)].Value(); - - builder.Name = name; - builder.NameComparisonMode = (RequiredAttributeDescriptor.NameComparisonMode)nameComparison; - builder.Value = value; - builder.ValueComparisonMode = (RequiredAttributeDescriptor.ValueComparisonMode)valueComparison; - - foreach (var diagnostic in diagnostics) + if (!reader.Read()) { - var diagnosticReader = diagnostic.CreateReader(); - var diagnosticObject = serializer.Deserialize(diagnosticReader); - builder.Diagnostics.Add(diagnosticObject); + return; } - var metadataReader = metadata.CreateReader(); - var metadataValue = serializer.Deserialize>(metadataReader); - foreach (var item in metadataValue) + if (reader.TokenType != JsonToken.StartObject) { - builder.Metadata[item.Key] = item.Value; + return; } + + builder.BindAttribute(attribute => + { + reader.ReadProperties(propertyName => + { + switch (propertyName) + { + case nameof(BoundAttributeDescriptor.Name): + if (reader.Read()) + { + var name = (string)reader.Value; + attribute.Name = name; + } + break; + case nameof(BoundAttributeDescriptor.TypeName): + if (reader.Read()) + { + var typeName = (string)reader.Value; + attribute.TypeName = typeName; + } + break; + case nameof(BoundAttributeDescriptor.Documentation): + if (reader.Read()) + { + var documentation = (string)reader.Value; + attribute.Documentation = documentation; + } + break; + case nameof(BoundAttributeDescriptor.IndexerNamePrefix): + if (reader.Read()) + { + var indexerNamePrefix = (string)reader.Value; + if (indexerNamePrefix != null) + { + attribute.IsDictionary = true; + attribute.IndexerAttributeNamePrefix = indexerNamePrefix; + } + } + break; + case nameof(BoundAttributeDescriptor.IndexerTypeName): + if (reader.Read()) + { + var indexerTypeName = (string)reader.Value; + if (indexerTypeName != null) + { + attribute.IsDictionary = true; + attribute.IndexerValueTypeName = indexerTypeName; + } + } + break; + case nameof(BoundAttributeDescriptor.IsEnum): + if (reader.Read()) + { + var isEnum = (bool)reader.Value; + attribute.IsEnum = isEnum; + } + break; + case nameof(BoundAttributeDescriptor.BoundAttributeParameters): + ReadBoundAttributeParameters(reader, attribute); + break; + case nameof(BoundAttributeDescriptor.Diagnostics): + ReadDiagnostics(reader, attribute.Diagnostics); + break; + case nameof(BoundAttributeDescriptor.Metadata): + ReadMetadata(reader, attribute.Metadata); + break; + } + }); + }); } - private static void ReadAllowedChildTag(AllowedChildTagDescriptorBuilder builder, JObject childTag, JsonSerializer serializer) + private static void ReadBoundAttributeParameters(JsonReader reader, BoundAttributeDescriptorBuilder builder) { - var name = childTag[nameof(AllowedChildTagDescriptor.Name)].Value(); - var displayName = childTag[nameof(AllowedChildTagDescriptor.DisplayName)].Value(); - var diagnostics = childTag[nameof(AllowedChildTagDescriptor.Diagnostics)].Value(); - - builder.Name = name; - builder.DisplayName = displayName; - - foreach (var diagnostic in diagnostics) + if (!reader.Read()) { - var diagnosticReader = diagnostic.CreateReader(); - var diagnosticObject = serializer.Deserialize(diagnosticReader); - builder.Diagnostics.Add(diagnosticObject); + return; } + + if (reader.TokenType != JsonToken.StartArray) + { + return; + } + + do + { + ReadBoundAttributeParameter(reader, builder); + } while (reader.TokenType != JsonToken.EndArray); } - private static void ReadBoundAttribute(BoundAttributeDescriptorBuilder builder, JObject attribute, JsonSerializer serializer) + private static void ReadBoundAttributeParameter(JsonReader reader, BoundAttributeDescriptorBuilder builder) { - var descriptorKind = attribute[nameof(BoundAttributeDescriptor.Kind)].Value(); - var name = attribute[nameof(BoundAttributeDescriptor.Name)].Value(); - var typeName = attribute[nameof(BoundAttributeDescriptor.TypeName)].Value(); - var isEnum = attribute[nameof(BoundAttributeDescriptor.IsEnum)].Value(); - var indexerNamePrefix = attribute[nameof(BoundAttributeDescriptor.IndexerNamePrefix)].Value(); - var indexerTypeName = attribute[nameof(BoundAttributeDescriptor.IndexerTypeName)].Value(); - var documentation = attribute[nameof(BoundAttributeDescriptor.Documentation)].Value(); - var diagnostics = attribute[nameof(BoundAttributeDescriptor.Diagnostics)].Value(); - var metadata = attribute[nameof(BoundAttributeDescriptor.Metadata)].Value(); - var boundAttributeParameters = attribute[nameof(BoundAttributeDescriptor.BoundAttributeParameters)].Value(); - - builder.Name = name; - builder.TypeName = typeName; - builder.Documentation = documentation; - - if (indexerNamePrefix != null) + if (!reader.Read()) { - builder.AsDictionary(indexerNamePrefix, indexerTypeName); + return; } - if (isEnum) + if (reader.TokenType != JsonToken.StartObject) { - builder.IsEnum = true; + return; } - foreach (var diagnostic in diagnostics) + builder.BindAttributeParameter(parameter => { - var diagnosticReader = diagnostic.CreateReader(); - var diagnosticObject = serializer.Deserialize(diagnosticReader); - builder.Diagnostics.Add(diagnosticObject); - } - - var metadataReader = metadata.CreateReader(); - var metadataValue = serializer.Deserialize>(metadataReader); - foreach (var item in metadataValue) - { - builder.Metadata[item.Key] = item.Value; - } - - foreach (var boundAttributeParameter in boundAttributeParameters) - { - var parameter = boundAttributeParameter.Value(); - builder.BindAttributeParameter(b => ReadBoundAttributeParameter(b, parameter, serializer)); - } + reader.ReadProperties(propertyName => + { + switch (propertyName) + { + case nameof(BoundAttributeParameterDescriptor.Name): + if (reader.Read()) + { + var name = (string)reader.Value; + parameter.Name = name; + } + break; + case nameof(BoundAttributeParameterDescriptor.TypeName): + if (reader.Read()) + { + var typeName = (string)reader.Value; + parameter.TypeName = typeName; + } + break; + case nameof(BoundAttributeParameterDescriptor.IsEnum): + if (reader.Read()) + { + var isEnum = (bool)reader.Value; + parameter.IsEnum = isEnum; + } + break; + case nameof(BoundAttributeParameterDescriptor.Documentation): + if (reader.Read()) + { + var documentation = (string)reader.Value; + parameter.Documentation = documentation; + } + break; + case nameof(BoundAttributeParameterDescriptor.Metadata): + ReadMetadata(reader, parameter.Metadata); + break; + case nameof(BoundAttributeParameterDescriptor.Diagnostics): + ReadDiagnostics(reader, parameter.Diagnostics); + break; + } + }); + }); } - private static void ReadBoundAttributeParameter(BoundAttributeParameterDescriptorBuilder builder, JObject parameter, JsonSerializer serializer) + private static void ReadTagMatchingRules(JsonReader reader, TagHelperDescriptorBuilder builder) { - var descriptorKind = parameter[nameof(BoundAttributeParameterDescriptor.Kind)].Value(); - var name = parameter[nameof(BoundAttributeParameterDescriptor.Name)].Value(); - var typeName = parameter[nameof(BoundAttributeParameterDescriptor.TypeName)].Value(); - var isEnum = parameter[nameof(BoundAttributeParameterDescriptor.IsEnum)].Value(); - var documentation = parameter[nameof(BoundAttributeParameterDescriptor.Documentation)].Value(); - var diagnostics = parameter[nameof(BoundAttributeParameterDescriptor.Diagnostics)].Value(); - var metadata = parameter[nameof(BoundAttributeParameterDescriptor.Metadata)].Value(); - - builder.Name = name; - builder.TypeName = typeName; - builder.Documentation = documentation; - - if (isEnum) + if (!reader.Read()) { - builder.IsEnum = true; + return; } - foreach (var diagnostic in diagnostics) + if (reader.TokenType != JsonToken.StartArray) { - var diagnosticReader = diagnostic.CreateReader(); - var diagnosticObject = serializer.Deserialize(diagnosticReader); - builder.Diagnostics.Add(diagnosticObject); + return; } - var metadataReader = metadata.CreateReader(); - var metadataValue = serializer.Deserialize>(metadataReader); - foreach (var item in metadataValue) + do { - builder.Metadata[item.Key] = item.Value; + ReadTagMatchingRule(reader, builder); + } while (reader.TokenType != JsonToken.EndArray); + } + + private static void ReadTagMatchingRule(JsonReader reader, TagHelperDescriptorBuilder builder) + { + if (!reader.Read()) + { + return; } + + if (reader.TokenType != JsonToken.StartObject) + { + return; + } + + builder.TagMatchingRule(rule => + { + reader.ReadProperties(propertyName => + { + switch (propertyName) + { + case nameof(TagMatchingRuleDescriptor.TagName): + if (reader.Read()) + { + var tagName = (string)reader.Value; + rule.TagName = tagName; + } + break; + case nameof(TagMatchingRuleDescriptor.ParentTag): + if (reader.Read()) + { + var parentTag = (string)reader.Value; + rule.ParentTag = parentTag; + } + break; + case nameof(TagMatchingRuleDescriptor.TagStructure): + rule.TagStructure = (TagStructure)reader.ReadAsInt32(); + break; + case nameof(TagMatchingRuleDescriptor.Attributes): + ReadRequiredAttributeValues(reader, rule); + break; + case nameof(TagMatchingRuleDescriptor.Diagnostics): + ReadDiagnostics(reader, rule.Diagnostics); + break; + } + }); + }); + } + + private static void ReadRequiredAttributeValues(JsonReader reader, TagMatchingRuleDescriptorBuilder builder) + { + if (!reader.Read()) + { + return; + } + + if (reader.TokenType != JsonToken.StartArray) + { + return; + } + + do + { + ReadRequiredAttribute(reader, builder); + } while (reader.TokenType != JsonToken.EndArray); + } + + private static void ReadRequiredAttribute(JsonReader reader, TagMatchingRuleDescriptorBuilder builder) + { + if (!reader.Read()) + { + return; + } + + if (reader.TokenType != JsonToken.StartObject) + { + return; + } + + builder.Attribute(attribute => + { + reader.ReadProperties(propertyName => + { + switch (propertyName) + { + case nameof(RequiredAttributeDescriptor.Name): + if (reader.Read()) + { + var name = (string)reader.Value; + attribute.Name = name; + } + break; + case nameof(RequiredAttributeDescriptor.NameComparison): + var nameComparison = (RequiredAttributeDescriptor.NameComparisonMode)reader.ReadAsInt32(); + attribute.NameComparisonMode = nameComparison; + break; + case nameof(RequiredAttributeDescriptor.Value): + if (reader.Read()) + { + var value = (string)reader.Value; + attribute.Value = value; + } + break; + case nameof(RequiredAttributeDescriptor.ValueComparison): + var valueComparison = (RequiredAttributeDescriptor.ValueComparisonMode)reader.ReadAsInt32(); + attribute.ValueComparisonMode = valueComparison; + break; + case nameof(RequiredAttributeDescriptor.Diagnostics): + ReadDiagnostics(reader, attribute.Diagnostics); + break; + case nameof(RequiredAttributeDescriptor.Metadata): + ReadMetadata(reader, attribute.Metadata); + break; + } + }); + }); + } + + private static void ReadAllowedChildTags(JsonReader reader, TagHelperDescriptorBuilder builder) + { + if (!reader.Read()) + { + return; + } + + if (reader.TokenType != JsonToken.StartArray) + { + return; + } + + do + { + ReadAllowedChildTag(reader, builder); + } while (reader.TokenType != JsonToken.EndArray); + } + + private static void ReadAllowedChildTag(JsonReader reader, TagHelperDescriptorBuilder builder) + { + if (!reader.Read()) + { + return; + } + + if (reader.TokenType != JsonToken.StartObject) + { + return; + } + + builder.AllowChildTag(childTag => + { + reader.ReadProperties(propertyName => + { + switch (propertyName) + { + case nameof(AllowedChildTagDescriptor.Name): + if (reader.Read()) + { + var name = (string)reader.Value; + childTag.Name = name; + } + break; + case nameof(AllowedChildTagDescriptor.DisplayName): + if (reader.Read()) + { + var displayName = (string)reader.Value; + childTag.DisplayName = displayName; + } + break; + case nameof(AllowedChildTagDescriptor.Diagnostics): + ReadDiagnostics(reader, childTag.Diagnostics); + break; + } + }); + }); + } + + private static void ReadMetadata(JsonReader reader, IDictionary metadata) + { + if (!reader.Read()) + { + return; + } + + if (reader.TokenType != JsonToken.StartObject) + { + return; + } + + reader.ReadProperties(propertyName => + { + if (reader.Read()) + { + var value = (string)reader.Value; + metadata[propertyName] = value; + } + }); + } + + private static void ReadDiagnostics(JsonReader reader, RazorDiagnosticCollection diagnostics) + { + if (!reader.Read()) + { + return; + } + + if (reader.TokenType != JsonToken.StartArray) + { + return; + } + + do + { + ReadDiagnostic(reader, diagnostics); + } while (reader.TokenType != JsonToken.EndArray); + } + + private static void ReadDiagnostic(JsonReader reader, RazorDiagnosticCollection diagnostics) + { + if (!reader.Read()) + { + return; + } + + if (reader.TokenType != JsonToken.StartObject) + { + return; + } + + string id = default; + int severity = default; + string message = default; + SourceSpan sourceSpan = default; + + reader.ReadProperties(propertyName => + { + switch (propertyName) + { + case nameof(RazorDiagnostic.Id): + if (reader.Read()) + { + id = (string)reader.Value; + } + break; + case nameof(RazorDiagnostic.Severity): + severity = reader.ReadAsInt32().Value; + break; + case "Message": + if (reader.Read()) + { + message = (string)reader.Value; + } + break; + case nameof(RazorDiagnostic.Span): + sourceSpan = ReadSourceSpan(reader); + break; + } + }); + + var descriptor = new RazorDiagnosticDescriptor(id, () => message, (RazorDiagnosticSeverity)severity); + + var diagnostic = RazorDiagnostic.Create(descriptor, sourceSpan); + diagnostics.Add(diagnostic); + } + + private static SourceSpan ReadSourceSpan(JsonReader reader) + { + if (!reader.Read()) + { + return SourceSpan.Undefined; + } + + if (reader.TokenType != JsonToken.StartObject) + { + return SourceSpan.Undefined; + } + + string filePath = default; + int absoluteIndex = default; + int lineIndex = default; + int characterIndex = default; + int length = default; + + reader.ReadProperties(propertyName => + { + switch (propertyName) + { + case nameof(SourceSpan.FilePath): + if (reader.Read()) + { + filePath = (string)reader.Value; + } + break; + case nameof(SourceSpan.AbsoluteIndex): + absoluteIndex = reader.ReadAsInt32().Value; + break; + case nameof(SourceSpan.LineIndex): + lineIndex = reader.ReadAsInt32().Value; + break; + case nameof(SourceSpan.CharacterIndex): + characterIndex = reader.ReadAsInt32().Value; + break; + case nameof(SourceSpan.Length): + length = reader.ReadAsInt32().Value; + break; + } + }); + + var sourceSpan = new SourceSpan(filePath, absoluteIndex, lineIndex, characterIndex, length); + return sourceSpan; } } } diff --git a/src/Razor/perf/Microsoft.AspNetCore.Razor.Performance/taghelpers.json b/src/Shared/RazorShared/taghelpers.json similarity index 100% rename from src/Razor/perf/Microsoft.AspNetCore.Razor.Performance/taghelpers.json rename to src/Shared/RazorShared/taghelpers.json