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 ScopeManagerVariableName = "__tagHelperScopeManager";
internal static readonly string RunnerVariableName = "__tagHelperRunner"; internal static readonly string RunnerVariableName = "__tagHelperRunner";
private static readonly TagHelperAttributeDescriptorComparer AttributeDescriptorComparer =
new TagHelperAttributeDescriptorComparer();
private readonly CSharpCodeWriter _writer; private readonly CSharpCodeWriter _writer;
private readonly CodeBuilderContext _context; private readonly CodeBuilderContext _context;
private readonly IChunkVisitor _bodyVisitor; 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. // 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 // 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. // 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); 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Razor.Parser.SyntaxTree namespace Microsoft.AspNet.Razor.Parser.SyntaxTree
{ {
internal class EquivalenceComparer : IEqualityComparer<SyntaxTreeNode> 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 || return htmlSymbol.Type == HtmlSymbolType.DoubleQuote ||
htmlSymbol.Type == HtmlSymbolType.SingleQuote; 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Microsoft.Framework.Internal;
using Microsoft.Internal.Web.Utils; using Microsoft.Internal.Web.Utils;
namespace Microsoft.AspNet.Razor.TagHelpers namespace Microsoft.AspNet.Razor.TagHelpers
@ -20,43 +21,39 @@ namespace Microsoft.AspNet.Razor.TagHelpers
public static readonly TagHelperDescriptorComparer Default = new TagHelperDescriptorComparer(); public static readonly TagHelperDescriptorComparer Default = new TagHelperDescriptorComparer();
/// <summary> /// <summary>
/// Determines if the two given tag helpers are equal. /// Initializes a new <see cref="TagHelperDescriptorComparer"/> instance.
/// </summary> /// </summary>
/// <param name="descriptorX">A <see cref="TagHelperDescriptor"/> to compare with the given protected TagHelperDescriptorComparer()
/// <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, /// <inheritdoc />
/// <c>false</c> otherwise.</returns>
/// <remarks> /// <remarks>
/// Determines equality based on <see cref="TagHelperDescriptor.TypeName"/>, /// Determines equality based on <see cref="TagHelperDescriptor.TypeName"/>,
/// <see cref="TagHelperDescriptor.AssemblyName"/>, <see cref="TagHelperDescriptor.TagName"/>, /// <see cref="TagHelperDescriptor.AssemblyName"/>, <see cref="TagHelperDescriptor.TagName"/>,
/// and <see cref="TagHelperDescriptor.RequiredAttributes"/>. /// and <see cref="TagHelperDescriptor.RequiredAttributes"/>.
/// </remarks> /// </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) && if (descriptorX == descriptorY)
string.Equals(descriptorX.TagName, descriptorY.TagName, StringComparison.OrdinalIgnoreCase) && {
string.Equals(descriptorX.AssemblyName, descriptorY.AssemblyName, StringComparison.Ordinal) && return true;
Enumerable.SequenceEqual( }
descriptorX.RequiredAttributes.OrderBy(
attribute => attribute, return descriptorX != null &&
StringComparer.OrdinalIgnoreCase), string.Equals(descriptorX.TypeName, descriptorY.TypeName, StringComparison.Ordinal) &&
descriptorY.RequiredAttributes.OrderBy( string.Equals(descriptorX.TagName, descriptorY.TagName, StringComparison.OrdinalIgnoreCase) &&
attribute => attribute, string.Equals(descriptorX.AssemblyName, descriptorY.AssemblyName, StringComparison.Ordinal) &&
StringComparer.OrdinalIgnoreCase), Enumerable.SequenceEqual(
StringComparer.OrdinalIgnoreCase); descriptorX.RequiredAttributes.OrderBy(attribute => attribute, StringComparer.OrdinalIgnoreCase),
descriptorY.RequiredAttributes.OrderBy(attribute => attribute, StringComparer.OrdinalIgnoreCase),
StringComparer.OrdinalIgnoreCase);
} }
/// <summary> /// <inheritdoc />
/// Returns an <see cref="int"/> value that uniquely identifies the given <see cref="TagHelperDescriptor"/>. public virtual int GetHashCode([NotNull] TagHelperDescriptor descriptor)
/// </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)
{ {
var hashCodeCombiner = HashCodeCombiner var hashCodeCombiner = HashCodeCombiner.Start()
.Start()
.Add(descriptor.TypeName, StringComparer.Ordinal) .Add(descriptor.TypeName, StringComparer.Ordinal)
.Add(descriptor.TagName, StringComparer.OrdinalIgnoreCase) .Add(descriptor.TagName, StringComparer.OrdinalIgnoreCase)
.Add(descriptor.AssemblyName, StringComparer.Ordinal); .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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic;
using Microsoft.AspNet.Razor.TagHelpers; using Microsoft.AspNet.Razor.TagHelpers;
using Microsoft.Internal.Web.Utils; using Microsoft.Internal.Web.Utils;
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers 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(); new CaseSensitiveTagHelperAttributeDescriptorComparer();
private 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. // Normal comparer doesn't care about case, in tests we do.
string.Equals(descriptorX.Name, descriptorY.Name, StringComparison.Ordinal) && string.Equals(descriptorX.Name, descriptorY.Name, StringComparison.Ordinal);
string.Equals(descriptorX.PropertyName, descriptorY.PropertyName, StringComparison.Ordinal) &&
string.Equals(descriptorX.TypeName, descriptorY.TypeName, StringComparison.Ordinal);
} }
public int GetHashCode(TagHelperAttributeDescriptor descriptor) public override int GetHashCode(TagHelperAttributeDescriptor descriptor)
{ {
return HashCodeCombiner return HashCodeCombiner.Start()
.Start() .Add(base.GetHashCode(descriptor))
.Add(descriptor.Name, StringComparer.Ordinal) .Add(descriptor.Name, StringComparer.Ordinal)
.Add(descriptor.PropertyName, StringComparer.Ordinal)
.Add(descriptor.TypeName, StringComparer.Ordinal)
.CombinedHash; .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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using Microsoft.AspNet.Razor.TagHelpers; using Microsoft.AspNet.Razor.TagHelpers;
using Microsoft.Internal.Web.Utils; using Microsoft.Internal.Web.Utils;
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
{ {
public class CaseSensitiveTagHelperDescriptorComparer : TagHelperDescriptorComparer, IEqualityComparer<TagHelperDescriptor> public class CaseSensitiveTagHelperDescriptorComparer : TagHelperDescriptorComparer
{ {
public new static readonly CaseSensitiveTagHelperDescriptorComparer Default = public new static readonly CaseSensitiveTagHelperDescriptorComparer Default =
new CaseSensitiveTagHelperDescriptorComparer(); new CaseSensitiveTagHelperDescriptorComparer();
private CaseSensitiveTagHelperDescriptorComparer() private CaseSensitiveTagHelperDescriptorComparer()
: base()
{ {
} }
bool IEqualityComparer<TagHelperDescriptor>.Equals( public override bool Equals(TagHelperDescriptor descriptorX, TagHelperDescriptor descriptorY)
TagHelperDescriptor descriptorX,
TagHelperDescriptor descriptorY)
{ {
if (descriptorX == descriptorY)
{
return true;
}
return base.Equals(descriptorX, descriptorY) && return base.Equals(descriptorX, descriptorY) &&
// Normal comparer doesn't care about the case, required attribute order, attributes or prefixes. // Normal comparer doesn't care about the case, required attribute order, attributes or prefixes.
// In tests we do. // In tests we do.
@ -36,10 +39,9 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
CaseSensitiveTagHelperAttributeDescriptorComparer.Default); CaseSensitiveTagHelperAttributeDescriptorComparer.Default);
} }
int IEqualityComparer<TagHelperDescriptor>.GetHashCode(TagHelperDescriptor descriptor) public override int GetHashCode(TagHelperDescriptor descriptor)
{ {
var hashCodeCombiner = HashCodeCombiner var hashCodeCombiner = HashCodeCombiner.Start()
.Start()
.Add(base.GetHashCode(descriptor)) .Add(base.GetHashCode(descriptor))
.Add(descriptor.TagName, StringComparer.Ordinal) .Add(descriptor.TagName, StringComparer.Ordinal)
.Add(descriptor.Prefix); .Add(descriptor.Prefix);