Skip to content
This repository has been archived by the owner on Dec 19, 2018. It is now read-only.

Commit

Permalink
De-dupe TagHelperDescriptors based on Type for rendering.
Browse files Browse the repository at this point in the history
- This can occur if you have multiple [TargetElement] attributes that overlap. Ultimately the descriptor is the same because its the same type, just the required attributes differ.
- Added tests to validate.

#326
  • Loading branch information
NTaylorMullen committed Mar 20, 2015
1 parent fa40fe6 commit b8432b7
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.TagHelpers;

namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
Expand Down Expand Up @@ -56,11 +55,15 @@ public CSharpTagHelperCodeRenderer([NotNull] IChunkVisitor bodyVisitor,
/// <param name="chunk">A <see cref="TagHelperChunk"/> to render.</param>
public void RenderTagHelper(TagHelperChunk chunk)
{
var tagHelperDescriptors = chunk.Descriptors;
// Remove any duplicate TagHelperDescriptors that referrence the same type name. Duplicates can occur when
// multiple TargetElement attributes are on a TagHelper type and matchs 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);

RenderBeginTagHelperScope(chunk.TagName, chunk.SelfClosing, chunk.Children);

RenderTagHelpersCreation(chunk);
RenderTagHelpersCreation(chunk, tagHelperDescriptors);

var attributeDescriptors = tagHelperDescriptors.SelectMany(descriptor => descriptor.Attributes);
var boundHTMLAttributes = attributeDescriptors.Select(descriptor => descriptor.Name);
Expand Down Expand Up @@ -146,10 +149,10 @@ protected virtual string GenerateUniqueId()
return Guid.NewGuid().ToString("N");
}

private void RenderTagHelpersCreation(TagHelperChunk chunk)
private void RenderTagHelpersCreation(
TagHelperChunk chunk,
IEnumerable<TagHelperDescriptor> tagHelperDescriptors)
{
var tagHelperDescriptors = chunk.Descriptors;

// This is to maintain value accessors for attributes when creating the TagHelpers.
// Ultimately it enables us to do scenarios like this:
// myTagHelper1.Foo = DateTime.Now;
Expand Down Expand Up @@ -551,5 +554,25 @@ 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.OrdinalIgnoreCase);
}

