Merge pull request #2061 from aspnet/artakm/RestrictChildrenComments

Ignoring HTML comments in tag helper's body. Updated the markup parser to be aware of HTML Comments.
This commit is contained in:
Artak 2018-03-12 11:17:57 -07:00 committed by GitHub
commit a9004e503e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 981 additions and 93 deletions

View File

@ -18,6 +18,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
// Special
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 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 SourceLocation _lastTagStart = SourceLocation.Zero;
private HtmlSymbol _bufferedOpenAngle;
@ -492,33 +495,37 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
if (AcceptAndMoveNext())
{
if (CurrentSymbol.Type == HtmlSymbolType.DoubleHyphen)
if (IsHtmlCommentAhead())
{
AcceptAndMoveNext();
Span.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.Any;
while (!EndOfFile)
using (Context.Builder.StartBlock(BlockKindInternal.HtmlComment))
{
SkipToAndParseCode(HtmlSymbolType.DoubleHyphen);
if (At(HtmlSymbolType.DoubleHyphen))
{
AcceptWhile(HtmlSymbolType.DoubleHyphen);
// Accept the double-hyphen symbol at the beginning of the comment block.
AcceptAndMoveNext();
Output(SpanKindInternal.Markup, AcceptedCharactersInternal.None);
if (At(HtmlSymbolType.Text) &&
string.Equals(CurrentSymbol.Content, "-", StringComparison.Ordinal))
{
AcceptAndMoveNext();
}
Span.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.WhiteSpace;
while (!EndOfFile)
{
SkipToAndParseCode(HtmlSymbolType.DoubleHyphen);
var lastDoubleHyphen = AcceptAllButLastDoubleHyphens();
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();
Output(SpanKindInternal.Markup, AcceptedCharactersInternal.None);
return true;
}
else if (lastDoubleHyphen != null)
{
Accept(lastDoubleHyphen);
}
}
}
return false;
}
else if (CurrentSymbol.Type == HtmlSymbolType.LeftBracket)
{
@ -537,6 +544,138 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
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()
{
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[, <!--.
if (!IsBangEscape(lookahead: 1))
{
if (Lookahead(2)?.Type == HtmlSymbolType.DoubleHyphen)
{
Output(SpanKindInternal.Markup);
}
AcceptAndMoveNext(); // Accept '<'
BangTag();
return;
}

View File

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

View File

@ -490,20 +490,29 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
if (HasAllowedChildren())
{
var content = child.Content;
if (!string.IsNullOrWhiteSpace(content))
var isDisallowedContent = true;
if (_featureFlags.AllowHtmlCommentsInTagHelpers)
{
var trimmedStart = content.TrimStart();
var whitespace = content.Substring(0, content.Length - trimmedStart.Length);
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));
isDisallowedContent = !IsComment(child) && child.Kind != SpanKindInternal.Transition && child.Kind != SpanKindInternal.Code;
}
if (isDisallowedContent)
{
var content = child.Content;
if (!string.IsNullOrWhiteSpace(content))
{
var trimmedStart = content.TrimStart();
var whitespace = content.Substring(0, content.Length - trimmedStart.Length);
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;
}
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)
{
Debug.Assert(tagBlock.Type == BlockKindInternal.Tag);

View File

@ -109,6 +109,52 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
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()
{
PreviousSymbol = CurrentSymbol;
@ -254,12 +300,21 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
protected internal bool NextIs(Func<TSymbol, bool> condition)
{
var cur = CurrentSymbol;
NextToken();
var result = condition(CurrentSymbol);
PutCurrentBack();
PutBack(cur);
EnsureCurrent();
return result;
if (NextToken())
{
var result = condition(CurrentSymbol);
PutCurrentBack();
PutBack(cur);
EnsureCurrent();
return result;
}
else
{
PutBack(cur);
EnsureCurrent();
}
return false;
}
protected internal bool Was(TSymbolType type)

View File

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

View File

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

View File

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

View File

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

View File

@ -214,7 +214,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
[Fact]
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]

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",
new MarkupBlock(
Factory.Markup("<!--Foo-->").Accepts(AcceptedCharactersInternal.None),
BlockFactory.HtmlCommentBlock("Foo"),
Factory.Markup(" ").Accepts(AcceptedCharactersInternal.None)));
}

View File

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

View File

@ -2950,7 +2950,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
intType),
RazorDiagnosticFactory.CreateParsing_TagHelperIndexerAttributeNameMustIncludeKey(
new SourceSpan(7, 0, 7, 11),
"int-prefix-",
"int-prefix-",
"input"),
}
},
@ -2973,7 +2973,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
stringType),
RazorDiagnosticFactory.CreateParsing_TagHelperIndexerAttributeNameMustIncludeKey(
new SourceSpan(7, 0, 7, 14),
"string-prefix-",
"string-prefix-",
"input"),
}
},
@ -3638,7 +3638,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
RazorDiagnosticFactory.CreateTagHelper_EmptyBoundAttribute(
new SourceSpan(7, 0, 7, 21),
"bound-required-string",
"input",
"input",
stringType),
}
},
@ -3962,7 +3962,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
.Build(),
};
var featureFlags = new TestRazorParserFeatureFlags(allowMinimizedBooleanTagHelperAttributes: false);
var featureFlags = new TestRazorParserFeatureFlags(allowMinimizedBooleanTagHelperAttributes: false, allowHtmlCommentsInTagHelper: false);
var expectedOutput = new MarkupBlock(
new MarkupTagHelperBlock(
@ -3994,12 +3994,15 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
private class TestRazorParserFeatureFlags : RazorParserFeatureFlags
{
public TestRazorParserFeatureFlags(bool allowMinimizedBooleanTagHelperAttributes)
public TestRazorParserFeatureFlags(bool allowMinimizedBooleanTagHelperAttributes, bool allowHtmlCommentsInTagHelper)
{
AllowMinimizedBooleanTagHelperAttributes = allowMinimizedBooleanTagHelperAttributes;
AllowHtmlCommentsInTagHelpers = allowHtmlCommentsInTagHelper;
}
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[]
{
TagHelperDescriptorBuilder.Create("InputTagHelper", "SomeAssembly")
.TagMatchingRuleDescriptor(rule =>
.TagMatchingRuleDescriptor(rule =>
rule
.RequireTagName("input")
.RequireTagStructure(TagStructure.WithoutEndTag))
@ -371,7 +371,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
.TagMatchingRuleDescriptor(rule => rule.RequireTagName("strong"))
.Build(),
};
// Act & Assert
EvaluateData(
descriptors,
@ -793,7 +793,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
var descriptors = new TagHelperDescriptor[]
{
TagHelperDescriptorBuilder.Create("StrongTagHelper", "SomeAssembly")
.TagMatchingRuleDescriptor(rule =>
.TagMatchingRuleDescriptor(rule =>
rule
.RequireTagName("strong")
.RequireAttributeDescriptor(attribute => attribute.Name("required")))
@ -830,7 +830,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
.TagMatchingRuleDescriptor(rule => rule.RequireTagName("strong"))
.Build(),
TagHelperDescriptorBuilder.Create("BRTagHelper", "SomeAssembly")
.TagMatchingRuleDescriptor(rule =>
.TagMatchingRuleDescriptor(rule =>
rule
.RequireTagName("br")
.RequireTagStructure(TagStructure.WithoutEndTag))
@ -1108,6 +1108,243 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
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]
public void Rewrite_UnderstandsNullTagNameWithAllowedChildrenForCatchAll()
{
@ -1173,7 +1410,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
var descriptors = new TagHelperDescriptor[]
{
TagHelperDescriptorBuilder.Create("InputTagHelper", "SomeAssembly")
.TagMatchingRuleDescriptor(rule =>
.TagMatchingRuleDescriptor(rule =>
rule
.RequireTagName("input")
.RequireTagStructure(TagStructure.WithoutEndTag))
@ -1646,7 +1883,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
var descriptors = new TagHelperDescriptor[]
{
TagHelperDescriptorBuilder.Create("pTagHelper", "SomeAssembly")
.TagMatchingRuleDescriptor(rule =>
.TagMatchingRuleDescriptor(rule =>
rule
.RequireTagName("p")
.RequireAttributeDescriptor(attribute => attribute.Name("class")))
@ -3901,14 +4138,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
get
{
var factory = new SpanFactory();
var blockFactory = new BlockFactory(factory);
yield return new object[]
{
"<foo><!-- Hello World --></foo>",
new MarkupBlock(
new MarkupTagBlock(
factory.Markup("<foo>")),
factory.Markup("<!-- Hello World -->"),
blockFactory.HtmlCommentBlock (" Hello World "),
new MarkupTagBlock(
factory.Markup("</foo>")))
};
@ -3918,13 +4155,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
new MarkupBlock(
new MarkupTagBlock(
factory.Markup("<foo>")),
factory.Markup("<!-- "),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code("foo")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
factory.Markup(" -->"),
BlockFactory.HtmlCommentBlock(factory, f=> new SyntaxTreeNode[]{
f.Markup(" ").Accepts(AcceptedCharactersInternal.WhiteSpace),
new ExpressionBlock(
f.CodeTransition(),
f.Code("foo")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
factory.Markup(" ").Accepts(AcceptedCharactersInternal.WhiteSpace) }),
new MarkupTagBlock(
factory.Markup("</foo>")))
};
@ -4000,8 +4238,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
new ExpressionBlock(
factory.CodeTransition(),
factory.Code("foo")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
factory.Markup(" ]]>"),
new MarkupTagBlock(
factory.Markup("</foo>")))

View File

@ -3,7 +3,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Xunit;
@ -56,6 +56,85 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
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>
{
public ExposedTokenizer(string input)
@ -116,5 +195,27 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
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.True(context.AllowMinimizedBooleanTagHelperAttributes);
Assert.True(context.AllowHtmlCommentsInTagHelpers);
}
[Fact]
@ -26,6 +27,7 @@ namespace Microsoft.AspNetCore.Razor.Language
// Assert
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
MethodDeclaration - - public async - System.Threading.Tasks.Task - ExecuteAsync
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 - (16:1,4 [26] HtmlCommentWithQuote_Double.cshtml) - Html - src="~/images/submit.png"
IntermediateToken - (42:1,30 [3] HtmlCommentWithQuote_Double.cshtml) - Html - />

View File

@ -5,7 +5,10 @@ Document -
ClassDeclaration - - public - TestFiles_IntegrationTests_CodeGenerationIntegrationTest_HtmlCommentWithQuote_Double_Runtime - -
MethodDeclaration - - public async - System.Threading.Tasks.Task - ExecuteAsync
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 - (16:1,4 [26] HtmlCommentWithQuote_Double.cshtml) - Html - src="~/images/submit.png"
IntermediateToken - (42:1,30 [3] HtmlCommentWithQuote_Double.cshtml) - Html - />

View File

@ -10,7 +10,10 @@ Document -
IntermediateToken - - CSharp - #pragma warning restore 0414
MethodDeclaration - - public async - System.Threading.Tasks.Task - ExecuteAsync
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 - (16:1,4 [26] HtmlCommentWithQuote_Single.cshtml) - Html - src="~/images/submit.png"
IntermediateToken - (42:1,30 [3] HtmlCommentWithQuote_Single.cshtml) - Html - />

View File

@ -5,7 +5,10 @@ Document -
ClassDeclaration - - public - TestFiles_IntegrationTests_CodeGenerationIntegrationTest_HtmlCommentWithQuote_Single_Runtime - -
MethodDeclaration - - public async - System.Threading.Tasks.Task - ExecuteAsync
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 - (16:1,4 [26] HtmlCommentWithQuote_Single.cshtml) - Html - src="~/images/submit.png"
IntermediateToken - (42:1,30 [3] HtmlCommentWithQuote_Single.cshtml) - Html - />

View File

@ -1,6 +1,7 @@
// 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;
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(
string tagName,
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)
{
}
}
}