Add `TagHelperFactsService`.

- Add API to enable the editor to query information on the state of `TagHelper`s within a Razor document.
- Refactored methods from `TagHelperDescriptorProvider` to be in a `TagHelperDescriptorConventions` class so the language service could use them.
- Added `DefaultTagHelperFactService` tests.

#1120
This commit is contained in:
N. Taylor Mullen 2017-03-28 14:50:29 -07:00
parent c1500da2a8
commit 22c7c90b5a
16 changed files with 692 additions and 147 deletions

View File

@ -530,7 +530,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
{
var attributeValueNode = attribute.Value;
var associatedDescriptors = descriptors.Where(descriptor =>
descriptor.BoundAttributes.Any(attributeDescriptor => attributeDescriptor.CanMatchName(attribute.Name)));
descriptor.BoundAttributes.Any(attributeDescriptor => TagHelperMatchingConventions.CanSatisfyBoundAttribute(attribute.Name, attributeDescriptor)));
if (associatedDescriptors.Any() && renderedBoundAttributeNames.Add(attribute.Name))
{
@ -544,7 +544,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
foreach (var associatedDescriptor in associatedDescriptors)
{
var associatedAttributeDescriptor = associatedDescriptor.BoundAttributes.First(
attributeDescriptor => attributeDescriptor.CanMatchName(attribute.Name));
attributeDescriptor => TagHelperMatchingConventions.CanSatisfyBoundAttribute(attribute.Name, attributeDescriptor));
var tagHelperTypeName = associatedDescriptor.Metadata[ITagHelperDescriptorBuilder.TypeNameKey];
var attributePropertyName = associatedAttributeDescriptor.Metadata[ITagHelperBoundAttributeDescriptorBuilder.PropertyNameKey];
@ -557,7 +557,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
Binding = tagHelperBinding,
ValueStyle = attribute.ValueStyle,
Source = BuildSourceSpanFromNode(attributeValueNode),
IsIndexerNameMatch = associatedAttributeDescriptor.IsIndexerNameMatch(attribute.Name),
IsIndexerNameMatch = TagHelperMatchingConventions.SatisfiesBoundAttributeIndexer(attribute.Name, associatedAttributeDescriptor),
};
_builder.Push(setTagHelperProperty);

View File