public int GetHashCode(TagHelperDescriptor descriptor)
{
return StringComparer.OrdinalIgnoreCase.GetHashCode(descriptor.TypeName);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,56 @@ private static IEnumerable<TagHelperDescriptor> DefaultPAndInputTagHelperDescrip
private static IEnumerable<TagHelperDescriptor> PrefixedPAndInputTagHelperDescriptors
=> BuildPAndInputTagHelperDescriptors("THS");

private static IEnumerable<TagHelperDescriptor> DuplicateTargetTagHelperDescriptors
{
get
{
var inputTypePropertyInfo = typeof(TestType).GetProperty("Type");
var inputCheckedPropertyInfo = typeof(TestType).GetProperty("Checked");
return new[]
{
new TagHelperDescriptor(
tagName: "*",
typeName: "CatchAllTagHelper",
assemblyName: "SomeAssembly",
attributes: new TagHelperAttributeDescriptor[]
{
new TagHelperAttributeDescriptor("type", inputTypePropertyInfo)
},
requiredAttributes: new[] { "type" }),
new TagHelperDescriptor(
tagName: "*",
typeName: "CatchAllTagHelper",
assemblyName: "SomeAssembly",
attributes: new TagHelperAttributeDescriptor[]
{
new TagHelperAttributeDescriptor("type", inputTypePropertyInfo),
new TagHelperAttributeDescriptor("checked", inputCheckedPropertyInfo)
},
requiredAttributes: new[] { "type", "checked" }),
new TagHelperDescriptor(
tagName: "input",
typeName: "InputTagHelper",
assemblyName: "SomeAssembly",
attributes: new TagHelperAttributeDescriptor[]
{
new TagHelperAttributeDescriptor("type", inputTypePropertyInfo)
},
requiredAttributes: new[] { "type" }),
new TagHelperDescriptor(
tagName: "input",
typeName: "InputTagHelper",
assemblyName: "SomeAssembly",
attributes: new TagHelperAttributeDescriptor[]
{
new TagHelperAttributeDescriptor("type", inputTypePropertyInfo),
new TagHelperAttributeDescriptor("checked", inputCheckedPropertyInfo)
},
requiredAttributes: new[] { "type", "checked" })
};
}
}

private static IEnumerable<TagHelperDescriptor> AttributeTargetingTagHelperDescriptors
{
get
Expand Down Expand Up @@ -93,6 +143,13 @@ public static TheoryData TagHelperDescriptorFlowTestData
DefaultPAndInputTagHelperDescriptors,
false
},
{
"DuplicateTargetTagHelper",
"DuplicateTargetTagHelper",
DuplicateTargetTagHelperDescriptors,
DuplicateTargetTagHelperDescriptors,
false
},
{
"BasicTagHelpers",
"BasicTagHelpers.DesignTime",
Expand Down Expand Up @@ -410,6 +467,7 @@ public static TheoryData RuntimeTimeTagHelperTestData
{ "BasicTagHelpers.RemoveTagHelper", DefaultPAndInputTagHelperDescriptors },
{ "BasicTagHelpers.Prefixed", PrefixedPAndInputTagHelperDescriptors },
{ "ComplexTagHelpers", DefaultPAndInputTagHelperDescriptors },
{ "DuplicateTargetTagHelper", DuplicateTargetTagHelperDescriptors },
{ "EmptyAttributeTagHelpers", DefaultPAndInputTagHelperDescriptors },
{ "EscapedTagHelpers", DefaultPAndInputTagHelperDescriptors },
{ "AttributeTargetingTagHelpers", AttributeTargetingTagHelperDescriptors },
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#pragma checksum "DuplicateTargetTagHelper.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "9cd2f5a40be40d26a0756bf6a74cee58bd13927f"
namespace TestOutput
{
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using System;
using System.Threading.Tasks;

public class DuplicateTargetTagHelper
{
#line hidden
#pragma warning disable 0414
private TagHelperContent __tagHelperStringValueBuffer = null;
#pragma warning restore 0414
private TagHelperExecutionContext __tagHelperExecutionContext = null;
private TagHelperRunner __tagHelperRunner = null;
private TagHelperScopeManager __tagHelperScopeManager = new TagHelperScopeManager();
private InputTagHelper __InputTagHelper = null;
private CatchAllTagHelper __CatchAllTagHelper = null;
#line hidden
public DuplicateTargetTagHelper()
{
}

#pragma warning disable 1998
public override async Task ExecuteAsync()
{
__tagHelperRunner = __tagHelperRunner ?? new TagHelperRunner();
Instrumentation.BeginContext(33, 2, true);
WriteLiteral("\r\n");
Instrumentation.EndContext();
__tagHelperExecutionContext = __tagHelperScopeManager.Begin("input", true, "test", async() => {
}
, StartTagHelperWritingScope, EndTagHelperWritingScope);
__InputTagHelper = CreateTagHelper<InputTagHelper>();
__tagHelperExecutionContext.Add(__InputTagHelper);
__InputTagHelper.Type = "checkbox";
__tagHelperExecutionContext.AddTagHelperAttribute("type", __InputTagHelper.Type);
__CatchAllTagHelper = CreateTagHelper<CatchAllTagHelper>();
__tagHelperExecutionContext.Add(__CatchAllTagHelper);
__CatchAllTagHelper.Type = __InputTagHelper.Type;
__tagHelperExecutionContext.AddHtmlAttribute("checked", "true");
__tagHelperExecutionContext.Output = await __tagHelperRunner.RunAsync(__tagHelperExecutionContext);
await WriteTagHelperAsync(__tagHelperExecutionContext);
__tagHelperExecutionContext = __tagHelperScopeManager.End();
}
#pragma warning restore 1998
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@addTagHelper "something, nice"

<input type="checkbox" checked="true" />

0 comments on commit b8432b7

Please sign in to comment.