Revert "Revert "Make Razor HTML Comments aware (#2178)""

This reverts commit 1f3f4b79da.
This commit is contained in:
N. Taylor Mullen 2018-03-20 15:23:53 -07:00
parent 1f3f4b79da
commit c874f84c3d
24 changed files with 996 additions and 108 deletions

View File

@ -18,6 +18,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
// Special // Special
Comment = 8, Comment = 8,
Tag = 9 Tag = 9,
HtmlComment = 10
} }
} }

View File

@ -12,6 +12,9 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
{ {
private const string ScriptTagName = "script"; private const string ScriptTagName = "script";
private static readonly HtmlSymbol[] nonAllowedHtmlCommentEnding = new[] { HtmlSymbol.Hyphen, new HtmlSymbol("!", HtmlSymbolType.Bang), new HtmlSymbol("<", HtmlSymbolType.OpenAngle) };
private static readonly HtmlSymbol[] singleHyphenArray = new[] { HtmlSymbol.Hyphen };
private static readonly char[] ValidAfterTypeAttributeNameCharacters = { ' ', '\t', '\r', '\n', '\f', '=' }; private static readonly char[] ValidAfterTypeAttributeNameCharacters = { ' ', '\t', '\r', '\n', '\f', '=' };
private SourceLocation _lastTagStart = SourceLocation.Zero; private SourceLocation _lastTagStart = SourceLocation.Zero;
private HtmlSymbol _bufferedOpenAngle; private HtmlSymbol _bufferedOpenAngle;
@ -492,33 +495,37 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
if (AcceptAndMoveNext()) if (AcceptAndMoveNext())
{ {
if (CurrentSymbol.Type == HtmlSymbolType.DoubleHyphen) if (IsHtmlCommentAhead())
{ {
AcceptAndMoveNext(); using (Context.Builder.StartBlock(BlockKindInternal.HtmlComment))
Span.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.Any;
while (!EndOfFile)
{ {
SkipToAndParseCode(HtmlSymbolType.DoubleHyphen); // Accept the double-hyphen symbol at the beginning of the comment block.
if (At(HtmlSymbolType.DoubleHyphen)) AcceptAndMoveNext();
{ Output(SpanKindInternal.Markup, AcceptedCharactersInternal.None);
AcceptWhile(HtmlSymbolType.DoubleHyphen);
if (At(HtmlSymbolType.Text) && Span.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.WhiteSpace;
string.Equals(CurrentSymbol.Content, "-", StringComparison.Ordinal)) while (!EndOfFile)
{ {
AcceptAndMoveNext(); SkipToAndParseCode(HtmlSymbolType.DoubleHyphen);
} var lastDoubleHyphen = AcceptAllButLastDoubleHyphens();
if (At(HtmlSymbolType.CloseAngle)) if (At(HtmlSymbolType.CloseAngle))
{ {
// Output the content in the comment block as a separate markup
Output(SpanKindInternal.Markup, AcceptedCharactersInternal.WhiteSpace);
// This is the end of a comment block
Accept(lastDoubleHyphen);
AcceptAndMoveNext(); AcceptAndMoveNext();
Output(SpanKindInternal.Markup, AcceptedCharactersInternal.None);
return true; return true;
} }
else if (lastDoubleHyphen != null)
{
Accept(lastDoubleHyphen);
}
} }
} }
return false;
} }
else if (CurrentSymbol.Type == HtmlSymbolType.LeftBracket) else if (CurrentSymbol.Type == HtmlSymbolType.LeftBracket)
{ {
@ -537,6 +544,138 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
return false; return false;
} }
protected HtmlSymbol AcceptAllButLastDoubleHyphens()
{
var lastDoubleHyphen = CurrentSymbol;
AcceptWhile(s =>
{
if (NextIs(HtmlSymbolType.DoubleHyphen))
{
lastDoubleHyphen = s;
return true;
}
return false;
});
NextToken();
if (At(HtmlSymbolType.Text) && IsHyphen(CurrentSymbol))
{
// Doing this here to maintain the order of symbols
if (!NextIs(HtmlSymbolType.CloseAngle))
{
Accept(lastDoubleHyphen);
lastDoubleHyphen = null;
}
AcceptAndMoveNext();
}
return lastDoubleHyphen;
}
internal static bool IsHyphen(HtmlSymbol symbol)
{
return symbol.Equals(HtmlSymbol.Hyphen);
}
protected bool IsHtmlCommentAhead()
{
/*
* From HTML5 Specification, available at http://www.w3.org/TR/html52/syntax.html#comments
*
* Comments must have the following format:
* 1. The string "<!--"
* 2. Optionally, text, with the additional restriction that the text
* 2.1 must not start with the string ">" nor start with the string "->"
* 2.2 nor contain the strings
* 2.2.1 "<!--"
* 2.2.2 "-->" // As we will be treating this as a comment ending, there is no need to handle this case at all.
* 2.2.3 "--!>"
* 2.3 nor end with the string "<!-".
* 3. The string "-->"
*
* */
if (CurrentSymbol.Type != HtmlSymbolType.DoubleHyphen)
{
return false;
}
// Check condition 2.1
if (NextIs(HtmlSymbolType.CloseAngle) || NextIs(next => IsHyphen(next) && NextIs(HtmlSymbolType.CloseAngle)))
{
return false;
}
// Check condition 2.2
var isValidComment = false;
LookaheadUntil((symbol, prevSymbols) =>
{
if (symbol.Type == HtmlSymbolType.DoubleHyphen)
{
if (NextIs(HtmlSymbolType.CloseAngle))
{
// Check condition 2.3: We're at the end of a comment. Check to make sure the text ending is allowed.
isValidComment = !IsCommentContentEndingInvalid(prevSymbols);
return true;
}
else if (NextIs(ns => IsHyphen(ns) && NextIs(HtmlSymbolType.CloseAngle)))
{
// Check condition 2.3: we're at the end of a comment, which has an extra dash.
// Need to treat the dash as part of the content and check the ending.
// However, that case would have already been checked as part of check from 2.2.1 which
// would already fail this iteration and we wouldn't get here
isValidComment = true;
return true;
}
else if (NextIs(ns => ns.Type == HtmlSymbolType.Bang && NextIs(HtmlSymbolType.CloseAngle)))
{
// This is condition 2.2.3
isValidComment = false;
return true;
}
}
else if (symbol.Type == HtmlSymbolType.OpenAngle)
{
// Checking condition 2.2.1
if (NextIs(ns => ns.Type == HtmlSymbolType.Bang && NextIs(HtmlSymbolType.DoubleHyphen)))
{
isValidComment = false;
return true;
}
}
return false;
});
return isValidComment;
}
/// <summary>
/// Verifies, that the sequence doesn't end with the "&lt;!-" HtmlSymbols. Note, the first symbol is an opening bracket symbol
/// </summary>
internal static bool IsCommentContentEndingInvalid(IEnumerable<HtmlSymbol> sequence)
{
var reversedSequence = sequence.Reverse();
var index = 0;
foreach (var item in reversedSequence)
{
if (!item.Equals(nonAllowedHtmlCommentEnding[index++]))
{
return false;
}
if (index == nonAllowedHtmlCommentEnding.Length)
{
return true;
}
}
return false;
}
private bool CData() private bool CData()
{ {
if (CurrentSymbol.Type == HtmlSymbolType.Text && string.Equals(CurrentSymbol.Content, "cdata", StringComparison.OrdinalIgnoreCase)) if (CurrentSymbol.Type == HtmlSymbolType.Text && string.Equals(CurrentSymbol.Content, "cdata", StringComparison.OrdinalIgnoreCase))
@ -1474,8 +1613,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
// Checking to see if we meet the conditions of a special '!' tag: <!DOCTYPE, <![CDATA[, <!--. // Checking to see if we meet the conditions of a special '!' tag: <!DOCTYPE, <![CDATA[, <!--.
if (!IsBangEscape(lookahead: 1)) if (!IsBangEscape(lookahead: 1))
{ {
if (Lookahead(2)?.Type == HtmlSymbolType.DoubleHyphen)
{
Output(SpanKindInternal.Markup);
}
AcceptAndMoveNext(); // Accept '<' AcceptAndMoveNext(); // Accept '<'
BangTag(); BangTag();
return; return;
} }
@ -1761,4 +1906,4 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
return 0; return 0;
} }
} }
} }

View File

@ -8,6 +8,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
{ {
internal class HtmlSymbol : SymbolBase<HtmlSymbolType> internal class HtmlSymbol : SymbolBase<HtmlSymbolType>
{ {
internal static readonly HtmlSymbol Hyphen = new HtmlSymbol("-", HtmlSymbolType.Text);
public HtmlSymbol(string content, HtmlSymbolType type) public HtmlSymbol(string content, HtmlSymbolType type)
: base(content, type, RazorDiagnostic.EmptyArray) : base(content, type, RazorDiagnostic.EmptyArray)
{ {
@ -29,4 +31,4 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
} }
} }
} }
} }

