Change `[RestrictChildren]` to allow non-`TagHelper` tags.
- Updated the `TagHelperParseTreeRewriter` loosen child restrictions to non-`TagHelper` HTML elements. - Updated tests to showcase that non-`TagHelper` elements are allowed to be restricted. - Added an additional test case to showcase sub-sub nesting of non-`TagHelper` restricted children. #543
This commit is contained in:
parent
816b1f4190
commit
d2a7a355cc
|
|
@ -19,10 +19,10 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
/// Instantiates a new instance of the <see cref="RestrictChildrenAttribute"/> class.
|
/// Instantiates a new instance of the <see cref="RestrictChildrenAttribute"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="childTag">
|
/// <param name="childTag">
|
||||||
/// The tag name of an element allowed as a child. Tag helpers must target the element.
|
/// The tag name of an element allowed as a child.
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <param name="childTags">
|
/// <param name="childTags">
|
||||||
/// Additional names of elements allowed as children. Tag helpers must target all such elements.
|
/// Additional names of elements allowed as children.
|
||||||
/// </param>
|
/// </param>
|
||||||
public RestrictChildrenAttribute(string childTag, params string[] childTags)
|
public RestrictChildrenAttribute(string childTag, params string[] childTags)
|
||||||
{
|
{
|
||||||
|
|
@ -35,7 +35,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the names of elements allowed as children. Tag helpers must target all such elements.
|
/// Get the names of elements allowed as children.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IEnumerable<string> ChildTags { get; }
|
public IEnumerable<string> ChildTags { get; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -81,10 +81,10 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
TrackTagBlock(childBlock);
|
|
||||||
|
|
||||||
// Non-TagHelper tag.
|
// Non-TagHelper tag.
|
||||||
ValidateParentTagHelperAllowsPlainTag(childBlock, context.ErrorSink);
|
ValidateParentAllowsPlainTag(childBlock, context.ErrorSink);
|
||||||
|
|
||||||
|
TrackTagBlock(childBlock);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we get to here it means that we're a normal html tag. No need to iterate any deeper into
|
// If we get to here it means that we're a normal html tag. No need to iterate any deeper into
|
||||||
|
|
@ -99,7 +99,7 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ValidateParentTagHelperAllowsContent((Span)child, context.ErrorSink);
|
ValidateParentAllowsContent((Span)child, context.ErrorSink);
|
||||||
}
|
}
|
||||||
|
|
||||||
// At this point the child is a Span or Block with Type BlockType.Tag that doesn't happen to be a
|
// At this point the child is a Span or Block with Type BlockType.Tag that doesn't happen to be a
|
||||||
|
|
@ -197,7 +197,7 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ValidateParentTagHelperAllowsTagHelper(tagName, tagBlock, context.ErrorSink);
|
ValidateParentAllowsTagHelper(tagName, tagBlock, context.ErrorSink);
|
||||||
ValidateDescriptors(descriptors, tagName, tagBlock, context.ErrorSink);
|
ValidateDescriptors(descriptors, tagName, tagBlock, context.ErrorSink);
|
||||||
|
|
||||||
// We're in a start TagHelper block.
|
// We're in a start TagHelper block.
|
||||||
|
|
@ -277,7 +277,7 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
||||||
// can't recover it means there was no corresponding tag helper start tag.
|
// can't recover it means there was no corresponding tag helper start tag.
|
||||||
if (TryRecoverTagHelper(tagName, tagBlock, context))
|
if (TryRecoverTagHelper(tagName, tagBlock, context))
|
||||||
{
|
{
|
||||||
ValidateParentTagHelperAllowsTagHelper(tagName, tagBlock, context.ErrorSink);
|
ValidateParentAllowsTagHelper(tagName, tagBlock, context.ErrorSink);
|
||||||
ValidateTagSyntax(tagName, tagBlock, context);
|
ValidateTagSyntax(tagName, tagBlock, context);
|
||||||
|
|
||||||
// Successfully recovered, move onto the next element.
|
// Successfully recovered, move onto the next element.
|
||||||
|
|
@ -335,10 +335,22 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
||||||
return attributeNames;
|
return attributeNames;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ValidateParentTagHelperAllowsContent(Span child, ErrorSink errorSink)
|
private bool HasAllowedChildren()
|
||||||
{
|
{
|
||||||
var allowedChildren = _currentTagHelperTracker?.AllowedChildren;
|
var currentTracker = _trackerStack.Count > 0 ? _trackerStack.Peek() : null;
|
||||||
if (allowedChildren != null)
|
|
||||||
|
// If the current tracker is not a TagHelper then there's no AllowedChildren to enforce.
|
||||||
|
if (currentTracker == null || !currentTracker.IsTagHelper)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _currentTagHelperTracker.AllowedChildren != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ValidateParentAllowsContent(Span child, ErrorSink errorSink)
|
||||||
|
{
|
||||||
|
if (HasAllowedChildren())
|
||||||
{
|
{
|
||||||
var content = child.Content;
|
var content = child.Content;
|
||||||
if (!string.IsNullOrWhiteSpace(content))
|
if (!string.IsNullOrWhiteSpace(content))
|
||||||
|
|
@ -347,6 +359,7 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
||||||
var whitespace = content.Substring(0, content.Length - trimmedStart.Length);
|
var whitespace = content.Substring(0, content.Length - trimmedStart.Length);
|
||||||
var errorStart = SourceLocation.Advance(child.Start, whitespace);
|
var errorStart = SourceLocation.Advance(child.Start, whitespace);
|
||||||
var length = trimmedStart.TrimEnd().Length;
|
var length = trimmedStart.TrimEnd().Length;
|
||||||
|
var allowedChildren = _currentTagHelperTracker.AllowedChildren;
|
||||||
var allowedChildrenString = string.Join(", ", allowedChildren);
|
var allowedChildrenString = string.Join(", ", allowedChildren);
|
||||||
errorSink.OnError(
|
errorSink.OnError(
|
||||||
errorStart,
|
errorStart,
|
||||||
|
|
@ -358,31 +371,32 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ValidateParentTagHelperAllowsPlainTag(Block tagBlock, ErrorSink errorSink)
|
private void ValidateParentAllowsPlainTag(Block tagBlock, ErrorSink errorSink)
|
||||||
{
|
{
|
||||||
if (_currentTagHelperTracker?.AllowedChildren != null)
|
var tagName = GetTagName(tagBlock);
|
||||||
|
|
||||||
|
// Treat partial tags such as '</' which have no tag names as content.
|
||||||
|
if (string.IsNullOrEmpty(tagName))
|
||||||
{
|
{
|
||||||
var tagName = GetTagName(tagBlock);
|
Debug.Assert(tagBlock.Children.First() is Span);
|
||||||
|
|
||||||
// Treat partial tags such as '</' which have no tag names as content based errors.
|
ValidateParentAllowsContent((Span)tagBlock.Children.First(), errorSink);
|
||||||
if (string.IsNullOrEmpty(tagName))
|
return;
|
||||||
{
|
}
|
||||||
Debug.Assert(tagBlock.Children.First() is Span);
|
|
||||||
|
|
||||||
ValidateParentTagHelperAllowsContent(tagBlock.Children.First() as Span, errorSink);
|
var currentTracker = _trackerStack.Count > 0 ? _trackerStack.Peek() : null;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (HasAllowedChildren() &&
|
||||||
|
!_currentTagHelperTracker.AllowedChildren.Contains(tagName, StringComparer.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
OnAllowedChildrenTagError(_currentTagHelperTracker, tagName, tagBlock, errorSink);
|
OnAllowedChildrenTagError(_currentTagHelperTracker, tagName, tagBlock, errorSink);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ValidateParentTagHelperAllowsTagHelper(string tagName, Block tagBlock, ErrorSink errorSink)
|
private void ValidateParentAllowsTagHelper(string tagName, Block tagBlock, ErrorSink errorSink)
|
||||||
{
|
{
|
||||||
var currentlyAllowedChildren = _currentTagHelperTracker?.PrefixedAllowedChildren;
|
if (HasAllowedChildren() &&
|
||||||
|
!_currentTagHelperTracker.PrefixedAllowedChildren.Contains(tagName, StringComparer.OrdinalIgnoreCase))
|
||||||
if (currentlyAllowedChildren != null &&
|
|
||||||
!currentlyAllowedChildren.Contains(tagName, StringComparer.OrdinalIgnoreCase))
|
|
||||||
{
|
{
|
||||||
OnAllowedChildrenTagError(_currentTagHelperTracker, tagName, tagBlock, errorSink);
|
OnAllowedChildrenTagError(_currentTagHelperTracker, tagName, tagBlock, errorSink);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1531,7 +1531,7 @@ namespace Microsoft.AspNet.Razor
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The <{0}> tag is not allowed by parent <{1}> tag helper. Only child tag helper(s) targeting tag name(s) '{2}' are allowed.
|
/// The <{0}> tag is not allowed by parent <{1}> tag helper. Only child tags with name(s) '{2}' are allowed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string TagHelperParseTreeRewriter_InvalidNestedTag
|
internal static string TagHelperParseTreeRewriter_InvalidNestedTag
|
||||||
{
|
{
|
||||||
|
|
@ -1539,7 +1539,7 @@ namespace Microsoft.AspNet.Razor
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The <{0}> tag is not allowed by parent <{1}> tag helper. Only child tag helper(s) targeting tag name(s) '{2}' are allowed.
|
/// The <{0}> tag is not allowed by parent <{1}> tag helper. Only child tags with name(s) '{2}' are allowed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string FormatTagHelperParseTreeRewriter_InvalidNestedTag(object p0, object p1, object p2)
|
internal static string FormatTagHelperParseTreeRewriter_InvalidNestedTag(object p0, object p1, object p2)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -423,7 +423,7 @@ Instead, wrap the contents of the block in "{{}}":
|
||||||
<value>The parent <{0}> tag helper does not allow non-tag content. Only child tag helper(s) targeting tag name(s) '{1}' are allowed.</value>
|
<value>The parent <{0}> tag helper does not allow non-tag content. Only child tag helper(s) targeting tag name(s) '{1}' are allowed.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TagHelperParseTreeRewriter_InvalidNestedTag" xml:space="preserve">
|
<data name="TagHelperParseTreeRewriter_InvalidNestedTag" xml:space="preserve">
|
||||||
<value>The <{0}> tag is not allowed by parent <{1}> tag helper. Only child tag helper(s) targeting tag name(s) '{2}' are allowed.</value>
|
<value>The <{0}> tag is not allowed by parent <{1}> tag helper. Only child tags with name(s) '{2}' are allowed.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ParseError_HelperDirectiveNotAvailable" xml:space="preserve">
|
<data name="ParseError_HelperDirectiveNotAvailable" xml:space="preserve">
|
||||||
<value>The {0} directive is not supported.</value>
|
<value>The {0} directive is not supported.</value>
|
||||||
|
|
|
||||||
|
|
@ -158,7 +158,7 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the names of elements allowed as children. Tag helpers must target all such elements.
|
/// Get the names of elements allowed as children.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks><c>null</c> indicates all children are allowed.</remarks>
|
/// <remarks><c>null</c> indicates all children are allowed.</remarks>
|
||||||
public IEnumerable<string> AllowedChildren { get; set; }
|
public IEnumerable<string> AllowedChildren { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -661,18 +661,6 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
|
||||||
lineIndex: 1,
|
lineIndex: 1,
|
||||||
columnIndex: 5,
|
columnIndex: 5,
|
||||||
length: 6),
|
length: 6),
|
||||||
new RazorError(
|
|
||||||
RazorResources.FormatTagHelperParseTreeRewriter_CannotHaveNonTagContent("p", "br"),
|
|
||||||
absoluteIndex: 23 + newLineLength * 2,
|
|
||||||
lineIndex: 2,
|
|
||||||
columnIndex: 8,
|
|
||||||
length: 5),
|
|
||||||
new RazorError(
|
|
||||||
RazorResources.FormatTagHelperParseTreeRewriter_InvalidNestedTag("strong", "p", "br"),
|
|
||||||
absoluteIndex: 34 + newLineLength * 3,
|
|
||||||
lineIndex: 3,
|
|
||||||
columnIndex: 5,
|
|
||||||
length: 6),
|
|
||||||
};
|
};
|
||||||
var expectedOutput = new MarkupBlock(
|
var expectedOutput = new MarkupBlock(
|
||||||
new MarkupTagHelperBlock("p",
|
new MarkupTagHelperBlock("p",
|
||||||
|
|
@ -711,13 +699,7 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
|
||||||
absoluteIndex: 18,
|
absoluteIndex: 18,
|
||||||
lineIndex: 0,
|
lineIndex: 0,
|
||||||
columnIndex: 18,
|
columnIndex: 18,
|
||||||
length: 6),
|
length: 6)
|
||||||
new RazorError(
|
|
||||||
RazorResources.FormatTagHelperParseTreeRewriter_InvalidNestedTag("strong", "strong", "br"),
|
|
||||||
absoluteIndex: 27,
|
|
||||||
lineIndex: 0,
|
|
||||||
columnIndex: 27,
|
|
||||||
length: 6),
|
|
||||||
};
|
};
|
||||||
var expectedOutput = new MarkupBlock(
|
var expectedOutput = new MarkupBlock(
|
||||||
new MarkupTagHelperBlock("strong",
|
new MarkupTagHelperBlock("strong",
|
||||||
|
|
@ -973,8 +955,6 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
|
||||||
nestedContentError("strong", "strong", 11, 6),
|
nestedContentError("strong", "strong", 11, 6),
|
||||||
nestedTagError("br", "strong", "strong", 18, 2),
|
nestedTagError("br", "strong", "strong", 18, 2),
|
||||||
nestedTagError("em", "strong", "strong", 22, 2),
|
nestedTagError("em", "strong", "strong", 22, 2),
|
||||||
nestedContentError("strong", "strong", 25, 11),
|
|
||||||
nestedTagError("em", "strong", "strong", 38, 2),
|
|
||||||
nestedTagError("br", "p", "strong", 51, 2),
|
nestedTagError("br", "p", "strong", 51, 2),
|
||||||
nestedContentError("p", "strong", 56, 9)
|
nestedContentError("p", "strong", 56, 9)
|
||||||
}
|
}
|
||||||
|
|
@ -995,13 +975,6 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
|
||||||
factory.Markup("Something"))),
|
factory.Markup("Something"))),
|
||||||
new[]
|
new[]
|
||||||
{
|
{
|
||||||
nestedTagError("custom", "p", "custom", 4, 6),
|
|
||||||
nestedContentError("p", "custom", 11, 6),
|
|
||||||
nestedTagError("br", "p", "custom", 18, 2),
|
|
||||||
nestedTagError("em", "p", "custom", 22, 2),
|
|
||||||
nestedContentError("p", "custom", 25, 11),
|
|
||||||
nestedTagError("em", "p", "custom", 38, 2),
|
|
||||||
nestedTagError("custom", "p", "custom", 43, 6),
|
|
||||||
nestedTagError("br", "p", "custom", 51, 2),
|
nestedTagError("br", "p", "custom", 51, 2),
|
||||||
nestedContentError("p", "custom", 56, 9)
|
nestedContentError("p", "custom", 56, 9)
|
||||||
}
|
}
|
||||||
|
|
@ -1027,7 +1000,26 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
|
||||||
{
|
{
|
||||||
nestedContentError("p", "custom", 3, 1),
|
nestedContentError("p", "custom", 3, 1),
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
"<p><custom><br>:<strong><strong>Hello</strong></strong>:<input></custom></p>",
|
||||||
|
new[] { "custom", "strong" },
|
||||||
|
new MarkupBlock(
|
||||||
|
new MarkupTagHelperBlock("p",
|
||||||
|
blockFactory.MarkupTagBlock("<custom>"),
|
||||||
|
new MarkupTagHelperBlock("br", TagMode.StartTagOnly),
|
||||||
|
factory.Markup(":"),
|
||||||
|
new MarkupTagHelperBlock("strong",
|
||||||
|
new MarkupTagHelperBlock("strong",
|
||||||
|
factory.Markup("Hello"))),
|
||||||
|
factory.Markup(":"),
|
||||||
|
blockFactory.MarkupTagBlock("<input>"),
|
||||||
|
blockFactory.MarkupTagBlock("</custom>"))),
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
nestedContentError("strong", "custom, strong", 32, 5),
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue