// 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.Linq;
using Microsoft.AspNetCore.Razor.Language.Extensions;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
public class HtmlToCodeSwitchTest : CsHtmlMarkupParserTestBase
{
[Fact]
public void ParseBlockSwitchesWhenCharacterBeforeSwapIsNonAlphanumeric()
{
ParseBlockTest("
foo#@i
",
new MarkupBlock(
new MarkupTagBlock(
Factory.Markup("").Accepts(AcceptedCharactersInternal.None)),
Factory.Markup("foo#"),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("i").AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
new MarkupTagBlock(
Factory.Markup("
").Accepts(AcceptedCharactersInternal.None))));
}
[Fact]
public void ParseBlockSwitchesToCodeWhenSwapCharacterEncounteredMidTag()
{
ParseBlockTest("",
new MarkupBlock(
new MarkupTagBlock(
Factory.Markup("").Accepts(AcceptedCharactersInternal.None))));
}
[Fact]
public void ParseBlockSwitchesToCodeWhenSwapCharacterEncounteredInAttributeValue()
{
ParseBlockTest("",
new MarkupBlock(
new MarkupTagBlock(
Factory.Markup("(" bar=\"", 4, 0, 4), new LocationTagged("\"", 14, 0, 14)),
Factory.Markup(" bar=\"").With(SpanChunkGenerator.Null),
new MarkupBlock(new DynamicAttributeBlockChunkGenerator(new LocationTagged(string.Empty, 10, 0, 10), 10, 0, 10),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("baz")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharactersInternal.NonWhiteSpace))),
Factory.Markup("\"").With(SpanChunkGenerator.Null)),
Factory.Markup(" />").Accepts(AcceptedCharactersInternal.None))));
}
[Fact]
public void ParseBlockSwitchesToCodeWhenSwapCharacterEncounteredInTagContent()
{
ParseBlockTest("@bar@boz",
new MarkupBlock(
new MarkupTagBlock(
Factory.Markup("").Accepts(AcceptedCharactersInternal.None)),
Factory.EmptyHtml(),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("bar")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
new MarkupTagBlock(
Factory.Markup("").Accepts(AcceptedCharactersInternal.None)),
Factory.EmptyHtml(),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("boz")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
new MarkupTagBlock(
Factory.Markup("").Accepts(AcceptedCharactersInternal.None)),
new MarkupTagBlock(
Factory.Markup("").Accepts(AcceptedCharactersInternal.None))));
}
[Fact]
public void ParseBlockParsesCodeWithinSingleLineMarkup()
{
// TODO: Fix at a later date, HTML should be a tag block: https://github.com/aspnet/Razor/issues/101
ParseBlockTest("@:Foo @Bar Baz" + Environment.NewLine
+ "bork",
new MarkupBlock(
Factory.MarkupTransition(),
Factory.MetaMarkup(":", HtmlSymbolType.Colon),
Factory.Markup("Foo ").With(new SpanEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString)),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("Bar")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
Factory.Markup(" Baz" + Environment.NewLine)
.Accepts(AcceptedCharactersInternal.None)));
}
[Fact]
public void ParseBlockSupportsCodeWithinComment()
{
ParseBlockTest("",
new MarkupBlock(
new MarkupTagBlock(
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("").Accepts(AcceptedCharactersInternal.None))));
}
[Fact]
public void ParseBlockSupportsCodeWithinSGMLDeclaration()
{
ParseBlockTest("",
new MarkupBlock(
new MarkupTagBlock(
Factory.Markup("").Accepts(AcceptedCharactersInternal.None)),
Factory.Markup("").Accepts(AcceptedCharactersInternal.None),
new MarkupTagBlock(
Factory.Markup("").Accepts(AcceptedCharactersInternal.None))));
}
[Fact]
public void ParseBlockSupportsCodeWithinCDataDeclaration()
{
ParseBlockTest("",
new MarkupBlock(
new MarkupTagBlock(
Factory.Markup("").Accepts(AcceptedCharactersInternal.None)),
Factory.Markup("").Accepts(AcceptedCharactersInternal.None),
new MarkupTagBlock(
Factory.Markup("").Accepts(AcceptedCharactersInternal.None))));
}
[Fact]
public void ParseBlockSupportsCodeWithinXMLProcessingInstruction()
{
ParseBlockTest("",
new MarkupBlock(
new MarkupTagBlock(
Factory.Markup("").Accepts(AcceptedCharactersInternal.None)),
Factory.Markup("").Accepts(AcceptedCharactersInternal.None),
new MarkupTagBlock(
Factory.Markup("").Accepts(AcceptedCharactersInternal.None))));
}
[Fact]
public void ParseBlockDoesNotSwitchToCodeOnEmailAddressInText()
{
ParseBlockTest("anurse@microsoft.com",
new MarkupBlock(
new MarkupTagBlock(
Factory.Markup("").Accepts(AcceptedCharactersInternal.None)),
Factory.Markup("anurse@microsoft.com"),
new MarkupTagBlock(
Factory.Markup("").Accepts(AcceptedCharactersInternal.None))));
}
[Fact]
public void ParseBlockDoesNotSwitchToCodeOnEmailAddressInAttribute()
{
ParseBlockTest("Email me",
new MarkupBlock(
new MarkupTagBlock(
Factory.Markup("(" href=\"", 2, 0, 2), new LocationTagged("\"", 36, 0, 36)),
Factory.Markup(" href=\"").With(SpanChunkGenerator.Null),
Factory.Markup("mailto:anurse@microsoft.com")
.With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 9, 0, 9), new LocationTagged("mailto:anurse@microsoft.com", 9, 0, 9))),
Factory.Markup("\"").With(SpanChunkGenerator.Null)),
Factory.Markup(">").Accepts(AcceptedCharactersInternal.None)),
Factory.Markup("Email me"),
new MarkupTagBlock(
Factory.Markup("").Accepts(AcceptedCharactersInternal.None))));
}
[Fact]
public void ParseBlockGivesWhitespacePreceedingAtToCodeIfThereIsNoMarkupOnThatLine()
{
ParseBlockTest(" " + Environment.NewLine
+ " @foreach(var p in Products) {" + Environment.NewLine
+ " - Product: @p.Name
" + Environment.NewLine
+ " }" + Environment.NewLine
+ "
",
new MarkupBlock(
Factory.Markup(" "),
new MarkupTagBlock(
Factory.Markup("").Accepts(AcceptedCharactersInternal.None)),
Factory.Markup(Environment.NewLine),
new StatementBlock(
Factory.Code(" ").AsStatement(),
Factory.CodeTransition(),
Factory.Code("foreach(var p in Products) {" + Environment.NewLine).AsStatement(),
new MarkupBlock(
Factory.Markup(" "),
new MarkupTagBlock(
Factory.Markup("- ").Accepts(AcceptedCharactersInternal.None)),
Factory.Markup("Product: "),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("p.Name")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
new MarkupTagBlock(
Factory.Markup("
").Accepts(AcceptedCharactersInternal.None)),
Factory.Markup(Environment.NewLine).Accepts(AcceptedCharactersInternal.None)),
Factory.Code(" }" + Environment.NewLine).AsStatement().Accepts(AcceptedCharactersInternal.None)),
Factory.Markup(" "),
new MarkupTagBlock(
Factory.Markup("
").Accepts(AcceptedCharactersInternal.None))));
}
[Fact]
public void ParseDocumentGivesWhitespacePreceedingAtToCodeIfThereIsNoMarkupOnThatLine()
{
ParseDocumentTest(" " + Environment.NewLine
+ " @foreach(var p in Products) {" + Environment.NewLine
+ " - Product: @p.Name
" + Environment.NewLine
+ " }" + Environment.NewLine
+ "
",
new MarkupBlock(
Factory.Markup(" "),
new MarkupTagBlock(
Factory.Markup("")),
Factory.Markup(Environment.NewLine),
new StatementBlock(
Factory.Code(" ").AsStatement(),
Factory.CodeTransition(),
Factory.Code("foreach(var p in Products) {" + Environment.NewLine).AsStatement(),
new MarkupBlock(
Factory.Markup(" "),
new MarkupTagBlock(
Factory.Markup("- ").Accepts(AcceptedCharactersInternal.None)),
Factory.Markup("Product: "),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("p.Name")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
new MarkupTagBlock(
Factory.Markup("
").Accepts(AcceptedCharactersInternal.None)),
Factory.Markup(Environment.NewLine).Accepts(AcceptedCharactersInternal.None)),
Factory.Code(" }" + Environment.NewLine).AsStatement().Accepts(AcceptedCharactersInternal.None)),
Factory.Markup(" "),
new MarkupTagBlock(
Factory.Markup("
"))));
}
[Fact]
public void SectionContextGivesWhitespacePreceedingAtToCodeIfThereIsNoMarkupOnThatLine()
{
var sectionDescriptor = SectionDirective.Directive;
ParseDocumentTest("@section foo {" + Environment.NewLine
+ " " + Environment.NewLine
+ " @foreach(var p in Products) {" + Environment.NewLine
+ " - Product: @p.Name
" + Environment.NewLine
+ " }" + Environment.NewLine
+ "
" + Environment.NewLine
+ "}",
new[] { SectionDirective.Directive, },
new MarkupBlock(
Factory.EmptyHtml(),
new DirectiveBlock(new DirectiveChunkGenerator(SectionDirective.Directive),
Factory.CodeTransition(),
Factory.MetaCode("section").Accepts(AcceptedCharactersInternal.None),
Factory.Span(SpanKindInternal.Code, " ", CSharpSymbolType.WhiteSpace).Accepts(AcceptedCharactersInternal.WhiteSpace),
Factory.Span(SpanKindInternal.Code, "foo", CSharpSymbolType.Identifier).AsDirectiveToken(SectionDirective.Directive.Tokens[0]),
Factory.Span(SpanKindInternal.Markup, " ", CSharpSymbolType.WhiteSpace).Accepts(AcceptedCharactersInternal.AllWhiteSpace),
Factory.MetaCode("{").AutoCompleteWith(null, atEndOfSpan: true).Accepts(AcceptedCharactersInternal.None),
new MarkupBlock(
Factory.Markup(Environment.NewLine + " "),
new MarkupTagBlock(
Factory.Markup("")),
Factory.Markup(Environment.NewLine),
new StatementBlock(
Factory.Code(" ").AsStatement(),
Factory.CodeTransition(),
Factory.Code("foreach(var p in Products) {" + Environment.NewLine).AsStatement(),
new MarkupBlock(
Factory.Markup(" "),
new MarkupTagBlock(
Factory.Markup("- ").Accepts(AcceptedCharactersInternal.None)),
Factory.Markup("Product: "),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("p.Name")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
new MarkupTagBlock(
Factory.Markup("
").Accepts(AcceptedCharactersInternal.None)),
Factory.Markup(Environment.NewLine).Accepts(AcceptedCharactersInternal.None)),
Factory.Code(" }" + Environment.NewLine).AsStatement().Accepts(AcceptedCharactersInternal.None)),
Factory.Markup(" "),
new MarkupTagBlock(
Factory.Markup("
")),
Factory.Markup(Environment.NewLine)),
Factory.MetaCode("}").Accepts(AcceptedCharactersInternal.None)),
Factory.EmptyHtml()));
}
[Fact]
public void CSharpCodeParserDoesNotAcceptLeadingOrTrailingWhitespaceInDesignMode()
{
ParseBlockTest(" " + Environment.NewLine
+ " @foreach(var p in Products) {" + Environment.NewLine
+ " - Product: @p.Name
" + Environment.NewLine
+ " }" + Environment.NewLine
+ "
",
new MarkupBlock(
Factory.Markup(" "),
new MarkupTagBlock(
Factory.Markup("").Accepts(AcceptedCharactersInternal.None)),
Factory.Markup(Environment.NewLine + " "),
new StatementBlock(
Factory.CodeTransition(),
Factory.Code($"foreach(var p in Products) {{{Environment.NewLine} ").AsStatement(),
new MarkupBlock(
new MarkupTagBlock(
Factory.Markup("- ").Accepts(AcceptedCharactersInternal.None)),
Factory.Markup("Product: "),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("p.Name").AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
new MarkupTagBlock(
Factory.Markup("
").Accepts(AcceptedCharactersInternal.None))),
Factory.Code(Environment.NewLine + " }").AsStatement().Accepts(AcceptedCharactersInternal.None)),
Factory.Markup(Environment.NewLine + " "),
new MarkupTagBlock(
Factory.Markup("
").Accepts(AcceptedCharactersInternal.None))),
designTime: true);
}
// Tests for "@@" escape sequence:
[Fact]
public void ParseBlockTreatsTwoAtSignsAsEscapeSequence()
{
HtmlParserTestUtils.RunSingleAtEscapeTest(ParseBlockTest);
}
[Fact]
public void ParseBlockTreatsPairsOfAtSignsAsEscapeSequence()
{
HtmlParserTestUtils.RunMultiAtEscapeTest(ParseBlockTest);
}
[Fact]
public void ParseDocumentTreatsTwoAtSignsAsEscapeSequence()
{
HtmlParserTestUtils.RunSingleAtEscapeTest(ParseDocumentTest, lastSpanAcceptedCharacters: AcceptedCharactersInternal.Any);
}
[Fact]
public void ParseDocumentTreatsPairsOfAtSignsAsEscapeSequence()
{
HtmlParserTestUtils.RunMultiAtEscapeTest(ParseDocumentTest, lastSpanAcceptedCharacters: AcceptedCharactersInternal.Any);
}
[Fact]
public void SectionBodyTreatsTwoAtSignsAsEscapeSequence()
{
ParseDocumentTest(
"@section Foo { @@bar }",
new[] { SectionDirective.Directive, },
new MarkupBlock(
Factory.EmptyHtml(),
new DirectiveBlock(new DirectiveChunkGenerator(SectionDirective.Directive),
Factory.CodeTransition(),
Factory.MetaCode("section").Accepts(AcceptedCharactersInternal.None),
Factory.Span(SpanKindInternal.Code, " ", CSharpSymbolType.WhiteSpace).Accepts(AcceptedCharactersInternal.WhiteSpace),
Factory.Span(SpanKindInternal.Code, "Foo", CSharpSymbolType.Identifier).AsDirectiveToken(SectionDirective.Directive.Tokens[0]),
Factory.Span(SpanKindInternal.Markup, " ", CSharpSymbolType.WhiteSpace).Accepts(AcceptedCharactersInternal.AllWhiteSpace),
Factory.MetaCode("{").AutoCompleteWith(null, atEndOfSpan: true).Accepts(AcceptedCharactersInternal.None),
new MarkupBlock(
Factory.Markup(" "),
new MarkupTagBlock(
Factory.Markup("")),
Factory.Markup("@").Hidden(),
Factory.Markup("@bar"),
new MarkupTagBlock(
Factory.Markup("")),
Factory.Markup(" ")),
Factory.MetaCode("}").Accepts(AcceptedCharactersInternal.None)),
Factory.EmptyHtml()));
}
[Fact]
public void SectionBodyTreatsPairsOfAtSignsAsEscapeSequence()
{
ParseDocumentTest(
"@section Foo { @@@@@bar }",
new[] { SectionDirective.Directive, },
new MarkupBlock(
Factory.EmptyHtml(),
new DirectiveBlock(new DirectiveChunkGenerator(SectionDirective.Directive),
Factory.CodeTransition(),
Factory.MetaCode("section").Accepts(AcceptedCharactersInternal.None),
Factory.Span(SpanKindInternal.Code, " ", CSharpSymbolType.WhiteSpace).Accepts(AcceptedCharactersInternal.WhiteSpace),
Factory.Span(SpanKindInternal.Code, "Foo", CSharpSymbolType.Identifier).AsDirectiveToken(SectionDirective.Directive.Tokens[0]),
Factory.Span(SpanKindInternal.Markup, " ", CSharpSymbolType.WhiteSpace).Accepts(AcceptedCharactersInternal.AllWhiteSpace),
Factory.MetaCode("{").AutoCompleteWith(null, atEndOfSpan: true).Accepts(AcceptedCharactersInternal.None),
new MarkupBlock(
Factory.Markup(" "),
new MarkupTagBlock(
Factory.Markup("")),
Factory.Markup("@").Hidden(),
Factory.Markup("@"),
Factory.Markup("@").Hidden(),
Factory.Markup("@"),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("bar")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
new MarkupTagBlock(
Factory.Markup("")),
Factory.Markup(" ")),
Factory.MetaCode("}").Accepts(AcceptedCharactersInternal.None)),
Factory.EmptyHtml()));
}
}
}