View File

@ -490,20 +490,29 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
{ {
if (HasAllowedChildren()) if (HasAllowedChildren())
{ {
var content = child.Content; var isDisallowedContent = true;
if (!string.IsNullOrWhiteSpace(content)) if (_featureFlags.AllowHtmlCommentsInTagHelpers)
{ {
var trimmedStart = content.TrimStart(); isDisallowedContent = !IsComment(child) && child.Kind != SpanKindInternal.Transition && child.Kind != SpanKindInternal.Code;
var whitespace = content.Substring(0, content.Length - trimmedStart.Length); }
var errorStart = SourceLocationTracker.Advance(child.Start, whitespace);
var length = trimmedStart.TrimEnd().Length; if (isDisallowedContent)
var allowedChildren = _currentTagHelperTracker.AllowedChildren; {
var allowedChildrenString = string.Join(", ", allowedChildren); var content = child.Content;
errorSink.OnError( if (!string.IsNullOrWhiteSpace(content))
RazorDiagnosticFactory.CreateTagHelper_CannotHaveNonTagContent( {
new SourceSpan(errorStart, length), var trimmedStart = content.TrimStart();
_currentTagHelperTracker.TagName, var whitespace = content.Substring(0, content.Length - trimmedStart.Length);
allowedChildrenString)); var errorStart = SourceLocationTracker.Advance(child.Start, whitespace);
var length = trimmedStart.TrimEnd().Length;
var allowedChildren = _currentTagHelperTracker.AllowedChildren;
var allowedChildrenString = string.Join(", ", allowedChildren);
errorSink.OnError(
RazorDiagnosticFactory.CreateTagHelper_CannotHaveNonTagContent(
new SourceSpan(errorStart, length),
_currentTagHelperTracker.TagName,
allowedChildrenString));
}
} }
} }
} }
@ -817,6 +826,18 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
return relevantSymbol.Type == HtmlSymbolType.ForwardSlash; return relevantSymbol.Type == HtmlSymbolType.ForwardSlash;
} }
internal static bool IsComment(Span span)
{
Block currentBlock = span.Parent;
while (currentBlock != null && currentBlock.Type != BlockKindInternal.Comment && currentBlock.Type != BlockKindInternal.HtmlComment)
{
currentBlock = currentBlock.Parent;
}
return currentBlock != null;
}
private static void EnsureTagBlock(Block tagBlock) private static void EnsureTagBlock(Block tagBlock)
{ {
Debug.Assert(tagBlock.Type == BlockKindInternal.Tag); Debug.Assert(tagBlock.Type == BlockKindInternal.Tag);

View File

@ -109,6 +109,52 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
return symbols[count]; return symbols[count];
} }
/// <summary>
/// Looks forward until the specified condition is met.
/// </summary>
/// <param name="condition">A predicate accepting the symbol being evaluated and the list of symbols which have been looped through.</param>
/// <returns>true, if the condition was met. false - if the condition wasn't met and the last symbol has already been processed.</returns>
/// <remarks>The list of previous symbols is passed in the reverse order. So the last processed element will be the first one in the list.</remarks>
protected bool LookaheadUntil(Func<TSymbol, IEnumerable<TSymbol>, bool> condition)
{
if (condition == null)
{
throw new ArgumentNullException(nameof(condition));
}
var matchFound = false;
var symbols = new List<TSymbol>();
symbols.Add(CurrentSymbol);
while (true)
{
if (!NextToken())
{
break;
}
symbols.Add(CurrentSymbol);
if (condition(CurrentSymbol, symbols))
{
matchFound = true;
break;
}
}
// Restore Tokenizer's location to where it was pointing before the look-ahead.
for (var i = symbols.Count - 1; i >= 0; i--)
{
PutBack(symbols[i]);
}
// The PutBacks above will set CurrentSymbol to null. EnsureCurrent will set our CurrentSymbol to the
// next symbol.
EnsureCurrent();
return matchFound;
}
protected internal bool NextToken() protected internal bool NextToken()
{ {
PreviousSymbol = CurrentSymbol; PreviousSymbol = CurrentSymbol;
@ -254,12 +300,21 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
protected internal bool NextIs(Func<TSymbol, bool> condition) protected internal bool NextIs(Func<TSymbol, bool> condition)
{ {
var cur = CurrentSymbol; var cur = CurrentSymbol;
NextToken(); if (NextToken())
var result = condition(CurrentSymbol); {
PutCurrentBack(); var result = condition(CurrentSymbol);
PutBack(cur); PutCurrentBack();
EnsureCurrent(); PutBack(cur);
return result; EnsureCurrent();
return result;
}
else
{
PutBack(cur);
EnsureCurrent();
}
return false;
} }
protected internal bool Was(TSymbolType type) protected internal bool Was(TSymbolType type)
@ -621,4 +676,4 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
Initialize(Span); Initialize(Span);
} }
} }
} }

View File

@ -8,26 +8,33 @@ namespace Microsoft.AspNetCore.Razor.Language
public static RazorParserFeatureFlags Create(RazorLanguageVersion version) public static RazorParserFeatureFlags Create(RazorLanguageVersion version)
{ {
var allowMinimizedBooleanTagHelperAttributes = false; var allowMinimizedBooleanTagHelperAttributes = false;
var allowHtmlCommentsInTagHelpers = false;
if (version.CompareTo(RazorLanguageVersion.Version_2_1) >= 0) if (version.CompareTo(RazorLanguageVersion.Version_2_1) >= 0)
{ {
// Added in 2.1 // Added in 2.1
allowMinimizedBooleanTagHelperAttributes = true; allowMinimizedBooleanTagHelperAttributes = true;
allowHtmlCommentsInTagHelpers = true;
} }
return new DefaultRazorParserFeatureFlags(allowMinimizedBooleanTagHelperAttributes); return new DefaultRazorParserFeatureFlags(allowMinimizedBooleanTagHelperAttributes, allowHtmlCommentsInTagHelpers);
} }
public abstract bool AllowMinimizedBooleanTagHelperAttributes { get; } public abstract bool AllowMinimizedBooleanTagHelperAttributes { get; }
public abstract bool AllowHtmlCommentsInTagHelpers { get; }
private class DefaultRazorParserFeatureFlags : RazorParserFeatureFlags private class DefaultRazorParserFeatureFlags : RazorParserFeatureFlags
{ {
public DefaultRazorParserFeatureFlags(bool allowMinimizedBooleanTagHelperAttributes) public DefaultRazorParserFeatureFlags(bool allowMinimizedBooleanTagHelperAttributes, bool allowHtmlCommentsInTagHelpers)
{ {
AllowMinimizedBooleanTagHelperAttributes = allowMinimizedBooleanTagHelperAttributes; AllowMinimizedBooleanTagHelperAttributes = allowMinimizedBooleanTagHelperAttributes;
AllowHtmlCommentsInTagHelpers = allowHtmlCommentsInTagHelpers;
} }
public override bool AllowMinimizedBooleanTagHelperAttributes { get; } public override bool AllowMinimizedBooleanTagHelperAttributes { get; }
public override bool AllowHtmlCommentsInTagHelpers { get; }
} }
} }
} }

View File

@ -19,6 +19,8 @@ namespace Microsoft.VisualStudio.Editor.Razor
// Special // Special
Comment, Comment,
Tag Tag,
HtmlComment
} }
} }

View File

