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 `<inheritdoc/>` in `TagHelperDescriptorComparer`
 - also give it an explicit constructor
- make product comparers easier to subclass
 - base test `CaseSensitiveTagHelperAttributeDescriptorComparer` on product code
This commit is contained in:
Doug Bunting 2015-05-10 17:34:58 -07:00
parent 959616cb9c
commit 7dc0508c03
8 changed files with 176 additions and 107 deletions

View File

@ -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<TagHelperAttributeDescriptor>
{
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<TagHelperDescriptor>
{
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);
}
}
}
}

View File

@ -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<SyntaxTreeNode>
{
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();
}
}
}

View File

@ -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<TagHelperAttributeDescriptor>
{
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);
}
}
}
}

View File

@ -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
{
/// <summary>
/// An <see cref="IEqualityComparer{TagHelperAttributeDescriptor}"/> used to check equality between
/// two <see cref="TagHelperAttributeDescriptor"/>s.
/// </summary>
public class TagHelperAttributeDescriptorComparer : IEqualityComparer<TagHelperAttributeDescriptor>
{
/// <summary>
/// A default instance of the <see cref="TagHelperAttributeDescriptorComparer"/>.
/// </summary>
public static readonly TagHelperAttributeDescriptorComparer Default =
new TagHelperAttributeDescriptorComparer();
/// <summary>
/// Initializes a new <see cref="TagHelperAttributeDescriptorComparer"/> instance.
/// </summary>
protected TagHelperAttributeDescriptorComparer()
{
}
/// <inheritdoc />
/// <remarks>
/// Determines equality based on <see cref="TagHelperAttributeDescriptor.Name"/>,
/// <see cref="TagHelperAttributeDescriptor.PropertyName"/>,
/// and <see cref="TagHelperAttributeDescriptor.TypeName"/>.
/// </remarks>
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);
}
/// <inheritdoc />
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;
}
}
}

View File

@ -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();
/// <summary>
/// Determines if the two given tag helpers are equal.
/// Initializes a new <see cref="TagHelperDescriptorComparer"/> instance.
/// </summary>
/// <param name="descriptorX">A <see cref="TagHelperDescriptor"/> to compare with the given
/// <paramref name="descriptorY"/>.</param>
/// <param name="descriptorY">A <see cref="TagHelperDescriptor"/> to compare with the given
/// <paramref name="descriptorX"/>.</param>
/// <returns><c>true</c> if <paramref name="descriptorX"/> and <paramref name="descriptorY"/> are equal,
/// <c>false</c> otherwise.</returns>
protected TagHelperDescriptorComparer()
{
}
/// <inheritdoc />
/// <remarks>
/// Determines equality based on <see cref="TagHelperDescriptor.TypeName"/>,
/// <see cref="TagHelperDescriptor.AssemblyName"/>, <see cref="TagHelperDescriptor.TagName"/>,
/// and <see cref="TagHelperDescriptor.RequiredAttributes"/>.
/// </remarks>
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);
}
/// <summary>
/// Returns an <see cref="int"/> value that uniquely identifies the given <see cref="TagHelperDescriptor"/>.
/// </summary>
/// <param name="descriptor">The <see cref="TagHelperDescriptor"/> to create a hash code for.</param>
/// <returns>An <see cref="int"/> that uniquely identifies the given <paramref name="descriptor"/>.</returns>
public int GetHashCode(TagHelperDescriptor descriptor)
/// <inheritdoc />
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);

View File

@ -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
{
/// <summary>
/// An <see cref="IEqualityComparer{TagHelperDescriptor}"/> that checks equality between two
/// <see cref="TagHelperDescriptor"/>s using only their <see cref="TagHelperDescriptor.AssemblyName"/>s and
/// <see cref="TagHelperDescriptor.TypeName"/>s.
/// </summary>
/// <remarks>
/// This class is intended for scenarios where Reflection-based information is all important i.e.
/// <see cref="TagHelperDescriptor.RequiredAttributes"/>, <see cref="TagHelperDescriptor.TagName"/>, and related
/// properties are not relevant.
/// </remarks>
public class TypeBasedTagHelperDescriptorComparer : IEqualityComparer<TagHelperDescriptor>
{
/// <summary>
/// A default instance of the <see cref="TypeBasedTagHelperDescriptorComparer"/>.
/// </summary>
public static readonly TypeBasedTagHelperDescriptorComparer Default =
new TypeBasedTagHelperDescriptorComparer();
private TypeBasedTagHelperDescriptorComparer()
{
}
/// <inheritdoc />
/// <remarks>
/// Determines equality based on <see cref="TagHelperDescriptor.AssemblyName"/> and
/// <see cref="TagHelperDescriptor.TypeName"/>.
/// </remarks>
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);
}
/// <inheritdoc />
public int GetHashCode([NotNull] TagHelperDescriptor descriptor)
{
return HashCodeCombiner.Start()
.Add(descriptor.AssemblyName, StringComparer.Ordinal)
.Add(descriptor.TypeName, StringComparer.Ordinal)
.CombinedHash;
}
}
}

View File

@ -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<TagHelperAttributeDescriptor>
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;
}
}

View File

@ -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<TagHelperDescriptor>
public class CaseSensitiveTagHelperDescriptorComparer : TagHelperDescriptorComparer
{
public new static readonly CaseSensitiveTagHelperDescriptorComparer Default =
new CaseSensitiveTagHelperDescriptorComparer();
private CaseSensitiveTagHelperDescriptorComparer()
: base()
{
}
bool IEqualityComparer<TagHelperDescriptor>.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<TagHelperDescriptor>.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);