Use the TagHelperBinder to dedupe taghelpers
This change does deduplication of taghelpers during the binding/rewriting phase. This is needed when a taghelper has multiple sets of html attributes that are required (behaves like an OR). This is used lots in MVC. The old codebase used to do this in the codegen phase, but it seems beneficial to do as early as possible.
This commit is contained in:
parent
98d5d1c70e
commit
d187edbd76
|
|
@ -19,6 +19,12 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
|
|||
/// </summary>
|
||||
public static readonly TagHelperDescriptorComparer Default = new TagHelperDescriptorComparer();
|
||||
|
||||
/// <summary>
|
||||
/// An instance of <see cref="TagHelperDescriptorComparer"/> that only compares
|
||||
/// <see cref="TagHelperDescriptor.TypeName"/>.
|
||||
/// </summary>
|
||||
public static readonly TagHelperDescriptorComparer TypeName = new TypeNameTagHelperDescriptorComparer();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="TagHelperDescriptorComparer"/> instance.
|
||||
/// </summary>
|
||||
|
|
@ -101,5 +107,34 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
|
|||
|
||||
return hashCodeCombiner.CombinedHash;
|
||||
}
|
||||
|
||||
private class TypeNameTagHelperDescriptorComparer : TagHelperDescriptorComparer
|
||||
{
|
||||
public override bool Equals(TagHelperDescriptor descriptorX, TagHelperDescriptor descriptorY)
|
||||
{
|
||||
if (object.ReferenceEquals(descriptorX, descriptorY))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (descriptorX == null ^ descriptorY == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return string.Equals(descriptorX.TypeName, descriptorY.TypeName, StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
|
||||
public override int GetHashCode(TagHelperDescriptor descriptor)
|
||||
{
|
||||
if (descriptor == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(descriptor));
|
||||
}
|
||||
|
||||
return descriptor.TypeName == null ? 0 : StringComparer.Ordinal.GetHashCode(descriptor.TypeName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -86,7 +86,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
|
|||
}
|
||||
}
|
||||
|
||||
return applicableDescriptors;
|
||||
return applicableDescriptors.Distinct(TagHelperDescriptorComparer.TypeName);
|
||||
}
|
||||
|
||||
private bool HasRequiredParentTag(
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
|
|||
var catchAllDescriptor2 = new TagHelperDescriptor
|
||||
{
|
||||
TagName = TagHelperDescriptorProvider.ElementCatchAllTarget,
|
||||
TypeName = "CatchAllTagHelper",
|
||||
TypeName = "CatchAllTagHelper2",
|
||||
AssemblyName = "SomeAssembly",
|
||||
RequiredAttributes = new[]
|
||||
{
|
||||
|
|
@ -268,7 +268,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
|
|||
var provider = new TagHelperDescriptorProvider((IEnumerable<TagHelperDescriptor>)availableDescriptors);
|
||||
|
||||
// Act
|
||||
var resolvedDescriptors = provider.GetDescriptors(tagName, providedAttributes, parentTagName: "p");
|
||||
var resolvedDescriptors = provider.GetDescriptors(tagName, providedAttributes, parentTagName: "p").ToArray();
|
||||
|
||||
// Assert
|
||||
Assert.Equal((IEnumerable<TagHelperDescriptor>)expectedDescriptors, resolvedDescriptors, CaseSensitiveTagHelperDescriptorComparer.Default);
|
||||
|
|
@ -295,6 +295,57 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
|
|||
Assert.Empty(resolvedDescriptors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDescriptors_DeduplicatesTagHelpersByTypeName()
|
||||
{
|
||||
// Arrange
|
||||
var descriptors = new[]
|
||||
{
|
||||
new TagHelperDescriptor
|
||||
{
|
||||
AssemblyName = "TestAssembly",
|
||||
TagName = "form",
|
||||
TypeName = "TestFormTagHelper",
|
||||
RequiredAttributes = new List<TagHelperRequiredAttributeDescriptor>()
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor()
|
||||
{
|
||||
Name = "a",
|
||||
NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch
|
||||
}
|
||||
},
|
||||
},
|
||||
new TagHelperDescriptor
|
||||
{
|
||||
AssemblyName = "TestAssembly",
|
||||
TagName = "form",
|
||||
TypeName = "TestFormTagHelper",
|
||||
RequiredAttributes = new List<TagHelperRequiredAttributeDescriptor>()
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor()
|
||||
{
|
||||
Name = "b",
|
||||
NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
var provider = new TagHelperDescriptorProvider(descriptors);
|
||||
|
||||
// Act
|
||||
var resolvedDescriptors = provider.GetDescriptors(
|
||||
tagName: "form",
|
||||
attributes: new List<KeyValuePair<string, string>>()
|
||||
{
|
||||
new KeyValuePair<string, string>("a", "hi" ),
|
||||
new KeyValuePair<string, string>("b", "there"),
|
||||
},
|
||||
parentTagName: "p");
|
||||
|
||||
// Assert
|
||||
Assert.Same(descriptors[0], Assert.Single(resolvedDescriptors));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDescriptors_OnlyUnderstandsSinglePrefix()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -55,6 +55,71 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
|||
Assert.Equal("input", inputTagHelper.TagName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_RewritesTagHelpers_TagHelperMatchesElementTwice()
|
||||
{
|
||||
// Arrange
|
||||
var engine = RazorEngine.Create(builder =>
|
||||
{
|
||||
builder.AddTagHelpers(new[]
|
||||
{
|
||||
new TagHelperDescriptor
|
||||
{
|
||||
AssemblyName = "TestAssembly",
|
||||
TagName = "form",
|
||||
TypeName = "TestFormTagHelper",
|
||||
RequiredAttributes = new List<TagHelperRequiredAttributeDescriptor>()
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor()
|
||||
{
|
||||
Name = "a",
|
||||
NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch
|
||||
}
|
||||
},
|
||||
},
|
||||
new TagHelperDescriptor
|
||||
{
|
||||
AssemblyName = "TestAssembly",
|
||||
TagName = "form",
|
||||
TypeName = "TestFormTagHelper",
|
||||
RequiredAttributes = new List<TagHelperRequiredAttributeDescriptor>()
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor()
|
||||
{
|
||||
Name = "b",
|
||||
NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
var pass = new TagHelperBinderSyntaxTreePass()
|
||||
{
|
||||
Engine = engine,
|
||||
};
|
||||
|
||||
var content =
|
||||
@"
|
||||
@addTagHelper *, TestAssembly
|
||||
<form a=""hi"" b=""there"">
|
||||
</form>";
|
||||
var sourceDocument = TestRazorSourceDocument.Create(content);
|
||||
var codeDocument = RazorCodeDocument.Create(sourceDocument);
|
||||
var originalTree = RazorSyntaxTree.Parse(sourceDocument);
|
||||
|
||||
// Act
|
||||
var rewrittenTree = pass.Execute(codeDocument, originalTree);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(rewrittenTree.Diagnostics);
|
||||
Assert.Equal(3, rewrittenTree.Root.Children.Count);
|
||||
|
||||
var formTagHelper = Assert.IsType<TagHelperBlock>(rewrittenTree.Root.Children[2]);
|
||||
Assert.Equal("form", formTagHelper.TagName);
|
||||
Assert.Single(formTagHelper.Descriptors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_NoopsWhenNoTagHelperFeature()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -16,20 +16,14 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests.TestFiles
|
|||
public async System.Threading.Tasks.Task ExecuteAsync()
|
||||
{
|
||||
__TestNamespace_InputTagHelper = CreateTagHelper<global::TestNamespace.InputTagHelper>();
|
||||
__TestNamespace_InputTagHelper = CreateTagHelper<global::TestNamespace.InputTagHelper>();
|
||||
__TestNamespace_CatchAllTagHelper = CreateTagHelper<global::TestNamespace.CatchAllTagHelper>();
|
||||
__TestNamespace_CatchAllTagHelper = CreateTagHelper<global::TestNamespace.CatchAllTagHelper>();
|
||||
__TestNamespace_InputTagHelper.Type = "checkbox";
|
||||
__TestNamespace_InputTagHelper.Type = __TestNamespace_InputTagHelper.Type;
|
||||
__TestNamespace_CatchAllTagHelper.Type = __TestNamespace_InputTagHelper.Type;
|
||||
__TestNamespace_CatchAllTagHelper.Type = __TestNamespace_InputTagHelper.Type;
|
||||
#line 3 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/DuplicateTargetTagHelper.cshtml"
|
||||
__TestNamespace_InputTagHelper.Checked = true;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
__TestNamespace_InputTagHelper.Checked = __TestNamespace_InputTagHelper.Checked;
|
||||
__TestNamespace_CatchAllTagHelper.Checked = __TestNamespace_InputTagHelper.Checked;
|
||||
__TestNamespace_CatchAllTagHelper.Checked = __TestNamespace_InputTagHelper.Checked;
|
||||
}
|
||||
#pragma warning restore 1998
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
Source Location: (67:2,32 [4] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/DuplicateTargetTagHelper.cshtml)
|
||||
|true|
|
||||
Generated Location: (1664:26,41 [4] )
|
||||
Generated Location: (1273:22,41 [4] )
|
||||
|true|
|
||||
|
||||
|
|
|
|||
|
|
@ -36,18 +36,10 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests.TestFiles
|
|||
);
|
||||
__TestNamespace_InputTagHelper = CreateTagHelper<global::TestNamespace.InputTagHelper>();
|
||||
__tagHelperExecutionContext.Add(__TestNamespace_InputTagHelper);
|
||||
__TestNamespace_InputTagHelper = CreateTagHelper<global::TestNamespace.InputTagHelper>();
|
||||
__tagHelperExecutionContext.Add(__TestNamespace_InputTagHelper);
|
||||
__TestNamespace_CatchAllTagHelper = CreateTagHelper<global::TestNamespace.CatchAllTagHelper>();
|
||||
__tagHelperExecutionContext.Add(__TestNamespace_CatchAllTagHelper);
|
||||
__TestNamespace_CatchAllTagHelper = CreateTagHelper<global::TestNamespace.CatchAllTagHelper>();
|
||||
__tagHelperExecutionContext.Add(__TestNamespace_CatchAllTagHelper);
|
||||
__TestNamespace_InputTagHelper.Type = (string)__tagHelperAttribute_0.Value;
|
||||
__tagHelperExecutionContext.AddTagHelperAttribute(__tagHelperAttribute_0);
|
||||
__TestNamespace_InputTagHelper.Type = (string)__tagHelperAttribute_0.Value;
|
||||
__tagHelperExecutionContext.AddTagHelperAttribute(__tagHelperAttribute_0);
|
||||
__TestNamespace_CatchAllTagHelper.Type = (string)__tagHelperAttribute_0.Value;
|
||||
__tagHelperExecutionContext.AddTagHelperAttribute(__tagHelperAttribute_0);
|
||||
__TestNamespace_CatchAllTagHelper.Type = (string)__tagHelperAttribute_0.Value;
|
||||
__tagHelperExecutionContext.AddTagHelperAttribute(__tagHelperAttribute_0);
|
||||
#line 3 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/DuplicateTargetTagHelper.cshtml"
|
||||
|
|
@ -56,8 +48,6 @@ __TestNamespace_InputTagHelper.Checked = true;
|
|||
#line default
|
||||
#line hidden
|
||||
__tagHelperExecutionContext.AddTagHelperAttribute("checked", __TestNamespace_InputTagHelper.Checked, global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.DoubleQuotes);
|
||||
__TestNamespace_InputTagHelper.Checked = __TestNamespace_InputTagHelper.Checked;
|
||||
__TestNamespace_CatchAllTagHelper.Checked = __TestNamespace_InputTagHelper.Checked;
|
||||
__TestNamespace_CatchAllTagHelper.Checked = __TestNamespace_InputTagHelper.Checked;
|
||||
await __tagHelperRunner.RunAsync(__tagHelperExecutionContext);
|
||||
if (!__tagHelperExecutionContext.Output.IsContentModified)
|
||||
|
|
|
|||
Loading…
Reference in New Issue