@ -367,7 +367,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
{ {
// Act & Assert // Act & Assert
ParseDocumentTest( ParseDocumentTest(
"@section foo " "@section foo "
+ Environment.NewLine + Environment.NewLine
+ Environment.NewLine + Environment.NewLine
+ Environment.NewLine + Environment.NewLine
@ -606,7 +606,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
Factory.Span(SpanKindInternal.Markup, " ", CSharpSymbolType.WhiteSpace).Accepts(AcceptedCharactersInternal.AllWhiteSpace), Factory.Span(SpanKindInternal.Markup, " ", CSharpSymbolType.WhiteSpace).Accepts(AcceptedCharactersInternal.AllWhiteSpace),
Factory.MetaCode("{").AutoCompleteWith(null, atEndOfSpan: true).Accepts(AcceptedCharactersInternal.None), Factory.MetaCode("{").AutoCompleteWith(null, atEndOfSpan: true).Accepts(AcceptedCharactersInternal.None),
new MarkupBlock( new MarkupBlock(
Factory.Markup("<!-- -->")), BlockFactory.HtmlCommentBlock(" "),
Factory.EmptyHtml()),
Factory.MetaCode("}").Accepts(AcceptedCharactersInternal.None)), Factory.MetaCode("}").Accepts(AcceptedCharactersInternal.None)),
Factory.EmptyHtml())); Factory.EmptyHtml()));
} }
@ -630,7 +631,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
Factory.Span(SpanKindInternal.Markup, " ", CSharpSymbolType.WhiteSpace).Accepts(AcceptedCharactersInternal.AllWhiteSpace), Factory.Span(SpanKindInternal.Markup, " ", CSharpSymbolType.WhiteSpace).Accepts(AcceptedCharactersInternal.AllWhiteSpace),
Factory.MetaCode("{").AutoCompleteWith(null, atEndOfSpan: true).Accepts(AcceptedCharactersInternal.None), Factory.MetaCode("{").AutoCompleteWith(null, atEndOfSpan: true).Accepts(AcceptedCharactersInternal.None),
new MarkupBlock( new MarkupBlock(
Factory.Markup("<!-- > \" '-->")), BlockFactory.HtmlCommentBlock(" > \" '"),
Factory.EmptyHtml()),
Factory.MetaCode("}").Accepts(AcceptedCharactersInternal.None)), Factory.MetaCode("}").Accepts(AcceptedCharactersInternal.None)),
Factory.EmptyHtml())); Factory.EmptyHtml()));
} }
@ -655,7 +657,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
Factory.Markup(Environment.NewLine), Factory.Markup(Environment.NewLine),
new MarkupTagBlock( new MarkupTagBlock(
Factory.Markup("<a" + Environment.NewLine)), Factory.Markup("<a" + Environment.NewLine)),
Factory.Markup("<!-- > \" '-->")), BlockFactory.HtmlCommentBlock(" > \" '"),
Factory.EmptyHtml()),
Factory.MetaCode("}").Accepts(AcceptedCharactersInternal.None)), Factory.MetaCode("}").Accepts(AcceptedCharactersInternal.None)),
Factory.EmptyHtml())); Factory.EmptyHtml()));
} }
@ -760,4 +763,4 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
ParseDocumentTest(input, new[] { SectionDirective.Directive }, (Block)expected); ParseDocumentTest(input, new[] { SectionDirective.Directive }, (Block)expected);
} }
} }
} }

View File

@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
Factory.Code(Environment.NewLine).AsStatement().AutoCompleteWith(null), Factory.Code(Environment.NewLine).AsStatement().AutoCompleteWith(null),
new MarkupBlock( new MarkupBlock(
Factory.Markup(" "), Factory.Markup(" "),
Factory.Markup("<!-- Hello, I'm a comment that shouldn't break razor --->").Accepts(AcceptedCharactersInternal.None), BlockFactory.HtmlCommentBlock(" Hello, I'm a comment that shouldn't break razor -"),
Factory.Markup(Environment.NewLine).Accepts(AcceptedCharactersInternal.None)), Factory.Markup(Environment.NewLine).Accepts(AcceptedCharactersInternal.None)),
Factory.EmptyCSharp().AsStatement(), Factory.EmptyCSharp().AsStatement(),
Factory.MetaCode("}").Accepts(AcceptedCharactersInternal.None)), Factory.MetaCode("}").Accepts(AcceptedCharactersInternal.None)),
@ -333,7 +333,13 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
[Fact] [Fact]
public void ParseBlockSupportsCommentAsBlock() public void ParseBlockSupportsCommentAsBlock()
{ {
SingleSpanBlockTest("<!-- foo -->", BlockKindInternal.Markup, SpanKindInternal.Markup, acceptedCharacters: AcceptedCharactersInternal.None); ParseBlockTest("<!-- foo -->", new MarkupBlock(BlockFactory.HtmlCommentBlock(" foo ")));
}
[Fact]
public void ParseBlockSupportsCommentWithExtraDashAsBlock()
{
ParseBlockTest("<!-- foo --->", new MarkupBlock(BlockFactory.HtmlCommentBlock(" foo -")));
} }
[Fact] [Fact]
@ -344,8 +350,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
new MarkupTagBlock( new MarkupTagBlock(
Factory.Markup("<foo>").Accepts(AcceptedCharactersInternal.None)), Factory.Markup("<foo>").Accepts(AcceptedCharactersInternal.None)),
Factory.Markup("bar"), Factory.Markup("bar"),
Factory.Markup("<!-- zoop -->").Accepts(AcceptedCharactersInternal.None), BlockFactory.HtmlCommentBlock(" zoop "),
Factory.Markup("baz"), Factory.Markup("baz").Accepts(AcceptedCharactersInternal.None),
new MarkupTagBlock( new MarkupTagBlock(
Factory.Markup("</foo>").Accepts(AcceptedCharactersInternal.None)))); Factory.Markup("</foo>").Accepts(AcceptedCharactersInternal.None))));
} }
@ -355,7 +361,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
get get
{ {
var factory = new SpanFactory(); var factory = new SpanFactory();
var blockFactory = new BlockFactory(factory);
return new TheoryData<string, MarkupBlock> return new TheoryData<string, MarkupBlock>
{ {
{ {
@ -363,7 +369,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
new MarkupBlock( new MarkupBlock(
new MarkupTagBlock( new MarkupTagBlock(
factory.Markup("<div>").Accepts(AcceptedCharactersInternal.None)), factory.Markup("<div>").Accepts(AcceptedCharactersInternal.None)),
factory.Markup("<!--- Hello World --->").Accepts(AcceptedCharactersInternal.None), blockFactory.HtmlCommentBlock("- Hello World -"),
new MarkupTagBlock( new MarkupTagBlock(
factory.Markup("</div>").Accepts(AcceptedCharactersInternal.None))) factory.Markup("</div>").Accepts(AcceptedCharactersInternal.None)))
}, },
@ -372,7 +378,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
new MarkupBlock( new MarkupBlock(
new MarkupTagBlock( new MarkupTagBlock(
factory.Markup("<div>").Accepts(AcceptedCharactersInternal.None)), factory.Markup("<div>").Accepts(AcceptedCharactersInternal.None)),
factory.Markup("<!---- Hello World ---->").Accepts(AcceptedCharactersInternal.None), blockFactory.HtmlCommentBlock("-- Hello World --"),
new MarkupTagBlock( new MarkupTagBlock(
factory.Markup("</div>").Accepts(AcceptedCharactersInternal.None))) factory.Markup("</div>").Accepts(AcceptedCharactersInternal.None)))
}, },
@ -381,7 +387,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
new MarkupBlock( new MarkupBlock(
new MarkupTagBlock( new MarkupTagBlock(
factory.Markup("<div>").Accepts(AcceptedCharactersInternal.None)), factory.Markup("<div>").Accepts(AcceptedCharactersInternal.None)),
factory.Markup("<!----- Hello World ----->").Accepts(AcceptedCharactersInternal.None), blockFactory.HtmlCommentBlock("--- Hello World ---"),
new MarkupTagBlock( new MarkupTagBlock(
factory.Markup("</div>").Accepts(AcceptedCharactersInternal.None))) factory.Markup("</div>").Accepts(AcceptedCharactersInternal.None)))
}, },
@ -390,7 +396,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
new MarkupBlock( new MarkupBlock(
new MarkupTagBlock( new MarkupTagBlock(
factory.Markup("<div>").Accepts(AcceptedCharactersInternal.None)), factory.Markup("<div>").Accepts(AcceptedCharactersInternal.None)),
factory.Markup("<!----- Hello < --- > World </div> ----->").Accepts(AcceptedCharactersInternal.None), blockFactory.HtmlCommentBlock("--- Hello < --- > World </div> ---"),
new MarkupTagBlock( new MarkupTagBlock(
factory.Markup("</div>").Accepts(AcceptedCharactersInternal.None))) factory.Markup("</div>").Accepts(AcceptedCharactersInternal.None)))
}, },
@ -410,19 +416,22 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
[Fact] [Fact]
public void ParseBlockProperlyBalancesCommentStartAndEndTags() public void ParseBlockProperlyBalancesCommentStartAndEndTags()
{ {
SingleSpanBlockTest("<!--<foo></bar>-->", BlockKindInternal.Markup, SpanKindInternal.Markup, acceptedCharacters: AcceptedCharactersInternal.None); ParseBlockTest("<!--<foo></bar>-->", new MarkupBlock(BlockFactory.HtmlCommentBlock("<foo></bar>")));
} }
[Fact] [Fact]
public void ParseBlockTerminatesAtEOFWhenParsingComment() public void ParseBlockTerminatesAtEOFWhenParsingComment()
{ {
SingleSpanBlockTest("<!--<foo>", "<!--<foo>", BlockKindInternal.Markup, SpanKindInternal.Markup); ParseBlockTest(
"<!--<foo>",
new MarkupBlock(
Factory.Markup("<!--<foo>").Accepts(AcceptedCharactersInternal.None)));
} }
[Fact] [Fact]
public void ParseBlockOnlyTerminatesCommentOnFullEndSequence() public void ParseBlockOnlyTerminatesCommentOnFullEndSequence()
{ {
SingleSpanBlockTest("<!--<foo>--</bar>-->", BlockKindInternal.Markup, SpanKindInternal.Markup, acceptedCharacters: AcceptedCharactersInternal.None); ParseBlockTest("<!--<foo>--</bar>-->", new MarkupBlock(BlockFactory.HtmlCommentBlock("<foo>--</bar>")));
} }
[Fact] [Fact]
@ -432,8 +441,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
new MarkupBlock( new MarkupBlock(
new MarkupTagBlock( new MarkupTagBlock(
Factory.Markup("<foo>").Accepts(AcceptedCharactersInternal.None)), Factory.Markup("<foo>").Accepts(AcceptedCharactersInternal.None)),
Factory.Markup("<!--<foo></bar-->").Accepts(AcceptedCharactersInternal.None), BlockFactory.HtmlCommentBlock("<foo></bar"),
Factory.Markup("-->"), Factory.Markup("-->").Accepts(AcceptedCharactersInternal.None),
new MarkupTagBlock( new MarkupTagBlock(
Factory.Markup("</foo>").Accepts(AcceptedCharactersInternal.None)))); Factory.Markup("</foo>").Accepts(AcceptedCharactersInternal.None))));
} }
@ -645,4 +654,4 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
Factory.Markup("</script>").Accepts(AcceptedCharactersInternal.None)))); Factory.Markup("</script>").Accepts(AcceptedCharactersInternal.None))));
} }
} }
} }

