From 7dc0508c0361a2f66e3287f5abffe7754bd389eb Mon Sep 17 00:00:00 2001 From: Doug Bunting Date: Sun, 10 May 2015 17:34:58 -0700 Subject: [PATCH] Add `[HtmlAttributeName(..., DictionaryAttributePrefix="prefix")]` part 1 - related to #89 because we need more descriptor comparers in more places - separate `TagHelperAttributeDescriptorComparer` and `TypeBasedTagHelperDescriptorComparer` - encourages reuse and most will soon be used in multiple classes - add `null` checks to `EquivalenceComparer` - also give parameters better names nits: - use `` in `TagHelperDescriptorComparer` - also give it an explicit constructor - make product comparers easier to subclass - base test `CaseSensitiveTagHelperAttributeDescriptorComparer` on product code --- .../CSharp/CSharpTagHelperCodeRenderer.cs | 39 +----------- .../Parser/SyntaxTree/EquivalenceComparer.cs | 14 +++-- .../TagHelpers/TagHelperBlockRewriter.cs | 17 ------ .../TagHelperAttributeDescriptorComparer.cs | 59 +++++++++++++++++++ .../TagHelpers/TagHelperDescriptorComparer.cs | 51 ++++++++-------- .../TypeBasedTagHelperDescriptorComparer.cs | 58 ++++++++++++++++++ ...iveTagHelperAttributeDescriptorComparer.cs | 27 +++++---- ...aseSensitiveTagHelperDescriptorComparer.cs | 18 +++--- 8 files changed, 176 insertions(+), 107 deletions(-) create mode 100644 src/Microsoft.AspNet.Razor/TagHelpers/TagHelperAttributeDescriptorComparer.cs create mode 100644 src/Microsoft.AspNet.Razor/TagHelpers/TypeBasedTagHelperDescriptorComparer.cs diff --git a/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpTagHelperCodeRenderer.cs b/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpTagHelperCodeRenderer.cs index c5555b415b..08abfc1f31 100644 --- a/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpTagHelperCodeRenderer.cs +++ b/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpTagHelperCodeRenderer.cs @@ -20,9 +20,6 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp internal static readonly string ScopeManagerVariableName = "__tagHelperScopeManager"; internal static readonly string RunnerVariableName = "__tagHelperRunner"; - private static readonly TagHelperAttributeDescriptorComparer AttributeDescriptorComparer = - new TagHelperAttributeDescriptorComparer(); - private readonly CSharpCodeWriter _writer; private readonly CodeBuilderContext _context; private readonly IChunkVisitor _bodyVisitor; @@ -64,7 +61,7 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp // multiple TargetElement attributes are on a TagHelper type and matches overlap for an HTML element. // Having more than one descriptor with the same TagHelper type results in generated code that runs // the same TagHelper X many times (instead of once) over a single HTML element. - var tagHelperDescriptors = chunk.Descriptors.Distinct(TypeNameTagHelperDescriptorComparer.Default); + var tagHelperDescriptors = chunk.Descriptors.Distinct(TypeBasedTagHelperDescriptorComparer.Default); RenderBeginTagHelperScope(chunk.TagName, chunk.SelfClosing, chunk.Children); @@ -577,39 +574,5 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp } } } - - // This class is used to compare tag helper attributes by comparing only the HTML attribute name. - private class TagHelperAttributeDescriptorComparer : IEqualityComparer - { - public bool Equals(TagHelperAttributeDescriptor descriptorX, TagHelperAttributeDescriptor descriptorY) - { - return string.Equals(descriptorX.Name, descriptorY.Name, StringComparison.OrdinalIgnoreCase); - } - - public int GetHashCode(TagHelperAttributeDescriptor descriptor) - { - return StringComparer.OrdinalIgnoreCase.GetHashCode(descriptor.Name); - } - } - - private class TypeNameTagHelperDescriptorComparer : IEqualityComparer - { - public static readonly TypeNameTagHelperDescriptorComparer Default = - new TypeNameTagHelperDescriptorComparer(); - - private TypeNameTagHelperDescriptorComparer() - { - } - - public bool Equals(TagHelperDescriptor descriptorX, TagHelperDescriptor descriptorY) - { - return string.Equals(descriptorX.TypeName, descriptorY.TypeName, StringComparison.Ordinal); - } - - public int GetHashCode(TagHelperDescriptor descriptor) - { - return StringComparer.Ordinal.GetHashCode(descriptor.TypeName); - } - } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Razor/Parser/SyntaxTree/EquivalenceComparer.cs b/src/Microsoft.AspNet.Razor/Parser/SyntaxTree/EquivalenceComparer.cs index e7f2cfdbd1..e12ee9901a 100644 --- a/src/Microsoft.AspNet.Razor/Parser/SyntaxTree/EquivalenceComparer.cs +++ b/src/Microsoft.AspNet.Razor/Parser/SyntaxTree/EquivalenceComparer.cs @@ -2,19 +2,25 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; +using Microsoft.Framework.Internal; namespace Microsoft.AspNet.Razor.Parser.SyntaxTree { internal class EquivalenceComparer : IEqualityComparer { - public bool Equals(SyntaxTreeNode x, SyntaxTreeNode y) + public bool Equals(SyntaxTreeNode nodeX, SyntaxTreeNode nodeY) { - return x.EquivalentTo(y); + if (nodeX == nodeY) + { + return true; + } + + return nodeX != null && nodeX.EquivalentTo(nodeY); } - public int GetHashCode(SyntaxTreeNode obj) + public int GetHashCode([NotNull] SyntaxTreeNode node) { - return obj.GetEquivalenceHash(); + return node.GetEquivalenceHash(); } } } diff --git a/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperBlockRewriter.cs b/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperBlockRewriter.cs index cefb2172ba..a4e046355d 100644 --- a/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperBlockRewriter.cs +++ b/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperBlockRewriter.cs @@ -474,22 +474,5 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal return htmlSymbol.Type == HtmlSymbolType.DoubleQuote || htmlSymbol.Type == HtmlSymbolType.SingleQuote; } - - // This class is used to compare tag helper attributes by comparing only the HTML attribute name. - private class TagHelperAttributeDescriptorComparer : IEqualityComparer - { - public static readonly TagHelperAttributeDescriptorComparer Default = - new TagHelperAttributeDescriptorComparer(); - - public bool Equals(TagHelperAttributeDescriptor descriptorX, TagHelperAttributeDescriptor descriptorY) - { - return string.Equals(descriptorX.Name, descriptorY.Name, StringComparison.OrdinalIgnoreCase); - } - - public int GetHashCode(TagHelperAttributeDescriptor descriptor) - { - return StringComparer.OrdinalIgnoreCase.GetHashCode(descriptor.Name); - } - } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperAttributeDescriptorComparer.cs b/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperAttributeDescriptorComparer.cs new file mode 100644 index 0000000000..fa2b082d2d --- /dev/null +++ b/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperAttributeDescriptorComparer.cs @@ -0,0 +1,59 @@ +// 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 Microsoft.Framework.Internal; +using Microsoft.Internal.Web.Utils; + +namespace Microsoft.AspNet.Razor.TagHelpers +{ + /// + /// An used to check equality between + /// two s. + /// + public class TagHelperAttributeDescriptorComparer : IEqualityComparer + { + /// + /// A default instance of the . + /// + public static readonly TagHelperAttributeDescriptorComparer Default = + new TagHelperAttributeDescriptorComparer(); + + /// + /// Initializes a new instance. + /// + protected TagHelperAttributeDescriptorComparer() + { + } + + /// + /// + /// Determines equality based on , + /// , + /// and . + /// + public virtual bool Equals(TagHelperAttributeDescriptor descriptorX, TagHelperAttributeDescriptor descriptorY) + { + if (descriptorX == descriptorY) + { + return true; + } + + return descriptorX != null && + string.Equals(descriptorX.Name, descriptorY.Name, StringComparison.OrdinalIgnoreCase) && + string.Equals(descriptorX.PropertyName, descriptorY.PropertyName, StringComparison.Ordinal) && + string.Equals(descriptorX.TypeName, descriptorY.TypeName, StringComparison.Ordinal); + } + + /// + public virtual int GetHashCode([NotNull] TagHelperAttributeDescriptor descriptor) + { + return HashCodeCombiner.Start() + .Add(descriptor.Name, StringComparer.OrdinalIgnoreCase) + .Add(descriptor.PropertyName, StringComparer.Ordinal) + .Add(descriptor.TypeName, StringComparer.Ordinal) + .CombinedHash; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperDescriptorComparer.cs b/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperDescriptorComparer.cs index 74ee054fec..8eb961b0c4 100644 --- a/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperDescriptorComparer.cs +++ b/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperDescriptorComparer.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.Framework.Internal; using Microsoft.Internal.Web.Utils; namespace Microsoft.AspNet.Razor.TagHelpers @@ -20,43 +21,39 @@ namespace Microsoft.AspNet.Razor.TagHelpers public static readonly TagHelperDescriptorComparer Default = new TagHelperDescriptorComparer(); /// - /// Determines if the two given tag helpers are equal. + /// Initializes a new instance. /// - /// A to compare with the given - /// . - /// A to compare with the given - /// . - /// true if and are equal, - /// false otherwise. + protected TagHelperDescriptorComparer() + { + } + + /// /// /// Determines equality based on , /// , , /// and . /// - public bool Equals(TagHelperDescriptor descriptorX, TagHelperDescriptor descriptorY) + public virtual bool Equals(TagHelperDescriptor descriptorX, TagHelperDescriptor descriptorY) { - return string.Equals(descriptorX.TypeName, descriptorY.TypeName, StringComparison.Ordinal) && - string.Equals(descriptorX.TagName, descriptorY.TagName, StringComparison.OrdinalIgnoreCase) && - string.Equals(descriptorX.AssemblyName, descriptorY.AssemblyName, StringComparison.Ordinal) && - Enumerable.SequenceEqual( - descriptorX.RequiredAttributes.OrderBy( - attribute => attribute, - StringComparer.OrdinalIgnoreCase), - descriptorY.RequiredAttributes.OrderBy( - attribute => attribute, - StringComparer.OrdinalIgnoreCase), - StringComparer.OrdinalIgnoreCase); + if (descriptorX == descriptorY) + { + return true; + } + + return descriptorX != null && + string.Equals(descriptorX.TypeName, descriptorY.TypeName, StringComparison.Ordinal) && + string.Equals(descriptorX.TagName, descriptorY.TagName, StringComparison.OrdinalIgnoreCase) && + string.Equals(descriptorX.AssemblyName, descriptorY.AssemblyName, StringComparison.Ordinal) && + Enumerable.SequenceEqual( + descriptorX.RequiredAttributes.OrderBy(attribute => attribute, StringComparer.OrdinalIgnoreCase), + descriptorY.RequiredAttributes.OrderBy(attribute => attribute, StringComparer.OrdinalIgnoreCase), + StringComparer.OrdinalIgnoreCase); } - /// - /// Returns an value that uniquely identifies the given . - /// - /// The to create a hash code for. - /// An that uniquely identifies the given . - public int GetHashCode(TagHelperDescriptor descriptor) + /// + public virtual int GetHashCode([NotNull] TagHelperDescriptor descriptor) { - var hashCodeCombiner = HashCodeCombiner - .Start() + var hashCodeCombiner = HashCodeCombiner.Start() .Add(descriptor.TypeName, StringComparer.Ordinal) .Add(descriptor.TagName, StringComparer.OrdinalIgnoreCase) .Add(descriptor.AssemblyName, StringComparer.Ordinal); diff --git a/src/Microsoft.AspNet.Razor/TagHelpers/TypeBasedTagHelperDescriptorComparer.cs b/src/Microsoft.AspNet.Razor/TagHelpers/TypeBasedTagHelperDescriptorComparer.cs new file mode 100644 index 0000000000..2e3356ab17 --- /dev/null +++ b/src/Microsoft.AspNet.Razor/TagHelpers/TypeBasedTagHelperDescriptorComparer.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 Microsoft.Framework.Internal; +using Microsoft.Internal.Web.Utils; + +namespace Microsoft.AspNet.Razor.TagHelpers +{ + /// + /// An that checks equality between two + /// s using only their s and + /// s. + /// + /// + /// This class is intended for scenarios where Reflection-based information is all important i.e. + /// , , and related + /// properties are not relevant. + /// + public class TypeBasedTagHelperDescriptorComparer : IEqualityComparer + { + /// + /// A default instance of the . + /// + public static readonly TypeBasedTagHelperDescriptorComparer Default = + new TypeBasedTagHelperDescriptorComparer(); + + private TypeBasedTagHelperDescriptorComparer() + { + } + + /// + /// + /// Determines equality based on and + /// . + /// + public bool Equals(TagHelperDescriptor descriptorX, TagHelperDescriptor descriptorY) + { + if (descriptorX == descriptorY) + { + return true; + } + + return string.Equals(descriptorX.AssemblyName, descriptorY.AssemblyName, StringComparison.Ordinal) && + string.Equals(descriptorX.TypeName, descriptorY.TypeName, StringComparison.Ordinal); + } + + /// + public int GetHashCode([NotNull] TagHelperDescriptor descriptor) + { + return HashCodeCombiner.Start() + .Add(descriptor.AssemblyName, StringComparer.Ordinal) + .Add(descriptor.TypeName, StringComparer.Ordinal) + .CombinedHash; + } + } +} diff --git a/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/CaseSensitiveTagHelperAttributeDescriptorComparer.cs b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/CaseSensitiveTagHelperAttributeDescriptorComparer.cs index 9e7380409d..cbd9655812 100644 --- a/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/CaseSensitiveTagHelperAttributeDescriptorComparer.cs +++ b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/CaseSensitiveTagHelperAttributeDescriptorComparer.cs @@ -2,37 +2,38 @@ // 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 Microsoft.AspNet.Razor.TagHelpers; using Microsoft.Internal.Web.Utils; namespace Microsoft.AspNet.Razor.Runtime.TagHelpers { - public class CaseSensitiveTagHelperAttributeDescriptorComparer : IEqualityComparer + public class CaseSensitiveTagHelperAttributeDescriptorComparer : TagHelperAttributeDescriptorComparer { - public static readonly CaseSensitiveTagHelperAttributeDescriptorComparer Default = + public new static readonly CaseSensitiveTagHelperAttributeDescriptorComparer Default = new CaseSensitiveTagHelperAttributeDescriptorComparer(); private CaseSensitiveTagHelperAttributeDescriptorComparer() + : base() { } - public bool Equals(TagHelperAttributeDescriptor descriptorX, TagHelperAttributeDescriptor descriptorY) + public override bool Equals(TagHelperAttributeDescriptor descriptorX, TagHelperAttributeDescriptor descriptorY) { - return + if (descriptorX == descriptorY) + { + return true; + } + + return base.Equals(descriptorX, descriptorY) && // Normal comparer doesn't care about case, in tests we do. - string.Equals(descriptorX.Name, descriptorY.Name, StringComparison.Ordinal) && - string.Equals(descriptorX.PropertyName, descriptorY.PropertyName, StringComparison.Ordinal) && - string.Equals(descriptorX.TypeName, descriptorY.TypeName, StringComparison.Ordinal); + string.Equals(descriptorX.Name, descriptorY.Name, StringComparison.Ordinal); } - public int GetHashCode(TagHelperAttributeDescriptor descriptor) + public override int GetHashCode(TagHelperAttributeDescriptor descriptor) { - return HashCodeCombiner - .Start() + return HashCodeCombiner.Start() + .Add(base.GetHashCode(descriptor)) .Add(descriptor.Name, StringComparer.Ordinal) - .Add(descriptor.PropertyName, StringComparer.Ordinal) - .Add(descriptor.TypeName, StringComparer.Ordinal) .CombinedHash; } } diff --git a/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/CaseSensitiveTagHelperDescriptorComparer.cs b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/CaseSensitiveTagHelperDescriptorComparer.cs index a5bc7a83da..3bdfade638 100644 --- a/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/CaseSensitiveTagHelperDescriptorComparer.cs +++ b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/CaseSensitiveTagHelperDescriptorComparer.cs @@ -2,26 +2,29 @@ // 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.Linq; using Microsoft.AspNet.Razor.TagHelpers; using Microsoft.Internal.Web.Utils; namespace Microsoft.AspNet.Razor.Runtime.TagHelpers { - public class CaseSensitiveTagHelperDescriptorComparer : TagHelperDescriptorComparer, IEqualityComparer + public class CaseSensitiveTagHelperDescriptorComparer : TagHelperDescriptorComparer { public new static readonly CaseSensitiveTagHelperDescriptorComparer Default = new CaseSensitiveTagHelperDescriptorComparer(); private CaseSensitiveTagHelperDescriptorComparer() + : base() { } - bool IEqualityComparer.Equals( - TagHelperDescriptor descriptorX, - TagHelperDescriptor descriptorY) + public override bool Equals(TagHelperDescriptor descriptorX, TagHelperDescriptor descriptorY) { + if (descriptorX == descriptorY) + { + return true; + } + return base.Equals(descriptorX, descriptorY) && // Normal comparer doesn't care about the case, required attribute order, attributes or prefixes. // In tests we do. @@ -36,10 +39,9 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers CaseSensitiveTagHelperAttributeDescriptorComparer.Default); } - int IEqualityComparer.GetHashCode(TagHelperDescriptor descriptor) + public override int GetHashCode(TagHelperDescriptor descriptor) { - var hashCodeCombiner = HashCodeCombiner - .Start() + var hashCodeCombiner = HashCodeCombiner.Start() .Add(base.GetHashCode(descriptor)) .Add(descriptor.TagName, StringComparer.Ordinal) .Add(descriptor.Prefix);