diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperDescriptorComparer.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperDescriptorComparer.cs index abac00462c..5c4e4ca403 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperDescriptorComparer.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperDescriptorComparer.cs @@ -19,6 +19,12 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy /// public static readonly TagHelperDescriptorComparer Default = new TagHelperDescriptorComparer(); + /// + /// An instance of that only compares + /// . + /// + public static readonly TagHelperDescriptorComparer TypeName = new TypeNameTagHelperDescriptorComparer(); + /// /// Initializes a new instance. /// @@ -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); + } + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperDescriptorProvider.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperDescriptorProvider.cs index 3d7356f81c..744e400fb1 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperDescriptorProvider.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperDescriptorProvider.cs @@ -86,7 +86,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy } } - return applicableDescriptors; + return applicableDescriptors.Distinct(TagHelperDescriptorComparer.TypeName); } private bool HasRequiredParentTag( diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/TagHelperDescriptorProviderTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/TagHelperDescriptorProviderTest.cs index a8ebd5f399..bd0ac4c64c 100644 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/TagHelperDescriptorProviderTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/TagHelperDescriptorProviderTest.cs @@ -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)availableDescriptors); // Act - var resolvedDescriptors = provider.GetDescriptors(tagName, providedAttributes, parentTagName: "p"); + var resolvedDescriptors = provider.GetDescriptors(tagName, providedAttributes, parentTagName: "p").ToArray(); // Assert Assert.Equal((IEnumerable)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() + { + new TagHelperRequiredAttributeDescriptor() + { + Name = "a", + NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch + } + }, + }, + new TagHelperDescriptor + { + AssemblyName = "TestAssembly", + TagName = "form", + TypeName = "TestFormTagHelper", + RequiredAttributes = new List() + { + new TagHelperRequiredAttributeDescriptor() + { + Name = "b", + NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch + } + }, + }, + }; + var provider = new TagHelperDescriptorProvider(descriptors); + + // Act + var resolvedDescriptors = provider.GetDescriptors( + tagName: "form", + attributes: new List>() + { + new KeyValuePair("a", "hi" ), + new KeyValuePair("b", "there"), + }, + parentTagName: "p"); + + // Assert + Assert.Same(descriptors[0], Assert.Single(resolvedDescriptors)); + } + [Fact] public void GetDescriptors_OnlyUnderstandsSinglePrefix() { diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/TagHelperBinderSyntaxTreePassTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/TagHelperBinderSyntaxTreePassTest.cs index 6d4590cd48..885429c3f2 100644 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/TagHelperBinderSyntaxTreePassTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/TagHelperBinderSyntaxTreePassTest.cs @@ -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() + { + new TagHelperRequiredAttributeDescriptor() + { + Name = "a", + NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch + } + }, + }, + new TagHelperDescriptor + { + AssemblyName = "TestAssembly", + TagName = "form", + TypeName = "TestFormTagHelper", + RequiredAttributes = new List() + { + new TagHelperRequiredAttributeDescriptor() + { + Name = "b", + NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch + } + }, + } + }); + }); + + var pass = new TagHelperBinderSyntaxTreePass() + { + Engine = engine, + }; + + var content = +@" +@addTagHelper *, TestAssembly +
+
"; + 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(rewrittenTree.Root.Children[2]); + Assert.Equal("form", formTagHelper.TagName); + Assert.Single(formTagHelper.Descriptors); + } + [Fact] public void Execute_NoopsWhenNoTagHelperFeature() { diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/DuplicateTargetTagHelper_DesignTime.codegen.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/DuplicateTargetTagHelper_DesignTime.codegen.cs index eea441ba7f..bd9dbfd51e 100644 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/DuplicateTargetTagHelper_DesignTime.codegen.cs +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/DuplicateTargetTagHelper_DesignTime.codegen.cs @@ -16,20 +16,14 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests.TestFiles public async System.Threading.Tasks.Task ExecuteAsync() { __TestNamespace_InputTagHelper = CreateTagHelper(); - __TestNamespace_InputTagHelper = CreateTagHelper(); - __TestNamespace_CatchAllTagHelper = CreateTagHelper(); __TestNamespace_CatchAllTagHelper = CreateTagHelper(); __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 diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/DuplicateTargetTagHelper_DesignTime.mappings.txt b/test/Microsoft.AspNetCore.Razor.Evolution.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/DuplicateTargetTagHelper_DesignTime.mappings.txt index 1d5624408a..282cffc3b7 100644 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/DuplicateTargetTagHelper_DesignTime.mappings.txt +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/DuplicateTargetTagHelper_DesignTime.mappings.txt @@ -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| diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/DuplicateTargetTagHelper_Runtime.codegen.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/DuplicateTargetTagHelper_Runtime.codegen.cs index 7c0e710c8f..b009254072 100644 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/DuplicateTargetTagHelper_Runtime.codegen.cs +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/DuplicateTargetTagHelper_Runtime.codegen.cs @@ -36,18 +36,10 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests.TestFiles ); __TestNamespace_InputTagHelper = CreateTagHelper(); __tagHelperExecutionContext.Add(__TestNamespace_InputTagHelper); - __TestNamespace_InputTagHelper = CreateTagHelper(); - __tagHelperExecutionContext.Add(__TestNamespace_InputTagHelper); - __TestNamespace_CatchAllTagHelper = CreateTagHelper(); - __tagHelperExecutionContext.Add(__TestNamespace_CatchAllTagHelper); __TestNamespace_CatchAllTagHelper = CreateTagHelper(); __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)