View File

@ -214,7 +214,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
[Fact] [Fact]
public void ParseDocumentReturnsOneMarkupSegmentIfNoCodeBlocksEncountered() public void ParseDocumentReturnsOneMarkupSegmentIfNoCodeBlocksEncountered()
{ {
SingleSpanDocumentTest("Foo Baz<!--Foo-->Bar<!--F> Qux", BlockKindInternal.Markup, SpanKindInternal.Markup); ParseDocumentTest("Foo Baz<!--Foo-->Bar<!--F> Qux",
new MarkupBlock(
Factory.Markup("Foo Baz"),
BlockFactory.HtmlCommentBlock("Foo"),
Factory.Markup("Bar"),
Factory.Markup("<!--F> Qux")));
} }
[Fact] [Fact]
@ -795,4 +800,4 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
ParseDocumentTest("<span foo='@ @' />", expected, expectedErrors); ParseDocumentTest("<span foo='@ @' />", expected, expectedErrors);
} }
} }
} }

View File

@ -0,0 +1,226 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Razor.Language.Legacy;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Test.Legacy
{
public class HtmlMarkupParserTests
{
private static readonly HtmlSymbol doubleHyphenSymbol = new HtmlSymbol("--", HtmlSymbolType.DoubleHyphen);
public static IEnumerable<object[]> NonDashSymbols
{
get
{
yield return new[] { new HtmlSymbol("--", HtmlSymbolType.DoubleHyphen) };
yield return new[] { new HtmlSymbol("asdf", HtmlSymbolType.Text) };
yield return new[] { new HtmlSymbol(">", HtmlSymbolType.CloseAngle) };
yield return new[] { new HtmlSymbol("<", HtmlSymbolType.OpenAngle) };
yield return new[] { new HtmlSymbol("!", HtmlSymbolType.Bang) };
}
}
[Theory]
[MemberData(nameof(NonDashSymbols))]
public void IsHyphen_ReturnsFalseForNonDashSymbol(object symbol)
{
// Arrange
var convertedSymbol = (HtmlSymbol)symbol;
// Act & Assert
Assert.False(HtmlMarkupParser.IsHyphen(convertedSymbol));
}
[Fact]
public void IsHyphen_ReturnsTrueForADashSymbol()
{
// Arrange
var dashSymbol = new HtmlSymbol("-", HtmlSymbolType.Text);
// Act & Assert
Assert.True(HtmlMarkupParser.IsHyphen(dashSymbol));
}
[Fact]
public void AcceptAllButLastDoubleHypens_ReturnsTheOnlyDoubleHyphenSymbol()
{
// Arrange
var sut = CreateTestParserForContent("-->");
// Act
var symbol = sut.AcceptAllButLastDoubleHyphens();
// Assert
Assert.Equal(doubleHyphenSymbol, symbol);
Assert.True(sut.At(HtmlSymbolType.CloseAngle));
Assert.Equal(doubleHyphenSymbol, sut.PreviousSymbol);
}
[Fact]
public void AcceptAllButLastDoubleHypens_ReturnsTheDoubleHyphenSymbolAfterAcceptingTheDash()
{
// Arrange
var sut = CreateTestParserForContent("--->");
// Act
var symbol = sut.AcceptAllButLastDoubleHyphens();
// Assert
Assert.Equal(doubleHyphenSymbol, symbol);
Assert.True(sut.At(HtmlSymbolType.CloseAngle));
Assert.True(HtmlMarkupParser.IsHyphen(sut.PreviousSymbol));
}
[Fact]
public void IsHtmlCommentAhead_ReturnsTrueForEmptyCommentTag()
{
// Arrange
var sut = CreateTestParserForContent("---->");
// Act & Assert
Assert.True(sut.IsHtmlCommentAhead());
}
[Fact]
public void IsHtmlCommentAhead_ReturnsTrueForValidCommentTag()
{
// Arrange
var sut = CreateTestParserForContent("-- Some comment content in here -->");
// Act & Assert
Assert.True(sut.IsHtmlCommentAhead());
}
[Fact]
public void IsHtmlCommentAhead_ReturnsTrueForValidCommentTagWithExtraDashesAtClosingTag()
{
// Arrange
var sut = CreateTestParserForContent("-- Some comment content in here ----->");
// Act & Assert
Assert.True(sut.IsHtmlCommentAhead());
}
[Fact]
public void IsHtmlCommentAhead_ReturnsFalseForContentWithBadEndingAndExtraDash()
{
// Arrange
var sut = CreateTestParserForContent("-- Some comment content in here <!--->");
// Act & Assert
Assert.False(sut.IsHtmlCommentAhead());
}
[Fact]
public void IsHtmlCommentAhead_ReturnsTrueForValidCommentTagWithExtraInfoAfter()
{
// Arrange
var sut = CreateTestParserForContent("-- comment --> the first part is a valid comment without the Open angle and bang symbols");
// Act & Assert
Assert.True(sut.IsHtmlCommentAhead());
}
[Fact]
public void IsHtmlCommentAhead_ReturnsFalseForNotClosedComment()
{
// Arrange
var sut = CreateTestParserForContent("-- not closed comment");
// Act & Assert
Assert.False(sut.IsHtmlCommentAhead());
}
[Fact]
public void IsHtmlCommentAhead_ReturnsFalseForCommentWithoutLastClosingAngle()
{
// Arrange
var sut = CreateTestParserForContent("-- not closed comment--");
// Act & Assert
Assert.False(sut.IsHtmlCommentAhead());
}
[Fact]
public void IsHtmlCommentAhead_ReturnsTrueForCommentWithCodeInside()
{
// Arrange
var sut = CreateTestParserForContent("-- not closed @DateTime.Now comment-->");
// Act & Assert
Assert.True(sut.IsHtmlCommentAhead());
}
[Fact]
public void IsCommentContentEndingInvalid_ReturnsFalseForAllowedContent()
{
// Arrange
var expectedSymbol1 = new HtmlSymbol("a", HtmlSymbolType.Text);
var sequence = Enumerable.Range((int)'a', 26).Select(item => new HtmlSymbol(((char)item).ToString(), HtmlSymbolType.Text));
// Act & Assert
Assert.False(HtmlMarkupParser.IsCommentContentEndingInvalid(sequence));
}
[Fact]
public void IsCommentContentEndingInvalid_ReturnsTrueForDisallowedContent()
{
// Arrange
var expectedSymbol1 = new HtmlSymbol("a", HtmlSymbolType.Text);
var sequence = new[] { new HtmlSymbol("<", HtmlSymbolType.OpenAngle), new HtmlSymbol("!", HtmlSymbolType.Bang), new HtmlSymbol("-", HtmlSymbolType.Text) };
// Act & Assert
Assert.True(HtmlMarkupParser.IsCommentContentEndingInvalid(sequence));
}
[Fact]
public void IsCommentContentEndingInvalid_ReturnsFalseForEmptyContent()
{
// Arrange
var expectedSymbol1 = new HtmlSymbol("a", HtmlSymbolType.Text);
var sequence = Array.Empty<HtmlSymbol>();
// Act & Assert
Assert.False(HtmlMarkupParser.IsCommentContentEndingInvalid(sequence));
}
private class TestHtmlMarkupParser : HtmlMarkupParser
{
public new HtmlSymbol PreviousSymbol
{
get => base.PreviousSymbol;
}
public new bool IsHtmlCommentAhead()
{
return base.IsHtmlCommentAhead();
}
public TestHtmlMarkupParser(ParserContext context) : base(context)
{
this.EnsureCurrent();
}
public new HtmlSymbol AcceptAllButLastDoubleHyphens()
{
return base.AcceptAllButLastDoubleHyphens();
}
public override void BuildSpan(SpanBuilder span, SourceLocation start, string content)
{
base.BuildSpan(span, start, content);
}
}
private static TestHtmlMarkupParser CreateTestParserForContent(string content)
{
var source = TestRazorSourceDocument.Create(content);
var options = RazorParserOptions.CreateDefault();
var context = new ParserContext(source, options);
return new TestHtmlMarkupParser(context);
}
}
}

