// 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())); } } }