@ -181,7 +181,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
yield return diagnostic;
}
else if (name != TagHelperDescriptorProvider.ElementCatchAllTarget)
else if (name != TagHelperMatchingConventions.ElementCatchAllName)
{
foreach (var character in name)
{

View File

@ -655,7 +655,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
private static string GetPropertyType(string name, IEnumerable<TagHelperDescriptor> descriptors)
{
var firstBoundAttribute = FindFirstBoundAttribute(name, descriptors);
var isBoundToIndexer = firstBoundAttribute.IsIndexerNameMatch(name);
var isBoundToIndexer = TagHelperMatchingConventions.SatisfiesBoundAttributeIndexer(name, firstBoundAttribute);
if (isBoundToIndexer)
{
@ -674,7 +674,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
var isBoundAttribute = firstBoundAttribute != null;
var isBoundNonStringAttribute = isBoundAttribute &&
!(firstBoundAttribute.IsStringProperty ||
(firstBoundAttribute.IsIndexerNameMatch(name) && firstBoundAttribute.IsIndexerStringProperty));
(TagHelperMatchingConventions.SatisfiesBoundAttributeIndexer(name, firstBoundAttribute) && firstBoundAttribute.IsIndexerStringProperty));
var isMissingDictionaryKey = isBoundAttribute &&
firstBoundAttribute.IndexerNamePrefix != null &&
name.Length == firstBoundAttribute.IndexerNamePrefix.Length;
@ -695,7 +695,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
var firstBoundAttribute = descriptors
.SelectMany(descriptor => descriptor.BoundAttributes)
.FirstOrDefault(attributeDescriptor => attributeDescriptor.CanMatchName(name));
.FirstOrDefault(attributeDescriptor => TagHelperMatchingConventions.CanSatisfyBoundAttribute(name, attributeDescriptor));
return firstBoundAttribute;
}

View File

@ -12,8 +12,6 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
/// </summary>
internal class TagHelperDescriptorProvider
{
public const string ElementCatchAllTarget = "*";
private IDictionary<string, HashSet<TagHelperDescriptor>> _registrations;
private readonly string _tagHelperPrefix;
@ -60,7 +58,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
IEnumerable<TagHelperDescriptor> descriptors;
// Ensure there's a HashSet to use.
if (!_registrations.TryGetValue(ElementCatchAllTarget, out HashSet<TagHelperDescriptor> catchAllDescriptors))
if (!_registrations.TryGetValue(TagHelperMatchingConventions.ElementCatchAllName, out HashSet<TagHelperDescriptor> catchAllDescriptors))
{
descriptors = new HashSet<TagHelperDescriptor>(TagHelperDescriptorComparer.Default);
}
@ -81,7 +79,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
foreach (var descriptor in descriptors)
{
var applicableRules = descriptor.TagMatchingRules.Where(
rule => MatchesRule(rule, attributes, tagNameWithoutPrefix, parentTagName));
rule => TagHelperMatchingConventions.SatisfiesRule(tagNameWithoutPrefix, parentTagName, attributes, rule));
if (applicableRules.Any())
{
@ -104,43 +102,13 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
return tagMappingResult;
}
private bool MatchesRule(
TagMatchingRule rule,
IEnumerable<KeyValuePair<string, string>> tagAttributes,
string tagNameWithoutPrefix,
string parentTagName)
{
// Verify tag name
if (rule.TagName != ElementCatchAllTarget &&
rule.TagName != null &&
!string.Equals(tagNameWithoutPrefix, rule.TagName, StringComparison.OrdinalIgnoreCase))
{
return false;
}
// Verify parent tag
if (rule.ParentTag != null && !string.Equals(parentTagName, rule.ParentTag, StringComparison.OrdinalIgnoreCase))
{
return false;
}
if (!rule.Attributes.All(
requiredAttribute => tagAttributes.Any(
attribute => requiredAttribute.IsMatch(attribute.Key, attribute.Value))))
{
return false;
}
return true;
}
private void Register(TagHelperDescriptor descriptor)
{
foreach (var rule in descriptor.TagMatchingRules)
{
var registrationKey =
string.Equals(rule.TagName, ElementCatchAllTarget, StringComparison.Ordinal) ?
ElementCatchAllTarget :
string.Equals(rule.TagName, TagHelperMatchingConventions.ElementCatchAllName, StringComparison.Ordinal) ?
TagHelperMatchingConventions.ElementCatchAllName :
_tagHelperPrefix + rule.TagName;
// Ensure there's a HashSet to add the descriptor to.

View File

@ -1,67 +0,0 @@
// 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.Diagnostics;
namespace Microsoft.AspNetCore.Razor.Evolution
{
internal static class TagHelperDescriptorMatchingConventions
{
public static bool CanMatchName(this BoundAttributeDescriptor descriptor, string name)
{
return IsFullNameMatch(descriptor, name) || IsIndexerNameMatch(descriptor, name);
}
public static bool IsFullNameMatch(this BoundAttributeDescriptor descriptor, string name)
{
return string.Equals(descriptor.Name, name, StringComparison.OrdinalIgnoreCase);
}
public static bool IsIndexerNameMatch(this BoundAttributeDescriptor descriptor, string name)
{
return descriptor.IndexerNamePrefix != null &&
!IsFullNameMatch(descriptor, name) &&
name.StartsWith(descriptor.IndexerNamePrefix, StringComparison.OrdinalIgnoreCase);
}
public static bool IsMatch(this RequiredAttributeDescriptor descriptor, string attributeName, string attributeValue)
{
var nameMatches = false;
if (descriptor.NameComparison == RequiredAttributeDescriptor.NameComparisonMode.FullMatch)
{
nameMatches = string.Equals(descriptor.Name, attributeName, StringComparison.OrdinalIgnoreCase);
}
else if (descriptor.NameComparison == RequiredAttributeDescriptor.NameComparisonMode.PrefixMatch)
{
// attributeName cannot equal the Name if comparing as a PrefixMatch.
nameMatches = attributeName.Length != descriptor.Name.Length &&
attributeName.StartsWith(descriptor.Name, StringComparison.OrdinalIgnoreCase);
}
else
{
Debug.Assert(false, "Unknown name comparison.");
}
if (!nameMatches)
{
return false;
}
switch (descriptor.ValueComparison)
{
case RequiredAttributeDescriptor.ValueComparisonMode.None:
return true;
case RequiredAttributeDescriptor.ValueComparisonMode.PrefixMatch: // Value starts with
return attributeValue.StartsWith(descriptor.Value, StringComparison.Ordinal);
case RequiredAttributeDescriptor.ValueComparisonMode.SuffixMatch: // Value ends with
return attributeValue.EndsWith(descriptor.Value, StringComparison.Ordinal);
case RequiredAttributeDescriptor.ValueComparisonMode.FullMatch: // Value equals
return string.Equals(attributeValue, descriptor.Value, StringComparison.Ordinal);
default:
Debug.Assert(false, "Unknown value comparison.");
return false;
}
}
}
}

View File

@ -0,0 +1,173 @@
// 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 System.Diagnostics;
using System.Linq;
namespace Microsoft.AspNetCore.Razor.Evolution
{
internal static class TagHelperMatchingConventions
{
public const string ElementCatchAllName = "*";
public static bool SatisfiesRule(
string tagNameWithoutPrefix,
string parentTagName,
IEnumerable<KeyValuePair<string, string>> tagAttributes,
TagMatchingRule rule)
{
if (tagNameWithoutPrefix == null)
{
throw new ArgumentNullException(nameof(tagNameWithoutPrefix));
}
if (tagAttributes == null)
{
throw new ArgumentNullException(nameof(tagAttributes));
}
if (rule == null)
{
throw new ArgumentNullException(nameof(rule));
}
var satisfiesTagName = SatisfiesTagName(tagNameWithoutPrefix, rule);
if (!satisfiesTagName)
{
return false;
}
var satisfiesParentTag = SatisfiesParentTag(parentTagName, rule);
if (!satisfiesParentTag)
{
return false;
}
var satisfiesAttributes = SatisfiesAttributes(tagAttributes, rule);
if (!satisfiesAttributes)
{
return false;
}
return true;
}
public static bool SatisfiesTagName(string tagNameWithoutPrefix, TagMatchingRule rule)
{
if (tagNameWithoutPrefix == null)
{
throw new ArgumentNullException(nameof(tagNameWithoutPrefix));
}
if (rule == null)
{
throw new ArgumentNullException(nameof(rule));
}
if (rule.TagName != ElementCatchAllName &&
rule.TagName != null &&
!string.Equals(tagNameWithoutPrefix, rule.TagName, StringComparison.OrdinalIgnoreCase))
{
return false;
}
return true;
}
public static bool SatisfiesParentTag(string parentTagName, TagMatchingRule rule)
{
if (rule == null)
{
throw new ArgumentNullException(nameof(rule));
}
if (rule.ParentTag != null && !string.Equals(parentTagName, rule.ParentTag, StringComparison.OrdinalIgnoreCase))
{
return false;
}
return true;
}
public static bool SatisfiesAttributes(IEnumerable<KeyValuePair<string, string>> tagAttributes, TagMatchingRule rule)
{
if (tagAttributes == null)
{
throw new ArgumentNullException(nameof(tagAttributes));
}
if (rule == null)
{
throw new ArgumentNullException(nameof(rule));
}
if (!rule.Attributes.All(
requiredAttribute => tagAttributes.Any(
attribute => SatisfiesRequiredAttribute(attribute.Key, attribute.Value, requiredAttribute))))
{
return false;
}
return true;
}
public static bool CanSatisfyBoundAttribute(string name, BoundAttributeDescriptor descriptor)
{
return SatisfiesBoundAttributeName(name, descriptor) || SatisfiesBoundAttributeIndexer(name, descriptor);
}
public static bool SatisfiesBoundAttributeIndexer(string name, BoundAttributeDescriptor descriptor)
{
return descriptor.IndexerNamePrefix != null &&
!SatisfiesBoundAttributeName(name, descriptor) &&
name.StartsWith(descriptor.IndexerNamePrefix, StringComparison.OrdinalIgnoreCase);
}
private static bool SatisfiesBoundAttributeName(string name, BoundAttributeDescriptor descriptor)
{
return string.Equals(descriptor.Name, name, StringComparison.OrdinalIgnoreCase);
}
// Internal for testing
internal static bool SatisfiesRequiredAttribute(string attributeName, string attributeValue, RequiredAttributeDescriptor descriptor)
{
var nameMatches = false;
if (descriptor.NameComparison == RequiredAttributeDescriptor.NameComparisonMode.FullMatch)
{
nameMatches = string.Equals(descriptor.Name, attributeName, StringComparison.OrdinalIgnoreCase);
}
else if (descriptor.NameComparison == RequiredAttributeDescriptor.NameComparisonMode.PrefixMatch)
{
// attributeName cannot equal the Name if comparing as a PrefixMatch.
nameMatches = attributeName.Length != descriptor.Name.Length &&
attributeName.StartsWith(descriptor.Name, StringComparison.OrdinalIgnoreCase);
}
else
{
Debug.Assert(false, "Unknown name comparison.");
}
if (!nameMatches)
{
return false;
}
switch (descriptor.ValueComparison)
{
case RequiredAttributeDescriptor.ValueComparisonMode.None:
return true;
case RequiredAttributeDescriptor.ValueComparisonMode.PrefixMatch: // Value starts with
return attributeValue.StartsWith(descriptor.Value, StringComparison.Ordinal);
case RequiredAttributeDescriptor.ValueComparisonMode.SuffixMatch: // Value ends with
return attributeValue.EndsWith(descriptor.Value, StringComparison.Ordinal);
case RequiredAttributeDescriptor.ValueComparisonMode.FullMatch: // Value equals
return string.Equals(attributeValue, descriptor.Value, StringComparison.Ordinal);
default:
Debug.Assert(false, "Unknown value comparison.");
return false;
}
}
}
}

View File

@ -122,7 +122,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
yield return diagnostic;
}
else if (_tagName != TagHelperDescriptorProvider.ElementCatchAllTarget)
else if (_tagName != TagHelperMatchingConventions.ElementCatchAllName)
{
foreach (var character in _tagName)
{

View File

@ -371,7 +371,7 @@ namespace Microsoft.CodeAnalysis.Razor
{
if (attibute.ConstructorArguments.Length == 0)
{
return TagHelperDescriptorProvider.ElementCatchAllTarget;
return TagHelperMatchingConventions.ElementCatchAllName;
}
else
{

View File

@ -1,12 +0,0 @@
// 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.ComponentModel.Composition;
namespace Microsoft.VisualStudio.LanguageServices.Razor
{
[Export(typeof(RazorTagHelperFactsService))]
internal class DefaultRazorTagHelperFactsService : RazorTagHelperFactsService
{
}
}

View File

@ -0,0 +1,170 @@
// 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 Microsoft.AspNetCore.Razor.Evolution;
using Microsoft.AspNetCore.Razor.Evolution.Legacy;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
namespace Microsoft.VisualStudio.LanguageServices.Razor
{
[Export(typeof(TagHelperFactsService))]
internal class DefaultTagHelperFactsService : TagHelperFactsService
{
public override TagHelperBinding GetTagHelperBinding(
TagHelperDocumentContext documentContext,
string tagName,
IEnumerable<KeyValuePair<string, string>> attributes,
string parentTag)
{
if (documentContext == null)
{
throw new ArgumentNullException(nameof(documentContext));
}
if (tagName == null)
{
throw new ArgumentNullException(nameof(tagName));
}
if (attributes == null)
{
throw new ArgumentNullException(nameof(attributes));
}
var descriptors = documentContext.TagHelpers;
if (descriptors == null || descriptors.Count == 0)
{
return null;
}
var prefix = documentContext.Prefix;
var provider = new TagHelperDescriptorProvider(prefix, descriptors);
var binding = provider.GetTagHelperBinding(tagName, attributes, parentTag);
return binding;
}
public override IEnumerable<BoundAttributeDescriptor> GetBoundTagHelperAttributes(
TagHelperDocumentContext documentContext,
string attributeName,
TagHelperBinding binding)
{
if (documentContext == null)
{
throw new ArgumentNullException(nameof(documentContext));
}
if (attributeName == null)
{
throw new ArgumentNullException(nameof(attributeName));
}
if (binding == null)
{
throw new ArgumentNullException(nameof(binding));
}
var matchingBoundAttributes = new List<BoundAttributeDescriptor>();
foreach (var descriptor in binding.Descriptors)
{
foreach (var boundAttributeDescriptor in descriptor.BoundAttributes)
{
if (TagHelperMatchingConventions.CanSatisfyBoundAttribute(attributeName, boundAttributeDescriptor))
{
matchingBoundAttributes.Add(boundAttributeDescriptor);
// Only one bound attribute can match an attribute
break;
}
}
}
return matchingBoundAttributes;
}
public override IReadOnlyList<TagHelperDescriptor> GetTagHelpersGivenTag(
TagHelperDocumentContext documentContext,
string tagName,
string parentTag)
{
if (documentContext == null)
{
throw new ArgumentNullException(nameof(documentContext));
}
if (tagName == null)
{
throw new ArgumentNullException(nameof(tagName));
}
var matchingDescriptors = new List<TagHelperDescriptor>();
var descriptors = documentContext?.TagHelpers;
if (descriptors?.Count == 0)
{
return matchingDescriptors;
}
var prefix = documentContext.Prefix ?? string.Empty;
if (!tagName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{
// Can't possibly match TagHelpers, it doesn't start with the TagHelperPrefix.
return matchingDescriptors;
}
var tagNameWithoutPrefix = tagName.Substring(prefix.Length);
for (var i = 0; i < descriptors.Count; i++)
{
var descriptor = descriptors[i];
foreach (var rule in descriptor.TagMatchingRules)
{
if (TagHelperMatchingConventions.SatisfiesTagName(tagNameWithoutPrefix, rule) &&
TagHelperMatchingConventions.SatisfiesParentTag(parentTag, rule))
{
matchingDescriptors.Add(descriptor);
break;
}
}
}
return matchingDescriptors;
}
public override IReadOnlyList<TagHelperDescriptor> GetTagHelpersGivenParent(TagHelperDocumentContext documentContext, string parentTag)
{
if (documentContext == null)
{
throw new ArgumentNullException(nameof(documentContext));
}
if (parentTag == null)
{
throw new ArgumentNullException(nameof(parentTag));
}
var matchingDescriptors = new List<TagHelperDescriptor>();
var descriptors = documentContext?.TagHelpers;
if (descriptors?.Count == 0)
{
return matchingDescriptors;
}
for (var i = 0; i < descriptors.Count; i++)
{
var descriptor = descriptors[i];
foreach (var rule in descriptor.TagMatchingRules)
{
if (TagHelperMatchingConventions.SatisfiesParentTag(parentTag, rule))
{
matchingDescriptors.Add(descriptor);
break;
}
}
}
return matchingDescriptors;
}
}
}

View File

@ -1,9 +0,0 @@
// 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.
namespace Microsoft.VisualStudio.LanguageServices.Razor
{
public abstract class RazorTagHelperFactsService
{
}
}

View File

@ -0,0 +1,20 @@
// 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 Microsoft.AspNetCore.Razor.Evolution;
using Microsoft.AspNetCore.Razor.Evolution.Legacy;
using System.Collections.Generic;
namespace Microsoft.VisualStudio.LanguageServices.Razor
{
public abstract class TagHelperFactsService
{
public abstract TagHelperBinding GetTagHelperBinding(TagHelperDocumentContext documentContext, string tagName, IEnumerable<KeyValuePair<string, string>> attributes, string parentTag);
public abstract IEnumerable<BoundAttributeDescriptor> GetBoundTagHelperAttributes(TagHelperDocumentContext documentContext, string attributeName, TagHelperBinding binding);
public abstract IReadOnlyList<TagHelperDescriptor> GetTagHelpersGivenTag(TagHelperDocumentContext documentContext, string tagName, string parentTag);
public abstract IReadOnlyList<TagHelperDescriptor> GetTagHelpersGivenParent(TagHelperDocumentContext documentContext, string parentTag);
}
}

View File

@ -115,20 +115,20 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
var catchAllDescriptor = ITagHelperDescriptorBuilder.Create("CatchAllTagHelper", "SomeAssembly")
.TagMatchingRule(rule =>
rule
.RequireTagName(TagHelperDescriptorProvider.ElementCatchAllTarget)
.RequireTagName(TagHelperMatchingConventions.ElementCatchAllName)
.RequireAttribute(attribute => attribute.Name("class")))
.Build();
var catchAllDescriptor2 = ITagHelperDescriptorBuilder.Create("CatchAllTagHelper2", "SomeAssembly")
.TagMatchingRule(rule =>
rule
.RequireTagName(TagHelperDescriptorProvider.ElementCatchAllTarget)
.RequireTagName(TagHelperMatchingConventions.ElementCatchAllName)
.RequireAttribute(attribute => attribute.Name("custom"))
.RequireAttribute(attribute => attribute.Name("class")))
.Build();
var catchAllWildcardPrefixDescriptor = ITagHelperDescriptorBuilder.Create("CatchAllWildCardAttribute", "SomeAssembly")
.TagMatchingRule(rule =>
rule
.RequireTagName(TagHelperDescriptorProvider.ElementCatchAllTarget)
.RequireTagName(TagHelperMatchingConventions.ElementCatchAllName)
.RequireAttribute(attribute =>
attribute
.Name("prefix-")
@ -254,7 +254,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
// Arrange
var catchAllDescriptor = ITagHelperDescriptorBuilder.Create("foo1", "SomeAssembly")
.TagMatchingRule(rule => rule.RequireTagName(TagHelperDescriptorProvider.ElementCatchAllTarget))
.TagMatchingRule(rule => rule.RequireTagName(TagHelperMatchingConventions.ElementCatchAllName))
.Build();
var descriptors = new[] { catchAllDescriptor };
var provider = new TagHelperDescriptorProvider("th", descriptors);
@ -274,7 +274,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
// Arrange
var catchAllDescriptor = ITagHelperDescriptorBuilder.Create("foo1", "SomeAssembly")
.TagMatchingRule(rule => rule.RequireTagName(TagHelperDescriptorProvider.ElementCatchAllTarget))
.TagMatchingRule(rule => rule.RequireTagName(TagHelperMatchingConventions.ElementCatchAllName))
.Build();
var descriptors = new[] { catchAllDescriptor };
var provider = new TagHelperDescriptorProvider("th:", descriptors);
@ -373,7 +373,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
.TagMatchingRule(rule => rule.RequireTagName("span"))
.Build();
var catchAllDescriptor = ITagHelperDescriptorBuilder.Create("foo3", "SomeAssembly")
.TagMatchingRule(rule => rule.RequireTagName(TagHelperDescriptorProvider.ElementCatchAllTarget))
.TagMatchingRule(rule => rule.RequireTagName(TagHelperMatchingConventions.ElementCatchAllName))
.Build();
var descriptors = new TagHelperDescriptor[] { divDescriptor, spanDescriptor, catchAllDescriptor };
var provider = new TagHelperDescriptorProvider(null, descriptors);
@ -427,7 +427,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
// Arrange
var multiRuleDescriptor = ITagHelperDescriptorBuilder.Create("foo", "SomeAssembly")
.TagMatchingRule(rule => rule
.RequireTagName(TagHelperDescriptorProvider.ElementCatchAllTarget)
.RequireTagName(TagHelperMatchingConventions.ElementCatchAllName)
.RequireParentTag("body"))
.TagMatchingRule(rule => rule
.RequireTagName("div"))

View File

@ -150,7 +150,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
bool expectedResult)
{
// Act
var result = ((RequiredAttributeDescriptor)requiredAttributeDescriptor).IsMatch(attributeName, attributeValue);
var result = TagHelperMatchingConventions.SatisfiesRequiredAttribute(attributeName, attributeValue, (RequiredAttributeDescriptor)requiredAttributeDescriptor);
// Assert
Assert.Equal(expectedResult, result);

View File

@ -720,7 +720,7 @@ namespace Microsoft.CodeAnalysis.Razor.Workspaces
{
typeof(AttributeTargetingTagHelper),
CreateTagHelperDescriptor(
TagHelperDescriptorProvider.ElementCatchAllTarget,
TagHelperMatchingConventions.ElementCatchAllName,
typeof(AttributeTargetingTagHelper).FullName,
AssemblyName,
ruleBuilders: new Action<TagMatchingRuleBuilder>[]
@ -731,7 +731,7 @@ namespace Microsoft.CodeAnalysis.Razor.Workspaces
{
typeof(MultiAttributeTargetingTagHelper),
CreateTagHelperDescriptor(
TagHelperDescriptorProvider.ElementCatchAllTarget,
TagHelperMatchingConventions.ElementCatchAllName,
typeof(MultiAttributeTargetingTagHelper).FullName,
AssemblyName,
ruleBuilders: new Action<TagMatchingRuleBuilder>[]
@ -747,7 +747,7 @@ namespace Microsoft.CodeAnalysis.Razor.Workspaces
{
typeof(MultiAttributeAttributeTargetingTagHelper),
CreateTagHelperDescriptor(
TagHelperDescriptorProvider.ElementCatchAllTarget,
TagHelperMatchingConventions.ElementCatchAllName,
typeof(MultiAttributeAttributeTargetingTagHelper).FullName,
AssemblyName,
ruleBuilders: new Action<TagMatchingRuleBuilder>[]
@ -764,7 +764,7 @@ namespace Microsoft.CodeAnalysis.Razor.Workspaces
{
typeof(InheritedAttributeTargetingTagHelper),
CreateTagHelperDescriptor(
TagHelperDescriptorProvider.ElementCatchAllTarget,
TagHelperMatchingConventions.ElementCatchAllName,
typeof(InheritedAttributeTargetingTagHelper).FullName,
AssemblyName,
ruleBuilders: new Action<TagMatchingRuleBuilder>[]
@ -856,7 +856,7 @@ namespace Microsoft.CodeAnalysis.Razor.Workspaces
{
typeof(AttributeWildcardTargetingTagHelper),
CreateTagHelperDescriptor(
TagHelperDescriptorProvider.ElementCatchAllTarget,
TagHelperMatchingConventions.ElementCatchAllName,
typeof(AttributeWildcardTargetingTagHelper).FullName,
AssemblyName,
ruleBuilders: new Action<TagMatchingRuleBuilder>[]
@ -870,7 +870,7 @@ namespace Microsoft.CodeAnalysis.Razor.Workspaces
{
typeof(MultiAttributeWildcardTargetingTagHelper),
CreateTagHelperDescriptor(
TagHelperDescriptorProvider.ElementCatchAllTarget,
TagHelperMatchingConventions.ElementCatchAllName,
typeof(MultiAttributeWildcardTargetingTagHelper).FullName,
AssemblyName,
ruleBuilders: new Action<TagMatchingRuleBuilder>[]

View File

@ -0,0 +1,302 @@
// 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 Microsoft.AspNetCore.Razor.Evolution;
using System.Collections.Generic;
using System.Linq;
using Xunit;
namespace Microsoft.VisualStudio.LanguageServices.Razor
{
public class DefaultTagHelperFactsServiceTest
{
// Purposefully not thoroughly testing DefaultTagHelperFactsService.GetTagHelperBinding because it's a pass through
// into TagHelperDescriptorProvider.GetTagHelperBinding.
[Fact]
public void GetTagHelperBinding_WorksAsExpected()
{
// Arrange
var documentDescriptors = new[]
{
ITagHelperDescriptorBuilder.Create("TestType", "TestAssembly")
.TagMatchingRule(rule =>
rule
.RequireTagName("a")
.RequireAttribute(attribute => attribute.Name("asp-for")))
.BindAttribute(attribute =>
attribute
.Name("asp-for")
.TypeName(typeof(string).FullName)
.PropertyName("AspFor"))
.BindAttribute(attribute =>
attribute
.Name("asp-route")
.TypeName(typeof(IDictionary<string, string>).Namespace + "IDictionary<string, string>")
.PropertyName("AspRoute")
.AsDictionary("asp-route-", typeof(string).FullName))
.Build(),
ITagHelperDescriptorBuilder.Create("TestType", "TestAssembly")
.TagMatchingRule(rule => rule.RequireTagName("input"))
.BindAttribute(attribute =>
attribute
.Name("asp-for")
.TypeName(typeof(string).FullName)
.PropertyName("AspFor"))
.Build(),
};
var documentContext = TagHelperDocumentContext.Create(string.Empty, documentDescriptors);
var service = new DefaultTagHelperFactsService();
var attributes = new[]
{
new KeyValuePair<string, string>("asp-for", "Name")
};
// Act
var binding = service.GetTagHelperBinding(documentContext, "a", attributes, parentTag: "p");
// Assert
var descriptor = Assert.Single(binding.Descriptors);
Assert.Equal(documentDescriptors[0], descriptor, TagHelperDescriptorComparer.CaseSensitive);
var boundRule = Assert.Single(binding.GetBoundRules(descriptor));
Assert.Equal(documentDescriptors[0].TagMatchingRules.First(), boundRule, TagMatchingRuleComparer.CaseSensitive);
}
[Fact]
public void GetBoundTagHelperAttributes_MatchesPrefixedAttributeName()
{
// Arrange
var documentDescriptors = new[]
{
ITagHelperDescriptorBuilder.Create("TestType", "TestAssembly")
.TagMatchingRule(rule => rule.RequireTagName("a"))
.BindAttribute(attribute =>
attribute
.Name("asp-for")
.TypeName(typeof(string).FullName)
.PropertyName("AspFor"))
.BindAttribute(attribute =>
attribute
.Name("asp-route")
.TypeName(typeof(IDictionary<string, string>).Namespace + "IDictionary<string, string>")
.PropertyName("AspRoute")
.AsDictionary("asp-route-", typeof(string).FullName))
.Build()
};
var expectedAttributeDescriptors = new[]
{
documentDescriptors[0].BoundAttributes.Last()
};
var documentContext = TagHelperDocumentContext.Create(string.Empty, documentDescriptors);
var service = new DefaultTagHelperFactsService();
var binding = service.GetTagHelperBinding(documentContext, "a", Enumerable.Empty<KeyValuePair<string, string>>(), parentTag: null);
// Act
var descriptors = service.GetBoundTagHelperAttributes(documentContext, "asp-route-something", binding);
// Assert
Assert.Equal(expectedAttributeDescriptors, descriptors, BoundAttributeDescriptorComparer.CaseSensitive);
}
[Fact]
public void GetBoundTagHelperAttributes_MatchesAttributeName()
{
// Arrange
var documentDescriptors = new[]
{
ITagHelperDescriptorBuilder.Create("TestType", "TestAssembly")
.TagMatchingRule(rule => rule.RequireTagName("input"))
.BindAttribute(attribute =>
attribute
.Name("asp-for")
.TypeName(typeof(string).FullName)
.PropertyName("AspFor"))
.BindAttribute(attribute =>
attribute
.Name("asp-extra")
.TypeName(typeof(string).FullName)
.PropertyName("AspExtra"))
.Build()
};
var expectedAttributeDescriptors = new[]
{
documentDescriptors[0].BoundAttributes.First()
};
var documentContext = TagHelperDocumentContext.Create(string.Empty, documentDescriptors);
var service = new DefaultTagHelperFactsService();
var binding = service.GetTagHelperBinding(documentContext, "input", Enumerable.Empty<KeyValuePair<string, string>>(), parentTag: null);
// Act
var descriptors = service.GetBoundTagHelperAttributes(documentContext, "asp-for", binding);
// Assert
Assert.Equal(expectedAttributeDescriptors, descriptors, BoundAttributeDescriptorComparer.CaseSensitive);
}
[Fact]
public void GetTagHelpersGivenTag_RequiresTagName()
{
// Arrange
var documentDescriptors = new[]
{
ITagHelperDescriptorBuilder.Create("TestType", "TestAssembly")
.TagMatchingRule(rule => rule.RequireTagName("strong"))
.Build()
};
var documentContext = TagHelperDocumentContext.Create(string.Empty, documentDescriptors);
var service = new DefaultTagHelperFactsService();
// Act
var descriptors = service.GetTagHelpersGivenTag(documentContext, "strong", "p");
// Assert
Assert.Equal(documentDescriptors, descriptors, TagHelperDescriptorComparer.CaseSensitive);
}
[Fact]
public void GetTagHelpersGivenTag_RestrictsTagHelpersBasedOnTagName()
{
// Arrange
var expectedDescriptors = new[]
{
ITagHelperDescriptorBuilder.Create("TestType", "TestAssembly")
.TagMatchingRule(
rule => rule
.RequireTagName("a")
.RequireParentTag("div"))
.Build()
};
var documentDescriptors = new[]
{
expectedDescriptors[0],
ITagHelperDescriptorBuilder.Create("TestType2", "TestAssembly")
.TagMatchingRule(
rule => rule
.RequireTagName("strong")
.RequireParentTag("div"))
.Build()
};
var documentContext = TagHelperDocumentContext.Create(string.Empty, documentDescriptors);
var service = new DefaultTagHelperFactsService();
// Act
var descriptors = service.GetTagHelpersGivenTag(documentContext, "a", "div");
// Assert
Assert.Equal(expectedDescriptors, descriptors, TagHelperDescriptorComparer.CaseSensitive);
}
[Fact]
public void GetTagHelpersGivenTag_RestrictsTagHelpersBasedOnTagHelperPrefix()
{
// Arrange
var expectedDescriptors = new[]
{
ITagHelperDescriptorBuilder.Create("TestType", "TestAssembly")
.TagMatchingRule(rule => rule.RequireTagName("strong"))
.Build()
};
var documentDescriptors = new[]
{
expectedDescriptors[0],
ITagHelperDescriptorBuilder.Create("TestType2", "TestAssembly")
.TagMatchingRule(rule => rule.RequireTagName("thstrong"))
.Build()
};
var documentContext = TagHelperDocumentContext.Create("th", documentDescriptors);
var service = new DefaultTagHelperFactsService();
// Act
var descriptors = service.GetTagHelpersGivenTag(documentContext, "thstrong", "div");
// Assert
Assert.Equal(expectedDescriptors, descriptors, TagHelperDescriptorComparer.CaseSensitive);
}
[Fact]
public void GetTagHelpersGivenTag_RestrictsTagHelpersBasedOnParent()
{
// Arrange
var expectedDescriptors = new[]
{
ITagHelperDescriptorBuilder.Create("TestType", "TestAssembly")
.TagMatchingRule(
rule => rule
.RequireTagName("strong")
.RequireParentTag("div"))
.Build()
};
var documentDescriptors = new[]
{
expectedDescriptors[0],
ITagHelperDescriptorBuilder.Create("TestType2", "TestAssembly")
.TagMatchingRule(
rule => rule
.RequireTagName("strong")
.RequireParentTag("p"))
.Build()
};
var documentContext = TagHelperDocumentContext.Create(string.Empty, documentDescriptors);
var service = new DefaultTagHelperFactsService();
// Act
var descriptors = service.GetTagHelpersGivenTag(documentContext, "strong", "div");
// Assert
Assert.Equal(expectedDescriptors, descriptors, TagHelperDescriptorComparer.CaseSensitive);
}
[Fact]
public void GetTagHelpersGivenParent_AllowsUnspecifiedParentTagHelpers()
{
// Arrange
var documentDescriptors = new[]
{
ITagHelperDescriptorBuilder.Create("TestType", "TestAssembly")
.TagMatchingRule(rule => rule.RequireTagName("div"))
.Build()
};
var documentContext = TagHelperDocumentContext.Create(string.Empty, documentDescriptors);
var service = new DefaultTagHelperFactsService();
// Act
var descriptors = service.GetTagHelpersGivenParent(documentContext, "p");
// Assert
Assert.Equal(documentDescriptors, descriptors, TagHelperDescriptorComparer.CaseSensitive);
}
[Fact]
public void GetTagHelpersGivenParent_RestrictsTagHelpersBasedOnParent()
{
// Arrange
var expectedDescriptors = new[]
{
ITagHelperDescriptorBuilder.Create("TestType", "TestAssembly")
.TagMatchingRule(
rule => rule
.RequireTagName("p")
.RequireParentTag("div"))
.Build()
};
var documentDescriptors = new[]
{
expectedDescriptors[0],
ITagHelperDescriptorBuilder.Create("TestType2", "TestAssembly")
.TagMatchingRule(
rule => rule
.RequireTagName("strong")
.RequireParentTag("p"))
.Build()
};
var documentContext = TagHelperDocumentContext.Create(string.Empty, documentDescriptors);
var service = new DefaultTagHelperFactsService();
// Act
var descriptors = service.GetTagHelpersGivenParent(documentContext, "div");
// Assert
Assert.Equal(expectedDescriptors, descriptors, TagHelperDescriptorComparer.CaseSensitive);
}
}
}