View File

@ -60,7 +60,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
{ {
ParseBlockTest("<!--Foo--> Bar", ParseBlockTest("<!--Foo--> Bar",
new MarkupBlock( new MarkupBlock(
Factory.Markup("<!--Foo-->").Accepts(AcceptedCharactersInternal.None), BlockFactory.HtmlCommentBlock("Foo"),
Factory.Markup(" ").Accepts(AcceptedCharactersInternal.None))); Factory.Markup(" ").Accepts(AcceptedCharactersInternal.None)));
} }
@ -203,4 +203,4 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
BlockFactory.MarkupTagBlock("</" + tagName))); BlockFactory.MarkupTagBlock("</" + tagName)));
} }
} }
} }

View File

@ -112,13 +112,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
new MarkupBlock( new MarkupBlock(
new MarkupTagBlock( new MarkupTagBlock(
Factory.Markup("<foo>").Accepts(AcceptedCharactersInternal.None)), Factory.Markup("<foo>").Accepts(AcceptedCharactersInternal.None)),
Factory.Markup("<!-- "), BlockFactory.HtmlCommentBlock(Factory, f => new SyntaxTreeNode[] {
new ExpressionBlock( f.Markup(" ").Accepts(AcceptedCharactersInternal.WhiteSpace),
Factory.CodeTransition(), new ExpressionBlock(
Factory.Code("foo") f.CodeTransition(),
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords) f.Code("foo")
.Accepts(AcceptedCharactersInternal.NonWhiteSpace)), .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
Factory.Markup(" -->").Accepts(AcceptedCharactersInternal.None), .Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
f.Markup(" ").Accepts(AcceptedCharactersInternal.WhiteSpace) }),
new MarkupTagBlock( new MarkupTagBlock(
Factory.Markup("</foo>").Accepts(AcceptedCharactersInternal.None)))); Factory.Markup("</foo>").Accepts(AcceptedCharactersInternal.None))));
} }
@ -451,4 +452,4 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
Factory.EmptyHtml())); Factory.EmptyHtml()));
} }
} }
} }

View File

@ -2950,7 +2950,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
intType), intType),
RazorDiagnosticFactory.CreateParsing_TagHelperIndexerAttributeNameMustIncludeKey( RazorDiagnosticFactory.CreateParsing_TagHelperIndexerAttributeNameMustIncludeKey(
new SourceSpan(7, 0, 7, 11), new SourceSpan(7, 0, 7, 11),
"int-prefix-", "int-prefix-",
"input"), "input"),
} }
}, },
@ -2973,7 +2973,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
stringType), stringType),
RazorDiagnosticFactory.CreateParsing_TagHelperIndexerAttributeNameMustIncludeKey( RazorDiagnosticFactory.CreateParsing_TagHelperIndexerAttributeNameMustIncludeKey(
new SourceSpan(7, 0, 7, 14), new SourceSpan(7, 0, 7, 14),
"string-prefix-", "string-prefix-",
"input"), "input"),
} }
}, },
@ -3638,7 +3638,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
RazorDiagnosticFactory.CreateTagHelper_EmptyBoundAttribute( RazorDiagnosticFactory.CreateTagHelper_EmptyBoundAttribute(
new SourceSpan(7, 0, 7, 21), new SourceSpan(7, 0, 7, 21),
"bound-required-string", "bound-required-string",
"input", "input",
stringType), stringType),
} }
}, },
@ -3962,7 +3962,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
.Build(), .Build(),
}; };
var featureFlags = new TestRazorParserFeatureFlags(allowMinimizedBooleanTagHelperAttributes: false); var featureFlags = new TestRazorParserFeatureFlags(allowMinimizedBooleanTagHelperAttributes: false, allowHtmlCommentsInTagHelper: false);
var expectedOutput = new MarkupBlock( var expectedOutput = new MarkupBlock(
new MarkupTagHelperBlock( new MarkupTagHelperBlock(
@ -3994,12 +3994,15 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
private class TestRazorParserFeatureFlags : RazorParserFeatureFlags private class TestRazorParserFeatureFlags : RazorParserFeatureFlags
{ {
public TestRazorParserFeatureFlags(bool allowMinimizedBooleanTagHelperAttributes) public TestRazorParserFeatureFlags(bool allowMinimizedBooleanTagHelperAttributes, bool allowHtmlCommentsInTagHelper)
{ {
AllowMinimizedBooleanTagHelperAttributes = allowMinimizedBooleanTagHelperAttributes; AllowMinimizedBooleanTagHelperAttributes = allowMinimizedBooleanTagHelperAttributes;
AllowHtmlCommentsInTagHelpers = allowHtmlCommentsInTagHelper;
} }
public override bool AllowMinimizedBooleanTagHelperAttributes { get; } public override bool AllowMinimizedBooleanTagHelperAttributes { get; }
public override bool AllowHtmlCommentsInTagHelpers { get; }
} }
} }
} }

View File

