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);
|
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)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -195,4 +195,19 @@
|
||||||
<data name="ArgumentMustBeAnInstanceOf" xml:space="preserve">
|
<data name="ArgumentMustBeAnInstanceOf" xml:space="preserve">
|
||||||
<value>Argument must be an instance of '{0}'.</value>
|
<value>Argument must be an instance of '{0}'.</value>
|
||||||
</data>
|
</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>
|
</root>
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
@ -21,6 +22,7 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
||||||
private const string DataDashPrefix = "data-";
|
private const string DataDashPrefix = "data-";
|
||||||
private const string TagHelperNameEnding = "TagHelper";
|
private const string TagHelperNameEnding = "TagHelper";
|
||||||
private const string HtmlCaseRegexReplacement = "-$1$2";
|
private const string HtmlCaseRegexReplacement = "-$1$2";
|
||||||
|
private const char RequiredAttributeWildcardSuffix = '*';
|
||||||
|
|
||||||
// This matches the following AFTER the start of the input string (MATCH).
|
// 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)
|
// 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,
|
typeName,
|
||||||
assemblyName,
|
assemblyName,
|
||||||
attributeDescriptors,
|
attributeDescriptors,
|
||||||
requiredAttributes: Enumerable.Empty<string>(),
|
requiredAttributeDescriptors: Enumerable.Empty<TagHelperRequiredAttributeDescriptor>(),
|
||||||
allowedChildren: allowedChildren,
|
allowedChildren: allowedChildren,
|
||||||
tagStructure: default(TagStructure),
|
tagStructure: default(TagStructure),
|
||||||
parentTag: null,
|
parentTag: null,
|
||||||
|
|
@ -235,14 +237,15 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
||||||
IEnumerable<string> allowedChildren,
|
IEnumerable<string> allowedChildren,
|
||||||
TagHelperDesignTimeDescriptor designTimeDescriptor)
|
TagHelperDesignTimeDescriptor designTimeDescriptor)
|
||||||
{
|
{
|
||||||
var requiredAttributes = GetCommaSeparatedValues(targetElementAttribute.Attributes);
|
IEnumerable<TagHelperRequiredAttributeDescriptor> requiredAttributeDescriptors;
|
||||||
|
TryGetRequiredAttributeDescriptors(targetElementAttribute.Attributes, errorSink: null, descriptors: out requiredAttributeDescriptors);
|
||||||
|
|
||||||
return BuildTagHelperDescriptor(
|
return BuildTagHelperDescriptor(
|
||||||
targetElementAttribute.Tag,
|
targetElementAttribute.Tag,
|
||||||
typeName,
|
typeName,
|
||||||
assemblyName,
|
assemblyName,
|
||||||
attributeDescriptors,
|
attributeDescriptors,
|
||||||
requiredAttributes,
|
requiredAttributeDescriptors,
|
||||||
allowedChildren,
|
allowedChildren,
|
||||||
targetElementAttribute.ParentTag,
|
targetElementAttribute.ParentTag,
|
||||||
targetElementAttribute.TagStructure,
|
targetElementAttribute.TagStructure,
|
||||||
|
|
@ -254,7 +257,7 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
||||||
string typeName,
|
string typeName,
|
||||||
string assemblyName,
|
string assemblyName,
|
||||||
IEnumerable<TagHelperAttributeDescriptor> attributeDescriptors,
|
IEnumerable<TagHelperAttributeDescriptor> attributeDescriptors,
|
||||||
IEnumerable<string> requiredAttributes,
|
IEnumerable<TagHelperRequiredAttributeDescriptor> requiredAttributeDescriptors,
|
||||||
IEnumerable<string> allowedChildren,
|
IEnumerable<string> allowedChildren,
|
||||||
string parentTag,
|
string parentTag,
|
||||||
TagStructure tagStructure,
|
TagStructure tagStructure,
|
||||||
|
|
@ -266,7 +269,7 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
||||||
TypeName = typeName,
|
TypeName = typeName,
|
||||||
AssemblyName = assemblyName,
|
AssemblyName = assemblyName,
|
||||||
Attributes = attributeDescriptors,
|
Attributes = attributeDescriptors,
|
||||||
RequiredAttributes = requiredAttributes,
|
RequiredAttributes = requiredAttributeDescriptors,
|
||||||
AllowedChildren = allowedChildren,
|
AllowedChildren = allowedChildren,
|
||||||
RequiredParent = parentTag,
|
RequiredParent = parentTag,
|
||||||
TagStructure = tagStructure,
|
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>
|
/// <summary>
|
||||||
/// Internal for testing.
|
/// Internal for testing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -291,20 +285,11 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
||||||
ErrorSink errorSink)
|
ErrorSink errorSink)
|
||||||
{
|
{
|
||||||
var validTagName = ValidateName(attribute.Tag, targetingAttributes: false, errorSink: errorSink);
|
var validTagName = ValidateName(attribute.Tag, targetingAttributes: false, errorSink: errorSink);
|
||||||
var validAttributeNames = true;
|
IEnumerable<TagHelperRequiredAttributeDescriptor> requiredAttributeDescriptors;
|
||||||
var attributeNames = GetCommaSeparatedValues(attribute.Attributes);
|
var validRequiredAttributes = TryGetRequiredAttributeDescriptors(attribute.Attributes, errorSink, out requiredAttributeDescriptors);
|
||||||
|
|
||||||
foreach (var attributeName in attributeNames)
|
|
||||||
{
|
|
||||||
if (!ValidateName(attributeName, targetingAttributes: true, errorSink: errorSink))
|
|
||||||
{
|
|
||||||
validAttributeNames = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var validParentTagName = ValidateParentTagName(attribute.ParentTag, errorSink);
|
var validParentTagName = ValidateParentTagName(attribute.ParentTag, errorSink);
|
||||||
|
|
||||||
return validTagName && validAttributeNames && validParentTagName;
|
return validTagName && validRequiredAttributes && validParentTagName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -325,10 +310,17 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
||||||
errorSink: errorSink);
|
errorSink: errorSink);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool ValidateName(
|
private static bool TryGetRequiredAttributeDescriptors(
|
||||||
string name,
|
string requiredAttributes,
|
||||||
bool targetingAttributes,
|
ErrorSink errorSink,
|
||||||
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 &&
|
if (!targetingAttributes &&
|
||||||
string.Equals(
|
string.Equals(
|
||||||
|
|
@ -339,15 +331,6 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
||||||
// '*' as the entire name is OK in the HtmlTargetElement catch-all case.
|
// '*' as the entire name is OK in the HtmlTargetElement catch-all case.
|
||||||
return true;
|
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 ?
|
var targetName = targetingAttributes ?
|
||||||
Resources.TagHelperDescriptorFactory_Attribute :
|
Resources.TagHelperDescriptorFactory_Attribute :
|
||||||
|
|
@ -750,5 +733,329 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
||||||
{
|
{
|
||||||
return HtmlCaseRegex.Replace(name, HtmlCaseRegexReplacement).ToLowerInvariant();
|
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; }
|
public string Tag { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A comma-separated <see cref="string"/> of attribute names the HTML element must contain for the
|
/// 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.
|
/// <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>
|
/// </summary>
|
||||||
public string Attributes { get; set; }
|
public string Attributes { get; set; }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,10 @@ namespace Microsoft.AspNetCore.Razor.Test.Internal
|
||||||
// attributes or prefixes. In tests we do.
|
// attributes or prefixes. In tests we do.
|
||||||
Assert.Equal(descriptorX.TagName, descriptorY.TagName, StringComparer.Ordinal);
|
Assert.Equal(descriptorX.TagName, descriptorY.TagName, StringComparer.Ordinal);
|
||||||
Assert.Equal(descriptorX.Prefix, descriptorY.Prefix, 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);
|
Assert.Equal(descriptorX.RequiredParent, descriptorY.RequiredParent, StringComparer.Ordinal);
|
||||||
|
|
||||||
if (descriptorX.AllowedChildren != descriptorY.AllowedChildren)
|
if (descriptorX.AllowedChildren != descriptorY.AllowedChildren)
|
||||||
|
|
@ -66,9 +69,10 @@ namespace Microsoft.AspNetCore.Razor.Test.Internal
|
||||||
TagHelperDesignTimeDescriptorComparer.Default.GetHashCode(descriptor.DesignTimeDescriptor));
|
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)
|
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 string _assemblyName;
|
||||||
private IEnumerable<TagHelperAttributeDescriptor> _attributes =
|
private IEnumerable<TagHelperAttributeDescriptor> _attributes =
|
||||||
Enumerable.Empty<TagHelperAttributeDescriptor>();
|
Enumerable.Empty<TagHelperAttributeDescriptor>();
|
||||||
private IEnumerable<string> _requiredAttributes = Enumerable.Empty<string>();
|
private IEnumerable<TagHelperRequiredAttributeDescriptor> _requiredAttributes = Enumerable.Empty<TagHelperRequiredAttributeDescriptor>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Text used as a required prefix when matching HTML start and end tags in the Razor source to available
|
/// 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>
|
/// <remarks>
|
||||||
/// <c>*</c> at the end of an attribute name acts as a prefix match.
|
/// <c>*</c> at the end of an attribute name acts as a prefix match.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public IEnumerable<string> RequiredAttributes
|
public IEnumerable<TagHelperRequiredAttributeDescriptor> RequiredAttributes
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -51,9 +51,9 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
||||||
descriptorY.RequiredParent,
|
descriptorY.RequiredParent,
|
||||||
StringComparison.OrdinalIgnoreCase) &&
|
StringComparison.OrdinalIgnoreCase) &&
|
||||||
Enumerable.SequenceEqual(
|
Enumerable.SequenceEqual(
|
||||||
descriptorX.RequiredAttributes.OrderBy(attribute => attribute, StringComparer.OrdinalIgnoreCase),
|
descriptorX.RequiredAttributes.OrderBy(attribute => attribute.Name, StringComparer.OrdinalIgnoreCase),
|
||||||
descriptorY.RequiredAttributes.OrderBy(attribute => attribute, StringComparer.OrdinalIgnoreCase),
|
descriptorY.RequiredAttributes.OrderBy(attribute => attribute.Name, StringComparer.OrdinalIgnoreCase),
|
||||||
StringComparer.OrdinalIgnoreCase) &&
|
TagHelperRequiredAttributeDescriptorComparer.Default) &&
|
||||||
(descriptorX.AllowedChildren == descriptorY.AllowedChildren ||
|
(descriptorX.AllowedChildren == descriptorY.AllowedChildren ||
|
||||||
(descriptorX.AllowedChildren != null &&
|
(descriptorX.AllowedChildren != null &&
|
||||||
descriptorY.AllowedChildren != null &&
|
descriptorY.AllowedChildren != null &&
|
||||||
|
|
@ -80,11 +80,11 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
||||||
hashCodeCombiner.Add(descriptor.TagStructure);
|
hashCodeCombiner.Add(descriptor.TagStructure);
|
||||||
|
|
||||||
var attributes = descriptor.RequiredAttributes.OrderBy(
|
var attributes = descriptor.RequiredAttributes.OrderBy(
|
||||||
attribute => attribute,
|
attribute => attribute.Name,
|
||||||
StringComparer.OrdinalIgnoreCase);
|
StringComparer.OrdinalIgnoreCase);
|
||||||
foreach (var attribute in attributes)
|
foreach (var attribute in attributes)
|
||||||
{
|
{
|
||||||
hashCodeCombiner.Add(attribute, StringComparer.OrdinalIgnoreCase);
|
hashCodeCombiner.Add(TagHelperRequiredAttributeDescriptorComparer.Default.GetHashCode(attribute));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (descriptor.AllowedChildren != null)
|
if (descriptor.AllowedChildren != null)
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Microsoft.AspNetCore.Razor.Parser.TagHelpers;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
||||||
{
|
{
|
||||||
|
|
@ -14,8 +15,6 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
||||||
{
|
{
|
||||||
public const string ElementCatchAllTarget = "*";
|
public const string ElementCatchAllTarget = "*";
|
||||||
|
|
||||||
public static readonly string RequiredAttributeWildcardSuffix = "*";
|
|
||||||
|
|
||||||
private IDictionary<string, HashSet<TagHelperDescriptor>> _registrations;
|
private IDictionary<string, HashSet<TagHelperDescriptor>> _registrations;
|
||||||
private string _tagHelperPrefix;
|
private string _tagHelperPrefix;
|
||||||
|
|
||||||
|
|
@ -39,14 +38,14 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="tagName">The name of the HTML tag to match. Providing a '*' tag name
|
/// <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>
|
/// 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>
|
/// <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"/>.
|
/// <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
|
/// Will return an empty <see cref="Enumerable" /> if no <see cref="TagHelperDescriptor"/>s are
|
||||||
/// found.</returns>
|
/// found.</returns>
|
||||||
public IEnumerable<TagHelperDescriptor> GetDescriptors(
|
public IEnumerable<TagHelperDescriptor> GetDescriptors(
|
||||||
string tagName,
|
string tagName,
|
||||||
IEnumerable<string> attributeNames,
|
IEnumerable<KeyValuePair<string, string>> attributes,
|
||||||
string parentTagName)
|
string parentTagName)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(_tagHelperPrefix) &&
|
if (!string.IsNullOrEmpty(_tagHelperPrefix) &&
|
||||||
|
|
@ -78,10 +77,10 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
||||||
descriptors = matchingDescriptors.Concat(descriptors);
|
descriptors = matchingDescriptors.Concat(descriptors);
|
||||||
}
|
}
|
||||||
|
|
||||||
var applicableDescriptors = ApplyRequiredAttributes(descriptors, attributeNames);
|
var applicableDescriptors = ApplyRequiredAttributes(descriptors, attributes);
|
||||||
applicableDescriptors = ApplyParentTagFilter(applicableDescriptors, parentTagName);
|
applicableDescriptors = ApplyParentTagFilter(applicableDescriptors, parentTagName);
|
||||||
|
|
||||||
return applicableDescriptors;
|
return applicableDescriptors.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<TagHelperDescriptor> ApplyParentTagFilter(
|
private IEnumerable<TagHelperDescriptor> ApplyParentTagFilter(
|
||||||
|
|
@ -95,37 +94,12 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
||||||
|
|
||||||
private IEnumerable<TagHelperDescriptor> ApplyRequiredAttributes(
|
private IEnumerable<TagHelperDescriptor> ApplyRequiredAttributes(
|
||||||
IEnumerable<TagHelperDescriptor> descriptors,
|
IEnumerable<TagHelperDescriptor> descriptors,
|
||||||
IEnumerable<string> attributeNames)
|
IEnumerable<KeyValuePair<string, string>> attributes)
|
||||||
{
|
{
|
||||||
return descriptors.Where(
|
return descriptors.Where(
|
||||||
descriptor =>
|
descriptor => descriptor.RequiredAttributes.All(
|
||||||
{
|
requiredAttribute => attributes.Any(
|
||||||
foreach (var requiredAttribute in descriptor.RequiredAttributes)
|
attribute => requiredAttribute.IsMatch(attribute.Key, attribute.Value))));
|
||||||
{
|
|
||||||
// '*' 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;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Register(TagHelperDescriptor descriptor)
|
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);
|
return addOrRemoveTagHelperSpanVisitor.GetDescriptors(documentRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<ISyntaxTreeRewriter> GetDefaultRewriters(ParserBase markupParser)
|
// Internal for testing
|
||||||
|
internal static IEnumerable<ISyntaxTreeRewriter> GetDefaultRewriters(ParserBase markupParser)
|
||||||
{
|
{
|
||||||
return new ISyntaxTreeRewriter[]
|
return new ISyntaxTreeRewriter[]
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using Microsoft.AspNetCore.Razor.Compilation.TagHelpers;
|
using Microsoft.AspNetCore.Razor.Compilation.TagHelpers;
|
||||||
using Microsoft.AspNetCore.Razor.Parser.SyntaxTree;
|
using Microsoft.AspNetCore.Razor.Parser.SyntaxTree;
|
||||||
using Microsoft.AspNetCore.Razor.TagHelpers;
|
using Microsoft.AspNetCore.Razor.TagHelpers;
|
||||||
|
|
@ -14,6 +15,10 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
||||||
{
|
{
|
||||||
public class TagHelperParseTreeRewriter : ISyntaxTreeRewriter
|
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
|
// From http://dev.w3.org/html5/spec/Overview.html#elements-0
|
||||||
private static readonly HashSet<string> VoidElements = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
private static readonly HashSet<string> VoidElements = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||||
{
|
{
|
||||||
|
|
@ -35,10 +40,12 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
||||||
"wbr"
|
"wbr"
|
||||||
};
|
};
|
||||||
|
|
||||||
private TagHelperDescriptorProvider _provider;
|
private readonly List<KeyValuePair<string, string>> _htmlAttributeTracker;
|
||||||
private Stack<TagBlockTracker> _trackerStack;
|
private readonly StringBuilder _attributeValueBuilder;
|
||||||
|
private readonly TagHelperDescriptorProvider _provider;
|
||||||
|
private readonly Stack<TagBlockTracker> _trackerStack;
|
||||||
|
private readonly Stack<BlockBuilder> _blockStack;
|
||||||
private TagHelperBlockTracker _currentTagHelperTracker;
|
private TagHelperBlockTracker _currentTagHelperTracker;
|
||||||
private Stack<BlockBuilder> _blockStack;
|
|
||||||
private BlockBuilder _currentBlock;
|
private BlockBuilder _currentBlock;
|
||||||
private string _currentParentTagName;
|
private string _currentParentTagName;
|
||||||
|
|
||||||
|
|
@ -47,6 +54,8 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
||||||
_provider = provider;
|
_provider = provider;
|
||||||
_trackerStack = new Stack<TagBlockTracker>();
|
_trackerStack = new Stack<TagBlockTracker>();
|
||||||
_blockStack = new Stack<BlockBuilder>();
|
_blockStack = new Stack<BlockBuilder>();
|
||||||
|
_attributeValueBuilder = new StringBuilder();
|
||||||
|
_htmlAttributeTracker = new List<KeyValuePair<string, string>>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Rewrite(RewritingContext context)
|
public void Rewrite(RewritingContext context)
|
||||||
|
|
@ -177,7 +186,7 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
||||||
if (!IsEndTag(tagBlock))
|
if (!IsEndTag(tagBlock))
|
||||||
{
|
{
|
||||||
// We're now in a start tag block, we first need to see if the tag block is a tag helper.
|
// 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);
|
descriptors = _provider.GetDescriptors(tagName, providedAttributes, _currentParentTagName);
|
||||||
|
|
||||||
|
|
@ -246,7 +255,7 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
||||||
{
|
{
|
||||||
descriptors = _provider.GetDescriptors(
|
descriptors = _provider.GetDescriptors(
|
||||||
tagName,
|
tagName,
|
||||||
attributeNames: Enumerable.Empty<string>(),
|
attributes: Enumerable.Empty<KeyValuePair<string, string>>(),
|
||||||
parentTagName: _currentParentTagName);
|
parentTagName: _currentParentTagName);
|
||||||
|
|
||||||
// If there are not TagHelperDescriptors associated with the end tag block that also have no
|
// 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;
|
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.
|
// Need to calculate how many children we should take that represent the attributes.
|
||||||
var childrenOffset = IsPartialTag(tagBlock) ? 0 : 1;
|
var childrenOffset = IsPartialTag(tagBlock) ? 0 : 1;
|
||||||
|
|
@ -307,32 +317,112 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
||||||
|
|
||||||
if (childCount <= 1)
|
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++)
|
for (var i = 1; i < childCount; i++)
|
||||||
{
|
{
|
||||||
attributeChildren.Add(tagBlock.Children[i]);
|
var child = tagBlock.Children[i];
|
||||||
}
|
|
||||||
var attributeNames = new List<string>();
|
|
||||||
|
|
||||||
foreach (var child in attributeChildren)
|
|
||||||
{
|
|
||||||
Span childSpan;
|
Span childSpan;
|
||||||
|
|
||||||
if (child.IsBlock)
|
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)
|
if (childSpan == null)
|
||||||
{
|
{
|
||||||
|
_attributeValueBuilder.Append(InvalidAttributeValueMarker);
|
||||||
continue;
|
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
|
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;
|
var start = 0;
|
||||||
|
|
@ -344,8 +434,8 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var end = 0;
|
var end = start;
|
||||||
for (end = start; end < childSpan.Content.Length; end++)
|
for (; end < childSpan.Content.Length; end++)
|
||||||
{
|
{
|
||||||
if (childSpan.Content[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()
|
private bool HasAllowedChildren()
|
||||||
|
|
@ -650,7 +745,7 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
||||||
{
|
{
|
||||||
var child = tagBlock.Children[0];
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,155 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
||||||
|
|
||||||
protected static readonly string AssemblyName = TagHelperDescriptorFactoryTestAssembly.Name;
|
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
|
public static TheoryData IsEnumData
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
@ -617,7 +766,10 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
||||||
typeof(AttributeTargetingTagHelper).FullName,
|
typeof(AttributeTargetingTagHelper).FullName,
|
||||||
AssemblyName,
|
AssemblyName,
|
||||||
attributes,
|
attributes,
|
||||||
requiredAttributes: new[] { "class" })
|
requiredAttributes: new[]
|
||||||
|
{
|
||||||
|
new TagHelperRequiredAttributeDescriptor { Name = "class" }
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -629,7 +781,11 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
||||||
typeof(MultiAttributeTargetingTagHelper).FullName,
|
typeof(MultiAttributeTargetingTagHelper).FullName,
|
||||||
AssemblyName,
|
AssemblyName,
|
||||||
attributes,
|
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,
|
typeof(MultiAttributeAttributeTargetingTagHelper).FullName,
|
||||||
AssemblyName,
|
AssemblyName,
|
||||||
attributes,
|
attributes,
|
||||||
requiredAttributes: new[] { "custom" }),
|
requiredAttributes: new[]
|
||||||
|
{
|
||||||
|
new TagHelperRequiredAttributeDescriptor { Name = "custom" }
|
||||||
|
}),
|
||||||
CreateTagHelperDescriptor(
|
CreateTagHelperDescriptor(
|
||||||
TagHelperDescriptorProvider.ElementCatchAllTarget,
|
TagHelperDescriptorProvider.ElementCatchAllTarget,
|
||||||
typeof(MultiAttributeAttributeTargetingTagHelper).FullName,
|
typeof(MultiAttributeAttributeTargetingTagHelper).FullName,
|
||||||
AssemblyName,
|
AssemblyName,
|
||||||
attributes,
|
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,
|
typeof(InheritedAttributeTargetingTagHelper).FullName,
|
||||||
AssemblyName,
|
AssemblyName,
|
||||||
attributes,
|
attributes,
|
||||||
requiredAttributes: new[] { "style" })
|
requiredAttributes: new[]
|
||||||
|
{
|
||||||
|
new TagHelperRequiredAttributeDescriptor { Name = "style" }
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -671,7 +837,10 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
||||||
typeof(RequiredAttributeTagHelper).FullName,
|
typeof(RequiredAttributeTagHelper).FullName,
|
||||||
AssemblyName,
|
AssemblyName,
|
||||||
attributes,
|
attributes,
|
||||||
requiredAttributes: new[] { "class" })
|
requiredAttributes: new[]
|
||||||
|
{
|
||||||
|
new TagHelperRequiredAttributeDescriptor { Name = "class" }
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -683,7 +852,10 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
||||||
typeof(InheritedRequiredAttributeTagHelper).FullName,
|
typeof(InheritedRequiredAttributeTagHelper).FullName,
|
||||||
AssemblyName,
|
AssemblyName,
|
||||||
attributes,
|
attributes,
|
||||||
requiredAttributes: new[] { "class" })
|
requiredAttributes: new[]
|
||||||
|
{
|
||||||
|
new TagHelperRequiredAttributeDescriptor { Name = "class" }
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -695,13 +867,19 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
||||||
typeof(MultiAttributeRequiredAttributeTagHelper).FullName,
|
typeof(MultiAttributeRequiredAttributeTagHelper).FullName,
|
||||||
AssemblyName,
|
AssemblyName,
|
||||||
attributes,
|
attributes,
|
||||||
requiredAttributes: new[] { "class" }),
|
requiredAttributes: new[]
|
||||||
|
{
|
||||||
|
new TagHelperRequiredAttributeDescriptor { Name = "class" }
|
||||||
|
}),
|
||||||
CreateTagHelperDescriptor(
|
CreateTagHelperDescriptor(
|
||||||
"input",
|
"input",
|
||||||
typeof(MultiAttributeRequiredAttributeTagHelper).FullName,
|
typeof(MultiAttributeRequiredAttributeTagHelper).FullName,
|
||||||
AssemblyName,
|
AssemblyName,
|
||||||
attributes,
|
attributes,
|
||||||
requiredAttributes: new[] { "class" })
|
requiredAttributes: new[]
|
||||||
|
{
|
||||||
|
new TagHelperRequiredAttributeDescriptor { Name = "class" }
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -713,13 +891,19 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
||||||
typeof(MultiAttributeSameTagRequiredAttributeTagHelper).FullName,
|
typeof(MultiAttributeSameTagRequiredAttributeTagHelper).FullName,
|
||||||
AssemblyName,
|
AssemblyName,
|
||||||
attributes,
|
attributes,
|
||||||
requiredAttributes: new[] { "style" }),
|
requiredAttributes: new[]
|
||||||
|
{
|
||||||
|
new TagHelperRequiredAttributeDescriptor { Name = "style" }
|
||||||
|
}),
|
||||||
CreateTagHelperDescriptor(
|
CreateTagHelperDescriptor(
|
||||||
"input",
|
"input",
|
||||||
typeof(MultiAttributeSameTagRequiredAttributeTagHelper).FullName,
|
typeof(MultiAttributeSameTagRequiredAttributeTagHelper).FullName,
|
||||||
AssemblyName,
|
AssemblyName,
|
||||||
attributes,
|
attributes,
|
||||||
requiredAttributes: new[] { "class" })
|
requiredAttributes: new[]
|
||||||
|
{
|
||||||
|
new TagHelperRequiredAttributeDescriptor { Name = "class" }
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -731,7 +915,11 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
||||||
typeof(MultiRequiredAttributeTagHelper).FullName,
|
typeof(MultiRequiredAttributeTagHelper).FullName,
|
||||||
AssemblyName,
|
AssemblyName,
|
||||||
attributes,
|
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,
|
typeof(MultiTagMultiRequiredAttributeTagHelper).FullName,
|
||||||
AssemblyName,
|
AssemblyName,
|
||||||
attributes,
|
attributes,
|
||||||
requiredAttributes: new[] { "class", "style" }),
|
requiredAttributes: new[]
|
||||||
|
{
|
||||||
|
new TagHelperRequiredAttributeDescriptor { Name = "class" },
|
||||||
|
new TagHelperRequiredAttributeDescriptor { Name = "style" }
|
||||||
|
}),
|
||||||
CreateTagHelperDescriptor(
|
CreateTagHelperDescriptor(
|
||||||
"input",
|
"input",
|
||||||
typeof(MultiTagMultiRequiredAttributeTagHelper).FullName,
|
typeof(MultiTagMultiRequiredAttributeTagHelper).FullName,
|
||||||
AssemblyName,
|
AssemblyName,
|
||||||
attributes,
|
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,
|
typeof(AttributeWildcardTargetingTagHelper).FullName,
|
||||||
AssemblyName,
|
AssemblyName,
|
||||||
attributes,
|
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,
|
typeof(MultiAttributeWildcardTargetingTagHelper).FullName,
|
||||||
AssemblyName,
|
AssemblyName,
|
||||||
attributes,
|
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
|
public static TheoryData InvalidTagHelperAttributeDescriptorData
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
@ -2293,7 +2484,7 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
||||||
string typeName,
|
string typeName,
|
||||||
string assemblyName,
|
string assemblyName,
|
||||||
IEnumerable<TagHelperAttributeDescriptor> attributes = null,
|
IEnumerable<TagHelperAttributeDescriptor> attributes = null,
|
||||||
IEnumerable<string> requiredAttributes = null)
|
IEnumerable<TagHelperRequiredAttributeDescriptor> requiredAttributes = null)
|
||||||
{
|
{
|
||||||
return new TagHelperDescriptor
|
return new TagHelperDescriptor
|
||||||
{
|
{
|
||||||
|
|
@ -2301,7 +2492,7 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
|
||||||
TypeName = typeName,
|
TypeName = typeName,
|
||||||
AssemblyName = assemblyName,
|
AssemblyName = assemblyName,
|
||||||
Attributes = attributes ?? Enumerable.Empty<TagHelperAttributeDescriptor>(),
|
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; }
|
private static IEnumerable<TagHelperDescriptor> PrefixedPAndInputTagHelperDescriptors { get; }
|
||||||
= BuildPAndInputTagHelperDescriptors(prefix: "THS");
|
= 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
|
private static IEnumerable<TagHelperDescriptor> EnumTagHelperDescriptors
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
@ -113,7 +233,7 @@ namespace Microsoft.AspNetCore.Razor.Test.Generator
|
||||||
TypeName = typeof(string).FullName
|
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
|
IsStringProperty = true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
RequiredAttributes = new[] { "catchall-unbound-required" },
|
RequiredAttributes = new[]
|
||||||
|
{
|
||||||
|
new TagHelperRequiredAttributeDescriptor { Name = "catchall-unbound-required" }
|
||||||
|
},
|
||||||
},
|
},
|
||||||
new TagHelperDescriptor
|
new TagHelperDescriptor
|
||||||
{
|
{
|
||||||
|
|
@ -164,7 +287,11 @@ namespace Microsoft.AspNetCore.Razor.Test.Generator
|
||||||
IsStringProperty = true
|
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("type", inputTypePropertyInfo),
|
||||||
new TagHelperAttributeDescriptor("checked", inputCheckedPropertyInfo)
|
new TagHelperAttributeDescriptor("checked", inputCheckedPropertyInfo)
|
||||||
},
|
},
|
||||||
RequiredAttributes = new[] { "type" },
|
RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "type" } },
|
||||||
},
|
},
|
||||||
new TagHelperDescriptor
|
new TagHelperDescriptor
|
||||||
{
|
{
|
||||||
|
|
@ -226,7 +353,7 @@ namespace Microsoft.AspNetCore.Razor.Test.Generator
|
||||||
new TagHelperAttributeDescriptor("type", inputTypePropertyInfo),
|
new TagHelperAttributeDescriptor("type", inputTypePropertyInfo),
|
||||||
new TagHelperAttributeDescriptor("checked", inputCheckedPropertyInfo)
|
new TagHelperAttributeDescriptor("checked", inputCheckedPropertyInfo)
|
||||||
},
|
},
|
||||||
RequiredAttributes = new[] { "checked" },
|
RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "checked" } },
|
||||||
},
|
},
|
||||||
new TagHelperDescriptor
|
new TagHelperDescriptor
|
||||||
{
|
{
|
||||||
|
|
@ -238,7 +365,7 @@ namespace Microsoft.AspNetCore.Razor.Test.Generator
|
||||||
new TagHelperAttributeDescriptor("type", inputTypePropertyInfo),
|
new TagHelperAttributeDescriptor("type", inputTypePropertyInfo),
|
||||||
new TagHelperAttributeDescriptor("checked", inputCheckedPropertyInfo)
|
new TagHelperAttributeDescriptor("checked", inputCheckedPropertyInfo)
|
||||||
},
|
},
|
||||||
RequiredAttributes = new[] { "type" },
|
RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "type" } },
|
||||||
},
|
},
|
||||||
new TagHelperDescriptor
|
new TagHelperDescriptor
|
||||||
{
|
{
|
||||||
|
|
@ -250,7 +377,7 @@ namespace Microsoft.AspNetCore.Razor.Test.Generator
|
||||||
new TagHelperAttributeDescriptor("type", inputTypePropertyInfo),
|
new TagHelperAttributeDescriptor("type", inputTypePropertyInfo),
|
||||||
new TagHelperAttributeDescriptor("checked", inputCheckedPropertyInfo)
|
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",
|
TagName = "p",
|
||||||
TypeName = "TestNamespace.PTagHelper",
|
TypeName = "TestNamespace.PTagHelper",
|
||||||
AssemblyName = "SomeAssembly",
|
AssemblyName = "SomeAssembly",
|
||||||
RequiredAttributes = new[] { "class" },
|
RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "class" } },
|
||||||
},
|
},
|
||||||
new TagHelperDescriptor
|
new TagHelperDescriptor
|
||||||
{
|
{
|
||||||
|
|
@ -280,7 +407,7 @@ namespace Microsoft.AspNetCore.Razor.Test.Generator
|
||||||
{
|
{
|
||||||
new TagHelperAttributeDescriptor("type", inputTypePropertyInfo)
|
new TagHelperAttributeDescriptor("type", inputTypePropertyInfo)
|
||||||
},
|
},
|
||||||
RequiredAttributes = new[] { "type" },
|
RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "type" } },
|
||||||
},
|
},
|
||||||
new TagHelperDescriptor
|
new TagHelperDescriptor
|
||||||
{
|
{
|
||||||
|
|
@ -292,14 +419,18 @@ namespace Microsoft.AspNetCore.Razor.Test.Generator
|
||||||
new TagHelperAttributeDescriptor("type", inputTypePropertyInfo),
|
new TagHelperAttributeDescriptor("type", inputTypePropertyInfo),
|
||||||
new TagHelperAttributeDescriptor("checked", inputCheckedPropertyInfo)
|
new TagHelperAttributeDescriptor("checked", inputCheckedPropertyInfo)
|
||||||
},
|
},
|
||||||
RequiredAttributes = new[] { "type", "checked" },
|
RequiredAttributes = new[]
|
||||||
|
{
|
||||||
|
new TagHelperRequiredAttributeDescriptor { Name = "type" },
|
||||||
|
new TagHelperRequiredAttributeDescriptor { Name = "checked" }
|
||||||
|
},
|
||||||
},
|
},
|
||||||
new TagHelperDescriptor
|
new TagHelperDescriptor
|
||||||
{
|
{
|
||||||
TagName = "*",
|
TagName = "*",
|
||||||
TypeName = "TestNamespace.CatchAllTagHelper",
|
TypeName = "TestNamespace.CatchAllTagHelper",
|
||||||
AssemblyName = "SomeAssembly",
|
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.
|
// Note: The baseline resource name is equivalent to the test resource name.
|
||||||
return new TheoryData<string, string, IEnumerable<TagHelperDescriptor>>
|
return new TheoryData<string, string, IEnumerable<TagHelperDescriptor>>
|
||||||
{
|
{
|
||||||
|
{ "CssSelectorTagHelperAttributes", null, CssSelectorTagHelperDescriptors },
|
||||||
{ "IncompleteTagHelper", null, DefaultPAndInputTagHelperDescriptors },
|
{ "IncompleteTagHelper", null, DefaultPAndInputTagHelperDescriptors },
|
||||||
{ "SingleTagHelper", null, DefaultPAndInputTagHelperDescriptors },
|
{ "SingleTagHelper", null, DefaultPAndInputTagHelperDescriptors },
|
||||||
{ "SingleTagHelperWithNewlineBeforeAttributes", null, DefaultPAndInputTagHelperDescriptors },
|
{ "SingleTagHelperWithNewlineBeforeAttributes", null, DefaultPAndInputTagHelperDescriptors },
|
||||||
|
|
|
||||||
|
|
@ -157,7 +157,7 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
||||||
TypeName = typeof(string).FullName
|
TypeName = typeof(string).FullName
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
RequiredAttributes = new[] { "bound" },
|
RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "bound" } },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
var descriptorProvider = new TagHelperDescriptorProvider(descriptors);
|
var descriptorProvider = new TagHelperDescriptorProvider(descriptors);
|
||||||
|
|
@ -3940,7 +3940,10 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
||||||
IsStringProperty = true
|
IsStringProperty = true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
RequiredAttributes = new[] { "unbound-required" }
|
RequiredAttributes = new[]
|
||||||
|
{
|
||||||
|
new TagHelperRequiredAttributeDescriptor { Name = "unbound-required" }
|
||||||
|
}
|
||||||
},
|
},
|
||||||
new TagHelperDescriptor
|
new TagHelperDescriptor
|
||||||
{
|
{
|
||||||
|
|
@ -3957,7 +3960,10 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
||||||
IsStringProperty = true
|
IsStringProperty = true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
RequiredAttributes = new[] { "bound-required-string" }
|
RequiredAttributes = new[]
|
||||||
|
{
|
||||||
|
new TagHelperRequiredAttributeDescriptor { Name = "bound-required-string" }
|
||||||
|
}
|
||||||
},
|
},
|
||||||
new TagHelperDescriptor
|
new TagHelperDescriptor
|
||||||
{
|
{
|
||||||
|
|
@ -3973,7 +3979,10 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
||||||
TypeName = typeof(int).FullName
|
TypeName = typeof(int).FullName
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
RequiredAttributes = new[] { "bound-required-int" }
|
RequiredAttributes = new[]
|
||||||
|
{
|
||||||
|
new TagHelperRequiredAttributeDescriptor { Name = "bound-required-int" }
|
||||||
|
}
|
||||||
},
|
},
|
||||||
new TagHelperDescriptor
|
new TagHelperDescriptor
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// 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.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.AspNetCore.Razor.TagHelpers;
|
|
||||||
using Microsoft.AspNetCore.Razor.Test.Internal;
|
using Microsoft.AspNetCore.Razor.Test.Internal;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
@ -73,7 +73,7 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(RequiredParentData))]
|
[MemberData(nameof(RequiredParentData))]
|
||||||
public void GetDescriptors_ReturnsDescriptorsWithRequiredAttributes(
|
public void GetDescriptors_ReturnsDescriptorsParentTags(
|
||||||
string tagName,
|
string tagName,
|
||||||
string parentTagName,
|
string parentTagName,
|
||||||
IEnumerable<TagHelperDescriptor> availableDescriptors,
|
IEnumerable<TagHelperDescriptor> availableDescriptors,
|
||||||
|
|
@ -85,7 +85,7 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
||||||
// Act
|
// Act
|
||||||
var resolvedDescriptors = provider.GetDescriptors(
|
var resolvedDescriptors = provider.GetDescriptors(
|
||||||
tagName,
|
tagName,
|
||||||
attributeNames: Enumerable.Empty<string>(),
|
attributes: Enumerable.Empty<KeyValuePair<string, string>>(),
|
||||||
parentTagName: parentTagName);
|
parentTagName: parentTagName);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -101,131 +101,155 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
||||||
TagName = "div",
|
TagName = "div",
|
||||||
TypeName = "DivTagHelper",
|
TypeName = "DivTagHelper",
|
||||||
AssemblyName = "SomeAssembly",
|
AssemblyName = "SomeAssembly",
|
||||||
RequiredAttributes = new[] { "style" }
|
RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "style" } }
|
||||||
};
|
};
|
||||||
var inputDescriptor = new TagHelperDescriptor
|
var inputDescriptor = new TagHelperDescriptor
|
||||||
{
|
{
|
||||||
TagName = "input",
|
TagName = "input",
|
||||||
TypeName = "InputTagHelper",
|
TypeName = "InputTagHelper",
|
||||||
AssemblyName = "SomeAssembly",
|
AssemblyName = "SomeAssembly",
|
||||||
RequiredAttributes = new[] { "class", "style" }
|
RequiredAttributes = new[]
|
||||||
|
{
|
||||||
|
new TagHelperRequiredAttributeDescriptor { Name = "class" },
|
||||||
|
new TagHelperRequiredAttributeDescriptor { Name = "style" }
|
||||||
|
}
|
||||||
};
|
};
|
||||||
var inputWildcardPrefixDescriptor = new TagHelperDescriptor
|
var inputWildcardPrefixDescriptor = new TagHelperDescriptor
|
||||||
{
|
{
|
||||||
TagName = "input",
|
TagName = "input",
|
||||||
TypeName = "InputWildCardAttribute",
|
TypeName = "InputWildCardAttribute",
|
||||||
AssemblyName = "SomeAssembly",
|
AssemblyName = "SomeAssembly",
|
||||||
RequiredAttributes = new[] { "nodashprefix*" }
|
RequiredAttributes = new[]
|
||||||
|
{
|
||||||
|
new TagHelperRequiredAttributeDescriptor
|
||||||
|
{
|
||||||
|
Name = "nodashprefix",
|
||||||
|
NameComparison = TagHelperRequiredAttributeNameComparison.PrefixMatch,
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
var catchAllDescriptor = new TagHelperDescriptor
|
var catchAllDescriptor = new TagHelperDescriptor
|
||||||
{
|
{
|
||||||
TagName = TagHelperDescriptorProvider.ElementCatchAllTarget,
|
TagName = TagHelperDescriptorProvider.ElementCatchAllTarget,
|
||||||
TypeName = "CatchAllTagHelper",
|
TypeName = "CatchAllTagHelper",
|
||||||
AssemblyName = "SomeAssembly",
|
AssemblyName = "SomeAssembly",
|
||||||
RequiredAttributes = new[] { "class" }
|
RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "class" } }
|
||||||
};
|
};
|
||||||
var catchAllDescriptor2 = new TagHelperDescriptor
|
var catchAllDescriptor2 = new TagHelperDescriptor
|
||||||
{
|
{
|
||||||
TagName = TagHelperDescriptorProvider.ElementCatchAllTarget,
|
TagName = TagHelperDescriptorProvider.ElementCatchAllTarget,
|
||||||
TypeName = "CatchAllTagHelper",
|
TypeName = "CatchAllTagHelper",
|
||||||
AssemblyName = "SomeAssembly",
|
AssemblyName = "SomeAssembly",
|
||||||
RequiredAttributes = new[] { "custom", "class" }
|
RequiredAttributes = new[]
|
||||||
|
{
|
||||||
|
new TagHelperRequiredAttributeDescriptor { Name = "custom" },
|
||||||
|
new TagHelperRequiredAttributeDescriptor { Name = "class" }
|
||||||
|
}
|
||||||
};
|
};
|
||||||
var catchAllWildcardPrefixDescriptor = new TagHelperDescriptor
|
var catchAllWildcardPrefixDescriptor = new TagHelperDescriptor
|
||||||
{
|
{
|
||||||
TagName = TagHelperDescriptorProvider.ElementCatchAllTarget,
|
TagName = TagHelperDescriptorProvider.ElementCatchAllTarget,
|
||||||
TypeName = "CatchAllWildCardAttribute",
|
TypeName = "CatchAllWildCardAttribute",
|
||||||
AssemblyName = "SomeAssembly",
|
AssemblyName = "SomeAssembly",
|
||||||
RequiredAttributes = new[] { "prefix-*" }
|
RequiredAttributes = new[]
|
||||||
|
{
|
||||||
|
new TagHelperRequiredAttributeDescriptor
|
||||||
|
{
|
||||||
|
Name = "prefix-",
|
||||||
|
NameComparison = TagHelperRequiredAttributeNameComparison.PrefixMatch,
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
var defaultAvailableDescriptors =
|
var defaultAvailableDescriptors =
|
||||||
new[] { divDescriptor, inputDescriptor, catchAllDescriptor, catchAllDescriptor2 };
|
new[] { divDescriptor, inputDescriptor, catchAllDescriptor, catchAllDescriptor2 };
|
||||||
var defaultWildcardDescriptors =
|
var defaultWildcardDescriptors =
|
||||||
new[] { inputWildcardPrefixDescriptor, catchAllWildcardPrefixDescriptor };
|
new[] { inputWildcardPrefixDescriptor, catchAllWildcardPrefixDescriptor };
|
||||||
|
Func<string, KeyValuePair<string, string>> kvp =
|
||||||
|
(name) => new KeyValuePair<string, string>(name, "test value");
|
||||||
|
|
||||||
return new TheoryData<
|
return new TheoryData<
|
||||||
string, // tagName
|
string, // tagName
|
||||||
IEnumerable<string>, // providedAttributes
|
IEnumerable<KeyValuePair<string, string>>, // providedAttributes
|
||||||
IEnumerable<TagHelperDescriptor>, // availableDescriptors
|
IEnumerable<TagHelperDescriptor>, // availableDescriptors
|
||||||
IEnumerable<TagHelperDescriptor>> // expectedDescriptors
|
IEnumerable<TagHelperDescriptor>> // expectedDescriptors
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
"div",
|
"div",
|
||||||
new[] { "custom" },
|
new[] { kvp("custom") },
|
||||||
defaultAvailableDescriptors,
|
defaultAvailableDescriptors,
|
||||||
Enumerable.Empty<TagHelperDescriptor>()
|
Enumerable.Empty<TagHelperDescriptor>()
|
||||||
},
|
},
|
||||||
{ "div", new[] { "style" }, defaultAvailableDescriptors, new[] { divDescriptor } },
|
{ "div", new[] { kvp("style") }, defaultAvailableDescriptors, new[] { divDescriptor } },
|
||||||
{ "div", new[] { "class" }, defaultAvailableDescriptors, new[] { catchAllDescriptor } },
|
{ "div", new[] { kvp("class") }, defaultAvailableDescriptors, new[] { catchAllDescriptor } },
|
||||||
{
|
{
|
||||||
"div",
|
"div",
|
||||||
new[] { "class", "style" },
|
new[] { kvp("class"), kvp("style") },
|
||||||
defaultAvailableDescriptors,
|
defaultAvailableDescriptors,
|
||||||
new[] { divDescriptor, catchAllDescriptor }
|
new[] { divDescriptor, catchAllDescriptor }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"div",
|
"div",
|
||||||
new[] { "class", "style", "custom" },
|
new[] { kvp("class"), kvp("style"), kvp("custom") },
|
||||||
defaultAvailableDescriptors,
|
defaultAvailableDescriptors,
|
||||||
new[] { divDescriptor, catchAllDescriptor, catchAllDescriptor2 }
|
new[] { divDescriptor, catchAllDescriptor, catchAllDescriptor2 }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input",
|
"input",
|
||||||
new[] { "class", "style" },
|
new[] { kvp("class"), kvp("style") },
|
||||||
defaultAvailableDescriptors,
|
defaultAvailableDescriptors,
|
||||||
new[] { inputDescriptor, catchAllDescriptor }
|
new[] { inputDescriptor, catchAllDescriptor }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input",
|
"input",
|
||||||
new[] { "nodashprefixA" },
|
new[] { kvp("nodashprefixA") },
|
||||||
defaultWildcardDescriptors,
|
defaultWildcardDescriptors,
|
||||||
new[] { inputWildcardPrefixDescriptor }
|
new[] { inputWildcardPrefixDescriptor }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input",
|
"input",
|
||||||
new[] { "nodashprefix-ABC-DEF", "random" },
|
new[] { kvp("nodashprefix-ABC-DEF"), kvp("random") },
|
||||||
defaultWildcardDescriptors,
|
defaultWildcardDescriptors,
|
||||||
new[] { inputWildcardPrefixDescriptor }
|
new[] { inputWildcardPrefixDescriptor }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input",
|
"input",
|
||||||
new[] { "prefixABCnodashprefix" },
|
new[] { kvp("prefixABCnodashprefix") },
|
||||||
defaultWildcardDescriptors,
|
defaultWildcardDescriptors,
|
||||||
Enumerable.Empty<TagHelperDescriptor>()
|
Enumerable.Empty<TagHelperDescriptor>()
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input",
|
"input",
|
||||||
new[] { "prefix-" },
|
new[] { kvp("prefix-") },
|
||||||
defaultWildcardDescriptors,
|
defaultWildcardDescriptors,
|
||||||
Enumerable.Empty<TagHelperDescriptor>()
|
Enumerable.Empty<TagHelperDescriptor>()
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input",
|
"input",
|
||||||
new[] { "nodashprefix" },
|
new[] { kvp("nodashprefix") },
|
||||||
defaultWildcardDescriptors,
|
defaultWildcardDescriptors,
|
||||||
Enumerable.Empty<TagHelperDescriptor>()
|
Enumerable.Empty<TagHelperDescriptor>()
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input",
|
"input",
|
||||||
new[] { "prefix-A" },
|
new[] { kvp("prefix-A") },
|
||||||
defaultWildcardDescriptors,
|
defaultWildcardDescriptors,
|
||||||
new[] { catchAllWildcardPrefixDescriptor }
|
new[] { catchAllWildcardPrefixDescriptor }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input",
|
"input",
|
||||||
new[] { "prefix-ABC-DEF", "random" },
|
new[] { kvp("prefix-ABC-DEF"), kvp("random") },
|
||||||
defaultWildcardDescriptors,
|
defaultWildcardDescriptors,
|
||||||
new[] { catchAllWildcardPrefixDescriptor }
|
new[] { catchAllWildcardPrefixDescriptor }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input",
|
"input",
|
||||||
new[] { "prefix-abc", "nodashprefix-def" },
|
new[] { kvp("prefix-abc"), kvp("nodashprefix-def") },
|
||||||
defaultWildcardDescriptors,
|
defaultWildcardDescriptors,
|
||||||
new[] { inputWildcardPrefixDescriptor, catchAllWildcardPrefixDescriptor }
|
new[] { inputWildcardPrefixDescriptor, catchAllWildcardPrefixDescriptor }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input",
|
"input",
|
||||||
new[] { "class", "prefix-abc", "onclick", "nodashprefix-def", "style" },
|
new[] { kvp("class"), kvp("prefix-abc"), kvp("onclick"), kvp("nodashprefix-def"), kvp("style") },
|
||||||
defaultWildcardDescriptors,
|
defaultWildcardDescriptors,
|
||||||
new[] { inputWildcardPrefixDescriptor, catchAllWildcardPrefixDescriptor }
|
new[] { inputWildcardPrefixDescriptor, catchAllWildcardPrefixDescriptor }
|
||||||
},
|
},
|
||||||
|
|
@ -237,7 +261,7 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
||||||
[MemberData(nameof(RequiredAttributeData))]
|
[MemberData(nameof(RequiredAttributeData))]
|
||||||
public void GetDescriptors_ReturnsDescriptorsWithRequiredAttributes(
|
public void GetDescriptors_ReturnsDescriptorsWithRequiredAttributes(
|
||||||
string tagName,
|
string tagName,
|
||||||
IEnumerable<string> providedAttributes,
|
IEnumerable<KeyValuePair<string, string>> providedAttributes,
|
||||||
IEnumerable<TagHelperDescriptor> availableDescriptors,
|
IEnumerable<TagHelperDescriptor> availableDescriptors,
|
||||||
IEnumerable<TagHelperDescriptor> expectedDescriptors)
|
IEnumerable<TagHelperDescriptor> expectedDescriptors)
|
||||||
{
|
{
|
||||||
|
|
@ -265,7 +289,7 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
||||||
// Act
|
// Act
|
||||||
var resolvedDescriptors = provider.GetDescriptors(
|
var resolvedDescriptors = provider.GetDescriptors(
|
||||||
tagName: "th",
|
tagName: "th",
|
||||||
attributeNames: Enumerable.Empty<string>(),
|
attributes: Enumerable.Empty<KeyValuePair<string, string>>(),
|
||||||
parentTagName: "p");
|
parentTagName: "p");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -284,11 +308,11 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
||||||
// Act
|
// Act
|
||||||
var retrievedDescriptorsDiv = provider.GetDescriptors(
|
var retrievedDescriptorsDiv = provider.GetDescriptors(
|
||||||
tagName: "th:div",
|
tagName: "th:div",
|
||||||
attributeNames: Enumerable.Empty<string>(),
|
attributes: Enumerable.Empty<KeyValuePair<string, string>>(),
|
||||||
parentTagName: "p");
|
parentTagName: "p");
|
||||||
var retrievedDescriptorsSpan = provider.GetDescriptors(
|
var retrievedDescriptorsSpan = provider.GetDescriptors(
|
||||||
tagName: "th2:span",
|
tagName: "th2:span",
|
||||||
attributeNames: Enumerable.Empty<string>(),
|
attributes: Enumerable.Empty<KeyValuePair<string, string>>(),
|
||||||
parentTagName: "p");
|
parentTagName: "p");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -308,11 +332,11 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
||||||
// Act
|
// Act
|
||||||
var retrievedDescriptorsDiv = provider.GetDescriptors(
|
var retrievedDescriptorsDiv = provider.GetDescriptors(
|
||||||
tagName: "th:div",
|
tagName: "th:div",
|
||||||
attributeNames: Enumerable.Empty<string>(),
|
attributes: Enumerable.Empty<KeyValuePair<string, string>>(),
|
||||||
parentTagName: "p");
|
parentTagName: "p");
|
||||||
var retrievedDescriptorsSpan = provider.GetDescriptors(
|
var retrievedDescriptorsSpan = provider.GetDescriptors(
|
||||||
tagName: "th:span",
|
tagName: "th:span",
|
||||||
attributeNames: Enumerable.Empty<string>(),
|
attributes: Enumerable.Empty<KeyValuePair<string, string>>(),
|
||||||
parentTagName: "p");
|
parentTagName: "p");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -333,7 +357,7 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
||||||
// Act
|
// Act
|
||||||
var retrievedDescriptors = provider.GetDescriptors(
|
var retrievedDescriptors = provider.GetDescriptors(
|
||||||
tagName: "th:div",
|
tagName: "th:div",
|
||||||
attributeNames: Enumerable.Empty<string>(),
|
attributes: Enumerable.Empty<KeyValuePair<string, string>>(),
|
||||||
parentTagName: "p");
|
parentTagName: "p");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -354,7 +378,7 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
||||||
// Act
|
// Act
|
||||||
var retrievedDescriptorsDiv = provider.GetDescriptors(
|
var retrievedDescriptorsDiv = provider.GetDescriptors(
|
||||||
tagName: "div",
|
tagName: "div",
|
||||||
attributeNames: Enumerable.Empty<string>(),
|
attributes: Enumerable.Empty<KeyValuePair<string, string>>(),
|
||||||
parentTagName: "p");
|
parentTagName: "p");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -383,7 +407,7 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
||||||
// Act
|
// Act
|
||||||
var retrievedDescriptors = provider.GetDescriptors(
|
var retrievedDescriptors = provider.GetDescriptors(
|
||||||
tagName: "foo",
|
tagName: "foo",
|
||||||
attributeNames: Enumerable.Empty<string>(),
|
attributes: Enumerable.Empty<KeyValuePair<string, string>>(),
|
||||||
parentTagName: "p");
|
parentTagName: "p");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -418,11 +442,11 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
||||||
// Act
|
// Act
|
||||||
var divDescriptors = provider.GetDescriptors(
|
var divDescriptors = provider.GetDescriptors(
|
||||||
tagName: "div",
|
tagName: "div",
|
||||||
attributeNames: Enumerable.Empty<string>(),
|
attributes: Enumerable.Empty<KeyValuePair<string, string>>(),
|
||||||
parentTagName: "p");
|
parentTagName: "p");
|
||||||
var spanDescriptors = provider.GetDescriptors(
|
var spanDescriptors = provider.GetDescriptors(
|
||||||
tagName: "span",
|
tagName: "span",
|
||||||
attributeNames: Enumerable.Empty<string>(),
|
attributes: Enumerable.Empty<KeyValuePair<string, string>>(),
|
||||||
parentTagName: "p");
|
parentTagName: "p");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -453,7 +477,7 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
||||||
// Act
|
// Act
|
||||||
var retrievedDescriptors = provider.GetDescriptors(
|
var retrievedDescriptors = provider.GetDescriptors(
|
||||||
tagName: "div",
|
tagName: "div",
|
||||||
attributeNames: Enumerable.Empty<string>(),
|
attributes: Enumerable.Empty<KeyValuePair<string, string>>(),
|
||||||
parentTagName: "p");
|
parentTagName: "p");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,22 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
||||||
Prefix = "prefix:",
|
Prefix = "prefix:",
|
||||||
TagName = "tag name",
|
TagName = "tag name",
|
||||||
TypeName = "type name",
|
TypeName = "type name",
|
||||||
AssemblyName = "assembly 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" },
|
AllowedChildren = new[] { "allowed child one" },
|
||||||
RequiredParent = "parent name",
|
RequiredParent = "parent name",
|
||||||
DesignTimeDescriptor = new TagHelperDesignTimeDescriptor
|
DesignTimeDescriptor = new TagHelperDesignTimeDescriptor
|
||||||
|
|
@ -41,7 +55,14 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
||||||
$"\"{ nameof(TagHelperDescriptor.AssemblyName) }\":\"assembly name\"," +
|
$"\"{ nameof(TagHelperDescriptor.AssemblyName) }\":\"assembly name\"," +
|
||||||
$"\"{ nameof(TagHelperDescriptor.Attributes) }\":[]," +
|
$"\"{ nameof(TagHelperDescriptor.Attributes) }\":[]," +
|
||||||
$"\"{ nameof(TagHelperDescriptor.RequiredAttributes) }\":" +
|
$"\"{ 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.AllowedChildren) }\":[\"allowed child one\"]," +
|
||||||
$"\"{ nameof(TagHelperDescriptor.RequiredParent) }\":\"parent name\"," +
|
$"\"{ nameof(TagHelperDescriptor.RequiredParent) }\":\"parent name\"," +
|
||||||
$"\"{ nameof(TagHelperDescriptor.TagStructure) }\":0," +
|
$"\"{ nameof(TagHelperDescriptor.TagStructure) }\":0," +
|
||||||
|
|
@ -200,8 +221,15 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
||||||
$"\"{nameof(TagHelperDescriptor.TypeName)}\":\"type name\"," +
|
$"\"{nameof(TagHelperDescriptor.TypeName)}\":\"type name\"," +
|
||||||
$"\"{nameof(TagHelperDescriptor.AssemblyName)}\":\"assembly name\"," +
|
$"\"{nameof(TagHelperDescriptor.AssemblyName)}\":\"assembly name\"," +
|
||||||
$"\"{nameof(TagHelperDescriptor.Attributes)}\":[]," +
|
$"\"{nameof(TagHelperDescriptor.Attributes)}\":[]," +
|
||||||
$"\"{nameof(TagHelperDescriptor.RequiredAttributes)}\":" +
|
$"\"{ 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\",\"allowed child two\"]," +
|
$"\"{ nameof(TagHelperDescriptor.AllowedChildren) }\":[\"allowed child one\",\"allowed child two\"]," +
|
||||||
$"\"{ nameof(TagHelperDescriptor.RequiredParent) }\":\"parent name\"," +
|
$"\"{ nameof(TagHelperDescriptor.RequiredParent) }\":\"parent name\"," +
|
||||||
$"\"{nameof(TagHelperDescriptor.TagStructure)}\":2," +
|
$"\"{nameof(TagHelperDescriptor.TagStructure)}\":2," +
|
||||||
|
|
@ -215,7 +243,21 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
||||||
TagName = "tag name",
|
TagName = "tag name",
|
||||||
TypeName = "type name",
|
TypeName = "type name",
|
||||||
AssemblyName = "assembly 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" },
|
AllowedChildren = new[] { "allowed child one", "allowed child two" },
|
||||||
RequiredParent = "parent name",
|
RequiredParent = "parent name",
|
||||||
DesignTimeDescriptor = new TagHelperDesignTimeDescriptor
|
DesignTimeDescriptor = new TagHelperDesignTimeDescriptor
|
||||||
|
|
@ -237,7 +279,7 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
|
||||||
Assert.Equal(expectedDescriptor.TypeName, descriptor.TypeName, StringComparer.Ordinal);
|
Assert.Equal(expectedDescriptor.TypeName, descriptor.TypeName, StringComparer.Ordinal);
|
||||||
Assert.Equal(expectedDescriptor.AssemblyName, descriptor.AssemblyName, StringComparer.Ordinal);
|
Assert.Equal(expectedDescriptor.AssemblyName, descriptor.AssemblyName, StringComparer.Ordinal);
|
||||||
Assert.Empty(descriptor.Attributes);
|
Assert.Empty(descriptor.Attributes);
|
||||||
Assert.Equal(expectedDescriptor.RequiredAttributes, descriptor.RequiredAttributes, StringComparer.Ordinal);
|
Assert.Equal(expectedDescriptor.RequiredAttributes, descriptor.RequiredAttributes, TagHelperRequiredAttributeDescriptorComparer.Default);
|
||||||
Assert.Equal(
|
Assert.Equal(
|
||||||
expectedDescriptor.DesignTimeDescriptor,
|
expectedDescriptor.DesignTimeDescriptor,
|
||||||
descriptor.DesignTimeDescriptor,
|
descriptor.DesignTimeDescriptor,
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,74 @@ using Microsoft.AspNetCore.Razor.Compilation.TagHelpers;
|
||||||
using Microsoft.AspNetCore.Razor.Test.Framework;
|
using Microsoft.AspNetCore.Razor.Test.Framework;
|
||||||
using Microsoft.AspNetCore.Razor.Text;
|
using Microsoft.AspNetCore.Razor.Text;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
using Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Test.TagHelpers
|
namespace Microsoft.AspNetCore.Razor.Test.TagHelpers
|
||||||
{
|
{
|
||||||
public class TagHelperParseTreeRewriterTest : TagHelperRewritingTestBase
|
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
|
public static TheoryData PartialRequiredParentData
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
@ -716,7 +779,7 @@ namespace Microsoft.AspNetCore.Razor.Test.TagHelpers
|
||||||
TagName = "strong",
|
TagName = "strong",
|
||||||
TypeName = "StrongTagHelper",
|
TypeName = "StrongTagHelper",
|
||||||
AssemblyName = "SomeAssembly",
|
AssemblyName = "SomeAssembly",
|
||||||
RequiredAttributes = new[] { "required" },
|
RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "required" } },
|
||||||
AllowedChildren = new[] { "br" }
|
AllowedChildren = new[] { "br" }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -1648,21 +1711,25 @@ namespace Microsoft.AspNetCore.Razor.Test.TagHelpers
|
||||||
TagName = "p",
|
TagName = "p",
|
||||||
TypeName = "pTagHelper",
|
TypeName = "pTagHelper",
|
||||||
AssemblyName = "SomeAssembly",
|
AssemblyName = "SomeAssembly",
|
||||||
RequiredAttributes = new[] { "class" }
|
RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "class" } }
|
||||||
},
|
},
|
||||||
new TagHelperDescriptor
|
new TagHelperDescriptor
|
||||||
{
|
{
|
||||||
TagName = "div",
|
TagName = "div",
|
||||||
TypeName = "divTagHelper",
|
TypeName = "divTagHelper",
|
||||||
AssemblyName = "SomeAssembly",
|
AssemblyName = "SomeAssembly",
|
||||||
RequiredAttributes = new[] { "class", "style" }
|
RequiredAttributes = new[]
|
||||||
|
{
|
||||||
|
new TagHelperRequiredAttributeDescriptor { Name = "class" },
|
||||||
|
new TagHelperRequiredAttributeDescriptor { Name = "style" }
|
||||||
|
}
|
||||||
},
|
},
|
||||||
new TagHelperDescriptor
|
new TagHelperDescriptor
|
||||||
{
|
{
|
||||||
TagName = "*",
|
TagName = "*",
|
||||||
TypeName = "catchAllTagHelper",
|
TypeName = "catchAllTagHelper",
|
||||||
AssemblyName = "SomeAssembly",
|
AssemblyName = "SomeAssembly",
|
||||||
RequiredAttributes = new[] { "catchAll" }
|
RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "catchAll" } }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var descriptorProvider = new TagHelperDescriptorProvider(descriptors);
|
var descriptorProvider = new TagHelperDescriptorProvider(descriptors);
|
||||||
|
|
@ -1911,14 +1978,14 @@ namespace Microsoft.AspNetCore.Razor.Test.TagHelpers
|
||||||
TagName = "p",
|
TagName = "p",
|
||||||
TypeName = "pTagHelper",
|
TypeName = "pTagHelper",
|
||||||
AssemblyName = "SomeAssembly",
|
AssemblyName = "SomeAssembly",
|
||||||
RequiredAttributes = new[] { "class" }
|
RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "class" } }
|
||||||
},
|
},
|
||||||
new TagHelperDescriptor
|
new TagHelperDescriptor
|
||||||
{
|
{
|
||||||
TagName = "*",
|
TagName = "*",
|
||||||
TypeName = "catchAllTagHelper",
|
TypeName = "catchAllTagHelper",
|
||||||
AssemblyName = "SomeAssembly",
|
AssemblyName = "SomeAssembly",
|
||||||
RequiredAttributes = new[] { "catchAll" }
|
RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "catchAll" } }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var descriptorProvider = new TagHelperDescriptorProvider(descriptors);
|
var descriptorProvider = new TagHelperDescriptorProvider(descriptors);
|
||||||
|
|
@ -2135,7 +2202,7 @@ namespace Microsoft.AspNetCore.Razor.Test.TagHelpers
|
||||||
TagName = "p",
|
TagName = "p",
|
||||||
TypeName = "pTagHelper",
|
TypeName = "pTagHelper",
|
||||||
AssemblyName = "SomeAssembly",
|
AssemblyName = "SomeAssembly",
|
||||||
RequiredAttributes = new[] { "class" }
|
RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "class" } }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var descriptorProvider = new TagHelperDescriptorProvider(descriptors);
|
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