Add CSS attribute selectors for `TagHelper` attributes.
- Added the ability for users to opt into CSS `TagHelper` selectors in their required attributes by surrounding the value with `[` and `]`. Added operators `^`, `$` and `=`. - Added tests to cover code paths used when determining CSS selectors. #684
This commit is contained in:
parent
3b53e42f36
commit
e5927ddd01
|
|
@ -426,6 +426,86 @@ namespace Microsoft.AspNetCore.Razor.Runtime
|
|||
return string.Format(CultureInfo.CurrentCulture, GetString("ArgumentMustBeAnInstanceOf"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Could not find matching ']' for required attribute '{0}'.
|
||||
/// </summary>
|
||||
internal static string TagHelperDescriptorFactory_CouldNotFindMatchingEndBrace
|
||||
{
|
||||
get { return GetString("TagHelperDescriptorFactory_CouldNotFindMatchingEndBrace"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Could not find matching ']' for required attribute '{0}'.
|
||||
/// </summary>
|
||||
internal static string FormatTagHelperDescriptorFactory_CouldNotFindMatchingEndBrace(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_CouldNotFindMatchingEndBrace"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid required attribute character '{0}' in required attribute '{1}'. Separate required attributes with commas.
|
||||
/// </summary>
|
||||
internal static string TagHelperDescriptorFactory_InvalidRequiredAttributeCharacter
|
||||
{
|
||||
get { return GetString("TagHelperDescriptorFactory_InvalidRequiredAttributeCharacter"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid required attribute character '{0}' in required attribute '{1}'. Separate required attributes with commas.
|
||||
/// </summary>
|
||||
internal static string FormatTagHelperDescriptorFactory_InvalidRequiredAttributeCharacter(object p0, object p1)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidRequiredAttributeCharacter"), p0, p1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Required attribute '{0}' has mismatched quotes '{1}' around value.
|
||||
/// </summary>
|
||||
internal static string TagHelperDescriptorFactory_InvalidRequiredAttributeMismatchedQuotes
|
||||
{
|
||||
get { return GetString("TagHelperDescriptorFactory_InvalidRequiredAttributeMismatchedQuotes"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Required attribute '{0}' has mismatched quotes '{1}' around value.
|
||||
/// </summary>
|
||||
internal static string FormatTagHelperDescriptorFactory_InvalidRequiredAttributeMismatchedQuotes(object p0, object p1)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidRequiredAttributeMismatchedQuotes"), p0, p1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Required attribute '{0}' has a partial CSS operator. '{1}' must be followed by an equals.
|
||||
/// </summary>
|
||||
internal static string TagHelperDescriptorFactory_PartialRequiredAttributeOperator
|
||||
{
|
||||
get { return GetString("TagHelperDescriptorFactory_PartialRequiredAttributeOperator"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Required attribute '{0}' has a partial CSS operator. '{1}' must be followed by an equals.
|
||||
/// </summary>
|
||||
internal static string FormatTagHelperDescriptorFactory_PartialRequiredAttributeOperator(object p0, object p1)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_PartialRequiredAttributeOperator"), p0, p1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid character '{0}' in required attribute '{1}'. Expected supported CSS operator or ']'.
|
||||
/// </summary>
|
||||
internal static string TagHelperDescriptorFactory_InvalidRequiredAttributeOperator
|
||||
{
|
||||
get { return GetString("TagHelperDescriptorFactory_InvalidRequiredAttributeOperator"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid character '{0}' in required attribute '{1}'. Expected supported CSS operator or ']'.
|
||||
/// </summary>
|
||||
internal static string FormatTagHelperDescriptorFactory_InvalidRequiredAttributeOperator(object p0, object p1)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidRequiredAttributeOperator"), p0, p1);
|
||||
}
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -195,4 +195,19 @@
|
|||
<data name="ArgumentMustBeAnInstanceOf" xml:space="preserve">
|
||||
<value>Argument must be an instance of '{0}'.</value>
|
||||
</data>
|
||||
<data name="TagHelperDescriptorFactory_CouldNotFindMatchingEndBrace" xml:space="preserve">
|
||||
<value>Could not find matching ']' for required attribute '{0}'.</value>
|
||||
</data>
|
||||
<data name="TagHelperDescriptorFactory_InvalidRequiredAttributeCharacter" xml:space="preserve">
|
||||
<value>Invalid required attribute character '{0}' in required attribute '{1}'. Separate required attributes with commas.</value>
|
||||
</data>
|
||||
<data name="TagHelperDescriptorFactory_InvalidRequiredAttributeMismatchedQuotes" xml:space="preserve">
|
||||
<value>Required attribute '{0}' has mismatched quotes '{1}' around value.</value>
|
||||
</data>
|
||||
<data name="TagHelperDescriptorFactory_PartialRequiredAttributeOperator" xml:space="preserve">
|
||||
<value>Required attribute '{0}' has a partial CSS operator. '{1}' must be followed by an equals.</value>
|
||||
</data>
|
||||
<data name="TagHelperDescriptorFactory_InvalidRequiredAttributeOperator" xml:space="preserve">
|
||||
<value>Invalid character '{0}' in required attribute '{1}'. Expected supported CSS operator or ']'.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
|
|
@ -21,6 +22,7 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
|||
private const string DataDashPrefix = "data-";
|
||||
private const string TagHelperNameEnding = "TagHelper";
|
||||
private const string HtmlCaseRegexReplacement = "-$1$2";
|
||||
private const char RequiredAttributeWildcardSuffix = '*';
|
||||
|
||||
// This matches the following AFTER the start of the input string (MATCH).
|
||||
// Any letter/number followed by an uppercase letter then lowercase letter: 1(Aa), a(Aa), A(Aa)
|
||||
|
|
@ -153,7 +155,7 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
|||
typeName,
|
||||
assemblyName,
|
||||
attributeDescriptors,
|
||||
requiredAttributes: Enumerable.Empty<string>(),
|
||||
requiredAttributeDescriptors: Enumerable.Empty<TagHelperRequiredAttributeDescriptor>(),
|
||||
allowedChildren: allowedChildren,
|
||||
tagStructure: default(TagStructure),
|
||||
parentTag: null,
|
||||
|
|
@ -235,14 +237,15 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
|||
IEnumerable<string> allowedChildren,
|
||||
TagHelperDesignTimeDescriptor designTimeDescriptor)
|
||||
{
|
||||
var requiredAttributes = GetCommaSeparatedValues(targetElementAttribute.Attributes);
|
||||
IEnumerable<TagHelperRequiredAttributeDescriptor> requiredAttributeDescriptors;
|
||||
TryGetRequiredAttributeDescriptors(targetElementAttribute.Attributes, errorSink: null, descriptors: out requiredAttributeDescriptors);
|
||||
|
||||
return BuildTagHelperDescriptor(
|
||||
targetElementAttribute.Tag,
|
||||
typeName,
|
||||
assemblyName,
|
||||
attributeDescriptors,
|
||||
requiredAttributes,
|
||||
requiredAttributeDescriptors,
|
||||
allowedChildren,
|
||||
targetElementAttribute.ParentTag,
|
||||
targetElementAttribute.TagStructure,
|
||||
|
|
@ -254,7 +257,7 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
|||
string typeName,
|
||||
string assemblyName,
|
||||
IEnumerable<TagHelperAttributeDescriptor> attributeDescriptors,
|
||||
IEnumerable<string> requiredAttributes,
|
||||
IEnumerable<TagHelperRequiredAttributeDescriptor> requiredAttributeDescriptors,
|
||||
IEnumerable<string> allowedChildren,
|
||||
string parentTag,
|
||||
TagStructure tagStructure,
|
||||
|
|
@ -266,7 +269,7 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
|||
TypeName = typeName,
|
||||
AssemblyName = assemblyName,
|
||||
Attributes = attributeDescriptors,
|
||||
RequiredAttributes = requiredAttributes,
|
||||
RequiredAttributes = requiredAttributeDescriptors,
|
||||
AllowedChildren = allowedChildren,
|
||||
RequiredParent = parentTag,
|
||||
TagStructure = tagStructure,
|
||||
|
|
@ -274,15 +277,6 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
|||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal for testing.
|
||||
/// </summary>
|
||||
internal static IEnumerable<string> GetCommaSeparatedValues(string text)
|
||||
{
|
||||
// We don't want to remove empty entries, need to notify users of invalid values.
|
||||
return text?.Split(',').Select(tagName => tagName.Trim()) ?? Enumerable.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal for testing.
|
||||
/// </summary>
|
||||
|
|
@ -291,20 +285,11 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
|||
ErrorSink errorSink)
|
||||
{
|
||||
var validTagName = ValidateName(attribute.Tag, targetingAttributes: false, errorSink: errorSink);
|
||||
var validAttributeNames = true;
|
||||
var attributeNames = GetCommaSeparatedValues(attribute.Attributes);
|
||||
|
||||
foreach (var attributeName in attributeNames)
|
||||
{
|
||||
if (!ValidateName(attributeName, targetingAttributes: true, errorSink: errorSink))
|
||||
{
|
||||
validAttributeNames = false;
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<TagHelperRequiredAttributeDescriptor> requiredAttributeDescriptors;
|
||||
var validRequiredAttributes = TryGetRequiredAttributeDescriptors(attribute.Attributes, errorSink, out requiredAttributeDescriptors);
|
||||
var validParentTagName = ValidateParentTagName(attribute.ParentTag, errorSink);
|
||||
|
||||
return validTagName && validAttributeNames && validParentTagName;
|
||||
return validTagName && validRequiredAttributes && validParentTagName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -325,10 +310,17 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
|||
errorSink: errorSink);
|
||||
}
|
||||
|
||||
private static bool ValidateName(
|
||||
string name,
|
||||
bool targetingAttributes,
|
||||
ErrorSink errorSink)
|
||||
private static bool TryGetRequiredAttributeDescriptors(
|
||||
string requiredAttributes,
|
||||
ErrorSink errorSink,
|
||||
out IEnumerable<TagHelperRequiredAttributeDescriptor> descriptors)
|
||||
{
|
||||
var parser = new RequiredAttributeParser(requiredAttributes);
|
||||
|
||||
return parser.TryParse(errorSink, out descriptors);
|
||||
}
|
||||
|
||||
private static bool ValidateName(string name, bool targetingAttributes, ErrorSink errorSink)
|
||||
{
|
||||
if (!targetingAttributes &&
|
||||
string.Equals(
|
||||
|
|
@ -339,15 +331,6 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
|||
// '*' as the entire name is OK in the HtmlTargetElement catch-all case.
|
||||
return true;
|
||||
}
|
||||
else if (targetingAttributes &&
|
||||
name.EndsWith(
|
||||
TagHelperDescriptorProvider.RequiredAttributeWildcardSuffix,
|
||||
StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// A single '*' at the end of a required attribute is valid; everywhere else is invalid. Strip it from
|
||||
// the end so we can validate the rest of the name.
|
||||
name = name.Substring(0, name.Length - 1);
|
||||
}
|
||||
|
||||
var targetName = targetingAttributes ?
|
||||
Resources.TagHelperDescriptorFactory_Attribute :
|
||||
|
|
@ -750,5 +733,329 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
|||
{
|
||||
return HtmlCaseRegex.Replace(name, HtmlCaseRegexReplacement).ToLowerInvariant();
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal class RequiredAttributeParser
|
||||
{
|
||||
private static readonly IReadOnlyDictionary<char, TagHelperRequiredAttributeValueComparison> CssValueComparisons =
|
||||
new Dictionary<char, TagHelperRequiredAttributeValueComparison>
|
||||
{
|
||||
{ '=', TagHelperRequiredAttributeValueComparison.FullMatch },
|
||||
{ '^', TagHelperRequiredAttributeValueComparison.PrefixMatch },
|
||||
{ '$', TagHelperRequiredAttributeValueComparison.SuffixMatch }
|
||||
};
|
||||
private static readonly char[] InvalidPlainAttributeNameCharacters = { ' ', '\t', ',', RequiredAttributeWildcardSuffix };
|
||||
private static readonly char[] InvalidCssAttributeNameCharacters = (new[] { ' ', '\t', ',', ']' })
|
||||
.Concat(CssValueComparisons.Keys)
|
||||
.ToArray();
|
||||
private static readonly char[] InvalidCssQuotelessValueCharacters = { ' ', '\t', ']' };
|
||||
|
||||
private int _index;
|
||||
private string _requiredAttributes;
|
||||
|
||||
public RequiredAttributeParser(string requiredAttributes)
|
||||
{
|
||||
_requiredAttributes = requiredAttributes;
|
||||
}
|
||||
|
||||
private char Current => _requiredAttributes[_index];
|
||||
|
||||
private bool AtEnd => _index >= _requiredAttributes.Length;
|
||||
|
||||
public bool TryParse(
|
||||
ErrorSink errorSink,
|
||||
out IEnumerable<TagHelperRequiredAttributeDescriptor> requiredAttributeDescriptors)
|
||||
{
|
||||
if (string.IsNullOrEmpty(_requiredAttributes))
|
||||
{
|
||||
requiredAttributeDescriptors = Enumerable.Empty<TagHelperRequiredAttributeDescriptor>();
|
||||
return true;
|
||||
}
|
||||
|
||||
requiredAttributeDescriptors = null;
|
||||
var descriptors = new List<TagHelperRequiredAttributeDescriptor>();
|
||||
|
||||
PassOptionalWhitespace();
|
||||
|
||||
do
|
||||
{
|
||||
TagHelperRequiredAttributeDescriptor descriptor;
|
||||
if (At('['))
|
||||
{
|
||||
descriptor = ParseCssSelector(errorSink);
|
||||
}
|
||||
else
|
||||
{
|
||||
descriptor = ParsePlainSelector(errorSink);
|
||||
}
|
||||
|
||||
if (descriptor == null)
|
||||
{
|
||||
// Failed to create the descriptor due to an invalid required attribute.
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
descriptors.Add(descriptor);
|
||||
}
|
||||
|
||||
PassOptionalWhitespace();
|
||||
|
||||
if (At(','))
|
||||
{
|
||||
_index++;
|
||||
|
||||
if (!EnsureNotAtEnd(errorSink))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (!AtEnd)
|
||||
{
|
||||
errorSink.OnError(
|
||||
SourceLocation.Zero,
|
||||
Resources.FormatTagHelperDescriptorFactory_InvalidRequiredAttributeCharacter(Current, _requiredAttributes),
|
||||
length: 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
PassOptionalWhitespace();
|
||||
}
|
||||
while (!AtEnd);
|
||||
|
||||
requiredAttributeDescriptors = descriptors;
|
||||
return true;
|
||||
}
|
||||
|
||||
private TagHelperRequiredAttributeDescriptor ParsePlainSelector(ErrorSink errorSink)
|
||||
{
|
||||
var nameEndIndex = _requiredAttributes.IndexOfAny(InvalidPlainAttributeNameCharacters, _index);
|
||||
string attributeName;
|
||||
|
||||
var nameComparison = TagHelperRequiredAttributeNameComparison.FullMatch;
|
||||
if (nameEndIndex == -1)
|
||||
{
|
||||
attributeName = _requiredAttributes.Substring(_index);
|
||||
_index = _requiredAttributes.Length;
|
||||
}
|
||||
else
|
||||
{
|
||||
attributeName = _requiredAttributes.Substring(_index, nameEndIndex - _index);
|
||||
_index = nameEndIndex;
|
||||
|
||||
if (_requiredAttributes[nameEndIndex] == RequiredAttributeWildcardSuffix)
|
||||
{
|
||||
nameComparison = TagHelperRequiredAttributeNameComparison.PrefixMatch;
|
||||
|
||||
// Move past wild card
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
|
||||
TagHelperRequiredAttributeDescriptor descriptor = null;
|
||||
if (ValidateName(attributeName, targetingAttributes: true, errorSink: errorSink))
|
||||
{
|
||||
descriptor = new TagHelperRequiredAttributeDescriptor
|
||||
{
|
||||
Name = attributeName,
|
||||
NameComparison = nameComparison
|
||||
};
|
||||
}
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
private string ParseCssAttributeName(ErrorSink errorSink)
|
||||
{
|
||||
var nameStartIndex = _index;
|
||||
var nameEndIndex = _requiredAttributes.IndexOfAny(InvalidCssAttributeNameCharacters, _index);
|
||||
nameEndIndex = nameEndIndex == -1 ? _requiredAttributes.Length : nameEndIndex;
|
||||
_index = nameEndIndex;
|
||||
|
||||
var attributeName = _requiredAttributes.Substring(nameStartIndex, nameEndIndex - nameStartIndex);
|
||||
|
||||
return attributeName;
|
||||
}
|
||||
|
||||
private TagHelperRequiredAttributeValueComparison? ParseCssValueComparison(ErrorSink errorSink)
|
||||
{
|
||||
Debug.Assert(!AtEnd);
|
||||
TagHelperRequiredAttributeValueComparison valueComparison;
|
||||
|
||||
if (CssValueComparisons.TryGetValue(Current, out valueComparison))
|
||||
{
|
||||
var op = Current;
|
||||
_index++;
|
||||
|
||||
if (op != '=' && At('='))
|
||||
{
|
||||
// Two length operator (ex: ^=). Move past the second piece
|
||||
_index++;
|
||||
}
|
||||
else if (op != '=') // We're at an incomplete operator (ex: [foo^]
|
||||
{
|
||||
errorSink.OnError(
|
||||
SourceLocation.Zero,
|
||||
Resources.FormatTagHelperDescriptorFactory_PartialRequiredAttributeOperator(_requiredAttributes, op),
|
||||
length: 0);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else if (!At(']'))
|
||||
{
|
||||
errorSink.OnError(
|
||||
SourceLocation.Zero,
|
||||
Resources.FormatTagHelperDescriptorFactory_InvalidRequiredAttributeOperator(Current, _requiredAttributes),
|
||||
length: 0);
|
||||
return null;
|
||||
}
|
||||
|
||||
return valueComparison;
|
||||
}
|
||||
|
||||
private string ParseCssValue(ErrorSink errorSink)
|
||||
{
|
||||
int valueStart;
|
||||
int valueEnd;
|
||||
if (At('\'') || At('"'))
|
||||
{
|
||||
var quote = Current;
|
||||
|
||||
// Move past the quote
|
||||
_index++;
|
||||
|
||||
valueStart = _index;
|
||||
valueEnd = _requiredAttributes.IndexOf(quote, _index);
|
||||
if (valueEnd == -1)
|
||||
{
|
||||
errorSink.OnError(
|
||||
SourceLocation.Zero,
|
||||
Resources.FormatTagHelperDescriptorFactory_InvalidRequiredAttributeMismatchedQuotes(
|
||||
_requiredAttributes,
|
||||
quote),
|
||||
length: 0);
|
||||
return null;
|
||||
}
|
||||
_index = valueEnd + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
valueStart = _index;
|
||||
var valueEndIndex = _requiredAttributes.IndexOfAny(InvalidCssQuotelessValueCharacters, _index);
|
||||
valueEnd = valueEndIndex == -1 ? _requiredAttributes.Length : valueEndIndex;
|
||||
_index = valueEnd;
|
||||
}
|
||||
|
||||
var value = _requiredAttributes.Substring(valueStart, valueEnd - valueStart);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private TagHelperRequiredAttributeDescriptor ParseCssSelector(ErrorSink errorSink)
|
||||
{
|
||||
Debug.Assert(At('['));
|
||||
|
||||
// Move past '['.
|
||||
_index++;
|
||||
PassOptionalWhitespace();
|
||||
|
||||
var attributeName = ParseCssAttributeName(errorSink);
|
||||
|
||||
PassOptionalWhitespace();
|
||||
|
||||
if (!EnsureNotAtEnd(errorSink))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!ValidateName(attributeName, targetingAttributes: true, errorSink: errorSink))
|
||||
{
|
||||
// Couldn't parse a valid attribute name.
|
||||
return null;
|
||||
}
|
||||
|
||||
var valueComparison = ParseCssValueComparison(errorSink);
|
||||
|
||||
if (!valueComparison.HasValue)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
PassOptionalWhitespace();
|
||||
|
||||
if (!EnsureNotAtEnd(errorSink))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var value = ParseCssValue(errorSink);
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
// Couldn't parse value
|
||||
return null;
|
||||
}
|
||||
|
||||
PassOptionalWhitespace();
|
||||
|
||||
if (At(']'))
|
||||
{
|
||||
// Move past the ending bracket.
|
||||
_index++;
|
||||
}
|
||||
else if (AtEnd)
|
||||
{
|
||||
errorSink.OnError(
|
||||
SourceLocation.Zero,
|
||||
Resources.FormatTagHelperDescriptorFactory_CouldNotFindMatchingEndBrace(_requiredAttributes),
|
||||
length: 0);
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
errorSink.OnError(
|
||||
SourceLocation.Zero,
|
||||
Resources.FormatTagHelperDescriptorFactory_InvalidRequiredAttributeCharacter(Current, _requiredAttributes),
|
||||
length: 0);
|
||||
return null;
|
||||
}
|
||||
|
||||
return new TagHelperRequiredAttributeDescriptor
|
||||
{
|
||||
Name = attributeName,
|
||||
NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch,
|
||||
Value = value,
|
||||
ValueComparison = valueComparison.Value,
|
||||
};
|
||||
}
|
||||
|
||||
private bool EnsureNotAtEnd(ErrorSink errorSink)
|
||||
{
|
||||
if (AtEnd)
|
||||
{
|
||||
errorSink.OnError(
|
||||
SourceLocation.Zero,
|
||||
Resources.FormatTagHelperDescriptorFactory_CouldNotFindMatchingEndBrace(_requiredAttributes),
|
||||
length: 0);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool At(char c)
|
||||
{
|
||||
return !AtEnd && Current == c;
|
||||
}
|
||||
|
||||
private void PassOptionalWhitespace()
|
||||
{
|
||||
while (!AtEnd && (Current == ' ' || Current == '\t'))
|
||||
{
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -45,8 +45,10 @@ namespace Microsoft.AspNetCore.Razor.TagHelpers
|
|||
public string Tag { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A comma-separated <see cref="string"/> of attribute names the HTML element must contain for the
|
||||
/// <see cref="ITagHelper"/> to run. <c>*</c> at the end of an attribute name acts as a prefix match.
|
||||
/// A comma-separated <see cref="string"/> of attribute selectors the HTML element must match for the
|
||||
/// <see cref="ITagHelper"/> to run. <c>*</c> at the end of an attribute name acts as a prefix match. A value
|
||||
/// surrounded by square brackets is handled as a CSS attribute value selector. Operators <c>^=</c>, <c>$=</c> and
|
||||
/// <c>=</c> are supported e.g. <c>"name"</c>, <c>"[name]"</c>, <c>"[name=value]"</c>, <c>"[ name ^= 'value' ]"</c>.
|
||||
/// </summary>
|
||||
public string Attributes { get; set; }
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,10 @@ namespace Microsoft.AspNetCore.Razor.Test.Internal
|
|||
// attributes or prefixes. In tests we do.
|
||||
Assert.Equal(descriptorX.TagName, descriptorY.TagName, StringComparer.Ordinal);
|
||||
Assert.Equal(descriptorX.Prefix, descriptorY.Prefix, StringComparer.Ordinal);
|
||||
Assert.Equal(descriptorX.RequiredAttributes, descriptorY.RequiredAttributes, StringComparer.Ordinal);
|
||||
Assert.Equal(
|
||||
descriptorX.RequiredAttributes,
|
||||
descriptorY.RequiredAttributes,
|
||||
CaseSensitiveTagHelperRequiredAttributeDescriptorComparer.Default);
|
||||
Assert.Equal(descriptorX.RequiredParent, descriptorY.RequiredParent, StringComparer.Ordinal);
|
||||
|
||||
if (descriptorX.AllowedChildren != descriptorY.AllowedChildren)
|
||||
|
|
@ -66,9 +69,10 @@ namespace Microsoft.AspNetCore.Razor.Test.Internal
|
|||
TagHelperDesignTimeDescriptorComparer.Default.GetHashCode(descriptor.DesignTimeDescriptor));
|
||||
}
|
||||
|
||||
foreach (var requiredAttribute in descriptor.RequiredAttributes.OrderBy(attribute => attribute))
|
||||
foreach (var requiredAttribute in descriptor.RequiredAttributes.OrderBy(attribute => attribute.Name))
|
||||
{
|
||||
hashCodeCombiner.Add(requiredAttribute, StringComparer.Ordinal);
|
||||
hashCodeCombiner.Add(
|
||||
CaseSensitiveTagHelperRequiredAttributeDescriptorComparer.Default.GetHashCode(requiredAttribute));
|
||||
}
|
||||
|
||||
if (descriptor.AllowedChildren != null)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
// 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 Microsoft.AspNetCore.Razor.Compilation.TagHelpers;
|
||||
using Microsoft.Extensions.Internal;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Test.Internal
|
||||
{
|
||||
internal class CaseSensitiveTagHelperRequiredAttributeDescriptorComparer : TagHelperRequiredAttributeDescriptorComparer
|
||||
{
|
||||
public new static readonly CaseSensitiveTagHelperRequiredAttributeDescriptorComparer Default =
|
||||
new CaseSensitiveTagHelperRequiredAttributeDescriptorComparer();
|
||||
|
||||
private CaseSensitiveTagHelperRequiredAttributeDescriptorComparer()
|
||||
: base()
|
||||
{
|
||||
}
|
||||
|
||||
public override bool Equals(TagHelperRequiredAttributeDescriptor descriptorX, TagHelperRequiredAttributeDescriptor descriptorY)
|
||||
{
|
||||
if (descriptorX == descriptorY)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Assert.True(base.Equals(descriptorX, descriptorY));
|
||||
Assert.Equal(descriptorX.Name, descriptorY.Name, StringComparer.Ordinal);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override int GetHashCode(TagHelperRequiredAttributeDescriptor descriptor)
|
||||
{
|
||||
var hashCodeCombiner = HashCodeCombiner.Start();
|
||||
hashCodeCombiner.Add(base.GetHashCode(descriptor));
|
||||
hashCodeCombiner.Add(descriptor.Name, StringComparer.Ordinal);
|
||||
|
||||
return hashCodeCombiner.CombinedHash;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
|||
private string _assemblyName;
|
||||
private IEnumerable<TagHelperAttributeDescriptor> _attributes =
|
||||
Enumerable.Empty<TagHelperAttributeDescriptor>();
|
||||
private IEnumerable<string> _requiredAttributes = Enumerable.Empty<string>();
|
||||
private IEnumerable<TagHelperRequiredAttributeDescriptor> _requiredAttributes = Enumerable.Empty<TagHelperRequiredAttributeDescriptor>();
|
||||
|
||||
/// <summary>
|
||||
/// Text used as a required prefix when matching HTML start and end tags in the Razor source to available
|
||||
|
|
@ -140,7 +140,7 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
|||
/// <remarks>
|
||||
/// <c>*</c> at the end of an attribute name acts as a prefix match.
|
||||
/// </remarks>
|
||||
public IEnumerable<string> RequiredAttributes
|
||||
public IEnumerable<TagHelperRequiredAttributeDescriptor> RequiredAttributes
|
||||
{
|
||||
get
|
||||
{
|
||||
|
|
|
|||
|
|
@ -51,9 +51,9 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
|||
descriptorY.RequiredParent,
|
||||
StringComparison.OrdinalIgnoreCase) &&
|
||||
Enumerable.SequenceEqual(
|
||||
descriptorX.RequiredAttributes.OrderBy(attribute => attribute, StringComparer.OrdinalIgnoreCase),
|
||||
descriptorY.RequiredAttributes.OrderBy(attribute => attribute, StringComparer.OrdinalIgnoreCase),
|
||||
StringComparer.OrdinalIgnoreCase) &&
|
||||
descriptorX.RequiredAttributes.OrderBy(attribute => attribute.Name, StringComparer.OrdinalIgnoreCase),
|
||||
descriptorY.RequiredAttributes.OrderBy(attribute => attribute.Name, StringComparer.OrdinalIgnoreCase),
|
||||
TagHelperRequiredAttributeDescriptorComparer.Default) &&
|
||||
(descriptorX.AllowedChildren == descriptorY.AllowedChildren ||
|
||||
(descriptorX.AllowedChildren != null &&
|
||||
descriptorY.AllowedChildren != null &&
|
||||
|
|
@ -80,11 +80,11 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
|||
hashCodeCombiner.Add(descriptor.TagStructure);
|
||||
|
||||
var attributes = descriptor.RequiredAttributes.OrderBy(
|
||||
attribute => attribute,
|
||||
attribute => attribute.Name,
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var attribute in attributes)
|
||||
{
|
||||
hashCodeCombiner.Add(attribute, StringComparer.OrdinalIgnoreCase);
|
||||
hashCodeCombiner.Add(TagHelperRequiredAttributeDescriptorComparer.Default.GetHashCode(attribute));
|
||||
}
|
||||
|
||||
if (descriptor.AllowedChildren != null)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Razor.Parser.TagHelpers;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
||||
{
|
||||
|
|
@ -14,8 +15,6 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
|||
{
|
||||
public const string ElementCatchAllTarget = "*";
|
||||
|
||||
public static readonly string RequiredAttributeWildcardSuffix = "*";
|
||||
|
||||
private IDictionary<string, HashSet<TagHelperDescriptor>> _registrations;
|
||||
private string _tagHelperPrefix;
|
||||
|
||||
|
|
@ -39,14 +38,14 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
|||
/// </summary>
|
||||
/// <param name="tagName">The name of the HTML tag to match. Providing a '*' tag name
|
||||
/// retrieves catch-all <see cref="TagHelperDescriptor"/>s (descriptors that target every tag).</param>
|
||||
/// <param name="attributeNames">Attributes the HTML element must contain to match.</param>
|
||||
/// <param name="attributes">Attributes the HTML element must contain to match.</param>
|
||||
/// <param name="parentTagName">The parent tag name of the given <paramref name="tagName"/> tag.</param>
|
||||
/// <returns><see cref="TagHelperDescriptor"/>s that apply to the given <paramref name="tagName"/>.
|
||||
/// Will return an empty <see cref="Enumerable" /> if no <see cref="TagHelperDescriptor"/>s are
|
||||
/// found.</returns>
|
||||
public IEnumerable<TagHelperDescriptor> GetDescriptors(
|
||||
string tagName,
|
||||
IEnumerable<string> attributeNames,
|
||||
IEnumerable<KeyValuePair<string, string>> attributes,
|
||||
string parentTagName)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_tagHelperPrefix) &&
|
||||
|
|
@ -78,10 +77,10 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
|||
descriptors = matchingDescriptors.Concat(descriptors);
|
||||
}
|
||||
|
||||
var applicableDescriptors = ApplyRequiredAttributes(descriptors, attributeNames);
|
||||
var applicableDescriptors = ApplyRequiredAttributes(descriptors, attributes);
|
||||
applicableDescriptors = ApplyParentTagFilter(applicableDescriptors, parentTagName);
|
||||
|
||||
return applicableDescriptors;
|
||||
return applicableDescriptors.ToArray();
|
||||
}
|
||||
|
||||
private IEnumerable<TagHelperDescriptor> ApplyParentTagFilter(
|
||||
|
|
@ -95,37 +94,12 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
|||
|
||||
private IEnumerable<TagHelperDescriptor> ApplyRequiredAttributes(
|
||||
IEnumerable<TagHelperDescriptor> descriptors,
|
||||
IEnumerable<string> attributeNames)
|
||||
IEnumerable<KeyValuePair<string, string>> attributes)
|
||||
{
|
||||
return descriptors.Where(
|
||||
descriptor =>
|
||||
{
|
||||
foreach (var requiredAttribute in descriptor.RequiredAttributes)
|
||||
{
|
||||
// '*' at the end of a required attribute indicates: apply to attributes prefixed with the
|
||||
// required attribute value.
|
||||
if (requiredAttribute.EndsWith(
|
||||
RequiredAttributeWildcardSuffix,
|
||||
StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var prefix = requiredAttribute.Substring(0, requiredAttribute.Length - 1);
|
||||
|
||||
if (!attributeNames.Any(
|
||||
attributeName =>
|
||||
attributeName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase) &&
|
||||
!string.Equals(attributeName, prefix, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (!attributeNames.Contains(requiredAttribute, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
descriptor => descriptor.RequiredAttributes.All(
|
||||
requiredAttribute => attributes.Any(
|
||||
attribute => requiredAttribute.IsMatch(attribute.Key, attribute.Value))));
|
||||
}
|
||||
|
||||
private void Register(TagHelperDescriptor descriptor)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,82 @@
|
|||
// 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;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// A metadata class describing a required tag helper attribute.
|
||||
/// </summary>
|
||||
public class TagHelperRequiredAttributeDescriptor
|
||||
{
|
||||
/// <summary>
|
||||
/// The HTML attribute name.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The comparison method to use for <see cref="Name"/> when determining if an HTML attribute name matches.
|
||||
/// </summary>
|
||||
public TagHelperRequiredAttributeNameComparison NameComparison { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The HTML attribute value.
|
||||
/// </summary>
|
||||
public string Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The comparison method to use for <see cref="Value"/> when determining if an HTML attribute value matches.
|
||||
/// </summary>
|
||||
public TagHelperRequiredAttributeValueComparison ValueComparison { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the current <see cref="TagHelperRequiredAttributeDescriptor"/> matches the given
|
||||
/// <paramref name="attributeName"/> and <paramref name="attributeValue"/>.
|
||||
/// </summary>
|
||||
/// <param name="attributeName">An HTML attribute name.</param>
|
||||
/// <param name="attributeValue">An HTML attribute value.</param>
|
||||
/// <returns><c>true</c> if the current <see cref="TagHelperRequiredAttributeDescriptor"/> matches
|
||||
/// <paramref name="attributeName"/> and <paramref name="attributeValue"/>; <c>false</c> otherwise.</returns>
|
||||
public bool IsMatch(string attributeName, string attributeValue)
|
||||
{
|
||||
var nameMatches = false;
|
||||
if (NameComparison == TagHelperRequiredAttributeNameComparison.FullMatch)
|
||||
{
|
||||
nameMatches = string.Equals(Name, attributeName, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
else if (NameComparison == TagHelperRequiredAttributeNameComparison.PrefixMatch)
|
||||
{
|
||||
// attributeName cannot equal the Name if comparing as a PrefixMatch.
|
||||
nameMatches = attributeName.Length != Name.Length &&
|
||||
attributeName.StartsWith(Name, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(false, "Unknown name comparison.");
|
||||
}
|
||||
|
||||
if (!nameMatches)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (ValueComparison)
|
||||
{
|
||||
case TagHelperRequiredAttributeValueComparison.None:
|
||||
return true;
|
||||
case TagHelperRequiredAttributeValueComparison.PrefixMatch: // Value starts with
|
||||
return attributeValue.StartsWith(Value, StringComparison.Ordinal);
|
||||
case TagHelperRequiredAttributeValueComparison.SuffixMatch: // Value ends with
|
||||
return attributeValue.EndsWith(Value, StringComparison.Ordinal);
|
||||
case TagHelperRequiredAttributeValueComparison.FullMatch: // Value equals
|
||||
return string.Equals(attributeValue, Value, StringComparison.Ordinal);
|
||||
default:
|
||||
Debug.Assert(false, "Unknown value comparison.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.Extensions.Internal;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="IEqualityComparer{TagHelperRequiredAttributeDescriptor}"/> used to check equality between
|
||||
/// two <see cref="TagHelperRequiredAttributeDescriptor"/>s.
|
||||
/// </summary>
|
||||
public class TagHelperRequiredAttributeDescriptorComparer : IEqualityComparer<TagHelperRequiredAttributeDescriptor>
|
||||
{
|
||||
/// <summary>
|
||||
/// A default instance of the <see cref="TagHelperRequiredAttributeDescriptor"/>.
|
||||
/// </summary>
|
||||
public static readonly TagHelperRequiredAttributeDescriptorComparer Default =
|
||||
new TagHelperRequiredAttributeDescriptorComparer();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="TagHelperRequiredAttributeDescriptor"/> instance.
|
||||
/// </summary>
|
||||
protected TagHelperRequiredAttributeDescriptorComparer()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual bool Equals(
|
||||
TagHelperRequiredAttributeDescriptor descriptorX,
|
||||
TagHelperRequiredAttributeDescriptor descriptorY)
|
||||
{
|
||||
if (descriptorX == descriptorY)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return descriptorX != null &&
|
||||
descriptorX.NameComparison == descriptorY.NameComparison &&
|
||||
descriptorX.ValueComparison == descriptorY.ValueComparison &&
|
||||
string.Equals(descriptorX.Name, descriptorY.Name, StringComparison.OrdinalIgnoreCase) &&
|
||||
string.Equals(descriptorX.Value, descriptorY.Value, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual int GetHashCode(TagHelperRequiredAttributeDescriptor descriptor)
|
||||
{
|
||||
var hashCodeCombiner = HashCodeCombiner.Start();
|
||||
hashCodeCombiner.Add(descriptor.NameComparison);
|
||||
hashCodeCombiner.Add(descriptor.ValueComparison);
|
||||
hashCodeCombiner.Add(descriptor.Name, StringComparer.OrdinalIgnoreCase);
|
||||
hashCodeCombiner.Add(descriptor.Value, StringComparer.Ordinal);
|
||||
|
||||
return hashCodeCombiner.CombinedHash;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
// 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.AspNetCore.Razor.Compilation.TagHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Acceptable <see cref="TagHelperRequiredAttributeDescriptor.Name"/> comparison modes.
|
||||
/// </summary>
|
||||
public enum TagHelperRequiredAttributeNameComparison
|
||||
{
|
||||
/// <summary>
|
||||
/// HTML attribute name case insensitively matches <see cref="TagHelperRequiredAttributeDescriptor.Name"/>.
|
||||
/// </summary>
|
||||
FullMatch,
|
||||
|
||||
/// <summary>
|
||||
/// HTML attribute name case insensitively starts with <see cref="TagHelperRequiredAttributeDescriptor.Name"/>.
|
||||
/// </summary>
|
||||
PrefixMatch,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
// 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.AspNetCore.Razor.Compilation.TagHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Acceptable <see cref="TagHelperRequiredAttributeDescriptor.Value"/> comparison modes.
|
||||
/// </summary>
|
||||
public enum TagHelperRequiredAttributeValueComparison
|
||||
{
|
||||
/// <summary>
|
||||
/// HTML attribute value always matches <see cref="TagHelperRequiredAttributeDescriptor.Value"/>.
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// HTML attribute value case sensitively matches <see cref="TagHelperRequiredAttributeDescriptor.Value"/>.
|
||||
/// </summary>
|
||||
FullMatch,
|
||||
|
||||
/// <summary>
|
||||
/// HTML attribute value case sensitively starts with <see cref="TagHelperRequiredAttributeDescriptor.Value"/>.
|
||||
/// </summary>
|
||||
PrefixMatch,
|
||||
|
||||
/// <summary>
|
||||
/// HTML attribute value case sensitively ends with <see cref="TagHelperRequiredAttributeDescriptor.Value"/>.
|
||||
/// </summary>
|
||||
SuffixMatch,
|
||||
}
|
||||
}
|
||||
|
|
@ -239,7 +239,8 @@ namespace Microsoft.AspNetCore.Razor.Parser
|
|||
return addOrRemoveTagHelperSpanVisitor.GetDescriptors(documentRoot);
|
||||
}
|
||||
|
||||
private static IEnumerable<ISyntaxTreeRewriter> GetDefaultRewriters(ParserBase markupParser)
|
||||
// Internal for testing
|
||||
internal static IEnumerable<ISyntaxTreeRewriter> GetDefaultRewriters(ParserBase markupParser)
|
||||
{
|
||||
return new ISyntaxTreeRewriter[]
|
||||
{
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Razor.Compilation.TagHelpers;
|
||||
using Microsoft.AspNetCore.Razor.Parser.SyntaxTree;
|
||||
using Microsoft.AspNetCore.Razor.TagHelpers;
|
||||
|
|
@ -14,6 +15,10 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
|||
{
|
||||
public class TagHelperParseTreeRewriter : ISyntaxTreeRewriter
|
||||
{
|
||||
// Internal for testing.
|
||||
// Null characters are invalid markup for HTML attribute values.
|
||||
internal static readonly string InvalidAttributeValueMarker = "\0";
|
||||
|
||||
// From http://dev.w3.org/html5/spec/Overview.html#elements-0
|
||||
private static readonly HashSet<string> VoidElements = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
|
|
@ -35,10 +40,12 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
|||
"wbr"
|
||||
};
|
||||
|
||||
private TagHelperDescriptorProvider _provider;
|
||||
private Stack<TagBlockTracker> _trackerStack;
|
||||
private readonly List<KeyValuePair<string, string>> _htmlAttributeTracker;
|
||||
private readonly StringBuilder _attributeValueBuilder;
|
||||
private readonly TagHelperDescriptorProvider _provider;
|
||||
private readonly Stack<TagBlockTracker> _trackerStack;
|
||||
private readonly Stack<BlockBuilder> _blockStack;
|
||||
private TagHelperBlockTracker _currentTagHelperTracker;
|
||||
private Stack<BlockBuilder> _blockStack;
|
||||
private BlockBuilder _currentBlock;
|
||||
private string _currentParentTagName;
|
||||
|
||||
|
|
@ -47,6 +54,8 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
|||
_provider = provider;
|
||||
_trackerStack = new Stack<TagBlockTracker>();
|
||||
_blockStack = new Stack<BlockBuilder>();
|
||||
_attributeValueBuilder = new StringBuilder();
|
||||
_htmlAttributeTracker = new List<KeyValuePair<string, string>>();
|
||||
}
|
||||
|
||||
public void Rewrite(RewritingContext context)
|
||||
|
|
@ -177,7 +186,7 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
|||
if (!IsEndTag(tagBlock))
|
||||
{
|
||||
// We're now in a start tag block, we first need to see if the tag block is a tag helper.
|
||||
var providedAttributes = GetAttributeNames(tagBlock);
|
||||
var providedAttributes = GetAttributeNameValuePairs(tagBlock);
|
||||
|
||||
descriptors = _provider.GetDescriptors(tagName, providedAttributes, _currentParentTagName);
|
||||
|
||||
|
|
@ -246,7 +255,7 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
|||
{
|
||||
descriptors = _provider.GetDescriptors(
|
||||
tagName,
|
||||
attributeNames: Enumerable.Empty<string>(),
|
||||
attributes: Enumerable.Empty<KeyValuePair<string, string>>(),
|
||||
parentTagName: _currentParentTagName);
|
||||
|
||||
// If there are not TagHelperDescriptors associated with the end tag block that also have no
|
||||
|
|
@ -299,7 +308,8 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
|||
return true;
|
||||
}
|
||||
|
||||
private IEnumerable<string> GetAttributeNames(Block tagBlock)
|
||||
// Internal for testing
|
||||
internal IEnumerable<KeyValuePair<string, string>> GetAttributeNameValuePairs(Block tagBlock)
|
||||
{
|
||||
// Need to calculate how many children we should take that represent the attributes.
|
||||
var childrenOffset = IsPartialTag(tagBlock) ? 0 : 1;
|
||||
|
|
@ -307,32 +317,112 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
|||
|
||||
if (childCount <= 1)
|
||||
{
|
||||
return Enumerable.Empty<string>();
|
||||
return Enumerable.Empty<KeyValuePair<string, string>>();
|
||||
}
|
||||
|
||||
var attributeChildren = new List<SyntaxTreeNode>(childCount - 1);
|
||||
_htmlAttributeTracker.Clear();
|
||||
|
||||
var attributes = _htmlAttributeTracker;
|
||||
|
||||
for (var i = 1; i < childCount; i++)
|
||||
{
|
||||
attributeChildren.Add(tagBlock.Children[i]);
|
||||
}
|
||||
var attributeNames = new List<string>();
|
||||
|
||||
foreach (var child in attributeChildren)
|
||||
{
|
||||
var child = tagBlock.Children[i];
|
||||
Span childSpan;
|
||||
|
||||
if (child.IsBlock)
|
||||
{
|
||||
childSpan = ((Block)child).FindFirstDescendentSpan();
|
||||
var childBlock = (Block)child;
|
||||
|
||||
if (childBlock.Type != BlockType.Markup)
|
||||
{
|
||||
// Anything other than markup blocks in the attribute area of tags mangles following attributes.
|
||||
// It's also not supported by TagHelpers, bail early to avoid creating bad attribute value pairs.
|
||||
break;
|
||||
}
|
||||
|
||||
childSpan = childBlock.FindFirstDescendentSpan();
|
||||
|
||||
if (childSpan == null)
|
||||
{
|
||||
_attributeValueBuilder.Append(InvalidAttributeValueMarker);
|
||||
continue;
|
||||
}
|
||||
|
||||
// We can assume the first span will always contain attributename=" and the last span will always
|
||||
// contain the final quote. Therefore, if the values not quoted there's no ending quote to skip.
|
||||
var childOffset = 0;
|
||||
if (childSpan.Symbols.Count > 0)
|
||||
{
|
||||
var potentialQuote = childSpan.Symbols[childSpan.Symbols.Count - 1] as HtmlSymbol;
|
||||
if (potentialQuote != null &&
|
||||
(potentialQuote.Type == HtmlSymbolType.DoubleQuote ||
|
||||
potentialQuote.Type == HtmlSymbolType.SingleQuote))
|
||||
{
|
||||
childOffset = 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (var j = 1; j < childBlock.Children.Count - childOffset; j++)
|
||||
{
|
||||
var valueChild = childBlock.Children[j];
|
||||
if (valueChild.IsBlock)
|
||||
{
|
||||
_attributeValueBuilder.Append(InvalidAttributeValueMarker);
|
||||
}
|
||||
else
|
||||
{
|
||||
var valueChildSpan = (Span)valueChild;
|
||||
for (var k = 0; k < valueChildSpan.Symbols.Count; k++)
|
||||
{
|
||||
_attributeValueBuilder.Append(valueChildSpan.Symbols[k].Content);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
childSpan = child as Span;
|
||||
childSpan = (Span)child;
|
||||
|
||||
var afterEquals = false;
|
||||
var atValue = false;
|
||||
var endValueMarker = childSpan.Symbols.Count;
|
||||
|
||||
// Entire attribute is a string
|
||||
for (var j = 0; j < endValueMarker; j++)
|
||||
{
|
||||
var htmlSymbol = (HtmlSymbol)childSpan.Symbols[j];
|
||||
|
||||
if (!afterEquals)
|
||||
{
|
||||
afterEquals = htmlSymbol.Type == HtmlSymbolType.Equals;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!atValue)
|
||||
{
|
||||
atValue = htmlSymbol.Type != HtmlSymbolType.WhiteSpace &&
|
||||
htmlSymbol.Type != HtmlSymbolType.NewLine;
|
||||
|
||||
if (atValue)
|
||||
{
|
||||
if (htmlSymbol.Type == HtmlSymbolType.DoubleQuote ||
|
||||
htmlSymbol.Type == HtmlSymbolType.SingleQuote)
|
||||
{
|
||||
endValueMarker--;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Current symbol is considered the value (unquoted). Add its content to the
|
||||
// attribute value builder before we move past it.
|
||||
_attributeValueBuilder.Append(htmlSymbol.Content);
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
_attributeValueBuilder.Append(htmlSymbol.Content);
|
||||
}
|
||||
}
|
||||
|
||||
var start = 0;
|
||||
|
|
@ -344,8 +434,8 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
|||
}
|
||||
}
|
||||
|
||||
var end = 0;
|
||||
for (end = start; end < childSpan.Content.Length; end++)
|
||||
var end = start;
|
||||
for (; end < childSpan.Content.Length; end++)
|
||||
{
|
||||
if (childSpan.Content[end] == '=')
|
||||
{
|
||||
|
|
@ -353,10 +443,15 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
|||
}
|
||||
}
|
||||
|
||||
attributeNames.Add(childSpan.Content.Substring(start, end - start));
|
||||
var attributeName = childSpan.Content.Substring(start, end - start);
|
||||
var attributeValue = _attributeValueBuilder.ToString();
|
||||
var attribute = new KeyValuePair<string, string>(attributeName, attributeValue);
|
||||
attributes.Add(attribute);
|
||||
|
||||
_attributeValueBuilder.Clear();
|
||||
}
|
||||
|
||||
return attributeNames;
|
||||
return attributes;
|
||||
}
|
||||
|
||||
private bool HasAllowedChildren()
|
||||
|
|
@ -650,7 +745,7 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
|||
{
|
||||
var child = tagBlock.Children[0];
|
||||
|
||||
if (tagBlock.Type != BlockType.Tag || tagBlock.Children.Count == 0|| !(child is Span))
|
||||
if (tagBlock.Type != BlockType.Tag || tagBlock.Children.Count == 0 || !(child is Span))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,155 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
|||
|
||||
protected static readonly string AssemblyName = TagHelperDescriptorFactoryTestAssembly.Name;
|
||||
|
||||
public static TheoryData RequiredAttributeParserErrorData
|
||||
{
|
||||
get
|
||||
{
|
||||
Func<string, RazorError> error = (message) => new RazorError(message, SourceLocation.Zero, 0);
|
||||
|
||||
return new TheoryData<string, RazorError>
|
||||
{
|
||||
{ "name,", error(Resources.FormatTagHelperDescriptorFactory_CouldNotFindMatchingEndBrace("name,")) },
|
||||
{ " ", error(Resources.FormatHtmlTargetElementAttribute_NameCannotBeNullOrWhitespace("Attribute")) },
|
||||
{ "n@me", error(Resources.FormatHtmlTargetElementAttribute_InvalidName("attribute", "n@me", '@')) },
|
||||
{ "name extra", error(Resources.FormatTagHelperDescriptorFactory_InvalidRequiredAttributeCharacter('e', "name extra")) },
|
||||
{ "[[ ", error(Resources.FormatTagHelperDescriptorFactory_CouldNotFindMatchingEndBrace("[[ ")) },
|
||||
{ "[ ", error(Resources.FormatTagHelperDescriptorFactory_CouldNotFindMatchingEndBrace("[ ")) },
|
||||
{
|
||||
"[name='unended]",
|
||||
error(Resources.FormatTagHelperDescriptorFactory_InvalidRequiredAttributeMismatchedQuotes("[name='unended]", '\''))
|
||||
},
|
||||
{
|
||||
"[name='unended",
|
||||
error(Resources.FormatTagHelperDescriptorFactory_InvalidRequiredAttributeMismatchedQuotes("[name='unended", '\''))
|
||||
},
|
||||
{ "[name", error(Resources.FormatTagHelperDescriptorFactory_CouldNotFindMatchingEndBrace("[name")) },
|
||||
{ "[ ]", error(Resources.FormatHtmlTargetElementAttribute_NameCannotBeNullOrWhitespace("Attribute")) },
|
||||
{ "[n@me]", error(Resources.FormatHtmlTargetElementAttribute_InvalidName("attribute", "n@me", '@')) },
|
||||
{ "[name@]", error(Resources.FormatHtmlTargetElementAttribute_InvalidName("attribute", "name@", '@')) },
|
||||
{ "[name^]", error(Resources.FormatTagHelperDescriptorFactory_PartialRequiredAttributeOperator("[name^]", '^')) },
|
||||
{ "[name='value'", error(Resources.FormatTagHelperDescriptorFactory_CouldNotFindMatchingEndBrace("[name='value'")) },
|
||||
{ "[name ", error(Resources.FormatTagHelperDescriptorFactory_CouldNotFindMatchingEndBrace("[name ")) },
|
||||
{ "[name extra]", error(Resources.FormatTagHelperDescriptorFactory_InvalidRequiredAttributeOperator('e', "[name extra]")) },
|
||||
{ "[name=value ", error(Resources.FormatTagHelperDescriptorFactory_CouldNotFindMatchingEndBrace("[name=value ")) },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RequiredAttributeParserErrorData))]
|
||||
public void RequiredAttributeParser_ParsesRequiredAttributesAndLogsErrorCorrectly(
|
||||
string requiredAttributes,
|
||||
RazorError expectedError)
|
||||
{
|
||||
// Arrange
|
||||
var parser = new TagHelperDescriptorFactory.RequiredAttributeParser(requiredAttributes);
|
||||
var errorSink = new ErrorSink();
|
||||
IEnumerable<TagHelperRequiredAttributeDescriptor> descriptors;
|
||||
|
||||
// Act
|
||||
var parsedCorrectly = parser.TryParse(errorSink, out descriptors);
|
||||
|
||||
// Assert
|
||||
Assert.False(parsedCorrectly);
|
||||
Assert.Null(descriptors);
|
||||
var error = Assert.Single(errorSink.Errors);
|
||||
Assert.Equal(expectedError, error);
|
||||
}
|
||||
|
||||
public static TheoryData RequiredAttributeParserData
|
||||
{
|
||||
get
|
||||
{
|
||||
Func<string, TagHelperRequiredAttributeNameComparison, TagHelperRequiredAttributeDescriptor> plain =
|
||||
(name, nameComparison) => new TagHelperRequiredAttributeDescriptor
|
||||
{
|
||||
Name = name,
|
||||
NameComparison = nameComparison
|
||||
};
|
||||
Func<string, string, TagHelperRequiredAttributeValueComparison, TagHelperRequiredAttributeDescriptor> css =
|
||||
(name, value, valueComparison) => new TagHelperRequiredAttributeDescriptor
|
||||
{
|
||||
Name = name,
|
||||
NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch,
|
||||
Value = value,
|
||||
ValueComparison = valueComparison,
|
||||
};
|
||||
|
||||
return new TheoryData<string, IEnumerable<TagHelperRequiredAttributeDescriptor>>
|
||||
{
|
||||
{ null, Enumerable.Empty<TagHelperRequiredAttributeDescriptor>() },
|
||||
{ string.Empty, Enumerable.Empty<TagHelperRequiredAttributeDescriptor>() },
|
||||
{ "name", new[] { plain("name", TagHelperRequiredAttributeNameComparison.FullMatch) } },
|
||||
{ "name-*", new[] { plain("name-", TagHelperRequiredAttributeNameComparison.PrefixMatch) } },
|
||||
{ " name-* ", new[] { plain("name-", TagHelperRequiredAttributeNameComparison.PrefixMatch) } },
|
||||
{
|
||||
"asp-route-*,valid , name-* ,extra",
|
||||
new[]
|
||||
{
|
||||
plain("asp-route-", TagHelperRequiredAttributeNameComparison.PrefixMatch),
|
||||
plain("valid", TagHelperRequiredAttributeNameComparison.FullMatch),
|
||||
plain("name-", TagHelperRequiredAttributeNameComparison.PrefixMatch),
|
||||
plain("extra", TagHelperRequiredAttributeNameComparison.FullMatch),
|
||||
}
|
||||
},
|
||||
{ "[name]", new[] { css("name", "", TagHelperRequiredAttributeValueComparison.None) } },
|
||||
{ "[ name ]", new[] { css("name", "", TagHelperRequiredAttributeValueComparison.None) } },
|
||||
{ " [ name ] ", new[] { css("name", "", TagHelperRequiredAttributeValueComparison.None) } },
|
||||
{ "[name=]", new[] { css("name", "", TagHelperRequiredAttributeValueComparison.FullMatch) } },
|
||||
{ "[name='']", new[] { css("name", "", TagHelperRequiredAttributeValueComparison.FullMatch) } },
|
||||
{ "[name ^=]", new[] { css("name", "", TagHelperRequiredAttributeValueComparison.PrefixMatch) } },
|
||||
{ "[name=hello]", new[] { css("name", "hello", TagHelperRequiredAttributeValueComparison.FullMatch) } },
|
||||
{ "[name= hello]", new[] { css("name", "hello", TagHelperRequiredAttributeValueComparison.FullMatch) } },
|
||||
{ "[name='hello']", new[] { css("name", "hello", TagHelperRequiredAttributeValueComparison.FullMatch) } },
|
||||
{ "[name=\"hello\"]", new[] { css("name", "hello", TagHelperRequiredAttributeValueComparison.FullMatch) } },
|
||||
{ " [ name $= \" hello\" ] ", new[] { css("name", " hello", TagHelperRequiredAttributeValueComparison.SuffixMatch) } },
|
||||
{
|
||||
"[name=\"hello\"],[other^=something ], [val = 'cool']",
|
||||
new[]
|
||||
{
|
||||
css("name", "hello", TagHelperRequiredAttributeValueComparison.FullMatch),
|
||||
css("other", "something", TagHelperRequiredAttributeValueComparison.PrefixMatch),
|
||||
css("val", "cool", TagHelperRequiredAttributeValueComparison.FullMatch) }
|
||||
},
|
||||
{
|
||||
"asp-route-*,[name=\"hello\"],valid ,[other^=something ], name-* ,[val = 'cool'],extra",
|
||||
new[]
|
||||
{
|
||||
plain("asp-route-", TagHelperRequiredAttributeNameComparison.PrefixMatch),
|
||||
css("name", "hello", TagHelperRequiredAttributeValueComparison.FullMatch),
|
||||
plain("valid", TagHelperRequiredAttributeNameComparison.FullMatch),
|
||||
css("other", "something", TagHelperRequiredAttributeValueComparison.PrefixMatch),
|
||||
plain("name-", TagHelperRequiredAttributeNameComparison.PrefixMatch),
|
||||
css("val", "cool", TagHelperRequiredAttributeValueComparison.FullMatch),
|
||||
plain("extra", TagHelperRequiredAttributeNameComparison.FullMatch),
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RequiredAttributeParserData))]
|
||||
public void RequiredAttributeParser_ParsesRequiredAttributesCorrectly(
|
||||
string requiredAttributes,
|
||||
IEnumerable<TagHelperRequiredAttributeDescriptor> expectedDescriptors)
|
||||
{
|
||||
// Arrange
|
||||
var parser = new TagHelperDescriptorFactory.RequiredAttributeParser(requiredAttributes);
|
||||
var errorSink = new ErrorSink();
|
||||
IEnumerable<TagHelperRequiredAttributeDescriptor> descriptors;
|
||||
|
||||
// Act
|
||||
//System.Diagnostics.Debugger.Launch();
|
||||
var parsedCorrectly = parser.TryParse(errorSink, out descriptors);
|
||||
|
||||
// Assert
|
||||
Assert.True(parsedCorrectly);
|
||||
Assert.Empty(errorSink.Errors);
|
||||
Assert.Equal(expectedDescriptors, descriptors, CaseSensitiveTagHelperRequiredAttributeDescriptorComparer.Default);
|
||||
}
|
||||
|
||||
public static TheoryData IsEnumData
|
||||
{
|
||||
get
|
||||
|
|
@ -617,7 +766,10 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
|||
typeof(AttributeTargetingTagHelper).FullName,
|
||||
AssemblyName,
|
||||
attributes,
|
||||
requiredAttributes: new[] { "class" })
|
||||
requiredAttributes: new[]
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor { Name = "class" }
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -629,7 +781,11 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
|||
typeof(MultiAttributeTargetingTagHelper).FullName,
|
||||
AssemblyName,
|
||||
attributes,
|
||||
requiredAttributes: new[] { "class", "style" })
|
||||
requiredAttributes: new[]
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor { Name = "class" },
|
||||
new TagHelperRequiredAttributeDescriptor { Name = "style" }
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -641,13 +797,20 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
|||
typeof(MultiAttributeAttributeTargetingTagHelper).FullName,
|
||||
AssemblyName,
|
||||
attributes,
|
||||
requiredAttributes: new[] { "custom" }),
|
||||
requiredAttributes: new[]
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor { Name = "custom" }
|
||||
}),
|
||||
CreateTagHelperDescriptor(
|
||||
TagHelperDescriptorProvider.ElementCatchAllTarget,
|
||||
typeof(MultiAttributeAttributeTargetingTagHelper).FullName,
|
||||
AssemblyName,
|
||||
attributes,
|
||||
requiredAttributes: new[] { "class", "style" })
|
||||
requiredAttributes: new[]
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor { Name = "class" },
|
||||
new TagHelperRequiredAttributeDescriptor { Name = "style" }
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -659,7 +822,10 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
|||
typeof(InheritedAttributeTargetingTagHelper).FullName,
|
||||
AssemblyName,
|
||||
attributes,
|
||||
requiredAttributes: new[] { "style" })
|
||||
requiredAttributes: new[]
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor { Name = "style" }
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -671,7 +837,10 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
|||
typeof(RequiredAttributeTagHelper).FullName,
|
||||
AssemblyName,
|
||||
attributes,
|
||||
requiredAttributes: new[] { "class" })
|
||||
requiredAttributes: new[]
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor { Name = "class" }
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -683,7 +852,10 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
|||
typeof(InheritedRequiredAttributeTagHelper).FullName,
|
||||
AssemblyName,
|
||||
attributes,
|
||||
requiredAttributes: new[] { "class" })
|
||||
requiredAttributes: new[]
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor { Name = "class" }
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -695,13 +867,19 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
|||
typeof(MultiAttributeRequiredAttributeTagHelper).FullName,
|
||||
AssemblyName,
|
||||
attributes,
|
||||
requiredAttributes: new[] { "class" }),
|
||||
requiredAttributes: new[]
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor { Name = "class" }
|
||||
}),
|
||||
CreateTagHelperDescriptor(
|
||||
"input",
|
||||
typeof(MultiAttributeRequiredAttributeTagHelper).FullName,
|
||||
AssemblyName,
|
||||
attributes,
|
||||
requiredAttributes: new[] { "class" })
|
||||
requiredAttributes: new[]
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor { Name = "class" }
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -713,13 +891,19 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
|||
typeof(MultiAttributeSameTagRequiredAttributeTagHelper).FullName,
|
||||
AssemblyName,
|
||||
attributes,
|
||||
requiredAttributes: new[] { "style" }),
|
||||
requiredAttributes: new[]
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor { Name = "style" }
|
||||
}),
|
||||
CreateTagHelperDescriptor(
|
||||
"input",
|
||||
typeof(MultiAttributeSameTagRequiredAttributeTagHelper).FullName,
|
||||
AssemblyName,
|
||||
attributes,
|
||||
requiredAttributes: new[] { "class" })
|
||||
requiredAttributes: new[]
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor { Name = "class" }
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -731,7 +915,11 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
|||
typeof(MultiRequiredAttributeTagHelper).FullName,
|
||||
AssemblyName,
|
||||
attributes,
|
||||
requiredAttributes: new[] { "class", "style" })
|
||||
requiredAttributes: new[]
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor { Name = "class" },
|
||||
new TagHelperRequiredAttributeDescriptor { Name = "style" }
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -743,13 +931,20 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
|||
typeof(MultiTagMultiRequiredAttributeTagHelper).FullName,
|
||||
AssemblyName,
|
||||
attributes,
|
||||
requiredAttributes: new[] { "class", "style" }),
|
||||
requiredAttributes: new[]
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor { Name = "class" },
|
||||
new TagHelperRequiredAttributeDescriptor { Name = "style" }
|
||||
}),
|
||||
CreateTagHelperDescriptor(
|
||||
"input",
|
||||
typeof(MultiTagMultiRequiredAttributeTagHelper).FullName,
|
||||
AssemblyName,
|
||||
attributes,
|
||||
requiredAttributes: new[] { "class", "style" }),
|
||||
requiredAttributes: new[] {
|
||||
new TagHelperRequiredAttributeDescriptor { Name = "class" },
|
||||
new TagHelperRequiredAttributeDescriptor { Name = "style" }
|
||||
}),
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -761,7 +956,14 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
|||
typeof(AttributeWildcardTargetingTagHelper).FullName,
|
||||
AssemblyName,
|
||||
attributes,
|
||||
requiredAttributes: new[] { "class*" })
|
||||
requiredAttributes: new[]
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor
|
||||
{
|
||||
Name = "class",
|
||||
NameComparison = TagHelperRequiredAttributeNameComparison.PrefixMatch,
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -773,7 +975,19 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
|||
typeof(MultiAttributeWildcardTargetingTagHelper).FullName,
|
||||
AssemblyName,
|
||||
attributes,
|
||||
requiredAttributes: new[] { "class*", "style*" })
|
||||
requiredAttributes: new[]
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor
|
||||
{
|
||||
Name = "class",
|
||||
NameComparison = TagHelperRequiredAttributeNameComparison.PrefixMatch,
|
||||
},
|
||||
new TagHelperRequiredAttributeDescriptor
|
||||
{
|
||||
Name = "style",
|
||||
NameComparison = TagHelperRequiredAttributeNameComparison.PrefixMatch,
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
@ -1327,29 +1541,6 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
|||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ValidNameData))]
|
||||
public void GetCommaSeparatedValues_OutputsCommaSeparatedListOfNames(
|
||||
string name,
|
||||
IEnumerable<string> expectedNames)
|
||||
{
|
||||
// Act
|
||||
var result = TagHelperDescriptorFactory.GetCommaSeparatedValues(name);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedNames, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCommaSeparatedValues_OutputsEmptyArrayForNullValue()
|
||||
{
|
||||
// Act
|
||||
var result = TagHelperDescriptorFactory.GetCommaSeparatedValues(text: null);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
public static TheoryData InvalidTagHelperAttributeDescriptorData
|
||||
{
|
||||
get
|
||||
|
|
@ -2293,7 +2484,7 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
|||
string typeName,
|
||||
string assemblyName,
|
||||
IEnumerable<TagHelperAttributeDescriptor> attributes = null,
|
||||
IEnumerable<string> requiredAttributes = null)
|
||||
IEnumerable<TagHelperRequiredAttributeDescriptor> requiredAttributes = null)
|
||||
{
|
||||
return new TagHelperDescriptor
|
||||
{
|
||||
|
|
@ -2301,7 +2492,7 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
|||
TypeName = typeName,
|
||||
AssemblyName = assemblyName,
|
||||
Attributes = attributes ?? Enumerable.Empty<TagHelperAttributeDescriptor>(),
|
||||
RequiredAttributes = requiredAttributes ?? Enumerable.Empty<string>()
|
||||
RequiredAttributes = requiredAttributes ?? Enumerable.Empty<TagHelperRequiredAttributeDescriptor>()
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,126 @@ namespace Microsoft.AspNetCore.Razor.Test.Generator
|
|||
private static IEnumerable<TagHelperDescriptor> PrefixedPAndInputTagHelperDescriptors { get; }
|
||||
= BuildPAndInputTagHelperDescriptors(prefix: "THS");
|
||||
|
||||
private static IEnumerable<TagHelperDescriptor> CssSelectorTagHelperDescriptors
|
||||
{
|
||||
get
|
||||
{
|
||||
var inputTypePropertyInfo = typeof(TestType).GetProperty("Type");
|
||||
var inputCheckedPropertyInfo = typeof(TestType).GetProperty("Checked");
|
||||
|
||||
return new[]
|
||||
{
|
||||
new TagHelperDescriptor
|
||||
{
|
||||
TagName = "a",
|
||||
TypeName = "TestNamespace.ATagHelper",
|
||||
AssemblyName = "SomeAssembly",
|
||||
RequiredAttributes = new[]
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor
|
||||
{
|
||||
Name = "href",
|
||||
NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch,
|
||||
Value = "~/",
|
||||
ValueComparison = TagHelperRequiredAttributeValueComparison.FullMatch,
|
||||
}
|
||||
},
|
||||
},
|
||||
new TagHelperDescriptor
|
||||
{
|
||||
TagName = "a",
|
||||
TypeName = "TestNamespace.ATagHelperMultipleSelectors",
|
||||
AssemblyName = "SomeAssembly",
|
||||
RequiredAttributes = new[]
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor
|
||||
{
|
||||
Name = "href",
|
||||
NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch,
|
||||
Value = "~/",
|
||||
ValueComparison = TagHelperRequiredAttributeValueComparison.PrefixMatch,
|
||||
},
|
||||
new TagHelperRequiredAttributeDescriptor
|
||||
{
|
||||
Name = "href",
|
||||
NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch,
|
||||
Value = "?hello=world",
|
||||
ValueComparison = TagHelperRequiredAttributeValueComparison.SuffixMatch,
|
||||
}
|
||||
},
|
||||
},
|
||||
new TagHelperDescriptor
|
||||
{
|
||||
TagName = "input",
|
||||
TypeName = "TestNamespace.InputTagHelper",
|
||||
AssemblyName = "SomeAssembly",
|
||||
Attributes = new TagHelperAttributeDescriptor[]
|
||||
{
|
||||
new TagHelperAttributeDescriptor("type", inputTypePropertyInfo),
|
||||
},
|
||||
RequiredAttributes = new[]
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor
|
||||
{
|
||||
Name = "type",
|
||||
NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch,
|
||||
Value = "text",
|
||||
ValueComparison = TagHelperRequiredAttributeValueComparison.FullMatch,
|
||||
}
|
||||
},
|
||||
},
|
||||
new TagHelperDescriptor
|
||||
{
|
||||
TagName = "input",
|
||||
TypeName = "TestNamespace.InputTagHelper2",
|
||||
AssemblyName = "SomeAssembly",
|
||||
Attributes = new TagHelperAttributeDescriptor[]
|
||||
{
|
||||
new TagHelperAttributeDescriptor("type", inputTypePropertyInfo),
|
||||
},
|
||||
RequiredAttributes = new[]
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor
|
||||
{
|
||||
Name = "ty",
|
||||
NameComparison = TagHelperRequiredAttributeNameComparison.PrefixMatch,
|
||||
}
|
||||
},
|
||||
},
|
||||
new TagHelperDescriptor
|
||||
{
|
||||
TagName = "*",
|
||||
TypeName = "TestNamespace.CatchAllTagHelper",
|
||||
AssemblyName = "SomeAssembly",
|
||||
RequiredAttributes = new[]
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor
|
||||
{
|
||||
Name = "href",
|
||||
NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch,
|
||||
Value = "~/",
|
||||
ValueComparison = TagHelperRequiredAttributeValueComparison.PrefixMatch,
|
||||
}
|
||||
},
|
||||
},
|
||||
new TagHelperDescriptor
|
||||
{
|
||||
TagName = "*",
|
||||
TypeName = "TestNamespace.CatchAllTagHelper2",
|
||||
AssemblyName = "SomeAssembly",
|
||||
RequiredAttributes = new[]
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor
|
||||
{
|
||||
Name = "type",
|
||||
NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch,
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<TagHelperDescriptor> EnumTagHelperDescriptors
|
||||
{
|
||||
get
|
||||
|
|
@ -113,7 +233,7 @@ namespace Microsoft.AspNetCore.Razor.Test.Generator
|
|||
TypeName = typeof(string).FullName
|
||||
},
|
||||
},
|
||||
RequiredAttributes = new[] { "bound" },
|
||||
RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "bound" } },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -140,7 +260,10 @@ namespace Microsoft.AspNetCore.Razor.Test.Generator
|
|||
IsStringProperty = true
|
||||
}
|
||||
},
|
||||
RequiredAttributes = new[] { "catchall-unbound-required" },
|
||||
RequiredAttributes = new[]
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor { Name = "catchall-unbound-required" }
|
||||
},
|
||||
},
|
||||
new TagHelperDescriptor
|
||||
{
|
||||
|
|
@ -164,7 +287,11 @@ namespace Microsoft.AspNetCore.Razor.Test.Generator
|
|||
IsStringProperty = true
|
||||
}
|
||||
},
|
||||
RequiredAttributes = new[] { "input-bound-required-string", "input-unbound-required" },
|
||||
RequiredAttributes = new[]
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor { Name = "input-bound-required-string" },
|
||||
new TagHelperRequiredAttributeDescriptor { Name = "input-unbound-required" }
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -214,7 +341,7 @@ namespace Microsoft.AspNetCore.Razor.Test.Generator
|
|||
new TagHelperAttributeDescriptor("type", inputTypePropertyInfo),
|
||||
new TagHelperAttributeDescriptor("checked", inputCheckedPropertyInfo)
|
||||
},
|
||||
RequiredAttributes = new[] { "type" },
|
||||
RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "type" } },
|
||||
},
|
||||
new TagHelperDescriptor
|
||||
{
|
||||
|
|
@ -226,7 +353,7 @@ namespace Microsoft.AspNetCore.Razor.Test.Generator
|
|||
new TagHelperAttributeDescriptor("type", inputTypePropertyInfo),
|
||||
new TagHelperAttributeDescriptor("checked", inputCheckedPropertyInfo)
|
||||
},
|
||||
RequiredAttributes = new[] { "checked" },
|
||||
RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "checked" } },
|
||||
},
|
||||
new TagHelperDescriptor
|
||||
{
|
||||
|
|
@ -238,7 +365,7 @@ namespace Microsoft.AspNetCore.Razor.Test.Generator
|
|||
new TagHelperAttributeDescriptor("type", inputTypePropertyInfo),
|
||||
new TagHelperAttributeDescriptor("checked", inputCheckedPropertyInfo)
|
||||
},
|
||||
RequiredAttributes = new[] { "type" },
|
||||
RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "type" } },
|
||||
},
|
||||
new TagHelperDescriptor
|
||||
{
|
||||
|
|
@ -250,7 +377,7 @@ namespace Microsoft.AspNetCore.Razor.Test.Generator
|
|||
new TagHelperAttributeDescriptor("type", inputTypePropertyInfo),
|
||||
new TagHelperAttributeDescriptor("checked", inputCheckedPropertyInfo)
|
||||
},
|
||||
RequiredAttributes = new[] { "checked" },
|
||||
RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "checked" } },
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -269,7 +396,7 @@ namespace Microsoft.AspNetCore.Razor.Test.Generator
|
|||
TagName = "p",
|
||||
TypeName = "TestNamespace.PTagHelper",
|
||||
AssemblyName = "SomeAssembly",
|
||||
RequiredAttributes = new[] { "class" },
|
||||
RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "class" } },
|
||||
},
|
||||
new TagHelperDescriptor
|
||||
{
|
||||
|
|
@ -280,7 +407,7 @@ namespace Microsoft.AspNetCore.Razor.Test.Generator
|
|||
{
|
||||
new TagHelperAttributeDescriptor("type", inputTypePropertyInfo)
|
||||
},
|
||||
RequiredAttributes = new[] { "type" },
|
||||
RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "type" } },
|
||||
},
|
||||
new TagHelperDescriptor
|
||||
{
|
||||
|
|
@ -292,14 +419,18 @@ namespace Microsoft.AspNetCore.Razor.Test.Generator
|
|||
new TagHelperAttributeDescriptor("type", inputTypePropertyInfo),
|
||||
new TagHelperAttributeDescriptor("checked", inputCheckedPropertyInfo)
|
||||
},
|
||||
RequiredAttributes = new[] { "type", "checked" },
|
||||
RequiredAttributes = new[]
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor { Name = "type" },
|
||||
new TagHelperRequiredAttributeDescriptor { Name = "checked" }
|
||||
},
|
||||
},
|
||||
new TagHelperDescriptor
|
||||
{
|
||||
TagName = "*",
|
||||
TypeName = "TestNamespace.CatchAllTagHelper",
|
||||
AssemblyName = "SomeAssembly",
|
||||
RequiredAttributes = new[] { "catchAll" },
|
||||
RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "catchAll" } },
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -1774,6 +1905,7 @@ namespace Microsoft.AspNetCore.Razor.Test.Generator
|
|||
// Note: The baseline resource name is equivalent to the test resource name.
|
||||
return new TheoryData<string, string, IEnumerable<TagHelperDescriptor>>
|
||||
{
|
||||
{ "CssSelectorTagHelperAttributes", null, CssSelectorTagHelperDescriptors },
|
||||
{ "IncompleteTagHelper", null, DefaultPAndInputTagHelperDescriptors },
|
||||
{ "SingleTagHelper", null, DefaultPAndInputTagHelperDescriptors },
|
||||
{ "SingleTagHelperWithNewlineBeforeAttributes", null, DefaultPAndInputTagHelperDescriptors },
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
|||
TypeName = typeof(string).FullName
|
||||
},
|
||||
},
|
||||
RequiredAttributes = new[] { "bound" },
|
||||
RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "bound" } },
|
||||
},
|
||||
};
|
||||
var descriptorProvider = new TagHelperDescriptorProvider(descriptors);
|
||||
|
|
@ -3940,7 +3940,10 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
|||
IsStringProperty = true
|
||||
}
|
||||
},
|
||||
RequiredAttributes = new[] { "unbound-required" }
|
||||
RequiredAttributes = new[]
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor { Name = "unbound-required" }
|
||||
}
|
||||
},
|
||||
new TagHelperDescriptor
|
||||
{
|
||||
|
|
@ -3957,7 +3960,10 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
|||
IsStringProperty = true
|
||||
}
|
||||
},
|
||||
RequiredAttributes = new[] { "bound-required-string" }
|
||||
RequiredAttributes = new[]
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor { Name = "bound-required-string" }
|
||||
}
|
||||
},
|
||||
new TagHelperDescriptor
|
||||
{
|
||||
|
|
@ -3973,7 +3979,10 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
|||
TypeName = typeof(int).FullName
|
||||
}
|
||||
},
|
||||
RequiredAttributes = new[] { "bound-required-int" }
|
||||
RequiredAttributes = new[]
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor { Name = "bound-required-int" }
|
||||
}
|
||||
},
|
||||
new TagHelperDescriptor
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
// 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.Linq;
|
||||
using Microsoft.AspNetCore.Razor.TagHelpers;
|
||||
using Microsoft.AspNetCore.Razor.Test.Internal;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -73,7 +73,7 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
|||
|
||||
[Theory]
|
||||
[MemberData(nameof(RequiredParentData))]
|
||||
public void GetDescriptors_ReturnsDescriptorsWithRequiredAttributes(
|
||||
public void GetDescriptors_ReturnsDescriptorsParentTags(
|
||||
string tagName,
|
||||
string parentTagName,
|
||||
IEnumerable<TagHelperDescriptor> availableDescriptors,
|
||||
|
|
@ -85,7 +85,7 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
|||
// Act
|
||||
var resolvedDescriptors = provider.GetDescriptors(
|
||||
tagName,
|
||||
attributeNames: Enumerable.Empty<string>(),
|
||||
attributes: Enumerable.Empty<KeyValuePair<string, string>>(),
|
||||
parentTagName: parentTagName);
|
||||
|
||||
// Assert
|
||||
|
|
@ -101,131 +101,155 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
|||
TagName = "div",
|
||||
TypeName = "DivTagHelper",
|
||||
AssemblyName = "SomeAssembly",
|
||||
RequiredAttributes = new[] { "style" }
|
||||
RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "style" } }
|
||||
};
|
||||
var inputDescriptor = new TagHelperDescriptor
|
||||
{
|
||||
TagName = "input",
|
||||
TypeName = "InputTagHelper",
|
||||
AssemblyName = "SomeAssembly",
|
||||
RequiredAttributes = new[] { "class", "style" }
|
||||
RequiredAttributes = new[]
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor { Name = "class" },
|
||||
new TagHelperRequiredAttributeDescriptor { Name = "style" }
|
||||
}
|
||||
};
|
||||
var inputWildcardPrefixDescriptor = new TagHelperDescriptor
|
||||
{
|
||||
TagName = "input",
|
||||
TypeName = "InputWildCardAttribute",
|
||||
AssemblyName = "SomeAssembly",
|
||||
RequiredAttributes = new[] { "nodashprefix*" }
|
||||
RequiredAttributes = new[]
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor
|
||||
{
|
||||
Name = "nodashprefix",
|
||||
NameComparison = TagHelperRequiredAttributeNameComparison.PrefixMatch,
|
||||
}
|
||||
}
|
||||
};
|
||||
var catchAllDescriptor = new TagHelperDescriptor
|
||||
{
|
||||
TagName = TagHelperDescriptorProvider.ElementCatchAllTarget,
|
||||
TypeName = "CatchAllTagHelper",
|
||||
AssemblyName = "SomeAssembly",
|
||||
RequiredAttributes = new[] { "class" }
|
||||
RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "class" } }
|
||||
};
|
||||
var catchAllDescriptor2 = new TagHelperDescriptor
|
||||
{
|
||||
TagName = TagHelperDescriptorProvider.ElementCatchAllTarget,
|
||||
TypeName = "CatchAllTagHelper",
|
||||
AssemblyName = "SomeAssembly",
|
||||
RequiredAttributes = new[] { "custom", "class" }
|
||||
RequiredAttributes = new[]
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor { Name = "custom" },
|
||||
new TagHelperRequiredAttributeDescriptor { Name = "class" }
|
||||
}
|
||||
};
|
||||
var catchAllWildcardPrefixDescriptor = new TagHelperDescriptor
|
||||
{
|
||||
TagName = TagHelperDescriptorProvider.ElementCatchAllTarget,
|
||||
TypeName = "CatchAllWildCardAttribute",
|
||||
AssemblyName = "SomeAssembly",
|
||||
RequiredAttributes = new[] { "prefix-*" }
|
||||
RequiredAttributes = new[]
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor
|
||||
{
|
||||
Name = "prefix-",
|
||||
NameComparison = TagHelperRequiredAttributeNameComparison.PrefixMatch,
|
||||
}
|
||||
}
|
||||
};
|
||||
var defaultAvailableDescriptors =
|
||||
new[] { divDescriptor, inputDescriptor, catchAllDescriptor, catchAllDescriptor2 };
|
||||
var defaultWildcardDescriptors =
|
||||
new[] { inputWildcardPrefixDescriptor, catchAllWildcardPrefixDescriptor };
|
||||
Func<string, KeyValuePair<string, string>> kvp =
|
||||
(name) => new KeyValuePair<string, string>(name, "test value");
|
||||
|
||||
return new TheoryData<
|
||||
string, // tagName
|
||||
IEnumerable<string>, // providedAttributes
|
||||
IEnumerable<KeyValuePair<string, string>>, // providedAttributes
|
||||
IEnumerable<TagHelperDescriptor>, // availableDescriptors
|
||||
IEnumerable<TagHelperDescriptor>> // expectedDescriptors
|
||||
{
|
||||
{
|
||||
"div",
|
||||
new[] { "custom" },
|
||||
new[] { kvp("custom") },
|
||||
defaultAvailableDescriptors,
|
||||
Enumerable.Empty<TagHelperDescriptor>()
|
||||
},
|
||||
{ "div", new[] { "style" }, defaultAvailableDescriptors, new[] { divDescriptor } },
|
||||
{ "div", new[] { "class" }, defaultAvailableDescriptors, new[] { catchAllDescriptor } },
|
||||
{ "div", new[] { kvp("style") }, defaultAvailableDescriptors, new[] { divDescriptor } },
|
||||
{ "div", new[] { kvp("class") }, defaultAvailableDescriptors, new[] { catchAllDescriptor } },
|
||||
{
|
||||
"div",
|
||||
new[] { "class", "style" },
|
||||
new[] { kvp("class"), kvp("style") },
|
||||
defaultAvailableDescriptors,
|
||||
new[] { divDescriptor, catchAllDescriptor }
|
||||
},
|
||||
{
|
||||
"div",
|
||||
new[] { "class", "style", "custom" },
|
||||
new[] { kvp("class"), kvp("style"), kvp("custom") },
|
||||
defaultAvailableDescriptors,
|
||||
new[] { divDescriptor, catchAllDescriptor, catchAllDescriptor2 }
|
||||
},
|
||||
{
|
||||
"input",
|
||||
new[] { "class", "style" },
|
||||
new[] { kvp("class"), kvp("style") },
|
||||
defaultAvailableDescriptors,
|
||||
new[] { inputDescriptor, catchAllDescriptor }
|
||||
},
|
||||
{
|
||||
"input",
|
||||
new[] { "nodashprefixA" },
|
||||
new[] { kvp("nodashprefixA") },
|
||||
defaultWildcardDescriptors,
|
||||
new[] { inputWildcardPrefixDescriptor }
|
||||
},
|
||||
{
|
||||
"input",
|
||||
new[] { "nodashprefix-ABC-DEF", "random" },
|
||||
new[] { kvp("nodashprefix-ABC-DEF"), kvp("random") },
|
||||
defaultWildcardDescriptors,
|
||||
new[] { inputWildcardPrefixDescriptor }
|
||||
},
|
||||
{
|
||||
"input",
|
||||
new[] { "prefixABCnodashprefix" },
|
||||
new[] { kvp("prefixABCnodashprefix") },
|
||||
defaultWildcardDescriptors,
|
||||
Enumerable.Empty<TagHelperDescriptor>()
|
||||
},
|
||||
{
|
||||
"input",
|
||||
new[] { "prefix-" },
|
||||
new[] { kvp("prefix-") },
|
||||
defaultWildcardDescriptors,
|
||||
Enumerable.Empty<TagHelperDescriptor>()
|
||||
},
|
||||
{
|
||||
"input",
|
||||
new[] { "nodashprefix" },
|
||||
new[] { kvp("nodashprefix") },
|
||||
defaultWildcardDescriptors,
|
||||
Enumerable.Empty<TagHelperDescriptor>()
|
||||
},
|
||||
{
|
||||
"input",
|
||||
new[] { "prefix-A" },
|
||||
new[] { kvp("prefix-A") },
|
||||
defaultWildcardDescriptors,
|
||||
new[] { catchAllWildcardPrefixDescriptor }
|
||||
},
|
||||
{
|
||||
"input",
|
||||
new[] { "prefix-ABC-DEF", "random" },
|
||||
new[] { kvp("prefix-ABC-DEF"), kvp("random") },
|
||||
defaultWildcardDescriptors,
|
||||
new[] { catchAllWildcardPrefixDescriptor }
|
||||
},
|
||||
{
|
||||
"input",
|
||||
new[] { "prefix-abc", "nodashprefix-def" },
|
||||
new[] { kvp("prefix-abc"), kvp("nodashprefix-def") },
|
||||
defaultWildcardDescriptors,
|
||||
new[] { inputWildcardPrefixDescriptor, catchAllWildcardPrefixDescriptor }
|
||||
},
|
||||
{
|
||||
"input",
|
||||
new[] { "class", "prefix-abc", "onclick", "nodashprefix-def", "style" },
|
||||
new[] { kvp("class"), kvp("prefix-abc"), kvp("onclick"), kvp("nodashprefix-def"), kvp("style") },
|
||||
defaultWildcardDescriptors,
|
||||
new[] { inputWildcardPrefixDescriptor, catchAllWildcardPrefixDescriptor }
|
||||
},
|
||||
|
|
@ -237,7 +261,7 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
|||
[MemberData(nameof(RequiredAttributeData))]
|
||||
public void GetDescriptors_ReturnsDescriptorsWithRequiredAttributes(
|
||||
string tagName,
|
||||
IEnumerable<string> providedAttributes,
|
||||
IEnumerable<KeyValuePair<string, string>> providedAttributes,
|
||||
IEnumerable<TagHelperDescriptor> availableDescriptors,
|
||||
IEnumerable<TagHelperDescriptor> expectedDescriptors)
|
||||
{
|
||||
|
|
@ -265,7 +289,7 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
|||
// Act
|
||||
var resolvedDescriptors = provider.GetDescriptors(
|
||||
tagName: "th",
|
||||
attributeNames: Enumerable.Empty<string>(),
|
||||
attributes: Enumerable.Empty<KeyValuePair<string, string>>(),
|
||||
parentTagName: "p");
|
||||
|
||||
// Assert
|
||||
|
|
@ -284,11 +308,11 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
|||
// Act
|
||||
var retrievedDescriptorsDiv = provider.GetDescriptors(
|
||||
tagName: "th:div",
|
||||
attributeNames: Enumerable.Empty<string>(),
|
||||
attributes: Enumerable.Empty<KeyValuePair<string, string>>(),
|
||||
parentTagName: "p");
|
||||
var retrievedDescriptorsSpan = provider.GetDescriptors(
|
||||
tagName: "th2:span",
|
||||
attributeNames: Enumerable.Empty<string>(),
|
||||
attributes: Enumerable.Empty<KeyValuePair<string, string>>(),
|
||||
parentTagName: "p");
|
||||
|
||||
// Assert
|
||||
|
|
@ -308,11 +332,11 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
|||
// Act
|
||||
var retrievedDescriptorsDiv = provider.GetDescriptors(
|
||||
tagName: "th:div",
|
||||
attributeNames: Enumerable.Empty<string>(),
|
||||
attributes: Enumerable.Empty<KeyValuePair<string, string>>(),
|
||||
parentTagName: "p");
|
||||
var retrievedDescriptorsSpan = provider.GetDescriptors(
|
||||
tagName: "th:span",
|
||||
attributeNames: Enumerable.Empty<string>(),
|
||||
attributes: Enumerable.Empty<KeyValuePair<string, string>>(),
|
||||
parentTagName: "p");
|
||||
|
||||
// Assert
|
||||
|
|
@ -333,7 +357,7 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
|||
// Act
|
||||
var retrievedDescriptors = provider.GetDescriptors(
|
||||
tagName: "th:div",
|
||||
attributeNames: Enumerable.Empty<string>(),
|
||||
attributes: Enumerable.Empty<KeyValuePair<string, string>>(),
|
||||
parentTagName: "p");
|
||||
|
||||
// Assert
|
||||
|
|
@ -354,7 +378,7 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
|||
// Act
|
||||
var retrievedDescriptorsDiv = provider.GetDescriptors(
|
||||
tagName: "div",
|
||||
attributeNames: Enumerable.Empty<string>(),
|
||||
attributes: Enumerable.Empty<KeyValuePair<string, string>>(),
|
||||
parentTagName: "p");
|
||||
|
||||
// Assert
|
||||
|
|
@ -383,7 +407,7 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
|||
// Act
|
||||
var retrievedDescriptors = provider.GetDescriptors(
|
||||
tagName: "foo",
|
||||
attributeNames: Enumerable.Empty<string>(),
|
||||
attributes: Enumerable.Empty<KeyValuePair<string, string>>(),
|
||||
parentTagName: "p");
|
||||
|
||||
// Assert
|
||||
|
|
@ -418,11 +442,11 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
|||
// Act
|
||||
var divDescriptors = provider.GetDescriptors(
|
||||
tagName: "div",
|
||||
attributeNames: Enumerable.Empty<string>(),
|
||||
attributes: Enumerable.Empty<KeyValuePair<string, string>>(),
|
||||
parentTagName: "p");
|
||||
var spanDescriptors = provider.GetDescriptors(
|
||||
tagName: "span",
|
||||
attributeNames: Enumerable.Empty<string>(),
|
||||
attributes: Enumerable.Empty<KeyValuePair<string, string>>(),
|
||||
parentTagName: "p");
|
||||
|
||||
// Assert
|
||||
|
|
@ -453,7 +477,7 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
|||
// Act
|
||||
var retrievedDescriptors = provider.GetDescriptors(
|
||||
tagName: "div",
|
||||
attributeNames: Enumerable.Empty<string>(),
|
||||
attributes: Enumerable.Empty<KeyValuePair<string, string>>(),
|
||||
parentTagName: "p");
|
||||
|
||||
// Assert
|
||||
|
|
|
|||
|
|
@ -21,8 +21,22 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
|||
Prefix = "prefix:",
|
||||
TagName = "tag name",
|
||||
TypeName = "type name",
|
||||
AssemblyName = "assembly name",
|
||||
RequiredAttributes = new[] { "required attribute one", "required attribute two" },
|
||||
AssemblyName = "assembly name",
|
||||
RequiredAttributes = new[]
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor
|
||||
{
|
||||
Name = "required attribute one",
|
||||
NameComparison = TagHelperRequiredAttributeNameComparison.PrefixMatch
|
||||
},
|
||||
new TagHelperRequiredAttributeDescriptor
|
||||
{
|
||||
Name = "required attribute two",
|
||||
NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch,
|
||||
Value = "something",
|
||||
ValueComparison = TagHelperRequiredAttributeValueComparison.PrefixMatch,
|
||||
}
|
||||
},
|
||||
AllowedChildren = new[] { "allowed child one" },
|
||||
RequiredParent = "parent name",
|
||||
DesignTimeDescriptor = new TagHelperDesignTimeDescriptor
|
||||
|
|
@ -41,7 +55,14 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
|||
$"\"{ nameof(TagHelperDescriptor.AssemblyName) }\":\"assembly name\"," +
|
||||
$"\"{ nameof(TagHelperDescriptor.Attributes) }\":[]," +
|
||||
$"\"{ nameof(TagHelperDescriptor.RequiredAttributes) }\":" +
|
||||
"[\"required attribute one\",\"required attribute two\"]," +
|
||||
$"[{{\"{ nameof(TagHelperRequiredAttributeDescriptor.Name)}\":\"required attribute one\"," +
|
||||
$"\"{ nameof(TagHelperRequiredAttributeDescriptor.NameComparison) }\":1," +
|
||||
$"\"{ nameof(TagHelperRequiredAttributeDescriptor.Value) }\":null," +
|
||||
$"\"{ nameof(TagHelperRequiredAttributeDescriptor.ValueComparison) }\":0}}," +
|
||||
$"{{\"{ nameof(TagHelperRequiredAttributeDescriptor.Name)}\":\"required attribute two\"," +
|
||||
$"\"{ nameof(TagHelperRequiredAttributeDescriptor.NameComparison) }\":0," +
|
||||
$"\"{ nameof(TagHelperRequiredAttributeDescriptor.Value) }\":\"something\"," +
|
||||
$"\"{ nameof(TagHelperRequiredAttributeDescriptor.ValueComparison) }\":2}}]," +
|
||||
$"\"{ nameof(TagHelperDescriptor.AllowedChildren) }\":[\"allowed child one\"]," +
|
||||
$"\"{ nameof(TagHelperDescriptor.RequiredParent) }\":\"parent name\"," +
|
||||
$"\"{ nameof(TagHelperDescriptor.TagStructure) }\":0," +
|
||||
|
|
@ -200,8 +221,15 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
|||
$"\"{nameof(TagHelperDescriptor.TypeName)}\":\"type name\"," +
|
||||
$"\"{nameof(TagHelperDescriptor.AssemblyName)}\":\"assembly name\"," +
|
||||
$"\"{nameof(TagHelperDescriptor.Attributes)}\":[]," +
|
||||
$"\"{nameof(TagHelperDescriptor.RequiredAttributes)}\":" +
|
||||
"[\"required attribute one\",\"required attribute two\"]," +
|
||||
$"\"{ nameof(TagHelperDescriptor.RequiredAttributes) }\":" +
|
||||
$"[{{\"{ nameof(TagHelperRequiredAttributeDescriptor.Name)}\":\"required attribute one\"," +
|
||||
$"\"{ nameof(TagHelperRequiredAttributeDescriptor.NameComparison) }\":1," +
|
||||
$"\"{ nameof(TagHelperRequiredAttributeDescriptor.Value) }\":null," +
|
||||
$"\"{ nameof(TagHelperRequiredAttributeDescriptor.ValueComparison) }\":0}}," +
|
||||
$"{{\"{ nameof(TagHelperRequiredAttributeDescriptor.Name)}\":\"required attribute two\"," +
|
||||
$"\"{ nameof(TagHelperRequiredAttributeDescriptor.NameComparison) }\":0," +
|
||||
$"\"{ nameof(TagHelperRequiredAttributeDescriptor.Value) }\":\"something\"," +
|
||||
$"\"{ nameof(TagHelperRequiredAttributeDescriptor.ValueComparison) }\":2}}]," +
|
||||
$"\"{ nameof(TagHelperDescriptor.AllowedChildren) }\":[\"allowed child one\",\"allowed child two\"]," +
|
||||
$"\"{ nameof(TagHelperDescriptor.RequiredParent) }\":\"parent name\"," +
|
||||
$"\"{nameof(TagHelperDescriptor.TagStructure)}\":2," +
|
||||
|
|
@ -215,7 +243,21 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
|||
TagName = "tag name",
|
||||
TypeName = "type name",
|
||||
AssemblyName = "assembly name",
|
||||
RequiredAttributes = new[] { "required attribute one", "required attribute two" },
|
||||
RequiredAttributes = new[]
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor
|
||||
{
|
||||
Name = "required attribute one",
|
||||
NameComparison = TagHelperRequiredAttributeNameComparison.PrefixMatch
|
||||
},
|
||||
new TagHelperRequiredAttributeDescriptor
|
||||
{
|
||||
Name = "required attribute two",
|
||||
NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch,
|
||||
Value = "something",
|
||||
ValueComparison = TagHelperRequiredAttributeValueComparison.PrefixMatch,
|
||||
}
|
||||
},
|
||||
AllowedChildren = new[] { "allowed child one", "allowed child two" },
|
||||
RequiredParent = "parent name",
|
||||
DesignTimeDescriptor = new TagHelperDesignTimeDescriptor
|
||||
|
|
@ -237,7 +279,7 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
|||
Assert.Equal(expectedDescriptor.TypeName, descriptor.TypeName, StringComparer.Ordinal);
|
||||
Assert.Equal(expectedDescriptor.AssemblyName, descriptor.AssemblyName, StringComparer.Ordinal);
|
||||
Assert.Empty(descriptor.Attributes);
|
||||
Assert.Equal(expectedDescriptor.RequiredAttributes, descriptor.RequiredAttributes, StringComparer.Ordinal);
|
||||
Assert.Equal(expectedDescriptor.RequiredAttributes, descriptor.RequiredAttributes, TagHelperRequiredAttributeDescriptorComparer.Default);
|
||||
Assert.Equal(
|
||||
expectedDescriptor.DesignTimeDescriptor,
|
||||
descriptor.DesignTimeDescriptor,
|
||||
|
|
|
|||
|
|
@ -13,11 +13,74 @@ using Microsoft.AspNetCore.Razor.Compilation.TagHelpers;
|
|||
using Microsoft.AspNetCore.Razor.Test.Framework;
|
||||
using Microsoft.AspNetCore.Razor.Text;
|
||||
using Xunit;
|
||||
using Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Test.TagHelpers
|
||||
{
|
||||
public class TagHelperParseTreeRewriterTest : TagHelperRewritingTestBase
|
||||
{
|
||||
public static TheoryData GetAttributeNameValuePairsData
|
||||
{
|
||||
get
|
||||
{
|
||||
var factory = CreateDefaultSpanFactory();
|
||||
var blockFactory = new BlockFactory(factory);
|
||||
Func<string, string, KeyValuePair<string, string>> kvp =
|
||||
(key, value) => new KeyValuePair<string, string>(key, value);
|
||||
var empty = Enumerable.Empty<KeyValuePair<string, string>>();
|
||||
var csharp = TagHelperParseTreeRewriter.InvalidAttributeValueMarker;
|
||||
|
||||
// documentContent, expectedPairs
|
||||
return new TheoryData<string, IEnumerable<KeyValuePair<string, string>>>
|
||||
{
|
||||
{ "<a>", empty },
|
||||
{ "<a @{ } href='~/home'>", empty },
|
||||
{ "<a href=\"@true\">", new[] { kvp("href", csharp) } },
|
||||
{ "<a href=\"prefix @true suffix\">", new[] { kvp("href", $"prefix{csharp} suffix") } },
|
||||
{ "<a href=~/home>", new[] { kvp("href", "~/home") } },
|
||||
{ "<a href=~/home @{ } nothing='something'>", new[] { kvp("href", "~/home"), kvp("", "") } },
|
||||
{
|
||||
"<a href=\"@DateTime.Now::0\" class='btn btn-success' random>",
|
||||
new[] { kvp("href", $"{csharp}::0"), kvp("class", "btn btn-success"), kvp("random", "") }
|
||||
},
|
||||
{ "<a href=>", new[] { kvp("href", "") } },
|
||||
{ "<a href='\"> ", new[] { kvp("href", "\">") } },
|
||||
{ "<a href'", new[] { kvp("href'", "") } },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(GetAttributeNameValuePairsData))]
|
||||
public void GetAttributeNameValuePairs_ParsesPairsCorrectly(
|
||||
string documentContent,
|
||||
IEnumerable<KeyValuePair<string, string>> expectedPairs)
|
||||
{
|
||||
// Arrange
|
||||
var errorSink = new ErrorSink();
|
||||
var parseResult = ParseDocument(documentContent, errorSink);
|
||||
var document = parseResult.Document;
|
||||
var rewriters = RazorParser.GetDefaultRewriters(new HtmlMarkupParser());
|
||||
var rewritingContext = new RewritingContext(document, errorSink);
|
||||
foreach (var rewriter in rewriters)
|
||||
{
|
||||
rewriter.Rewrite(rewritingContext);
|
||||
}
|
||||
var block = rewritingContext.SyntaxTree.Children.First();
|
||||
var parseTreeRewriter = new TagHelperParseTreeRewriter(provider: null);
|
||||
|
||||
// Assert - Guard
|
||||
var tagBlock = Assert.IsType<Block>(block);
|
||||
Assert.Equal(BlockType.Tag, tagBlock.Type);
|
||||
Assert.Empty(errorSink.Errors);
|
||||
|
||||
// Act
|
||||
var pairs = parseTreeRewriter.GetAttributeNameValuePairs(tagBlock);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedPairs, pairs);
|
||||
}
|
||||
|
||||
public static TheoryData PartialRequiredParentData
|
||||
{
|
||||
get
|
||||
|
|
@ -716,7 +779,7 @@ namespace Microsoft.AspNetCore.Razor.Test.TagHelpers
|
|||
TagName = "strong",
|
||||
TypeName = "StrongTagHelper",
|
||||
AssemblyName = "SomeAssembly",
|
||||
RequiredAttributes = new[] { "required" },
|
||||
RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "required" } },
|
||||
AllowedChildren = new[] { "br" }
|
||||
}
|
||||
};
|
||||
|
|
@ -1648,21 +1711,25 @@ namespace Microsoft.AspNetCore.Razor.Test.TagHelpers
|
|||
TagName = "p",
|
||||
TypeName = "pTagHelper",
|
||||
AssemblyName = "SomeAssembly",
|
||||
RequiredAttributes = new[] { "class" }
|
||||
RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "class" } }
|
||||
},
|
||||
new TagHelperDescriptor
|
||||
{
|
||||
TagName = "div",
|
||||
TypeName = "divTagHelper",
|
||||
AssemblyName = "SomeAssembly",
|
||||
RequiredAttributes = new[] { "class", "style" }
|
||||
RequiredAttributes = new[]
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor { Name = "class" },
|
||||
new TagHelperRequiredAttributeDescriptor { Name = "style" }
|
||||
}
|
||||
},
|
||||
new TagHelperDescriptor
|
||||
{
|
||||
TagName = "*",
|
||||
TypeName = "catchAllTagHelper",
|
||||
AssemblyName = "SomeAssembly",
|
||||
RequiredAttributes = new[] { "catchAll" }
|
||||
RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "catchAll" } }
|
||||
}
|
||||
};
|
||||
var descriptorProvider = new TagHelperDescriptorProvider(descriptors);
|
||||
|
|
@ -1911,14 +1978,14 @@ namespace Microsoft.AspNetCore.Razor.Test.TagHelpers
|
|||
TagName = "p",
|
||||
TypeName = "pTagHelper",
|
||||
AssemblyName = "SomeAssembly",
|
||||
RequiredAttributes = new[] { "class" }
|
||||
RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "class" } }
|
||||
},
|
||||
new TagHelperDescriptor
|
||||
{
|
||||
TagName = "*",
|
||||
TypeName = "catchAllTagHelper",
|
||||
AssemblyName = "SomeAssembly",
|
||||
RequiredAttributes = new[] { "catchAll" }
|
||||
RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "catchAll" } }
|
||||
}
|
||||
};
|
||||
var descriptorProvider = new TagHelperDescriptorProvider(descriptors);
|
||||
|
|
@ -2135,7 +2202,7 @@ namespace Microsoft.AspNetCore.Razor.Test.TagHelpers
|
|||
TagName = "p",
|
||||
TypeName = "pTagHelper",
|
||||
AssemblyName = "SomeAssembly",
|
||||
RequiredAttributes = new[] { "class" }
|
||||
RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "class" } }
|
||||
}
|
||||
};
|
||||
var descriptorProvider = new TagHelperDescriptorProvider(descriptors);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,173 @@
|
|||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
||||
{
|
||||
public class TagHelperRequiredAttributeDescriptorTest
|
||||
{
|
||||
public static TheoryData RequiredAttributeDescriptorData
|
||||
{
|
||||
get
|
||||
{
|
||||
// requiredAttributeDescriptor, attributeName, attributeValue, expectedResult
|
||||
return new TheoryData<TagHelperRequiredAttributeDescriptor, string, string, bool>
|
||||
{
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor
|
||||
{
|
||||
Name = "key"
|
||||
},
|
||||
"KeY",
|
||||
"value",
|
||||
true
|
||||
},
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor
|
||||
{
|
||||
Name = "key"
|
||||
},
|
||||
"keys",
|
||||
"value",
|
||||
false
|
||||
},
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor
|
||||
{
|
||||
Name = "route-",
|
||||
NameComparison = TagHelperRequiredAttributeNameComparison.PrefixMatch,
|
||||
},
|
||||
"ROUTE-area",
|
||||
"manage",
|
||||
true
|
||||
},
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor
|
||||
{
|
||||
Name = "route-",
|
||||
NameComparison = TagHelperRequiredAttributeNameComparison.PrefixMatch,
|
||||
},
|
||||
"routearea",
|
||||
"manage",
|
||||
false
|
||||
},
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor
|
||||
{
|
||||
Name = "route-",
|
||||
NameComparison = TagHelperRequiredAttributeNameComparison.PrefixMatch,
|
||||
},
|
||||
"route-",
|
||||
"manage",
|
||||
false
|
||||
},
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor
|
||||
{
|
||||
Name = "key",
|
||||
NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch,
|
||||
},
|
||||
"KeY",
|
||||
"value",
|
||||
true
|
||||
},
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor
|
||||
{
|
||||
Name = "key",
|
||||
NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch,
|
||||
},
|
||||
"keys",
|
||||
"value",
|
||||
false
|
||||
},
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor
|
||||
{
|
||||
Name = "key",
|
||||
NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch,
|
||||
Value = "value",
|
||||
ValueComparison = TagHelperRequiredAttributeValueComparison.FullMatch,
|
||||
},
|
||||
"key",
|
||||
"value",
|
||||
true
|
||||
},
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor
|
||||
{
|
||||
Name = "key",
|
||||
NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch,
|
||||
Value = "value",
|
||||
ValueComparison = TagHelperRequiredAttributeValueComparison.FullMatch,
|
||||
},
|
||||
"key",
|
||||
"Value",
|
||||
false
|
||||
},
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor
|
||||
{
|
||||
Name = "class",
|
||||
NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch,
|
||||
Value = "btn",
|
||||
ValueComparison = TagHelperRequiredAttributeValueComparison.PrefixMatch,
|
||||
},
|
||||
"class",
|
||||
"btn btn-success",
|
||||
true
|
||||
},
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor
|
||||
{
|
||||
Name = "class",
|
||||
NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch,
|
||||
Value = "btn",
|
||||
ValueComparison = TagHelperRequiredAttributeValueComparison.PrefixMatch,
|
||||
},
|
||||
"class",
|
||||
"BTN btn-success",
|
||||
false
|
||||
},
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor
|
||||
{
|
||||
Name = "href",
|
||||
NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch,
|
||||
Value = "#navigate",
|
||||
ValueComparison = TagHelperRequiredAttributeValueComparison.SuffixMatch,
|
||||
},
|
||||
"href",
|
||||
"/home/index#navigate",
|
||||
true
|
||||
},
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor
|
||||
{
|
||||
Name = "href",
|
||||
NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch,
|
||||
Value = "#navigate",
|
||||
ValueComparison = TagHelperRequiredAttributeValueComparison.SuffixMatch,
|
||||
},
|
||||
"href",
|
||||
"/home/index#NAVigate",
|
||||
false
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RequiredAttributeDescriptorData))]
|
||||
public void Matches_ReturnsExpectedResult(
|
||||
TagHelperRequiredAttributeDescriptor requiredAttributeDescriptor,
|
||||
string attributeName,
|
||||
string attributeValue,
|
||||
bool expectedResult)
|
||||
{
|
||||
// Act
|
||||
var result = requiredAttributeDescriptor.IsMatch(attributeName, attributeValue);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedResult, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,272 @@
|
|||
#pragma checksum "CssSelectorTagHelperAttributes.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "7c9072aeb0075e207732cb34646d54529eade9f0"
|
||||
namespace TestOutput
|
||||
{
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public class CssSelectorTagHelperAttributes
|
||||
{
|
||||
#line hidden
|
||||
#pragma warning disable 0414
|
||||
private global::Microsoft.AspNetCore.Razor.TagHelperContent __tagHelperStringValueBuffer = null;
|
||||
#pragma warning restore 0414
|
||||
private global::Microsoft.AspNetCore.Razor.Runtime.TagHelperExecutionContext __tagHelperExecutionContext = null;
|
||||
private global::Microsoft.AspNetCore.Razor.Runtime.TagHelperRunner __tagHelperRunner = null;
|
||||
private global::Microsoft.AspNetCore.Razor.Runtime.TagHelperScopeManager __tagHelperScopeManager = new global::Microsoft.AspNetCore.Razor.Runtime.TagHelperScopeManager();
|
||||
private global::TestNamespace.ATagHelper __TestNamespace_ATagHelper = null;
|
||||
private global::TestNamespace.CatchAllTagHelper __TestNamespace_CatchAllTagHelper = null;
|
||||
private static readonly global::Microsoft.AspNetCore.Razor.TagHelpers.TagHelperAttribute __tagHelperAttribute_0 = new global::Microsoft.AspNetCore.Razor.TagHelpers.TagHelperAttribute("href", new global::Microsoft.AspNetCore.Html.HtmlEncodedString("~/"));
|
||||
private static readonly global::Microsoft.AspNetCore.Razor.TagHelpers.TagHelperAttribute __tagHelperAttribute_1 = new global::Microsoft.AspNetCore.Razor.TagHelpers.TagHelperAttribute("href", new global::Microsoft.AspNetCore.Html.HtmlEncodedString("~/hello"));
|
||||
private global::TestNamespace.ATagHelperMultipleSelectors __TestNamespace_ATagHelperMultipleSelectors = null;
|
||||
private static readonly global::Microsoft.AspNetCore.Razor.TagHelpers.TagHelperAttribute __tagHelperAttribute_2 = new global::Microsoft.AspNetCore.Razor.TagHelpers.TagHelperAttribute("href", new global::Microsoft.AspNetCore.Html.HtmlEncodedString("~/?hello=world"));
|
||||
private static readonly global::Microsoft.AspNetCore.Razor.TagHelpers.TagHelperAttribute __tagHelperAttribute_3 = new global::Microsoft.AspNetCore.Razor.TagHelpers.TagHelperAttribute("href", new global::Microsoft.AspNetCore.Html.HtmlEncodedString("~/?hello=world@false"));
|
||||
private global::TestNamespace.InputTagHelper __TestNamespace_InputTagHelper = null;
|
||||
private global::TestNamespace.InputTagHelper2 __TestNamespace_InputTagHelper2 = null;
|
||||
private global::TestNamespace.CatchAllTagHelper2 __TestNamespace_CatchAllTagHelper2 = null;
|
||||
private static readonly global::Microsoft.AspNetCore.Razor.TagHelpers.TagHelperAttribute __tagHelperAttribute_4 = new global::Microsoft.AspNetCore.Razor.TagHelpers.TagHelperAttribute("value", new global::Microsoft.AspNetCore.Html.HtmlEncodedString("3 TagHelpers"));
|
||||
private static readonly global::Microsoft.AspNetCore.Razor.TagHelpers.TagHelperAttribute __tagHelperAttribute_5 = new global::Microsoft.AspNetCore.Razor.TagHelpers.TagHelperAttribute("value", new global::Microsoft.AspNetCore.Html.HtmlEncodedString("2 TagHelper"));
|
||||
#line hidden
|
||||
public CssSelectorTagHelperAttributes()
|
||||
{
|
||||
}
|
||||
|
||||
#pragma warning disable 1998
|
||||
public override async Task ExecuteAsync()
|
||||
{
|
||||
__tagHelperRunner = __tagHelperRunner ?? new global::Microsoft.AspNetCore.Razor.Runtime.TagHelperRunner();
|
||||
Instrumentation.BeginContext(30, 2, true);
|
||||
WriteLiteral("\r\n");
|
||||
Instrumentation.EndContext();
|
||||
__tagHelperExecutionContext = __tagHelperScopeManager.Begin("a", global::Microsoft.AspNetCore.Razor.TagHelpers.TagMode.StartTagAndEndTag, "test", async() => {
|
||||
Instrumentation.BeginContext(45, 13, true);
|
||||
WriteLiteral("2 TagHelpers.");
|
||||
Instrumentation.EndContext();
|
||||
}
|
||||
, StartTagHelperWritingScope, EndTagHelperWritingScope);
|
||||
__TestNamespace_ATagHelper = CreateTagHelper<global::TestNamespace.ATagHelper>();
|
||||
__tagHelperExecutionContext.Add(__TestNamespace_ATagHelper);
|
||||
__TestNamespace_CatchAllTagHelper = CreateTagHelper<global::TestNamespace.CatchAllTagHelper>();
|
||||
__tagHelperExecutionContext.Add(__TestNamespace_CatchAllTagHelper);
|
||||
__tagHelperExecutionContext.AddHtmlAttribute(__tagHelperAttribute_0);
|
||||
__tagHelperExecutionContext.Output = await __tagHelperRunner.RunAsync(__tagHelperExecutionContext);
|
||||
if (!__tagHelperExecutionContext.Output.IsContentModified)
|
||||
{
|
||||
__tagHelperExecutionContext.Output.Content = await __tagHelperExecutionContext.Output.GetChildContentAsync();
|
||||
}
|
||||
Instrumentation.BeginContext(32, 30, false);
|
||||
Write(__tagHelperExecutionContext.Output);
|
||||
Instrumentation.EndContext();
|
||||
__tagHelperExecutionContext = __tagHelperScopeManager.End();
|
||||
Instrumentation.BeginContext(62, 2, true);
|
||||
WriteLiteral("\r\n");
|
||||
Instrumentation.EndContext();
|
||||
__tagHelperExecutionContext = __tagHelperScopeManager.Begin("a", global::Microsoft.AspNetCore.Razor.TagHelpers.TagMode.StartTagAndEndTag, "test", async() => {
|
||||
Instrumentation.BeginContext(80, 12, true);
|
||||
WriteLiteral("1 TagHelper.");
|
||||
Instrumentation.EndContext();
|
||||
}
|
||||
, StartTagHelperWritingScope, EndTagHelperWritingScope);
|
||||
__TestNamespace_CatchAllTagHelper = CreateTagHelper<global::TestNamespace.CatchAllTagHelper>();
|
||||
__tagHelperExecutionContext.Add(__TestNamespace_CatchAllTagHelper);
|
||||
__tagHelperExecutionContext.AddHtmlAttribute(__tagHelperAttribute_1);
|
||||
__tagHelperExecutionContext.Output = await __tagHelperRunner.RunAsync(__tagHelperExecutionContext);
|
||||
if (!__tagHelperExecutionContext.Output.IsContentModified)
|
||||
{
|
||||
__tagHelperExecutionContext.Output.Content = await __tagHelperExecutionContext.Output.GetChildContentAsync();
|
||||
}
|
||||
Instrumentation.BeginContext(64, 32, false);
|
||||
Write(__tagHelperExecutionContext.Output);
|
||||
Instrumentation.EndContext();
|
||||
__tagHelperExecutionContext = __tagHelperScopeManager.End();
|
||||
Instrumentation.BeginContext(96, 2, true);
|
||||
WriteLiteral("\r\n");
|
||||
Instrumentation.EndContext();
|
||||
__tagHelperExecutionContext = __tagHelperScopeManager.Begin("a", global::Microsoft.AspNetCore.Razor.TagHelpers.TagMode.StartTagAndEndTag, "test", async() => {
|
||||
Instrumentation.BeginContext(123, 12, true);
|
||||
WriteLiteral("2 TagHelpers");
|
||||
Instrumentation.EndContext();
|
||||
}
|
||||
, StartTagHelperWritingScope, EndTagHelperWritingScope);
|
||||
__TestNamespace_ATagHelperMultipleSelectors = CreateTagHelper<global::TestNamespace.ATagHelperMultipleSelectors>();
|
||||
__tagHelperExecutionContext.Add(__TestNamespace_ATagHelperMultipleSelectors);
|
||||
__TestNamespace_CatchAllTagHelper = CreateTagHelper<global::TestNamespace.CatchAllTagHelper>();
|
||||
__tagHelperExecutionContext.Add(__TestNamespace_CatchAllTagHelper);
|
||||
__tagHelperExecutionContext.AddHtmlAttribute(__tagHelperAttribute_2);
|
||||
__tagHelperExecutionContext.Output = await __tagHelperRunner.RunAsync(__tagHelperExecutionContext);
|
||||
if (!__tagHelperExecutionContext.Output.IsContentModified)
|
||||
{
|
||||
__tagHelperExecutionContext.Output.Content = await __tagHelperExecutionContext.Output.GetChildContentAsync();
|
||||
}
|
||||
Instrumentation.BeginContext(98, 41, false);
|
||||
Write(__tagHelperExecutionContext.Output);
|
||||
Instrumentation.EndContext();
|
||||
__tagHelperExecutionContext = __tagHelperScopeManager.End();
|
||||
Instrumentation.BeginContext(139, 2, true);
|
||||
WriteLiteral("\r\n");
|
||||
Instrumentation.EndContext();
|
||||
__tagHelperExecutionContext = __tagHelperScopeManager.Begin("a", global::Microsoft.AspNetCore.Razor.TagHelpers.TagMode.StartTagAndEndTag, "test", async() => {
|
||||
Instrumentation.BeginContext(172, 12, true);
|
||||
WriteLiteral("2 TagHelpers");
|
||||
Instrumentation.EndContext();
|
||||
}
|
||||
, StartTagHelperWritingScope, EndTagHelperWritingScope);
|
||||
__TestNamespace_ATagHelperMultipleSelectors = CreateTagHelper<global::TestNamespace.ATagHelperMultipleSelectors>();
|
||||
__tagHelperExecutionContext.Add(__TestNamespace_ATagHelperMultipleSelectors);
|
||||
__TestNamespace_CatchAllTagHelper = CreateTagHelper<global::TestNamespace.CatchAllTagHelper>();
|
||||
__tagHelperExecutionContext.Add(__TestNamespace_CatchAllTagHelper);
|
||||
BeginAddHtmlAttributeValues(__tagHelperExecutionContext, "href", 3);
|
||||
AddHtmlAttributeValue("", 150, "~/", 150, 2, true);
|
||||
#line 6 "CssSelectorTagHelperAttributes.cshtml"
|
||||
AddHtmlAttributeValue("", 152, false, 152, 6, false);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
AddHtmlAttributeValue("", 158, "?hello=world", 158, 12, true);
|
||||
EndAddHtmlAttributeValues(__tagHelperExecutionContext);
|
||||
__tagHelperExecutionContext.Output = await __tagHelperRunner.RunAsync(__tagHelperExecutionContext);
|
||||
if (!__tagHelperExecutionContext.Output.IsContentModified)
|
||||
{
|
||||
__tagHelperExecutionContext.Output.Content = await __tagHelperExecutionContext.Output.GetChildContentAsync();
|
||||
}
|
||||
Instrumentation.BeginContext(141, 47, false);
|
||||
Write(__tagHelperExecutionContext.Output);
|
||||
Instrumentation.EndContext();
|
||||
__tagHelperExecutionContext = __tagHelperScopeManager.End();
|
||||
Instrumentation.BeginContext(188, 35, true);
|
||||
WriteLiteral("\r\n<a href=\' ~/\'>0 TagHelpers.</a>\r\n");
|
||||
Instrumentation.EndContext();
|
||||
__tagHelperExecutionContext = __tagHelperScopeManager.Begin("a", global::Microsoft.AspNetCore.Razor.TagHelpers.TagMode.StartTagAndEndTag, "test", async() => {
|
||||
Instrumentation.BeginContext(240, 11, true);
|
||||
WriteLiteral("1 TagHelper");
|
||||
Instrumentation.EndContext();
|
||||
}
|
||||
, StartTagHelperWritingScope, EndTagHelperWritingScope);
|
||||
__TestNamespace_CatchAllTagHelper = CreateTagHelper<global::TestNamespace.CatchAllTagHelper>();
|
||||
__tagHelperExecutionContext.Add(__TestNamespace_CatchAllTagHelper);
|
||||
BeginAddHtmlAttributeValues(__tagHelperExecutionContext, "href", 2);
|
||||
AddHtmlAttributeValue("", 231, "~/", 231, 2, true);
|
||||
#line 8 "CssSelectorTagHelperAttributes.cshtml"
|
||||
AddHtmlAttributeValue("", 233, false, 233, 6, false);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
EndAddHtmlAttributeValues(__tagHelperExecutionContext);
|
||||
__tagHelperExecutionContext.Output = await __tagHelperRunner.RunAsync(__tagHelperExecutionContext);
|
||||
if (!__tagHelperExecutionContext.Output.IsContentModified)
|
||||
{
|
||||
__tagHelperExecutionContext.Output.Content = await __tagHelperExecutionContext.Output.GetChildContentAsync();
|
||||
}
|
||||
Instrumentation.BeginContext(223, 32, false);
|
||||
Write(__tagHelperExecutionContext.Output);
|
||||
Instrumentation.EndContext();
|
||||
__tagHelperExecutionContext = __tagHelperScopeManager.End();
|
||||
Instrumentation.BeginContext(255, 2, true);
|
||||
WriteLiteral("\r\n");
|
||||
Instrumentation.EndContext();
|
||||
__tagHelperExecutionContext = __tagHelperScopeManager.Begin("a", global::Microsoft.AspNetCore.Razor.TagHelpers.TagMode.StartTagAndEndTag, "test", async() => {
|
||||
Instrumentation.BeginContext(288, 11, true);
|
||||
WriteLiteral("1 TagHelper");
|
||||
Instrumentation.EndContext();
|
||||
}
|
||||
, StartTagHelperWritingScope, EndTagHelperWritingScope);
|
||||
__TestNamespace_CatchAllTagHelper = CreateTagHelper<global::TestNamespace.CatchAllTagHelper>();
|
||||
__tagHelperExecutionContext.Add(__TestNamespace_CatchAllTagHelper);
|
||||
__tagHelperExecutionContext.AddHtmlAttribute(__tagHelperAttribute_3);
|
||||
__tagHelperExecutionContext.Output = await __tagHelperRunner.RunAsync(__tagHelperExecutionContext);
|
||||
if (!__tagHelperExecutionContext.Output.IsContentModified)
|
||||
{
|
||||
__tagHelperExecutionContext.Output.Content = await __tagHelperExecutionContext.Output.GetChildContentAsync();
|
||||
}
|
||||
Instrumentation.BeginContext(257, 46, false);
|
||||
Write(__tagHelperExecutionContext.Output);
|
||||
Instrumentation.EndContext();
|
||||
__tagHelperExecutionContext = __tagHelperScopeManager.End();
|
||||
Instrumentation.BeginContext(303, 2, true);
|
||||
WriteLiteral("\r\n");
|
||||
Instrumentation.EndContext();
|
||||
__tagHelperExecutionContext = __tagHelperScopeManager.Begin("a", global::Microsoft.AspNetCore.Razor.TagHelpers.TagMode.StartTagAndEndTag, "test", async() => {
|
||||
Instrumentation.BeginContext(337, 11, true);
|
||||
WriteLiteral("1 TagHelper");
|
||||
Instrumentation.EndContext();
|
||||
}
|
||||
, StartTagHelperWritingScope, EndTagHelperWritingScope);
|
||||
__TestNamespace_CatchAllTagHelper = CreateTagHelper<global::TestNamespace.CatchAllTagHelper>();
|
||||
__tagHelperExecutionContext.Add(__TestNamespace_CatchAllTagHelper);
|
||||
BeginAddHtmlAttributeValues(__tagHelperExecutionContext, "href", 2);
|
||||
AddHtmlAttributeValue("", 314, "~/?hello=world", 314, 14, true);
|
||||
#line 10 "CssSelectorTagHelperAttributes.cshtml"
|
||||
AddHtmlAttributeValue(" ", 328, false, 329, 7, false);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
EndAddHtmlAttributeValues(__tagHelperExecutionContext);
|
||||
__tagHelperExecutionContext.Output = await __tagHelperRunner.RunAsync(__tagHelperExecutionContext);
|
||||
if (!__tagHelperExecutionContext.Output.IsContentModified)
|
||||
{
|
||||
__tagHelperExecutionContext.Output.Content = await __tagHelperExecutionContext.Output.GetChildContentAsync();
|
||||
}
|
||||
Instrumentation.BeginContext(305, 47, false);
|
||||
Write(__tagHelperExecutionContext.Output);
|
||||
Instrumentation.EndContext();
|
||||
__tagHelperExecutionContext = __tagHelperScopeManager.End();
|
||||
Instrumentation.BeginContext(352, 2, true);
|
||||
WriteLiteral("\r\n");
|
||||
Instrumentation.EndContext();
|
||||
__tagHelperExecutionContext = __tagHelperScopeManager.Begin("input", global::Microsoft.AspNetCore.Razor.TagHelpers.TagMode.SelfClosing, "test", async() => {
|
||||
}
|
||||
, StartTagHelperWritingScope, EndTagHelperWritingScope);
|
||||
__TestNamespace_InputTagHelper = CreateTagHelper<global::TestNamespace.InputTagHelper>();
|
||||
__tagHelperExecutionContext.Add(__TestNamespace_InputTagHelper);
|
||||
__TestNamespace_InputTagHelper2 = CreateTagHelper<global::TestNamespace.InputTagHelper2>();
|
||||
__tagHelperExecutionContext.Add(__TestNamespace_InputTagHelper2);
|
||||
__TestNamespace_CatchAllTagHelper2 = CreateTagHelper<global::TestNamespace.CatchAllTagHelper2>();
|
||||
__tagHelperExecutionContext.Add(__TestNamespace_CatchAllTagHelper2);
|
||||
__TestNamespace_InputTagHelper.Type = "text";
|
||||
__tagHelperExecutionContext.AddTagHelperAttribute("type", __TestNamespace_InputTagHelper.Type);
|
||||
__TestNamespace_InputTagHelper2.Type = __TestNamespace_InputTagHelper.Type;
|
||||
__tagHelperExecutionContext.AddHtmlAttribute(__tagHelperAttribute_4);
|
||||
__tagHelperExecutionContext.Output = await __tagHelperRunner.RunAsync(__tagHelperExecutionContext);
|
||||
Instrumentation.BeginContext(354, 42, false);
|
||||
Write(__tagHelperExecutionContext.Output);
|
||||
Instrumentation.EndContext();
|
||||
__tagHelperExecutionContext = __tagHelperScopeManager.End();
|
||||
Instrumentation.BeginContext(396, 2, true);
|
||||
WriteLiteral("\r\n");
|
||||
Instrumentation.EndContext();
|
||||
__tagHelperExecutionContext = __tagHelperScopeManager.Begin("input", global::Microsoft.AspNetCore.Razor.TagHelpers.TagMode.SelfClosing, "test", async() => {
|
||||
}
|
||||
, StartTagHelperWritingScope, EndTagHelperWritingScope);
|
||||
__TestNamespace_InputTagHelper2 = CreateTagHelper<global::TestNamespace.InputTagHelper2>();
|
||||
__tagHelperExecutionContext.Add(__TestNamespace_InputTagHelper2);
|
||||
__TestNamespace_CatchAllTagHelper2 = CreateTagHelper<global::TestNamespace.CatchAllTagHelper2>();
|
||||
__tagHelperExecutionContext.Add(__TestNamespace_CatchAllTagHelper2);
|
||||
__TestNamespace_InputTagHelper2.Type = "texty";
|
||||
__tagHelperExecutionContext.AddTagHelperAttribute("type", __TestNamespace_InputTagHelper2.Type);
|
||||
__tagHelperExecutionContext.AddHtmlAttribute(__tagHelperAttribute_4);
|
||||
__tagHelperExecutionContext.Output = await __tagHelperRunner.RunAsync(__tagHelperExecutionContext);
|
||||
Instrumentation.BeginContext(398, 43, false);
|
||||
Write(__tagHelperExecutionContext.Output);
|
||||
Instrumentation.EndContext();
|
||||
__tagHelperExecutionContext = __tagHelperScopeManager.End();
|
||||
Instrumentation.BeginContext(441, 2, true);
|
||||
WriteLiteral("\r\n");
|
||||
Instrumentation.EndContext();
|
||||
__tagHelperExecutionContext = __tagHelperScopeManager.Begin("input", global::Microsoft.AspNetCore.Razor.TagHelpers.TagMode.SelfClosing, "test", async() => {
|
||||
}
|
||||
, StartTagHelperWritingScope, EndTagHelperWritingScope);
|
||||
__TestNamespace_InputTagHelper2 = CreateTagHelper<global::TestNamespace.InputTagHelper2>();
|
||||
__tagHelperExecutionContext.Add(__TestNamespace_InputTagHelper2);
|
||||
__TestNamespace_CatchAllTagHelper2 = CreateTagHelper<global::TestNamespace.CatchAllTagHelper2>();
|
||||
__tagHelperExecutionContext.Add(__TestNamespace_CatchAllTagHelper2);
|
||||
__TestNamespace_InputTagHelper2.Type = "checkbox";
|
||||
__tagHelperExecutionContext.AddTagHelperAttribute("type", __TestNamespace_InputTagHelper2.Type);
|
||||
__tagHelperExecutionContext.AddHtmlAttribute(__tagHelperAttribute_5);
|
||||
__tagHelperExecutionContext.Output = await __tagHelperRunner.RunAsync(__tagHelperExecutionContext);
|
||||
Instrumentation.BeginContext(443, 45, false);
|
||||
Write(__tagHelperExecutionContext.Output);
|
||||
Instrumentation.EndContext();
|
||||
__tagHelperExecutionContext = __tagHelperScopeManager.End();
|
||||
}
|
||||
#pragma warning restore 1998
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
@addTagHelper "*, something"
|
||||
|
||||
<a href="~/">2 TagHelpers.</a>
|
||||
<a href=~/hello>1 TagHelper.</a>
|
||||
<a href="~/?hello=world">2 TagHelpers</a>
|
||||
<a href="~/@false?hello=world">2 TagHelpers</a>
|
||||
<a href=' ~/'>0 TagHelpers.</a>
|
||||
<a href=~/@false>1 TagHelper</a>
|
||||
<a href="~/?hello=world@false">1 TagHelper</a>
|
||||
<a href='~/?hello=world @false'>1 TagHelper</a>
|
||||
<input type="text" value="3 TagHelpers" />
|
||||
<input type='texty' value="3 TagHelpers" />
|
||||
<input type="checkbox" value="2 TagHelper" />
|
||||
Loading…
Reference in New Issue