@ -254,7 +254,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
var descriptors = new TagHelperDescriptor[] var descriptors = new TagHelperDescriptor[]
{ {
TagHelperDescriptorBuilder.Create("InputTagHelper", "SomeAssembly") TagHelperDescriptorBuilder.Create("InputTagHelper", "SomeAssembly")
.TagMatchingRuleDescriptor(rule => .TagMatchingRuleDescriptor(rule =>
rule rule
.RequireTagName("input") .RequireTagName("input")
.RequireTagStructure(TagStructure.WithoutEndTag)) .RequireTagStructure(TagStructure.WithoutEndTag))
@ -371,7 +371,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
.TagMatchingRuleDescriptor(rule => rule.RequireTagName("strong")) .TagMatchingRuleDescriptor(rule => rule.RequireTagName("strong"))
.Build(), .Build(),
}; };
// Act & Assert // Act & Assert
EvaluateData( EvaluateData(
descriptors, descriptors,
@ -793,7 +793,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
var descriptors = new TagHelperDescriptor[] var descriptors = new TagHelperDescriptor[]
{ {
TagHelperDescriptorBuilder.Create("StrongTagHelper", "SomeAssembly") TagHelperDescriptorBuilder.Create("StrongTagHelper", "SomeAssembly")
.TagMatchingRuleDescriptor(rule => .TagMatchingRuleDescriptor(rule =>
rule rule
.RequireTagName("strong") .RequireTagName("strong")
.RequireAttributeDescriptor(attribute => attribute.Name("required"))) .RequireAttributeDescriptor(attribute => attribute.Name("required")))
@ -830,7 +830,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
.TagMatchingRuleDescriptor(rule => rule.RequireTagName("strong")) .TagMatchingRuleDescriptor(rule => rule.RequireTagName("strong"))
.Build(), .Build(),
TagHelperDescriptorBuilder.Create("BRTagHelper", "SomeAssembly") TagHelperDescriptorBuilder.Create("BRTagHelper", "SomeAssembly")
.TagMatchingRuleDescriptor(rule => .TagMatchingRuleDescriptor(rule =>
rule rule
.RequireTagName("br") .RequireTagName("br")
.RequireTagStructure(TagStructure.WithoutEndTag)) .RequireTagStructure(TagStructure.WithoutEndTag))
@ -1108,6 +1108,243 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
EvaluateData(descriptors, documentContent, (MarkupBlock)expectedOutput, (RazorDiagnostic[])expectedErrors); EvaluateData(descriptors, documentContent, (MarkupBlock)expectedOutput, (RazorDiagnostic[])expectedErrors);
} }
[Fact]
public void Rewrite_AllowsSimpleHtmlCommentsAsChildren()
{
// Arrange
IEnumerable<string> allowedChildren = new List<string> { "b" };
string literal = "asdf";
string commentOutput = "Hello World";
string expectedOutput = $"<p><b>{literal}</b><!--{commentOutput}--></p>";
var pTagHelperBuilder = TagHelperDescriptorBuilder
.Create("PTagHelper", "SomeAssembly")
.TagMatchingRuleDescriptor(rule => rule.RequireTagName("p"));
foreach (var childTag in allowedChildren)
{
pTagHelperBuilder.AllowChildTag(childTag);
}
var descriptors = new TagHelperDescriptor[]
{
pTagHelperBuilder.Build()
};
var factory = new SpanFactory();
var blockFactory = new BlockFactory(factory);
var expectedMarkup = new MarkupBlock(
new MarkupTagHelperBlock("p",
blockFactory.MarkupTagBlock("<b>"),
factory.Markup(literal),
blockFactory.MarkupTagBlock("</b>"),
blockFactory.HtmlCommentBlock(commentOutput)));
// Act & Assert
EvaluateData(
descriptors,
expectedOutput,
expectedMarkup,
Array.Empty<RazorDiagnostic>());
}
[Fact]
public void Rewrite_DoesntAllowSimpleHtmlCommentsAsChildrenWhenFeatureFlagIsOff()
{
// Arrange
Func<string, string, string, int, int, RazorDiagnostic> nestedTagError =
(childName, parentName, allowed, location, length) =>
RazorDiagnosticFactory.CreateTagHelper_InvalidNestedTag(
new SourceSpan(absoluteIndex: location, lineIndex: 0, characterIndex: location, length: length), childName, parentName, allowed);
Func<string, string, int, int, RazorDiagnostic> nestedContentError =
(parentName, allowed, location, length) =>
RazorDiagnosticFactory.CreateTagHelper_CannotHaveNonTagContent(
new SourceSpan(absoluteIndex: location, lineIndex: 0, characterIndex: location, length: length), parentName, allowed);
IEnumerable<string> allowedChildren = new List<string> { "b" };
string comment1 = "Hello";
string expectedOutput = $"<p><!--{comment1}--></p>";
var pTagHelperBuilder = TagHelperDescriptorBuilder
.Create("PTagHelper", "SomeAssembly")
.TagMatchingRuleDescriptor(rule => rule.RequireTagName("p"));
foreach (var childTag in allowedChildren)
{
pTagHelperBuilder.AllowChildTag(childTag);
}
var descriptors = new TagHelperDescriptor[]
{
pTagHelperBuilder.Build()
};
var factory = new SpanFactory();
var blockFactory = new BlockFactory(factory);
var expectedMarkup = new MarkupBlock(
new MarkupTagHelperBlock("p",
blockFactory.HtmlCommentBlock(comment1)));
// Act & Assert
EvaluateData(
descriptors,
expectedOutput,
expectedMarkup,
new[]
{
nestedContentError("p", "b", 3, 4),
nestedContentError("p", "b", 7, 5),
nestedContentError("p", "b", 12, 3),
},
featureFlags: RazorParserFeatureFlags.Create(RazorLanguageVersion.Version_2_0));
}
[Fact]
public void Rewrite_FailsForContentWithCommentsAsChildren()
{
// Arrange
Func<string, string, string, int, int, RazorDiagnostic> nestedTagError =
(childName, parentName, allowed, location, length) =>
RazorDiagnosticFactory.CreateTagHelper_InvalidNestedTag(
new SourceSpan(absoluteIndex: location, lineIndex: 0, characterIndex: location, length: length), childName, parentName, allowed);
Func<string, string, int, int, RazorDiagnostic> nestedContentError =
(parentName, allowed, location, length) =>
RazorDiagnosticFactory.CreateTagHelper_CannotHaveNonTagContent(
new SourceSpan(absoluteIndex: location, lineIndex: 0, characterIndex: location, length: length), parentName, allowed);
IEnumerable<string> allowedChildren = new List<string> { "b" };
string comment1 = "Hello";
string literal = "asdf";
string comment2 = "World";
string expectedOutput = $"<p><!--{comment1}-->{literal}<!--{comment2}--></p>";
var pTagHelperBuilder = TagHelperDescriptorBuilder
.Create("PTagHelper", "SomeAssembly")
.TagMatchingRuleDescriptor(rule => rule.RequireTagName("p"));
foreach (var childTag in allowedChildren)
{
pTagHelperBuilder.AllowChildTag(childTag);
}
var descriptors = new TagHelperDescriptor[]
{
pTagHelperBuilder.Build()
};
var factory = new SpanFactory();
var blockFactory = new BlockFactory(factory);
var expectedMarkup = new MarkupBlock(
new MarkupTagHelperBlock("p",
blockFactory.HtmlCommentBlock(comment1),
factory.Markup(literal),
blockFactory.HtmlCommentBlock(comment2)));
// Act & Assert
EvaluateData(
descriptors,
expectedOutput,
expectedMarkup,
new[]
{
nestedContentError("p", "b", 15, 4),
});
}
[Fact]
public void Rewrite_AllowsRazorCommentsAsChildren()
{
// Arrange
IEnumerable<string> allowedChildren = new List<string> { "b" };
string literal = "asdf";
string commentOutput = $"@*{literal}*@";
string expectedOutput = $"<p><b>{literal}</b>{commentOutput}</p>";
var pTagHelperBuilder = TagHelperDescriptorBuilder
.Create("PTagHelper", "SomeAssembly")
.TagMatchingRuleDescriptor(rule => rule.RequireTagName("p"));
foreach (var childTag in allowedChildren)
{
pTagHelperBuilder.AllowChildTag(childTag);
}
var descriptors = new TagHelperDescriptor[]
{
pTagHelperBuilder.Build()
};
var factory = new SpanFactory();
var blockFactory = new BlockFactory(factory);
var expectedMarkup = new MarkupBlock(
new MarkupTagHelperBlock("p",
blockFactory.MarkupTagBlock("<b>"),
factory.Markup(literal),
blockFactory.MarkupTagBlock("</b>"),
new CommentBlock(
Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition).Accepts(AcceptedCharactersInternal.None),
Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar).Accepts(AcceptedCharactersInternal.None),
Factory.Span(SpanKindInternal.Comment, new HtmlSymbol(literal, HtmlSymbolType.RazorComment)).Accepts(AcceptedCharactersInternal.Any),
Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar).Accepts(AcceptedCharactersInternal.None),
Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition).Accepts(AcceptedCharactersInternal.None))));
// Act & Assert
EvaluateData(
descriptors,
expectedOutput,
expectedMarkup,
Array.Empty<RazorDiagnostic>());
}
[Fact]
public void Rewrite_AllowsRazorMarkupInHtmlComment()
{
// Arrange
IEnumerable<string> allowedChildren = new List<string> { "b" };
string literal = "asdf";
string part1 = "Hello ";
string part2 = "World";
string commentStart = "<!--";
string commentEnd = "-->";
string expectedOutput = $"<p><b>{literal}</b>{commentStart}{part1}@{part2}{commentEnd}</p>";
var pTagHelperBuilder = TagHelperDescriptorBuilder
.Create("PTagHelper", "SomeAssembly")
.TagMatchingRuleDescriptor(rule => rule.RequireTagName("p"));
foreach (var childTag in allowedChildren)
{
pTagHelperBuilder.AllowChildTag(childTag);
}
var descriptors = new TagHelperDescriptor[]
{
pTagHelperBuilder.Build()
};
var factory = new SpanFactory();
var blockFactory = new BlockFactory(factory);
var expectedMarkup = new MarkupBlock(
new MarkupTagHelperBlock("p",
blockFactory.MarkupTagBlock("<b>"),
factory.Markup(literal),
blockFactory.MarkupTagBlock("</b>"),
BlockFactory.HtmlCommentBlock(factory, f => new SyntaxTreeNode[] {
f.Markup(part1).Accepts(AcceptedCharactersInternal.WhiteSpace),
new ExpressionBlock(
f.CodeTransition(),
f.Code(part2)
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharactersInternal.NonWhiteSpace)) })));
// Act & Assert
EvaluateData(
descriptors,
expectedOutput,
expectedMarkup,
Array.Empty<RazorDiagnostic>());
}
[Fact] [Fact]
public void Rewrite_UnderstandsNullTagNameWithAllowedChildrenForCatchAll() public void Rewrite_UnderstandsNullTagNameWithAllowedChildrenForCatchAll()
{ {
@ -1173,7 +1410,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
var descriptors = new TagHelperDescriptor[] var descriptors = new TagHelperDescriptor[]
{ {
TagHelperDescriptorBuilder.Create("InputTagHelper", "SomeAssembly") TagHelperDescriptorBuilder.Create("InputTagHelper", "SomeAssembly")
.TagMatchingRuleDescriptor(rule => .TagMatchingRuleDescriptor(rule =>
rule rule
.RequireTagName("input") .RequireTagName("input")
.RequireTagStructure(TagStructure.WithoutEndTag)) .RequireTagStructure(TagStructure.WithoutEndTag))
@ -1646,7 +1883,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
var descriptors = new TagHelperDescriptor[] var descriptors = new TagHelperDescriptor[]
{ {
TagHelperDescriptorBuilder.Create("pTagHelper", "SomeAssembly") TagHelperDescriptorBuilder.Create("pTagHelper", "SomeAssembly")
.TagMatchingRuleDescriptor(rule => .TagMatchingRuleDescriptor(rule =>
rule rule
.RequireTagName("p") .RequireTagName("p")
.RequireAttributeDescriptor(attribute => attribute.Name("class"))) .RequireAttributeDescriptor(attribute => attribute.Name("class")))
@ -3901,14 +4138,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
get get
{ {
var factory = new SpanFactory(); var factory = new SpanFactory();
var blockFactory = new BlockFactory(factory);
yield return new object[] yield return new object[]
{ {
"<foo><!-- Hello World --></foo>", "<foo><!-- Hello World --></foo>",
new MarkupBlock( new MarkupBlock(
new MarkupTagBlock( new MarkupTagBlock(
factory.Markup("<foo>")), factory.Markup("<foo>")),
factory.Markup("<!-- Hello World -->"), blockFactory.HtmlCommentBlock (" Hello World "),
new MarkupTagBlock( new MarkupTagBlock(
factory.Markup("</foo>"))) factory.Markup("</foo>")))
}; };
@ -3918,13 +4155,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
new MarkupBlock( new MarkupBlock(
new MarkupTagBlock( new MarkupTagBlock(
factory.Markup("<foo>")), factory.Markup("<foo>")),
factory.Markup("<!-- "), BlockFactory.HtmlCommentBlock(factory, f=> new SyntaxTreeNode[]{
new ExpressionBlock( f.Markup(" ").Accepts(AcceptedCharactersInternal.WhiteSpace),
factory.CodeTransition(), new ExpressionBlock(
factory.Code("foo") f.CodeTransition(),
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords) f.Code("foo")
.Accepts(AcceptedCharactersInternal.NonWhiteSpace)), .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
factory.Markup(" -->"), .Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
factory.Markup(" ").Accepts(AcceptedCharactersInternal.WhiteSpace) }),
new MarkupTagBlock( new MarkupTagBlock(
factory.Markup("</foo>"))) factory.Markup("</foo>")))
}; };
@ -4000,8 +4238,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
new ExpressionBlock( new ExpressionBlock(
factory.CodeTransition(), factory.CodeTransition(),
factory.Code("foo") factory.Code("foo")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords) .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharactersInternal.NonWhiteSpace)), .Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
factory.Markup(" ]]>"), factory.Markup(" ]]>"),
new MarkupTagBlock( new MarkupTagBlock(
factory.Markup("</foo>"))) factory.Markup("</foo>")))

View File

@ -3,7 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.Linq;
using System.Text; using System.Text;
using Xunit; using Xunit;
@ -56,6 +56,85 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
Assert.Equal("pre-existing values", tokenizer.Buffer.ToString(), StringComparer.Ordinal); Assert.Equal("pre-existing values", tokenizer.Buffer.ToString(), StringComparer.Ordinal);
} }
[Fact]
public void LookaheadUntil_PassesThePreviousSymbolsInTheSameOrder()
{
// Arrange
var tokenizer = CreateContentTokenizer("asdf--fvd--<");
// Act
var i = 3;
IEnumerable<HtmlSymbol> previousSymbols = null;
var symbolFound = tokenizer.LookaheadUntil((s, p) =>
{
previousSymbols = p;
return --i == 0;
});
// Assert
Assert.Equal(4, previousSymbols.Count());
// For the very first element, there will be no previous items, so null is expected
var orderIndex = 0;
Assert.Null(previousSymbols.ElementAt(orderIndex++));
Assert.Equal(new HtmlSymbol("asdf", HtmlSymbolType.Text), previousSymbols.ElementAt(orderIndex++));
Assert.Equal(new HtmlSymbol("--", HtmlSymbolType.DoubleHyphen), previousSymbols.ElementAt(orderIndex++));
Assert.Equal(new HtmlSymbol("fvd", HtmlSymbolType.Text), previousSymbols.ElementAt(orderIndex++));
}
[Fact]
public void LookaheadUntil_ReturnsFalseAfterIteratingOverAllSymbolsIfConditionIsNotMet()
{
// Arrange
var tokenizer = CreateContentTokenizer("asdf--fvd");
// Act
var symbols = new Stack<HtmlSymbol>();
var symbolFound = tokenizer.LookaheadUntil((s, p) =>
{
symbols.Push(s);
return false;
});
// Assert
Assert.False(symbolFound);
Assert.Equal(3, symbols.Count);
Assert.Equal(new HtmlSymbol("fvd", HtmlSymbolType.Text), symbols.Pop());
Assert.Equal(new HtmlSymbol("--", HtmlSymbolType.DoubleHyphen), symbols.Pop());
Assert.Equal(new HtmlSymbol("asdf", HtmlSymbolType.Text), symbols.Pop());
}
[Fact]
public void LookaheadUntil_ReturnsTrueAndBreaksIteration()
{
// Arrange
var tokenizer = CreateContentTokenizer("asdf--fvd");
// Act
var symbols = new Stack<HtmlSymbol>();
var symbolFound = tokenizer.LookaheadUntil((s, p) =>
{
symbols.Push(s);
return s.Type == HtmlSymbolType.DoubleHyphen;
});
// Assert
Assert.True(symbolFound);
Assert.Equal(2, symbols.Count);
Assert.Equal(new HtmlSymbol("--", HtmlSymbolType.DoubleHyphen), symbols.Pop());
Assert.Equal(new HtmlSymbol("asdf", HtmlSymbolType.Text), symbols.Pop());
}
private static TestTokenizerBackedParser CreateContentTokenizer(string content)
{
var source = TestRazorSourceDocument.Create(content);
var options = RazorParserOptions.CreateDefault();
var context = new ParserContext(source, options);
var tokenizer = new TestTokenizerBackedParser(HtmlLanguageCharacteristics.Instance, context);
return tokenizer;
}
private class ExposedTokenizer : Tokenizer<CSharpSymbol, CSharpSymbolType> private class ExposedTokenizer : Tokenizer<CSharpSymbol, CSharpSymbolType>
{ {
public ExposedTokenizer(string input) public ExposedTokenizer(string input)
@ -116,5 +195,27 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
throw new NotImplementedException(); throw new NotImplementedException();
} }
} }
private class TestTokenizerBackedParser : TokenizerBackedParser<HtmlTokenizer, HtmlSymbol, HtmlSymbolType>
{
internal TestTokenizerBackedParser(LanguageCharacteristics<HtmlTokenizer, HtmlSymbol, HtmlSymbolType> language, ParserContext context) : base(language, context)
{
}
public override void ParseBlock()
{
throw new NotImplementedException();
}
protected override bool SymbolTypeEquals(HtmlSymbolType x, HtmlSymbolType y)
{
throw new NotImplementedException();
}
internal new bool LookaheadUntil(Func<HtmlSymbol, IEnumerable<HtmlSymbol>, bool> condition)
{
return base.LookaheadUntil(condition);
}
}
} }
} }

View File

@ -16,6 +16,7 @@ namespace Microsoft.AspNetCore.Razor.Language
// Assert // Assert
Assert.True(context.AllowMinimizedBooleanTagHelperAttributes); Assert.True(context.AllowMinimizedBooleanTagHelperAttributes);
Assert.True(context.AllowHtmlCommentsInTagHelpers);
} }
[Fact] [Fact]
@ -26,6 +27,7 @@ namespace Microsoft.AspNetCore.Razor.Language
// Assert // Assert
Assert.False(context.AllowMinimizedBooleanTagHelperAttributes); Assert.False(context.AllowMinimizedBooleanTagHelperAttributes);
Assert.False(context.AllowHtmlCommentsInTagHelpers);
} }
} }
} }

View File

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.AspNetCore.Razor.Language.Legacy;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Test
{
public class TagHelperParseTreeRewriterTests
{
public void IsComment_ReturnsTrueForSpanInHtmlCommentBlock()
{
// Arrange
SpanFactory spanFactory = new SpanFactory();
Span content = spanFactory.Markup("<!-- comment -->");
Block commentBlock = new HtmlCommentBlock(content);
// Act
bool actualResult = TagHelperParseTreeRewriter.IsComment(content);
// Assert
Assert.True(actualResult);
}
}
}

View File

@ -10,7 +10,10 @@ Document -
IntermediateToken - - CSharp - #pragma warning restore 0414 IntermediateToken - - CSharp - #pragma warning restore 0414
MethodDeclaration - - public async - System.Threading.Tasks.Task - ExecuteAsync MethodDeclaration - - public async - System.Threading.Tasks.Task - ExecuteAsync
HtmlContent - (0:0,0 [45] HtmlCommentWithQuote_Double.cshtml) HtmlContent - (0:0,0 [45] HtmlCommentWithQuote_Double.cshtml)
IntermediateToken - (0:0,0 [12] HtmlCommentWithQuote_Double.cshtml) - Html - <!-- " -->\n IntermediateToken - (0:0,0 [4] HtmlCommentWithQuote_Double.cshtml) - Html - <!--
IntermediateToken - (4:0,4 [3] HtmlCommentWithQuote_Double.cshtml) - Html - "
IntermediateToken - (7:0,7 [3] HtmlCommentWithQuote_Double.cshtml) - Html - -->
IntermediateToken - (10:0,10 [2] HtmlCommentWithQuote_Double.cshtml) - Html - \n
IntermediateToken - (12:1,0 [4] HtmlCommentWithQuote_Double.cshtml) - Html - <img IntermediateToken - (12:1,0 [4] HtmlCommentWithQuote_Double.cshtml) - Html - <img
IntermediateToken - (16:1,4 [26] HtmlCommentWithQuote_Double.cshtml) - Html - src="~/images/submit.png" IntermediateToken - (16:1,4 [26] HtmlCommentWithQuote_Double.cshtml) - Html - src="~/images/submit.png"
IntermediateToken - (42:1,30 [3] HtmlCommentWithQuote_Double.cshtml) - Html - /> IntermediateToken - (42:1,30 [3] HtmlCommentWithQuote_Double.cshtml) - Html - />

View File

@ -5,7 +5,10 @@ Document -
ClassDeclaration - - public - TestFiles_IntegrationTests_CodeGenerationIntegrationTest_HtmlCommentWithQuote_Double_Runtime - - ClassDeclaration - - public - TestFiles_IntegrationTests_CodeGenerationIntegrationTest_HtmlCommentWithQuote_Double_Runtime - -
MethodDeclaration - - public async - System.Threading.Tasks.Task - ExecuteAsync MethodDeclaration - - public async - System.Threading.Tasks.Task - ExecuteAsync
HtmlContent - (0:0,0 [45] HtmlCommentWithQuote_Double.cshtml) HtmlContent - (0:0,0 [45] HtmlCommentWithQuote_Double.cshtml)
IntermediateToken - (0:0,0 [12] HtmlCommentWithQuote_Double.cshtml) - Html - <!-- " -->\n IntermediateToken - (0:0,0 [4] HtmlCommentWithQuote_Double.cshtml) - Html - <!--
IntermediateToken - (4:0,4 [3] HtmlCommentWithQuote_Double.cshtml) - Html - "
IntermediateToken - (7:0,7 [3] HtmlCommentWithQuote_Double.cshtml) - Html - -->
IntermediateToken - (10:0,10 [2] HtmlCommentWithQuote_Double.cshtml) - Html - \n
IntermediateToken - (12:1,0 [4] HtmlCommentWithQuote_Double.cshtml) - Html - <img IntermediateToken - (12:1,0 [4] HtmlCommentWithQuote_Double.cshtml) - Html - <img
IntermediateToken - (16:1,4 [26] HtmlCommentWithQuote_Double.cshtml) - Html - src="~/images/submit.png" IntermediateToken - (16:1,4 [26] HtmlCommentWithQuote_Double.cshtml) - Html - src="~/images/submit.png"
IntermediateToken - (42:1,30 [3] HtmlCommentWithQuote_Double.cshtml) - Html - /> IntermediateToken - (42:1,30 [3] HtmlCommentWithQuote_Double.cshtml) - Html - />

View File

@ -10,7 +10,10 @@ Document -
IntermediateToken - - CSharp - #pragma warning restore 0414 IntermediateToken - - CSharp - #pragma warning restore 0414
MethodDeclaration - - public async - System.Threading.Tasks.Task - ExecuteAsync MethodDeclaration - - public async - System.Threading.Tasks.Task - ExecuteAsync
HtmlContent - (0:0,0 [45] HtmlCommentWithQuote_Single.cshtml) HtmlContent - (0:0,0 [45] HtmlCommentWithQuote_Single.cshtml)
IntermediateToken - (0:0,0 [12] HtmlCommentWithQuote_Single.cshtml) - Html - <!-- ' -->\n IntermediateToken - (0:0,0 [4] HtmlCommentWithQuote_Single.cshtml) - Html - <!--
IntermediateToken - (4:0,4 [3] HtmlCommentWithQuote_Single.cshtml) - Html - '
IntermediateToken - (7:0,7 [3] HtmlCommentWithQuote_Single.cshtml) - Html - -->
IntermediateToken - (10:0,10 [2] HtmlCommentWithQuote_Single.cshtml) - Html - \n
IntermediateToken - (12:1,0 [4] HtmlCommentWithQuote_Single.cshtml) - Html - <img IntermediateToken - (12:1,0 [4] HtmlCommentWithQuote_Single.cshtml) - Html - <img
IntermediateToken - (16:1,4 [26] HtmlCommentWithQuote_Single.cshtml) - Html - src="~/images/submit.png" IntermediateToken - (16:1,4 [26] HtmlCommentWithQuote_Single.cshtml) - Html - src="~/images/submit.png"
IntermediateToken - (42:1,30 [3] HtmlCommentWithQuote_Single.cshtml) - Html - /> IntermediateToken - (42:1,30 [3] HtmlCommentWithQuote_Single.cshtml) - Html - />

View File

@ -5,7 +5,10 @@ Document -
ClassDeclaration - - public - TestFiles_IntegrationTests_CodeGenerationIntegrationTest_HtmlCommentWithQuote_Single_Runtime - - ClassDeclaration - - public - TestFiles_IntegrationTests_CodeGenerationIntegrationTest_HtmlCommentWithQuote_Single_Runtime - -
MethodDeclaration - - public async - System.Threading.Tasks.Task - ExecuteAsync MethodDeclaration - - public async - System.Threading.Tasks.Task - ExecuteAsync
HtmlContent - (0:0,0 [45] HtmlCommentWithQuote_Single.cshtml) HtmlContent - (0:0,0 [45] HtmlCommentWithQuote_Single.cshtml)
IntermediateToken - (0:0,0 [12] HtmlCommentWithQuote_Single.cshtml) - Html - <!-- ' -->\n IntermediateToken - (0:0,0 [4] HtmlCommentWithQuote_Single.cshtml) - Html - <!--
IntermediateToken - (4:0,4 [3] HtmlCommentWithQuote_Single.cshtml) - Html - '
IntermediateToken - (7:0,7 [3] HtmlCommentWithQuote_Single.cshtml) - Html - -->
IntermediateToken - (10:0,10 [2] HtmlCommentWithQuote_Single.cshtml) - Html - \n
IntermediateToken - (12:1,0 [4] HtmlCommentWithQuote_Single.cshtml) - Html - <img IntermediateToken - (12:1,0 [4] HtmlCommentWithQuote_Single.cshtml) - Html - <img
IntermediateToken - (16:1,4 [26] HtmlCommentWithQuote_Single.cshtml) - Html - src="~/images/submit.png" IntermediateToken - (16:1,4 [26] HtmlCommentWithQuote_Single.cshtml) - Html - src="~/images/submit.png"
IntermediateToken - (42:1,30 [3] HtmlCommentWithQuote_Single.cshtml) - Html - /> IntermediateToken - (42:1,30 [3] HtmlCommentWithQuote_Single.cshtml) - Html - />

View File

@ -1,6 +1,7 @@
// 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;
namespace Microsoft.AspNetCore.Razor.Language.Legacy namespace Microsoft.AspNetCore.Razor.Language.Legacy
@ -55,6 +56,24 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
); );
} }
public HtmlCommentBlock HtmlCommentBlock(string content)
{
return HtmlCommentBlock(_factory, f => new SyntaxTreeNode[] { f.Markup(content).Accepts(AcceptedCharactersInternal.WhiteSpace) });
}
public static HtmlCommentBlock HtmlCommentBlock(SpanFactory factory, Func<SpanFactory, IEnumerable<SyntaxTreeNode>> nodesBuilder = null)
{
var nodes = new List<SyntaxTreeNode>();
nodes.Add(factory.Markup("<!--").Accepts(AcceptedCharactersInternal.None));
if (nodesBuilder != null)
{
nodes.AddRange(nodesBuilder(factory));
}
nodes.Add(factory.Markup("-->").Accepts(AcceptedCharactersInternal.None));
return new HtmlCommentBlock(nodes.ToArray());
}
public Block TagHelperBlock( public Block TagHelperBlock(
string tagName, string tagName,
TagMode tagMode, TagMode tagMode,

View File

@ -217,4 +217,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
{ {
} }
} }
}
internal class HtmlCommentBlock : Block
{
private const BlockKindInternal ThisBlockKind = BlockKindInternal.HtmlComment;
public HtmlCommentBlock(params SyntaxTreeNode[] children)
: base(ThisBlockKind, children, ParentChunkGenerator.Null)
{
}
}
}