From d06e5b6002388c35791a4c9b76e4ddd6df13d630 Mon Sep 17 00:00:00 2001
From: "N. Taylor Mullen"
Date: Thu, 13 Oct 2016 15:12:07 -0700
Subject: [PATCH] Add legacy parser
---
.../DefaultRazorSyntaxTree.cs | 21 +
.../Legacy/AcceptedCharacters.cs | 22 +
.../Legacy/AddImportChunkGenerator.cs | 47 +
.../Legacy/AddTagHelperChunkGenerator.cs | 41 +
.../Legacy/AttributeBlockChunkGenerator.cs | 63 +
.../Legacy/AutoCompleteEditHandler.cs | 69 +
.../Legacy/BalancingModes.cs | 17 +
.../Legacy/Block.cs | 188 ++
.../Legacy/BlockBuilder.cs | 40 +
.../Legacy/BlockType.cs | 24 +
.../Legacy/CSharpCodeParser.cs | 1786 +++++++++++++++
.../Legacy/CSharpKeyword.cs | 88 +
.../Legacy/CSharpLanguageCharacteristics.cs | 175 ++
.../Legacy/CSharpSymbol.cs | 72 +
.../Legacy/CSharpSymbolType.cs | 75 +
.../Legacy/CSharpTokenizer.cs | 625 +++++
.../Legacy/ChunkGeneratorContext.cs | 25 +
.../Legacy/DisposableAction.cs | 32 +
.../DynamicAttributeBlockChunkGenerator.cs | 54 +
.../Legacy/EditResult.cs | 17 +
.../Legacy/ErrorSink.cs | 46 +
.../Legacy/ExpressionChunkGenerator.cs | 41 +
.../Legacy/HtmlLanguageCharacteristics.cs | 133 ++
.../Legacy/HtmlMarkupParser.cs | 1757 +++++++++++++++
.../Legacy/HtmlSymbol.cs | 57 +
.../Legacy/HtmlSymbolType.cs | 32 +
.../Legacy/HtmlTokenizer.cs | 250 ++
.../Legacy/IParentChunkGenerator.cs | 11 +
.../Legacy/ISpanChunkGenerator.cs | 10 +
.../Legacy/ISymbol.cs | 14 +
.../Legacy/ITextBuffer.cs | 13 +
.../Legacy/ITextDocument.cs | 10 +
.../Legacy/ITokenizer.cs | 10 +
.../Legacy/ImplicitExpressionEditHandler.cs | 320 +++
.../Legacy/KnownSymbolType.cs | 18 +
.../Legacy/LanguageCharacteristics.cs | 109 +
.../Legacy/LineTrackingStringBuffer.cs | 165 ++
.../Legacy/LiteralAttributeChunkGenerator.cs | 81 +
.../Legacy/LocationTagged.cs | 84 +
.../Legacy/MarkupChunkGenerator.cs | 18 +
.../Legacy/ParentChunkGenerator.cs | 47 +
.../Legacy/ParserBase.cs | 21 +
.../Legacy/ParserContext.cs | 98 +
.../Legacy/ParserHelpers.cs | 100 +
.../Legacy/PartialParseResult.cs | 59 +
.../Legacy/RazorCommentChunkGenerator.cs | 9 +
.../Legacy/RazorError.cs | 80 +
.../Legacy/RazorParser.cs | 48 +
.../Legacy/RemoveTagHelperChunkGenerator.cs | 41 +
.../Legacy/SectionChunkGenerator.cs | 46 +
.../Legacy/SeekableTextReader.cs | 90 +
.../Legacy/SetBaseTypeChunkGenerator.cs | 38 +
.../Legacy/SourceLocation.cs | 194 ++
.../Legacy/SourceLocationTracker.cs | 101 +
.../Legacy/Span.cs | 135 ++
.../Legacy/SpanBuilder.cs | 92 +
.../Legacy/SpanChunkGenerator.cs | 39 +
.../Legacy/SpanEditHandler.cs | 143 ++
.../Legacy/SpanKind.cs | 14 +
.../Legacy/StatementChunkGenerator.cs | 18 +
.../Legacy/SymbolBase.cs | 72 +
.../Legacy/SymbolExtensions.cs | 39 +
.../Legacy/SyntaxConstants.cs | 31 +
.../Legacy/SyntaxTreeBuilder.cs | 94 +
.../Legacy/SyntaxTreeNode.cs | 45 +
.../TagHelperPrefixDirectiveChunkGenerator.cs | 41 +
.../Legacy/TemplateBlockChunkGenerator.cs | 18 +
.../Legacy/TextChange.cs | 252 +++
.../Legacy/TextReaderExtensions.cs | 167 ++
.../Legacy/Tokenizer.cs | 440 ++++
.../Legacy/TokenizerBackedParser.cs | 661 ++++++
.../Legacy/TokenizerView.cs | 52 +
.../Legacy/TypeMemberChunkGenerator.cs | 18 +
.../LegacyResources.resx | 351 +++
.../Properties/LegacyResources.Designer.cs | 1162 ++++++++++
.../RazorSyntaxTree.cs | 46 +
.../Legacy/BaselineWriter.cs | 46 +
.../Legacy/BlockExtensions.cs | 28 +
.../Legacy/BlockFactory.cs | 58 +
.../Legacy/BlockTest.cs | 62 +
.../Legacy/BlockTypes.cs | 202 ++
.../Legacy/CSharpAutoCompleteTest.cs | 138 ++
.../Legacy/CSharpBlockTest.cs | 1258 +++++++++++
.../Legacy/CSharpDirectivesTest.cs | 426 ++++
.../Legacy/CSharpErrorTest.cs | 695 ++++++
.../Legacy/CSharpExplicitExpressionTest.cs | 139 ++
.../Legacy/CSharpImplicitExpressionTest.cs | 297 +++
.../Legacy/CSharpNestedStatementsTest.cs | 104 +
.../Legacy/CSharpRazorCommentsTest.cs | 423 ++++
.../Legacy/CSharpReservedWordsTest.cs | 42 +
.../Legacy/CSharpSectionTest.cs | 588 +++++
.../Legacy/CSharpSpecialBlockTest.cs | 217 ++
.../Legacy/CSharpStatementTest.cs | 418 ++++
.../Legacy/CSharpTemplateTest.cs | 321 +++
.../Legacy/CSharpToMarkupSwitchTest.cs | 693 ++++++
.../Legacy/CSharpTokenizerCommentTest.cs | 89 +
.../Legacy/CSharpTokenizerIdentifierTest.cs | 170 ++
.../Legacy/CSharpTokenizerLiteralTest.cs | 285 +++
.../Legacy/CSharpTokenizerOperatorsTest.cs | 296 +++
.../Legacy/CSharpTokenizerTest.cs | 96 +
.../Legacy/CSharpTokenizerTestBase.cs | 25 +
.../Legacy/CSharpVerbatimBlockTest.cs | 138 ++
.../Legacy/CSharpWhitespaceHandlingTest.cs | 34 +
.../Legacy/CodeParserTestBase.cs | 75 +
.../Legacy/CsHtmlCodeParserTestBase.cs | 20 +
.../Legacy/CsHtmlMarkupParserTestBase.cs | 20 +
.../Legacy/DisposableActionTest.cs | 25 +
.../Legacy/ErrorCollector.cs | 57 +
.../Legacy/ExceptionHelpers.cs | 16 +
.../Legacy/HtmlBlockTest.cs | 639 ++++++
.../Legacy/HtmlDocumentTest.cs | 792 +++++++
.../Legacy/HtmlErrorTest.cs | 124 +
.../Legacy/HtmlParserTestUtils.cs | 43 +
.../Legacy/HtmlTagsTest.cs | 208 ++
.../Legacy/HtmlToCodeSwitchTest.cs | 434 ++++
.../Legacy/HtmlTokenizerTest.cs | 160 ++
.../Legacy/HtmlTokenizerTestBase.cs | 25 +
.../Legacy/LineTrackingStringBufferTest.cs | 26 +
.../Legacy/MarkupParserTestBase.cs | 20 +
.../Legacy/MiscUtils.cs | 33 +
.../Legacy/ParserTestBase.cs | 448 ++++
.../Legacy/RawTextSymbol.cs | 63 +
.../Legacy/RazorErrorTest.cs | 75 +
.../Legacy/RazorParserTest.cs | 66 +
.../Legacy/SourceLocationTest.cs | 266 +++
.../Legacy/SourceLocationTrackerTest.cs | 196 ++
.../Legacy/StringTextBuffer.cs | 53 +
.../Legacy/TestFile.cs | 95 +
.../Legacy/TestSpanBuilder.cs | 431 ++++
.../Legacy/TextChangeTest.cs | 317 +++
.../Legacy/TextReaderExtensionsTest.cs | 113 +
.../Legacy/TokenizerLookaheadTest.cs | 121 +
.../Legacy/TokenizerTestBase.cs | 76 +
.../RazorSyntaxTreeTest.cs | 24 +
.../TestFiles/Source/BasicMarkup.cshtml | 8 +
.../TestFiles/nested-1000.html | 2002 +++++++++++++++++
.../TestRazorSourceDocument.cs | 16 +
.../project.json | 1 +
138 files changed, 25938 insertions(+)
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/DefaultRazorSyntaxTree.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/AcceptedCharacters.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/AddImportChunkGenerator.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/AddTagHelperChunkGenerator.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/AttributeBlockChunkGenerator.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/AutoCompleteEditHandler.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/BalancingModes.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/Block.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/BlockBuilder.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/BlockType.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/CSharpCodeParser.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/CSharpKeyword.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/CSharpLanguageCharacteristics.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/CSharpSymbol.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/CSharpSymbolType.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/CSharpTokenizer.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/ChunkGeneratorContext.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/DisposableAction.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/DynamicAttributeBlockChunkGenerator.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/EditResult.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/ErrorSink.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/ExpressionChunkGenerator.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/HtmlLanguageCharacteristics.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/HtmlMarkupParser.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/HtmlSymbol.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/HtmlSymbolType.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/HtmlTokenizer.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/IParentChunkGenerator.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/ISpanChunkGenerator.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/ISymbol.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/ITextBuffer.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/ITextDocument.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/ITokenizer.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/ImplicitExpressionEditHandler.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/KnownSymbolType.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/LanguageCharacteristics.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/LineTrackingStringBuffer.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/LiteralAttributeChunkGenerator.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/LocationTagged.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/MarkupChunkGenerator.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/ParentChunkGenerator.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/ParserBase.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/ParserContext.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/ParserHelpers.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/PartialParseResult.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/RazorCommentChunkGenerator.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/RazorError.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/RazorParser.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/RemoveTagHelperChunkGenerator.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/SectionChunkGenerator.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/SeekableTextReader.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/SetBaseTypeChunkGenerator.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/SourceLocation.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/SourceLocationTracker.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/Span.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/SpanBuilder.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/SpanChunkGenerator.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/SpanEditHandler.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/SpanKind.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/StatementChunkGenerator.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/SymbolBase.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/SymbolExtensions.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/SyntaxConstants.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/SyntaxTreeBuilder.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/SyntaxTreeNode.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperPrefixDirectiveChunkGenerator.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TemplateBlockChunkGenerator.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TextChange.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TextReaderExtensions.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/Tokenizer.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TokenizerBackedParser.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TokenizerView.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TypeMemberChunkGenerator.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/LegacyResources.resx
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Properties/LegacyResources.Designer.cs
create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/RazorSyntaxTree.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/BaselineWriter.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/BlockExtensions.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/BlockFactory.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/BlockTest.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/BlockTypes.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpAutoCompleteTest.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpBlockTest.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpDirectivesTest.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpErrorTest.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpExplicitExpressionTest.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpImplicitExpressionTest.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpNestedStatementsTest.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpRazorCommentsTest.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpReservedWordsTest.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpSectionTest.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpSpecialBlockTest.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpStatementTest.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpTemplateTest.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpToMarkupSwitchTest.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpTokenizerCommentTest.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpTokenizerIdentifierTest.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpTokenizerLiteralTest.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpTokenizerOperatorsTest.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpTokenizerTest.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpTokenizerTestBase.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpVerbatimBlockTest.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpWhitespaceHandlingTest.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CodeParserTestBase.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CsHtmlCodeParserTestBase.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CsHtmlMarkupParserTestBase.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/DisposableActionTest.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/ErrorCollector.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/ExceptionHelpers.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/HtmlBlockTest.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/HtmlDocumentTest.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/HtmlErrorTest.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/HtmlParserTestUtils.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/HtmlTagsTest.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/HtmlToCodeSwitchTest.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/HtmlTokenizerTest.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/HtmlTokenizerTestBase.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/LineTrackingStringBufferTest.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/MarkupParserTestBase.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/MiscUtils.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/ParserTestBase.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/RawTextSymbol.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/RazorErrorTest.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/RazorParserTest.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/SourceLocationTest.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/SourceLocationTrackerTest.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/StringTextBuffer.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/TestFile.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/TestSpanBuilder.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/TextChangeTest.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/TextReaderExtensionsTest.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/TokenizerLookaheadTest.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/TokenizerTestBase.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/RazorSyntaxTreeTest.cs
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/TestFiles/Source/BasicMarkup.cshtml
create mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/TestFiles/nested-1000.html
diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/DefaultRazorSyntaxTree.cs b/src/Microsoft.AspNetCore.Razor.Evolution/DefaultRazorSyntaxTree.cs
new file mode 100644
index 0000000000..2890b981a3
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Razor.Evolution/DefaultRazorSyntaxTree.cs
@@ -0,0 +1,21 @@
+// 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.Collections.Generic;
+using Microsoft.AspNetCore.Razor.Evolution.Legacy;
+
+namespace Microsoft.AspNetCore.Razor.Evolution
+{
+ internal class DefaultRazorSyntaxTree : RazorSyntaxTree
+ {
+ public DefaultRazorSyntaxTree(Block root, IReadOnlyList diagnostics)
+ {
+ Root = root;
+ Diagnostics = diagnostics;
+ }
+
+ internal override IReadOnlyList Diagnostics { get; }
+
+ internal override Block Root { get; }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/AcceptedCharacters.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/AcceptedCharacters.cs
new file mode 100644
index 0000000000..1e11a476cf
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/AcceptedCharacters.cs
@@ -0,0 +1,22 @@
+// 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;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ [Flags]
+ internal enum AcceptedCharacters
+ {
+ None = 0,
+ NewLine = 1,
+ WhiteSpace = 2,
+
+ NonWhiteSpace = 4,
+
+ AllWhiteSpace = NewLine | WhiteSpace,
+ Any = AllWhiteSpace | NonWhiteSpace,
+
+ AnyExceptNewline = NonWhiteSpace | WhiteSpace
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/AddImportChunkGenerator.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/AddImportChunkGenerator.cs
new file mode 100644
index 0000000000..ae0147117c
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/AddImportChunkGenerator.cs
@@ -0,0 +1,47 @@
+// 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;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal class AddImportChunkGenerator : SpanChunkGenerator
+ {
+ public AddImportChunkGenerator(string ns)
+ {
+ Namespace = ns;
+ }
+
+ public string Namespace { get; }
+
+ public override void GenerateChunk(Span target, ChunkGeneratorContext context)
+ {
+ var ns = Namespace;
+
+ if (!string.IsNullOrEmpty(ns) && char.IsWhiteSpace(ns[0]))
+ {
+ ns = ns.Substring(1);
+ }
+
+ //context.ChunkTreeBuilder.AddUsingChunk(ns, target);
+ }
+
+ public override string ToString()
+ {
+ return "Import:" + Namespace + ";";
+ }
+
+ public override bool Equals(object obj)
+ {
+ var other = obj as AddImportChunkGenerator;
+ return other != null &&
+ string.Equals(Namespace, other.Namespace, StringComparison.Ordinal);
+ }
+
+ public override int GetHashCode()
+ {
+ // Hash code should include only immutable properties.
+ return Namespace == null ? 0 : StringComparer.Ordinal.GetHashCode(Namespace);
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/AddTagHelperChunkGenerator.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/AddTagHelperChunkGenerator.cs
new file mode 100644
index 0000000000..e9577d7e4f
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/AddTagHelperChunkGenerator.cs
@@ -0,0 +1,41 @@
+// 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 Microsoft.Extensions.Internal;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal class AddTagHelperChunkGenerator : SpanChunkGenerator
+ {
+ public AddTagHelperChunkGenerator(string lookupText)
+ {
+ LookupText = lookupText;
+ }
+
+ public string LookupText { get; }
+
+ public override void GenerateChunk(Span target, ChunkGeneratorContext context)
+ {
+ //context.ChunkTreeBuilder.AddAddTagHelperChunk(LookupText, target);
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ var other = obj as AddTagHelperChunkGenerator;
+ return base.Equals(other) &&
+ string.Equals(LookupText, other.LookupText, StringComparison.Ordinal);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ var combiner = HashCodeCombiner.Start();
+ combiner.Add(base.GetHashCode());
+ combiner.Add(LookupText, StringComparer.Ordinal);
+
+ return combiner.CombinedHash;
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/AttributeBlockChunkGenerator.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/AttributeBlockChunkGenerator.cs
new file mode 100644
index 0000000000..662441405f
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/AttributeBlockChunkGenerator.cs
@@ -0,0 +1,63 @@
+// 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.Globalization;
+using Microsoft.Extensions.Internal;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal class AttributeBlockChunkGenerator : ParentChunkGenerator
+ {
+ public AttributeBlockChunkGenerator(string name, LocationTagged prefix, LocationTagged suffix)
+ {
+ Name = name;
+ Prefix = prefix;
+ Suffix = suffix;
+ }
+
+ public string Name { get; }
+
+ public LocationTagged Prefix { get; }
+
+ public LocationTagged Suffix { get; }
+
+ public override void GenerateStartParentChunk(Block target, ChunkGeneratorContext context)
+ {
+ //var chunk = context.ChunkTreeBuilder.StartParentChunk(target);
+
+ //chunk.Attribute = Name;
+ //chunk.Prefix = Prefix;
+ //chunk.Suffix = Suffix;
+ }
+
+ public override void GenerateEndParentChunk(Block target, ChunkGeneratorContext context)
+ {
+ //context.ChunkTreeBuilder.EndParentChunk();
+ }
+
+ public override string ToString()
+ {
+ return string.Format(CultureInfo.CurrentCulture, "Attr:{0},{1:F},{2:F}", Name, Prefix, Suffix);
+ }
+
+ public override bool Equals(object obj)
+ {
+ var other = obj as AttributeBlockChunkGenerator;
+ return other != null &&
+ string.Equals(other.Name, Name, StringComparison.Ordinal) &&
+ Equals(other.Prefix, Prefix) &&
+ Equals(other.Suffix, Suffix);
+ }
+
+ public override int GetHashCode()
+ {
+ var hashCodeCombiner = HashCodeCombiner.Start();
+ hashCodeCombiner.Add(Name, StringComparer.Ordinal);
+ hashCodeCombiner.Add(Prefix);
+ hashCodeCombiner.Add(Suffix);
+
+ return hashCodeCombiner;
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/AutoCompleteEditHandler.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/AutoCompleteEditHandler.cs
new file mode 100644
index 0000000000..f81777c9aa
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/AutoCompleteEditHandler.cs
@@ -0,0 +1,69 @@
+// 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;
+using Microsoft.Extensions.Internal;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal class AutoCompleteEditHandler : SpanEditHandler
+ {
+ private static readonly int TypeHashCode = typeof(AutoCompleteEditHandler).GetHashCode();
+
+ public AutoCompleteEditHandler(Func> tokenizer)
+ : base(tokenizer)
+ {
+ }
+
+ public AutoCompleteEditHandler(Func> tokenizer, bool autoCompleteAtEndOfSpan)
+ : this(tokenizer)
+ {
+ AutoCompleteAtEndOfSpan = autoCompleteAtEndOfSpan;
+ }
+
+ public AutoCompleteEditHandler(Func> tokenizer, AcceptedCharacters accepted)
+ : base(tokenizer, accepted)
+ {
+ }
+
+ public bool AutoCompleteAtEndOfSpan { get; }
+
+ public string AutoCompleteString { get; set; }
+
+ protected override PartialParseResult CanAcceptChange(Span target, TextChange normalizedChange)
+ {
+ if (((AutoCompleteAtEndOfSpan && IsAtEndOfSpan(target, normalizedChange)) || IsAtEndOfFirstLine(target, normalizedChange)) &&
+ normalizedChange.IsInsert &&
+ ParserHelpers.IsNewLine(normalizedChange.NewText) &&
+ AutoCompleteString != null)
+ {
+ return PartialParseResult.Rejected | PartialParseResult.AutoCompleteBlock;
+ }
+ return PartialParseResult.Rejected;
+ }
+
+ public override string ToString()
+ {
+ return base.ToString() + ",AutoComplete:[" + (AutoCompleteString ?? "") + "]" + (AutoCompleteAtEndOfSpan ? ";AtEnd" : ";AtEOL");
+ }
+
+ public override bool Equals(object obj)
+ {
+ var other = obj as AutoCompleteEditHandler;
+ return base.Equals(other) &&
+ string.Equals(other.AutoCompleteString, AutoCompleteString, StringComparison.Ordinal) &&
+ AutoCompleteAtEndOfSpan == other.AutoCompleteAtEndOfSpan;
+ }
+
+ public override int GetHashCode()
+ {
+ // Hash code should include only immutable properties but Equals also checks the type.
+ var hashCodeCombiner = HashCodeCombiner.Start();
+ hashCodeCombiner.Add(TypeHashCode);
+ hashCodeCombiner.Add(AutoCompleteAtEndOfSpan);
+
+ return hashCodeCombiner.CombinedHash;
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/BalancingModes.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/BalancingModes.cs
new file mode 100644
index 0000000000..d1a629ed7b
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/BalancingModes.cs
@@ -0,0 +1,17 @@
+// 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;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ [Flags]
+ internal enum BalancingModes
+ {
+ None = 0,
+ BacktrackOnFailure = 1,
+ NoErrorOnFailure = 2,
+ AllowCommentsAndTemplates = 4,
+ AllowEmbeddedTransitions = 8
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/Block.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/Block.cs
new file mode 100644
index 0000000000..e5a27dc5fa
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/Block.cs
@@ -0,0 +1,188 @@
+// 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;
+using System.Globalization;
+using System.Linq;
+using Microsoft.Extensions.Internal;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal class Block : SyntaxTreeNode
+ {
+ public Block(BlockBuilder source)
+ : this(source.Type, source.Children, source.ChunkGenerator)
+ {
+ source.Reset();
+ }
+
+ protected Block(BlockType? type, IReadOnlyList children, IParentChunkGenerator generator)
+ {
+ if (type == null)
+ {
+ throw new InvalidOperationException(LegacyResources.Block_Type_Not_Specified);
+ }
+
+ Type = type.Value;
+ Children = children;
+ ChunkGenerator = generator;
+
+ // Perf: Avoid allocating an enumerator.
+ for (var i = 0; i < Children.Count; i++)
+ {
+ Children[i].Parent = this;
+ }
+ }
+ public IParentChunkGenerator ChunkGenerator { get; }
+
+ public BlockType Type { get; }
+
+ public IReadOnlyList Children { get; }
+
+ public override bool IsBlock => true;
+
+ public override SourceLocation Start
+ {
+ get
+ {
+ var child = Children.FirstOrDefault();
+ if (child == null)
+ {
+ return SourceLocation.Zero;
+ }
+ else
+ {
+ return child.Start;
+ }
+ }
+ }
+
+ public override int Length => Children.Sum(child => child.Length);
+
+ public virtual IEnumerable Flatten()
+ {
+ // Perf: Avoid allocating an enumerator.
+ for (var i = 0; i < Children.Count; i++)
+ {
+ var element = Children[i];
+ var span = element as Span;
+ if (span != null)
+ {
+ yield return span;
+ }
+ else
+ {
+ var block = element as Block;
+ foreach (Span childSpan in block.Flatten())
+ {
+ yield return childSpan;
+ }
+ }
+ }
+ }
+
+ public override string ToString()
+ {
+ return string.Format(
+ CultureInfo.CurrentCulture,
+ "{0} Block at {1}::{2} (Gen:{3})",
+ Type,
+ Start,
+ Length,
+ ChunkGenerator);
+ }
+
+ public override bool Equals(object obj)
+ {
+ var other = obj as Block;
+ return other != null &&
+ Type == other.Type &&
+ Equals(ChunkGenerator, other.ChunkGenerator) &&
+ ChildrenEqual(Children, other.Children);
+ }
+
+ public override int GetHashCode()
+ {
+ var hashCodeCombiner = HashCodeCombiner.Start();
+ hashCodeCombiner.Add(Type);
+ hashCodeCombiner.Add(ChunkGenerator);
+ hashCodeCombiner.Add(Children);
+
+ return hashCodeCombiner;
+ }
+
+ private static bool ChildrenEqual(IEnumerable left, IEnumerable right)
+ {
+ IEnumerator leftEnum = left.GetEnumerator();
+ IEnumerator rightEnum = right.GetEnumerator();
+ while (leftEnum.MoveNext())
+ {
+ if (!rightEnum.MoveNext() || // More items in left than in right
+ !Equals(leftEnum.Current, rightEnum.Current))
+ {
+ // Nodes are not equal
+ return false;
+ }
+ }
+ if (rightEnum.MoveNext())
+ {
+ // More items in right than left
+ return false;
+ }
+ return true;
+ }
+
+ public override bool EquivalentTo(SyntaxTreeNode node)
+ {
+ var other = node as Block;
+ if (other == null || other.Type != Type)
+ {
+ return false;
+ }
+
+ return Enumerable.SequenceEqual(Children, other.Children, EquivalenceComparer.Default);
+ }
+
+ public override int GetEquivalenceHash()
+ {
+ var hashCodeCombiner = HashCodeCombiner.Start();
+ hashCodeCombiner.Add(Type);
+ foreach (var child in Children)
+ {
+ hashCodeCombiner.Add(child.GetEquivalenceHash());
+ }
+
+ return hashCodeCombiner.CombinedHash;
+ }
+
+ private class EquivalenceComparer : IEqualityComparer
+ {
+ public static readonly EquivalenceComparer Default = new EquivalenceComparer();
+
+ private EquivalenceComparer()
+ {
+ }
+
+ public bool Equals(SyntaxTreeNode nodeX, SyntaxTreeNode nodeY)
+ {
+ if (nodeX == nodeY)
+ {
+ return true;
+ }
+
+ return nodeX != null && nodeX.EquivalentTo(nodeY);
+ }
+
+ public int GetHashCode(SyntaxTreeNode node)
+ {
+ if (node == null)
+ {
+ throw new ArgumentNullException(nameof(node));
+ }
+
+ return node.GetEquivalenceHash();
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/BlockBuilder.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/BlockBuilder.cs
new file mode 100644
index 0000000000..be23d9e2ff
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/BlockBuilder.cs
@@ -0,0 +1,40 @@
+// 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.Collections.Generic;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal class BlockBuilder
+ {
+ public BlockBuilder()
+ {
+ Reset();
+ }
+
+ public BlockBuilder(Block original)
+ {
+ Type = original.Type;
+ Children = new List(original.Children);
+ ChunkGenerator = original.ChunkGenerator;
+ }
+
+ public IParentChunkGenerator ChunkGenerator { get; set; }
+
+ public BlockType? Type { get; set; }
+
+ public List Children { get; private set; }
+
+ public virtual Block Build()
+ {
+ return new Block(this);
+ }
+
+ public virtual void Reset()
+ {
+ Type = null;
+ Children = new List();
+ ChunkGenerator = ParentChunkGenerator.Null;
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/BlockType.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/BlockType.cs
new file mode 100644
index 0000000000..fac152aea9
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/BlockType.cs
@@ -0,0 +1,24 @@
+// 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.
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal enum BlockType
+ {
+ // Code
+ Statement,
+ Directive,
+ Functions,
+ Expression,
+ Helper,
+
+ // Markup
+ Markup,
+ Section,
+ Template,
+
+ // Special
+ Comment,
+ Tag
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/CSharpCodeParser.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/CSharpCodeParser.cs
new file mode 100644
index 0000000000..72e13c2bd3
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/CSharpCodeParser.cs
@@ -0,0 +1,1786 @@
+// 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;
+using System.Diagnostics;
+using System.Linq;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal class CSharpCodeParser : TokenizerBackedParser
+ {
+ internal static readonly int UsingKeywordLength = 5; // using
+ private static readonly Func IsValidStatementSpacingSymbol =
+ IsSpacingToken(includeNewLines: true, includeComments: true);
+
+ internal static ISet DefaultKeywords = new HashSet()
+ {
+ SyntaxConstants.CSharp.TagHelperPrefixKeyword,
+ SyntaxConstants.CSharp.AddTagHelperKeyword,
+ SyntaxConstants.CSharp.RemoveTagHelperKeyword,
+ "if",
+ "do",
+ "try",
+ "for",
+ "foreach",
+ "while",
+ "switch",
+ "lock",
+ "using",
+ "section",
+ "inherits",
+ "functions",
+ "namespace",
+ "class",
+ };
+
+ private Dictionary _directiveParsers = new Dictionary(StringComparer.Ordinal);
+ private Dictionary> _keywordParsers = new Dictionary>();
+
+ public CSharpCodeParser(ParserContext context)
+ : base(CSharpLanguageCharacteristics.Instance, context)
+ {
+ Keywords = new HashSet();
+ SetUpKeywords();
+ SetupDirectives();
+ SetUpExpressions();
+ }
+
+ public HtmlMarkupParser HtmlParser { get; set; }
+
+ protected internal ISet Keywords { get; private set; }
+
+ public bool IsNested { get; set; }
+
+ protected override bool SymbolTypeEquals(CSharpSymbolType x, CSharpSymbolType y) => x == y;
+
+ protected void MapDirectives(Action handler, params string[] directives)
+ {
+ foreach (string directive in directives)
+ {
+ _directiveParsers.Add(directive, handler);
+ Keywords.Add(directive);
+ }
+ }
+
+ protected bool TryGetDirectiveHandler(string directive, out Action handler)
+ {
+ return _directiveParsers.TryGetValue(directive, out handler);
+ }
+
+ private void MapExpressionKeyword(Action handler, CSharpKeyword keyword)
+ {
+ _keywordParsers.Add(keyword, handler);
+
+ // Expression keywords don't belong in the regular keyword list
+ }
+
+ private void MapKeywords(Action handler, params CSharpKeyword[] keywords)
+ {
+ MapKeywords(handler, topLevel: true, keywords: keywords);
+ }
+
+ private void MapKeywords(Action handler, bool topLevel, params CSharpKeyword[] keywords)
+ {
+ foreach (CSharpKeyword keyword in keywords)
+ {
+ _keywordParsers.Add(keyword, handler);
+ if (topLevel)
+ {
+ Keywords.Add(CSharpLanguageCharacteristics.GetKeyword(keyword));
+ }
+ }
+ }
+
+ [Conditional("DEBUG")]
+ internal void Assert(CSharpKeyword expectedKeyword)
+ {
+ Debug.Assert(CurrentSymbol.Type == CSharpSymbolType.Keyword &&
+ CurrentSymbol.Keyword.HasValue &&
+ CurrentSymbol.Keyword.Value == expectedKeyword);
+ }
+
+ protected internal bool At(CSharpKeyword keyword)
+ {
+ return At(CSharpSymbolType.Keyword) &&
+ CurrentSymbol.Keyword.HasValue &&
+ CurrentSymbol.Keyword.Value == keyword;
+ }
+
+ protected internal bool AcceptIf(CSharpKeyword keyword)
+ {
+ if (At(keyword))
+ {
+ AcceptAndMoveNext();
+ return true;
+ }
+ return false;
+ }
+
+ protected static Func IsSpacingToken(bool includeNewLines, bool includeComments)
+ {
+ return sym => sym.Type == CSharpSymbolType.WhiteSpace ||
+ (includeNewLines && sym.Type == CSharpSymbolType.NewLine) ||
+ (includeComments && sym.Type == CSharpSymbolType.Comment);
+ }
+
+ public override void ParseBlock()
+ {
+ using (PushSpanConfig(DefaultSpanConfig))
+ {
+ if (Context == null)
+ {
+ throw new InvalidOperationException(LegacyResources.Parser_Context_Not_Set);
+ }
+
+ // Unless changed, the block is a statement block
+ using (Context.Builder.StartBlock(BlockType.Statement))
+ {
+ NextToken();
+
+ AcceptWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
+
+ var current = CurrentSymbol;
+ if (At(CSharpSymbolType.StringLiteral) &&
+ CurrentSymbol.Content.Length > 0 &&
+ CurrentSymbol.Content[0] == SyntaxConstants.TransitionCharacter)
+ {
+ var split = Language.SplitSymbol(CurrentSymbol, 1, CSharpSymbolType.Transition);
+ current = split.Item1;
+ Context.Source.Position = split.Item2.Start.AbsoluteIndex;
+ NextToken();
+ }
+ else if (At(CSharpSymbolType.Transition))
+ {
+ NextToken();
+ }
+
+ // Accept "@" if we see it, but if we don't, that's OK. We assume we were started for a good reason
+ if (current.Type == CSharpSymbolType.Transition)
+ {
+ if (Span.Symbols.Count > 0)
+ {
+ Output(SpanKind.Code);
+ }
+ AtTransition(current);
+ }
+ else
+ {
+ // No "@" => Jump straight to AfterTransition
+ AfterTransition();
+ }
+ Output(SpanKind.Code);
+ }
+ }
+ }
+
+ private void DefaultSpanConfig(SpanBuilder span)
+ {
+ span.EditHandler = SpanEditHandler.CreateDefault(Language.TokenizeString);
+ span.ChunkGenerator = new StatementChunkGenerator();
+ }
+
+ private void AtTransition(CSharpSymbol current)
+ {
+ Debug.Assert(current.Type == CSharpSymbolType.Transition);
+ Accept(current);
+ Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
+ Span.ChunkGenerator = SpanChunkGenerator.Null;
+
+ // Output the "@" span and continue here
+ Output(SpanKind.Transition);
+ AfterTransition();
+ }
+
+ private void AfterTransition()
+ {
+ using (PushSpanConfig(DefaultSpanConfig))
+ {
+ EnsureCurrent();
+ try
+ {
+ // What type of block is this?
+ if (!EndOfFile)
+ {
+ if (CurrentSymbol.Type == CSharpSymbolType.LeftParenthesis)
+ {
+ Context.Builder.CurrentBlock.Type = BlockType.Expression;
+ Context.Builder.CurrentBlock.ChunkGenerator = new ExpressionChunkGenerator();
+ ExplicitExpression();
+ return;
+ }
+ else if (CurrentSymbol.Type == CSharpSymbolType.Identifier)
+ {
+ Action handler;
+ if (TryGetDirectiveHandler(CurrentSymbol.Content, out handler))
+ {
+ Span.ChunkGenerator = SpanChunkGenerator.Null;
+ handler();
+ return;
+ }
+ else
+ {
+ if (string.Equals(
+ CurrentSymbol.Content,
+ SyntaxConstants.CSharp.HelperKeyword,
+ StringComparison.Ordinal))
+ {
+ Context.ErrorSink.OnError(
+ CurrentLocation,
+ LegacyResources.FormatParseError_HelperDirectiveNotAvailable(
+ SyntaxConstants.CSharp.HelperKeyword),
+ CurrentSymbol.Content.Length);
+ }
+
+ Context.Builder.CurrentBlock.Type = BlockType.Expression;
+ Context.Builder.CurrentBlock.ChunkGenerator = new ExpressionChunkGenerator();
+ ImplicitExpression();
+ return;
+ }
+ }
+ else if (CurrentSymbol.Type == CSharpSymbolType.Keyword)
+ {
+ KeywordBlock(topLevel: true);
+ return;
+ }
+ else if (CurrentSymbol.Type == CSharpSymbolType.LeftBrace)
+ {
+ VerbatimBlock();
+ return;
+ }
+ }
+
+ // Invalid character
+ Context.Builder.CurrentBlock.Type = BlockType.Expression;
+ Context.Builder.CurrentBlock.ChunkGenerator = new ExpressionChunkGenerator();
+ AddMarkerSymbolIfNecessary();
+ Span.ChunkGenerator = new ExpressionChunkGenerator();
+ Span.EditHandler = new ImplicitExpressionEditHandler(
+ Language.TokenizeString,
+ DefaultKeywords,
+ acceptTrailingDot: IsNested)
+ {
+ AcceptedCharacters = AcceptedCharacters.NonWhiteSpace
+ };
+ if (At(CSharpSymbolType.WhiteSpace) || At(CSharpSymbolType.NewLine))
+ {
+ Context.ErrorSink.OnError(
+ CurrentLocation,
+ LegacyResources.ParseError_Unexpected_WhiteSpace_At_Start_Of_CodeBlock_CS,
+ CurrentSymbol.Content.Length);
+ }
+ else if (EndOfFile)
+ {
+ Context.ErrorSink.OnError(
+ CurrentLocation,
+ LegacyResources.ParseError_Unexpected_EndOfFile_At_Start_Of_CodeBlock,
+ length: 1 /* end of file */);
+ }
+ else
+ {
+ Context.ErrorSink.OnError(
+ CurrentLocation,
+ LegacyResources.FormatParseError_Unexpected_Character_At_Start_Of_CodeBlock_CS(
+ CurrentSymbol.Content),
+ CurrentSymbol.Content.Length);
+ }
+ }
+ finally
+ {
+ // Always put current character back in the buffer for the next parser.
+ PutCurrentBack();
+ }
+ }
+ }
+
+ private void VerbatimBlock()
+ {
+ Assert(CSharpSymbolType.LeftBrace);
+ var block = new Block(LegacyResources.BlockName_Code, CurrentLocation);
+ AcceptAndMoveNext();
+
+ // Set up the "{" span and output
+ Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
+ Span.ChunkGenerator = SpanChunkGenerator.Null;
+ Output(SpanKind.MetaCode);
+
+ // Set up auto-complete and parse the code block
+ var editHandler = new AutoCompleteEditHandler(Language.TokenizeString);
+ Span.EditHandler = editHandler;
+ CodeBlock(false, block);
+
+ Span.ChunkGenerator = new StatementChunkGenerator();
+ AddMarkerSymbolIfNecessary();
+ if (!At(CSharpSymbolType.RightBrace))
+ {
+ editHandler.AutoCompleteString = "}";
+ }
+ Output(SpanKind.Code);
+
+ if (Optional(CSharpSymbolType.RightBrace))
+ {
+ // Set up the "}" span
+ Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
+ Span.ChunkGenerator = SpanChunkGenerator.Null;
+ }
+
+ if (!IsNested)
+ {
+ EnsureCurrent();
+ if (At(CSharpSymbolType.NewLine) ||
+ (At(CSharpSymbolType.WhiteSpace) && NextIs(CSharpSymbolType.NewLine)))
+ {
+ Context.NullGenerateWhitespaceAndNewLine = true;
+ }
+ }
+
+ Output(SpanKind.MetaCode);
+ }
+
+ private void ImplicitExpression()
+ {
+ ImplicitExpression(AcceptedCharacters.NonWhiteSpace);
+ }
+
+ // Async implicit expressions include the "await" keyword and therefore need to allow spaces to
+ // separate the "await" and the following code.
+ private void AsyncImplicitExpression()
+ {
+ ImplicitExpression(AcceptedCharacters.AnyExceptNewline);
+ }
+
+ private void ImplicitExpression(AcceptedCharacters acceptedCharacters)
+ {
+ Context.Builder.CurrentBlock.Type = BlockType.Expression;
+ Context.Builder.CurrentBlock.ChunkGenerator = new ExpressionChunkGenerator();
+
+ using (PushSpanConfig(span =>
+ {
+ span.EditHandler = new ImplicitExpressionEditHandler(
+ Language.TokenizeString,
+ Keywords,
+ acceptTrailingDot: IsNested);
+ span.EditHandler.AcceptedCharacters = acceptedCharacters;
+ span.ChunkGenerator = new ExpressionChunkGenerator();
+ }))
+ {
+ do
+ {
+ if (AtIdentifier(allowKeywords: true))
+ {
+ AcceptAndMoveNext();
+ }
+ }
+ while (MethodCallOrArrayIndex(acceptedCharacters));
+
+ PutCurrentBack();
+ Output(SpanKind.Code);
+ }
+ }
+
+ private bool MethodCallOrArrayIndex(AcceptedCharacters acceptedCharacters)
+ {
+ if (!EndOfFile)
+ {
+ if (CurrentSymbol.Type == CSharpSymbolType.LeftParenthesis ||
+ CurrentSymbol.Type == CSharpSymbolType.LeftBracket)
+ {
+ // If we end within "(", whitespace is fine
+ Span.EditHandler.AcceptedCharacters = AcceptedCharacters.Any;
+
+ CSharpSymbolType right;
+ bool success;
+
+ using (PushSpanConfig((span, prev) =>
+ {
+ prev(span);
+ span.EditHandler.AcceptedCharacters = AcceptedCharacters.Any;
+ }))
+ {
+ right = Language.FlipBracket(CurrentSymbol.Type);
+ success = Balance(BalancingModes.BacktrackOnFailure | BalancingModes.AllowCommentsAndTemplates);
+ }
+
+ if (!success)
+ {
+ AcceptUntil(CSharpSymbolType.LessThan);
+ }
+ if (At(right))
+ {
+ AcceptAndMoveNext();
+
+ // At the ending brace, restore the initial accepted characters.
+ Span.EditHandler.AcceptedCharacters = acceptedCharacters;
+ }
+ return MethodCallOrArrayIndex(acceptedCharacters);
+ }
+ if (At(CSharpSymbolType.QuestionMark))
+ {
+ var next = Lookahead(count: 1);
+
+ if (next != null)
+ {
+ if (next.Type == CSharpSymbolType.Dot)
+ {
+ // Accept null conditional dot operator (?.).
+ AcceptAndMoveNext();
+ AcceptAndMoveNext();
+
+ // If the next piece after the ?. is a keyword or identifier then we want to continue.
+ return At(CSharpSymbolType.Identifier) || At(CSharpSymbolType.Keyword);
+ }
+ else if (next.Type == CSharpSymbolType.LeftBracket)
+ {
+ // We're at the ? for a null conditional bracket operator (?[).
+ AcceptAndMoveNext();
+
+ // Accept the [ and any content inside (it will attempt to balance).
+ return MethodCallOrArrayIndex(acceptedCharacters);
+ }
+ }
+ }
+ else if (At(CSharpSymbolType.Dot))
+ {
+ var dot = CurrentSymbol;
+ if (NextToken())
+ {
+ if (At(CSharpSymbolType.Identifier) || At(CSharpSymbolType.Keyword))
+ {
+ // Accept the dot and return to the start
+ Accept(dot);
+ return true; // continue
+ }
+ else
+ {
+ // Put the symbol back
+ PutCurrentBack();
+ }
+ }
+ if (!IsNested)
+ {
+ // Put the "." back
+ PutBack(dot);
+ }
+ else
+ {
+ Accept(dot);
+ }
+ }
+ else if (!At(CSharpSymbolType.WhiteSpace) && !At(CSharpSymbolType.NewLine))
+ {
+ PutCurrentBack();
+ }
+ }
+
+ // Implicit Expression is complete
+ return false;
+ }
+
+ protected void CompleteBlock()
+ {
+ CompleteBlock(insertMarkerIfNecessary: true);
+ }
+
+ protected void CompleteBlock(bool insertMarkerIfNecessary)
+ {
+ CompleteBlock(insertMarkerIfNecessary, captureWhitespaceToEndOfLine: insertMarkerIfNecessary);
+ }
+
+ protected void CompleteBlock(bool insertMarkerIfNecessary, bool captureWhitespaceToEndOfLine)
+ {
+ if (insertMarkerIfNecessary && Context.Builder.LastAcceptedCharacters != AcceptedCharacters.Any)
+ {
+ AddMarkerSymbolIfNecessary();
+ }
+
+ EnsureCurrent();
+
+ // Read whitespace, but not newlines
+ // If we're not inserting a marker span, we don't need to capture whitespace
+ if (!Context.WhiteSpaceIsSignificantToAncestorBlock &&
+ Context.Builder.CurrentBlock.Type != BlockType.Expression &&
+ captureWhitespaceToEndOfLine &&
+ !Context.DesignTimeMode &&
+ !IsNested)
+ {
+ CaptureWhitespaceAtEndOfCodeOnlyLine();
+ }
+ else
+ {
+ PutCurrentBack();
+ }
+ }
+
+ private void CaptureWhitespaceAtEndOfCodeOnlyLine()
+ {
+ IEnumerable ws = ReadWhile(sym => sym.Type == CSharpSymbolType.WhiteSpace);
+ if (At(CSharpSymbolType.NewLine))
+ {
+ Accept(ws);
+ AcceptAndMoveNext();
+ PutCurrentBack();
+ }
+ else
+ {
+ PutCurrentBack();
+ PutBack(ws);
+ }
+ }
+
+ private void ConfigureExplicitExpressionSpan(SpanBuilder sb)
+ {
+ sb.EditHandler = SpanEditHandler.CreateDefault(Language.TokenizeString);
+ sb.ChunkGenerator = new ExpressionChunkGenerator();
+ }
+
+ private void ExplicitExpression()
+ {
+ var block = new Block(LegacyResources.BlockName_ExplicitExpression, CurrentLocation);
+ Assert(CSharpSymbolType.LeftParenthesis);
+ AcceptAndMoveNext();
+ Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
+ Span.ChunkGenerator = SpanChunkGenerator.Null;
+ Output(SpanKind.MetaCode);
+ using (PushSpanConfig(ConfigureExplicitExpressionSpan))
+ {
+ var success = Balance(
+ BalancingModes.BacktrackOnFailure |
+ BalancingModes.NoErrorOnFailure |
+ BalancingModes.AllowCommentsAndTemplates,
+ CSharpSymbolType.LeftParenthesis,
+ CSharpSymbolType.RightParenthesis,
+ block.Start);
+
+ if (!success)
+ {
+ AcceptUntil(CSharpSymbolType.LessThan);
+ Context.ErrorSink.OnError(
+ block.Start,
+ LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF(block.Name, ")", "("),
+ length: 1 /* ( */);
+ }
+
+ // If necessary, put an empty-content marker symbol here
+ if (Span.Symbols.Count == 0)
+ {
+ Accept(new CSharpSymbol(CurrentLocation, string.Empty, CSharpSymbolType.Unknown));
+ }
+
+ // Output the content span and then capture the ")"
+ Output(SpanKind.Code);
+ }
+ Optional(CSharpSymbolType.RightParenthesis);
+ if (!EndOfFile)
+ {
+ PutCurrentBack();
+ }
+ Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
+ Span.ChunkGenerator = SpanChunkGenerator.Null;
+ CompleteBlock(insertMarkerIfNecessary: false);
+ Output(SpanKind.MetaCode);
+ }
+
+ private void Template()
+ {
+ if (Context.Builder.ActiveBlocks.Any(block => block.Type == BlockType.Template))
+ {
+ Context.ErrorSink.OnError(
+ CurrentLocation,
+ LegacyResources.ParseError_InlineMarkup_Blocks_Cannot_Be_Nested,
+ length: 1 /* @ */);
+ }
+ Output(SpanKind.Code);
+ using (Context.Builder.StartBlock(BlockType.Template))
+ {
+ Context.Builder.CurrentBlock.ChunkGenerator = new TemplateBlockChunkGenerator();
+ PutCurrentBack();
+ OtherParserBlock();
+ }
+ }
+
+ private void OtherParserBlock()
+ {
+ ParseWithOtherParser(p => p.ParseBlock());
+ }
+
+ private void SectionBlock(string left, string right, bool caseSensitive)
+ {
+ ParseWithOtherParser(p => p.ParseSection(Tuple.Create(left, right), caseSensitive));
+ }
+
+ private void NestedBlock()
+ {
+ Output(SpanKind.Code);
+ var wasNested = IsNested;
+ IsNested = true;
+ using (PushSpanConfig())
+ {
+ ParseBlock();
+ }
+ Initialize(Span);
+ IsNested = wasNested;
+ NextToken();
+ }
+
+ protected override bool IsAtEmbeddedTransition(bool allowTemplatesAndComments, bool allowTransitions)
+ {
+ // No embedded transitions in C#, so ignore that param
+ return allowTemplatesAndComments
+ && ((Language.IsTransition(CurrentSymbol)
+ && NextIs(CSharpSymbolType.LessThan, CSharpSymbolType.Colon, CSharpSymbolType.DoubleColon))
+ || Language.IsCommentStart(CurrentSymbol));
+ }
+
+ protected override void HandleEmbeddedTransition()
+ {
+ if (Language.IsTransition(CurrentSymbol))
+ {
+ PutCurrentBack();
+ Template();
+ }
+ else if (Language.IsCommentStart(CurrentSymbol))
+ {
+ RazorComment();
+ }
+ }
+
+ private void ParseWithOtherParser(Action parseAction)
+ {
+ // When transitioning to the HTML parser we no longer want to act as if we're in a nested C# state.
+ // For instance, if @hello.
is in a nested C# block we don't want the trailing '.' to be handled
+ // as C#; it should be handled as a period because it's wrapped in markup.
+ var wasNested = IsNested;
+ IsNested = false;
+ using (PushSpanConfig())
+ {
+ parseAction(HtmlParser);
+ }
+ Initialize(Span);
+ IsNested = wasNested;
+ NextToken();
+ }
+
+ private void SetUpKeywords()
+ {
+ MapKeywords(
+ ConditionalBlock,
+ CSharpKeyword.For,
+ CSharpKeyword.Foreach,
+ CSharpKeyword.While,
+ CSharpKeyword.Switch,
+ CSharpKeyword.Lock);
+ MapKeywords(CaseStatement, false, CSharpKeyword.Case, CSharpKeyword.Default);
+ MapKeywords(IfStatement, CSharpKeyword.If);
+ MapKeywords(TryStatement, CSharpKeyword.Try);
+ MapKeywords(UsingKeyword, CSharpKeyword.Using);
+ MapKeywords(DoStatement, CSharpKeyword.Do);
+ MapKeywords(ReservedDirective, CSharpKeyword.Namespace, CSharpKeyword.Class);
+ }
+
+ protected virtual void ReservedDirective(bool topLevel)
+ {
+ Context.ErrorSink.OnError(
+ CurrentLocation,
+ LegacyResources.FormatParseError_ReservedWord(CurrentSymbol.Content),
+ CurrentSymbol.Content.Length);
+ AcceptAndMoveNext();
+ Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
+ Span.ChunkGenerator = SpanChunkGenerator.Null;
+ Context.Builder.CurrentBlock.Type = BlockType.Directive;
+ CompleteBlock();
+ Output(SpanKind.MetaCode);
+ }
+
+ private void KeywordBlock(bool topLevel)
+ {
+ HandleKeyword(topLevel, () =>
+ {
+ Context.Builder.CurrentBlock.Type = BlockType.Expression;
+ Context.Builder.CurrentBlock.ChunkGenerator = new ExpressionChunkGenerator();
+ ImplicitExpression();
+ });
+ }
+
+ private void CaseStatement(bool topLevel)
+ {
+ Assert(CSharpSymbolType.Keyword);
+ Debug.Assert(CurrentSymbol.Keyword != null &&
+ (CurrentSymbol.Keyword.Value == CSharpKeyword.Case ||
+ CurrentSymbol.Keyword.Value == CSharpKeyword.Default));
+ AcceptUntil(CSharpSymbolType.Colon);
+ Optional(CSharpSymbolType.Colon);
+ }
+
+ private void DoStatement(bool topLevel)
+ {
+ Assert(CSharpKeyword.Do);
+ UnconditionalBlock();
+ WhileClause();
+ if (topLevel)
+ {
+ CompleteBlock();
+ }
+ }
+
+ private void WhileClause()
+ {
+ Span.EditHandler.AcceptedCharacters = AcceptedCharacters.Any;
+ IEnumerable ws = SkipToNextImportantToken();
+
+ if (At(CSharpKeyword.While))
+ {
+ Accept(ws);
+ Assert(CSharpKeyword.While);
+ AcceptAndMoveNext();
+ AcceptWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
+ if (AcceptCondition() && Optional(CSharpSymbolType.Semicolon))
+ {
+ Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
+ }
+ }
+ else
+ {
+ PutCurrentBack();
+ PutBack(ws);
+ }
+ }
+
+ private void UsingKeyword(bool topLevel)
+ {
+ Assert(CSharpKeyword.Using);
+ var block = new Block(CurrentSymbol);
+ AcceptAndMoveNext();
+ AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
+
+ if (At(CSharpSymbolType.LeftParenthesis))
+ {
+ // using ( ==> Using Statement
+ UsingStatement(block);
+ }
+ else if (At(CSharpSymbolType.Identifier) || At(CSharpKeyword.Static))
+ {
+ // using Identifier ==> Using Declaration
+ if (!topLevel)
+ {
+ Context.ErrorSink.OnError(
+ block.Start,
+ LegacyResources.ParseError_NamespaceImportAndTypeAlias_Cannot_Exist_Within_CodeBlock,
+ block.Name.Length);
+ StandardStatement();
+ }
+ else
+ {
+ UsingDeclaration();
+ }
+ }
+
+ if (topLevel)
+ {
+ CompleteBlock();
+ }
+ }
+
+ private void UsingDeclaration()
+ {
+ // Set block type to directive
+ Context.Builder.CurrentBlock.Type = BlockType.Directive;
+
+ if (At(CSharpSymbolType.Identifier))
+ {
+ // non-static using
+ NamespaceOrTypeName();
+ var whitespace = ReadWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
+ if (At(CSharpSymbolType.Assign))
+ {
+ // Alias
+ Accept(whitespace);
+ Assert(CSharpSymbolType.Assign);
+ AcceptAndMoveNext();
+
+ AcceptWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
+
+ // One more namespace or type name
+ NamespaceOrTypeName();
+ }
+ else
+ {
+ PutCurrentBack();
+ PutBack(whitespace);
+ }
+ }
+ else if (At(CSharpKeyword.Static))
+ {
+ // static using
+ AcceptAndMoveNext();
+ AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
+ NamespaceOrTypeName();
+ }
+
+ Span.EditHandler.AcceptedCharacters = AcceptedCharacters.AnyExceptNewline;
+ Span.ChunkGenerator = new AddImportChunkGenerator(
+ Span.GetContent(symbols => symbols.Skip(1)));
+
+ // Optional ";"
+ if (EnsureCurrent())
+ {
+ Optional(CSharpSymbolType.Semicolon);
+ }
+ }
+
+ protected bool NamespaceOrTypeName()
+ {
+ if (Optional(CSharpSymbolType.Identifier) || Optional(CSharpSymbolType.Keyword))
+ {
+ Optional(CSharpSymbolType.QuestionMark); // Nullable
+ if (Optional(CSharpSymbolType.DoubleColon))
+ {
+ if (!Optional(CSharpSymbolType.Identifier))
+ {
+ Optional(CSharpSymbolType.Keyword);
+ }
+ }
+ if (At(CSharpSymbolType.LessThan))
+ {
+ TypeArgumentList();
+ }
+ if (Optional(CSharpSymbolType.Dot))
+ {
+ NamespaceOrTypeName();
+ }
+ while (At(CSharpSymbolType.LeftBracket))
+ {
+ Balance(BalancingModes.None);
+ Optional(CSharpSymbolType.RightBracket);
+ }
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ private void TypeArgumentList()
+ {
+ Assert(CSharpSymbolType.LessThan);
+ Balance(BalancingModes.None);
+ Optional(CSharpSymbolType.GreaterThan);
+ }
+
+ private void UsingStatement(Block block)
+ {
+ Assert(CSharpSymbolType.LeftParenthesis);
+
+ // Parse condition
+ if (AcceptCondition())
+ {
+ AcceptWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
+
+ // Parse code block
+ ExpectCodeBlock(block);
+ }
+ }
+
+ private void TryStatement(bool topLevel)
+ {
+ Assert(CSharpKeyword.Try);
+ UnconditionalBlock();
+ AfterTryClause();
+ if (topLevel)
+ {
+ CompleteBlock();
+ }
+ }
+
+ private void IfStatement(bool topLevel)
+ {
+ Assert(CSharpKeyword.If);
+ ConditionalBlock(topLevel: false);
+ AfterIfClause();
+ if (topLevel)
+ {
+ CompleteBlock();
+ }
+ }
+
+ private void AfterTryClause()
+ {
+ // Grab whitespace
+ var whitespace = SkipToNextImportantToken();
+
+ // Check for a catch or finally part
+ if (At(CSharpKeyword.Catch))
+ {
+ Accept(whitespace);
+ Assert(CSharpKeyword.Catch);
+ FilterableCatchBlock();
+ AfterTryClause();
+ }
+ else if (At(CSharpKeyword.Finally))
+ {
+ Accept(whitespace);
+ Assert(CSharpKeyword.Finally);
+ UnconditionalBlock();
+ }
+ else
+ {
+ // Return whitespace and end the block
+ PutCurrentBack();
+ PutBack(whitespace);
+ Span.EditHandler.AcceptedCharacters = AcceptedCharacters.Any;
+ }
+ }
+
+ private void AfterIfClause()
+ {
+ // Grab whitespace and razor comments
+ IEnumerable ws = SkipToNextImportantToken();
+
+ // Check for an else part
+ if (At(CSharpKeyword.Else))
+ {
+ Accept(ws);
+ Assert(CSharpKeyword.Else);
+ ElseClause();
+ }
+ else
+ {
+ // No else, return whitespace
+ PutCurrentBack();
+ PutBack(ws);
+ Span.EditHandler.AcceptedCharacters = AcceptedCharacters.Any;
+ }
+ }
+
+ private void ElseClause()
+ {
+ if (!At(CSharpKeyword.Else))
+ {
+ return;
+ }
+ var block = new Block(CurrentSymbol);
+
+ AcceptAndMoveNext();
+ AcceptWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
+ if (At(CSharpKeyword.If))
+ {
+ // ElseIf
+ block.Name = SyntaxConstants.CSharp.ElseIfKeyword;
+ ConditionalBlock(block);
+ AfterIfClause();
+ }
+ else if (!EndOfFile)
+ {
+ // Else
+ ExpectCodeBlock(block);
+ }
+ }
+
+ private void ExpectCodeBlock(Block block)
+ {
+ if (!EndOfFile)
+ {
+ // Check for "{" to make sure we're at a block
+ if (!At(CSharpSymbolType.LeftBrace))
+ {
+ Context.ErrorSink.OnError(
+ CurrentLocation,
+ LegacyResources.FormatParseError_SingleLine_ControlFlowStatements_Not_Allowed(
+ Language.GetSample(CSharpSymbolType.LeftBrace),
+ CurrentSymbol.Content),
+ CurrentSymbol.Content.Length);
+ }
+
+ // Parse the statement and then we're done
+ Statement(block);
+ }
+ }
+
+ private void UnconditionalBlock()
+ {
+ Assert(CSharpSymbolType.Keyword);
+ var block = new Block(CurrentSymbol);
+ AcceptAndMoveNext();
+ AcceptWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
+ ExpectCodeBlock(block);
+ }
+
+ private void FilterableCatchBlock()
+ {
+ Assert(CSharpKeyword.Catch);
+
+ var block = new Block(CurrentSymbol);
+
+ // Accept "catch"
+ AcceptAndMoveNext();
+ AcceptWhile(IsValidStatementSpacingSymbol);
+
+ // Parse the catch condition if present. If not present, let the C# compiler complain.
+ if (AcceptCondition())
+ {
+ AcceptWhile(IsValidStatementSpacingSymbol);
+
+ if (At(CSharpKeyword.When))
+ {
+ // Accept "when".
+ AcceptAndMoveNext();
+ AcceptWhile(IsValidStatementSpacingSymbol);
+
+ // Parse the filter condition if present. If not present, let the C# compiler complain.
+ if (!AcceptCondition())
+ {
+ // Incomplete condition.
+ return;
+ }
+
+ AcceptWhile(IsValidStatementSpacingSymbol);
+ }
+
+ ExpectCodeBlock(block);
+ }
+ }
+
+ private void ConditionalBlock(bool topLevel)
+ {
+ Assert(CSharpSymbolType.Keyword);
+ var block = new Block(CurrentSymbol);
+ ConditionalBlock(block);
+ if (topLevel)
+ {
+ CompleteBlock();
+ }
+ }
+
+ private void ConditionalBlock(Block block)
+ {
+ AcceptAndMoveNext();
+ AcceptWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
+
+ // Parse the condition, if present (if not present, we'll let the C# compiler complain)
+ if (AcceptCondition())
+ {
+ AcceptWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
+ ExpectCodeBlock(block);
+ }
+ }
+
+ private bool AcceptCondition()
+ {
+ if (At(CSharpSymbolType.LeftParenthesis))
+ {
+ var complete = Balance(BalancingModes.BacktrackOnFailure | BalancingModes.AllowCommentsAndTemplates);
+ if (!complete)
+ {
+ AcceptUntil(CSharpSymbolType.NewLine);
+ }
+ else
+ {
+ Optional(CSharpSymbolType.RightParenthesis);
+ }
+ return complete;
+ }
+ return true;
+ }
+
+ private void Statement()
+ {
+ Statement(null);
+ }
+
+ private void Statement(Block block)
+ {
+ Span.EditHandler.AcceptedCharacters = AcceptedCharacters.Any;
+
+ // Accept whitespace but always keep the last whitespace node so we can put it back if necessary
+ var lastWhitespace = AcceptWhiteSpaceInLines();
+ Debug.Assert(lastWhitespace == null ||
+ (lastWhitespace.Start.AbsoluteIndex + lastWhitespace.Content.Length == CurrentLocation.AbsoluteIndex));
+
+ if (EndOfFile)
+ {
+ if (lastWhitespace != null)
+ {
+ Accept(lastWhitespace);
+ }
+ return;
+ }
+
+ var type = CurrentSymbol.Type;
+ var loc = CurrentLocation;
+
+ // Both cases @: and @:: are triggered as markup, second colon in second case will be triggered as a plain text
+ var isSingleLineMarkup = type == CSharpSymbolType.Transition &&
+ (NextIs(CSharpSymbolType.Colon, CSharpSymbolType.DoubleColon));
+
+ var isMarkup = isSingleLineMarkup ||
+ type == CSharpSymbolType.LessThan ||
+ (type == CSharpSymbolType.Transition && NextIs(CSharpSymbolType.LessThan));
+
+ if (Context.DesignTimeMode || !isMarkup)
+ {
+ // CODE owns whitespace, MARKUP owns it ONLY in DesignTimeMode.
+ if (lastWhitespace != null)
+ {
+ Accept(lastWhitespace);
+ }
+ }
+ else
+ {
+ var nextSymbol = Lookahead(1);
+
+ // MARKUP owns whitespace EXCEPT in DesignTimeMode.
+ PutCurrentBack();
+
+ // Put back the whitespace unless it precedes a '' tag.
+ if (nextSymbol != null &&
+ !string.Equals(nextSymbol.Content, SyntaxConstants.TextTagName, StringComparison.Ordinal))
+ {
+ PutBack(lastWhitespace);
+ }
+ }
+
+ if (isMarkup)
+ {
+ if (type == CSharpSymbolType.Transition && !isSingleLineMarkup)
+ {
+ Context.ErrorSink.OnError(
+ loc,
+ LegacyResources.ParseError_AtInCode_Must_Be_Followed_By_Colon_Paren_Or_Identifier_Start,
+ length: 1 /* @ */);
+ }
+
+ // Markup block
+ Output(SpanKind.Code);
+ if (Context.DesignTimeMode && CurrentSymbol != null &&
+ (CurrentSymbol.Type == CSharpSymbolType.LessThan || CurrentSymbol.Type == CSharpSymbolType.Transition))
+ {
+ PutCurrentBack();
+ }
+ OtherParserBlock();
+ }
+ else
+ {
+ // What kind of statement is this?
+ HandleStatement(block, type);
+ }
+ }
+
+ private void HandleStatement(Block block, CSharpSymbolType type)
+ {
+ switch (type)
+ {
+ case CSharpSymbolType.RazorCommentTransition:
+ Output(SpanKind.Code);
+ RazorComment();
+ Statement(block);
+ break;
+ case CSharpSymbolType.LeftBrace:
+ // Verbatim Block
+ block = block ?? new Block(LegacyResources.BlockName_Code, CurrentLocation);
+ AcceptAndMoveNext();
+ CodeBlock(block);
+ break;
+ case CSharpSymbolType.Keyword:
+ // Keyword block
+ HandleKeyword(false, StandardStatement);
+ break;
+ case CSharpSymbolType.Transition:
+ // Embedded Expression block
+ EmbeddedExpression();
+ break;
+ case CSharpSymbolType.RightBrace:
+ // Possible end of Code Block, just run the continuation
+ break;
+ case CSharpSymbolType.Comment:
+ AcceptAndMoveNext();
+ break;
+ default:
+ // Other statement
+ StandardStatement();
+ break;
+ }
+ }
+
+ private void EmbeddedExpression()
+ {
+ // First, verify the type of the block
+ Assert(CSharpSymbolType.Transition);
+ var transition = CurrentSymbol;
+ NextToken();
+
+ if (At(CSharpSymbolType.Transition))
+ {
+ // Escaped "@"
+ Output(SpanKind.Code);
+
+ // Output "@" as hidden span
+ Accept(transition);
+ Span.ChunkGenerator = SpanChunkGenerator.Null;
+ Output(SpanKind.Code);
+
+ Assert(CSharpSymbolType.Transition);
+ AcceptAndMoveNext();
+ StandardStatement();
+ }
+ else
+ {
+ // Throw errors as necessary, but continue parsing
+ if (At(CSharpSymbolType.LeftBrace))
+ {
+ Context.ErrorSink.OnError(
+ CurrentLocation,
+ LegacyResources.ParseError_Unexpected_Nested_CodeBlock,
+ length: 1 /* { */);
+ }
+
+ // @( or @foo - Nested expression, parse a child block
+ PutCurrentBack();
+ PutBack(transition);
+
+ // Before exiting, add a marker span if necessary
+ AddMarkerSymbolIfNecessary();
+
+ NestedBlock();
+ }
+ }
+
+ private void StandardStatement()
+ {
+ while (!EndOfFile)
+ {
+ var bookmark = CurrentLocation.AbsoluteIndex;
+ IEnumerable read = ReadWhile(sym => sym.Type != CSharpSymbolType.Semicolon &&
+ sym.Type != CSharpSymbolType.RazorCommentTransition &&
+ sym.Type != CSharpSymbolType.Transition &&
+ sym.Type != CSharpSymbolType.LeftBrace &&
+ sym.Type != CSharpSymbolType.LeftParenthesis &&
+ sym.Type != CSharpSymbolType.LeftBracket &&
+ sym.Type != CSharpSymbolType.RightBrace);
+ if (At(CSharpSymbolType.LeftBrace) ||
+ At(CSharpSymbolType.LeftParenthesis) ||
+ At(CSharpSymbolType.LeftBracket))
+ {
+ Accept(read);
+ if (Balance(BalancingModes.AllowCommentsAndTemplates | BalancingModes.BacktrackOnFailure))
+ {
+ Optional(CSharpSymbolType.RightBrace);
+ }
+ else
+ {
+ // Recovery
+ AcceptUntil(CSharpSymbolType.LessThan, CSharpSymbolType.RightBrace);
+ return;
+ }
+ }
+ else if (At(CSharpSymbolType.Transition) && (NextIs(CSharpSymbolType.LessThan, CSharpSymbolType.Colon)))
+ {
+ Accept(read);
+ Output(SpanKind.Code);
+ Template();
+ }
+ else if (At(CSharpSymbolType.RazorCommentTransition))
+ {
+ Accept(read);
+ RazorComment();
+ }
+ else if (At(CSharpSymbolType.Semicolon))
+ {
+ Accept(read);
+ AcceptAndMoveNext();
+ return;
+ }
+ else if (At(CSharpSymbolType.RightBrace))
+ {
+ Accept(read);
+ return;
+ }
+ else
+ {
+ Context.Source.Position = bookmark;
+ NextToken();
+ AcceptUntil(CSharpSymbolType.LessThan, CSharpSymbolType.LeftBrace, CSharpSymbolType.RightBrace);
+ return;
+ }
+ }
+ }
+
+ private void CodeBlock(Block block)
+ {
+ CodeBlock(true, block);
+ }
+
+ private void CodeBlock(bool acceptTerminatingBrace, Block block)
+ {
+ EnsureCurrent();
+ while (!EndOfFile && !At(CSharpSymbolType.RightBrace))
+ {
+ // Parse a statement, then return here
+ Statement();
+ EnsureCurrent();
+ }
+
+ if (EndOfFile)
+ {
+ Context.ErrorSink.OnError(
+ block.Start,
+ LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF(block.Name, '}', '{'),
+ length: 1 /* { OR } */);
+ }
+ else if (acceptTerminatingBrace)
+ {
+ Assert(CSharpSymbolType.RightBrace);
+ Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
+ AcceptAndMoveNext();
+ }
+ }
+
+ private void HandleKeyword(bool topLevel, Action fallback)
+ {
+ Debug.Assert(CurrentSymbol.Type == CSharpSymbolType.Keyword && CurrentSymbol.Keyword != null);
+ Action handler;
+ if (_keywordParsers.TryGetValue(CurrentSymbol.Keyword.Value, out handler))
+ {
+ handler(topLevel);
+ }
+ else
+ {
+ fallback();
+ }
+ }
+
+ private IEnumerable SkipToNextImportantToken()
+ {
+ while (!EndOfFile)
+ {
+ IEnumerable ws = ReadWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
+ if (At(CSharpSymbolType.RazorCommentTransition))
+ {
+ Accept(ws);
+ Span.EditHandler.AcceptedCharacters = AcceptedCharacters.Any;
+ RazorComment();
+ }
+ else
+ {
+ return ws;
+ }
+ }
+ return Enumerable.Empty();
+ }
+
+ // Common code for Parsers, but FxCop REALLY doesn't like it in the base class.. moving it here for now.
+ protected override void OutputSpanBeforeRazorComment()
+ {
+ AddMarkerSymbolIfNecessary();
+ Output(SpanKind.Code);
+ }
+
+ private void SetUpExpressions()
+ {
+ MapExpressionKeyword(AwaitExpression, CSharpKeyword.Await);
+ }
+
+ private void AwaitExpression(bool topLevel)
+ {
+ // Ensure that we're on the await statement (only runs in debug)
+ Assert(CSharpKeyword.Await);
+
+ // Accept the "await" and move on
+ AcceptAndMoveNext();
+
+ // Accept 1 or more spaces between the await and the following code.
+ AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
+
+ // Top level basically indicates if we're within an expression or statement.
+ // Ex: topLevel true = @await Foo() | topLevel false = @{ await Foo(); }
+ // Note that in this case @{ @await Foo() } top level is true for await.
+ // Therefore, if we're top level then we want to act like an implicit expression,
+ // otherwise just act as whatever we're contained in.
+ if (topLevel)
+ {
+ // Setup the Span to be an async implicit expression (an implicit expresison that allows spaces).
+ // Spaces are allowed because of "@await Foo()".
+ AsyncImplicitExpression();
+ }
+ }
+
+ private void SetupDirectives()
+ {
+ MapDirectives(TagHelperPrefixDirective, SyntaxConstants.CSharp.TagHelperPrefixKeyword);
+ MapDirectives(AddTagHelperDirective, SyntaxConstants.CSharp.AddTagHelperKeyword);
+ MapDirectives(RemoveTagHelperDirective, SyntaxConstants.CSharp.RemoveTagHelperKeyword);
+ MapDirectives(InheritsDirective, SyntaxConstants.CSharp.InheritsKeyword);
+ MapDirectives(FunctionsDirective, SyntaxConstants.CSharp.FunctionsKeyword);
+ MapDirectives(SectionDirective, SyntaxConstants.CSharp.SectionKeyword);
+ }
+
+ protected virtual void TagHelperPrefixDirective()
+ {
+ TagHelperDirective(
+ SyntaxConstants.CSharp.TagHelperPrefixKeyword,
+ prefix => new TagHelperPrefixDirectiveChunkGenerator(prefix));
+ }
+
+ protected virtual void AddTagHelperDirective()
+ {
+ TagHelperDirective(
+ SyntaxConstants.CSharp.AddTagHelperKeyword,
+ lookupText => new AddTagHelperChunkGenerator(lookupText));
+ }
+
+ protected virtual void RemoveTagHelperDirective()
+ {
+ TagHelperDirective(
+ SyntaxConstants.CSharp.RemoveTagHelperKeyword,
+ lookupText => new RemoveTagHelperChunkGenerator(lookupText));
+ }
+
+ protected virtual void SectionDirective()
+ {
+ var nested = Context.Builder.ActiveBlocks.Any(block => block.Type == BlockType.Section);
+ var errorReported = false;
+
+ // Set the block and span type
+ Context.Builder.CurrentBlock.Type = BlockType.Section;
+
+ // Verify we're on "section" and accept
+ AssertDirective(SyntaxConstants.CSharp.SectionKeyword);
+ var startLocation = CurrentLocation;
+ AcceptAndMoveNext();
+
+ if (nested)
+ {
+ Context.ErrorSink.OnError(
+ startLocation,
+ LegacyResources.FormatParseError_Sections_Cannot_Be_Nested(LegacyResources.SectionExample_CS),
+ Span.GetContent().Value.Length);
+ errorReported = true;
+ }
+
+ var whitespace = ReadWhile(IsSpacingToken(includeNewLines: true, includeComments: false));
+
+ // Get the section name
+ var sectionName = string.Empty;
+ if (!Required(CSharpSymbolType.Identifier,
+ errorIfNotFound: true,
+ errorBase: LegacyResources.FormatParseError_Unexpected_Character_At_Section_Name_Start))
+ {
+ if (!errorReported)
+ {
+ errorReported = true;
+ }
+
+ PutCurrentBack();
+ PutBack(whitespace);
+ AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: false));
+ }
+ else
+ {
+ Accept(whitespace);
+ sectionName = CurrentSymbol.Content;
+ AcceptAndMoveNext();
+ }
+ Context.Builder.CurrentBlock.ChunkGenerator = new SectionChunkGenerator(sectionName);
+
+ var errorLocation = CurrentLocation;
+ whitespace = ReadWhile(IsSpacingToken(includeNewLines: true, includeComments: false));
+
+ // Get the starting brace
+ var sawStartingBrace = At(CSharpSymbolType.LeftBrace);
+ if (!sawStartingBrace)
+ {
+ if (!errorReported)
+ {
+ errorReported = true;
+ Context.ErrorSink.OnError(
+ errorLocation,
+ LegacyResources.ParseError_MissingOpenBraceAfterSection,
+ length: 1 /* { */);
+ }
+
+ PutCurrentBack();
+ PutBack(whitespace);
+ AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: false));
+ Optional(CSharpSymbolType.NewLine);
+ Output(SpanKind.MetaCode);
+ CompleteBlock();
+ return;
+ }
+ else
+ {
+ Accept(whitespace);
+ }
+
+ var startingBraceLocation = CurrentLocation;
+
+ // Set up edit handler
+ var editHandler = new AutoCompleteEditHandler(Language.TokenizeString, autoCompleteAtEndOfSpan: true);
+
+ Span.EditHandler = editHandler;
+ Span.Accept(CurrentSymbol);
+
+ // Output Metacode then switch to section parser
+ Output(SpanKind.MetaCode);
+ SectionBlock("{", "}", caseSensitive: true);
+
+ Span.ChunkGenerator = SpanChunkGenerator.Null;
+ // Check for the terminating "}"
+ if (!Optional(CSharpSymbolType.RightBrace))
+ {
+ editHandler.AutoCompleteString = "}";
+ Context.ErrorSink.OnError(
+ startingBraceLocation,
+ LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF(
+ SyntaxConstants.CSharp.SectionKeyword,
+ Language.GetSample(CSharpSymbolType.RightBrace),
+ Language.GetSample(CSharpSymbolType.LeftBrace)),
+ length: 1 /* } */);
+ }
+ else
+ {
+ Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
+ }
+ CompleteBlock(insertMarkerIfNecessary: false, captureWhitespaceToEndOfLine: true);
+ Output(SpanKind.MetaCode);
+ return;
+ }
+
+ protected virtual void FunctionsDirective()
+ {
+ // Set the block type
+ Context.Builder.CurrentBlock.Type = BlockType.Functions;
+
+ // Verify we're on "functions" and accept
+ AssertDirective(SyntaxConstants.CSharp.FunctionsKeyword);
+ var block = new Block(CurrentSymbol);
+ AcceptAndMoveNext();
+
+ AcceptWhile(IsSpacingToken(includeNewLines: true, includeComments: false));
+
+ if (!At(CSharpSymbolType.LeftBrace))
+ {
+ Context.ErrorSink.OnError(
+ CurrentLocation,
+ LegacyResources.FormatParseError_Expected_X(Language.GetSample(CSharpSymbolType.LeftBrace)),
+ length: 1 /* { */);
+ CompleteBlock();
+ Output(SpanKind.MetaCode);
+ return;
+ }
+ else
+ {
+ Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
+ }
+
+ // Capture start point and continue
+ var blockStart = CurrentLocation;
+ AcceptAndMoveNext();
+
+ // Output what we've seen and continue
+ Output(SpanKind.MetaCode);
+
+ var editHandler = new AutoCompleteEditHandler(Language.TokenizeString);
+ Span.EditHandler = editHandler;
+
+ Balance(BalancingModes.NoErrorOnFailure, CSharpSymbolType.LeftBrace, CSharpSymbolType.RightBrace, blockStart);
+ Span.ChunkGenerator = new TypeMemberChunkGenerator();
+ if (!At(CSharpSymbolType.RightBrace))
+ {
+ editHandler.AutoCompleteString = "}";
+ Context.ErrorSink.OnError(
+ blockStart,
+ LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF(block.Name, "}", "{"),
+ length: 1 /* } */);
+ CompleteBlock();
+ Output(SpanKind.Code);
+ }
+ else
+ {
+ Output(SpanKind.Code);
+ Assert(CSharpSymbolType.RightBrace);
+ Span.ChunkGenerator = SpanChunkGenerator.Null;
+ Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
+ AcceptAndMoveNext();
+ CompleteBlock();
+ Output(SpanKind.MetaCode);
+ }
+ }
+
+ protected virtual void InheritsDirective()
+ {
+ // Verify we're on the right keyword and accept
+ AssertDirective(SyntaxConstants.CSharp.InheritsKeyword);
+ AcceptAndMoveNext();
+
+ InheritsDirectiveCore();
+ }
+
+ [Conditional("DEBUG")]
+ protected void AssertDirective(string directive)
+ {
+ Assert(CSharpSymbolType.Identifier);
+ Debug.Assert(string.Equals(CurrentSymbol.Content, directive, StringComparison.Ordinal));
+ }
+
+ protected void InheritsDirectiveCore()
+ {
+ BaseTypeDirective(
+ LegacyResources.ParseError_InheritsKeyword_Must_Be_Followed_By_TypeName,
+ baseType => new SetBaseTypeChunkGenerator(baseType));
+ }
+
+ protected void BaseTypeDirective(string noTypeNameError, Func createChunkGenerator)
+ {
+ var keywordStartLocation = Span.Start;
+
+ // Set the block type
+ Context.Builder.CurrentBlock.Type = BlockType.Directive;
+
+ var keywordLength = Span.GetContent().Value.Length;
+
+ // Accept whitespace
+ var remainingWhitespace = AcceptSingleWhiteSpaceCharacter();
+
+ if (Span.Symbols.Count > 1)
+ {
+ Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
+ }
+
+ Output(SpanKind.MetaCode);
+
+ if (remainingWhitespace != null)
+ {
+ Accept(remainingWhitespace);
+ }
+
+ AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
+
+ if (EndOfFile || At(CSharpSymbolType.WhiteSpace) || At(CSharpSymbolType.NewLine))
+ {
+ Context.ErrorSink.OnError(
+ keywordStartLocation,
+ noTypeNameError,
+ keywordLength);
+ }
+
+ // Parse to the end of the line
+ AcceptUntil(CSharpSymbolType.NewLine);
+ if (!Context.DesignTimeMode)
+ {
+ // We want the newline to be treated as code, but it causes issues at design-time.
+ Optional(CSharpSymbolType.NewLine);
+ }
+
+ // Pull out the type name
+ string baseType = Span.GetContent();
+
+ // Set up chunk generation
+ Span.ChunkGenerator = createChunkGenerator(baseType.Trim());
+
+ // Output the span and finish the block
+ CompleteBlock();
+ Output(SpanKind.Code, AcceptedCharacters.AnyExceptNewline);
+ }
+
+ private void TagHelperDirective(string keyword, Func chunkGeneratorFactory)
+ {
+ AssertDirective(keyword);
+ var keywordStartLocation = CurrentLocation;
+
+ // Accept the directive name
+ AcceptAndMoveNext();
+
+ // Set the block type
+ Context.Builder.CurrentBlock.Type = BlockType.Directive;
+
+ var keywordLength = Span.GetContent().Value.Length;
+
+ var foundWhitespace = At(CSharpSymbolType.WhiteSpace);
+ AcceptWhile(CSharpSymbolType.WhiteSpace);
+
+ // If we found whitespace then any content placed within the whitespace MAY cause a destructive change
+ // to the document. We can't accept it.
+ Output(SpanKind.MetaCode, foundWhitespace ? AcceptedCharacters.None : AcceptedCharacters.AnyExceptNewline);
+
+ ISpanChunkGenerator chunkGenerator;
+ if (EndOfFile || At(CSharpSymbolType.NewLine))
+ {
+ Context.ErrorSink.OnError(
+ keywordStartLocation,
+ LegacyResources.FormatParseError_DirectiveMustHaveValue(keyword),
+ keywordLength);
+
+ chunkGenerator = chunkGeneratorFactory(string.Empty);
+ }
+ else
+ {
+ // Need to grab the current location before we accept until the end of the line.
+ var startLocation = CurrentLocation;
+
+ // Parse to the end of the line. Essentially accepts anything until end of line, comments, invalid code
+ // etc.
+ AcceptUntil(CSharpSymbolType.NewLine);
+
+ // Pull out the value and remove whitespaces and optional quotes
+ var rawValue = Span.GetContent().Value.Trim();
+
+ var startsWithQuote = rawValue.StartsWith("\"", StringComparison.Ordinal);
+ var endsWithQuote = rawValue.EndsWith("\"", StringComparison.Ordinal);
+ if (startsWithQuote != endsWithQuote)
+ {
+ Context.ErrorSink.OnError(
+ startLocation,
+ LegacyResources.FormatParseError_IncompleteQuotesAroundDirective(keyword),
+ rawValue.Length);
+ }
+ else if (startsWithQuote)
+ {
+ if (rawValue.Length > 2)
+ {
+ // Remove extra quotes
+ rawValue = rawValue.Substring(1, rawValue.Length - 2);
+ }
+ else
+ {
+ // raw value is only quotes
+ rawValue = string.Empty;
+ }
+ }
+
+ chunkGenerator = chunkGeneratorFactory(rawValue);
+ }
+
+ Span.ChunkGenerator = chunkGenerator;
+
+ // Output the span and finish the block
+ CompleteBlock();
+ Output(SpanKind.Code, AcceptedCharacters.AnyExceptNewline);
+ }
+
+ protected class Block
+ {
+ public Block(string name, SourceLocation start)
+ {
+ Name = name;
+ Start = start;
+ }
+
+ public Block(CSharpSymbol symbol)
+ : this(GetName(symbol), symbol.Start)
+ {
+ }
+
+ public string Name { get; set; }
+ public SourceLocation Start { get; set; }
+
+ private static string GetName(CSharpSymbol sym)
+ {
+ if (sym.Type == CSharpSymbolType.Keyword)
+ {
+ return CSharpLanguageCharacteristics.GetKeyword(sym.Keyword.Value);
+ }
+ return sym.Content;
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/CSharpKeyword.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/CSharpKeyword.cs
new file mode 100644
index 0000000000..b1129253ef
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/CSharpKeyword.cs
@@ -0,0 +1,88 @@
+// 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.
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal enum CSharpKeyword
+ {
+ Await,
+ Abstract,
+ Byte,
+ Class,
+ Delegate,
+ Event,
+ Fixed,
+ If,
+ Internal,
+ New,
+ Override,
+ Readonly,
+ Short,
+ Struct,
+ Try,
+ Unsafe,
+ Volatile,
+ As,
+ Do,
+ Is,
+ Params,
+ Ref,
+ Switch,
+ Ushort,
+ While,
+ Case,
+ Const,
+ Explicit,
+ Float,
+ Null,
+ Sizeof,
+ Typeof,
+ Implicit,
+ Private,
+ This,
+ Using,
+ Extern,
+ Return,
+ Stackalloc,
+ Uint,
+ Base,
+ Catch,
+ Continue,
+ Double,
+ For,
+ In,
+ Lock,
+ Object,
+ Protected,
+ Static,
+ False,
+ Public,
+ Sbyte,
+ Throw,
+ Virtual,
+ Decimal,
+ Else,
+ Operator,
+ String,
+ Ulong,
+ Bool,
+ Char,
+ Default,
+ Foreach,
+ Long,
+ Void,
+ Enum,
+ Finally,
+ Int,
+ Out,
+ Sealed,
+ True,
+ Goto,
+ Unchecked,
+ Interface,
+ Break,
+ Checked,
+ Namespace,
+ When
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/CSharpLanguageCharacteristics.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/CSharpLanguageCharacteristics.cs
new file mode 100644
index 0000000000..0aa0fedd7e
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/CSharpLanguageCharacteristics.cs
@@ -0,0 +1,175 @@
+// 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.Collections.Generic;
+using System.Diagnostics;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal class CSharpLanguageCharacteristics : LanguageCharacteristics
+ {
+ private static readonly CSharpLanguageCharacteristics _instance = new CSharpLanguageCharacteristics();
+
+ private static Dictionary _symbolSamples = new Dictionary()
+ {
+ { CSharpSymbolType.Arrow, "->" },
+ { CSharpSymbolType.Minus, "-" },
+ { CSharpSymbolType.Decrement, "--" },
+ { CSharpSymbolType.MinusAssign, "-=" },
+ { CSharpSymbolType.NotEqual, "!=" },
+ { CSharpSymbolType.Not, "!" },
+ { CSharpSymbolType.Modulo, "%" },
+ { CSharpSymbolType.ModuloAssign, "%=" },
+ { CSharpSymbolType.AndAssign, "&=" },
+ { CSharpSymbolType.And, "&" },
+ { CSharpSymbolType.DoubleAnd, "&&" },
+ { CSharpSymbolType.LeftParenthesis, "(" },
+ { CSharpSymbolType.RightParenthesis, ")" },
+ { CSharpSymbolType.Star, "*" },
+ { CSharpSymbolType.MultiplyAssign, "*=" },
+ { CSharpSymbolType.Comma, "," },
+ { CSharpSymbolType.Dot, "." },
+ { CSharpSymbolType.Slash, "/" },
+ { CSharpSymbolType.DivideAssign, "/=" },
+ { CSharpSymbolType.DoubleColon, "::" },
+ { CSharpSymbolType.Colon, ":" },
+ { CSharpSymbolType.Semicolon, ";" },
+ { CSharpSymbolType.QuestionMark, "?" },
+ { CSharpSymbolType.NullCoalesce, "??" },
+ { CSharpSymbolType.RightBracket, "]" },
+ { CSharpSymbolType.LeftBracket, "[" },
+ { CSharpSymbolType.XorAssign, "^=" },
+ { CSharpSymbolType.Xor, "^" },
+ { CSharpSymbolType.LeftBrace, "{" },
+ { CSharpSymbolType.OrAssign, "|=" },
+ { CSharpSymbolType.DoubleOr, "||" },
+ { CSharpSymbolType.Or, "|" },
+ { CSharpSymbolType.RightBrace, "}" },
+ { CSharpSymbolType.Tilde, "~" },
+ { CSharpSymbolType.Plus, "+" },
+ { CSharpSymbolType.PlusAssign, "+=" },
+ { CSharpSymbolType.Increment, "++" },
+ { CSharpSymbolType.LessThan, "<" },
+ { CSharpSymbolType.LessThanEqual, "<=" },
+ { CSharpSymbolType.LeftShift, "<<" },
+ { CSharpSymbolType.LeftShiftAssign, "<<=" },
+ { CSharpSymbolType.Assign, "=" },
+ { CSharpSymbolType.Equals, "==" },
+ { CSharpSymbolType.GreaterThan, ">" },
+ { CSharpSymbolType.GreaterThanEqual, ">=" },
+ { CSharpSymbolType.RightShift, ">>" },
+ { CSharpSymbolType.RightShiftAssign, ">>>" },
+ { CSharpSymbolType.Hash, "#" },
+ { CSharpSymbolType.Transition, "@" },
+ };
+
+ private CSharpLanguageCharacteristics()
+ {
+ }
+
+ public static CSharpLanguageCharacteristics Instance => _instance;
+
+ public override CSharpTokenizer CreateTokenizer(ITextDocument source)
+ {
+ return new CSharpTokenizer(source);
+ }
+
+ protected override CSharpSymbol CreateSymbol(SourceLocation location, string content, CSharpSymbolType type, IReadOnlyList errors)
+ {
+ return new CSharpSymbol(location, content, type, errors);
+ }
+
+ public override string GetSample(CSharpSymbolType type)
+ {
+ string sample;
+ if (!_symbolSamples.TryGetValue(type, out sample))
+ {
+ switch (type)
+ {
+ case CSharpSymbolType.Identifier:
+ return LegacyResources.CSharpSymbol_Identifier;
+ case CSharpSymbolType.Keyword:
+ return LegacyResources.CSharpSymbol_Keyword;
+ case CSharpSymbolType.IntegerLiteral:
+ return LegacyResources.CSharpSymbol_IntegerLiteral;
+ case CSharpSymbolType.NewLine:
+ return LegacyResources.CSharpSymbol_Newline;
+ case CSharpSymbolType.WhiteSpace:
+ return LegacyResources.CSharpSymbol_Whitespace;
+ case CSharpSymbolType.Comment:
+ return LegacyResources.CSharpSymbol_Comment;
+ case CSharpSymbolType.RealLiteral:
+ return LegacyResources.CSharpSymbol_RealLiteral;
+ case CSharpSymbolType.CharacterLiteral:
+ return LegacyResources.CSharpSymbol_CharacterLiteral;
+ case CSharpSymbolType.StringLiteral:
+ return LegacyResources.CSharpSymbol_StringLiteral;
+ default:
+ return LegacyResources.Symbol_Unknown;
+ }
+ }
+ return sample;
+ }
+
+ public override CSharpSymbol CreateMarkerSymbol(SourceLocation location)
+ {
+ return new CSharpSymbol(location, string.Empty, CSharpSymbolType.Unknown);
+ }
+
+ public override CSharpSymbolType GetKnownSymbolType(KnownSymbolType type)
+ {
+ switch (type)
+ {
+ case KnownSymbolType.Identifier:
+ return CSharpSymbolType.Identifier;
+ case KnownSymbolType.Keyword:
+ return CSharpSymbolType.Keyword;
+ case KnownSymbolType.NewLine:
+ return CSharpSymbolType.NewLine;
+ case KnownSymbolType.WhiteSpace:
+ return CSharpSymbolType.WhiteSpace;
+ case KnownSymbolType.Transition:
+ return CSharpSymbolType.Transition;
+ case KnownSymbolType.CommentStart:
+ return CSharpSymbolType.RazorCommentTransition;
+ case KnownSymbolType.CommentStar:
+ return CSharpSymbolType.RazorCommentStar;
+ case KnownSymbolType.CommentBody:
+ return CSharpSymbolType.RazorComment;
+ default:
+ return CSharpSymbolType.Unknown;
+ }
+ }
+
+ public override CSharpSymbolType FlipBracket(CSharpSymbolType bracket)
+ {
+ switch (bracket)
+ {
+ case CSharpSymbolType.LeftBrace:
+ return CSharpSymbolType.RightBrace;
+ case CSharpSymbolType.LeftBracket:
+ return CSharpSymbolType.RightBracket;
+ case CSharpSymbolType.LeftParenthesis:
+ return CSharpSymbolType.RightParenthesis;
+ case CSharpSymbolType.LessThan:
+ return CSharpSymbolType.GreaterThan;
+ case CSharpSymbolType.RightBrace:
+ return CSharpSymbolType.LeftBrace;
+ case CSharpSymbolType.RightBracket:
+ return CSharpSymbolType.LeftBracket;
+ case CSharpSymbolType.RightParenthesis:
+ return CSharpSymbolType.LeftParenthesis;
+ case CSharpSymbolType.GreaterThan:
+ return CSharpSymbolType.LessThan;
+ default:
+ Debug.Fail("FlipBracket must be called with a bracket character");
+ return CSharpSymbolType.Unknown;
+ }
+ }
+
+ public static string GetKeyword(CSharpKeyword keyword)
+ {
+ return keyword.ToString().ToLowerInvariant();
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/CSharpSymbol.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/CSharpSymbol.cs
new file mode 100644
index 0000000000..2a4f3f1d59
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/CSharpSymbol.cs
@@ -0,0 +1,72 @@
+// 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.Evolution.Legacy
+{
+ internal class CSharpSymbol : SymbolBase
+ {
+ public CSharpSymbol(int absoluteIndex, int lineIndex, int characterIndex, string content, CSharpSymbolType type)
+ : this(new SourceLocation(absoluteIndex, lineIndex, characterIndex), content, type, RazorError.EmptyArray)
+ {
+ if (content == null)
+ {
+ throw new ArgumentNullException(nameof(content));
+ }
+ }
+
+ public CSharpSymbol(SourceLocation start, string content, CSharpSymbolType type)
+ : this(start, content, type, RazorError.EmptyArray)
+ {
+ if (content == null)
+ {
+ throw new ArgumentNullException(nameof(content));
+ }
+ }
+
+ public CSharpSymbol(
+ int offset,
+ int line,
+ int column,
+ string content,
+ CSharpSymbolType type,
+ IReadOnlyList errors)
+ : base(new SourceLocation(offset, line, column), content, type, errors)
+ {
+ if (content == null)
+ {
+ throw new ArgumentNullException(nameof(content));
+ }
+ }
+
+ public CSharpSymbol(
+ SourceLocation start,
+ string content,
+ CSharpSymbolType type,
+ IReadOnlyList errors)
+ : base(start, content, type, errors)
+ {
+ if (content == null)
+ {
+ throw new ArgumentNullException(nameof(content));
+ }
+ }
+
+ public CSharpKeyword? Keyword { get; set; }
+
+ public override bool Equals(object obj)
+ {
+ var other = obj as CSharpSymbol;
+ return base.Equals(other) &&
+ other.Keyword == Keyword;
+ }
+
+ public override int GetHashCode()
+ {
+ // Hash code should include only immutable properties.
+ return base.GetHashCode();
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/CSharpSymbolType.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/CSharpSymbolType.cs
new file mode 100644
index 0000000000..4ae06b192a
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/CSharpSymbolType.cs
@@ -0,0 +1,75 @@
+// 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.
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal enum CSharpSymbolType
+ {
+ Unknown,
+ Identifier,
+ Keyword,
+ IntegerLiteral,
+ NewLine,
+ WhiteSpace,
+ Comment,
+ RealLiteral,
+ CharacterLiteral,
+ StringLiteral,
+
+ // Operators
+ Arrow,
+ Minus,
+ Decrement,
+ MinusAssign,
+ NotEqual,
+ Not,
+ Modulo,
+ ModuloAssign,
+ AndAssign,
+ And,
+ DoubleAnd,
+ LeftParenthesis,
+ RightParenthesis,
+ Star,
+ MultiplyAssign,
+ Comma,
+ Dot,
+ Slash,
+ DivideAssign,
+ DoubleColon,
+ Colon,
+ Semicolon,
+ QuestionMark,
+ NullCoalesce,
+ RightBracket,
+ LeftBracket,
+ XorAssign,
+ Xor,
+ LeftBrace,
+ OrAssign,
+ DoubleOr,
+ Or,
+ RightBrace,
+ Tilde,
+ Plus,
+ PlusAssign,
+ Increment,
+ LessThan,
+ LessThanEqual,
+ LeftShift,
+ LeftShiftAssign,
+ Assign,
+ Equals,
+ GreaterThan,
+ GreaterThanEqual,
+ RightShift,
+ RightShiftAssign,
+ Hash,
+ Transition,
+
+ // Razor specific
+ RazorCommentTransition,
+ RazorCommentStar,
+ RazorComment
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/CSharpTokenizer.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/CSharpTokenizer.cs
new file mode 100644
index 0000000000..f7c1e08fbb
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/CSharpTokenizer.cs
@@ -0,0 +1,625 @@
+// 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;
+using System.Diagnostics;
+using System.Globalization;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal class CSharpTokenizer : Tokenizer
+ {
+ private Dictionary> _operatorHandlers;
+
+ private static readonly Dictionary _keywords = new Dictionary(StringComparer.Ordinal)
+ {
+ { "await", CSharpKeyword.Await },
+ { "abstract", CSharpKeyword.Abstract },
+ { "byte", CSharpKeyword.Byte },
+ { "class", CSharpKeyword.Class },
+ { "delegate", CSharpKeyword.Delegate },
+ { "event", CSharpKeyword.Event },
+ { "fixed", CSharpKeyword.Fixed },
+ { "if", CSharpKeyword.If },
+ { "internal", CSharpKeyword.Internal },
+ { "new", CSharpKeyword.New },
+ { "override", CSharpKeyword.Override },
+ { "readonly", CSharpKeyword.Readonly },
+ { "short", CSharpKeyword.Short },
+ { "struct", CSharpKeyword.Struct },
+ { "try", CSharpKeyword.Try },
+ { "unsafe", CSharpKeyword.Unsafe },
+ { "volatile", CSharpKeyword.Volatile },
+ { "as", CSharpKeyword.As },
+ { "do", CSharpKeyword.Do },
+ { "is", CSharpKeyword.Is },
+ { "params", CSharpKeyword.Params },
+ { "ref", CSharpKeyword.Ref },
+ { "switch", CSharpKeyword.Switch },
+ { "ushort", CSharpKeyword.Ushort },
+ { "while", CSharpKeyword.While },
+ { "case", CSharpKeyword.Case },
+ { "const", CSharpKeyword.Const },
+ { "explicit", CSharpKeyword.Explicit },
+ { "float", CSharpKeyword.Float },
+ { "null", CSharpKeyword.Null },
+ { "sizeof", CSharpKeyword.Sizeof },
+ { "typeof", CSharpKeyword.Typeof },
+ { "implicit", CSharpKeyword.Implicit },
+ { "private", CSharpKeyword.Private },
+ { "this", CSharpKeyword.This },
+ { "using", CSharpKeyword.Using },
+ { "extern", CSharpKeyword.Extern },
+ { "return", CSharpKeyword.Return },
+ { "stackalloc", CSharpKeyword.Stackalloc },
+ { "uint", CSharpKeyword.Uint },
+ { "base", CSharpKeyword.Base },
+ { "catch", CSharpKeyword.Catch },
+ { "continue", CSharpKeyword.Continue },
+ { "double", CSharpKeyword.Double },
+ { "for", CSharpKeyword.For },
+ { "in", CSharpKeyword.In },
+ { "lock", CSharpKeyword.Lock },
+ { "object", CSharpKeyword.Object },
+ { "protected", CSharpKeyword.Protected },
+ { "static", CSharpKeyword.Static },
+ { "false", CSharpKeyword.False },
+ { "public", CSharpKeyword.Public },
+ { "sbyte", CSharpKeyword.Sbyte },
+ { "throw", CSharpKeyword.Throw },
+ { "virtual", CSharpKeyword.Virtual },
+ { "decimal", CSharpKeyword.Decimal },
+ { "else", CSharpKeyword.Else },
+ { "operator", CSharpKeyword.Operator },
+ { "string", CSharpKeyword.String },
+ { "ulong", CSharpKeyword.Ulong },
+ { "bool", CSharpKeyword.Bool },
+ { "char", CSharpKeyword.Char },
+ { "default", CSharpKeyword.Default },
+ { "foreach", CSharpKeyword.Foreach },
+ { "long", CSharpKeyword.Long },
+ { "void", CSharpKeyword.Void },
+ { "enum", CSharpKeyword.Enum },
+ { "finally", CSharpKeyword.Finally },
+ { "int", CSharpKeyword.Int },
+ { "out", CSharpKeyword.Out },
+ { "sealed", CSharpKeyword.Sealed },
+ { "true", CSharpKeyword.True },
+ { "goto", CSharpKeyword.Goto },
+ { "unchecked", CSharpKeyword.Unchecked },
+ { "interface", CSharpKeyword.Interface },
+ { "break", CSharpKeyword.Break },
+ { "checked", CSharpKeyword.Checked },
+ { "namespace", CSharpKeyword.Namespace },
+ { "when", CSharpKeyword.When }
+ };
+
+ public CSharpTokenizer(ITextDocument source)
+ : base(source)
+ {
+ base.CurrentState = StartState;
+
+ _operatorHandlers = new Dictionary>()
+ {
+ { '-', MinusOperator },
+ { '<', LessThanOperator },
+ { '>', GreaterThanOperator },
+ { '&', CreateTwoCharOperatorHandler(CSharpSymbolType.And, '=', CSharpSymbolType.AndAssign, '&', CSharpSymbolType.DoubleAnd) },
+ { '|', CreateTwoCharOperatorHandler(CSharpSymbolType.Or, '=', CSharpSymbolType.OrAssign, '|', CSharpSymbolType.DoubleOr) },
+ { '+', CreateTwoCharOperatorHandler(CSharpSymbolType.Plus, '=', CSharpSymbolType.PlusAssign, '+', CSharpSymbolType.Increment) },
+ { '=', CreateTwoCharOperatorHandler(CSharpSymbolType.Assign, '=', CSharpSymbolType.Equals, '>', CSharpSymbolType.GreaterThanEqual) },
+ { '!', CreateTwoCharOperatorHandler(CSharpSymbolType.Not, '=', CSharpSymbolType.NotEqual) },
+ { '%', CreateTwoCharOperatorHandler(CSharpSymbolType.Modulo, '=', CSharpSymbolType.ModuloAssign) },
+ { '*', CreateTwoCharOperatorHandler(CSharpSymbolType.Star, '=', CSharpSymbolType.MultiplyAssign) },
+ { ':', CreateTwoCharOperatorHandler(CSharpSymbolType.Colon, ':', CSharpSymbolType.DoubleColon) },
+ { '?', CreateTwoCharOperatorHandler(CSharpSymbolType.QuestionMark, '?', CSharpSymbolType.NullCoalesce) },
+ { '^', CreateTwoCharOperatorHandler(CSharpSymbolType.Xor, '=', CSharpSymbolType.XorAssign) },
+ { '(', () => CSharpSymbolType.LeftParenthesis },
+ { ')', () => CSharpSymbolType.RightParenthesis },
+ { '{', () => CSharpSymbolType.LeftBrace },
+ { '}', () => CSharpSymbolType.RightBrace },
+ { '[', () => CSharpSymbolType.LeftBracket },
+ { ']', () => CSharpSymbolType.RightBracket },
+ { ',', () => CSharpSymbolType.Comma },
+ { ';', () => CSharpSymbolType.Semicolon },
+ { '~', () => CSharpSymbolType.Tilde },
+ { '#', () => CSharpSymbolType.Hash }
+ };
+ }
+
+ protected override int StartState => (int)CSharpTokenizerState.Data;
+
+ private new CSharpTokenizerState? CurrentState => (CSharpTokenizerState?)base.CurrentState;
+
+ public override CSharpSymbolType RazorCommentType => CSharpSymbolType.RazorComment;
+
+ public override CSharpSymbolType RazorCommentTransitionType => CSharpSymbolType.RazorCommentTransition;
+
+ public override CSharpSymbolType RazorCommentStarType => CSharpSymbolType.RazorCommentStar;
+
+ protected override StateResult Dispatch()
+ {
+ switch (CurrentState)
+ {
+ case CSharpTokenizerState.Data:
+ return Data();
+ case CSharpTokenizerState.BlockComment:
+ return BlockComment();
+ case CSharpTokenizerState.QuotedCharacterLiteral:
+ return QuotedCharacterLiteral();
+ case CSharpTokenizerState.QuotedStringLiteral:
+ return QuotedStringLiteral();
+ case CSharpTokenizerState.VerbatimStringLiteral:
+ return VerbatimStringLiteral();
+ case CSharpTokenizerState.AfterRazorCommentTransition:
+ return AfterRazorCommentTransition();
+ case CSharpTokenizerState.EscapedRazorCommentTransition:
+ return EscapedRazorCommentTransition();
+ case CSharpTokenizerState.RazorCommentBody:
+ return RazorCommentBody();
+ case CSharpTokenizerState.StarAfterRazorCommentBody:
+ return StarAfterRazorCommentBody();
+ case CSharpTokenizerState.AtSymbolAfterRazorCommentBody:
+ return AtSymbolAfterRazorCommentBody();
+ default:
+ Debug.Fail("Invalid TokenizerState");
+ return default(StateResult);
+ }
+ }
+
+ protected override CSharpSymbol CreateSymbol(SourceLocation start, string content, CSharpSymbolType type, IReadOnlyList errors)
+ {
+ return new CSharpSymbol(start, content, type, errors);
+ }
+
+ private StateResult Data()
+ {
+ if (ParserHelpers.IsNewLine(CurrentCharacter))
+ {
+ // CSharp Spec §2.3.1
+ var checkTwoCharNewline = CurrentCharacter == '\r';
+ TakeCurrent();
+ if (checkTwoCharNewline && CurrentCharacter == '\n')
+ {
+ TakeCurrent();
+ }
+ return Stay(EndSymbol(CSharpSymbolType.NewLine));
+ }
+ else if (ParserHelpers.IsWhitespace(CurrentCharacter))
+ {
+ // CSharp Spec §2.3.3
+ TakeUntil(c => !ParserHelpers.IsWhitespace(c));
+ return Stay(EndSymbol(CSharpSymbolType.WhiteSpace));
+ }
+ else if (IsIdentifierStart(CurrentCharacter))
+ {
+ return Identifier();
+ }
+ else if (char.IsDigit(CurrentCharacter))
+ {
+ return NumericLiteral();
+ }
+ switch (CurrentCharacter)
+ {
+ case '@':
+ return AtSymbol();
+ case '\'':
+ TakeCurrent();
+ return Transition(CSharpTokenizerState.QuotedCharacterLiteral);
+ case '"':
+ TakeCurrent();
+ return Transition(CSharpTokenizerState.QuotedStringLiteral);
+ case '.':
+ if (char.IsDigit(Peek()))
+ {
+ return RealLiteral();
+ }
+ return Stay(Single(CSharpSymbolType.Dot));
+ case '/':
+ TakeCurrent();
+ if (CurrentCharacter == '/')
+ {
+ TakeCurrent();
+ return SingleLineComment();
+ }
+ else if (CurrentCharacter == '*')
+ {
+ TakeCurrent();
+ return Transition(CSharpTokenizerState.BlockComment);
+ }
+ else if (CurrentCharacter == '=')
+ {
+ TakeCurrent();
+ return Stay(EndSymbol(CSharpSymbolType.DivideAssign));
+ }
+ else
+ {
+ return Stay(EndSymbol(CSharpSymbolType.Slash));
+ }
+ default:
+ return Stay(EndSymbol(Operator()));
+ }
+ }
+
+ private StateResult AtSymbol()
+ {
+ TakeCurrent();
+ if (CurrentCharacter == '"')
+ {
+ TakeCurrent();
+ return Transition(CSharpTokenizerState.VerbatimStringLiteral);
+ }
+ else if (CurrentCharacter == '*')
+ {
+ return Transition(
+ CSharpTokenizerState.AfterRazorCommentTransition,
+ EndSymbol(CSharpSymbolType.RazorCommentTransition));
+ }
+ else if (CurrentCharacter == '@')
+ {
+ // Could be escaped comment transition
+ return Transition(
+ CSharpTokenizerState.EscapedRazorCommentTransition,
+ EndSymbol(CSharpSymbolType.Transition));
+ }
+
+ return Stay(EndSymbol(CSharpSymbolType.Transition));
+ }
+
+ private StateResult EscapedRazorCommentTransition()
+ {
+ TakeCurrent();
+ return Transition(CSharpTokenizerState.Data, EndSymbol(CSharpSymbolType.Transition));
+ }
+
+ private CSharpSymbolType Operator()
+ {
+ var first = CurrentCharacter;
+ TakeCurrent();
+ Func handler;
+ if (_operatorHandlers.TryGetValue(first, out handler))
+ {
+ return handler();
+ }
+ return CSharpSymbolType.Unknown;
+ }
+
+ private CSharpSymbolType LessThanOperator()
+ {
+ if (CurrentCharacter == '=')
+ {
+ TakeCurrent();
+ return CSharpSymbolType.LessThanEqual;
+ }
+ return CSharpSymbolType.LessThan;
+ }
+
+ private CSharpSymbolType GreaterThanOperator()
+ {
+ if (CurrentCharacter == '=')
+ {
+ TakeCurrent();
+ return CSharpSymbolType.GreaterThanEqual;
+ }
+ return CSharpSymbolType.GreaterThan;
+ }
+
+ private CSharpSymbolType MinusOperator()
+ {
+ if (CurrentCharacter == '>')
+ {
+ TakeCurrent();
+ return CSharpSymbolType.Arrow;
+ }
+ else if (CurrentCharacter == '-')
+ {
+ TakeCurrent();
+ return CSharpSymbolType.Decrement;
+ }
+ else if (CurrentCharacter == '=')
+ {
+ TakeCurrent();
+ return CSharpSymbolType.MinusAssign;
+ }
+ return CSharpSymbolType.Minus;
+ }
+
+ private Func CreateTwoCharOperatorHandler(CSharpSymbolType typeIfOnlyFirst, char second, CSharpSymbolType typeIfBoth)
+ {
+ return () =>
+ {
+ if (CurrentCharacter == second)
+ {
+ TakeCurrent();
+ return typeIfBoth;
+ }
+ return typeIfOnlyFirst;
+ };
+ }
+
+ private Func CreateTwoCharOperatorHandler(CSharpSymbolType typeIfOnlyFirst, char option1, CSharpSymbolType typeIfOption1, char option2, CSharpSymbolType typeIfOption2)
+ {
+ return () =>
+ {
+ if (CurrentCharacter == option1)
+ {
+ TakeCurrent();
+ return typeIfOption1;
+ }
+ else if (CurrentCharacter == option2)
+ {
+ TakeCurrent();
+ return typeIfOption2;
+ }
+ return typeIfOnlyFirst;
+ };
+ }
+
+ private StateResult VerbatimStringLiteral()
+ {
+ TakeUntil(c => c == '"');
+ if (CurrentCharacter == '"')
+ {
+ TakeCurrent();
+ if (CurrentCharacter == '"')
+ {
+ TakeCurrent();
+ // Stay in the literal, this is an escaped "
+ return Stay();
+ }
+ }
+ else if (EndOfFile)
+ {
+ CurrentErrors.Add(
+ new RazorError(
+ LegacyResources.ParseError_Unterminated_String_Literal,
+ CurrentStart,
+ length: 1 /* end of file */));
+ }
+ return Transition(CSharpTokenizerState.Data, EndSymbol(CSharpSymbolType.StringLiteral));
+ }
+
+ private StateResult QuotedCharacterLiteral() => QuotedLiteral('\'', CSharpSymbolType.CharacterLiteral);
+
+ private StateResult QuotedStringLiteral() => QuotedLiteral('\"', CSharpSymbolType.StringLiteral);
+
+ private StateResult QuotedLiteral(char quote, CSharpSymbolType literalType)
+ {
+ TakeUntil(c => c == '\\' || c == quote || ParserHelpers.IsNewLine(c));
+ if (CurrentCharacter == '\\')
+ {
+ TakeCurrent(); // Take the '\'
+
+ // If the next char is the same quote that started this
+ if (CurrentCharacter == quote || CurrentCharacter == '\\')
+ {
+ TakeCurrent(); // Take it so that we don't prematurely end the literal.
+ }
+ return Stay();
+ }
+ else if (EndOfFile || ParserHelpers.IsNewLine(CurrentCharacter))
+ {
+ CurrentErrors.Add(
+ new RazorError(
+ LegacyResources.ParseError_Unterminated_String_Literal,
+ CurrentStart,
+ length: 1 /* " */));
+ }
+ else
+ {
+ TakeCurrent(); // No-op if at EOF
+ }
+ return Transition(CSharpTokenizerState.Data, EndSymbol(literalType));
+ }
+
+ // CSharp Spec §2.3.2
+ private StateResult BlockComment()
+ {
+ TakeUntil(c => c == '*');
+ if (EndOfFile)
+ {
+ CurrentErrors.Add(
+ new RazorError(
+ LegacyResources.ParseError_BlockComment_Not_Terminated,
+ CurrentStart,
+ length: 1 /* end of file */));
+ return Transition(CSharpTokenizerState.Data, EndSymbol(CSharpSymbolType.Comment));
+ }
+ if (CurrentCharacter == '*')
+ {
+ TakeCurrent();
+ if (CurrentCharacter == '/')
+ {
+ TakeCurrent();
+ return Transition(CSharpTokenizerState.Data, EndSymbol(CSharpSymbolType.Comment));
+ }
+ }
+ return Stay();
+ }
+
+ // CSharp Spec §2.3.2
+ private StateResult SingleLineComment()
+ {
+ TakeUntil(c => ParserHelpers.IsNewLine(c));
+ return Stay(EndSymbol(CSharpSymbolType.Comment));
+ }
+
+ // CSharp Spec §2.4.4
+ private StateResult NumericLiteral()
+ {
+ if (TakeAll("0x", caseSensitive: true))
+ {
+ return HexLiteral();
+ }
+ else
+ {
+ return DecimalLiteral();
+ }
+ }
+
+ private StateResult HexLiteral()
+ {
+ TakeUntil(c => !IsHexDigit(c));
+ TakeIntegerSuffix();
+ return Stay(EndSymbol(CSharpSymbolType.IntegerLiteral));
+ }
+
+ private StateResult DecimalLiteral()
+ {
+ TakeUntil(c => !Char.IsDigit(c));
+ if (CurrentCharacter == '.' && Char.IsDigit(Peek()))
+ {
+ return RealLiteral();
+ }
+ else if (IsRealLiteralSuffix(CurrentCharacter) ||
+ CurrentCharacter == 'E' || CurrentCharacter == 'e')
+ {
+ return RealLiteralExponentPart();
+ }
+ else
+ {
+ TakeIntegerSuffix();
+ return Stay(EndSymbol(CSharpSymbolType.IntegerLiteral));
+ }
+ }
+
+ private StateResult RealLiteralExponentPart()
+ {
+ if (CurrentCharacter == 'E' || CurrentCharacter == 'e')
+ {
+ TakeCurrent();
+ if (CurrentCharacter == '+' || CurrentCharacter == '-')
+ {
+ TakeCurrent();
+ }
+ TakeUntil(c => !Char.IsDigit(c));
+ }
+ if (IsRealLiteralSuffix(CurrentCharacter))
+ {
+ TakeCurrent();
+ }
+ return Stay(EndSymbol(CSharpSymbolType.RealLiteral));
+ }
+
+ // CSharp Spec §2.4.4.3
+ private StateResult RealLiteral()
+ {
+ AssertCurrent('.');
+ TakeCurrent();
+ Debug.Assert(Char.IsDigit(CurrentCharacter));
+ TakeUntil(c => !Char.IsDigit(c));
+ return RealLiteralExponentPart();
+ }
+
+ private void TakeIntegerSuffix()
+ {
+ if (Char.ToLowerInvariant(CurrentCharacter) == 'u')
+ {
+ TakeCurrent();
+ if (Char.ToLowerInvariant(CurrentCharacter) == 'l')
+ {
+ TakeCurrent();
+ }
+ }
+ else if (Char.ToLowerInvariant(CurrentCharacter) == 'l')
+ {
+ TakeCurrent();
+ if (Char.ToLowerInvariant(CurrentCharacter) == 'u')
+ {
+ TakeCurrent();
+ }
+ }
+ }
+
+ // CSharp Spec §2.4.2
+ private StateResult Identifier()
+ {
+ Debug.Assert(IsIdentifierStart(CurrentCharacter));
+ TakeCurrent();
+ TakeUntil(c => !IsIdentifierPart(c));
+ CSharpSymbol symbol = null;
+ if (HaveContent)
+ {
+ CSharpKeyword keyword;
+ var type = CSharpSymbolType.Identifier;
+ if (_keywords.TryGetValue(Buffer.ToString(), out keyword))
+ {
+ type = CSharpSymbolType.Keyword;
+ }
+
+ symbol = new CSharpSymbol(CurrentStart, Buffer.ToString(), type)
+ {
+ Keyword = type == CSharpSymbolType.Keyword ? (CSharpKeyword?)keyword : null,
+ };
+ }
+ StartSymbol();
+ return Stay(symbol);
+ }
+
+ private StateResult Transition(CSharpTokenizerState state)
+ {
+ return Transition((int)state, result: null);
+ }
+
+ private StateResult Transition(CSharpTokenizerState state, CSharpSymbol result)
+ {
+ return Transition((int)state, result);
+ }
+
+ private static bool IsIdentifierStart(char character)
+ {
+ return char.IsLetter(character) ||
+ character == '_' ||
+ CharUnicodeInfo.GetUnicodeCategory(character) == UnicodeCategory.LetterNumber;
+ }
+
+ private static bool IsIdentifierPart(char character)
+ {
+ return char.IsDigit(character) ||
+ IsIdentifierStart(character) ||
+ IsIdentifierPartByUnicodeCategory(character);
+ }
+
+ private static bool IsRealLiteralSuffix(char character)
+ {
+ return character == 'F' ||
+ character == 'f' ||
+ character == 'D' ||
+ character == 'd' ||
+ character == 'M' ||
+ character == 'm';
+ }
+
+ private static bool IsIdentifierPartByUnicodeCategory(char character)
+ {
+ var category = CharUnicodeInfo.GetUnicodeCategory(character);
+
+ return category == UnicodeCategory.NonSpacingMark || // Mn
+ category == UnicodeCategory.SpacingCombiningMark || // Mc
+ category == UnicodeCategory.ConnectorPunctuation || // Pc
+ category == UnicodeCategory.Format; // Cf
+ }
+
+ private static bool IsHexDigit(char value)
+ {
+ return (value >= '0' && value <= '9') || (value >= 'A' && value <= 'F') || (value >= 'a' && value <= 'f');
+ }
+
+ private enum CSharpTokenizerState
+ {
+ Data,
+ BlockComment,
+ QuotedCharacterLiteral,
+ QuotedStringLiteral,
+ VerbatimStringLiteral,
+
+ // Razor Comments - need to be the same for HTML and CSharp
+ AfterRazorCommentTransition = RazorCommentTokenizerState.AfterRazorCommentTransition,
+ EscapedRazorCommentTransition = RazorCommentTokenizerState.EscapedRazorCommentTransition,
+ RazorCommentBody = RazorCommentTokenizerState.RazorCommentBody,
+ StarAfterRazorCommentBody = RazorCommentTokenizerState.StarAfterRazorCommentBody,
+ AtSymbolAfterRazorCommentBody = RazorCommentTokenizerState.AtSymbolAfterRazorCommentBody,
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/ChunkGeneratorContext.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/ChunkGeneratorContext.cs
new file mode 100644
index 0000000000..1085c72724
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/ChunkGeneratorContext.cs
@@ -0,0 +1,25 @@
+// 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.
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal class ChunkGeneratorContext
+ {
+ public ChunkGeneratorContext(
+ string className,
+ string rootNamespace,
+ string sourceFile,
+ bool shouldGenerateLinePragmas)
+ {
+ SourceFile = shouldGenerateLinePragmas ? sourceFile : null;
+ RootNamespace = rootNamespace;
+ ClassName = className;
+ }
+
+ public string SourceFile { get; internal set; }
+
+ public string RootNamespace { get; }
+
+ public string ClassName { get; }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/DisposableAction.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/DisposableAction.cs
new file mode 100644
index 0000000000..c0bacf7826
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/DisposableAction.cs
@@ -0,0 +1,32 @@
+// 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;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal class DisposableAction : IDisposable
+ {
+ private readonly Action _action;
+ private bool _invoked;
+
+ public DisposableAction(Action action)
+ {
+ if (action == null)
+ {
+ throw new ArgumentNullException(nameof(action));
+ }
+
+ _action = action;
+ }
+
+ public void Dispose()
+ {
+ if (!_invoked)
+ {
+ _action();
+ _invoked = true;
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/DynamicAttributeBlockChunkGenerator.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/DynamicAttributeBlockChunkGenerator.cs
new file mode 100644
index 0000000000..c1d790ee4e
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/DynamicAttributeBlockChunkGenerator.cs
@@ -0,0 +1,54 @@
+// 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.Globalization;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal class DynamicAttributeBlockChunkGenerator : ParentChunkGenerator
+ {
+ public DynamicAttributeBlockChunkGenerator(LocationTagged prefix, int offset, int line, int col)
+ : this(prefix, new SourceLocation(offset, line, col))
+ {
+ }
+
+ public DynamicAttributeBlockChunkGenerator(LocationTagged prefix, SourceLocation valueStart)
+ {
+ Prefix = prefix;
+ ValueStart = valueStart;
+ }
+
+ public LocationTagged Prefix { get; }
+
+ public SourceLocation ValueStart { get; }
+
+ public override void GenerateStartParentChunk(Block target, ChunkGeneratorContext context)
+ {
+ //var chunk = context.ChunkTreeBuilder.StartParentChunk(target);
+ //chunk.Start = ValueStart;
+ //chunk.Prefix = Prefix;
+ }
+
+ public override void GenerateEndParentChunk(Block target, ChunkGeneratorContext context)
+ {
+ //context.ChunkTreeBuilder.EndParentChunk();
+ }
+
+ public override string ToString()
+ {
+ return string.Format(CultureInfo.CurrentCulture, "DynAttr:{0:F}", Prefix);
+ }
+
+ public override bool Equals(object obj)
+ {
+ var other = obj as DynamicAttributeBlockChunkGenerator;
+ return other != null &&
+ Equals(other.Prefix, Prefix);
+ }
+
+ public override int GetHashCode()
+ {
+ return Prefix == null ? 0 : Prefix.GetHashCode();
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/EditResult.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/EditResult.cs
new file mode 100644
index 0000000000..b54264592a
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/EditResult.cs
@@ -0,0 +1,17 @@
+// 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.
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal class EditResult
+ {
+ public EditResult(PartialParseResult result, SpanBuilder editedSpan)
+ {
+ Result = result;
+ EditedSpan = editedSpan;
+ }
+
+ public PartialParseResult Result { get; set; }
+ public SpanBuilder EditedSpan { get; set; }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/ErrorSink.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/ErrorSink.cs
new file mode 100644
index 0000000000..a048078561
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/ErrorSink.cs
@@ -0,0 +1,46 @@
+// 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.Collections.Generic;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ ///
+ /// Used to manage s encountered during the Razor parsing phase.
+ ///
+ internal class ErrorSink
+ {
+ private readonly List _errors;
+
+ ///
+ /// Instantiates a new instance of .
+ ///
+ public ErrorSink()
+ {
+ _errors = new List();
+ }
+
+ ///
+ /// s collected.
+ ///
+ public IEnumerable Errors => _errors;
+
+ ///
+ /// Tracks the given .
+ ///
+ /// The to track.
+ public void OnError(RazorError error) =>_errors.Add(error);
+
+ ///
+ /// Creates and tracks a new .
+ ///
+ /// of the error.
+ /// A message describing the error.
+ /// The length of the error.
+ public void OnError(SourceLocation location, string message, int length)
+ {
+ var error = new RazorError(message, location, length);
+ _errors.Add(error);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/ExpressionChunkGenerator.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/ExpressionChunkGenerator.cs
new file mode 100644
index 0000000000..4bc1e7789b
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/ExpressionChunkGenerator.cs
@@ -0,0 +1,41 @@
+// 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.
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal class ExpressionChunkGenerator : ISpanChunkGenerator, IParentChunkGenerator
+ {
+ private static readonly int TypeHashCode = typeof(ExpressionChunkGenerator).GetHashCode();
+
+ public void GenerateStartParentChunk(Block target, ChunkGeneratorContext context)
+ {
+ //context.ChunkTreeBuilder.StartParentChunk(target);
+ }
+
+ public void GenerateChunk(Span target, ChunkGeneratorContext context)
+ {
+ //context.ChunkTreeBuilder.AddExpressionChunk(target.Content, target);
+ }
+
+ public void GenerateEndParentChunk(Block target, ChunkGeneratorContext context)
+ {
+ //context.ChunkTreeBuilder.EndParentChunk();
+ }
+
+ public override string ToString()
+ {
+ return "Expr";
+ }
+
+ public override bool Equals(object obj)
+ {
+ return obj != null &&
+ GetType() == obj.GetType();
+ }
+
+ public override int GetHashCode()
+ {
+ return TypeHashCode;
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/HtmlLanguageCharacteristics.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/HtmlLanguageCharacteristics.cs
new file mode 100644
index 0000000000..db0126990b
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/HtmlLanguageCharacteristics.cs
@@ -0,0 +1,133 @@
+// 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.Collections.Generic;
+using System.Diagnostics;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal class HtmlLanguageCharacteristics : LanguageCharacteristics
+ {
+ private static readonly HtmlLanguageCharacteristics _instance = new HtmlLanguageCharacteristics();
+
+ private HtmlLanguageCharacteristics()
+ {
+ }
+
+ public static HtmlLanguageCharacteristics Instance
+ {
+ get { return _instance; }
+ }
+
+ public override string GetSample(HtmlSymbolType type)
+ {
+ switch (type)
+ {
+ case HtmlSymbolType.Text:
+ return LegacyResources.HtmlSymbol_Text;
+ case HtmlSymbolType.WhiteSpace:
+ return LegacyResources.HtmlSymbol_WhiteSpace;
+ case HtmlSymbolType.NewLine:
+ return LegacyResources.HtmlSymbol_NewLine;
+ case HtmlSymbolType.OpenAngle:
+ return "<";
+ case HtmlSymbolType.Bang:
+ return "!";
+ case HtmlSymbolType.ForwardSlash:
+ return "/";
+ case HtmlSymbolType.QuestionMark:
+ return "?";
+ case HtmlSymbolType.DoubleHyphen:
+ return "--";
+ case HtmlSymbolType.LeftBracket:
+ return "[";
+ case HtmlSymbolType.CloseAngle:
+ return ">";
+ case HtmlSymbolType.RightBracket:
+ return "]";
+ case HtmlSymbolType.Equals:
+ return "=";
+ case HtmlSymbolType.DoubleQuote:
+ return "\"";
+ case HtmlSymbolType.SingleQuote:
+ return "'";
+ case HtmlSymbolType.Transition:
+ return "@";
+ case HtmlSymbolType.Colon:
+ return ":";
+ case HtmlSymbolType.RazorComment:
+ return LegacyResources.HtmlSymbol_RazorComment;
+ case HtmlSymbolType.RazorCommentStar:
+ return "*";
+ case HtmlSymbolType.RazorCommentTransition:
+ return "@";
+ default:
+ return LegacyResources.Symbol_Unknown;
+ }
+ }
+
+ public override HtmlTokenizer CreateTokenizer(ITextDocument source)
+ {
+ return new HtmlTokenizer(source);
+ }
+
+ public override HtmlSymbolType FlipBracket(HtmlSymbolType bracket)
+ {
+ switch (bracket)
+ {
+ case HtmlSymbolType.LeftBracket:
+ return HtmlSymbolType.RightBracket;
+ case HtmlSymbolType.OpenAngle:
+ return HtmlSymbolType.CloseAngle;
+ case HtmlSymbolType.RightBracket:
+ return HtmlSymbolType.LeftBracket;
+ case HtmlSymbolType.CloseAngle:
+ return HtmlSymbolType.OpenAngle;
+ default:
+#if NET451
+ // No Debug.Fail in CoreCLR
+
+ Debug.Fail("FlipBracket must be called with a bracket character");
+#else
+ Debug.Assert(false, "FlipBracket must be called with a bracket character");
+#endif
+ return HtmlSymbolType.Unknown;
+ }
+ }
+
+ public override HtmlSymbol CreateMarkerSymbol(SourceLocation location)
+ {
+ return new HtmlSymbol(location, string.Empty, HtmlSymbolType.Unknown);
+ }
+
+ public override HtmlSymbolType GetKnownSymbolType(KnownSymbolType type)
+ {
+ switch (type)
+ {
+ case KnownSymbolType.CommentStart:
+ return HtmlSymbolType.RazorCommentTransition;
+ case KnownSymbolType.CommentStar:
+ return HtmlSymbolType.RazorCommentStar;
+ case KnownSymbolType.CommentBody:
+ return HtmlSymbolType.RazorComment;
+ case KnownSymbolType.Identifier:
+ return HtmlSymbolType.Text;
+ case KnownSymbolType.Keyword:
+ return HtmlSymbolType.Text;
+ case KnownSymbolType.NewLine:
+ return HtmlSymbolType.NewLine;
+ case KnownSymbolType.Transition:
+ return HtmlSymbolType.Transition;
+ case KnownSymbolType.WhiteSpace:
+ return HtmlSymbolType.WhiteSpace;
+ default:
+ return HtmlSymbolType.Unknown;
+ }
+ }
+
+ protected override HtmlSymbol CreateSymbol(SourceLocation location, string content, HtmlSymbolType type, IReadOnlyList errors)
+ {
+ return new HtmlSymbol(location, content, type, errors);
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/HtmlMarkupParser.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/HtmlMarkupParser.cs
new file mode 100644
index 0000000000..82753dfb6e
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/HtmlMarkupParser.cs
@@ -0,0 +1,1757 @@
+// 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;
+using System.Diagnostics;
+using System.Linq;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal class HtmlMarkupParser : TokenizerBackedParser
+ {
+ private const string ScriptTagName = "script";
+
+ private static readonly char[] ValidAfterTypeAttributeNameCharacters = { ' ', '\t', '\r', '\n', '\f', '=' };
+ private SourceLocation _lastTagStart = SourceLocation.Zero;
+ private HtmlSymbol _bufferedOpenAngle;
+
+ //From http://dev.w3.org/html5/spec/Overview.html#elements-0
+ private ISet _voidElements = new HashSet(StringComparer.OrdinalIgnoreCase)
+ {
+ "area",
+ "base",
+ "br",
+ "col",
+ "command",
+ "embed",
+ "hr",
+ "img",
+ "input",
+ "keygen",
+ "link",
+ "meta",
+ "param",
+ "source",
+ "track",
+ "wbr"
+ };
+
+ public HtmlMarkupParser(ParserContext context)
+ : base(HtmlLanguageCharacteristics.Instance, context)
+ {
+ }
+
+ public ParserBase CodeParser { get; set; }
+
+ public ISet VoidElements
+ {
+ get { return _voidElements; }
+ }
+
+ private bool CaseSensitive { get; set; }
+
+ private StringComparison Comparison
+ {
+ get { return CaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; }
+ }
+
+ protected override bool SymbolTypeEquals(HtmlSymbolType x, HtmlSymbolType y) => x == y;
+
+ public override void BuildSpan(SpanBuilder span, SourceLocation start, string content)
+ {
+ span.Kind = SpanKind.Markup;
+ span.ChunkGenerator = new MarkupChunkGenerator();
+ base.BuildSpan(span, start, content);
+ }
+
+ protected override void OutputSpanBeforeRazorComment()
+ {
+ Output(SpanKind.Markup);
+ }
+
+ protected void SkipToAndParseCode(HtmlSymbolType type)
+ {
+ SkipToAndParseCode(sym => sym.Type == type);
+ }
+
+ protected void SkipToAndParseCode(Func condition)
+ {
+ HtmlSymbol last = null;
+ var startOfLine = false;
+ while (!EndOfFile && !condition(CurrentSymbol))
+ {
+ if (Context.NullGenerateWhitespaceAndNewLine)
+ {
+ Context.NullGenerateWhitespaceAndNewLine = false;
+ Span.ChunkGenerator = SpanChunkGenerator.Null;
+ AcceptWhile(symbol => symbol.Type == HtmlSymbolType.WhiteSpace);
+ if (At(HtmlSymbolType.NewLine))
+ {
+ AcceptAndMoveNext();
+ }
+
+ Output(SpanKind.Markup);
+ }
+ else if (At(HtmlSymbolType.NewLine))
+ {
+ if (last != null)
+ {
+ Accept(last);
+ }
+
+ // Mark the start of a new line
+ startOfLine = true;
+ last = null;
+ AcceptAndMoveNext();
+ }
+ else if (At(HtmlSymbolType.Transition))
+ {
+ var transition = CurrentSymbol;
+ NextToken();
+ if (At(HtmlSymbolType.Transition))
+ {
+ if (last != null)
+ {
+ Accept(last);
+ last = null;
+ }
+ Output(SpanKind.Markup);
+ Accept(transition);
+ Span.ChunkGenerator = SpanChunkGenerator.Null;
+ Output(SpanKind.Markup);
+ AcceptAndMoveNext();
+ continue; // while
+ }
+ else
+ {
+ if (!EndOfFile)
+ {
+ PutCurrentBack();
+ }
+ PutBack(transition);
+ }
+
+ // Handle whitespace rewriting
+ if (last != null)
+ {
+ if (!Context.DesignTimeMode && last.Type == HtmlSymbolType.WhiteSpace && startOfLine)
+ {
+ // Put the whitespace back too
+ startOfLine = false;
+ PutBack(last);
+ last = null;
+ }
+ else
+ {
+ // Accept last
+ Accept(last);
+ last = null;
+ }
+ }
+
+ OtherParserBlock();
+ }
+ else if (At(HtmlSymbolType.RazorCommentTransition))
+ {
+ if (last != null)
+ {
+ // Don't render the whitespace between the start of the line and the razor comment.
+ if (startOfLine && last.Type == HtmlSymbolType.WhiteSpace)
+ {
+ AddMarkerSymbolIfNecessary();
+ // Output the symbols that may have been accepted prior to the whitespace.
+ Output(SpanKind.Markup);
+
+ Span.ChunkGenerator = SpanChunkGenerator.Null;
+ }
+
+ Accept(last);
+ last = null;
+ }
+
+ AddMarkerSymbolIfNecessary();
+ Output(SpanKind.Markup);
+
+ RazorComment();
+
+ // Handle the whitespace and newline at the end of a razor comment.
+ if (startOfLine &&
+ (At(HtmlSymbolType.NewLine) ||
+ (At(HtmlSymbolType.WhiteSpace) && NextIs(HtmlSymbolType.NewLine))))
+ {
+ AcceptWhile(IsSpacingToken(includeNewLines: false));
+ AcceptAndMoveNext();
+ Span.ChunkGenerator = SpanChunkGenerator.Null;
+ Output(SpanKind.Markup);
+ }
+ }
+ else
+ {
+ // As long as we see whitespace, we're still at the "start" of the line
+ startOfLine &= At(HtmlSymbolType.WhiteSpace);
+
+ // If there's a last token, accept it
+ if (last != null)
+ {
+ Accept(last);
+ last = null;
+ }
+
+ // Advance
+ last = CurrentSymbol;
+ NextToken();
+ }
+ }
+
+ if (last != null)
+ {
+ Accept(last);
+ }
+ }
+
+ protected static Func IsSpacingToken(bool includeNewLines)
+ {
+ return sym => sym.Type == HtmlSymbolType.WhiteSpace || (includeNewLines && sym.Type == HtmlSymbolType.NewLine);
+ }
+
+ private void OtherParserBlock()
+ {
+ AddMarkerSymbolIfNecessary();
+ Output(SpanKind.Markup);
+ using (PushSpanConfig())
+ {
+ CodeParser.ParseBlock();
+ }
+ Initialize(Span);
+ NextToken();
+ }
+
+ private bool IsBangEscape(int lookahead)
+ {
+ var potentialBang = Lookahead(lookahead);
+
+ if (potentialBang != null &&
+ potentialBang.Type == HtmlSymbolType.Bang)
+ {
+ var afterBang = Lookahead(lookahead + 1);
+
+ return afterBang != null &&
+ afterBang.Type == HtmlSymbolType.Text &&
+ !string.Equals(afterBang.Content, "DOCTYPE", StringComparison.OrdinalIgnoreCase);
+ }
+
+ return false;
+ }
+
+ private void OptionalBangEscape()
+ {
+ if (IsBangEscape(lookahead: 0))
+ {
+ Output(SpanKind.Markup);
+
+ // Accept the parser escape character '!'.
+ Assert(HtmlSymbolType.Bang);
+ AcceptAndMoveNext();
+
+ // Setup the metacode span that we will be outputing.
+ Span.ChunkGenerator = SpanChunkGenerator.Null;
+ Output(SpanKind.MetaCode, AcceptedCharacters.None);
+ }
+ }
+
+ public override void ParseBlock()
+ {
+ if (Context == null)
+ {
+ throw new InvalidOperationException(LegacyResources.Parser_Context_Not_Set);
+ }
+
+ using (PushSpanConfig(DefaultMarkupSpan))
+ {
+ using (Context.Builder.StartBlock(BlockType.Markup))
+ {
+ if (!NextToken())
+ {
+ return;
+ }
+
+ AcceptWhile(IsSpacingToken(includeNewLines: true));
+
+ if (CurrentSymbol.Type == HtmlSymbolType.OpenAngle)
+ {
+ // "<" => Implicit Tag Block
+ TagBlock(new Stack>());
+ }
+ else if (CurrentSymbol.Type == HtmlSymbolType.Transition)
+ {
+ // "@" => Explicit Tag/Single Line Block OR Template
+ Output(SpanKind.Markup);
+
+ // Definitely have a transition span
+ Assert(HtmlSymbolType.Transition);
+ AcceptAndMoveNext();
+ Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
+ Span.ChunkGenerator = SpanChunkGenerator.Null;
+ Output(SpanKind.Transition);
+ if (At(HtmlSymbolType.Transition))
+ {
+ Span.ChunkGenerator = SpanChunkGenerator.Null;
+ AcceptAndMoveNext();
+ Output(SpanKind.MetaCode);
+ }
+ AfterTransition();
+ }
+ else
+ {
+ Context.ErrorSink.OnError(
+ CurrentSymbol.Start,
+ LegacyResources.ParseError_MarkupBlock_Must_Start_With_Tag,
+ CurrentSymbol.Content.Length);
+ }
+ Output(SpanKind.Markup);
+ }
+ }
+ }
+
+ private void DefaultMarkupSpan(SpanBuilder span)
+ {
+ span.ChunkGenerator = new MarkupChunkGenerator();
+ span.EditHandler = new SpanEditHandler(Language.TokenizeString, AcceptedCharacters.Any);
+ }
+
+ private void AfterTransition()
+ {
+ // "@:" => Explicit Single Line Block
+ if (CurrentSymbol.Type == HtmlSymbolType.Text && CurrentSymbol.Content.Length > 0 && CurrentSymbol.Content[0] == ':')
+ {
+ // Split the token
+ Tuple split = Language.SplitSymbol(CurrentSymbol, 1, HtmlSymbolType.Colon);
+
+ // The first part (left) is added to this span and we return a MetaCode span
+ Accept(split.Item1);
+ Span.ChunkGenerator = SpanChunkGenerator.Null;
+ Output(SpanKind.MetaCode);
+ if (split.Item2 != null)
+ {
+ Accept(split.Item2);
+ }
+ NextToken();
+ SingleLineMarkup();
+ }
+ else if (CurrentSymbol.Type == HtmlSymbolType.OpenAngle)
+ {
+ TagBlock(new Stack>());
+ }
+ }
+
+ private void SingleLineMarkup()
+ {
+ // Parse until a newline, it's that simple!
+ // First, signal to code parser that whitespace is significant to us.
+ var old = Context.WhiteSpaceIsSignificantToAncestorBlock;
+ Context.WhiteSpaceIsSignificantToAncestorBlock = true;
+ Span.EditHandler = new SpanEditHandler(Language.TokenizeString);
+ SkipToAndParseCode(HtmlSymbolType.NewLine);
+ if (!EndOfFile && CurrentSymbol.Type == HtmlSymbolType.NewLine)
+ {
+ AcceptAndMoveNext();
+ Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
+ }
+ PutCurrentBack();
+ Context.WhiteSpaceIsSignificantToAncestorBlock = old;
+ Output(SpanKind.Markup);
+ }
+
+ private void TagBlock(Stack> tags)
+ {
+ // Skip Whitespace and Text
+ var complete = false;
+ do
+ {
+ SkipToAndParseCode(HtmlSymbolType.OpenAngle);
+
+ // Output everything prior to the OpenAngle into a markup span
+ Output(SpanKind.Markup);
+
+ // Do not want to start a new tag block if we're at the end of the file.
+ IDisposable tagBlockWrapper = null;
+ try
+ {
+ var atSpecialTag = AtSpecialTag;
+
+ if (!EndOfFile && !atSpecialTag)
+ {
+ // Start a Block tag. This is used to wrap things like or etc.
+ tagBlockWrapper = Context.Builder.StartBlock(BlockType.Tag);
+ }
+
+ if (EndOfFile)
+ {
+ EndTagBlock(tags, complete: true);
+ }
+ else
+ {
+ _bufferedOpenAngle = null;
+ _lastTagStart = CurrentLocation;
+ Assert(HtmlSymbolType.OpenAngle);
+ _bufferedOpenAngle = CurrentSymbol;
+ var tagStart = CurrentLocation;
+ if (!NextToken())
+ {
+ Accept(_bufferedOpenAngle);
+ EndTagBlock(tags, complete: false);
+ }
+ else
+ {
+ complete = AfterTagStart(tagStart, tags, atSpecialTag, tagBlockWrapper);
+ }
+ }
+
+ if (complete)
+ {
+ // Completed tags have no accepted characters inside of blocks.
+ Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
+ }
+
+ // Output the contents of the tag into its own markup span.
+ Output(SpanKind.Markup);
+ }
+ finally
+ {
+ // Will be null if we were at end of file or special tag when initially created.
+ if (tagBlockWrapper != null)
+ {
+ // End tag block
+ tagBlockWrapper.Dispose();
+ }
+ }
+ }
+ while (tags.Count > 0);
+
+ EndTagBlock(tags, complete);
+ }
+
+ private bool AfterTagStart(SourceLocation tagStart,
+ Stack> tags,
+ bool atSpecialTag,
+ IDisposable tagBlockWrapper)
+ {
+ if (!EndOfFile)
+ {
+ switch (CurrentSymbol.Type)
+ {
+ case HtmlSymbolType.ForwardSlash:
+ // End Tag
+ return EndTag(tagStart, tags, tagBlockWrapper);
+ case HtmlSymbolType.Bang:
+ // Comment, CDATA, DOCTYPE, or a parser-escaped HTML tag.
+ if (atSpecialTag)
+ {
+ Accept(_bufferedOpenAngle);
+ return BangTag();
+ }
+ else
+ {
+ goto default;
+ }
+ case HtmlSymbolType.QuestionMark:
+ // XML PI
+ Accept(_bufferedOpenAngle);
+ return XmlPI();
+ default:
+ // Start Tag
+ return StartTag(tags, tagBlockWrapper);
+ }
+ }
+ if (tags.Count == 0)
+ {
+ Context.ErrorSink.OnError(
+ CurrentLocation,
+ LegacyResources.ParseError_OuterTagMissingName,
+ length: 1 /* end of file */);
+ }
+ return false;
+ }
+
+ private bool XmlPI()
+ {
+ // Accept "?"
+ Assert(HtmlSymbolType.QuestionMark);
+ AcceptAndMoveNext();
+ return AcceptUntilAll(HtmlSymbolType.QuestionMark, HtmlSymbolType.CloseAngle);
+ }
+
+ private bool BangTag()
+ {
+ // Accept "!"
+ Assert(HtmlSymbolType.Bang);
+
+ if (AcceptAndMoveNext())
+ {
+ if (CurrentSymbol.Type == HtmlSymbolType.DoubleHyphen)
+ {
+ AcceptAndMoveNext();
+
+ Span.EditHandler.AcceptedCharacters = AcceptedCharacters.Any;
+ while (!EndOfFile)
+ {
+ SkipToAndParseCode(HtmlSymbolType.DoubleHyphen);
+ if (At(HtmlSymbolType.DoubleHyphen))
+ {
+ AcceptWhile(HtmlSymbolType.DoubleHyphen);
+
+ if (At(HtmlSymbolType.Text) &&
+ string.Equals(CurrentSymbol.Content, "-", StringComparison.Ordinal))
+ {
+ AcceptAndMoveNext();
+ }
+
+ if (At(HtmlSymbolType.CloseAngle))
+ {
+ AcceptAndMoveNext();
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+ else if (CurrentSymbol.Type == HtmlSymbolType.LeftBracket)
+ {
+ if (AcceptAndMoveNext())
+ {
+ return CData();
+ }
+ }
+ else
+ {
+ AcceptAndMoveNext();
+ return AcceptUntilAll(HtmlSymbolType.CloseAngle);
+ }
+ }
+
+ return false;
+ }
+
+ private bool CData()
+ {
+ if (CurrentSymbol.Type == HtmlSymbolType.Text && string.Equals(CurrentSymbol.Content, "cdata", StringComparison.OrdinalIgnoreCase))
+ {
+ if (AcceptAndMoveNext())
+ {
+ if (CurrentSymbol.Type == HtmlSymbolType.LeftBracket)
+ {
+ return AcceptUntilAll(HtmlSymbolType.RightBracket, HtmlSymbolType.RightBracket, HtmlSymbolType.CloseAngle);
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private bool EndTag(SourceLocation tagStart,
+ Stack> tags,
+ IDisposable tagBlockWrapper)
+ {
+ // Accept "/" and move next
+ Assert(HtmlSymbolType.ForwardSlash);
+ var forwardSlash = CurrentSymbol;
+ if (!NextToken())
+ {
+ Accept(_bufferedOpenAngle);
+ Accept(forwardSlash);
+ return false;
+ }
+ else
+ {
+ var tagName = string.Empty;
+ HtmlSymbol bangSymbol = null;
+
+ if (At(HtmlSymbolType.Bang))
+ {
+ bangSymbol = CurrentSymbol;
+
+ var nextSymbol = Lookahead(count: 1);
+
+ if (nextSymbol != null && nextSymbol.Type == HtmlSymbolType.Text)
+ {
+ tagName = "!" + nextSymbol.Content;
+ }
+ }
+ else if (At(HtmlSymbolType.Text))
+ {
+ tagName = CurrentSymbol.Content;
+ }
+
+ var matched = RemoveTag(tags, tagName, tagStart);
+
+ if (tags.Count == 0 &&
+ // Note tagName may contain a '!' escape character. This ensures !text> doesn't match here.
+ // !text> tags are treated like any other escaped HTML end tag.
+ string.Equals(tagName, SyntaxConstants.TextTagName, StringComparison.OrdinalIgnoreCase) &&
+ matched)
+ {
+ return EndTextTag(forwardSlash, tagBlockWrapper);
+ }
+ Accept(_bufferedOpenAngle);
+ Accept(forwardSlash);
+
+ OptionalBangEscape();
+
+ AcceptUntil(HtmlSymbolType.CloseAngle);
+
+ // Accept the ">"
+ return Optional(HtmlSymbolType.CloseAngle);
+ }
+ }
+
+ private void RecoverTextTag()
+ {
+ // We don't want to skip-to and parse because there shouldn't be anything in the body of text tags.
+ AcceptUntil(HtmlSymbolType.CloseAngle, HtmlSymbolType.NewLine);
+
+ // Include the close angle in the text tag block if it's there, otherwise just move on
+ Optional(HtmlSymbolType.CloseAngle);
+ }
+
+ private bool EndTextTag(HtmlSymbol solidus, IDisposable tagBlockWrapper)
+ {
+ Accept(_bufferedOpenAngle);
+ Accept(solidus);
+
+ var textLocation = CurrentLocation;
+ Assert(HtmlSymbolType.Text);
+ AcceptAndMoveNext();
+
+ var seenCloseAngle = Optional(HtmlSymbolType.CloseAngle);
+
+ if (!seenCloseAngle)
+ {
+ Context.ErrorSink.OnError(
+ textLocation,
+ LegacyResources.ParseError_TextTagCannotContainAttributes,
+ length: 4 /* text */);
+
+ Span.EditHandler.AcceptedCharacters = AcceptedCharacters.Any;
+ RecoverTextTag();
+ }
+ else
+ {
+ Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
+ }
+
+ Span.ChunkGenerator = SpanChunkGenerator.Null;
+
+ CompleteTagBlockWithSpan(tagBlockWrapper, Span.EditHandler.AcceptedCharacters, SpanKind.Transition);
+
+ return seenCloseAngle;
+ }
+
+ // Special tags include
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Value cannot be null or an empty string.
+
+
+ Value must be between {0} and {1}.
+
+
+ Value must be a value from the "{0}" enumeration.
+
+
+ Value must be greater than {0}.
+
+
+ Value must be greater than or equal to {0}.
+
+
+ Value must be less than {0}.
+
+
+ Value must be less than or equal to {0}.
+
+
+ Value cannot be an empty string. It must either be null or a non-empty string.
+
+
+ code
+ This is a literal used when composing ParserError_* messages. Most blocks are named by the keyword that starts them, for example "if". However, for those without keywords, a (localizable) name must be used. This literal is ALWAYS used mid-sentence, thus should not be capitalized.
+
+
+ explicit expression
+ This is a literal used when composing ParserError_* messages. Most blocks are named by the keyword that starts them, for example "if". However, for those without keywords, a (localizable) name must be used. This literal is ALWAYS used mid-sentence, thus should not be capitalized.
+
+
+ Block cannot be built because a Type has not been specified in the BlockBuilder
+
+
+ <<character literal>>
+
+
+ <<comment>>
+
+
+ <<identifier>>
+
+
+ <<integer literal>>
+
+
+ <<keyword>>
+
+
+ <<newline sequence>>
+
+
+ <<real literal>>
+
+
+ <<string literal>>
+
+
+ <<white space>>
+
+
+ "EndBlock" was called without a matching call to "StartBlock".
+
+
+ "{0}" character
+
+
+ end of file
+
+
+ line break
+
+
+ space or line break
+
+
+ <<newline sequence>>
+
+
+ <<razor comment>>
+
+
+ <<text>>
+
+
+ <<white space>>
+
+
+ Cannot use built-in RazorComment handler, language characteristics does not define the CommentStart, CommentStar and CommentBody known symbol types or parser does not override TokenizerBackedParser.OutputSpanBeforeRazorComment
+
+
+ The "@" character must be followed by a ":", "(", or a C# identifier. If you intended to switch to markup, use an HTML start tag, for example:
+
+@if(isLoggedIn) {
+ <p>Hello, @user!</p>
+}
+
+
+ End of file was reached before the end of the block comment. All comments started with "/*" sequence must be terminated with a matching "*/" sequence.
+
+
+ Directive '{0}' must have a value.
+
+
+ An opening "{0}" is missing the corresponding closing "{1}".
+
+
+ The {0} block is missing a closing "{1}" character. Make sure you have a matching "{1}" character for all the "{2}" characters within this block, and that none of the "{1}" characters are being interpreted as markup.
+
+
+ Expected "{0}".
+
+
+ The {0} directive is not supported.
+
+
+ Optional quote around the directive '{0}' is missing the corresponding opening or closing quote.
+
+
+ The 'inherits' keyword must be followed by a type name on the same line.
+
+
+ Inline markup blocks (@<p>Content</p>) cannot be nested. Only one level of inline markup is allowed.
+
+
+ Markup in a code block must start with a tag and all start tags must be matched with end tags. Do not use unclosed tags like "<br>". Instead use self-closing tags like "<br/>".
+
+
+ The "{0}" element was not closed. All elements must be either self-closing or have a matching end tag.
+
+
+ Sections cannot be empty. The "@section" keyword must be followed by a block of markup surrounded by "{}". For example:
+
+@section Sidebar {
+ <!-- Markup and text goes here -->
+}
+
+
+ Namespace imports and type aliases cannot be placed within code blocks. They must immediately follow an "@" character in markup. It is recommended that you put them at the top of the page, as in the following example:
+
+@using System.Drawing;
+@{
+ // OK here to use types from System.Drawing in the page.
+}
+
+
+ Outer tag is missing a name. The first character of a markup block must be an HTML tag with a valid name.
+
+
+ End of file was reached before the end of the block comment. All comments that start with the "@*" sequence must be terminated with a matching "*@" sequence.
+
+
+ "{0}" is a reserved word and cannot be used in implicit expressions. An explicit expression ("@()") must be used.
+
+
+ Section blocks ("{0}") cannot be nested. Only one level of section blocks are allowed.
+
+
+ Expected a "{0}" but found a "{1}". Block statements must be enclosed in "{{" and "}}". You cannot use single-statement control-flow statements in CSHTML pages. For example, the following is not allowed:
+
+@if(isLoggedIn)
+ <p>Hello, @user</p>
+
+Instead, wrap the contents of the block in "{{}}":
+
+@if(isLoggedIn) {{
+ <p>Hello, @user</p>
+}}
+ {0} is only ever a single character
+
+
+ "<text>" and "</text>" tags cannot contain attributes.
+
+
+ Encountered end tag "{0}" with no matching start tag. Are your start/end tags properly balanced?
+
+
+ Unexpected {0} after section keyword. Section names must start with an "_" or alphabetic character, and the remaining characters must be either "_" or alphanumeric.
+
+
+ "{0}" is not valid at the start of a code block. Only identifiers, keywords, comments, "(" and "{{" are valid.
+ "{{" is an escape sequence for string.Format, when outputted to the user it will be displayed as "{"
+
+
+ End-of-file was found after the "@" character. "@" must be followed by a valid code block. If you want to output an "@", escape it using the sequence: "@@"
+
+
+ End-of-file was found after the "@" character. "@" must be followed by a valid code block. If you want to output an "@", escape it using the sequence: "@@"
+
+
+ Unexpected "{" after "@" character. Once inside the body of a code block (@if {}, @{}, etc.) you do not need to use "@{" to switch to code.
+
+
+ A space or line break was encountered after the "@" character. Only valid identifiers, keywords, comments, "(" and "{" are valid at the start of a code block and they must occur immediately following "@" with no space in between.
+
+
+ End of file or an unexpected character was reached before the "{0}" tag could be parsed. Elements inside markup blocks must be complete. They must either be self-closing ("<br />") or have matching end tags ("<p>Hello</p>"). If you intended to display a "<" character, use the "<" HTML entity.
+
+
+ Unterminated string literal. Strings that start with a quotation mark (") must be terminated before the end of the line. However, strings that start with @ and a quotation mark (@") can span multiple lines.
+
+
+ Cannot complete the tree, StartBlock must be called at least once.
+
+
+ Cannot complete the tree, there are still open blocks.
+
+
+ Cannot finish span, there is no current block. Call StartBlock at least once before finishing a span
+
+
+ Cannot complete action, the parser has finished. Only CompleteParse can be called to extract the final parser results after the parser has finished
+
+
+ Parser was started with a null Context property. The Context property must be set BEFORE calling any methods on the parser.
+
+
+ @section Header { ... }
+ In CSHTML, the @section keyword is case-sensitive and lowercase (as with all C# keywords)
+
+
+ Cannot perform '{1}' operations on '{0}' instances with different file paths.
+
+
+ <<unknown>>
+
+
+ In order to put a symbol back, it must have been the symbol which ended at the current position. The specified symbol ends at {0}, but the current position is {1}
+
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Properties/LegacyResources.Designer.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Properties/LegacyResources.Designer.cs
new file mode 100644
index 0000000000..b4aa9a11b4
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Razor.Evolution/Properties/LegacyResources.Designer.cs
@@ -0,0 +1,1162 @@
+//
+namespace Microsoft.AspNetCore.Razor.Evolution
+{
+ using System.Globalization;
+ using System.Reflection;
+ using System.Resources;
+
+ internal static class LegacyResources
+ {
+ private static readonly ResourceManager _resourceManager
+ = new ResourceManager("Microsoft.AspNetCore.Razor.Evolution.LegacyResources", typeof(LegacyResources).GetTypeInfo().Assembly);
+
+ ///
+ /// Value cannot be null or an empty string.
+ ///
+ internal static string Argument_Cannot_Be_Null_Or_Empty
+ {
+ get { return GetString("Argument_Cannot_Be_Null_Or_Empty"); }
+ }
+
+ ///
+ /// Value cannot be null or an empty string.
+ ///
+ internal static string FormatArgument_Cannot_Be_Null_Or_Empty()
+ {
+ return GetString("Argument_Cannot_Be_Null_Or_Empty");
+ }
+
+ ///
+ /// Value must be between {0} and {1}.
+ ///
+ internal static string Argument_Must_Be_Between
+ {
+ get { return GetString("Argument_Must_Be_Between"); }
+ }
+
+ ///
+ /// Value must be between {0} and {1}.
+ ///
+ internal static string FormatArgument_Must_Be_Between(object p0, object p1)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("Argument_Must_Be_Between"), p0, p1);
+ }
+
+ ///
+ /// Value must be a value from the "{0}" enumeration.
+ ///
+ internal static string Argument_Must_Be_Enum_Member
+ {
+ get { return GetString("Argument_Must_Be_Enum_Member"); }
+ }
+
+ ///
+ /// Value must be a value from the "{0}" enumeration.
+ ///
+ internal static string FormatArgument_Must_Be_Enum_Member(object p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("Argument_Must_Be_Enum_Member"), p0);
+ }
+
+ ///
+ /// Value must be greater than {0}.
+ ///
+ internal static string Argument_Must_Be_GreaterThan
+ {
+ get { return GetString("Argument_Must_Be_GreaterThan"); }
+ }
+
+ ///
+ /// Value must be greater than {0}.
+ ///
+ internal static string FormatArgument_Must_Be_GreaterThan(object p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("Argument_Must_Be_GreaterThan"), p0);
+ }
+
+ ///
+ /// Value must be greater than or equal to {0}.
+ ///
+ internal static string Argument_Must_Be_GreaterThanOrEqualTo
+ {
+ get { return GetString("Argument_Must_Be_GreaterThanOrEqualTo"); }
+ }
+
+ ///
+ /// Value must be greater than or equal to {0}.
+ ///
+ internal static string FormatArgument_Must_Be_GreaterThanOrEqualTo(object p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("Argument_Must_Be_GreaterThanOrEqualTo"), p0);
+ }
+
+ ///
+ /// Value must be less than {0}.
+ ///
+ internal static string Argument_Must_Be_LessThan
+ {
+ get { return GetString("Argument_Must_Be_LessThan"); }
+ }
+
+ ///
+ /// Value must be less than {0}.
+ ///
+ internal static string FormatArgument_Must_Be_LessThan(object p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("Argument_Must_Be_LessThan"), p0);
+ }
+
+ ///
+ /// Value must be less than or equal to {0}.
+ ///
+ internal static string Argument_Must_Be_LessThanOrEqualTo
+ {
+ get { return GetString("Argument_Must_Be_LessThanOrEqualTo"); }
+ }
+
+ ///
+ /// Value must be less than or equal to {0}.
+ ///
+ internal static string FormatArgument_Must_Be_LessThanOrEqualTo(object p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("Argument_Must_Be_LessThanOrEqualTo"), p0);
+ }
+
+ ///
+ /// Value cannot be an empty string. It must either be null or a non-empty string.
+ ///
+ internal static string Argument_Must_Be_Null_Or_Non_Empty
+ {
+ get { return GetString("Argument_Must_Be_Null_Or_Non_Empty"); }
+ }
+
+ ///
+ /// Value cannot be an empty string. It must either be null or a non-empty string.
+ ///
+ internal static string FormatArgument_Must_Be_Null_Or_Non_Empty()
+ {
+ return GetString("Argument_Must_Be_Null_Or_Non_Empty");
+ }
+
+ ///
+ /// code
+ ///
+ internal static string BlockName_Code
+ {
+ get { return GetString("BlockName_Code"); }
+ }
+
+ ///
+ /// code
+ ///
+ internal static string FormatBlockName_Code()
+ {
+ return GetString("BlockName_Code");
+ }
+
+ ///
+ /// explicit expression
+ ///
+ internal static string BlockName_ExplicitExpression
+ {
+ get { return GetString("BlockName_ExplicitExpression"); }
+ }
+
+ ///
+ /// explicit expression
+ ///
+ internal static string FormatBlockName_ExplicitExpression()
+ {
+ return GetString("BlockName_ExplicitExpression");
+ }
+
+ ///
+ /// Block cannot be built because a Type has not been specified in the BlockBuilder
+ ///
+ internal static string Block_Type_Not_Specified
+ {
+ get { return GetString("Block_Type_Not_Specified"); }
+ }
+
+ ///
+ /// Block cannot be built because a Type has not been specified in the BlockBuilder
+ ///
+ internal static string FormatBlock_Type_Not_Specified()
+ {
+ return GetString("Block_Type_Not_Specified");
+ }
+
+ ///
+ /// <<character literal>>
+ ///
+ internal static string CSharpSymbol_CharacterLiteral
+ {
+ get { return GetString("CSharpSymbol_CharacterLiteral"); }
+ }
+
+ ///
+ /// <<character literal>>
+ ///
+ internal static string FormatCSharpSymbol_CharacterLiteral()
+ {
+ return GetString("CSharpSymbol_CharacterLiteral");
+ }
+
+ ///
+ /// <<comment>>
+ ///
+ internal static string CSharpSymbol_Comment
+ {
+ get { return GetString("CSharpSymbol_Comment"); }
+ }
+
+ ///
+ /// <<comment>>
+ ///
+ internal static string FormatCSharpSymbol_Comment()
+ {
+ return GetString("CSharpSymbol_Comment");
+ }
+
+ ///
+ /// <<identifier>>
+ ///
+ internal static string CSharpSymbol_Identifier
+ {
+ get { return GetString("CSharpSymbol_Identifier"); }
+ }
+
+ ///
+ /// <<identifier>>
+ ///
+ internal static string FormatCSharpSymbol_Identifier()
+ {
+ return GetString("CSharpSymbol_Identifier");
+ }
+
+ ///
+ /// <<integer literal>>
+ ///
+ internal static string CSharpSymbol_IntegerLiteral
+ {
+ get { return GetString("CSharpSymbol_IntegerLiteral"); }
+ }
+
+ ///
+ /// <<integer literal>>
+ ///
+ internal static string FormatCSharpSymbol_IntegerLiteral()
+ {
+ return GetString("CSharpSymbol_IntegerLiteral");
+ }
+
+ ///
+ /// <<keyword>>
+ ///
+ internal static string CSharpSymbol_Keyword
+ {
+ get { return GetString("CSharpSymbol_Keyword"); }
+ }
+
+ ///
+ /// <<keyword>>
+ ///
+ internal static string FormatCSharpSymbol_Keyword()
+ {
+ return GetString("CSharpSymbol_Keyword");
+ }
+
+ ///
+ /// <<newline sequence>>
+ ///
+ internal static string CSharpSymbol_Newline
+ {
+ get { return GetString("CSharpSymbol_Newline"); }
+ }
+
+ ///
+ /// <<newline sequence>>
+ ///
+ internal static string FormatCSharpSymbol_Newline()
+ {
+ return GetString("CSharpSymbol_Newline");
+ }
+
+ ///
+ /// <<real literal>>
+ ///
+ internal static string CSharpSymbol_RealLiteral
+ {
+ get { return GetString("CSharpSymbol_RealLiteral"); }
+ }
+
+ ///
+ /// <<real literal>>
+ ///
+ internal static string FormatCSharpSymbol_RealLiteral()
+ {
+ return GetString("CSharpSymbol_RealLiteral");
+ }
+
+ ///
+ /// <<string literal>>
+ ///
+ internal static string CSharpSymbol_StringLiteral
+ {
+ get { return GetString("CSharpSymbol_StringLiteral"); }
+ }
+
+ ///
+ /// <<string literal>>
+ ///
+ internal static string FormatCSharpSymbol_StringLiteral()
+ {
+ return GetString("CSharpSymbol_StringLiteral");
+ }
+
+ ///
+ /// <<white space>>
+ ///
+ internal static string CSharpSymbol_Whitespace
+ {
+ get { return GetString("CSharpSymbol_Whitespace"); }
+ }
+
+ ///
+ /// <<white space>>
+ ///
+ internal static string FormatCSharpSymbol_Whitespace()
+ {
+ return GetString("CSharpSymbol_Whitespace");
+ }
+
+ ///
+ /// "EndBlock" was called without a matching call to "StartBlock".
+ ///
+ internal static string EndBlock_Called_Without_Matching_StartBlock
+ {
+ get { return GetString("EndBlock_Called_Without_Matching_StartBlock"); }
+ }
+
+ ///
+ /// "EndBlock" was called without a matching call to "StartBlock".
+ ///
+ internal static string FormatEndBlock_Called_Without_Matching_StartBlock()
+ {
+ return GetString("EndBlock_Called_Without_Matching_StartBlock");
+ }
+
+ ///
+ /// "{0}" character
+ ///
+ internal static string ErrorComponent_Character
+ {
+ get { return GetString("ErrorComponent_Character"); }
+ }
+
+ ///
+ /// "{0}" character
+ ///
+ internal static string FormatErrorComponent_Character(object p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("ErrorComponent_Character"), p0);
+ }
+
+ ///
+ /// end of file
+ ///
+ internal static string ErrorComponent_EndOfFile
+ {
+ get { return GetString("ErrorComponent_EndOfFile"); }
+ }
+
+ ///
+ /// end of file
+ ///
+ internal static string FormatErrorComponent_EndOfFile()
+ {
+ return GetString("ErrorComponent_EndOfFile");
+ }
+
+ ///
+ /// line break
+ ///
+ internal static string ErrorComponent_Newline
+ {
+ get { return GetString("ErrorComponent_Newline"); }
+ }
+
+ ///
+ /// line break
+ ///
+ internal static string FormatErrorComponent_Newline()
+ {
+ return GetString("ErrorComponent_Newline");
+ }
+
+ ///
+ /// space or line break
+ ///
+ internal static string ErrorComponent_Whitespace
+ {
+ get { return GetString("ErrorComponent_Whitespace"); }
+ }
+
+ ///
+ /// space or line break
+ ///
+ internal static string FormatErrorComponent_Whitespace()
+ {
+ return GetString("ErrorComponent_Whitespace");
+ }
+
+ ///
+ /// <<newline sequence>>
+ ///
+ internal static string HtmlSymbol_NewLine
+ {
+ get { return GetString("HtmlSymbol_NewLine"); }
+ }
+
+ ///
+ /// <<newline sequence>>
+ ///
+ internal static string FormatHtmlSymbol_NewLine()
+ {
+ return GetString("HtmlSymbol_NewLine");
+ }
+
+ ///
+ /// <<razor comment>>
+ ///
+ internal static string HtmlSymbol_RazorComment
+ {
+ get { return GetString("HtmlSymbol_RazorComment"); }
+ }
+
+ ///
+ /// <<razor comment>>
+ ///
+ internal static string FormatHtmlSymbol_RazorComment()
+ {
+ return GetString("HtmlSymbol_RazorComment");
+ }
+
+ ///
+ /// <<text>>
+ ///
+ internal static string HtmlSymbol_Text
+ {
+ get { return GetString("HtmlSymbol_Text"); }
+ }
+
+ ///
+ /// <<text>>
+ ///
+ internal static string FormatHtmlSymbol_Text()
+ {
+ return GetString("HtmlSymbol_Text");
+ }
+
+ ///
+ /// <<white space>>
+ ///
+ internal static string HtmlSymbol_WhiteSpace
+ {
+ get { return GetString("HtmlSymbol_WhiteSpace"); }
+ }
+
+ ///
+ /// <<white space>>
+ ///
+ internal static string FormatHtmlSymbol_WhiteSpace()
+ {
+ return GetString("HtmlSymbol_WhiteSpace");
+ }
+
+ ///
+ /// Cannot use built-in RazorComment handler, language characteristics does not define the CommentStart, CommentStar and CommentBody known symbol types or parser does not override TokenizerBackedParser.OutputSpanBeforeRazorComment
+ ///
+ internal static string Language_Does_Not_Support_RazorComment
+ {
+ get { return GetString("Language_Does_Not_Support_RazorComment"); }
+ }
+
+ ///
+ /// Cannot use built-in RazorComment handler, language characteristics does not define the CommentStart, CommentStar and CommentBody known symbol types or parser does not override TokenizerBackedParser.OutputSpanBeforeRazorComment
+ ///
+ internal static string FormatLanguage_Does_Not_Support_RazorComment()
+ {
+ return GetString("Language_Does_Not_Support_RazorComment");
+ }
+
+ ///
+ /// The "@" character must be followed by a ":", "(", or a C# identifier. If you intended to switch to markup, use an HTML start tag, for example:
+ ///
+ /// @if(isLoggedIn) {
+ /// <p>Hello, @user!</p>
+ /// }
+ ///
+ internal static string ParseError_AtInCode_Must_Be_Followed_By_Colon_Paren_Or_Identifier_Start
+ {
+ get { return GetString("ParseError_AtInCode_Must_Be_Followed_By_Colon_Paren_Or_Identifier_Start"); }
+ }
+
+ ///
+ /// The "@" character must be followed by a ":", "(", or a C# identifier. If you intended to switch to markup, use an HTML start tag, for example:
+ ///
+ /// @if(isLoggedIn) {
+ /// <p>Hello, @user!</p>
+ /// }
+ ///
+ internal static string FormatParseError_AtInCode_Must_Be_Followed_By_Colon_Paren_Or_Identifier_Start()
+ {
+ return GetString("ParseError_AtInCode_Must_Be_Followed_By_Colon_Paren_Or_Identifier_Start");
+ }
+
+ ///
+ /// End of file was reached before the end of the block comment. All comments started with "/*" sequence must be terminated with a matching "*/" sequence.
+ ///
+ internal static string ParseError_BlockComment_Not_Terminated
+ {
+ get { return GetString("ParseError_BlockComment_Not_Terminated"); }
+ }
+
+ ///
+ /// End of file was reached before the end of the block comment. All comments started with "/*" sequence must be terminated with a matching "*/" sequence.
+ ///
+ internal static string FormatParseError_BlockComment_Not_Terminated()
+ {
+ return GetString("ParseError_BlockComment_Not_Terminated");
+ }
+
+ ///
+ /// Directive '{0}' must have a value.
+ ///
+ internal static string ParseError_DirectiveMustHaveValue
+ {
+ get { return GetString("ParseError_DirectiveMustHaveValue"); }
+ }
+
+ ///
+ /// Directive '{0}' must have a value.
+ ///
+ internal static string FormatParseError_DirectiveMustHaveValue(object p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("ParseError_DirectiveMustHaveValue"), p0);
+ }
+
+ ///
+ /// An opening "{0}" is missing the corresponding closing "{1}".
+ ///
+ internal static string ParseError_Expected_CloseBracket_Before_EOF
+ {
+ get { return GetString("ParseError_Expected_CloseBracket_Before_EOF"); }
+ }
+
+ ///
+ /// An opening "{0}" is missing the corresponding closing "{1}".
+ ///
+ internal static string FormatParseError_Expected_CloseBracket_Before_EOF(object p0, object p1)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("ParseError_Expected_CloseBracket_Before_EOF"), p0, p1);
+ }
+
+ ///
+ /// The {0} block is missing a closing "{1}" character. Make sure you have a matching "{1}" character for all the "{2}" characters within this block, and that none of the "{1}" characters are being interpreted as markup.
+ ///
+ internal static string ParseError_Expected_EndOfBlock_Before_EOF
+ {
+ get { return GetString("ParseError_Expected_EndOfBlock_Before_EOF"); }
+ }
+
+ ///
+ /// The {0} block is missing a closing "{1}" character. Make sure you have a matching "{1}" character for all the "{2}" characters within this block, and that none of the "{1}" characters are being interpreted as markup.
+ ///
+ internal static string FormatParseError_Expected_EndOfBlock_Before_EOF(object p0, object p1, object p2)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("ParseError_Expected_EndOfBlock_Before_EOF"), p0, p1, p2);
+ }
+
+ ///
+ /// Expected "{0}".
+ ///
+ internal static string ParseError_Expected_X
+ {
+ get { return GetString("ParseError_Expected_X"); }
+ }
+
+ ///
+ /// Expected "{0}".
+ ///
+ internal static string FormatParseError_Expected_X(object p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("ParseError_Expected_X"), p0);
+ }
+
+ ///
+ /// The {0} directive is not supported.
+ ///
+ internal static string ParseError_HelperDirectiveNotAvailable
+ {
+ get { return GetString("ParseError_HelperDirectiveNotAvailable"); }
+ }
+
+ ///
+ /// The {0} directive is not supported.
+ ///
+ internal static string FormatParseError_HelperDirectiveNotAvailable(object p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("ParseError_HelperDirectiveNotAvailable"), p0);
+ }
+
+ ///
+ /// Optional quote around the directive '{0}' is missing the corresponding opening or closing quote.
+ ///
+ internal static string ParseError_IncompleteQuotesAroundDirective
+ {
+ get { return GetString("ParseError_IncompleteQuotesAroundDirective"); }
+ }
+
+ ///
+ /// Optional quote around the directive '{0}' is missing the corresponding opening or closing quote.
+ ///
+ internal static string FormatParseError_IncompleteQuotesAroundDirective(object p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("ParseError_IncompleteQuotesAroundDirective"), p0);
+ }
+
+ ///
+ /// The 'inherits' keyword must be followed by a type name on the same line.
+ ///
+ internal static string ParseError_InheritsKeyword_Must_Be_Followed_By_TypeName
+ {
+ get { return GetString("ParseError_InheritsKeyword_Must_Be_Followed_By_TypeName"); }
+ }
+
+ ///
+ /// The 'inherits' keyword must be followed by a type name on the same line.
+ ///
+ internal static string FormatParseError_InheritsKeyword_Must_Be_Followed_By_TypeName()
+ {
+ return GetString("ParseError_InheritsKeyword_Must_Be_Followed_By_TypeName");
+ }
+
+ ///
+ /// Inline markup blocks (@<p>Content</p>) cannot be nested. Only one level of inline markup is allowed.
+ ///
+ internal static string ParseError_InlineMarkup_Blocks_Cannot_Be_Nested
+ {
+ get { return GetString("ParseError_InlineMarkup_Blocks_Cannot_Be_Nested"); }
+ }
+
+ ///
+ /// Inline markup blocks (@<p>Content</p>) cannot be nested. Only one level of inline markup is allowed.
+ ///
+ internal static string FormatParseError_InlineMarkup_Blocks_Cannot_Be_Nested()
+ {
+ return GetString("ParseError_InlineMarkup_Blocks_Cannot_Be_Nested");
+ }
+
+ ///
+ /// Markup in a code block must start with a tag and all start tags must be matched with end tags. Do not use unclosed tags like "<br>". Instead use self-closing tags like "<br/>".
+ ///
+ internal static string ParseError_MarkupBlock_Must_Start_With_Tag
+ {
+ get { return GetString("ParseError_MarkupBlock_Must_Start_With_Tag"); }
+ }
+
+ ///
+ /// Markup in a code block must start with a tag and all start tags must be matched with end tags. Do not use unclosed tags like "<br>". Instead use self-closing tags like "<br/>".
+ ///
+ internal static string FormatParseError_MarkupBlock_Must_Start_With_Tag()
+ {
+ return GetString("ParseError_MarkupBlock_Must_Start_With_Tag");
+ }
+
+ ///
+ /// The "{0}" element was not closed. All elements must be either self-closing or have a matching end tag.
+ ///
+ internal static string ParseError_MissingEndTag
+ {
+ get { return GetString("ParseError_MissingEndTag"); }
+ }
+
+ ///
+ /// The "{0}" element was not closed. All elements must be either self-closing or have a matching end tag.
+ ///
+ internal static string FormatParseError_MissingEndTag(object p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("ParseError_MissingEndTag"), p0);
+ }
+
+ ///
+ /// Sections cannot be empty. The "@section" keyword must be followed by a block of markup surrounded by "{}". For example:
+ ///
+ /// @section Sidebar {
+ /// <!-- Markup and text goes here -->
+ /// }
+ ///
+ internal static string ParseError_MissingOpenBraceAfterSection
+ {
+ get { return GetString("ParseError_MissingOpenBraceAfterSection"); }
+ }
+
+ ///
+ /// Sections cannot be empty. The "@section" keyword must be followed by a block of markup surrounded by "{}". For example:
+ ///
+ /// @section Sidebar {
+ /// <!-- Markup and text goes here -->
+ /// }
+ ///
+ internal static string FormatParseError_MissingOpenBraceAfterSection()
+ {
+ return GetString("ParseError_MissingOpenBraceAfterSection");
+ }
+
+ ///
+ /// Namespace imports and type aliases cannot be placed within code blocks. They must immediately follow an "@" character in markup. It is recommended that you put them at the top of the page, as in the following example:
+ ///
+ /// @using System.Drawing;
+ /// @{
+ /// // OK here to use types from System.Drawing in the page.
+ /// }
+ ///
+ internal static string ParseError_NamespaceImportAndTypeAlias_Cannot_Exist_Within_CodeBlock
+ {
+ get { return GetString("ParseError_NamespaceImportAndTypeAlias_Cannot_Exist_Within_CodeBlock"); }
+ }
+
+ ///
+ /// Namespace imports and type aliases cannot be placed within code blocks. They must immediately follow an "@" character in markup. It is recommended that you put them at the top of the page, as in the following example:
+ ///
+ /// @using System.Drawing;
+ /// @{
+ /// // OK here to use types from System.Drawing in the page.
+ /// }
+ ///
+ internal static string FormatParseError_NamespaceImportAndTypeAlias_Cannot_Exist_Within_CodeBlock()
+ {
+ return GetString("ParseError_NamespaceImportAndTypeAlias_Cannot_Exist_Within_CodeBlock");
+ }
+
+ ///
+ /// Outer tag is missing a name. The first character of a markup block must be an HTML tag with a valid name.
+ ///
+ internal static string ParseError_OuterTagMissingName
+ {
+ get { return GetString("ParseError_OuterTagMissingName"); }
+ }
+
+ ///
+ /// Outer tag is missing a name. The first character of a markup block must be an HTML tag with a valid name.
+ ///
+ internal static string FormatParseError_OuterTagMissingName()
+ {
+ return GetString("ParseError_OuterTagMissingName");
+ }
+
+ ///
+ /// End of file was reached before the end of the block comment. All comments that start with the "@*" sequence must be terminated with a matching "*@" sequence.
+ ///
+ internal static string ParseError_RazorComment_Not_Terminated
+ {
+ get { return GetString("ParseError_RazorComment_Not_Terminated"); }
+ }
+
+ ///
+ /// End of file was reached before the end of the block comment. All comments that start with the "@*" sequence must be terminated with a matching "*@" sequence.
+ ///
+ internal static string FormatParseError_RazorComment_Not_Terminated()
+ {
+ return GetString("ParseError_RazorComment_Not_Terminated");
+ }
+
+ ///
+ /// "{0}" is a reserved word and cannot be used in implicit expressions. An explicit expression ("@()") must be used.
+ ///
+ internal static string ParseError_ReservedWord
+ {
+ get { return GetString("ParseError_ReservedWord"); }
+ }
+
+ ///
+ /// "{0}" is a reserved word and cannot be used in implicit expressions. An explicit expression ("@()") must be used.
+ ///
+ internal static string FormatParseError_ReservedWord(object p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("ParseError_ReservedWord"), p0);
+ }
+
+ ///
+ /// Section blocks ("{0}") cannot be nested. Only one level of section blocks are allowed.
+ ///
+ internal static string ParseError_Sections_Cannot_Be_Nested
+ {
+ get { return GetString("ParseError_Sections_Cannot_Be_Nested"); }
+ }
+
+ ///
+ /// Section blocks ("{0}") cannot be nested. Only one level of section blocks are allowed.
+ ///
+ internal static string FormatParseError_Sections_Cannot_Be_Nested(object p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("ParseError_Sections_Cannot_Be_Nested"), p0);
+ }
+
+ ///
+ /// Expected a "{0}" but found a "{1}". Block statements must be enclosed in "{{" and "}}". You cannot use single-statement control-flow statements in CSHTML pages. For example, the following is not allowed:
+ ///
+ /// @if(isLoggedIn)
+ /// <p>Hello, @user</p>
+ ///
+ /// Instead, wrap the contents of the block in "{{}}":
+ ///
+ /// @if(isLoggedIn) {{
+ /// <p>Hello, @user</p>
+ /// }}
+ ///
+ internal static string ParseError_SingleLine_ControlFlowStatements_Not_Allowed
+ {
+ get { return GetString("ParseError_SingleLine_ControlFlowStatements_Not_Allowed"); }
+ }
+
+ ///
+ /// Expected a "{0}" but found a "{1}". Block statements must be enclosed in "{{" and "}}". You cannot use single-statement control-flow statements in CSHTML pages. For example, the following is not allowed:
+ ///
+ /// @if(isLoggedIn)
+ /// <p>Hello, @user</p>
+ ///
+ /// Instead, wrap the contents of the block in "{{}}":
+ ///
+ /// @if(isLoggedIn) {{
+ /// <p>Hello, @user</p>
+ /// }}
+ ///
+ internal static string FormatParseError_SingleLine_ControlFlowStatements_Not_Allowed(object p0, object p1)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("ParseError_SingleLine_ControlFlowStatements_Not_Allowed"), p0, p1);
+ }
+
+ ///
+ /// "<text>" and "</text>" tags cannot contain attributes.
+ ///
+ internal static string ParseError_TextTagCannotContainAttributes
+ {
+ get { return GetString("ParseError_TextTagCannotContainAttributes"); }
+ }
+
+ ///
+ /// "<text>" and "</text>" tags cannot contain attributes.
+ ///
+ internal static string FormatParseError_TextTagCannotContainAttributes()
+ {
+ return GetString("ParseError_TextTagCannotContainAttributes");
+ }
+
+ ///
+ /// Encountered end tag "{0}" with no matching start tag. Are your start/end tags properly balanced?
+ ///
+ internal static string ParseError_UnexpectedEndTag
+ {
+ get { return GetString("ParseError_UnexpectedEndTag"); }
+ }
+
+ ///
+ /// Encountered end tag "{0}" with no matching start tag. Are your start/end tags properly balanced?
+ ///
+ internal static string FormatParseError_UnexpectedEndTag(object p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("ParseError_UnexpectedEndTag"), p0);
+ }
+
+ ///
+ /// Unexpected {0} after section keyword. Section names must start with an "_" or alphabetic character, and the remaining characters must be either "_" or alphanumeric.
+ ///
+ internal static string ParseError_Unexpected_Character_At_Section_Name_Start
+ {
+ get { return GetString("ParseError_Unexpected_Character_At_Section_Name_Start"); }
+ }
+
+ ///
+ /// Unexpected {0} after section keyword. Section names must start with an "_" or alphabetic character, and the remaining characters must be either "_" or alphanumeric.
+ ///
+ internal static string FormatParseError_Unexpected_Character_At_Section_Name_Start(object p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("ParseError_Unexpected_Character_At_Section_Name_Start"), p0);
+ }
+
+ ///
+ /// "{0}" is not valid at the start of a code block. Only identifiers, keywords, comments, "(" and "{{" are valid.
+ ///
+ internal static string ParseError_Unexpected_Character_At_Start_Of_CodeBlock_CS
+ {
+ get { return GetString("ParseError_Unexpected_Character_At_Start_Of_CodeBlock_CS"); }
+ }
+
+ ///
+ /// "{0}" is not valid at the start of a code block. Only identifiers, keywords, comments, "(" and "{{" are valid.
+ ///
+ internal static string FormatParseError_Unexpected_Character_At_Start_Of_CodeBlock_CS(object p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("ParseError_Unexpected_Character_At_Start_Of_CodeBlock_CS"), p0);
+ }
+
+ ///
+ /// End-of-file was found after the "@" character. "@" must be followed by a valid code block. If you want to output an "@", escape it using the sequence: "@@"
+ ///
+ internal static string ParseError_Unexpected_EndOfFile_At_Start_Of_CodeBlock
+ {
+ get { return GetString("ParseError_Unexpected_EndOfFile_At_Start_Of_CodeBlock"); }
+ }
+
+ ///
+ /// End-of-file was found after the "@" character. "@" must be followed by a valid code block. If you want to output an "@", escape it using the sequence: "@@"
+ ///
+ internal static string FormatParseError_Unexpected_EndOfFile_At_Start_Of_CodeBlock()
+ {
+ return GetString("ParseError_Unexpected_EndOfFile_At_Start_Of_CodeBlock");
+ }
+
+ ///
+ /// End-of-file was found after the "@" character. "@" must be followed by a valid code block. If you want to output an "@", escape it using the sequence: "@@"
+ ///
+ internal static string ParseError_Unexpected_EndOfFile_At_Start_Of_CodeBlock1
+ {
+ get { return GetString("ParseError_Unexpected_EndOfFile_At_Start_Of_CodeBlock1"); }
+ }
+
+ ///
+ /// End-of-file was found after the "@" character. "@" must be followed by a valid code block. If you want to output an "@", escape it using the sequence: "@@"
+ ///
+ internal static string FormatParseError_Unexpected_EndOfFile_At_Start_Of_CodeBlock1()
+ {
+ return GetString("ParseError_Unexpected_EndOfFile_At_Start_Of_CodeBlock1");
+ }
+
+ ///
+ /// Unexpected "{" after "@" character. Once inside the body of a code block (@if {}, @{}, etc.) you do not need to use "@{" to switch to code.
+ ///
+ internal static string ParseError_Unexpected_Nested_CodeBlock
+ {
+ get { return GetString("ParseError_Unexpected_Nested_CodeBlock"); }
+ }
+
+ ///
+ /// Unexpected "{" after "@" character. Once inside the body of a code block (@if {}, @{}, etc.) you do not need to use "@{" to switch to code.
+ ///
+ internal static string FormatParseError_Unexpected_Nested_CodeBlock()
+ {
+ return GetString("ParseError_Unexpected_Nested_CodeBlock");
+ }
+
+ ///
+ /// A space or line break was encountered after the "@" character. Only valid identifiers, keywords, comments, "(" and "{" are valid at the start of a code block and they must occur immediately following "@" with no space in between.
+ ///
+ internal static string ParseError_Unexpected_WhiteSpace_At_Start_Of_CodeBlock_CS
+ {
+ get { return GetString("ParseError_Unexpected_WhiteSpace_At_Start_Of_CodeBlock_CS"); }
+ }
+
+ ///
+ /// A space or line break was encountered after the "@" character. Only valid identifiers, keywords, comments, "(" and "{" are valid at the start of a code block and they must occur immediately following "@" with no space in between.
+ ///
+ internal static string FormatParseError_Unexpected_WhiteSpace_At_Start_Of_CodeBlock_CS()
+ {
+ return GetString("ParseError_Unexpected_WhiteSpace_At_Start_Of_CodeBlock_CS");
+ }
+
+ ///
+ /// End of file or an unexpected character was reached before the "{0}" tag could be parsed. Elements inside markup blocks must be complete. They must either be self-closing ("<br />") or have matching end tags ("<p>Hello</p>"). If you intended to display a "<" character, use the "<" HTML entity.
+ ///
+ internal static string ParseError_UnfinishedTag
+ {
+ get { return GetString("ParseError_UnfinishedTag"); }
+ }
+
+ ///
+ /// End of file or an unexpected character was reached before the "{0}" tag could be parsed. Elements inside markup blocks must be complete. They must either be self-closing ("<br />") or have matching end tags ("<p>Hello</p>"). If you intended to display a "<" character, use the "<" HTML entity.
+ ///
+ internal static string FormatParseError_UnfinishedTag(object p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("ParseError_UnfinishedTag"), p0);
+ }
+
+ ///
+ /// Unterminated string literal. Strings that start with a quotation mark (") must be terminated before the end of the line. However, strings that start with @ and a quotation mark (@") can span multiple lines.
+ ///
+ internal static string ParseError_Unterminated_String_Literal
+ {
+ get { return GetString("ParseError_Unterminated_String_Literal"); }
+ }
+
+ ///
+ /// Unterminated string literal. Strings that start with a quotation mark (") must be terminated before the end of the line. However, strings that start with @ and a quotation mark (@") can span multiple lines.
+ ///
+ internal static string FormatParseError_Unterminated_String_Literal()
+ {
+ return GetString("ParseError_Unterminated_String_Literal");
+ }
+
+ ///
+ /// Cannot complete the tree, StartBlock must be called at least once.
+ ///
+ internal static string ParserContext_CannotCompleteTree_NoRootBlock
+ {
+ get { return GetString("ParserContext_CannotCompleteTree_NoRootBlock"); }
+ }
+
+ ///
+ /// Cannot complete the tree, StartBlock must be called at least once.
+ ///
+ internal static string FormatParserContext_CannotCompleteTree_NoRootBlock()
+ {
+ return GetString("ParserContext_CannotCompleteTree_NoRootBlock");
+ }
+
+ ///
+ /// Cannot complete the tree, there are still open blocks.
+ ///
+ internal static string ParserContext_CannotCompleteTree_OutstandingBlocks
+ {
+ get { return GetString("ParserContext_CannotCompleteTree_OutstandingBlocks"); }
+ }
+
+ ///
+ /// Cannot complete the tree, there are still open blocks.
+ ///
+ internal static string FormatParserContext_CannotCompleteTree_OutstandingBlocks()
+ {
+ return GetString("ParserContext_CannotCompleteTree_OutstandingBlocks");
+ }
+
+ ///
+ /// Cannot finish span, there is no current block. Call StartBlock at least once before finishing a span
+ ///
+ internal static string ParserContext_NoCurrentBlock
+ {
+ get { return GetString("ParserContext_NoCurrentBlock"); }
+ }
+
+ ///
+ /// Cannot finish span, there is no current block. Call StartBlock at least once before finishing a span
+ ///
+ internal static string FormatParserContext_NoCurrentBlock()
+ {
+ return GetString("ParserContext_NoCurrentBlock");
+ }
+
+ ///
+ /// Cannot complete action, the parser has finished. Only CompleteParse can be called to extract the final parser results after the parser has finished
+ ///
+ internal static string ParserContext_ParseComplete
+ {
+ get { return GetString("ParserContext_ParseComplete"); }
+ }
+
+ ///
+ /// Cannot complete action, the parser has finished. Only CompleteParse can be called to extract the final parser results after the parser has finished
+ ///
+ internal static string FormatParserContext_ParseComplete()
+ {
+ return GetString("ParserContext_ParseComplete");
+ }
+
+ ///
+ /// Parser was started with a null Context property. The Context property must be set BEFORE calling any methods on the parser.
+ ///
+ internal static string Parser_Context_Not_Set
+ {
+ get { return GetString("Parser_Context_Not_Set"); }
+ }
+
+ ///
+ /// Parser was started with a null Context property. The Context property must be set BEFORE calling any methods on the parser.
+ ///
+ internal static string FormatParser_Context_Not_Set()
+ {
+ return GetString("Parser_Context_Not_Set");
+ }
+
+ ///
+ /// @section Header { ... }
+ ///
+ internal static string SectionExample_CS
+ {
+ get { return GetString("SectionExample_CS"); }
+ }
+
+ ///
+ /// @section Header { ... }
+ ///
+ internal static string FormatSectionExample_CS()
+ {
+ return GetString("SectionExample_CS");
+ }
+
+ ///
+ /// Cannot perform '{1}' operations on '{0}' instances with different file paths.
+ ///
+ internal static string SourceLocationFilePathDoesNotMatch
+ {
+ get { return GetString("SourceLocationFilePathDoesNotMatch"); }
+ }
+
+ ///
+ /// Cannot perform '{1}' operations on '{0}' instances with different file paths.
+ ///
+ internal static string FormatSourceLocationFilePathDoesNotMatch(object p0, object p1)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("SourceLocationFilePathDoesNotMatch"), p0, p1);
+ }
+
+ ///
+ /// <<unknown>>
+ ///
+ internal static string Symbol_Unknown
+ {
+ get { return GetString("Symbol_Unknown"); }
+ }
+
+ ///
+ /// <<unknown>>
+ ///
+ internal static string FormatSymbol_Unknown()
+ {
+ return GetString("Symbol_Unknown");
+ }
+
+ ///
+ /// In order to put a symbol back, it must have been the symbol which ended at the current position. The specified symbol ends at {0}, but the current position is {1}
+ ///
+ internal static string TokenizerView_CannotPutBack
+ {
+ get { return GetString("TokenizerView_CannotPutBack"); }
+ }
+
+ ///
+ /// In order to put a symbol back, it must have been the symbol which ended at the current position. The specified symbol ends at {0}, but the current position is {1}
+ ///
+ internal static string FormatTokenizerView_CannotPutBack(object p0, object p1)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("TokenizerView_CannotPutBack"), p0, p1);
+ }
+
+ private static string GetString(string name, params string[] formatterNames)
+ {
+ var value = _resourceManager.GetString(name);
+
+ System.Diagnostics.Debug.Assert(value != null);
+
+ if (formatterNames != null)
+ {
+ for (var i = 0; i < formatterNames.Length; i++)
+ {
+ value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
+ }
+ }
+
+ return value;
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/RazorSyntaxTree.cs b/src/Microsoft.AspNetCore.Razor.Evolution/RazorSyntaxTree.cs
new file mode 100644
index 0000000000..401cec77ec
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Razor.Evolution/RazorSyntaxTree.cs
@@ -0,0 +1,46 @@
+// 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;
+using Microsoft.AspNetCore.Razor.Evolution.Legacy;
+
+namespace Microsoft.AspNetCore.Razor.Evolution
+{
+ public abstract class RazorSyntaxTree
+ {
+ internal static RazorSyntaxTree Create(Block root, IEnumerable diagnostics)
+ {
+ if (root == null)
+ {
+ throw new ArgumentNullException(nameof(root));
+ }
+
+ if (diagnostics == null)
+ {
+ throw new ArgumentNullException(nameof(diagnostics));
+ }
+
+ return new DefaultRazorSyntaxTree(root, new List(diagnostics));
+ }
+
+ public static RazorSyntaxTree Parse(RazorSourceDocument source)
+ {
+ if (source == null)
+ {
+ throw new ArgumentNullException(nameof(source));
+ }
+
+ var parser = new RazorParser();
+
+ using (var reader = source.CreateReader())
+ {
+ return parser.Parse(reader);
+ }
+ }
+
+ internal abstract IReadOnlyList Diagnostics { get; }
+
+ internal abstract Block Root { get; }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/BaselineWriter.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/BaselineWriter.cs
new file mode 100644
index 0000000000..b06f8b188f
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/BaselineWriter.cs
@@ -0,0 +1,46 @@
+// 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.Diagnostics;
+using System.IO;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ public static class BaselineWriter
+ {
+ private static object baselineLock = new object();
+
+ [Conditional("GENERATE_BASELINES")]
+ public static void WriteBaseline(string baselineFile, string output)
+ {
+ var root = RecursiveFind("Razor.sln", Path.GetFullPath("."));
+ var baselinePath = Path.Combine(root, baselineFile);
+
+ // Serialize writes to minimize contention for file handles and directory access.
+ lock (baselineLock)
+ {
+ // Update baseline
+ using (var stream = File.Open(baselinePath, FileMode.Create, FileAccess.Write))
+ {
+ using (var writer = new StreamWriter(stream))
+ {
+ writer.Write(output);
+ }
+ }
+ }
+ }
+
+ private static string RecursiveFind(string path, string start)
+ {
+ var test = Path.Combine(start, path);
+ if (File.Exists(test))
+ {
+ return start;
+ }
+ else
+ {
+ return RecursiveFind(path, new DirectoryInfo(start).Parent.FullName);
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/BlockExtensions.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/BlockExtensions.cs
new file mode 100644
index 0000000000..8edde939fe
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/BlockExtensions.cs
@@ -0,0 +1,28 @@
+// 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.
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal static class BlockExtensions
+ {
+ public static void LinkNodes(this Block self)
+ {
+ Span first = null;
+ Span previous = null;
+ foreach (Span span in self.Flatten())
+ {
+ if (first == null)
+ {
+ first = span;
+ }
+ span.Previous = previous;
+
+ if (previous != null)
+ {
+ previous.Next = span;
+ }
+ previous = span;
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/BlockFactory.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/BlockFactory.cs
new file mode 100644
index 0000000000..d289a60977
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/BlockFactory.cs
@@ -0,0 +1,58 @@
+// 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.Collections.Generic;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal class BlockFactory
+ {
+ private SpanFactory _factory;
+
+ public BlockFactory(SpanFactory factory)
+ {
+ _factory = factory;
+ }
+
+ public Block EscapedMarkupTagBlock(string prefix, string suffix)
+ {
+ return EscapedMarkupTagBlock(prefix, suffix, AcceptedCharacters.Any);
+ }
+
+ public Block EscapedMarkupTagBlock(string prefix, string suffix, params SyntaxTreeNode[] children)
+ {
+ return EscapedMarkupTagBlock(prefix, suffix, AcceptedCharacters.Any, children);
+ }
+
+ public Block EscapedMarkupTagBlock(
+ string prefix,
+ string suffix,
+ AcceptedCharacters acceptedCharacters,
+ params SyntaxTreeNode[] children)
+ {
+ var newChildren = new List(
+ new SyntaxTreeNode[]
+ {
+ _factory.Markup(prefix),
+ _factory.BangEscape(),
+ _factory.Markup(suffix).Accepts(acceptedCharacters)
+ });
+
+ newChildren.AddRange(children);
+
+ return new MarkupTagBlock(newChildren.ToArray());
+ }
+
+ public Block MarkupTagBlock(string content)
+ {
+ return MarkupTagBlock(content, AcceptedCharacters.Any);
+ }
+
+ public Block MarkupTagBlock(string content, AcceptedCharacters acceptedCharacters)
+ {
+ return new MarkupTagBlock(
+ _factory.Markup(content).Accepts(acceptedCharacters)
+ );
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/BlockTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/BlockTest.cs
new file mode 100644
index 0000000000..afbf9e8e7c
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/BlockTest.cs
@@ -0,0 +1,62 @@
+// 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.Linq;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal class BlockTest
+ {
+ [Fact]
+ public void ConstructorWithBlockBuilderSetsParent()
+ {
+ // Arrange
+ var builder = new BlockBuilder() { Type = BlockType.Comment };
+ var span = new SpanBuilder() { Kind = SpanKind.Code }.Build();
+ builder.Children.Add(span);
+
+ // Act
+ var block = builder.Build();
+
+ // Assert
+ Assert.Same(block, span.Parent);
+ }
+
+ [Fact]
+ public void ConstructorTransfersInstanceOfChunkGeneratorFromBlockBuilder()
+ {
+ // Arrange
+ var expected = new ExpressionChunkGenerator();
+ var builder = new BlockBuilder()
+ {
+ Type = BlockType.Helper,
+ ChunkGenerator = expected
+ };
+
+ // Act
+ var actual = builder.Build();
+
+ // Assert
+ Assert.Same(expected, actual.ChunkGenerator);
+ }
+
+ [Fact]
+ public void ConstructorTransfersChildrenFromBlockBuilder()
+ {
+ // Arrange
+ var expected = new SpanBuilder() { Kind = SpanKind.Code }.Build();
+ var builder = new BlockBuilder()
+ {
+ Type = BlockType.Functions
+ };
+ builder.Children.Add(expected);
+
+ // Act
+ var block = builder.Build();
+
+ // Assert
+ Assert.Same(expected, block.Children.Single());
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/BlockTypes.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/BlockTypes.cs
new file mode 100644
index 0000000000..d05d4d3f4c
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/BlockTypes.cs
@@ -0,0 +1,202 @@
+// 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.Collections.Generic;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ // The product code doesn't need this, but having subclasses for the block types makes tests much cleaner :)
+
+ internal class StatementBlock : Block
+ {
+ private const BlockType ThisBlockType = BlockType.Statement;
+
+ public StatementBlock(IParentChunkGenerator chunkGenerator, IReadOnlyList children)
+ : base(ThisBlockType, children, chunkGenerator)
+ {
+ }
+
+ public StatementBlock(IParentChunkGenerator chunkGenerator, params SyntaxTreeNode[] children)
+ : this(chunkGenerator, (IReadOnlyList)children)
+ {
+ }
+
+ public StatementBlock(params SyntaxTreeNode[] children)
+ : this(ParentChunkGenerator.Null, children)
+ {
+ }
+ }
+
+ internal class DirectiveBlock : Block
+ {
+ private const BlockType ThisBlockType = BlockType.Directive;
+
+ public DirectiveBlock(IParentChunkGenerator chunkGenerator, IReadOnlyList children)
+ : base(ThisBlockType, children, chunkGenerator)
+ {
+ }
+
+ public DirectiveBlock(IParentChunkGenerator chunkGenerator, params SyntaxTreeNode[] children)
+ : this(chunkGenerator, (IReadOnlyList)children)
+ {
+ }
+
+ public DirectiveBlock(params SyntaxTreeNode[] children)
+ : this(ParentChunkGenerator.Null, children)
+ {
+ }
+ }
+
+ internal class FunctionsBlock : Block
+ {
+ private const BlockType ThisBlockType = BlockType.Functions;
+
+ public FunctionsBlock(IParentChunkGenerator chunkGenerator, IReadOnlyList children)
+ : base(ThisBlockType, children, chunkGenerator)
+ {
+ }
+
+ public FunctionsBlock(IParentChunkGenerator chunkGenerator, params SyntaxTreeNode[] children)
+ : this(chunkGenerator, (IReadOnlyList)children)
+ {
+ }
+
+ public FunctionsBlock(params SyntaxTreeNode[] children)
+ : this(ParentChunkGenerator.Null, children)
+ {
+ }
+ }
+
+ internal class ExpressionBlock : Block
+ {
+ private const BlockType ThisBlockType = BlockType.Expression;
+
+ public ExpressionBlock(IParentChunkGenerator chunkGenerator, IReadOnlyList children)
+ : base(ThisBlockType, children, chunkGenerator)
+ {
+ }
+
+ public ExpressionBlock(IParentChunkGenerator chunkGenerator, params SyntaxTreeNode[] children)
+ : this(chunkGenerator, (IReadOnlyList)children)
+ {
+ }
+
+ public ExpressionBlock(params SyntaxTreeNode[] children)
+ : this(new ExpressionChunkGenerator(), children)
+ {
+ }
+ }
+
+ internal class MarkupTagBlock : Block
+ {
+ private const BlockType ThisBlockType = BlockType.Tag;
+
+ public MarkupTagBlock(params SyntaxTreeNode[] children)
+ : base(ThisBlockType, children, ParentChunkGenerator.Null)
+ {
+ }
+ }
+
+ internal class MarkupBlock : Block
+ {
+ private const BlockType ThisBlockType = BlockType.Markup;
+
+ public MarkupBlock(
+ BlockType blockType,
+ IParentChunkGenerator chunkGenerator,
+ IReadOnlyList children)
+ : base(blockType, children, chunkGenerator)
+ {
+ }
+
+ public MarkupBlock(IParentChunkGenerator chunkGenerator, IReadOnlyList children)
+ : this(ThisBlockType, chunkGenerator, children)
+ {
+ }
+
+ public MarkupBlock(IParentChunkGenerator chunkGenerator, params SyntaxTreeNode[] children)
+ : this(chunkGenerator, (IReadOnlyList)children)
+ {
+ }
+
+ public MarkupBlock(params SyntaxTreeNode[] children)
+ : this(ParentChunkGenerator.Null, children)
+ {
+ }
+ }
+
+ internal class SectionBlock : Block
+ {
+ private const BlockType ThisBlockType = BlockType.Section;
+
+ public SectionBlock(IParentChunkGenerator chunkGenerator, IReadOnlyList children)
+ : base(ThisBlockType, children, chunkGenerator)
+ {
+ }
+
+ public SectionBlock(IParentChunkGenerator chunkGenerator, params SyntaxTreeNode[] children)
+ : this(chunkGenerator, (IReadOnlyList)children)
+ {
+ }
+
+ public SectionBlock(params SyntaxTreeNode[] children)
+ : this(ParentChunkGenerator.Null, children)
+ {
+ }
+
+ public SectionBlock(IReadOnlyList children)
+ : this(ParentChunkGenerator.Null, children)
+ {
+ }
+ }
+
+ internal class TemplateBlock : Block
+ {
+ private const BlockType ThisBlockType = BlockType.Template;
+
+ public TemplateBlock(IParentChunkGenerator chunkGenerator, IReadOnlyList children)
+ : base(ThisBlockType, children, chunkGenerator)
+ {
+ }
+
+ public TemplateBlock(IParentChunkGenerator chunkGenerator, params SyntaxTreeNode[] children)
+ : this(chunkGenerator, (IReadOnlyList)children)
+ {
+ }
+
+ public TemplateBlock(params SyntaxTreeNode[] children)
+ : this(new TemplateBlockChunkGenerator(), children)
+ {
+ }
+
+ public TemplateBlock(IReadOnlyList children)
+ : this(new TemplateBlockChunkGenerator(), children)
+ {
+ }
+ }
+
+ internal class CommentBlock : Block
+ {
+ private const BlockType ThisBlockType = BlockType.Comment;
+
+ public CommentBlock(IParentChunkGenerator chunkGenerator, IReadOnlyList children)
+ : base(ThisBlockType, children, chunkGenerator)
+ {
+ }
+
+ public CommentBlock(IParentChunkGenerator chunkGenerator, params SyntaxTreeNode[] children)
+ : this(chunkGenerator, (IReadOnlyList)children)
+ {
+ }
+
+ public CommentBlock(params SyntaxTreeNode[] children)
+ : this(new RazorCommentChunkGenerator(), children)
+ {
+ }
+
+ public CommentBlock(IReadOnlyList children)
+ : this(new RazorCommentChunkGenerator(), children)
+ {
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpAutoCompleteTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpAutoCompleteTest.cs
new file mode 100644
index 0000000000..cb9ab28a40
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpAutoCompleteTest.cs
@@ -0,0 +1,138 @@
+// 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 Xunit;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal class CSharpAutoCompleteTest : CsHtmlCodeParserTestBase
+ {
+ [Fact]
+ public void FunctionsDirectiveAutoCompleteAtEOF()
+ {
+ ParseBlockTest(
+ "@functions{",
+ new FunctionsBlock(
+ Factory.CodeTransition("@")
+ .Accepts(AcceptedCharacters.None),
+ Factory.MetaCode("functions{")
+ .Accepts(AcceptedCharacters.None),
+ Factory.EmptyCSharp()
+ .AsFunctionsBody()
+ .With(new AutoCompleteEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString)
+ {
+ AutoCompleteString = "}"
+ })),
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF("functions", "}", "{"),
+ new SourceLocation(10, 0, 10),
+ length: 1));
+ }
+
+ [Fact]
+ public void SectionDirectiveAutoCompleteAtEOF()
+ {
+ ParseBlockTest("@section Header {",
+ new SectionBlock(new SectionChunkGenerator("Header"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section Header {")
+ .AutoCompleteWith("}", atEndOfSpan: true)
+ .Accepts(AcceptedCharacters.Any),
+ new MarkupBlock()),
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF("section", "}", "{"),
+ new SourceLocation(16, 0, 16),
+ length: 1));
+ }
+
+ [Fact]
+ public void VerbatimBlockAutoCompleteAtEOF()
+ {
+ ParseBlockTest("@{",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.EmptyCSharp()
+ .AsStatement()
+ .With(new AutoCompleteEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString) { AutoCompleteString = "}" })
+ ),
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF(
+ LegacyResources.BlockName_Code, "}", "{"),
+ new SourceLocation(1, 0, 1),
+ length: 1));
+ }
+
+ [Fact]
+ public void FunctionsDirectiveAutoCompleteAtStartOfFile()
+ {
+ ParseBlockTest("@functions{" + Environment.NewLine
+ + "foo",
+ new FunctionsBlock(
+ Factory.CodeTransition("@")
+ .Accepts(AcceptedCharacters.None),
+ Factory.MetaCode("functions{")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code(Environment.NewLine + "foo")
+ .AsFunctionsBody()
+ .With(new AutoCompleteEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString)
+ {
+ AutoCompleteString = "}"
+ })),
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF("functions", "}", "{"),
+ new SourceLocation(10, 0, 10),
+ length: 1));
+ }
+
+ [Fact]
+ public void SectionDirectiveAutoCompleteAtStartOfFile()
+ {
+ ParseBlockTest("@section Header {" + Environment.NewLine
+ + "Foo
",
+ new SectionBlock(new SectionChunkGenerator("Header"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section Header {")
+ .AutoCompleteWith("}", atEndOfSpan: true)
+ .Accepts(AcceptedCharacters.Any),
+ new MarkupBlock(
+ Factory.Markup(Environment.NewLine),
+ new MarkupTagBlock(
+ Factory.Markup("")),
+ Factory.Markup("Foo"),
+ new MarkupTagBlock(
+ Factory.Markup("
")))),
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF("section", "}", "{"),
+ new SourceLocation(16, 0, 16),
+ length: 1));
+ }
+
+ [Fact]
+ public void VerbatimBlockAutoCompleteAtStartOfFile()
+ {
+ ParseBlockTest("@{" + Environment.NewLine
+ + "",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code(Environment.NewLine)
+ .AsStatement()
+ .With(new AutoCompleteEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString) { AutoCompleteString = "}" }),
+ new MarkupBlock(
+ new MarkupTagBlock(
+ Factory.Markup("").Accepts(AcceptedCharacters.None)),
+ new MarkupTagBlock(
+ Factory.Markup("
").Accepts(AcceptedCharacters.None))),
+ Factory.Span(SpanKind.Code, new CSharpSymbol(Factory.LocationTracker.CurrentLocation, string.Empty, CSharpSymbolType.Unknown))
+ .With(new StatementChunkGenerator())
+ ),
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF(
+ LegacyResources.BlockName_Code, "}", "{"),
+ new SourceLocation(1, 0, 1),
+ length: 1));
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpBlockTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpBlockTest.cs
new file mode 100644
index 0000000000..ea9d24ad1a
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpBlockTest.cs
@@ -0,0 +1,1258 @@
+// 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 Xunit;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal class CSharpBlockTest : CsHtmlCodeParserTestBase
+ {
+ [Fact]
+ public void ParseBlock_NestedCodeBlockWithCSharpAt()
+ {
+ ParseBlockTest("{ if (true) { var val = @x; if (val != 3) { } } }",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory
+ .Code(" if (true) { var val = @x; if (val != 3) { } } ")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.Any)
+ .AutoCompleteWith(autoCompleteString: null, atEndOfSpan: false),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlock_NestedCodeBlockWithMarkupSetsDotAsMarkup()
+ {
+ ParseBlockTest("if (true) { @if(false) { @something.
} }",
+ new StatementBlock(
+ Factory.Code("if (true) { ").AsStatement(),
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("if(false) {").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ BlockFactory.MarkupTagBlock("", AcceptedCharacters.None),
+ Factory.EmptyHtml(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("something")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: false)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.Markup("."),
+ BlockFactory.MarkupTagBlock("
", AcceptedCharacters.None),
+ Factory.Markup(" ").Accepts(AcceptedCharacters.None)),
+ Factory.Code("}").AsStatement()),
+ Factory.Code(" }").AsStatement()));
+ }
+
+ [Fact]
+ public void BalancingBracketsIgnoresStringLiteralCharactersAndBracketsInsideSingleLineComments()
+ {
+ SingleSpanBlockTest(@"if(foo) {
+ // bar } "" baz '
+ zoop();
+}", BlockType.Statement, SpanKind.Code);
+ }
+
+ [Fact]
+ public void NestedCodeBlockWithAtDoesntCauseError()
+ {
+ ParseBlockTest("if (true) { @if(false) { } }",
+ new StatementBlock(
+ Factory.Code("if (true) { ").AsStatement(),
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("if(false) { }").AsStatement()
+ ),
+ Factory.Code(" }").AsStatement()));
+ }
+
+ [Fact]
+ public void BalancingBracketsIgnoresStringLiteralCharactersAndBracketsInsideBlockComments()
+ {
+ SingleSpanBlockTest(
+ @"if(foo) {
+ /* bar } "" */ ' baz } '
+ zoop();
+}", BlockType.Statement, SpanKind.Code);
+ }
+
+ [Fact]
+ public void ParseBlockSkipsParenthesisedExpressionAndThenBalancesBracesIfFirstIdentifierIsForKeyword()
+ {
+ SingleSpanBlockTest(
+ "for(int i = 0; i < 10; new Foo { Bar = \"baz\" }) { Debug.WriteLine(@\"foo } bar\"); }",
+ BlockType.Statement,
+ SpanKind.Code,
+ acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSkipsParenthesisedExpressionAndThenBalancesBracesIfFirstIdentifierIsForeachKeyword()
+ {
+ SingleSpanBlockTest(
+ "foreach(int i = 0; i < 10; new Foo { Bar = \"baz\" }) { Debug.WriteLine(@\"foo } bar\"); }",
+ BlockType.Statement,
+ SpanKind.Code,
+ acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSkipsParenthesisedExpressionAndThenBalancesBracesIfFirstIdentifierIsWhileKeyword()
+ {
+ SingleSpanBlockTest(
+ "while(int i = 0; i < 10; new Foo { Bar = \"baz\" }) { Debug.WriteLine(@\"foo } bar\"); }",
+ BlockType.Statement,
+ SpanKind.Code,
+ acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSkipsParenthesisedExpressionAndThenBalancesBracesIfFirstIdentifierIsUsingKeywordFollowedByParen()
+ {
+ SingleSpanBlockTest(
+ "using(int i = 0; i < 10; new Foo { Bar = \"baz\" }) { Debug.WriteLine(@\"foo } bar\"); }",
+ BlockType.Statement,
+ SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsUsingsNestedWithinOtherBlocks()
+ {
+ SingleSpanBlockTest(
+ "if(foo) { using(int i = 0; i < 10; new Foo { Bar = \"baz\" }) { Debug.WriteLine(@\"foo } bar\"); } }",
+ BlockType.Statement,
+ SpanKind.Code);
+ }
+
+ [Fact]
+ public void ParseBlockSkipsParenthesisedExpressionAndThenBalancesBracesIfFirstIdentifierIsIfKeywordWithNoElseBranches()
+ {
+ SingleSpanBlockTest(
+ "if(int i = 0; i < 10; new Foo { Bar = \"baz\" }) { Debug.WriteLine(@\"foo } bar\"); }",
+ BlockType.Statement,
+ SpanKind.Code);
+ }
+
+ [Fact]
+ public void ParseBlockAllowsEmptyBlockStatement()
+ {
+ SingleSpanBlockTest("if(false) { }", BlockType.Statement, SpanKind.Code);
+ }
+
+ [Fact]
+ public void ParseBlockTerminatesParenBalancingAtEOF()
+ {
+ ImplicitExpressionTest(
+ "Html.En(code()", "Html.En(code()",
+ AcceptedCharacters.Any,
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_CloseBracket_Before_EOF("(", ")"),
+ new SourceLocation(8, 0, 8),
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseBlockSupportsBlockCommentBetweenIfAndElseClause()
+ {
+ SingleSpanBlockTest(
+ "if(foo) { bar(); } /* Foo */ /* Bar */ else { baz(); }",
+ BlockType.Statement,
+ SpanKind.Code,
+ acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsRazorCommentBetweenIfAndElseClause()
+ {
+ RunRazorCommentBetweenClausesTest(
+ "if(foo) { bar(); } ", " else { baz(); }",
+ acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsBlockCommentBetweenElseIfAndElseClause()
+ {
+ SingleSpanBlockTest(
+ "if(foo) { bar(); } else if(bar) { baz(); } /* Foo */ /* Bar */ else { biz(); }",
+ BlockType.Statement,
+ SpanKind.Code,
+ acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsRazorCommentBetweenElseIfAndElseClause()
+ {
+ RunRazorCommentBetweenClausesTest(
+ "if(foo) { bar(); } else if(bar) { baz(); } ", " else { baz(); }",
+ acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsBlockCommentBetweenIfAndElseIfClause()
+ {
+ SingleSpanBlockTest(
+ "if(foo) { bar(); } /* Foo */ /* Bar */ else if(bar) { baz(); }",
+ BlockType.Statement,
+ SpanKind.Code);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsRazorCommentBetweenIfAndElseIfClause()
+ {
+ RunRazorCommentBetweenClausesTest("if(foo) { bar(); } ", " else if(bar) { baz(); }");
+ }
+
+ [Fact]
+ public void ParseBlockSupportsLineCommentBetweenIfAndElseClause()
+ {
+ SingleSpanBlockTest(@"if(foo) { bar(); }
+// Foo
+// Bar
+else { baz(); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsLineCommentBetweenElseIfAndElseClause()
+ {
+ SingleSpanBlockTest(@"if(foo) { bar(); } else if(bar) { baz(); }
+// Foo
+// Bar
+else { biz(); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsLineCommentBetweenIfAndElseIfClause()
+ {
+ SingleSpanBlockTest(@"if(foo) { bar(); }
+// Foo
+// Bar
+else if(bar) { baz(); }", BlockType.Statement, SpanKind.Code);
+ }
+
+ [Fact]
+ public void ParseBlockParsesElseIfBranchesOfIfStatement()
+ {
+ const string ifStatement = @"if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
+ Debug.WriteLine(@""foo } bar"");
+}";
+ const string elseIfBranch = @" else if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
+ Debug.WriteLine(@""bar } baz"");
+}";
+ const string document = ifStatement + elseIfBranch;
+
+ SingleSpanBlockTest(document, BlockType.Statement, SpanKind.Code);
+ }
+
+ [Fact]
+ public void ParseBlockParsesMultipleElseIfBranchesOfIfStatement()
+ {
+ const string ifStatement = @"if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
+ Debug.WriteLine(@""foo } bar"");
+}";
+ const string elseIfBranch = @" else if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
+ Debug.WriteLine(@""bar } baz"");
+}";
+ const string document = ifStatement + elseIfBranch + elseIfBranch + elseIfBranch + elseIfBranch;
+ SingleSpanBlockTest(document, BlockType.Statement, SpanKind.Code);
+ }
+
+ [Fact]
+ public void ParseBlockParsesMultipleElseIfBranchesOfIfStatementFollowedByOneElseBranch()
+ {
+ const string ifStatement = @"if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
+ Debug.WriteLine(@""foo } bar"");
+}";
+ const string elseIfBranch = @" else if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
+ Debug.WriteLine(@""bar } baz"");
+}";
+ const string elseBranch = @" else { Debug.WriteLine(@""bar } baz""); }";
+ const string document = ifStatement + elseIfBranch + elseIfBranch + elseBranch;
+
+ SingleSpanBlockTest(document, BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockStopsParsingCodeAfterElseBranch()
+ {
+ const string ifStatement = @"if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
+ Debug.WriteLine(@""foo } bar"");
+}";
+ const string elseIfBranch = @" else if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
+ Debug.WriteLine(@""bar } baz"");
+}";
+ const string elseBranch = @" else { Debug.WriteLine(@""bar } baz""); }";
+ const string document = ifStatement + elseIfBranch + elseBranch + elseIfBranch;
+ const string expected = ifStatement + elseIfBranch + elseBranch;
+
+ ParseBlockTest(
+ document,
+ new StatementBlock(Factory.Code(expected).AsStatement().Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlockStopsParsingIfIfStatementNotFollowedByElse()
+ {
+ const string document = @"if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
+ Debug.WriteLine(@""foo } bar"");
+}";
+
+ SingleSpanBlockTest(document, BlockType.Statement, SpanKind.Code);
+ }
+
+ [Fact]
+ public void ParseBlockAcceptsElseIfWithNoCondition()
+ {
+ // We don't want to be a full C# parser - If the else if is missing it's condition, the C# compiler
+ // can handle that, we have all the info we need to keep parsing
+ const string ifBranch = @"if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
+ Debug.WriteLine(@""foo } bar"");
+}";
+ const string elseIfBranch = @" else if { foo(); }";
+ const string document = ifBranch + elseIfBranch;
+
+ SingleSpanBlockTest(document, BlockType.Statement, SpanKind.Code);
+ }
+
+ [Fact]
+ public void ParseBlockCorrectlyParsesDoWhileBlock()
+ {
+ SingleSpanBlockTest(
+ "do { var foo = bar; } while(foo != bar);",
+ BlockType.Statement,
+ SpanKind.Code,
+ acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockCorrectlyParsesDoWhileBlockMissingSemicolon()
+ {
+ SingleSpanBlockTest("do { var foo = bar; } while(foo != bar)", BlockType.Statement, SpanKind.Code);
+ }
+
+ [Fact]
+ public void ParseBlockCorrectlyParsesDoWhileBlockMissingWhileCondition()
+ {
+ SingleSpanBlockTest("do { var foo = bar; } while", BlockType.Statement, SpanKind.Code);
+ }
+
+ [Fact]
+ public void ParseBlockCorrectlyParsesDoWhileBlockMissingWhileConditionWithSemicolon()
+ {
+ SingleSpanBlockTest(
+ "do { var foo = bar; } while;",
+ BlockType.Statement,
+ SpanKind.Code,
+ acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockCorrectlyParsesDoWhileBlockMissingWhileClauseEntirely()
+ {
+ SingleSpanBlockTest("do { var foo = bar; } narf;", "do { var foo = bar; }", BlockType.Statement, SpanKind.Code);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsBlockCommentBetweenDoAndWhileClause()
+ {
+ SingleSpanBlockTest(
+ "do { var foo = bar; } /* Foo */ /* Bar */ while(true);",
+ BlockType.Statement,
+ SpanKind.Code,
+ acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsLineCommentBetweenDoAndWhileClause()
+ {
+ SingleSpanBlockTest(@"do { var foo = bar; }
+// Foo
+// Bar
+while(true);", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsRazorCommentBetweenDoAndWhileClause()
+ {
+ RunRazorCommentBetweenClausesTest(
+ "do { var foo = bar; } ", " while(true);",
+ acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockCorrectlyParsesMarkupInDoWhileBlock()
+ {
+ ParseBlockTest("@do { var foo = bar; Foo
foo++; } while (foo);",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("do { var foo = bar;").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ BlockFactory.MarkupTagBlock("", AcceptedCharacters.None),
+ Factory.Markup("Foo"),
+ BlockFactory.MarkupTagBlock("
", AcceptedCharacters.None),
+ Factory.Markup(" ").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code("foo++; } while (foo);").AsStatement().Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockSkipsParenthesisedExpressionAndThenBalancesBracesIfFirstIdentifierIsSwitchKeyword()
+ {
+ SingleSpanBlockTest(@"switch(foo) {
+ case 0:
+ break;
+ case 1:
+ {
+ break;
+ }
+ case 2:
+ return;
+ default:
+ return;
+}", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSkipsParenthesisedExpressionAndThenBalancesBracesIfFirstIdentifierIsLockKeyword()
+ {
+ SingleSpanBlockTest(
+ "lock(foo) { Debug.WriteLine(@\"foo } bar\"); }",
+ BlockType.Statement,
+ SpanKind.Code,
+ acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockHasErrorsIfNamespaceImportMissingSemicolon()
+ {
+ NamespaceImportTest(
+ "using Foo.Bar.Baz",
+ " Foo.Bar.Baz",
+ acceptedCharacters: AcceptedCharacters.NonWhiteSpace | AcceptedCharacters.WhiteSpace,
+ location: new SourceLocation(17, 0, 17));
+ }
+
+ [Fact]
+ public void ParseBlockHasErrorsIfNamespaceAliasMissingSemicolon()
+ {
+ NamespaceImportTest(
+ "using Foo.Bar.Baz = FooBarBaz",
+ " Foo.Bar.Baz = FooBarBaz",
+ acceptedCharacters: AcceptedCharacters.NonWhiteSpace | AcceptedCharacters.WhiteSpace,
+ location: new SourceLocation(29, 0, 29));
+ }
+
+ [Fact]
+ public void ParseBlockParsesNamespaceImportWithSemicolonForUsingKeywordIfIsInValidFormat()
+ {
+ NamespaceImportTest(
+ "using Foo.Bar.Baz;",
+ " Foo.Bar.Baz",
+ AcceptedCharacters.NonWhiteSpace | AcceptedCharacters.WhiteSpace);
+ }
+
+ [Fact]
+ public void ParseBlockDoesntCaptureWhitespaceAfterUsing()
+ {
+ ParseBlockTest("using Foo ",
+ new DirectiveBlock(
+ Factory.Code("using Foo")
+ .AsNamespaceImport(" Foo")
+ .Accepts(AcceptedCharacters.NonWhiteSpace | AcceptedCharacters.WhiteSpace)));
+ }
+
+ [Fact]
+ public void ParseBlockParsesNamespaceAliasWithSemicolonForUsingKeywordIfIsInValidFormat()
+ {
+ NamespaceImportTest(
+ "using FooBarBaz = FooBarBaz;",
+ " FooBarBaz = FooBarBaz",
+ AcceptedCharacters.NonWhiteSpace | AcceptedCharacters.WhiteSpace);
+ }
+
+ [Fact]
+ public void ParseBlockTerminatesUsingKeywordAtEOFAndOutputsFileCodeBlock()
+ {
+ SingleSpanBlockTest("using ", BlockType.Statement, SpanKind.Code);
+ }
+
+ [Fact]
+ public void ParseBlockTerminatesSingleLineCommentAtEndOfFile()
+ {
+ const string document = "foreach(var f in Foo) { // foo bar baz";
+ SingleSpanBlockTest(
+ document,
+ document,
+ BlockType.Statement,
+ SpanKind.Code,
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF("foreach", '}', '{'),
+ SourceLocation.Zero,
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseBlockTerminatesBlockCommentAtEndOfFile()
+ {
+ const string document = "foreach(var f in Foo) { /* foo bar baz";
+ SingleSpanBlockTest(
+ document,
+ document,
+ BlockType.Statement,
+ SpanKind.Code,
+ new RazorError(
+ LegacyResources.ParseError_BlockComment_Not_Terminated,
+ new SourceLocation(24, 0, 24),
+ length: 1),
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF("foreach", '}', '{'),
+ SourceLocation.Zero,
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseBlockTerminatesSingleSlashAtEndOfFile()
+ {
+ const string document = "foreach(var f in Foo) { / foo bar baz";
+ SingleSpanBlockTest(
+ document,
+ document,
+ BlockType.Statement,
+ SpanKind.Code,
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF("foreach", '}', '{'),
+ SourceLocation.Zero,
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseBlockSupportsBlockCommentBetweenTryAndFinallyClause()
+ {
+ SingleSpanBlockTest("try { bar(); } /* Foo */ /* Bar */ finally { baz(); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsRazorCommentBetweenTryAndFinallyClause()
+ {
+ RunRazorCommentBetweenClausesTest("try { bar(); } ", " finally { biz(); }", acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsBlockCommentBetweenCatchAndFinallyClause()
+ {
+ SingleSpanBlockTest(
+ "try { bar(); } catch(bar) { baz(); } /* Foo */ /* Bar */ finally { biz(); }",
+ BlockType.Statement,
+ SpanKind.Code,
+ acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsRazorCommentBetweenCatchAndFinallyClause()
+ {
+ RunRazorCommentBetweenClausesTest(
+ "try { bar(); } catch(bar) { baz(); } ", " finally { biz(); }",
+ acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsBlockCommentBetweenTryAndCatchClause()
+ {
+ SingleSpanBlockTest("try { bar(); } /* Foo */ /* Bar */ catch(bar) { baz(); }", BlockType.Statement, SpanKind.Code);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsRazorCommentBetweenTryAndCatchClause()
+ {
+ RunRazorCommentBetweenClausesTest("try { bar(); }", " catch(bar) { baz(); }");
+ }
+
+ [Fact]
+ public void ParseBlockSupportsLineCommentBetweenTryAndFinallyClause()
+ {
+ SingleSpanBlockTest(@"try { bar(); }
+// Foo
+// Bar
+finally { baz(); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsLineCommentBetweenCatchAndFinallyClause()
+ {
+ SingleSpanBlockTest(@"try { bar(); } catch(bar) { baz(); }
+// Foo
+// Bar
+finally { biz(); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsLineCommentBetweenTryAndCatchClause()
+ {
+ SingleSpanBlockTest(@"try { bar(); }
+// Foo
+// Bar
+catch(bar) { baz(); }", BlockType.Statement, SpanKind.Code);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsTryStatementWithNoAdditionalClauses()
+ {
+ SingleSpanBlockTest("try { var foo = new { } }", BlockType.Statement, SpanKind.Code);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsMarkupWithinTryClause()
+ {
+ RunSimpleWrappedMarkupTest(
+ prefix: "try {",
+ markup: " Foo
",
+ suffix: "}",
+ expectedMarkup: new MarkupBlock(
+ Factory.Markup(" "),
+ BlockFactory.MarkupTagBlock("", AcceptedCharacters.None),
+ Factory.Markup("Foo"),
+ BlockFactory.MarkupTagBlock("
", AcceptedCharacters.None),
+ Factory.Markup(" ").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlockSupportsTryStatementWithOneCatchClause()
+ {
+ SingleSpanBlockTest("try { var foo = new { } } catch(Foo Bar Baz) { var foo = new { } }", BlockType.Statement, SpanKind.Code);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsMarkupWithinCatchClause()
+ {
+ RunSimpleWrappedMarkupTest(
+ prefix: "try { var foo = new { } } catch(Foo Bar Baz) {",
+ markup: " Foo
",
+ suffix: "}",
+ expectedMarkup: new MarkupBlock(
+ Factory.Markup(" "),
+ BlockFactory.MarkupTagBlock("", AcceptedCharacters.None),
+ Factory.Markup("Foo"),
+ BlockFactory.MarkupTagBlock("
", AcceptedCharacters.None),
+ Factory.Markup(" ").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlockSupportsTryStatementWithMultipleCatchClause()
+ {
+ SingleSpanBlockTest(
+ "try { var foo = new { } } catch(Foo Bar Baz) { var foo = new { } } catch(Foo Bar Baz) " +
+ "{ var foo = new { } } catch(Foo Bar Baz) { var foo = new { } }",
+ BlockType.Statement,
+ SpanKind.Code);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsExceptionLessCatchClauses()
+ {
+ SingleSpanBlockTest("try { var foo = new { } } catch { var foo = new { } }", BlockType.Statement, SpanKind.Code);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsMarkupWithinAdditionalCatchClauses()
+ {
+ RunSimpleWrappedMarkupTest(
+ prefix: "try { var foo = new { } } catch(Foo Bar Baz) { var foo = new { } } catch(Foo Bar Baz) " +
+ "{ var foo = new { } } catch(Foo Bar Baz) {",
+ markup: " Foo
",
+ suffix: "}",
+ expectedMarkup: new MarkupBlock(
+ Factory.Markup(" "),
+ BlockFactory.MarkupTagBlock("", AcceptedCharacters.None),
+ Factory.Markup("Foo"),
+ BlockFactory.MarkupTagBlock("
", AcceptedCharacters.None),
+ Factory.Markup(" ").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlockSupportsTryStatementWithFinallyClause()
+ {
+ SingleSpanBlockTest("try { var foo = new { } } finally { var foo = new { } }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockSupportsMarkupWithinFinallyClause()
+ {
+ RunSimpleWrappedMarkupTest(
+ prefix: "try { var foo = new { } } finally {",
+ markup: " Foo
",
+ suffix: "}",
+ expectedMarkup: new MarkupBlock(
+ Factory.Markup(" "),
+ BlockFactory.MarkupTagBlock("", AcceptedCharacters.None),
+ Factory.Markup("Foo"),
+ BlockFactory.MarkupTagBlock("
", AcceptedCharacters.None),
+ Factory.Markup(" ").Accepts(AcceptedCharacters.None)),
+ acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockStopsParsingCatchClausesAfterFinallyBlock()
+ {
+ var expectedContent = "try { var foo = new { } } finally { var foo = new { } }";
+ SingleSpanBlockTest(expectedContent + " catch(Foo Bar Baz) { }", expectedContent, BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockDoesNotAllowMultipleFinallyBlocks()
+ {
+ var expectedContent = "try { var foo = new { } } finally { var foo = new { } }";
+ SingleSpanBlockTest(expectedContent + " finally { }", expectedContent, BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
+ }
+
+ [Fact]
+ public void ParseBlockAcceptsTrailingDotIntoImplicitExpressionWhenEmbeddedInCode()
+ {
+ // Arrange
+ ParseBlockTest(@"if(foo) { @foo. }",
+ new StatementBlock(
+ Factory.Code("if(foo) { ").AsStatement(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("foo.")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)
+ ),
+ Factory.Code(" }").AsStatement()
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockParsesExpressionOnSwitchCharacterFollowedByOpenParen()
+ {
+ // Arrange
+ ParseBlockTest(@"if(foo) { @(foo + bar) }",
+ new StatementBlock(
+ Factory.Code("if(foo) { ").AsStatement(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
+ Factory.Code("foo + bar").AsExpression(),
+ Factory.MetaCode(")").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code(" }").AsStatement()
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockParsesExpressionOnSwitchCharacterFollowedByIdentifierStart()
+ {
+ // Arrange
+ ParseBlockTest(@"if(foo) { @foo[4].bar() }",
+ new StatementBlock(
+ Factory.Code("if(foo) { ").AsStatement(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("foo[4].bar()")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)
+ ),
+ Factory.Code(" }").AsStatement()
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockTreatsDoubleAtSignAsEscapeSequenceIfAtStatementStart()
+ {
+ // Arrange
+ ParseBlockTest(@"if(foo) { @@class.Foo() }",
+ new StatementBlock(
+ Factory.Code("if(foo) { ").AsStatement(),
+ Factory.Code("@").Hidden(),
+ Factory.Code("@class.Foo() }").AsStatement()
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockTreatsAtSignsAfterFirstPairAsPartOfCSharpStatement()
+ {
+ // Arrange
+ ParseBlockTest(@"if(foo) { @@@@class.Foo() }",
+ new StatementBlock(
+ Factory.Code("if(foo) { ").AsStatement(),
+ Factory.Code("@").Hidden(),
+ Factory.Code("@@@class.Foo() }").AsStatement()
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockDoesNotParseMarkupStatementOrExpressionOnSwitchCharacterNotFollowedByOpenAngleOrColon()
+ {
+ // Arrange
+ ParseBlockTest("if(foo) { @\"Foo\".ToString(); }",
+ new StatementBlock(
+ Factory.Code("if(foo) { @\"Foo\".ToString(); }").AsStatement()));
+ }
+
+ [Fact]
+ public void ParsersCanNestRecursively()
+ {
+ // Arrange
+ ParseBlockTest("foreach(var c in db.Categories) {" + Environment.NewLine
+ + " " + Environment.NewLine
+ + "
@c.Name
" + Environment.NewLine
+ + "
" + Environment.NewLine
+ + " @foreach(var p in c.Products) {" + Environment.NewLine
+ + " - @p.Name
" + Environment.NewLine
+ + " }" + Environment.NewLine
+ + "
" + Environment.NewLine
+ + "
" + Environment.NewLine
+ + " }",
+ new StatementBlock(
+ Factory.Code("foreach(var c in db.Categories) {" + Environment.NewLine).AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ BlockFactory.MarkupTagBlock("", AcceptedCharacters.None),
+ Factory.Markup(Environment.NewLine + " "),
+ BlockFactory.MarkupTagBlock("
", AcceptedCharacters.None),
+ Factory.EmptyHtml(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("c.Name")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ BlockFactory.MarkupTagBlock("
", AcceptedCharacters.None),
+ Factory.Markup(Environment.NewLine + " "),
+ BlockFactory.MarkupTagBlock("
", AcceptedCharacters.None),
+ Factory.Markup(Environment.NewLine),
+ new StatementBlock(
+ Factory.Code(@" ").AsStatement(),
+ Factory.CodeTransition(),
+ Factory.Code("foreach(var p in c.Products) {" + Environment.NewLine).AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ BlockFactory.MarkupTagBlock("- ", AcceptedCharacters.None),
+ new MarkupTagBlock(
+ Factory.Markup("(" href=\"", 183 + Environment.NewLine.Length * 5, 5, 30),
+ new LocationTagged("\"", 246 + Environment.NewLine.Length * 5, 5, 93)),
+ Factory.Markup(" href=\"").With(SpanChunkGenerator.Null),
+ new MarkupBlock(
+ new DynamicAttributeBlockChunkGenerator(
+ new LocationTagged(string.Empty, 190 + Environment.NewLine.Length * 5, 5, 37), 190 + Environment.NewLine.Length * 5, 5, 37),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("Html.ActionUrl(\"Products\", \"Detail\", new { id = p.Id })")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace))),
+ Factory.Markup("\"").With(SpanChunkGenerator.Null)),
+ Factory.Markup(">").Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("p.Name")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ BlockFactory.MarkupTagBlock("", AcceptedCharacters.None),
+ BlockFactory.MarkupTagBlock("
", AcceptedCharacters.None),
+ Factory.Markup(Environment.NewLine).Accepts(AcceptedCharacters.None)),
+ Factory.Code(" }" + Environment.NewLine).AsStatement().Accepts(AcceptedCharacters.None)),
+ Factory.Markup(" "),
+ BlockFactory.MarkupTagBlock("
", AcceptedCharacters.None),
+ Factory.Markup(Environment.NewLine + " "),
+ BlockFactory.MarkupTagBlock("
", AcceptedCharacters.None),
+ Factory.Markup(Environment.NewLine).Accepts(AcceptedCharacters.None)),
+ Factory.Code(" }").AsStatement().Accepts(AcceptedCharacters.None)));
+ }
+
+ public static TheoryData BlockWithEscapedTransitionData
+ {
+ get
+ {
+ var factory = new SpanFactory();
+ var datetimeBlock = new ExpressionBlock(
+ factory.CodeTransition(),
+ factory.Code("DateTime.Now")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace));
+
+ return new TheoryData
+ {
+ {
+ // Double transition in attribute value
+ "{}",
+ CreateStatementBlock(
+ new MarkupBlock(
+ new MarkupTagBlock(
+ factory.Markup("(" foo='", 6, 0, 6), new LocationTagged("'", 14, 0, 14)),
+ factory.Markup(" foo='").With(SpanChunkGenerator.Null),
+ new MarkupBlock(
+ factory.Markup("@").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 12, 0, 12), new LocationTagged("@", 12, 0, 12))).Accepts(AcceptedCharacters.None),
+ factory.Markup("@").With(SpanChunkGenerator.Null).Accepts(AcceptedCharacters.None)),
+ factory.Markup("'").With(SpanChunkGenerator.Null)),
+ factory.Markup(" />").Accepts(AcceptedCharacters.None))))
+ },
+ {
+ // Double transition at the end of attribute value
+ "{}",
+ CreateStatementBlock(
+ new MarkupBlock(
+ new MarkupTagBlock(
+ factory.Markup("(" foo='", 6, 0, 6), new LocationTagged("'", 17, 0, 17)),
+ factory.Markup(" foo='").With(SpanChunkGenerator.Null),
+ factory.Markup("abc").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 12, 0, 12), new LocationTagged("abc", 12, 0, 12))),
+ new MarkupBlock(
+ factory.Markup("@").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 15, 0, 15), new LocationTagged("@", 15, 0, 15))).Accepts(AcceptedCharacters.None),
+ factory.Markup("@").With(SpanChunkGenerator.Null).Accepts(AcceptedCharacters.None)),
+ factory.Markup("'").With(SpanChunkGenerator.Null)),
+ factory.Markup(" />").Accepts(AcceptedCharacters.None))))
+ },
+ {
+ // Double transition at the beginning attribute value
+ "{}",
+ CreateStatementBlock(
+ new MarkupBlock(
+ new MarkupTagBlock(
+ factory.Markup("(" foo='", 6, 0, 6), new LocationTagged("'", 17, 0, 17)),
+ factory.Markup(" foo='").With(SpanChunkGenerator.Null),
+ new MarkupBlock(
+ factory.Markup("@").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 12, 0, 12), new LocationTagged("@", 12, 0, 12))).Accepts(AcceptedCharacters.None),
+ factory.Markup("@").With(SpanChunkGenerator.Null).Accepts(AcceptedCharacters.None)),
+ factory.Markup("def").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 14, 0, 14), new LocationTagged("def", 14, 0, 14))),
+ factory.Markup("'").With(SpanChunkGenerator.Null)),
+ factory.Markup(" />").Accepts(AcceptedCharacters.None))))
+ },
+ {
+ // Double transition in between attribute value
+ "{}",
+ CreateStatementBlock(
+ new MarkupBlock(
+ new MarkupTagBlock(
+ factory.Markup("(" foo='", 6, 0, 6), new LocationTagged("'", 22, 0, 22)),
+ factory.Markup(" foo='").With(SpanChunkGenerator.Null),
+ factory.Markup("abc").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 12, 0, 12), new LocationTagged("abc", 12, 0, 12))),
+ new MarkupBlock(
+ factory.Markup(" @").With(new LiteralAttributeChunkGenerator(new LocationTagged(" ", 15, 0, 15), new LocationTagged("@", 16, 0, 16))).Accepts(AcceptedCharacters.None),
+ factory.Markup("@").With(SpanChunkGenerator.Null).Accepts(AcceptedCharacters.None)),
+ factory.Markup(" def").With(new LiteralAttributeChunkGenerator(new LocationTagged(" ", 18, 0, 18), new LocationTagged("def", 19, 0, 19))),
+ factory.Markup("'").With(SpanChunkGenerator.Null)),
+ factory.Markup(" />").Accepts(AcceptedCharacters.None))))
+ },
+ {
+ // Double transition with expression block
+ "{}",
+ CreateStatementBlock(
+ new MarkupBlock(
+ new MarkupTagBlock(
+ factory.Markup("(" foo='", 6, 0, 6), new LocationTagged("'", 27, 0, 27)),
+ factory.Markup(" foo='").With(SpanChunkGenerator.Null),
+ new MarkupBlock(
+ factory.Markup("@").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 12, 0, 12), new LocationTagged("@", 12, 0, 12))).Accepts(AcceptedCharacters.None),
+ factory.Markup("@").With(SpanChunkGenerator.Null).Accepts(AcceptedCharacters.None)),
+ new MarkupBlock(
+ new DynamicAttributeBlockChunkGenerator(new LocationTagged(string.Empty, 14, 0, 14), 14, 0, 14),
+ factory.EmptyHtml().With(SpanChunkGenerator.Null),
+ datetimeBlock),
+ factory.Markup("'").With(SpanChunkGenerator.Null)),
+ factory.Markup(" />").Accepts(AcceptedCharacters.None))))
+ },
+ {
+ "{}",
+ CreateStatementBlock(
+ new MarkupBlock(
+ new MarkupTagBlock(
+ factory.Markup("(" foo='", 6, 0, 6), new LocationTagged("'", 28, 0, 28)),
+ factory.Markup(" foo='").With(SpanChunkGenerator.Null),
+ new MarkupBlock(
+ new DynamicAttributeBlockChunkGenerator(new LocationTagged(string.Empty, 12, 0, 12), 12, 0, 12),
+ datetimeBlock),
+ new MarkupBlock(
+ factory.Markup(" @").With(new LiteralAttributeChunkGenerator(new LocationTagged(" ", 25, 0, 25), new LocationTagged("@", 26, 0, 26))).Accepts(AcceptedCharacters.None),
+ factory.Markup("@").With(SpanChunkGenerator.Null).Accepts(AcceptedCharacters.None)),
+ factory.Markup("'").With(SpanChunkGenerator.Null)),
+ factory.Markup(" />").Accepts(AcceptedCharacters.None))))
+ },
+ {
+ "{}",
+ CreateStatementBlock(
+ new MarkupBlock(
+ new MarkupTagBlock(
+ factory.Markup("(" foo='", 6, 0, 6), new LocationTagged("'", 27, 0, 27)),
+ factory.Markup(" foo='").With(SpanChunkGenerator.Null),
+ new MarkupBlock(
+ new DynamicAttributeBlockChunkGenerator(new LocationTagged(string.Empty, 12, 0, 12), 12, 0, 12),
+ datetimeBlock),
+ new MarkupBlock(
+ factory.Markup("@").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 25, 0, 25), new LocationTagged("@", 25, 0, 25))).Accepts(AcceptedCharacters.None),
+ factory.Markup("@").With(SpanChunkGenerator.Null).Accepts(AcceptedCharacters.None)),
+ factory.Markup("'").With(SpanChunkGenerator.Null)),
+ factory.Markup(" />").Accepts(AcceptedCharacters.None))))
+ },
+ {
+ "{}",
+ CreateStatementBlock(
+ new MarkupBlock(
+ new MarkupTagBlock(
+ factory.Markup("(" foo='", 6, 0, 6), new LocationTagged("'", 33, 0, 33)),
+ factory.Markup(" foo='").With(SpanChunkGenerator.Null),
+ new MarkupBlock(
+ new DynamicAttributeBlockChunkGenerator(new LocationTagged(string.Empty, 12, 0, 12), 12, 0, 12),
+ new ExpressionBlock(
+ factory.CodeTransition(),
+ factory.MetaCode("(").With(SpanChunkGenerator.Null).Accepts(AcceptedCharacters.None),
+ factory.Code("2+3").AsExpression(),
+ factory.MetaCode(")").With(SpanChunkGenerator.Null).Accepts(AcceptedCharacters.None))),
+ new MarkupBlock(
+ factory.Markup("@").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 18, 0, 18), new LocationTagged("@", 18, 0, 18))).Accepts(AcceptedCharacters.None),
+ factory.Markup("@").With(SpanChunkGenerator.Null).Accepts(AcceptedCharacters.None)),
+ new MarkupBlock(
+ new DynamicAttributeBlockChunkGenerator(new LocationTagged(string.Empty, 20, 0, 20), 20, 0, 20),
+ factory.EmptyHtml().With(SpanChunkGenerator.Null),
+ datetimeBlock),
+ factory.Markup("'").With(SpanChunkGenerator.Null)),
+ factory.Markup(" />").Accepts(AcceptedCharacters.None))))
+ },
+ {
+ "{}",
+ CreateStatementBlock(
+ new MarkupBlock(
+ new MarkupTagBlock(
+ factory.Markup("(" foo='", 6, 0, 6), new LocationTagged("'", 20, 0, 20)),
+ factory.Markup(" foo='").With(SpanChunkGenerator.Null),
+ new MarkupBlock(
+ factory.Markup("@").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 12, 0, 12), new LocationTagged("@", 12, 0, 12))).Accepts(AcceptedCharacters.None),
+ factory.Markup("@").With(SpanChunkGenerator.Null).Accepts(AcceptedCharacters.None)),
+ new MarkupBlock(
+ new DynamicAttributeBlockChunkGenerator(new LocationTagged(string.Empty, 14, 0, 14), 14, 0, 14),
+ factory.EmptyHtml().With(SpanChunkGenerator.Null),
+ new ExpressionBlock(
+ factory.CodeTransition(),
+ factory.MetaCode("(").With(SpanChunkGenerator.Null).Accepts(AcceptedCharacters.None),
+ factory.Code("2+3").AsExpression(),
+ factory.MetaCode(")").With(SpanChunkGenerator.Null).Accepts(AcceptedCharacters.None))),
+ factory.Markup("'").With(SpanChunkGenerator.Null)),
+ factory.Markup(" />").Accepts(AcceptedCharacters.None))))
+ },
+ {
+ // Double transition with email in attribute value
+ "{}",
+ CreateStatementBlock(
+ new MarkupBlock(
+ new MarkupTagBlock(
+ factory.Markup("(" foo='", 6, 0, 6), new LocationTagged("'", 26, 0, 26)),
+ factory.Markup(" foo='").With(SpanChunkGenerator.Null),
+ factory.Markup("abc@def.com").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 12, 0, 12), new LocationTagged("abc@def.com", 12, 0, 12))),
+ new MarkupBlock(
+ factory.Markup(" @").With(new LiteralAttributeChunkGenerator(new LocationTagged(" ", 23, 0, 23), new LocationTagged("@", 24, 0, 24))).Accepts(AcceptedCharacters.None),
+ factory.Markup("@").With(SpanChunkGenerator.Null).Accepts(AcceptedCharacters.None)),
+ factory.Markup("'").With(SpanChunkGenerator.Null)),
+ factory.Markup(" />").Accepts(AcceptedCharacters.None))))
+ },
+ {
+ "{}",
+ CreateStatementBlock(
+ new MarkupBlock(
+ new MarkupTagBlock(
+ factory.Markup("(" foo='", 6, 0, 6), new LocationTagged("'", 27, 0, 27)),
+ factory.Markup(" foo='").With(SpanChunkGenerator.Null),
+ factory.Markup("abc").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 12, 0, 12), new LocationTagged("abc", 12, 0, 12))),
+ new MarkupBlock(
+ factory.Markup("@").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 15, 0, 15), new LocationTagged("@", 15, 0, 15))).Accepts(AcceptedCharacters.None),
+ factory.Markup("@").With(SpanChunkGenerator.Null).Accepts(AcceptedCharacters.None)),
+ factory.Markup("def.com").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 17, 0, 17), new LocationTagged("def.com", 17, 0, 17))),
+ new MarkupBlock(
+ factory.Markup(" @").With(new LiteralAttributeChunkGenerator(new LocationTagged(" ", 24, 0, 24), new LocationTagged("@", 25, 0, 25))).Accepts(AcceptedCharacters.None),
+ factory.Markup("@").With(SpanChunkGenerator.Null).Accepts(AcceptedCharacters.None)),
+ factory.Markup("'").With(SpanChunkGenerator.Null)),
+ factory.Markup(" />").Accepts(AcceptedCharacters.None))))
+ },
+ {
+ // Double transition in complex regex in attribute value
+ @"{}",
+ CreateStatementBlock(
+ new MarkupBlock(
+ new MarkupTagBlock(
+ factory.Markup("(" foo=\"", 6, 0, 6), new LocationTagged("\"", 112, 0, 112)),
+ factory.Markup(" foo=\"").With(SpanChunkGenerator.Null),
+ factory.Markup(@"/^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 12, 0, 12), new LocationTagged(@"/^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+", 12, 0, 12))),
+ new MarkupBlock(
+ factory.Markup("@").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 44, 0, 44), new LocationTagged("@", 44, 0, 44))).Accepts(AcceptedCharacters.None),
+ factory.Markup("@").With(SpanChunkGenerator.Null).Accepts(AcceptedCharacters.None)),
+ factory.Markup(@"[a-z0-9]([a-z0-9-]*[a-z0-9])?\.([a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 46, 0, 46), new LocationTagged(@"[a-z0-9]([a-z0-9-]*[a-z0-9])?\.([a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i", 46, 0, 46))),
+ factory.Markup("\"").With(SpanChunkGenerator.Null)),
+ factory.Markup(" />").Accepts(AcceptedCharacters.None))))
+ },
+ };
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(BlockWithEscapedTransitionData))]
+ public void ParseBlock_WithDoubleTransition_DoesNotThrow(string input, Block expected)
+ {
+ // Act & Assert
+ ParseBlockTest(input, expected);
+ }
+
+ [Fact]
+ public void ParseBlock_WithDoubleTransition_EndOfFile_Throws()
+ {
+ // Arrange
+ var expected = new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ new MarkupBlock(
+ new MarkupTagBlock(
+ Factory.Markup("(" foo='", 6, 0, 6), new LocationTagged(string.Empty, 14, 0, 14)),
+ Factory.Markup(" foo='").With(SpanChunkGenerator.Null),
+ new MarkupBlock(
+ Factory.Markup("@").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 12, 0, 12), new LocationTagged("@", 12, 0, 12))).Accepts(AcceptedCharacters.None),
+ Factory.Markup("@").With(SpanChunkGenerator.Null).Accepts(AcceptedCharacters.None)))),
+ Factory.EmptyHtml()));
+ var expectedErrors = new RazorError[]
+ {
+ new RazorError(
+ @"End of file or an unexpected character was reached before the ""span"" tag could be parsed. Elements inside markup blocks must be complete. They must either be self-closing (""
"") or have matching end tags (""Hello
""). If you intended to display a ""<"" character, use the ""<"" HTML entity.",
+ new SourceLocation(2, 0, 2),
+ length: 4),
+ new RazorError(
+ @"The code block is missing a closing ""}"" character. Make sure you have a matching ""}"" character for all the ""{"" characters within this block, and that none of the ""}"" characters are being interpreted as markup.",
+ SourceLocation.Zero,
+ length: 1),
+ };
+
+ // Act & Assert
+ ParseBlockTest("{("'", 15, 0, 15)),
+ Factory.Markup(" foo='").With(SpanChunkGenerator.Null),
+ new MarkupBlock(
+ new DynamicAttributeBlockChunkGenerator(new LocationTagged(string.Empty, 12, 0, 12), 12, 0, 12),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.EmptyCSharp().AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharacters.NonWhiteSpace))),
+ new MarkupBlock(
+ new DynamicAttributeBlockChunkGenerator(new LocationTagged(" ", 13, 0, 13), 13, 0, 13),
+ Factory.Markup(" ").With(SpanChunkGenerator.Null),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.EmptyCSharp().AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharacters.NonWhiteSpace))),
+ Factory.Markup("'").With(SpanChunkGenerator.Null)),
+ Factory.Markup(" />").Accepts(AcceptedCharacters.None))),
+ Factory.EmptyCSharp().AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None));
+ var expectedErrors = new RazorError[]
+ {
+ new RazorError(
+ @"A space or line break was encountered after the ""@"" character. Only valid identifiers, keywords, comments, ""("" and ""{"" are valid at the start of a code block and they must occur immediately following ""@"" with no space in between.",
+ new SourceLocation(13, 0, 13),
+ length: 1),
+ new RazorError(
+ @"""' />}"" is not valid at the start of a code block. Only identifiers, keywords, comments, ""("" and ""{"" are valid.",
+ new SourceLocation(15, 0, 15),
+ length: 5),
+ };
+
+ // Act & Assert
+ ParseBlockTest("{}", expected, expectedErrors);
+ }
+
+ private void RunRazorCommentBetweenClausesTest(string preComment, string postComment, AcceptedCharacters acceptedCharacters = AcceptedCharacters.Any)
+ {
+ ParseBlockTest(preComment + "@* Foo *@ @* Bar *@" + postComment,
+ new StatementBlock(
+ Factory.Code(preComment).AsStatement(),
+ new CommentBlock(
+ Factory.CodeTransition(CSharpSymbolType.RazorCommentTransition),
+ Factory.MetaCode("*", CSharpSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.Comment(" Foo ", CSharpSymbolType.RazorComment),
+ Factory.MetaCode("*", CSharpSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.CodeTransition(CSharpSymbolType.RazorCommentTransition)
+ ),
+ Factory.Code(" ").AsStatement(),
+ new CommentBlock(
+ Factory.CodeTransition(CSharpSymbolType.RazorCommentTransition),
+ Factory.MetaCode("*", CSharpSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.Comment(" Bar ", CSharpSymbolType.RazorComment),
+ Factory.MetaCode("*", CSharpSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
+ Factory.CodeTransition(CSharpSymbolType.RazorCommentTransition)
+ ),
+ Factory.Code(postComment).AsStatement().Accepts(acceptedCharacters)));
+ }
+
+ private void RunSimpleWrappedMarkupTest(string prefix, string markup, string suffix, MarkupBlock expectedMarkup, AcceptedCharacters acceptedCharacters = AcceptedCharacters.Any)
+ {
+ ParseBlockTest(prefix + markup + suffix,
+ new StatementBlock(
+ Factory.Code(prefix).AsStatement(),
+ expectedMarkup,
+ Factory.Code(suffix).AsStatement().Accepts(acceptedCharacters)
+ ));
+ }
+
+ private void NamespaceImportTest(string content, string expectedNS, AcceptedCharacters acceptedCharacters = AcceptedCharacters.None, string errorMessage = null, SourceLocation? location = null)
+ {
+ var errors = new RazorError[0];
+ if (!string.IsNullOrEmpty(errorMessage) && location.HasValue)
+ {
+ errors = new RazorError[]
+ {
+ new RazorError(errorMessage, location.Value, length: 1)
+ };
+ }
+ ParseBlockTest(content,
+ new DirectiveBlock(
+ Factory.Code(content)
+ .AsNamespaceImport(expectedNS)
+ .Accepts(acceptedCharacters)),
+ errors);
+ }
+
+ private static StatementBlock CreateStatementBlock(MarkupBlock block)
+ {
+ var factory = new SpanFactory();
+ return new StatementBlock(
+ factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ block,
+ factory.EmptyCSharp().AsStatement(),
+ factory.MetaCode("}").Accepts(AcceptedCharacters.None));
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpDirectivesTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpDirectivesTest.cs
new file mode 100644
index 0000000000..ba1e002c5a
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpDirectivesTest.cs
@@ -0,0 +1,426 @@
+// 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 Xunit;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal class CSharpDirectivesTest : CsHtmlCodeParserTestBase
+ {
+ [Fact]
+ public void TagHelperPrefixDirective_NoValueSucceeds()
+ {
+ ParseBlockTest("@tagHelperPrefix \"\"",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory
+ .MetaCode(SyntaxConstants.CSharp.TagHelperPrefixKeyword + " ")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("\"\"")
+ .AsTagHelperPrefixDirective(string.Empty)));
+ }
+
+ [Fact]
+ public void TagHelperPrefixDirective_Succeeds()
+ {
+ ParseBlockTest("@tagHelperPrefix Foo",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory
+ .MetaCode(SyntaxConstants.CSharp.TagHelperPrefixKeyword + " ")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("Foo")
+ .AsTagHelperPrefixDirective("Foo")));
+ }
+
+ [Fact]
+ public void TagHelperPrefixDirective_WithQuotes_Succeeds()
+ {
+ ParseBlockTest("@tagHelperPrefix \"Foo\"",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory
+ .MetaCode(SyntaxConstants.CSharp.TagHelperPrefixKeyword + " ")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("\"Foo\"")
+ .AsTagHelperPrefixDirective("Foo")));
+ }
+
+ [Fact]
+ public void TagHelperPrefixDirective_RequiresValue()
+ {
+ ParseBlockTest("@tagHelperPrefix ",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory
+ .MetaCode(SyntaxConstants.CSharp.TagHelperPrefixKeyword + " ")
+ .Accepts(AcceptedCharacters.None),
+ Factory.EmptyCSharp()
+ .AsTagHelperPrefixDirective(string.Empty)
+ .Accepts(AcceptedCharacters.AnyExceptNewline)),
+ new RazorError(
+ LegacyResources.FormatParseError_DirectiveMustHaveValue(
+ SyntaxConstants.CSharp.TagHelperPrefixKeyword),
+ absoluteIndex: 1, lineIndex: 0, columnIndex: 1, length: 15));
+ }
+
+ [Fact]
+ public void TagHelperPrefixDirective_StartQuoteRequiresDoubleQuotesAroundValue()
+ {
+ ParseBlockTest("@tagHelperPrefix \"Foo",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory
+ .MetaCode(SyntaxConstants.CSharp.TagHelperPrefixKeyword + " ")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("\"Foo")
+ .AsTagHelperPrefixDirective("\"Foo")),
+ new RazorError(
+ LegacyResources.ParseError_Unterminated_String_Literal,
+ absoluteIndex: 17, lineIndex: 0, columnIndex: 17, length: 1),
+ new RazorError(
+ LegacyResources.FormatParseError_IncompleteQuotesAroundDirective(
+ SyntaxConstants.CSharp.TagHelperPrefixKeyword),
+ absoluteIndex: 17, lineIndex: 0, columnIndex: 17, length: 4));
+ }
+
+ [Fact]
+ public void TagHelperPrefixDirective_EndQuoteRequiresDoubleQuotesAroundValue()
+ {
+ ParseBlockTest("@tagHelperPrefix Foo \"",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory
+ .MetaCode(SyntaxConstants.CSharp.TagHelperPrefixKeyword + " ")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("Foo \"")
+ .AsTagHelperPrefixDirective("Foo \"")),
+ new RazorError(
+ LegacyResources.ParseError_Unterminated_String_Literal,
+ absoluteIndex: 23, lineIndex: 0, columnIndex: 23, length: 1),
+ new RazorError(
+ LegacyResources.FormatParseError_IncompleteQuotesAroundDirective(
+ SyntaxConstants.CSharp.TagHelperPrefixKeyword),
+ absoluteIndex: 17, lineIndex: 0, columnIndex: 17, length: 7));
+ }
+
+ [Fact]
+ public void RemoveTagHelperDirective_NoValue_Succeeds()
+ {
+ ParseBlockTest("@removeTagHelper \"\"",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode(SyntaxConstants.CSharp.RemoveTagHelperKeyword + " ")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("\"\"")
+ .AsRemoveTagHelper(string.Empty)));
+ }
+
+ [Fact]
+ public void RemoveTagHelperDirective_Succeeds()
+ {
+ ParseBlockTest("@removeTagHelper Foo",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode(SyntaxConstants.CSharp.RemoveTagHelperKeyword + " ")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("Foo")
+ .AsRemoveTagHelper("Foo")));
+ }
+
+ [Fact]
+ public void RemoveTagHelperDirective_WithQuotes_Succeeds()
+ {
+ ParseBlockTest("@removeTagHelper \"Foo\"",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode(SyntaxConstants.CSharp.RemoveTagHelperKeyword + " ")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("\"Foo\"")
+ .AsRemoveTagHelper("Foo")));
+ }
+
+ [Fact]
+ public void RemoveTagHelperDirective_SupportsSpaces()
+ {
+ ParseBlockTest("@removeTagHelper Foo, Bar ",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode(SyntaxConstants.CSharp.RemoveTagHelperKeyword + " ")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("Foo, Bar ")
+ .AsRemoveTagHelper("Foo, Bar")
+ .Accepts(AcceptedCharacters.AnyExceptNewline)));
+ }
+
+ [Fact]
+ public void RemoveTagHelperDirective_RequiresValue()
+ {
+ ParseBlockTest("@removeTagHelper ",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode(SyntaxConstants.CSharp.RemoveTagHelperKeyword + " ")
+ .Accepts(AcceptedCharacters.None),
+ Factory.EmptyCSharp()
+ .AsRemoveTagHelper(string.Empty)
+ .Accepts(AcceptedCharacters.AnyExceptNewline)),
+ new RazorError(
+ LegacyResources.FormatParseError_DirectiveMustHaveValue(
+ SyntaxConstants.CSharp.RemoveTagHelperKeyword),
+ absoluteIndex: 1, lineIndex: 0, columnIndex: 1, length: 15));
+ }
+
+ [Fact]
+ public void RemoveTagHelperDirective_StartQuoteRequiresDoubleQuotesAroundValue()
+ {
+ ParseBlockTest("@removeTagHelper \"Foo",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode(SyntaxConstants.CSharp.RemoveTagHelperKeyword + " ")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("\"Foo")
+ .AsRemoveTagHelper("\"Foo")),
+ new RazorError(
+ LegacyResources.ParseError_Unterminated_String_Literal,
+ absoluteIndex: 17, lineIndex: 0, columnIndex: 17, length: 1),
+ new RazorError(
+ LegacyResources.FormatParseError_IncompleteQuotesAroundDirective(
+ SyntaxConstants.CSharp.RemoveTagHelperKeyword),
+ absoluteIndex: 17, lineIndex: 0, columnIndex: 17, length: 4));
+ }
+
+ [Fact]
+ public void RemoveTagHelperDirective_EndQuoteRequiresDoubleQuotesAroundValue()
+ {
+ ParseBlockTest("@removeTagHelper Foo\"",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode(SyntaxConstants.CSharp.RemoveTagHelperKeyword + " ")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("Foo\"")
+ .AsRemoveTagHelper("Foo\"")
+ .Accepts(AcceptedCharacters.AnyExceptNewline)),
+ new RazorError(
+ LegacyResources.ParseError_Unterminated_String_Literal,
+ absoluteIndex: 20, lineIndex: 0, columnIndex: 20, length: 1),
+ new RazorError(
+ LegacyResources.FormatParseError_IncompleteQuotesAroundDirective(
+ SyntaxConstants.CSharp.RemoveTagHelperKeyword),
+ absoluteIndex: 17, lineIndex: 0, columnIndex: 17, length: 4));
+ }
+
+ [Fact]
+ public void AddTagHelperDirective_NoValue_Succeeds()
+ {
+ ParseBlockTest("@addTagHelper \"\"",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode(SyntaxConstants.CSharp.AddTagHelperKeyword + " ")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("\"\"")
+ .AsAddTagHelper(string.Empty)));
+ }
+
+ [Fact]
+ public void AddTagHelperDirective_Succeeds()
+ {
+ ParseBlockTest("@addTagHelper Foo",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode(SyntaxConstants.CSharp.AddTagHelperKeyword + " ")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("Foo")
+ .AsAddTagHelper("Foo")));
+ }
+
+ [Fact]
+ public void AddTagHelperDirective_WithQuotes_Succeeds()
+ {
+ ParseBlockTest("@addTagHelper \"Foo\"",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode(SyntaxConstants.CSharp.AddTagHelperKeyword + " ")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("\"Foo\"")
+ .AsAddTagHelper("Foo")));
+ }
+
+ [Fact]
+ public void AddTagHelperDirectiveSupportsSpaces()
+ {
+ ParseBlockTest("@addTagHelper Foo, Bar ",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode(SyntaxConstants.CSharp.AddTagHelperKeyword + " ")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("Foo, Bar ")
+ .AsAddTagHelper("Foo, Bar")));
+ }
+
+ [Fact]
+ public void AddTagHelperDirectiveRequiresValue()
+ {
+ ParseBlockTest("@addTagHelper ",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode(SyntaxConstants.CSharp.AddTagHelperKeyword + " ")
+ .Accepts(AcceptedCharacters.None),
+ Factory.EmptyCSharp()
+ .AsAddTagHelper(string.Empty)
+ .Accepts(AcceptedCharacters.AnyExceptNewline)),
+ new RazorError(
+ LegacyResources.FormatParseError_DirectiveMustHaveValue(SyntaxConstants.CSharp.AddTagHelperKeyword),
+ absoluteIndex: 1, lineIndex: 0, columnIndex: 1, length: 12));
+ }
+
+ [Fact]
+ public void AddTagHelperDirective_StartQuoteRequiresDoubleQuotesAroundValue()
+ {
+ ParseBlockTest("@addTagHelper \"Foo",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode(SyntaxConstants.CSharp.AddTagHelperKeyword + " ")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("\"Foo")
+ .AsAddTagHelper("\"Foo")),
+ new RazorError(
+ LegacyResources.ParseError_Unterminated_String_Literal,
+ absoluteIndex: 14, lineIndex: 0, columnIndex: 14, length: 1),
+ new RazorError(
+ LegacyResources.FormatParseError_IncompleteQuotesAroundDirective(
+ SyntaxConstants.CSharp.AddTagHelperKeyword),
+ absoluteIndex: 14, lineIndex: 0, columnIndex: 14, length: 4));
+ }
+
+ [Fact]
+ public void AddTagHelperDirective_EndQuoteRequiresDoubleQuotesAroundValue()
+ {
+ ParseBlockTest("@addTagHelper Foo\"",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode(SyntaxConstants.CSharp.AddTagHelperKeyword + " ")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("Foo\"")
+ .AsAddTagHelper("Foo\"")
+ .Accepts(AcceptedCharacters.AnyExceptNewline)),
+ new RazorError(
+ LegacyResources.ParseError_Unterminated_String_Literal,
+ absoluteIndex: 17, lineIndex: 0, columnIndex: 17, length: 1),
+ new RazorError(
+ LegacyResources.FormatParseError_IncompleteQuotesAroundDirective(
+ SyntaxConstants.CSharp.AddTagHelperKeyword),
+ absoluteIndex: 14, lineIndex: 0, columnIndex: 14, length: 4));
+ }
+
+ [Fact]
+ public void InheritsDirective()
+ {
+ ParseBlockTest("@inherits System.Web.WebPages.WebPage",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode(SyntaxConstants.CSharp.InheritsKeyword + " ")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("System.Web.WebPages.WebPage")
+ .AsBaseType("System.Web.WebPages.WebPage")));
+ }
+
+ [Fact]
+ public void InheritsDirectiveSupportsArrays()
+ {
+ ParseBlockTest("@inherits string[[]][]",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode(SyntaxConstants.CSharp.InheritsKeyword + " ")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("string[[]][]")
+ .AsBaseType("string[[]][]")));
+ }
+
+ [Fact]
+ public void InheritsDirectiveSupportsNestedGenerics()
+ {
+ ParseBlockTest("@inherits System.Web.Mvc.WebViewPage>",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode(SyntaxConstants.CSharp.InheritsKeyword + " ")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("System.Web.Mvc.WebViewPage>")
+ .AsBaseType("System.Web.Mvc.WebViewPage>")));
+ }
+
+ [Fact]
+ public void InheritsDirectiveSupportsTypeKeywords()
+ {
+ ParseBlockTest("@inherits string",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode(SyntaxConstants.CSharp.InheritsKeyword + " ")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("string")
+ .AsBaseType("string")));
+ }
+
+ [Fact]
+ public void InheritsDirectiveSupportsVSTemplateTokens()
+ {
+ ParseBlockTest("@inherits $rootnamespace$.MyBase",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode(SyntaxConstants.CSharp.InheritsKeyword + " ")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("$rootnamespace$.MyBase")
+ .AsBaseType("$rootnamespace$.MyBase")));
+ }
+
+ [Fact]
+ public void FunctionsDirective()
+ {
+ ParseBlockTest("@functions { foo(); bar(); }",
+ new FunctionsBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode(SyntaxConstants.CSharp.FunctionsKeyword + " {")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code(" foo(); bar(); ")
+ .AsFunctionsBody()
+ .AutoCompleteWith(autoCompleteString: null),
+ Factory.MetaCode("}")
+ .Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void EmptyFunctionsDirective()
+ {
+ ParseBlockTest("@functions { }",
+ new FunctionsBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode(SyntaxConstants.CSharp.FunctionsKeyword + " {")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code(" ")
+ .AsFunctionsBody()
+ .AutoCompleteWith(autoCompleteString: null),
+ Factory.MetaCode("}")
+ .Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void SectionDirective()
+ {
+ ParseBlockTest("@section Header { F{o}o
}",
+ new SectionBlock(new SectionChunkGenerator("Header"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section Header {")
+ .AutoCompleteWith(null, atEndOfSpan: true)
+ .Accepts(AcceptedCharacters.Any),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ new MarkupTagBlock(
+ Factory.Markup("")),
+ Factory.Markup("F", "{", "o", "}", "o"),
+ new MarkupTagBlock(
+ Factory.Markup("
")),
+ Factory.Markup(" ")),
+ Factory.MetaCode("}")
+ .Accepts(AcceptedCharacters.None)));
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpErrorTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpErrorTest.cs
new file mode 100644
index 0000000000..a79556c0fd
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpErrorTest.cs
@@ -0,0 +1,695 @@
+// 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 Xunit;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal class CSharpErrorTest : CsHtmlCodeParserTestBase
+ {
+ [Fact]
+ public void ParseBlockHandlesQuotesAfterTransition()
+ {
+ ParseBlockTest("@\"",
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.EmptyCSharp()
+ .AsImplicitExpression(KeywordSet)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)
+ ),
+ new RazorError(
+ LegacyResources.FormatParseError_Unexpected_Character_At_Start_Of_CodeBlock_CS('"'),
+ new SourceLocation(1, 0, 1),
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseBlockWithHelperDirectiveProducesError()
+ {
+ ParseBlockTest("@helper fooHelper { }",
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("helper")
+ .AsImplicitExpression(KeywordSet)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ new RazorError(
+ LegacyResources.FormatParseError_HelperDirectiveNotAvailable(SyntaxConstants.CSharp.HelperKeyword),
+ new SourceLocation(1, 0, 1),
+ length: 6));
+ }
+
+ [Fact]
+ public void ParseBlockCapturesWhitespaceToEndOfLineInInvalidUsingStatementAndTreatsAsFileCode()
+ {
+ ParseBlockTest("using " + Environment.NewLine
+ + Environment.NewLine,
+ new StatementBlock(
+ Factory.Code("using " + Environment.NewLine).AsStatement()
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockMethodOutputsOpenCurlyAsCodeSpanIfEofFoundAfterOpenCurlyBrace()
+ {
+ ParseBlockTest("{",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.EmptyCSharp()
+ .AsStatement()
+ .With(new AutoCompleteEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString) { AutoCompleteString = "}" })
+ ),
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF(LegacyResources.BlockName_Code, "}", "{"),
+ SourceLocation.Zero,
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseBlockMethodOutputsZeroLengthCodeSpanIfStatementBlockEmpty()
+ {
+ ParseBlockTest("{}",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.EmptyCSharp()
+ .AsStatement()
+ .AutoCompleteWith(autoCompleteString: null),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockMethodProducesErrorIfNewlineFollowsTransition()
+ {
+ ParseBlockTest("@" + Environment.NewLine,
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.EmptyCSharp()
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ new RazorError(
+ LegacyResources.ParseError_Unexpected_WhiteSpace_At_Start_Of_CodeBlock_CS,
+ new SourceLocation(1, 0, 1),
+ Environment.NewLine.Length));
+ }
+
+ [Fact]
+ public void ParseBlockMethodProducesErrorIfWhitespaceBetweenTransitionAndBlockStartInEmbeddedExpression()
+ {
+ ParseBlockTest("{" + Environment.NewLine
+ + " @ {}" + Environment.NewLine
+ + "}",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code(Environment.NewLine + " ")
+ .AsStatement()
+ .AutoCompleteWith(autoCompleteString: null),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.EmptyCSharp()
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.Code(" {}" + Environment.NewLine).AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
+ ),
+ new RazorError(
+ LegacyResources.ParseError_Unexpected_WhiteSpace_At_Start_Of_CodeBlock_CS,
+ new SourceLocation(6 + Environment.NewLine.Length, 1, 5),
+ length: 3));
+ }
+
+ [Fact]
+ public void ParseBlockMethodProducesErrorIfEOFAfterTransitionInEmbeddedExpression()
+ {
+ ParseBlockTest("{" + Environment.NewLine
+ + " @",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code(Environment.NewLine + " ")
+ .AsStatement()
+ .AutoCompleteWith("}"),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.EmptyCSharp()
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.EmptyCSharp().AsStatement()
+ ),
+ new RazorError(
+ LegacyResources.ParseError_Unexpected_EndOfFile_At_Start_Of_CodeBlock,
+ 6 + Environment.NewLine.Length, 1, 5, length: 1),
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF(LegacyResources.BlockName_Code, "}", "{"),
+ SourceLocation.Zero,
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseBlockMethodParsesNothingIfFirstCharacterIsNotIdentifierStartOrParenOrBrace()
+ {
+ ParseBlockTest("@!!!",
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.EmptyCSharp()
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ new RazorError(
+ LegacyResources.FormatParseError_Unexpected_Character_At_Start_Of_CodeBlock_CS("!"),
+ new SourceLocation(1, 0, 1),
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseBlockShouldReportErrorAndTerminateAtEOFIfIfParenInExplicitExpressionUnclosed()
+ {
+ ParseBlockTest("(foo bar" + Environment.NewLine
+ + "baz",
+ new ExpressionBlock(
+ Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
+ Factory.Code($"foo bar{Environment.NewLine}baz").AsExpression()
+ ),
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF(LegacyResources.BlockName_ExplicitExpression, ')', '('),
+ SourceLocation.Zero,
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseBlockShouldReportErrorAndTerminateAtMarkupIfIfParenInExplicitExpressionUnclosed()
+ {
+ ParseBlockTest("(foo bar" + Environment.NewLine
+ + "" + Environment.NewLine
+ + "baz" + Environment.NewLine
+ + "@Html.Foo(Bar);" + Environment.NewLine,
+ new ExpressionBlock(
+ Factory.Code("Href(" + Environment.NewLine)
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ ),
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_CloseBracket_Before_EOF("(", ")"),
+ new SourceLocation(4, 0, 4),
+ length: 1));
+ }
+
+ [Fact]
+ // Test for fix to Dev10 884975 - Incorrect Error Messaging
+ public void ParseBlockShouldReportErrorAndTerminateAtEOFIfParenInImplicitExpressionUnclosed()
+ {
+ ParseBlockTest("Foo(Bar(Baz)" + Environment.NewLine
+ + "Biz" + Environment.NewLine
+ + "Boz",
+ new ExpressionBlock(
+ Factory.Code($"Foo(Bar(Baz){Environment.NewLine}Biz{Environment.NewLine}Boz")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ ),
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_CloseBracket_Before_EOF("(", ")"),
+ new SourceLocation(3, 0, 3),
+ length: 1));
+ }
+
+ [Fact]
+ // Test for fix to Dev10 884975 - Incorrect Error Messaging
+ public void ParseBlockShouldReportErrorAndTerminateAtMarkupIfParenInImplicitExpressionUnclosed()
+ {
+ ParseBlockTest("Foo(Bar(Baz)" + Environment.NewLine
+ + "Biz" + Environment.NewLine
+ + "" + Environment.NewLine
+ + "Boz" + Environment.NewLine
+ + "",
+ new ExpressionBlock(
+ Factory.Code($"Foo(Bar(Baz){Environment.NewLine}Biz{Environment.NewLine}")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ ),
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_CloseBracket_Before_EOF("(", ")"),
+ new SourceLocation(3, 0, 3),
+ length: 1));
+ }
+
+ [Fact]
+ // Test for fix to Dev10 884975 - Incorrect Error Messaging
+ public void ParseBlockShouldReportErrorAndTerminateAtEOFIfBracketInImplicitExpressionUnclosed()
+ {
+ ParseBlockTest("Foo[Bar[Baz]" + Environment.NewLine
+ + "Biz" + Environment.NewLine
+ + "Boz",
+ new ExpressionBlock(
+ Factory.Code($"Foo[Bar[Baz]{Environment.NewLine}Biz{Environment.NewLine}Boz")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ ),
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_CloseBracket_Before_EOF("[", "]"),
+ new SourceLocation(3, 0, 3),
+ length: 1));
+ }
+
+ [Fact]
+ // Test for fix to Dev10 884975 - Incorrect Error Messaging
+ public void ParseBlockShouldReportErrorAndTerminateAtMarkupIfBracketInImplicitExpressionUnclosed()
+ {
+ ParseBlockTest("Foo[Bar[Baz]" + Environment.NewLine
+ + "Biz" + Environment.NewLine
+ + "" + Environment.NewLine
+ + "Boz" + Environment.NewLine
+ + "",
+ new ExpressionBlock(
+ Factory.Code($"Foo[Bar[Baz]{Environment.NewLine}Biz{Environment.NewLine}")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ ),
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_CloseBracket_Before_EOF("[", "]"),
+ new SourceLocation(3, 0, 3),
+ length: 1));
+ }
+
+ // Simple EOF handling errors:
+ [Fact]
+ public void ParseBlockReportsErrorIfExplicitCodeBlockUnterminatedAtEOF()
+ {
+ ParseBlockTest("{ var foo = bar; if(foo != null) { bar(); } ",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code(" var foo = bar; if(foo != null) { bar(); } ")
+ .AsStatement()
+ .AutoCompleteWith("}")),
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF(
+ LegacyResources.BlockName_Code, '}', '{'),
+ SourceLocation.Zero,
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseBlockReportsErrorIfClassBlockUnterminatedAtEOF()
+ {
+ ParseBlockTest("functions { var foo = bar; if(foo != null) { bar(); } ",
+ new FunctionsBlock(
+ Factory.MetaCode("functions {").Accepts(AcceptedCharacters.None),
+ Factory.Code(" var foo = bar; if(foo != null) { bar(); } ")
+ .AsFunctionsBody()
+ .AutoCompleteWith("}")),
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF("functions", '}', '{'),
+ new SourceLocation(10, 0, 10),
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseBlockReportsErrorIfIfBlockUnterminatedAtEOF()
+ {
+ RunUnterminatedSimpleKeywordBlock("if");
+ }
+
+ [Fact]
+ public void ParseBlockReportsErrorIfElseBlockUnterminatedAtEOF()
+ {
+ ParseBlockTest("if(foo) { baz(); } else { var foo = bar; if(foo != null) { bar(); } ",
+ new StatementBlock(
+ Factory.Code("if(foo) { baz(); } else { var foo = bar; if(foo != null) { bar(); } ").AsStatement()
+ ),
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF("else", '}', '{'),
+ new SourceLocation(19, 0, 19),
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseBlockReportsErrorIfElseIfBlockUnterminatedAtEOF()
+ {
+ ParseBlockTest("if(foo) { baz(); } else if { var foo = bar; if(foo != null) { bar(); } ",
+ new StatementBlock(
+ Factory.Code("if(foo) { baz(); } else if { var foo = bar; if(foo != null) { bar(); } ").AsStatement()
+ ),
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF("else if", '}', '{'),
+ new SourceLocation(19, 0, 19),
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseBlockReportsErrorIfDoBlockUnterminatedAtEOF()
+ {
+ ParseBlockTest("do { var foo = bar; if(foo != null) { bar(); } ",
+ new StatementBlock(
+ Factory.Code("do { var foo = bar; if(foo != null) { bar(); } ").AsStatement()
+ ),
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF("do", '}', '{'),
+ SourceLocation.Zero,
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseBlockReportsErrorIfTryBlockUnterminatedAtEOF()
+ {
+ ParseBlockTest("try { var foo = bar; if(foo != null) { bar(); } ",
+ new StatementBlock(
+ Factory.Code("try { var foo = bar; if(foo != null) { bar(); } ").AsStatement()
+ ),
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF("try", '}', '{'),
+ SourceLocation.Zero,
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseBlockReportsErrorIfCatchBlockUnterminatedAtEOF()
+ {
+ ParseBlockTest("try { baz(); } catch(Foo) { var foo = bar; if(foo != null) { bar(); } ",
+ new StatementBlock(
+ Factory.Code("try { baz(); } catch(Foo) { var foo = bar; if(foo != null) { bar(); } ").AsStatement()
+ ),
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF("catch", '}', '{'),
+ new SourceLocation(15, 0, 15),
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseBlockReportsErrorIfFinallyBlockUnterminatedAtEOF()
+ {
+ ParseBlockTest("try { baz(); } finally { var foo = bar; if(foo != null) { bar(); } ",
+ new StatementBlock(
+ Factory.Code("try { baz(); } finally { var foo = bar; if(foo != null) { bar(); } ").AsStatement()
+ ),
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF("finally", '}', '{'),
+ new SourceLocation(15, 0, 15),
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseBlockReportsErrorIfForBlockUnterminatedAtEOF()
+ {
+ RunUnterminatedSimpleKeywordBlock("for");
+ }
+
+ [Fact]
+ public void ParseBlockReportsErrorIfForeachBlockUnterminatedAtEOF()
+ {
+ RunUnterminatedSimpleKeywordBlock("foreach");
+ }
+
+ [Fact]
+ public void ParseBlockReportsErrorIfWhileBlockUnterminatedAtEOF()
+ {
+ RunUnterminatedSimpleKeywordBlock("while");
+ }
+
+ [Fact]
+ public void ParseBlockReportsErrorIfSwitchBlockUnterminatedAtEOF()
+ {
+ RunUnterminatedSimpleKeywordBlock("switch");
+ }
+
+ [Fact]
+ public void ParseBlockReportsErrorIfLockBlockUnterminatedAtEOF()
+ {
+ RunUnterminatedSimpleKeywordBlock("lock");
+ }
+
+ [Fact]
+ public void ParseBlockReportsErrorIfUsingBlockUnterminatedAtEOF()
+ {
+ RunUnterminatedSimpleKeywordBlock("using");
+ }
+
+ [Fact]
+ public void ParseBlockRequiresControlFlowStatementsToHaveBraces()
+ {
+ var expectedMessage = LegacyResources.FormatParseError_SingleLine_ControlFlowStatements_Not_Allowed("{", "<");
+ ParseBlockTest("if(foo) Bar
else if(bar) Baz
else Boz
",
+ new StatementBlock(
+ Factory.Code("if(foo) ").AsStatement(),
+ new MarkupBlock(
+ BlockFactory.MarkupTagBlock("", AcceptedCharacters.None),
+ Factory.Markup("Bar"),
+ BlockFactory.MarkupTagBlock("
", AcceptedCharacters.None),
+ Factory.Markup(" ").Accepts(AcceptedCharacters.None)),
+ Factory.Code("else if(bar) ").AsStatement(),
+ new MarkupBlock(
+ BlockFactory.MarkupTagBlock("", AcceptedCharacters.None),
+ Factory.Markup("Baz"),
+ BlockFactory.MarkupTagBlock("
", AcceptedCharacters.None),
+ Factory.Markup(" ").Accepts(AcceptedCharacters.None)),
+ Factory.Code("else ").AsStatement(),
+ new MarkupBlock(
+ BlockFactory.MarkupTagBlock("", AcceptedCharacters.None),
+ Factory.Markup("Boz"),
+ BlockFactory.MarkupTagBlock("
", AcceptedCharacters.None)),
+ Factory.EmptyCSharp().AsStatement()
+ ),
+ new RazorError(expectedMessage, 8, 0, 8, 1),
+ new RazorError(expectedMessage, 32, 0, 32, 1),
+ new RazorError(expectedMessage, 48, 0, 48, 1));
+ }
+
+ [Fact]
+ public void ParseBlockIncludesUnexpectedCharacterInSingleStatementControlFlowStatementError()
+ {
+ ParseBlockTest("if(foo)) { var bar = foo; }",
+ new StatementBlock(
+ Factory.Code("if(foo)) { var bar = foo; }").AsStatement()
+ ),
+ new RazorError(
+ LegacyResources.FormatParseError_SingleLine_ControlFlowStatements_Not_Allowed("{", ")"),
+ new SourceLocation(7, 0, 7),
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseBlockOutputsErrorIfAtSignFollowedByLessThanSignAtStatementStart()
+ {
+ ParseBlockTest("if(foo) { @Bar
}",
+ new StatementBlock(
+ Factory.Code("if(foo) {").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ Factory.MarkupTransition(),
+ BlockFactory.MarkupTagBlock("", AcceptedCharacters.None),
+ Factory.Markup("Bar"),
+ BlockFactory.MarkupTagBlock("
", AcceptedCharacters.None),
+ Factory.Markup(" ").Accepts(AcceptedCharacters.None)),
+ Factory.Code("}").AsStatement()
+ ),
+ new RazorError(
+ LegacyResources.ParseError_AtInCode_Must_Be_Followed_By_Colon_Paren_Or_Identifier_Start,
+ new SourceLocation(10, 0, 10),
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseBlockTerminatesIfBlockAtEOLWhenRecoveringFromMissingCloseParen()
+ {
+ ParseBlockTest("if(foo bar" + Environment.NewLine
+ + "baz",
+ new StatementBlock(
+ Factory.Code("if(foo bar" + Environment.NewLine).AsStatement()
+ ),
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_CloseBracket_Before_EOF("(", ")"),
+ new SourceLocation(2, 0, 2),
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseBlockTerminatesForeachBlockAtEOLWhenRecoveringFromMissingCloseParen()
+ {
+ ParseBlockTest("foreach(foo bar" + Environment.NewLine
+ + "baz",
+ new StatementBlock(
+ Factory.Code("foreach(foo bar" + Environment.NewLine).AsStatement()
+ ),
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_CloseBracket_Before_EOF("(", ")"),
+ new SourceLocation(7, 0, 7),
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseBlockTerminatesWhileClauseInDoStatementAtEOLWhenRecoveringFromMissingCloseParen()
+ {
+ ParseBlockTest("do { } while(foo bar" + Environment.NewLine
+ + "baz",
+ new StatementBlock(
+ Factory.Code("do { } while(foo bar" + Environment.NewLine).AsStatement()
+ ),
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_CloseBracket_Before_EOF("(", ")"),
+ new SourceLocation(12, 0, 12),
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseBlockTerminatesUsingBlockAtEOLWhenRecoveringFromMissingCloseParen()
+ {
+ ParseBlockTest("using(foo bar" + Environment.NewLine
+ + "baz",
+ new StatementBlock(
+ Factory.Code("using(foo bar" + Environment.NewLine).AsStatement()
+ ),
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_CloseBracket_Before_EOF("(", ")"),
+ new SourceLocation(5, 0, 5),
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseBlockResumesIfStatementAfterOpenParen()
+ {
+ ParseBlockTest("if(" + Environment.NewLine
+ + "else { Foo
}",
+ new StatementBlock(
+ Factory.Code($"if({Environment.NewLine}else {{").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ BlockFactory.MarkupTagBlock("", AcceptedCharacters.None),
+ Factory.Markup("Foo"),
+ BlockFactory.MarkupTagBlock("
", AcceptedCharacters.None),
+ Factory.Markup(" ").Accepts(AcceptedCharacters.None)),
+ Factory.Code("}").AsStatement().Accepts(AcceptedCharacters.None)
+ ),
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_CloseBracket_Before_EOF("(", ")"),
+ new SourceLocation(2, 0, 2),
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseBlockTerminatesNormalCSharpStringsAtEOLIfEndQuoteMissing()
+ {
+ SingleSpanBlockTest("if(foo) {" + Environment.NewLine
+ + " var p = \"foo bar baz" + Environment.NewLine
+ + ";" + Environment.NewLine
+ + "}",
+ BlockType.Statement, SpanKind.Code,
+ new RazorError(
+ LegacyResources.ParseError_Unterminated_String_Literal,
+ new SourceLocation(21 + Environment.NewLine.Length, 1, 12),
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseBlockTerminatesNormalStringAtEndOfFile()
+ {
+ SingleSpanBlockTest("if(foo) { var foo = \"blah blah blah blah blah", BlockType.Statement, SpanKind.Code,
+ new RazorError(
+ LegacyResources.ParseError_Unterminated_String_Literal,
+ new SourceLocation(20, 0, 20),
+ length: 1),
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF("if", '}', '{'),
+ SourceLocation.Zero,
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseBlockTerminatesVerbatimStringAtEndOfFile()
+ {
+ SingleSpanBlockTest("if(foo) { var foo = @\"blah " + Environment.NewLine
+ + "blah; " + Environment.NewLine
+ + "Foo
" + Environment.NewLine
+ + "blah " + Environment.NewLine
+ + "blah",
+ BlockType.Statement, SpanKind.Code,
+ new RazorError(
+ LegacyResources.ParseError_Unterminated_String_Literal,
+ new SourceLocation(20, 0, 20),
+ length: 1),
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF("if", '}', '{'),
+ SourceLocation.Zero,
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseBlockCorrectlyParsesMarkupIncorrectyAssumedToBeWithinAStatement()
+ {
+ ParseBlockTest("if(foo) {" + Environment.NewLine
+ + " var foo = \"foo bar baz" + Environment.NewLine
+ + " Foo is @foo
" + Environment.NewLine
+ + "}",
+ new StatementBlock(
+ Factory.Code($"if(foo) {{{Environment.NewLine} var foo = \"foo bar baz{Environment.NewLine} ").AsStatement(),
+ new MarkupBlock(
+ BlockFactory.MarkupTagBlock("", AcceptedCharacters.None),
+ Factory.Markup("Foo is "),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("foo")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ BlockFactory.MarkupTagBlock("
", AcceptedCharacters.None),
+ Factory.Markup(Environment.NewLine).Accepts(AcceptedCharacters.None)),
+ Factory.Code("}").AsStatement()
+ ),
+ new RazorError(
+ LegacyResources.ParseError_Unterminated_String_Literal,
+ new SourceLocation(23 + Environment.NewLine.Length, 1, 14),
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseBlockCorrectlyParsesAtSignInDelimitedBlock()
+ {
+ ParseBlockTest("(Request[\"description\"] ?? @photo.Description)",
+ new ExpressionBlock(
+ Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
+ Factory.Code("Request[\"description\"] ?? @photo.Description").AsExpression(),
+ Factory.MetaCode(")").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockCorrectlyRecoversFromMissingCloseParenInExpressionWithinCode()
+ {
+ ParseBlockTest(@"{string.Format(}",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code("string.Format(")
+ .AsStatement()
+ .AutoCompleteWith(autoCompleteString: null),
+ new MarkupBlock(
+ BlockFactory.MarkupTagBlock("", AcceptedCharacters.None),
+ BlockFactory.MarkupTagBlock("", AcceptedCharacters.None)),
+ Factory.EmptyCSharp().AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
+ expectedErrors: new[]
+ {
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_CloseBracket_Before_EOF("(", ")"),
+ new SourceLocation(14, 0, 14),
+ length: 1)
+ });
+
+ }
+
+ private void RunUnterminatedSimpleKeywordBlock(string keyword)
+ {
+ SingleSpanBlockTest(
+ keyword + " (foo) { var foo = bar; if(foo != null) { bar(); } ",
+ BlockType.Statement,
+ SpanKind.Code,
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF(keyword, '}', '{'),
+ SourceLocation.Zero,
+ length: 1));
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpExplicitExpressionTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpExplicitExpressionTest.cs
new file mode 100644
index 0000000000..1d05b53729
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpExplicitExpressionTest.cs
@@ -0,0 +1,139 @@
+// 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 Xunit;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal class CSharpExplicitExpressionTest : CsHtmlCodeParserTestBase
+ {
+ [Fact]
+ public void ParseBlockShouldOutputZeroLengthCodeSpanIfExplicitExpressionIsEmpty()
+ {
+ ParseBlockTest("@()",
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
+ Factory.EmptyCSharp().AsExpression(),
+ Factory.MetaCode(")").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockShouldOutputZeroLengthCodeSpanIfEOFOccursAfterStartOfExplicitExpression()
+ {
+ ParseBlockTest("@(",
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
+ Factory.EmptyCSharp().AsExpression()
+ ),
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF(
+ LegacyResources.BlockName_ExplicitExpression, ")", "("),
+ new SourceLocation(1, 0, 1),
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseBlockShouldAcceptEscapedQuoteInNonVerbatimStrings()
+ {
+ ParseBlockTest("@(\"\\\"\")",
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
+ Factory.Code("\"\\\"\"").AsExpression(),
+ Factory.MetaCode(")").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockShouldAcceptEscapedQuoteInVerbatimStrings()
+ {
+ ParseBlockTest("@(@\"\"\"\")",
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
+ Factory.Code("@\"\"\"\"").AsExpression(),
+ Factory.MetaCode(")").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockShouldAcceptMultipleRepeatedEscapedQuoteInVerbatimStrings()
+ {
+ ParseBlockTest("@(@\"\"\"\"\"\")",
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
+ Factory.Code("@\"\"\"\"\"\"").AsExpression(),
+ Factory.MetaCode(")").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockShouldAcceptMultiLineVerbatimStrings()
+ {
+ ParseBlockTest(@"@(@""" + Environment.NewLine
+ + @"Foo" + Environment.NewLine
+ + @"Bar" + Environment.NewLine
+ + @"Baz" + Environment.NewLine
+ + @""")",
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
+ Factory.Code($"@\"{Environment.NewLine}Foo{Environment.NewLine}Bar{Environment.NewLine}Baz{Environment.NewLine}\"").AsExpression(),
+ Factory.MetaCode(")").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockShouldAcceptMultipleEscapedQuotesInNonVerbatimStrings()
+ {
+ ParseBlockTest("@(\"\\\"hello, world\\\"\")",
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
+ Factory.Code("\"\\\"hello, world\\\"\"").AsExpression(),
+ Factory.MetaCode(")").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockShouldAcceptMultipleEscapedQuotesInVerbatimStrings()
+ {
+ ParseBlockTest("@(@\"\"\"hello, world\"\"\")",
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
+ Factory.Code("@\"\"\"hello, world\"\"\"").AsExpression(),
+ Factory.MetaCode(")").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockShouldAcceptConsecutiveEscapedQuotesInNonVerbatimStrings()
+ {
+ ParseBlockTest("@(\"\\\"\\\"\")",
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
+ Factory.Code("\"\\\"\\\"\"").AsExpression(),
+ Factory.MetaCode(")").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockShouldAcceptConsecutiveEscapedQuotesInVerbatimStrings()
+ {
+ ParseBlockTest("@(@\"\"\"\"\"\")",
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
+ Factory.Code("@\"\"\"\"\"\"").AsExpression(),
+ Factory.MetaCode(")").Accepts(AcceptedCharacters.None)
+ ));
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpImplicitExpressionTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpImplicitExpressionTest.cs
new file mode 100644
index 0000000000..4608d2b1e5
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpImplicitExpressionTest.cs
@@ -0,0 +1,297 @@
+// 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 Xunit;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal class CSharpImplicitExpressionTest : CsHtmlCodeParserTestBase
+ {
+ private const string TestExtraKeyword = "model";
+
+ public static TheoryData NullConditionalOperatorData_Bracket
+ {
+ get
+ {
+ var noErrors = new RazorError[0];
+ Func missingEndParenError = (index) =>
+ new RazorError[1]
+ {
+ new RazorError(
+ "An opening \"(\" is missing the corresponding closing \")\".",
+ new SourceLocation(index, 0, index),
+ length: 1)
+ };
+ Func missingEndBracketError = (index) =>
+ new RazorError[1]
+ {
+ new RazorError(
+ "An opening \"[\" is missing the corresponding closing \"]\".",
+ new SourceLocation(index, 0, index),
+ length: 1)
+ };
+
+ // implicitExpression, expectedImplicitExpression, acceptedCharacters, expectedErrors
+ return new TheoryData
+ {
+ { "val??[", "val", AcceptedCharacters.NonWhiteSpace, noErrors },
+ { "val??[0", "val", AcceptedCharacters.NonWhiteSpace, noErrors },
+ { "val?[", "val?[", AcceptedCharacters.Any, missingEndBracketError(5) },
+ { "val?(", "val", AcceptedCharacters.NonWhiteSpace, noErrors },
+ { "val?[more", "val?[more", AcceptedCharacters.Any, missingEndBracketError(5) },
+ { "val?[0]", "val?[0]", AcceptedCharacters.NonWhiteSpace, noErrors },
+ { "val?[", "val?[", AcceptedCharacters.Any, missingEndBracketError(5) },
+ { "val?[more.
", "val?[more.", AcceptedCharacters.Any, missingEndBracketError(5) },
+ { "val??[more
", "val", AcceptedCharacters.NonWhiteSpace, noErrors },
+ { "val?[-1]?", "val?[-1]", AcceptedCharacters.NonWhiteSpace, noErrors },
+ { "val?[abc]?[def", "val?[abc]?[def", AcceptedCharacters.Any, missingEndBracketError(11) },
+ { "val?[abc]?[2]", "val?[abc]?[2]", AcceptedCharacters.NonWhiteSpace, noErrors },
+ { "val?[abc]?.more?[def]", "val?[abc]?.more?[def]", AcceptedCharacters.NonWhiteSpace, noErrors },
+ { "val?[abc]?.more?.abc", "val?[abc]?.more?.abc", AcceptedCharacters.NonWhiteSpace, noErrors },
+ { "val?[null ?? true]", "val?[null ?? true]", AcceptedCharacters.NonWhiteSpace, noErrors },
+ { "val?[abc?.gef?[-1]]", "val?[abc?.gef?[-1]]", AcceptedCharacters.NonWhiteSpace, noErrors },
+ };
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(NullConditionalOperatorData_Bracket))]
+ public void ParseBlockMethodParsesNullConditionalOperatorImplicitExpression_Bracket(
+ string implicitExpresison,
+ string expectedImplicitExpression,
+ AcceptedCharacters acceptedCharacters,
+ RazorError[] expectedErrors)
+ {
+ // Act & Assert
+ ImplicitExpressionTest(
+ implicitExpresison,
+ expectedImplicitExpression,
+ acceptedCharacters,
+ expectedErrors);
+ }
+
+ public static TheoryData NullConditionalOperatorData_Dot
+ {
+ get
+ {
+ // implicitExpression, expectedImplicitExpression
+ return new TheoryData
+ {
+ { "val?", "val" },
+ { "val??", "val" },
+ { "val??more", "val" },
+ { "val?!", "val" },
+ { "val?.", "val?." },
+ { "val??.", "val" },
+ { "val?.(abc)", "val?." },
+ { "val?.", "val?." },
+ { "val?.more", "val?.more" },
+ { "val?.more
", "val?.more" },
+ { "val??.more
", "val" },
+ { "val?.more(false)?.
", "val?.more(false)?." },
+ { "val?.more(false)?.abc", "val?.more(false)?.abc" },
+ { "val?.more(null ?? true)?.abc", "val?.more(null ?? true)?.abc" },
+ };
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(NullConditionalOperatorData_Dot))]
+ public void ParseBlockMethodParsesNullConditionalOperatorImplicitExpression_Dot(
+ string implicitExpresison,
+ string expectedImplicitExpression)
+ {
+ // Act & Assert
+ ImplicitExpressionTest(implicitExpresison, expectedImplicitExpression);
+ }
+
+ [Fact]
+ public void NestedImplicitExpression()
+ {
+ ParseBlockTest("if (true) { @foo }",
+ new StatementBlock(
+ Factory.Code("if (true) { ").AsStatement(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("foo")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.Code(" }").AsStatement()));
+ }
+
+ [Fact]
+ public void ParseBlockAcceptsNonEnglishCharactersThatAreValidIdentifiers()
+ {
+ ImplicitExpressionTest("हळूँजद॔.", "हळूँजद॔");
+ }
+
+ [Fact]
+ public void ParseBlockOutputsZeroLengthCodeSpanIfInvalidCharacterFollowsTransition()
+ {
+ ParseBlockTest("@/",
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.EmptyCSharp()
+ .AsImplicitExpression(KeywordSet)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ new RazorError(
+ LegacyResources.FormatParseError_Unexpected_Character_At_Start_Of_CodeBlock_CS("/"),
+ new SourceLocation(1, 0, 1),
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseBlockOutputsZeroLengthCodeSpanIfEOFOccursAfterTransition()
+ {
+ ParseBlockTest("@",
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.EmptyCSharp()
+ .AsImplicitExpression(KeywordSet)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ new RazorError(
+ LegacyResources.ParseError_Unexpected_EndOfFile_At_Start_Of_CodeBlock,
+ new SourceLocation(1, 0, 1),
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseBlockSupportsSlashesWithinComplexImplicitExpressions()
+ {
+ ImplicitExpressionTest("DataGridColumn.Template(\"Years of Service\", e => (int)Math.Round((DateTime.Now - dt).TotalDays / 365))");
+ }
+
+ [Fact]
+ public void ParseBlockMethodParsesSingleIdentifierAsImplicitExpression()
+ {
+ ImplicitExpressionTest("foo");
+ }
+
+ [Fact]
+ public void ParseBlockMethodDoesNotAcceptSemicolonIfExpressionTerminatedByWhitespace()
+ {
+ ImplicitExpressionTest("foo ;", "foo");
+ }
+
+ [Fact]
+ public void ParseBlockMethodIgnoresSemicolonAtEndOfSimpleImplicitExpression()
+ {
+ RunTrailingSemicolonTest("foo");
+ }
+
+ [Fact]
+ public void ParseBlockMethodParsesDottedIdentifiersAsImplicitExpression()
+ {
+ ImplicitExpressionTest("foo.bar.baz");
+ }
+
+ [Fact]
+ public void ParseBlockMethodIgnoresSemicolonAtEndOfDottedIdentifiers()
+ {
+ RunTrailingSemicolonTest("foo.bar.baz");
+ }
+
+ [Fact]
+ public void ParseBlockMethodDoesNotIncludeDotAtEOFInImplicitExpression()
+ {
+ ImplicitExpressionTest("foo.bar.", "foo.bar");
+ }
+
+ [Fact]
+ public void ParseBlockMethodDoesNotIncludeDotFollowedByInvalidIdentifierCharacterInImplicitExpression()
+ {
+ ImplicitExpressionTest("foo.bar.0", "foo.bar");
+ ImplicitExpressionTest("foo.bar.
", "foo.bar");
+ }
+
+ [Fact]
+ public void ParseBlockMethodDoesNotIncludeSemicolonAfterDot()
+ {
+ ImplicitExpressionTest("foo.bar.;", "foo.bar");
+ }
+
+ [Fact]
+ public void ParseBlockMethodTerminatesAfterIdentifierUnlessFollowedByDotOrParenInImplicitExpression()
+ {
+ ImplicitExpressionTest("foo.bar
", "foo.bar");
+ }
+
+ [Fact]
+ public void ParseBlockProperlyParsesParenthesesAndBalancesThemInImplicitExpression()
+ {
+ ImplicitExpressionTest(@"foo().bar(""bi\""z"", 4)(""chained method; call"").baz(@""bo""""z"", '\'', () => { return 4; }, (4+5+new { foo = bar[4] }))");
+ }
+
+ [Fact]
+ public void ParseBlockProperlyParsesBracketsAndBalancesThemInImplicitExpression()
+ {
+ ImplicitExpressionTest(@"foo.bar[4 * (8 + 7)][""fo\""o""].baz");
+ }
+
+ [Fact]
+ public void ParseBlockTerminatesImplicitExpressionAtHtmlEndTag()
+ {
+ ImplicitExpressionTest("foo().bar.baz
zoop", "foo().bar.baz");
+ }
+
+ [Fact]
+ public void ParseBlockTerminatesImplicitExpressionAtHtmlStartTag()
+ {
+ ImplicitExpressionTest("foo().bar.bazzoop", "foo().bar.baz");
+ }
+
+ [Fact]
+ public void ParseBlockTerminatesImplicitExpressionBeforeDotIfDotNotFollowedByIdentifierStartCharacter()
+ {
+ ImplicitExpressionTest("foo().bar.baz.42", "foo().bar.baz");
+ }
+
+ [Fact]
+ public void ParseBlockStopsBalancingParenthesesAtEOF()
+ {
+ ImplicitExpressionTest(
+ "foo(()", "foo(()",
+ acceptedCharacters: AcceptedCharacters.Any,
+ errors: new RazorError(
+ LegacyResources.FormatParseError_Expected_CloseBracket_Before_EOF("(", ")"),
+ new SourceLocation(4, 0, 4),
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseBlockTerminatesImplicitExpressionIfCloseParenFollowedByAnyWhiteSpace()
+ {
+ ImplicitExpressionTest("foo.bar() (baz)", "foo.bar()");
+ }
+
+ [Fact]
+ public void ParseBlockTerminatesImplicitExpressionIfIdentifierFollowedByAnyWhiteSpace()
+ {
+ ImplicitExpressionTest("foo .bar() (baz)", "foo");
+ }
+
+ [Fact]
+ public void ParseBlockTerminatesImplicitExpressionAtLastValidPointIfDotFollowedByWhitespace()
+ {
+ ImplicitExpressionTest("foo. bar() (baz)", "foo");
+ }
+
+ [Fact]
+ public void ParseBlockOutputExpressionIfModuleTokenNotFollowedByBrace()
+ {
+ ImplicitExpressionTest("module.foo()");
+ }
+
+ private void RunTrailingSemicolonTest(string expr)
+ {
+ ParseBlockTest(SyntaxConstants.TransitionString + expr + ";",
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code(expr)
+ .AsImplicitExpression(KeywordSet)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)
+ ));
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpNestedStatementsTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpNestedStatementsTest.cs
new file mode 100644
index 0000000000..84530f04ab
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpNestedStatementsTest.cs
@@ -0,0 +1,104 @@
+// 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 Xunit;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal class CSharpNestedStatementsTest : CsHtmlCodeParserTestBase
+ {
+ [Fact]
+ public void NestedSimpleStatement()
+ {
+ ParseBlockTest("@while(true) { foo(); }",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("while(true) { foo(); }")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void NestedKeywordStatement()
+ {
+ ParseBlockTest("@while(true) { for(int i = 0; i < 10; i++) { foo(); } }",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("while(true) { for(int i = 0; i < 10; i++) { foo(); } }")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void NestedCodeBlock()
+ {
+ ParseBlockTest("@while(true) { { { { foo(); } } } }",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("while(true) { { { { foo(); } } } }")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void NestedImplicitExpression()
+ {
+ ParseBlockTest("@while(true) { @foo }",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("while(true) { ")
+ .AsStatement(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("foo")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.Code(" }")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void NestedExplicitExpression()
+ {
+ ParseBlockTest("@while(true) { @(foo) }",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("while(true) { ")
+ .AsStatement(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("(")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("foo")
+ .AsExpression(),
+ Factory.MetaCode(")")
+ .Accepts(AcceptedCharacters.None)),
+ Factory.Code(" }")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void NestedMarkupBlock()
+ {
+ ParseBlockTest("@while(true) {
Hello
}",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("while(true) {")
+ .AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ new MarkupTagBlock(
+ Factory.Markup("").Accepts(AcceptedCharacters.None)),
+ Factory.Markup("Hello"),
+ new MarkupTagBlock(
+ Factory.Markup("
").Accepts(AcceptedCharacters.None)),
+ Factory.Markup(" ").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code("}")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)));
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpRazorCommentsTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpRazorCommentsTest.cs
new file mode 100644
index 0000000000..36a546b1b7
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpRazorCommentsTest.cs
@@ -0,0 +1,423 @@
+// 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 Xunit;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal class CSharpRazorCommentsTest : CsHtmlMarkupParserTestBase
+ {
+ [Fact]
+ public void UnterminatedRazorComment()
+ {
+ ParseDocumentTest("@*",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new CommentBlock(
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
+ .Accepts(AcceptedCharacters.None),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
+ .Accepts(AcceptedCharacters.None),
+ Factory.Span(SpanKind.Comment, new HtmlSymbol(
+ Factory.LocationTracker.CurrentLocation,
+ string.Empty,
+ HtmlSymbolType.Unknown))
+ .Accepts(AcceptedCharacters.Any))),
+ new RazorError(
+ LegacyResources.ParseError_RazorComment_Not_Terminated,
+ SourceLocation.Zero,
+ length: 2));
+ }
+
+ [Fact]
+ public void EmptyRazorComment()
+ {
+ ParseDocumentTest("@**@",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new CommentBlock(
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
+ .Accepts(AcceptedCharacters.None),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
+ .Accepts(AcceptedCharacters.None),
+ Factory.Span(SpanKind.Comment, new HtmlSymbol(
+ Factory.LocationTracker.CurrentLocation,
+ string.Empty,
+ HtmlSymbolType.Unknown))
+ .Accepts(AcceptedCharacters.Any),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
+ .Accepts(AcceptedCharacters.None),
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
+ .Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void RazorCommentInImplicitExpressionMethodCall()
+ {
+ ParseDocumentTest("@foo(" + Environment.NewLine
+ + "@**@" + Environment.NewLine,
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("foo(" + Environment.NewLine)
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords),
+ new CommentBlock(
+ Factory.CodeTransition(CSharpSymbolType.RazorCommentTransition)
+ .Accepts(AcceptedCharacters.None),
+ Factory.MetaCode("*", CSharpSymbolType.RazorCommentStar)
+ .Accepts(AcceptedCharacters.None),
+ Factory.Span(SpanKind.Comment, new CSharpSymbol(
+ Factory.LocationTracker.CurrentLocation,
+ string.Empty,
+ CSharpSymbolType.Unknown))
+ .Accepts(AcceptedCharacters.Any),
+ Factory.MetaCode("*", CSharpSymbolType.RazorCommentStar)
+ .Accepts(AcceptedCharacters.None),
+ Factory.CodeTransition(CSharpSymbolType.RazorCommentTransition)
+ .Accepts(AcceptedCharacters.None)),
+ Factory.Code(Environment.NewLine)
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords))),
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_CloseBracket_Before_EOF("(", ")"),
+ new SourceLocation(4, 0, 4),
+ length: 1));
+ }
+
+ [Fact]
+ public void UnterminatedRazorCommentInImplicitExpressionMethodCall()
+ {
+ ParseDocumentTest("@foo(@*",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("foo(")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords),
+ new CommentBlock(
+ Factory.CodeTransition(CSharpSymbolType.RazorCommentTransition)
+ .Accepts(AcceptedCharacters.None),
+ Factory.MetaCode("*", CSharpSymbolType.RazorCommentStar)
+ .Accepts(AcceptedCharacters.None),
+ Factory.Span(SpanKind.Comment, new CSharpSymbol(
+ Factory.LocationTracker.CurrentLocation,
+ string.Empty,
+ CSharpSymbolType.Unknown))
+ .Accepts(AcceptedCharacters.Any)))),
+ new RazorError(
+ LegacyResources.ParseError_RazorComment_Not_Terminated,
+ new SourceLocation(5, 0, 5),
+ length: 2),
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_CloseBracket_Before_EOF("(", ")"),
+ new SourceLocation(4, 0, 4),
+ length: 1));
+ }
+
+ [Fact]
+ public void RazorCommentInVerbatimBlock()
+ {
+ ParseDocumentTest("@{" + Environment.NewLine
+ + " " + Environment.NewLine
+ + "@**@" + Environment.NewLine
+ + "
",
+ new MarkupBlock(
+ new MarkupTagBlock(
+ Factory.Markup("")),
+ Factory.Markup(Environment.NewLine),
+ new CommentBlock(
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
+ .Accepts(AcceptedCharacters.None),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
+ .Accepts(AcceptedCharacters.None),
+ Factory.Span(SpanKind.Comment, new HtmlSymbol(
+ Factory.LocationTracker.CurrentLocation,
+ string.Empty,
+ HtmlSymbolType.Unknown))
+ .Accepts(AcceptedCharacters.Any),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
+ .Accepts(AcceptedCharacters.None),
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
+ .Accepts(AcceptedCharacters.None)),
+ Factory.Markup(Environment.NewLine).With(SpanChunkGenerator.Null),
+ new MarkupTagBlock(
+ Factory.Markup("
"))
+ ));
+ }
+
+ [Fact]
+ public void MultipleRazorCommentInMarkup()
+ {
+ ParseDocumentTest(
+ "" + Environment.NewLine
+ + " @**@ " + Environment.NewLine
+ + "@**@" + Environment.NewLine
+ + "
",
+ new MarkupBlock(
+ new MarkupTagBlock(
+ Factory.Markup("")),
+ Factory.Markup(Environment.NewLine),
+ Factory.Markup(" ").With(SpanChunkGenerator.Null),
+ new CommentBlock(
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
+ .Accepts(AcceptedCharacters.None),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
+ .Accepts(AcceptedCharacters.None),
+ Factory.Span(SpanKind.Comment, new HtmlSymbol(
+ Factory.LocationTracker.CurrentLocation,
+ string.Empty,
+ HtmlSymbolType.Unknown))
+ .Accepts(AcceptedCharacters.Any),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
+ .Accepts(AcceptedCharacters.None),
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
+ .Accepts(AcceptedCharacters.None)),
+ Factory.Markup(" " + Environment.NewLine).With(SpanChunkGenerator.Null),
+ new CommentBlock(
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
+ .Accepts(AcceptedCharacters.None),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
+ .Accepts(AcceptedCharacters.None),
+ Factory.Span(SpanKind.Comment, new HtmlSymbol(
+ Factory.LocationTracker.CurrentLocation,
+ string.Empty,
+ HtmlSymbolType.Unknown))
+ .Accepts(AcceptedCharacters.Any),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
+ .Accepts(AcceptedCharacters.None),
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
+ .Accepts(AcceptedCharacters.None)),
+ Factory.Markup(Environment.NewLine).With(SpanChunkGenerator.Null),
+ new MarkupTagBlock(
+ Factory.Markup("
"))
+ ));
+ }
+
+ [Fact]
+ public void MultipleRazorCommentsInSameLineInMarkup()
+ {
+ ParseDocumentTest(
+ "" + Environment.NewLine
+ + "@**@ @**@" + Environment.NewLine
+ + "
",
+ new MarkupBlock(
+ new MarkupTagBlock(
+ Factory.Markup("")),
+ Factory.Markup(Environment.NewLine),
+ new CommentBlock(
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
+ .Accepts(AcceptedCharacters.None),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
+ .Accepts(AcceptedCharacters.None),
+ Factory.Span(SpanKind.Comment, new HtmlSymbol(
+ Factory.LocationTracker.CurrentLocation,
+ string.Empty,
+ HtmlSymbolType.Unknown))
+ .Accepts(AcceptedCharacters.Any),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
+ .Accepts(AcceptedCharacters.None),
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
+ .Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml(),
+ Factory.Markup(" ").With(SpanChunkGenerator.Null),
+ new CommentBlock(
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
+ .Accepts(AcceptedCharacters.None),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
+ .Accepts(AcceptedCharacters.None),
+ Factory.Span(SpanKind.Comment, new HtmlSymbol(
+ Factory.LocationTracker.CurrentLocation,
+ string.Empty,
+ HtmlSymbolType.Unknown))
+ .Accepts(AcceptedCharacters.Any),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
+ .Accepts(AcceptedCharacters.None),
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
+ .Accepts(AcceptedCharacters.None)),
+ Factory.Markup(Environment.NewLine).With(SpanChunkGenerator.Null),
+ new MarkupTagBlock(
+ Factory.Markup("
"))
+ ));
+ }
+
+ [Fact]
+ public void RazorCommentsSurroundingMarkup()
+ {
+ ParseDocumentTest(
+ "" + Environment.NewLine
+ + "@* hello *@ content @* world *@" + Environment.NewLine
+ + "
",
+ new MarkupBlock(
+ new MarkupTagBlock(
+ Factory.Markup("")),
+ Factory.Markup(Environment.NewLine),
+ new CommentBlock(
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
+ .Accepts(AcceptedCharacters.None),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
+ .Accepts(AcceptedCharacters.None),
+ Factory.Span(SpanKind.Comment, new HtmlSymbol(
+ Factory.LocationTracker.CurrentLocation,
+ " hello ",
+ HtmlSymbolType.RazorComment))
+ .Accepts(AcceptedCharacters.Any),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
+ .Accepts(AcceptedCharacters.None),
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
+ .Accepts(AcceptedCharacters.None)),
+ Factory.Markup(" content "),
+ new CommentBlock(
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
+ .Accepts(AcceptedCharacters.None),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
+ .Accepts(AcceptedCharacters.None),
+ Factory.Span(SpanKind.Comment, new HtmlSymbol(
+ Factory.LocationTracker.CurrentLocation,
+ " world ",
+ HtmlSymbolType.RazorComment))
+ .Accepts(AcceptedCharacters.Any),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
+ .Accepts(AcceptedCharacters.None),
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
+ .Accepts(AcceptedCharacters.None)),
+ Factory.Markup(Environment.NewLine),
+ new MarkupTagBlock(
+ Factory.Markup("
"))
+ ));
+ }
+
+ [Fact]
+ public void RazorCommentWithExtraNewLineInMarkup()
+ {
+ ParseDocumentTest(
+ "" + Environment.NewLine + Environment.NewLine
+ + "@* content *@" + Environment.NewLine
+ + "@*" + Environment.NewLine
+ + "content" + Environment.NewLine
+ + "*@" + Environment.NewLine + Environment.NewLine
+ + "
",
+ new MarkupBlock(
+ new MarkupTagBlock(
+ Factory.Markup("")),
+ Factory.Markup(Environment.NewLine + Environment.NewLine),
+ new CommentBlock(
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
+ .Accepts(AcceptedCharacters.None),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
+ .Accepts(AcceptedCharacters.None),
+ Factory.Span(SpanKind.Comment, new HtmlSymbol(
+ Factory.LocationTracker.CurrentLocation,
+ " content ",
+ HtmlSymbolType.RazorComment))
+ .Accepts(AcceptedCharacters.Any),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
+ .Accepts(AcceptedCharacters.None),
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
+ .Accepts(AcceptedCharacters.None)),
+ Factory.Markup(Environment.NewLine).With(SpanChunkGenerator.Null),
+ new CommentBlock(
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
+ .Accepts(AcceptedCharacters.None),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
+ .Accepts(AcceptedCharacters.None),
+ Factory.Span(SpanKind.Comment, new HtmlSymbol(
+ Factory.LocationTracker.CurrentLocation,
+ Environment.NewLine + "content" + Environment.NewLine,
+ HtmlSymbolType.RazorComment))
+ .Accepts(AcceptedCharacters.Any),
+ Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
+ .Accepts(AcceptedCharacters.None),
+ Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
+ .Accepts(AcceptedCharacters.None)),
+ Factory.Markup(Environment.NewLine).With(SpanChunkGenerator.Null),
+ Factory.Markup(Environment.NewLine),
+ new MarkupTagBlock(
+ Factory.Markup("
"))
+ ));
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpReservedWordsTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpReservedWordsTest.cs
new file mode 100644
index 0000000000..8815d18ebb
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpReservedWordsTest.cs
@@ -0,0 +1,42 @@
+// 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 Xunit;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal class CSharpReservedWordsTest : CsHtmlCodeParserTestBase
+ {
+ [Theory]
+ [InlineData("namespace")]
+ [InlineData("class")]
+ public void ReservedWords(string word)
+ {
+ ParseBlockTest(word,
+ new DirectiveBlock(
+ Factory.MetaCode(word).Accepts(AcceptedCharacters.None)
+ ),
+ new RazorError(
+ LegacyResources.FormatParseError_ReservedWord(word),
+ SourceLocation.Zero,
+ word.Length));
+ }
+
+ [Theory]
+ [InlineData("Namespace")]
+ [InlineData("Class")]
+ [InlineData("NAMESPACE")]
+ [InlineData("CLASS")]
+ [InlineData("nameSpace")]
+ [InlineData("NameSpace")]
+ private void ReservedWordsAreCaseSensitive(string word)
+ {
+ ParseBlockTest(word,
+ new ExpressionBlock(
+ Factory.Code(word)
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)
+ ));
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpSectionTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpSectionTest.cs
new file mode 100644
index 0000000000..85ff3f178b
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpSectionTest.cs
@@ -0,0 +1,588 @@
+// 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 Xunit;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal class CSharpSectionTest : CsHtmlMarkupParserTestBase
+ {
+ [Fact]
+ public void ParseSectionBlockCapturesNewlineImmediatelyFollowing()
+ {
+ ParseDocumentTest("@section" + Environment.NewLine,
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionChunkGenerator(string.Empty),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section" + Environment.NewLine))),
+ new RazorError(
+ LegacyResources.FormatParseError_Unexpected_Character_At_Section_Name_Start(
+ LegacyResources.ErrorComponent_EndOfFile),
+ new SourceLocation(8 + Environment.NewLine.Length, 1, 0),
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseSectionBlockCapturesWhitespaceToEndOfLineInSectionStatementMissingOpenBrace()
+ {
+ ParseDocumentTest("@section Foo " + Environment.NewLine
+ + " ",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionChunkGenerator("Foo"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section Foo " + Environment.NewLine)),
+ Factory.Markup(" ")),
+ new RazorError(
+ LegacyResources.ParseError_MissingOpenBraceAfterSection,
+ new SourceLocation(12, 0, 12),
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseSectionBlockCapturesWhitespaceToEndOfLineInSectionStatementMissingName()
+ {
+ ParseDocumentTest("@section " + Environment.NewLine
+ + " ",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionChunkGenerator(string.Empty),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section " + Environment.NewLine)),
+ Factory.Markup(" ")),
+ new RazorError(
+ LegacyResources.FormatParseError_Unexpected_Character_At_Section_Name_Start(
+ LegacyResources.ErrorComponent_EndOfFile),
+ new SourceLocation(21 + Environment.NewLine.Length, 1, 4),
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseSectionBlockIgnoresSectionUnlessAllLowerCase()
+ {
+ ParseDocumentTest("@Section foo",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("Section")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.Markup(" foo")));
+ }
+
+ [Fact]
+ public void ParseSectionBlockReportsErrorAndTerminatesSectionBlockIfKeywordNotFollowedByIdentifierStartCharacter()
+ {
+ ParseDocumentTest("@section 9 { Foo
}",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionChunkGenerator(string.Empty),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section ")),
+ Factory.Markup("9 { "),
+ new MarkupTagBlock(
+ Factory.Markup("")),
+ Factory.Markup("Foo"),
+ new MarkupTagBlock(
+ Factory.Markup("
")),
+ Factory.Markup(" }")),
+ new RazorError(
+ LegacyResources.FormatParseError_Unexpected_Character_At_Section_Name_Start(
+ LegacyResources.FormatErrorComponent_Character("9")),
+ new SourceLocation(9, 0, 9),
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseSectionBlockReportsErrorAndTerminatesSectionBlockIfNameNotFollowedByOpenBrace()
+ {
+ ParseDocumentTest("@section foo-bar { Foo
}",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionChunkGenerator("foo"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section foo")),
+ Factory.Markup("-bar { "),
+ new MarkupTagBlock(
+ Factory.Markup("")),
+ Factory.Markup("Foo"),
+ new MarkupTagBlock(
+ Factory.Markup("
")),
+ Factory.Markup(" }")),
+ new RazorError(
+ LegacyResources.ParseError_MissingOpenBraceAfterSection,
+ new SourceLocation(12, 0, 12),
+ length: 1));
+ }
+
+ [Fact]
+ public void ParserOutputsErrorOnNestedSections()
+ {
+ ParseDocumentTest("@section foo { @section bar { Foo
} }",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionChunkGenerator("foo"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section foo {")
+ .AutoCompleteWith(null, atEndOfSpan: true),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ new SectionBlock(new SectionChunkGenerator("bar"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section bar {")
+ .AutoCompleteWith(null, atEndOfSpan: true),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ new MarkupTagBlock(
+ Factory.Markup("")),
+ Factory.Markup("Foo"),
+ new MarkupTagBlock(
+ Factory.Markup("
")),
+ Factory.Markup(" ")),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
+ Factory.Markup(" ")),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()),
+ new RazorError(
+ LegacyResources.FormatParseError_Sections_Cannot_Be_Nested(LegacyResources.SectionExample_CS),
+ new SourceLocation(16, 0, 16),
+ 7));
+ }
+
+ [Fact]
+ public void ParseSectionBlockHandlesEOFAfterOpenBrace()
+ {
+ ParseDocumentTest("@section foo {",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionChunkGenerator("foo"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section foo {")
+ .AutoCompleteWith("}", atEndOfSpan: true),
+ new MarkupBlock())),
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF("section", "}", "{"),
+ new SourceLocation(13, 0, 13),
+ length: 1));
+ }
+
+ [Theory]
+ [InlineData(" ")]
+ [InlineData("\n")]
+ [InlineData(" abc")]
+ [InlineData(" \n abc")]
+ public void ParseSectionBlockHandlesEOFAfterOpenContent(string postStartBrace)
+ {
+ ParseDocumentTest("@section foo {" + postStartBrace,
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionChunkGenerator("foo"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section foo {")
+ .AutoCompleteWith("}", atEndOfSpan: true),
+ new MarkupBlock(
+ Factory.Markup(postStartBrace)))),
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF("section", "}", "{"),
+ new SourceLocation(13, 0, 13),
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseSectionBlockHandlesUnterminatedSection()
+ {
+ ParseDocumentTest("@section foo { Foo{}
",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionChunkGenerator("foo"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section foo {")
+ .AutoCompleteWith("}", atEndOfSpan: true),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ new MarkupTagBlock(
+ Factory.Markup("")),
+ // Need to provide the markup span as fragments, since the parser will split the {} into separate symbols.
+ Factory.Markup("Foo", "{", "}"),
+ new MarkupTagBlock(
+ Factory.Markup("
"))))),
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF("section", "}", "{"),
+ new SourceLocation(13, 0, 13),
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseSectionBlockHandlesUnterminatedSectionWithNestedIf()
+ {
+ var newLine = Environment.NewLine;
+ var spaces = " ";
+ ParseDocumentTest(
+ string.Format(
+ "@section Test{0}{{{0}{1}@if(true){0}{1}{{{0}{1}{1}Hello World
{0}{1}}}",
+ newLine,
+ spaces),
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionChunkGenerator("Test"),
+ Factory.CodeTransition(),
+ Factory.MetaCode($"section Test{newLine}{{")
+ .AutoCompleteWith("}", atEndOfSpan: true),
+ new MarkupBlock(
+ Factory.Markup(newLine),
+ new StatementBlock(
+ Factory.Code(spaces).AsStatement(),
+ Factory.CodeTransition(),
+ Factory.Code($"if(true){newLine}{spaces}{{{newLine}").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup($"{spaces}{spaces}"),
+ BlockFactory.MarkupTagBlock("", AcceptedCharacters.None),
+ Factory.Markup("Hello World"),
+ BlockFactory.MarkupTagBlock("
", AcceptedCharacters.None),
+ Factory.Markup(newLine).Accepts(AcceptedCharacters.None)),
+ Factory.Code($"{spaces}}}").AsStatement())))),
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF("section", "}", "{"),
+ new SourceLocation(13 + newLine.Length, 1, 0),
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseSectionBlockReportsErrorAndAcceptsWhitespaceToEndOfLineIfSectionNotFollowedByOpenBrace()
+ {
+ ParseDocumentTest("@section foo " + Environment.NewLine,
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionChunkGenerator("foo"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section foo " + Environment.NewLine))),
+ new RazorError(
+ LegacyResources.ParseError_MissingOpenBraceAfterSection,
+ new SourceLocation(12, 0, 12),
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseSectionBlockAcceptsOpenBraceMultipleLinesBelowSectionName()
+ {
+ ParseDocumentTest("@section foo " + Environment.NewLine
+ + Environment.NewLine
+ + Environment.NewLine
+ + Environment.NewLine
+ + Environment.NewLine
+ + Environment.NewLine
+ + "{" + Environment.NewLine
+ + "Foo
" + Environment.NewLine
+ + "}",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionChunkGenerator("foo"),
+ Factory.CodeTransition(),
+ Factory.MetaCode(string.Format("section foo {0}{0}{0}{0}{0}{0}{{", Environment.NewLine))
+ .AutoCompleteWith(null, atEndOfSpan: true),
+ new MarkupBlock(
+ Factory.Markup(Environment.NewLine),
+ new MarkupTagBlock(
+ Factory.Markup("")),
+ Factory.Markup("Foo"),
+ new MarkupTagBlock(
+ Factory.Markup("
")),
+ Factory.Markup(Environment.NewLine)),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void ParseSectionBlockParsesNamedSectionCorrectly()
+ {
+ ParseDocumentTest("@section foo { Foo
}",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionChunkGenerator("foo"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section foo {")
+ .AutoCompleteWith(null, atEndOfSpan: true),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ new MarkupTagBlock(
+ Factory.Markup("")),
+ Factory.Markup("Foo"),
+ new MarkupTagBlock(
+ Factory.Markup("
")),
+ Factory.Markup(" ")),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void ParseSectionBlockDoesNotRequireSpaceBetweenSectionNameAndOpenBrace()
+ {
+ ParseDocumentTest("@section foo{ Foo
}",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionChunkGenerator("foo"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section foo{")
+ .AutoCompleteWith(null, atEndOfSpan: true),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ new MarkupTagBlock(
+ Factory.Markup("")),
+ Factory.Markup("Foo"),
+ new MarkupTagBlock(
+ Factory.Markup("
")),
+ Factory.Markup(" ")),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void ParseSectionBlockBalancesBraces()
+ {
+ ParseDocumentTest("@section foo { }",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionChunkGenerator("foo"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section foo {")
+ .AutoCompleteWith(null, atEndOfSpan: true),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ new MarkupTagBlock(
+ Factory.Markup("")),
+ Factory.Markup(" ")),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void ParseSectionBlockAllowsBracesInCSharpExpression()
+ {
+ ParseDocumentTest("@section foo { I really want to render a close brace, so here I go: @(\"}\") }",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionChunkGenerator("foo"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section foo {")
+ .AutoCompleteWith(null, atEndOfSpan: true),
+ new MarkupBlock(
+ Factory.Markup(" I really want to render a close brace, so here I go: "),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
+ Factory.Code("\"}\"").AsExpression(),
+ Factory.MetaCode(")").Accepts(AcceptedCharacters.None)),
+ Factory.Markup(" ")),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void SectionIsCorrectlyTerminatedWhenCloseBraceImmediatelyFollowsCodeBlock()
+ {
+ ParseDocumentTest("@section Foo {" + Environment.NewLine
+ + "@if(true) {" + Environment.NewLine
+ + "}" + Environment.NewLine
+ + "}",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionChunkGenerator("Foo"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section Foo {")
+ .AutoCompleteWith(null, atEndOfSpan: true),
+ new MarkupBlock(
+ Factory.Markup(Environment.NewLine),
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code($"if(true) {{{Environment.NewLine}}}{Environment.NewLine}").AsStatement()
+ )),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void SectionIsCorrectlyTerminatedWhenCloseBraceImmediatelyFollowsCodeBlockNoWhitespace()
+ {
+ ParseDocumentTest("@section Foo {" + Environment.NewLine
+ + "@if(true) {" + Environment.NewLine
+ + "}}",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionChunkGenerator("Foo"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section Foo {")
+ .AutoCompleteWith(null, atEndOfSpan: true),
+ new MarkupBlock(
+ Factory.Markup(Environment.NewLine),
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code($"if(true) {{{Environment.NewLine}}}").AsStatement()
+ )),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void ParseSectionBlockCorrectlyTerminatesWhenCloseBraceImmediatelyFollowsMarkup()
+ {
+ ParseDocumentTest("@section foo {something}",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionChunkGenerator("foo"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section foo {")
+ .AutoCompleteWith(null, atEndOfSpan: true),
+ new MarkupBlock(
+ Factory.Markup("something")),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void ParseSectionBlockParsesComment()
+ {
+ ParseDocumentTest("@section s {}",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionChunkGenerator("s"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section s {")
+ .AutoCompleteWith(null, atEndOfSpan: true),
+ new MarkupBlock(
+ Factory.Markup("")),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()));
+ }
+
+ // This was a user reported bug (codeplex #710), the section parser wasn't handling
+ // comments.
+ [Fact]
+ public void ParseSectionBlockParsesCommentWithDelimiters()
+ {
+ ParseDocumentTest("@section s {}",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionChunkGenerator("s"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section s {")
+ .AutoCompleteWith(null, atEndOfSpan: true),
+ new MarkupBlock(
+ Factory.Markup("")),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void ParseSectionBlockCommentRecoversFromUnclosedTag()
+ {
+ ParseDocumentTest(
+ "@section s {" + Environment.NewLine + " \" '-->}",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionChunkGenerator("s"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section s {")
+ .AutoCompleteWith(null, atEndOfSpan: true),
+ new MarkupBlock(
+ Factory.Markup(Environment.NewLine),
+ new MarkupTagBlock(
+ Factory.Markup(" \" '-->")),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()));
+ }
+
+ [Fact]
+ public void ParseSectionBlockParsesXmlProcessingInstruction()
+ {
+ ParseDocumentTest(
+ "@section s { xml bleh ?>}",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new SectionBlock(new SectionChunkGenerator("s"),
+ Factory.CodeTransition(),
+ Factory.MetaCode("section s {")
+ .AutoCompleteWith(null, atEndOfSpan: true),
+ new MarkupBlock(
+ Factory.Markup(" xml bleh ?>")),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml()));
+ }
+
+ public static TheoryData SectionWithEscapedTransitionData
+ {
+ get
+ {
+ var factory = new SpanFactory();
+
+ return new TheoryData
+ {
+ {
+ "@section s {}",
+ new MarkupBlock(
+ factory.EmptyHtml(),
+ new SectionBlock(new SectionChunkGenerator("s"),
+ factory.CodeTransition(),
+ factory.MetaCode("section s {")
+ .AutoCompleteWith(null, atEndOfSpan: true),
+ new MarkupBlock(
+ new MarkupTagBlock(
+ factory.Markup("(" foo='", 17, 0, 17), new LocationTagged("'", 25, 0, 25)),
+ factory.Markup(" foo='").With(SpanChunkGenerator.Null),
+ new MarkupBlock(
+ factory.Markup("@").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 23, 0, 23), new LocationTagged("@", 23, 0, 23))).Accepts(AcceptedCharacters.None),
+ factory.Markup("@").With(SpanChunkGenerator.Null).Accepts(AcceptedCharacters.None)),
+ factory.Markup("'").With(SpanChunkGenerator.Null)),
+ factory.Markup(" />"))),
+ factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
+ factory.EmptyHtml())
+ },
+ {
+ "@section s {}",
+ new MarkupBlock(
+ factory.EmptyHtml(),
+ new SectionBlock(new SectionChunkGenerator("s"),
+ factory.CodeTransition(),
+ factory.MetaCode("section s {")
+ .AutoCompleteWith(null, atEndOfSpan: true),
+ new MarkupBlock(
+ new MarkupTagBlock(
+ factory.Markup("(" foo='", 17, 0, 17), new LocationTagged("'", 39, 0, 39)),
+ factory.Markup(" foo='").With(SpanChunkGenerator.Null),
+ new MarkupBlock(
+ new DynamicAttributeBlockChunkGenerator(new LocationTagged(string.Empty, 23, 0, 23), 23, 0, 23),
+ new ExpressionBlock(
+ factory.CodeTransition(),
+ factory.Code("DateTime.Now")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace))),
+ new MarkupBlock(
+ factory.Markup(" @").With(new LiteralAttributeChunkGenerator(new LocationTagged(" ", 36, 0, 36), new LocationTagged("@", 37, 0, 37))).Accepts(AcceptedCharacters.None),
+ factory.Markup("@").With(SpanChunkGenerator.Null).Accepts(AcceptedCharacters.None)),
+ factory.Markup("'").With(SpanChunkGenerator.Null)),
+ factory.Markup(" />"))),
+ factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
+ factory.EmptyHtml())
+ },
+ };
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(SectionWithEscapedTransitionData))]
+ public void ParseSectionBlock_WithDoubleTransition_DoesNotThrow(string input, Block expected)
+ {
+ ParseDocumentTest(input, expected);
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpSpecialBlockTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpSpecialBlockTest.cs
new file mode 100644
index 0000000000..859f7de659
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpSpecialBlockTest.cs
@@ -0,0 +1,217 @@
+// 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 Xunit;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal class CSharpSpecialBlockTest : CsHtmlCodeParserTestBase
+ {
+ [Fact]
+ public void ParseInheritsStatementMarksInheritsSpanAsCanGrowIfMissingTrailingSpace()
+ {
+ ParseBlockTest("inherits",
+ new DirectiveBlock(
+ Factory.MetaCode("inherits").Accepts(AcceptedCharacters.Any)
+ ),
+ new RazorError(
+ LegacyResources.ParseError_InheritsKeyword_Must_Be_Followed_By_TypeName,
+ new SourceLocation(0, 0, 0), 8));
+ }
+
+ [Fact]
+ public void InheritsBlockAcceptsMultipleGenericArguments()
+ {
+ ParseBlockTest("inherits Foo.Bar, string, int>.Baz",
+ new DirectiveBlock(
+ Factory.MetaCode("inherits ").Accepts(AcceptedCharacters.None),
+ Factory.Code("Foo.Bar, string, int>.Baz")
+ .AsBaseType("Foo.Bar, string, int>.Baz")
+ ));
+ }
+
+ [Fact]
+ public void InheritsBlockOutputsErrorIfInheritsNotFollowedByTypeButAcceptsEntireLineAsCode()
+ {
+ ParseBlockTest("inherits " + Environment.NewLine
+ + "foo",
+ new DirectiveBlock(
+ Factory.MetaCode("inherits ").Accepts(AcceptedCharacters.None),
+ Factory.Code(" " + Environment.NewLine)
+ .AsBaseType(string.Empty)
+ ),
+ new RazorError(LegacyResources.ParseError_InheritsKeyword_Must_Be_Followed_By_TypeName, 0, 0, 0, 8));
+ }
+
+ [Fact]
+ public void NamespaceImportInsideCodeBlockCausesError()
+ {
+ ParseBlockTest("{ using Foo.Bar.Baz; var foo = bar; }",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code(" using Foo.Bar.Baz; var foo = bar; ")
+ .AsStatement()
+ .AutoCompleteWith(autoCompleteString: null),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
+ ),
+ new RazorError(
+ LegacyResources.ParseError_NamespaceImportAndTypeAlias_Cannot_Exist_Within_CodeBlock,
+ new SourceLocation(2, 0, 2),
+ length: 5));
+ }
+
+ [Fact]
+ public void TypeAliasInsideCodeBlockIsNotHandledSpecially()
+ {
+ ParseBlockTest("{ using Foo = Bar.Baz; var foo = bar; }",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code(" using Foo = Bar.Baz; var foo = bar; ")
+ .AsStatement()
+ .AutoCompleteWith(autoCompleteString: null),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
+ ),
+ new RazorError(
+ LegacyResources.ParseError_NamespaceImportAndTypeAlias_Cannot_Exist_Within_CodeBlock,
+ new SourceLocation(2, 0, 2),
+ length: 5));
+ }
+
+ [Fact]
+ public void Plan9FunctionsKeywordInsideCodeBlockIsNotHandledSpecially()
+ {
+ ParseBlockTest("{ functions Foo; }",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code(" functions Foo; ")
+ .AsStatement()
+ .AutoCompleteWith(autoCompleteString: null),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void NonKeywordStatementInCodeBlockIsHandledCorrectly()
+ {
+ ParseBlockTest("{" + Environment.NewLine
+ + " List photos = gallery.Photo.ToList();" + Environment.NewLine
+ + "}",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code($"{Environment.NewLine} List photos = gallery.Photo.ToList();{Environment.NewLine}")
+ .AsStatement()
+ .AutoCompleteWith(autoCompleteString: null),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockBalancesBracesOutsideStringsIfFirstCharacterIsBraceAndReturnsSpanOfTypeCode()
+ {
+ // Arrange
+ const string code = "foo\"b}ar\" if(condition) { string.Format(\"{0}\"); } ";
+
+ // Act/Assert
+ ParseBlockTest("{" + code + "}",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code(code)
+ .AsStatement()
+ .AutoCompleteWith(autoCompleteString: null),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockBalancesParensOutsideStringsIfFirstCharacterIsParenAndReturnsSpanOfTypeExpression()
+ {
+ // Arrange
+ const string code = "foo\"b)ar\" if(condition) { string.Format(\"{0}\"); } ";
+
+ // Act/Assert
+ ParseBlockTest("(" + code + ")",
+ new ExpressionBlock(
+ Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
+ Factory.Code(code).AsExpression(),
+ Factory.MetaCode(")").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockBalancesBracesAndOutputsContentAsClassLevelCodeSpanIfFirstIdentifierIsFunctionsKeyword()
+ {
+ const string code = " foo(); \"bar}baz\" ";
+ ParseBlockTest("functions {" + code + "} zoop",
+ new FunctionsBlock(
+ Factory.MetaCode("functions {").Accepts(AcceptedCharacters.None),
+ Factory.Code(code)
+ .AsFunctionsBody()
+ .AutoCompleteWith(autoCompleteString: null),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockDoesNoErrorRecoveryForFunctionsBlock()
+ {
+ ParseBlockTest("functions { { { { { } zoop",
+ new FunctionsBlock(
+ Factory.MetaCode("functions {").Accepts(AcceptedCharacters.None),
+ Factory.Code(" { { { { } zoop")
+ .AsFunctionsBody()
+ .AutoCompleteWith("}")
+ ),
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF("functions", "}", "{"),
+ new SourceLocation(10, 0, 10),
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseBlockIgnoresFunctionsUnlessAllLowerCase()
+ {
+ ParseBlockTest("Functions { foo() }",
+ new ExpressionBlock(
+ Factory.Code("Functions")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)));
+ }
+
+ [Fact]
+ public void ParseBlockIgnoresSingleSlashAtStart()
+ {
+ ParseBlockTest("@/ foo",
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.EmptyCSharp()
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)),
+ new RazorError(
+ LegacyResources.FormatParseError_Unexpected_Character_At_Start_Of_CodeBlock_CS("/"),
+ new SourceLocation(1, 0, 1),
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseBlockTerminatesSingleLineCommentAtEndOfLine()
+ {
+ ParseBlockTest("if(!false) {" + Environment.NewLine
+ + " // Foo" + Environment.NewLine
+ + "\tA real tag!
" + Environment.NewLine
+ + "}",
+ new StatementBlock(
+ Factory.Code($"if(!false) {{{Environment.NewLine} // Foo{Environment.NewLine}").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup("\t"),
+ new MarkupTagBlock(
+ Factory.Markup("").Accepts(AcceptedCharacters.None)),
+ Factory.Markup("A real tag!"),
+ new MarkupTagBlock(
+ Factory.Markup("
").Accepts(AcceptedCharacters.None)),
+ Factory.Markup(Environment.NewLine).Accepts(AcceptedCharacters.None)),
+ Factory.Code("}").AsStatement()
+ ));
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpStatementTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpStatementTest.cs
new file mode 100644
index 0000000000..a6726ce429
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpStatementTest.cs
@@ -0,0 +1,418 @@
+// 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 Xunit;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ // Basic Tests for C# Statements:
+ // * Basic case for each statement
+ // * Basic case for ALL clauses
+
+ // This class DOES NOT contain
+ // * Error cases
+ // * Tests for various types of nested statements
+ // * Comment tests
+
+ internal class CSharpStatementTest : CsHtmlCodeParserTestBase
+ {
+ [Fact]
+ public void ForStatement()
+ {
+ ParseBlockTest("@for(int i = 0; i++; i < length) { foo(); }",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("for(int i = 0; i++; i < length) { foo(); }")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ForEachStatement()
+ {
+ ParseBlockTest("@foreach(var foo in bar) { foo(); }",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("foreach(var foo in bar) { foo(); }")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void WhileStatement()
+ {
+ ParseBlockTest("@while(true) { foo(); }",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("while(true) { foo(); }")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void SwitchStatement()
+ {
+ ParseBlockTest("@switch(foo) { foo(); }",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("switch(foo) { foo(); }")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void LockStatement()
+ {
+ ParseBlockTest("@lock(baz) { foo(); }",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("lock(baz) { foo(); }")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void IfStatement()
+ {
+ ParseBlockTest("@if(true) { foo(); }",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("if(true) { foo(); }")
+ .AsStatement()
+ ));
+ }
+
+ [Fact]
+ public void ElseIfClause()
+ {
+ ParseBlockTest("@if(true) { foo(); } else if(false) { foo(); } else if(!false) { foo(); }",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("if(true) { foo(); } else if(false) { foo(); } else if(!false) { foo(); }")
+ .AsStatement()
+ ));
+ }
+
+ [Fact]
+ public void ElseClause()
+ {
+ ParseBlockTest("@if(true) { foo(); } else { foo(); }",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("if(true) { foo(); } else { foo(); }")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void TryStatement()
+ {
+ ParseBlockTest("@try { foo(); }",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("try { foo(); }")
+ .AsStatement()
+ ));
+ }
+
+ [Fact]
+ public void CatchClause()
+ {
+ ParseBlockTest("@try { foo(); } catch(IOException ioex) { handleIO(); } catch(Exception ex) { handleOther(); }",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("try { foo(); } catch(IOException ioex) { handleIO(); } catch(Exception ex) { handleOther(); }")
+ .AsStatement()
+ ));
+ }
+
+ public static TheoryData ExceptionFilterData
+ {
+ get
+ {
+ var factory = new SpanFactory();
+
+ // document, expectedStatement
+ return new TheoryData
+ {
+ {
+ "@try { someMethod(); } catch(Exception) when (true) { handleIO(); }",
+ new StatementBlock(
+ factory.CodeTransition(),
+ factory
+ .Code("try { someMethod(); } catch(Exception) when (true) { handleIO(); }")
+ .AsStatement())
+ },
+ {
+ "@try { A(); } catch(Exception) when (true) { B(); } finally { C(); }",
+ new StatementBlock(
+ factory.CodeTransition(),
+ factory
+ .Code("try { A(); } catch(Exception) when (true) { B(); } finally { C(); }")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None))
+ },
+ {
+ "@try { A(); } catch(Exception) when (true) { B(); } catch(IOException) when (false) { C(); }",
+ new StatementBlock(
+ factory.CodeTransition(),
+ factory
+ .Code("try { A(); } catch(Exception) when (true) { B(); } catch(IOException) " +
+ "when (false) { C(); }")
+ .AsStatement())
+ },
+ {
+ string.Format("@try{0}{{{0} A();{0}}}{0}catch(Exception) when (true)", Environment.NewLine) +
+ string.Format("{0}{{{0} B();{0}}}{0}catch(IOException) when (false)", Environment.NewLine) +
+ string.Format("{0}{{{0} C();{0}}}", Environment.NewLine),
+ new StatementBlock(
+ factory.CodeTransition(),
+ factory
+ .Code(
+ string.Format("try{0}{{{0} A();{0}}}{0}catch(Exception) ", Environment.NewLine) +
+ string.Format("when (true){0}{{{0} B();{0}}}{0}", Environment.NewLine) +
+ string.Format("catch(IOException) when (false){0}{{{0} ", Environment.NewLine) +
+ string.Format("C();{0}}}", Environment.NewLine))
+ .AsStatement())
+ },
+
+ // Wrapped in @{ block.
+ {
+ "@{try { someMethod(); } catch(Exception) when (true) { handleIO(); }}",
+ new StatementBlock(
+ factory.CodeTransition(),
+ factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ factory
+ .Code("try { someMethod(); } catch(Exception) when (true) { handleIO(); }")
+ .AsStatement()
+ .AutoCompleteWith(autoCompleteString: null),
+ factory.MetaCode("}").Accepts(AcceptedCharacters.None))
+ },
+
+ // Partial exception filter data
+ {
+ "@try { someMethod(); } catch(Exception) when",
+ new StatementBlock(
+ factory.CodeTransition(),
+ factory
+ .Code("try { someMethod(); } catch(Exception) when")
+ .AsStatement())
+ },
+ {
+ "@try { someMethod(); } when",
+ new StatementBlock(
+ factory.CodeTransition(),
+ factory
+ .Code("try { someMethod(); }")
+ .AsStatement())
+ },
+ {
+ "@try { someMethod(); } catch(Exception) when { anotherMethod(); }",
+ new StatementBlock(
+ factory.CodeTransition(),
+ factory
+ .Code("try { someMethod(); } catch(Exception) when { anotherMethod(); }")
+ .AsStatement())
+ },
+ {
+ "@try { someMethod(); } catch(Exception) when (true)",
+ new StatementBlock(
+ factory.CodeTransition(),
+ factory
+ .Code("try { someMethod(); } catch(Exception) when (true)")
+ .AsStatement())
+ },
+ };
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(ExceptionFilterData))]
+ public void ExceptionFilters(string document, StatementBlock expectedStatement)
+ {
+ // Act & Assert
+ ParseBlockTest(document, expectedStatement);
+ }
+
+ public static TheoryData ExceptionFilterErrorData
+ {
+ get
+ {
+ var factory = new SpanFactory();
+ var unbalancedParenErrorString = "An opening \"(\" is missing the corresponding closing \")\".";
+ var unbalancedBracketCatchErrorString = "The catch block is missing a closing \"}\" character. " +
+ "Make sure you have a matching \"}\" character for all the \"{\" characters within this block, " +
+ "and that none of the \"}\" characters are being interpreted as markup.";
+
+ // document, expectedStatement, expectedErrors
+ return new TheoryData
+ {
+ {
+ "@try { someMethod(); } catch(Exception) when (",
+ new StatementBlock(
+ factory.CodeTransition(),
+ factory
+ .Code("try { someMethod(); } catch(Exception) when (")
+ .AsStatement()),
+ new[] { new RazorError(unbalancedParenErrorString, 45, 0, 45, 1) }
+ },
+ {
+ "@try { someMethod(); } catch(Exception) when (someMethod(",
+ new StatementBlock(
+ factory.CodeTransition(),
+ factory
+ .Code("try { someMethod(); } catch(Exception) when (someMethod(")
+ .AsStatement()),
+ new[] { new RazorError(unbalancedParenErrorString, 45, 0, 45, 1) }
+ },
+ {
+ "@try { someMethod(); } catch(Exception) when (true) {",
+ new StatementBlock(
+ factory.CodeTransition(),
+ factory
+ .Code("try { someMethod(); } catch(Exception) when (true) {")
+ .AsStatement()),
+ new[] { new RazorError(unbalancedBracketCatchErrorString, 23, 0, 23, 1) }
+ },
+ };
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(ExceptionFilterErrorData))]
+ public void ExceptionFilterErrors(
+ string document,
+ StatementBlock expectedStatement,
+ RazorError[] expectedErrors)
+ {
+ // Act & Assert
+ ParseBlockTest(document, expectedStatement, expectedErrors);
+ }
+
+ [Fact]
+ public void FinallyClause()
+ {
+ ParseBlockTest("@try { foo(); } finally { Dispose(); }",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("try { foo(); } finally { Dispose(); }")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ public static TheoryData StaticUsingData
+ {
+ get
+ {
+ var factory = new SpanFactory();
+ Func createUsing = (code, import) =>
+ new DirectiveBlock(
+ factory.CodeTransition(),
+ factory.Code(code)
+ .AsNamespaceImport(import)
+ .Accepts(AcceptedCharacters.AnyExceptNewline));
+
+ // document, expectedResult
+ return new TheoryData
+ {
+ { "@using static", createUsing("using static", " static") },
+ { "@using static ", createUsing("using static ", " static ") },
+ { "@using static ", createUsing("using static ", " static ") },
+ { "@using static System", createUsing("using static System", " static System") },
+ {
+ "@using static System",
+ createUsing("using static System", " static System")
+ },
+ {
+ "@using static System.Console",
+ createUsing("using static System.Console", " static System.Console")
+ },
+ {
+ "@using static global::System.Console",
+ createUsing("using static global::System.Console", " static global::System.Console")
+ },
+ {
+ "@using static global::System.Console ",
+ createUsing("using static global::System.Console", " static global::System.Console")
+ },
+ };
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(StaticUsingData))]
+ public void StaticUsingImport(string document, DirectiveBlock expectedResult)
+ {
+ // Act & Assert
+ ParseBlockTest(document, expectedResult);
+ }
+
+ [Fact]
+ public void UsingStatement()
+ {
+ ParseBlockTest("@using(var foo = new Foo()) { foo.Bar(); }",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("using(var foo = new Foo()) { foo.Bar(); }")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void UsingTypeAlias()
+ {
+ ParseBlockTest("@using StringDictionary = System.Collections.Generic.Dictionary",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.Code("using StringDictionary = System.Collections.Generic.Dictionary")
+ .AsNamespaceImport(" StringDictionary = System.Collections.Generic.Dictionary")
+ .Accepts(AcceptedCharacters.AnyExceptNewline)
+ ));
+ }
+
+ [Fact]
+ public void UsingNamespaceImport()
+ {
+ ParseBlockTest("@using System.Text.Encoding.ASCIIEncoding",
+ new DirectiveBlock(
+ Factory.CodeTransition(),
+ Factory.Code("using System.Text.Encoding.ASCIIEncoding")
+ .AsNamespaceImport(" System.Text.Encoding.ASCIIEncoding")
+ .Accepts(AcceptedCharacters.AnyExceptNewline)
+ ));
+ }
+
+ [Fact]
+ public void DoStatement()
+ {
+ ParseBlockTest("@do { foo(); } while(true);",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.Code("do { foo(); } while(true);")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void NonBlockKeywordTreatedAsImplicitExpression()
+ {
+ ParseBlockTest("@is foo",
+ new ExpressionBlock(new ExpressionChunkGenerator(),
+ Factory.CodeTransition(),
+ Factory.Code("is")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)
+ ));
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpTemplateTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpTemplateTest.cs
new file mode 100644
index 0000000000..3cce547916
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpTemplateTest.cs
@@ -0,0 +1,321 @@
+// 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 Xunit;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal class CSharpTemplateTest : CsHtmlCodeParserTestBase
+ {
+ private const string TestTemplateCode = " @Foo #@item
";
+
+ private TemplateBlock TestTemplate()
+ {
+ return new TemplateBlock(
+ new MarkupBlock(
+ Factory.MarkupTransition(),
+ new MarkupTagBlock(
+ Factory.Markup("").Accepts(AcceptedCharacters.None)),
+ Factory.Markup("Foo #"),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("item")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)
+ ),
+ new MarkupTagBlock(
+ Factory.Markup("
").Accepts(AcceptedCharacters.None))
+ )
+ );
+ }
+
+ private const string TestNestedTemplateCode = " @Foo #@Html.Repeat(10, @
@item
)";
+
+ private TemplateBlock TestNestedTemplate()
+ {
+ return new TemplateBlock(
+ new MarkupBlock(
+ Factory.MarkupTransition(),
+ new MarkupTagBlock(
+ Factory.Markup("").Accepts(AcceptedCharacters.None)),
+ Factory.Markup("Foo #"),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("Html.Repeat(10, ")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords),
+ new TemplateBlock(
+ new MarkupBlock(
+ Factory.MarkupTransition(),
+ new MarkupTagBlock(
+ Factory.Markup("
").Accepts(AcceptedCharacters.None)),
+ Factory.EmptyHtml(),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("item")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)
+ ),
+ new MarkupTagBlock(
+ Factory.Markup("
").Accepts(AcceptedCharacters.None))
+ )
+ ),
+ Factory.Code(")")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)
+ ),
+ new MarkupTagBlock(
+ Factory.Markup("").Accepts(AcceptedCharacters.None))
+ )
+ );
+ }
+
+ [Fact]
+ public void ParseBlockHandlesSingleLineTemplate()
+ {
+ ParseBlockTest("{ var foo = @: bar" + Environment.NewLine
+ + "; }",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code(" var foo = ")
+ .AsStatement()
+ .AutoCompleteWith(autoCompleteString: null),
+ new TemplateBlock(
+ new MarkupBlock(
+ Factory.MarkupTransition(),
+ Factory.MetaMarkup(":", HtmlSymbolType.Colon),
+ Factory.Markup(" bar" + Environment.NewLine)
+ .With(new SpanEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString))
+ .Accepts(AcceptedCharacters.None)
+ )
+ ),
+ Factory.Code("; ").AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockHandlesSingleLineImmediatelyFollowingStatementChar()
+ {
+ ParseBlockTest("{i@: bar" + Environment.NewLine
+ + "}",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code("i")
+ .AsStatement()
+ .AutoCompleteWith(autoCompleteString: null),
+ new TemplateBlock(
+ new MarkupBlock(
+ Factory.MarkupTransition(),
+ Factory.MetaMarkup(":", HtmlSymbolType.Colon),
+ Factory.Markup(" bar" + Environment.NewLine)
+ .With(new SpanEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString))
+ .Accepts(AcceptedCharacters.None)
+ )
+ ),
+ Factory.EmptyCSharp().AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockHandlesSimpleTemplateInExplicitExpressionParens()
+ {
+ ParseBlockTest("(Html.Repeat(10," + TestTemplateCode + "))",
+ new ExpressionBlock(
+ Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
+ Factory.Code("Html.Repeat(10, ").AsExpression(),
+ TestTemplate(),
+ Factory.Code(")").AsExpression(),
+ Factory.MetaCode(")").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockHandlesSimpleTemplateInImplicitExpressionParens()
+ {
+ ParseBlockTest("Html.Repeat(10," + TestTemplateCode + ")",
+ new ExpressionBlock(
+ Factory.Code("Html.Repeat(10, ")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords),
+ TestTemplate(),
+ Factory.Code(")")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockHandlesTwoTemplatesInImplicitExpressionParens()
+ {
+ ParseBlockTest("Html.Repeat(10," + TestTemplateCode + "," + TestTemplateCode + ")",
+ new ExpressionBlock(
+ Factory.Code("Html.Repeat(10, ")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords),
+ TestTemplate(),
+ Factory.Code(", ")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords),
+ TestTemplate(),
+ Factory.Code(")")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharacters.NonWhiteSpace)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockProducesErrorButCorrectlyParsesNestedTemplateInImplicitExpressionParens()
+ {
+ ParseBlockTest("Html.Repeat(10," + TestNestedTemplateCode + ")",
+ new ExpressionBlock(
+ Factory.Code("Html.Repeat(10, ")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords),
+ TestNestedTemplate(),
+ Factory.Code(")")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)
+ ),
+ GetNestedTemplateError(42));
+ }
+
+ [Fact]
+ public void ParseBlockHandlesSimpleTemplateInStatementWithinCodeBlock()
+ {
+ ParseBlockTest("foreach(foo in Bar) { Html.ExecuteTemplate(foo," + TestTemplateCode + "); }",
+ new StatementBlock(
+ Factory.Code("foreach(foo in Bar) { Html.ExecuteTemplate(foo, ").AsStatement(),
+ TestTemplate(),
+ Factory.Code("); }")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockHandlesTwoTemplatesInStatementWithinCodeBlock()
+ {
+ ParseBlockTest("foreach(foo in Bar) { Html.ExecuteTemplate(foo," + TestTemplateCode + "," + TestTemplateCode + "); }",
+ new StatementBlock(
+ Factory.Code("foreach(foo in Bar) { Html.ExecuteTemplate(foo, ").AsStatement(),
+ TestTemplate(),
+ Factory.Code(", ").AsStatement(),
+ TestTemplate(),
+ Factory.Code("); }")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockProducesErrorButCorrectlyParsesNestedTemplateInStatementWithinCodeBlock()
+ {
+ ParseBlockTest("foreach(foo in Bar) { Html.ExecuteTemplate(foo," + TestNestedTemplateCode + "); }",
+ new StatementBlock(
+ Factory.Code("foreach(foo in Bar) { Html.ExecuteTemplate(foo, ")
+ .AsStatement(),
+ TestNestedTemplate(),
+ Factory.Code("); }")
+ .AsStatement()
+ .Accepts(AcceptedCharacters.None)
+ ),
+ GetNestedTemplateError(74));
+ }
+
+ [Fact]
+ public void ParseBlockHandlesSimpleTemplateInStatementWithinStatementBlock()
+ {
+ ParseBlockTest("{ var foo = bar; Html.ExecuteTemplate(foo," + TestTemplateCode + "); }",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code(" var foo = bar; Html.ExecuteTemplate(foo, ")
+ .AsStatement()
+ .AutoCompleteWith(autoCompleteString: null),
+ TestTemplate(),
+ Factory.Code("); ").AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockHandlessTwoTemplatesInStatementWithinStatementBlock()
+ {
+ ParseBlockTest("{ var foo = bar; Html.ExecuteTemplate(foo," + TestTemplateCode + "," + TestTemplateCode + "); }",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code(" var foo = bar; Html.ExecuteTemplate(foo, ")
+ .AsStatement()
+ .AutoCompleteWith(autoCompleteString: null),
+ TestTemplate(),
+ Factory.Code(", ").AsStatement(),
+ TestTemplate(),
+ Factory.Code("); ").AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockProducesErrorButCorrectlyParsesNestedTemplateInStatementWithinStatementBlock()
+ {
+ ParseBlockTest("{ var foo = bar; Html.ExecuteTemplate(foo," + TestNestedTemplateCode + "); }",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code(" var foo = bar; Html.ExecuteTemplate(foo, ")
+ .AsStatement()
+ .AutoCompleteWith(autoCompleteString: null),
+ TestNestedTemplate(),
+ Factory.Code("); ").AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
+ ),
+ GetNestedTemplateError(69));
+ }
+
+ [Fact]
+ public void ParseBlock_WithDoubleTransition_DoesNotThrow()
+ {
+ // Arrange
+ var testTemplateWithDoubleTransitionCode = " @Foo #@item
";
+ var testTemplateWithDoubleTransition = new TemplateBlock(
+ new MarkupBlock(
+ Factory.MarkupTransition(),
+ new MarkupTagBlock(
+ Factory.Markup("(" foo='", 46, 0, 46), new LocationTagged("'", 54, 0, 54)),
+ Factory.Markup(" foo='").With(SpanChunkGenerator.Null),
+ new MarkupBlock(
+ Factory.Markup("@").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 52, 0, 52), new LocationTagged("@", 52, 0, 52))).Accepts(AcceptedCharacters.None),
+ Factory.Markup("@").With(SpanChunkGenerator.Null).Accepts(AcceptedCharacters.None)),
+ Factory.Markup("'").With(SpanChunkGenerator.Null)),
+ Factory.Markup(">").Accepts(AcceptedCharacters.None)),
+ Factory.Markup("Foo #"),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("item")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)
+ ),
+ new MarkupTagBlock(
+ Factory.Markup("
").Accepts(AcceptedCharacters.None))
+ )
+ );
+
+ var expected = new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code(" var foo = bar; Html.ExecuteTemplate(foo, ")
+ .AsStatement()
+ .AutoCompleteWith(autoCompleteString: null),
+ testTemplateWithDoubleTransition,
+ Factory.Code("); ").AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None));
+
+ // Act & Assert
+ ParseBlockTest("{ var foo = bar; Html.ExecuteTemplate(foo," + testTemplateWithDoubleTransitionCode + "); }", expected);
+ }
+
+ private static RazorError GetNestedTemplateError(int characterIndex)
+ {
+ return new RazorError(
+ LegacyResources.ParseError_InlineMarkup_Blocks_Cannot_Be_Nested,
+ new SourceLocation(characterIndex, 0, characterIndex),
+ length: 1);
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpToMarkupSwitchTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpToMarkupSwitchTest.cs
new file mode 100644
index 0000000000..afdd73662e
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpToMarkupSwitchTest.cs
@@ -0,0 +1,693 @@
+// 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 Xunit;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal class CSharpToMarkupSwitchTest : CsHtmlCodeParserTestBase
+ {
+ [Fact]
+ public void SingleAngleBracketDoesNotCauseSwitchIfOuterBlockIsTerminated()
+ {
+ ParseBlockTest("{ List< }",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code(" List< ")
+ .AsStatement()
+ .AutoCompleteWith(autoCompleteString: null),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlockGivesSpacesToCodeOnAtTagTemplateTransitionInDesignTimeMode()
+ {
+ ParseBlockTest("Foo( @Foo
)",
+ new ExpressionBlock(
+ Factory.Code("Foo( ")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.Any),
+ new TemplateBlock(
+ new MarkupBlock(
+ Factory.MarkupTransition(),
+ new MarkupTagBlock(
+ Factory.Markup("").Accepts(AcceptedCharacters.None)),
+ Factory.Markup("Foo"),
+ new MarkupTagBlock(
+ Factory.Markup("
").Accepts(AcceptedCharacters.None))
+ )
+ ),
+ Factory.Code(" )")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)
+ ), designTime: true);
+ }
+
+ [Fact]
+ public void ParseBlockGivesSpacesToCodeOnAtColonTemplateTransitionInDesignTimeMode()
+ {
+ ParseBlockTest("Foo( " + Environment.NewLine
+ + "@:Foo
" + Environment.NewLine
+ + ")",
+ new ExpressionBlock(
+ Factory.Code("Foo( " + Environment.NewLine).AsImplicitExpression(CSharpCodeParser.DefaultKeywords),
+ new TemplateBlock(
+ new MarkupBlock(
+ Factory.MarkupTransition(),
+ Factory.MetaMarkup(":", HtmlSymbolType.Colon),
+ Factory.Markup("Foo
" + Environment.NewLine)
+ .With(new SpanEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString, AcceptedCharacters.None))
+ )
+ ),
+ Factory.Code(")")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)
+ ), designTime: true);
+ }
+
+ [Fact]
+ public void ParseBlockGivesSpacesToCodeOnTagTransitionInDesignTimeMode()
+ {
+ ParseBlockTest("{" + Environment.NewLine
+ + " Foo
" + Environment.NewLine
+ + "}",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code(Environment.NewLine + " ")
+ .AsStatement()
+ .AutoCompleteWith(autoCompleteString: null),
+ new MarkupBlock(
+ new MarkupTagBlock(
+ Factory.Markup("").Accepts(AcceptedCharacters.None)),
+ Factory.Markup("Foo"),
+ new MarkupTagBlock(
+ Factory.Markup("
").Accepts(AcceptedCharacters.None))
+ ),
+ Factory.Code(" " + Environment.NewLine).AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
+ ), designTime: true);
+ }
+
+ [Fact]
+ public void ParseBlockGivesSpacesToCodeOnInvalidAtTagTransitionInDesignTimeMode()
+ {
+ ParseBlockTest("{" + Environment.NewLine
+ + " @Foo
" + Environment.NewLine
+ + "}",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code(Environment.NewLine + " ")
+ .AsStatement()
+ .AutoCompleteWith(autoCompleteString: null),
+ new MarkupBlock(
+ Factory.MarkupTransition(),
+ new MarkupTagBlock(
+ Factory.Markup("").Accepts(AcceptedCharacters.None)),
+ Factory.Markup("Foo"),
+ new MarkupTagBlock(
+ Factory.Markup("
").Accepts(AcceptedCharacters.None))
+ ),
+ Factory.Code(" " + Environment.NewLine).AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
+ ), true,
+ new RazorError(
+ LegacyResources.ParseError_AtInCode_Must_Be_Followed_By_Colon_Paren_Or_Identifier_Start,
+ new SourceLocation(5 + Environment.NewLine.Length, 1, 4),
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseBlockGivesSpacesToCodeOnAtColonTransitionInDesignTimeMode()
+ {
+ ParseBlockTest("{" + Environment.NewLine
+ + " @:Foo
" + Environment.NewLine
+ + "}",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code(Environment.NewLine + " ")
+ .AsStatement()
+ .AutoCompleteWith(autoCompleteString: null),
+ new MarkupBlock(
+ Factory.MarkupTransition(),
+ Factory.MetaMarkup(":", HtmlSymbolType.Colon),
+ Factory.Markup("Foo
" + Environment.NewLine)
+ .With(new SpanEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString, AcceptedCharacters.None))
+ ),
+ Factory.EmptyCSharp().AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
+ ), designTime: true);
+ }
+
+ [Fact]
+ public void ParseBlockShouldSupportSingleLineMarkupContainingStatementBlock()
+ {
+ ParseBlockTest("Repeat(10," + Environment.NewLine
+ + " @: @{}" + Environment.NewLine
+ + ")",
+ new ExpressionBlock(
+ Factory.Code($"Repeat(10,{Environment.NewLine} ")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords),
+ new TemplateBlock(
+ new MarkupBlock(
+ Factory.MarkupTransition(),
+ Factory.MetaMarkup(":", HtmlSymbolType.Colon),
+ Factory.Markup(" ")
+ .With(new SpanEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString)),
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.EmptyCSharp()
+ .AsStatement()
+ .AutoCompleteWith(autoCompleteString: null),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Markup(Environment.NewLine)
+ .Accepts(AcceptedCharacters.None)
+ )
+ ),
+ Factory.Code(")")
+ .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
+ .Accepts(AcceptedCharacters.NonWhiteSpace)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockShouldSupportMarkupWithoutPreceedingWhitespace()
+ {
+ ParseBlockTest("foreach(var file in files){" + Environment.NewLine
+ + Environment.NewLine
+ + Environment.NewLine
+ + "@:Baz" + Environment.NewLine
+ + "
" + Environment.NewLine
+ + "Foo" + Environment.NewLine
+ + "@:Bar" + Environment.NewLine
+ + "}",
+ new StatementBlock(
+ Factory.Code(string.Format("foreach(var file in files){{{0}{0}{0}", Environment.NewLine)).AsStatement(),
+ new MarkupBlock(
+ Factory.MarkupTransition(),
+ Factory.MetaMarkup(":", HtmlSymbolType.Colon),
+ Factory.Markup("Baz" + Environment.NewLine)
+ .With(new SpanEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString, AcceptedCharacters.None))
+ ),
+ new MarkupBlock(
+ new MarkupTagBlock(
+ Factory.Markup("
").Accepts(AcceptedCharacters.None)),
+ Factory.Markup(Environment.NewLine).Accepts(AcceptedCharacters.None)
+ ),
+ new MarkupBlock(
+ new MarkupTagBlock(
+ Factory.Markup("").Accepts(AcceptedCharacters.None)),
+ Factory.Markup("Foo"),
+ new MarkupTagBlock(
+ Factory.Markup("").Accepts(AcceptedCharacters.None)),
+ Factory.Markup(Environment.NewLine).Accepts(AcceptedCharacters.None)),
+ new MarkupBlock(
+ Factory.MarkupTransition(),
+ Factory.MetaMarkup(":", HtmlSymbolType.Colon),
+ Factory.Markup("Bar" + Environment.NewLine)
+ .With(new SpanEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString, AcceptedCharacters.None))
+ ),
+ Factory.Code("}").AsStatement().Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockGivesAllWhitespaceOnSameLineExcludingPreceedingNewlineButIncludingTrailingNewLineToMarkup()
+ {
+ ParseBlockTest("if(foo) {" + Environment.NewLine
+ + " var foo = \"After this statement there are 10 spaces\"; " + Environment.NewLine
+ + " " + Environment.NewLine
+ + " Foo" + Environment.NewLine
+ + " @bar" + Environment.NewLine
+ + "
" + Environment.NewLine
+ + " @:Hello!" + Environment.NewLine
+ + " var biz = boz;" + Environment.NewLine
+ + "}",
+ new StatementBlock(
+ Factory.Code(
+ $"if(foo) {{{Environment.NewLine} var foo = \"After this statement there are " +
+ "10 spaces\"; " + Environment.NewLine).AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ new MarkupTagBlock(
+ Factory.Markup("").Accepts(AcceptedCharacters.None)),
+ Factory.Markup($"{Environment.NewLine} Foo{Environment.NewLine}"),
+ new ExpressionBlock(
+ Factory.Code(" ").AsStatement(),
+ Factory.CodeTransition(),
+ Factory.Code("bar").AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharacters.NonWhiteSpace)
+ ),
+ Factory.Markup(Environment.NewLine + " "),
+ new MarkupTagBlock(
+ Factory.Markup("
").Accepts(AcceptedCharacters.None)),
+ Factory.Markup(Environment.NewLine).Accepts(AcceptedCharacters.None)
+ ),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ Factory.MarkupTransition(),
+ Factory.MetaMarkup(":", HtmlSymbolType.Colon),
+ Factory.Markup("Hello!" + Environment.NewLine).With(new SpanEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString, AcceptedCharacters.None))
+ ),
+ Factory.Code($" var biz = boz;{Environment.NewLine}}}").AsStatement()));
+ }
+
+ [Fact]
+ public void ParseBlockAllowsMarkupInIfBodyWithBraces()
+ {
+ ParseBlockTest("if(foo) { Bar
} else if(bar) { Baz
} else { Boz
}",
+ new StatementBlock(
+ Factory.Code("if(foo) {").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ new MarkupTagBlock(
+ Factory.Markup("").Accepts(AcceptedCharacters.None)),
+ Factory.Markup("Bar"),
+ new MarkupTagBlock(
+ Factory.Markup("
").Accepts(AcceptedCharacters.None)),
+ Factory.Markup(" ").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code("} else if(bar) {").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ new MarkupTagBlock(
+ Factory.Markup("").Accepts(AcceptedCharacters.None)),
+ Factory.Markup("Baz"),
+ new MarkupTagBlock(
+ Factory.Markup("
").Accepts(AcceptedCharacters.None)),
+ Factory.Markup(" ").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code("} else {").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ new MarkupTagBlock(
+ Factory.Markup("").Accepts(AcceptedCharacters.None)),
+ Factory.Markup("Boz"),
+ new MarkupTagBlock(
+ Factory.Markup("
").Accepts(AcceptedCharacters.None)),
+ Factory.Markup(" ").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code("}").AsStatement().Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockAllowsMarkupInIfBodyWithBracesWithinCodeBlock()
+ {
+ ParseBlockTest("{ if(foo) { Bar
} else if(bar) { Baz
} else { Boz
} }",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code(" if(foo) {")
+ .AsStatement()
+ .AutoCompleteWith(autoCompleteString: null),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ new MarkupTagBlock(
+ Factory.Markup("").Accepts(AcceptedCharacters.None)),
+ Factory.Markup("Bar"),
+ new MarkupTagBlock(
+ Factory.Markup("
").Accepts(AcceptedCharacters.None)),
+ Factory.Markup(" ").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code("} else if(bar) {").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ new MarkupTagBlock(
+ Factory.Markup("").Accepts(AcceptedCharacters.None)),
+ Factory.Markup("Baz"),
+ new MarkupTagBlock(
+ Factory.Markup("
").Accepts(AcceptedCharacters.None)),
+ Factory.Markup(" ").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code("} else {").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ new MarkupTagBlock(
+ Factory.Markup("").Accepts(AcceptedCharacters.None)),
+ Factory.Markup("Boz"),
+ new MarkupTagBlock(
+ Factory.Markup("
").Accepts(AcceptedCharacters.None)),
+ Factory.Markup(" ").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code("} ").AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockSupportsMarkupInCaseAndDefaultBranchesOfSwitch()
+ {
+ // Arrange
+ ParseBlockTest("switch(foo) {" + Environment.NewLine
+ + " case 0:" + Environment.NewLine
+ + " Foo
" + Environment.NewLine
+ + " break;" + Environment.NewLine
+ + " case 1:" + Environment.NewLine
+ + " Bar
" + Environment.NewLine
+ + " return;" + Environment.NewLine
+ + " case 2:" + Environment.NewLine
+ + " {" + Environment.NewLine
+ + " Baz
" + Environment.NewLine
+ + " Boz
" + Environment.NewLine
+ + " }" + Environment.NewLine
+ + " default:" + Environment.NewLine
+ + " Biz
" + Environment.NewLine
+ + "}",
+ new StatementBlock(
+ Factory.Code($"switch(foo) {{{Environment.NewLine} case 0:{Environment.NewLine}").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ new MarkupTagBlock(
+ Factory.Markup("").Accepts(AcceptedCharacters.None)),
+ Factory.Markup("Foo"),
+ new MarkupTagBlock(
+ Factory.Markup("
").Accepts(AcceptedCharacters.None)),
+ Factory.Markup(Environment.NewLine).Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code($" break;{Environment.NewLine} case 1:{Environment.NewLine}").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ new MarkupTagBlock(
+ Factory.Markup("").Accepts(AcceptedCharacters.None)),
+ Factory.Markup("Bar"),
+ new MarkupTagBlock(
+ Factory.Markup("
").Accepts(AcceptedCharacters.None)),
+ Factory.Markup(Environment.NewLine).Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code(
+ $" return;{Environment.NewLine} case 2:{Environment.NewLine}" +
+ " {" + Environment.NewLine).AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ new MarkupTagBlock(
+ Factory.Markup("").Accepts(AcceptedCharacters.None)),
+ Factory.Markup("Baz"),
+ new MarkupTagBlock(
+ Factory.Markup("
").Accepts(AcceptedCharacters.None)),
+ Factory.Markup(Environment.NewLine).Accepts(AcceptedCharacters.None)
+ ),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ new MarkupTagBlock(
+ Factory.Markup("").Accepts(AcceptedCharacters.None)),
+ Factory.Markup("Boz"),
+ new MarkupTagBlock(
+ Factory.Markup("
").Accepts(AcceptedCharacters.None)),
+ Factory.Markup(Environment.NewLine).Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code($" }}{Environment.NewLine} default:{Environment.NewLine}").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ new MarkupTagBlock(
+ Factory.Markup("").Accepts(AcceptedCharacters.None)),
+ Factory.Markup("Biz"),
+ new MarkupTagBlock(
+ Factory.Markup("
").Accepts(AcceptedCharacters.None)),
+ Factory.Markup(Environment.NewLine).Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code("}").AsStatement().Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlockSupportsMarkupInCaseAndDefaultBranchesOfSwitchInCodeBlock()
+ {
+ // Arrange
+ ParseBlockTest("{ switch(foo) {" + Environment.NewLine
+ + " case 0:" + Environment.NewLine
+ + " Foo
" + Environment.NewLine
+ + " break;" + Environment.NewLine
+ + " case 1:" + Environment.NewLine
+ + " Bar
" + Environment.NewLine
+ + " return;" + Environment.NewLine
+ + " case 2:" + Environment.NewLine
+ + " {" + Environment.NewLine
+ + " Baz
" + Environment.NewLine
+ + " Boz
" + Environment.NewLine
+ + " }" + Environment.NewLine
+ + " default:" + Environment.NewLine
+ + " Biz
" + Environment.NewLine
+ + "} }",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code($" switch(foo) {{{Environment.NewLine} case 0:{Environment.NewLine}")
+ .AsStatement()
+ .AutoCompleteWith(autoCompleteString: null),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ new MarkupTagBlock(
+ Factory.Markup("").Accepts(AcceptedCharacters.None)),
+ Factory.Markup("Foo"),
+ new MarkupTagBlock(
+ Factory.Markup("
").Accepts(AcceptedCharacters.None)),
+ Factory.Markup(Environment.NewLine).Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code($" break;{Environment.NewLine} case 1:{Environment.NewLine}").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ new MarkupTagBlock(
+ Factory.Markup("").Accepts(AcceptedCharacters.None)),
+ Factory.Markup("Bar"),
+ new MarkupTagBlock(
+ Factory.Markup("
").Accepts(AcceptedCharacters.None)),
+ Factory.Markup(Environment.NewLine).Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code(
+ $" return;{Environment.NewLine} case 2:{Environment.NewLine}" +
+ " {" + Environment.NewLine).AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ new MarkupTagBlock(
+ Factory.Markup("").Accepts(AcceptedCharacters.None)),
+ Factory.Markup("Baz"),
+ new MarkupTagBlock(
+ Factory.Markup("
").Accepts(AcceptedCharacters.None)),
+ Factory.Markup(Environment.NewLine).Accepts(AcceptedCharacters.None)
+ ),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ new MarkupTagBlock(
+ Factory.Markup("").Accepts(AcceptedCharacters.None)),
+ Factory.Markup("Boz"),
+ new MarkupTagBlock(
+ Factory.Markup("
").Accepts(AcceptedCharacters.None)),
+ Factory.Markup(Environment.NewLine).Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code($" }}{Environment.NewLine} default:{Environment.NewLine}").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ new MarkupTagBlock(
+ Factory.Markup("").Accepts(AcceptedCharacters.None)),
+ Factory.Markup("Biz"),
+ new MarkupTagBlock(
+ Factory.Markup("
").Accepts(AcceptedCharacters.None)),
+ Factory.Markup(Environment.NewLine).Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code("} ").AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlockParsesMarkupStatementOnOpenAngleBracket()
+ {
+ ParseBlockTest("for(int i = 0; i < 10; i++) { Foo
}",
+ new StatementBlock(
+ Factory.Code("for(int i = 0; i < 10; i++) {").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ new MarkupTagBlock(
+ Factory.Markup("").Accepts(AcceptedCharacters.None)),
+ Factory.Markup("Foo"),
+ new MarkupTagBlock(
+ Factory.Markup("
").Accepts(AcceptedCharacters.None)),
+ Factory.Markup(" ").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code("}").AsStatement().Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void ParseBlockParsesMarkupStatementOnOpenAngleBracketInCodeBlock()
+ {
+ ParseBlockTest("{ for(int i = 0; i < 10; i++) { Foo
} }",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code(" for(int i = 0; i < 10; i++) {")
+ .AsStatement()
+ .AutoCompleteWith(autoCompleteString: null),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ new MarkupTagBlock(
+ Factory.Markup("").Accepts(AcceptedCharacters.None)),
+ Factory.Markup("Foo"),
+ new MarkupTagBlock(
+ Factory.Markup("
").Accepts(AcceptedCharacters.None)),
+ Factory.Markup(" ").Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code("} ").AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlockParsesMarkupStatementOnSwitchCharacterFollowedByColon()
+ {
+ // Arrange
+ ParseBlockTest("if(foo) { @:Bar" + Environment.NewLine
+ + "} zoop",
+ new StatementBlock(
+ Factory.Code("if(foo) {").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ Factory.MarkupTransition(),
+ Factory.MetaMarkup(":", HtmlSymbolType.Colon),
+ Factory.Markup("Bar" + Environment.NewLine)
+ .With(new SpanEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString, AcceptedCharacters.None))
+ ),
+ Factory.Code("}").AsStatement()));
+ }
+
+ [Fact]
+ public void ParseBlockParsesMarkupStatementOnSwitchCharacterFollowedByDoubleColon()
+ {
+ // Arrange
+ ParseBlockTest("if(foo) { @::Sometext" + Environment.NewLine
+ + "}",
+ new StatementBlock(
+ Factory.Code("if(foo) {").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ Factory.MarkupTransition(),
+ Factory.MetaMarkup(":", HtmlSymbolType.Colon),
+ Factory.Markup(":Sometext" + Environment.NewLine)
+ .With(new SpanEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString, AcceptedCharacters.None))
+ ),
+ Factory.Code("}").AsStatement()));
+ }
+
+
+ [Fact]
+ public void ParseBlockParsesMarkupStatementOnSwitchCharacterFollowedByTripleColon()
+ {
+ // Arrange
+ ParseBlockTest("if(foo) { @:::Sometext" + Environment.NewLine
+ + "}",
+ new StatementBlock(
+ Factory.Code("if(foo) {").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ Factory.MarkupTransition(),
+ Factory.MetaMarkup(":", HtmlSymbolType.Colon),
+ Factory.Markup("::Sometext" + Environment.NewLine)
+ .With(new SpanEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString, AcceptedCharacters.None))
+ ),
+ Factory.Code("}").AsStatement()));
+ }
+
+ [Fact]
+ public void ParseBlockParsesMarkupStatementOnSwitchCharacterFollowedByColonInCodeBlock()
+ {
+ // Arrange
+ ParseBlockTest("{ if(foo) { @:Bar" + Environment.NewLine
+ + "} } zoop",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code(" if(foo) {")
+ .AsStatement()
+ .AutoCompleteWith(autoCompleteString: null),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ Factory.MarkupTransition(),
+ Factory.MetaMarkup(":", HtmlSymbolType.Colon),
+ Factory.Markup("Bar" + Environment.NewLine).Accepts(AcceptedCharacters.None)
+ .With(new SpanEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString, AcceptedCharacters.None))
+ ),
+ Factory.Code("} ").AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlockCorrectlyReturnsFromMarkupBlockWithPseudoTag()
+ {
+ ParseBlockTest("if (i > 0) { ; }",
+ new StatementBlock(
+ Factory.Code("if (i > 0) {").AsStatement(),
+ new MarkupBlock(
+ new MarkupTagBlock(
+ Factory.MarkupTransition("").Accepts(AcceptedCharacters.None)),
+ Factory.Markup(";").Accepts(AcceptedCharacters.None),
+ new MarkupTagBlock(
+ Factory.MarkupTransition("").Accepts(AcceptedCharacters.None))),
+ Factory.Code(" }").AsStatement()));
+ }
+
+ [Fact]
+ public void ParseBlockCorrectlyReturnsFromMarkupBlockWithPseudoTagInCodeBlock()
+ {
+ ParseBlockTest("{ if (i > 0) { ; } }",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code(" if (i > 0) {")
+ .AsStatement()
+ .AutoCompleteWith(autoCompleteString: null),
+ new MarkupBlock(
+ new MarkupTagBlock(
+ Factory.MarkupTransition("").Accepts(AcceptedCharacters.None)),
+ Factory.Markup(";").Accepts(AcceptedCharacters.None),
+ new MarkupTagBlock(
+ Factory.MarkupTransition("").Accepts(AcceptedCharacters.None))),
+ Factory.Code(" } ").AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void ParseBlockSupportsAllKindsOfImplicitMarkupInCodeBlock()
+ {
+ ParseBlockTest("{" + Environment.NewLine
+ + " if(true) {" + Environment.NewLine
+ + " @:Single Line Markup" + Environment.NewLine
+ + " }" + Environment.NewLine
+ + " foreach (var p in Enumerable.Range(1, 10)) {" + Environment.NewLine
+ + " The number is @p" + Environment.NewLine
+ + " }" + Environment.NewLine
+ + " if(!false) {" + Environment.NewLine
+ + " A real tag!
" + Environment.NewLine
+ + " }" + Environment.NewLine
+ + "}",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code($"{Environment.NewLine} if(true) {{{Environment.NewLine}")
+ .AsStatement()
+ .AutoCompleteWith(autoCompleteString: null),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ Factory.MarkupTransition(),
+ Factory.MetaMarkup(":", HtmlSymbolType.Colon),
+ Factory.Markup("Single Line Markup" + Environment.NewLine)
+ .With(new SpanEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString, AcceptedCharacters.None))
+ ),
+ Factory.Code($" }}{Environment.NewLine} foreach (var p in Enumerable.Range(1, 10)) {{{Environment.NewLine}").AsStatement(),
+ new MarkupBlock(
+ new MarkupTagBlock(
+ Factory.MarkupTransition("").Accepts(AcceptedCharacters.None)),
+ Factory.Markup("The number is ").Accepts(AcceptedCharacters.None),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("p").AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharacters.NonWhiteSpace)
+ ),
+ new MarkupTagBlock(
+ Factory.MarkupTransition("").Accepts(AcceptedCharacters.None))),
+ Factory.Code($"{Environment.NewLine} }}{Environment.NewLine} if(!false) {{{Environment.NewLine}").AsStatement(),
+ new MarkupBlock(
+ Factory.Markup(" "),
+ new MarkupTagBlock(
+ Factory.Markup("").Accepts(AcceptedCharacters.None)),
+ Factory.Markup("A real tag!"),
+ new MarkupTagBlock(
+ Factory.Markup("
").Accepts(AcceptedCharacters.None)),
+ Factory.Markup(Environment.NewLine).Accepts(AcceptedCharacters.None)
+ ),
+ Factory.Code(" }" + Environment.NewLine).AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)));
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpTokenizerCommentTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpTokenizerCommentTest.cs
new file mode 100644
index 0000000000..1b33fc7b69
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpTokenizerCommentTest.cs
@@ -0,0 +1,89 @@
+// 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 Xunit;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal class CSharpTokenizerCommentTest : CSharpTokenizerTestBase
+ {
+ [Fact]
+ public void Next_Ignores_Star_At_EOF_In_RazorComment()
+ {
+ TestTokenizer("@* Foo * Bar * Baz *",
+ new CSharpSymbol(0, 0, 0, "@", CSharpSymbolType.RazorCommentTransition),
+ new CSharpSymbol(1, 0, 1, "*", CSharpSymbolType.RazorCommentStar),
+ new CSharpSymbol(2, 0, 2, " Foo * Bar * Baz *", CSharpSymbolType.RazorComment));
+ }
+
+ [Fact]
+ public void Next_Ignores_Star_Without_Trailing_At()
+ {
+ TestTokenizer("@* Foo * Bar * Baz *@",
+ new CSharpSymbol(0, 0, 0, "@", CSharpSymbolType.RazorCommentTransition),
+ new CSharpSymbol(1, 0, 1, "*", CSharpSymbolType.RazorCommentStar),
+ new CSharpSymbol(2, 0, 2, " Foo * Bar * Baz ", CSharpSymbolType.RazorComment),
+ new CSharpSymbol(19, 0, 19, "*", CSharpSymbolType.RazorCommentStar),
+ new CSharpSymbol(20, 0, 20, "@", CSharpSymbolType.RazorCommentTransition));
+ }
+
+ [Fact]
+ public void Next_Returns_RazorComment_Token_For_Entire_Razor_Comment()
+ {
+ TestTokenizer("@* Foo Bar Baz *@",
+ new CSharpSymbol(0, 0, 0, "@", CSharpSymbolType.RazorCommentTransition),
+ new CSharpSymbol(1, 0, 1, "*", CSharpSymbolType.RazorCommentStar),
+ new CSharpSymbol(2, 0, 2, " Foo Bar Baz ", CSharpSymbolType.RazorComment),
+ new CSharpSymbol(15, 0, 15, "*", CSharpSymbolType.RazorCommentStar),
+ new CSharpSymbol(16, 0, 16, "@", CSharpSymbolType.RazorCommentTransition));
+ }
+
+ [Fact]
+ public void Next_Returns_Comment_Token_For_Entire_Single_Line_Comment()
+ {
+ TestTokenizer("// Foo Bar Baz", new CSharpSymbol(0, 0, 0, "// Foo Bar Baz", CSharpSymbolType.Comment));
+ }
+
+ [Fact]
+ public void Single_Line_Comment_Is_Terminated_By_Newline()
+ {
+ TestTokenizer("// Foo Bar Baz\na", new CSharpSymbol(0, 0, 0, "// Foo Bar Baz", CSharpSymbolType.Comment), IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Multi_Line_Comment_In_Single_Line_Comment_Has_No_Effect()
+ {
+ TestTokenizer("// Foo/*Bar*/ Baz\na", new CSharpSymbol(0, 0, 0, "// Foo/*Bar*/ Baz", CSharpSymbolType.Comment), IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Next_Returns_Comment_Token_For_Entire_Multi_Line_Comment()
+ {
+ TestTokenizer("/* Foo\nBar\nBaz */", new CSharpSymbol(0, 0, 0, "/* Foo\nBar\nBaz */", CSharpSymbolType.Comment));
+ }
+
+ [Fact]
+ public void Multi_Line_Comment_Is_Terminated_By_End_Sequence()
+ {
+ TestTokenizer("/* Foo\nBar\nBaz */a", new CSharpSymbol(0, 0, 0, "/* Foo\nBar\nBaz */", CSharpSymbolType.Comment), IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Unterminated_Multi_Line_Comment_Captures_To_EOF()
+ {
+ TestTokenizer("/* Foo\nBar\nBaz", new CSharpSymbol(0, 0, 0, "/* Foo\nBar\nBaz", CSharpSymbolType.Comment), IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Nested_Multi_Line_Comments_Terminated_At_First_End_Sequence()
+ {
+ TestTokenizer("/* Foo/*\nBar\nBaz*/ */", new CSharpSymbol(0, 0, 0, "/* Foo/*\nBar\nBaz*/", CSharpSymbolType.Comment), IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Nested_Multi_Line_Comments_Terminated_At_Full_End_Sequence()
+ {
+ TestTokenizer("/* Foo\nBar\nBaz* */", new CSharpSymbol(0, 0, 0, "/* Foo\nBar\nBaz* */", CSharpSymbolType.Comment), IgnoreRemaining);
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpTokenizerIdentifierTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpTokenizerIdentifierTest.cs
new file mode 100644
index 0000000000..8b2e2718da
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpTokenizerIdentifierTest.cs
@@ -0,0 +1,170 @@
+// 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 Xunit;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal class CSharpTokenizerIdentifierTest : CSharpTokenizerTestBase
+ {
+ [Fact]
+ public void Simple_Identifier_Is_Recognized()
+ {
+ TestTokenizer("foo", new CSharpSymbol(0, 0, 0, "foo", CSharpSymbolType.Identifier));
+ }
+
+ [Fact]
+ public void Identifier_Starting_With_Underscore_Is_Recognized()
+ {
+ TestTokenizer("_foo", new CSharpSymbol(0, 0, 0, "_foo", CSharpSymbolType.Identifier));
+ }
+
+ [Fact]
+ public void Identifier_Can_Contain_Digits()
+ {
+ TestTokenizer("foo4", new CSharpSymbol(0, 0, 0, "foo4", CSharpSymbolType.Identifier));
+ }
+
+ [Fact]
+ public void Identifier_Can_Start_With_Titlecase_Letter()
+ {
+ TestTokenizer("ῼfoo", new CSharpSymbol(0, 0, 0, "ῼfoo", CSharpSymbolType.Identifier));
+ }
+
+ [Fact]
+ public void Identifier_Can_Start_With_Letter_Modifier()
+ {
+ TestTokenizer("ᵊfoo", new CSharpSymbol(0, 0, 0, "ᵊfoo", CSharpSymbolType.Identifier));
+ }
+
+ [Fact]
+ public void Identifier_Can_Start_With_Other_Letter()
+ {
+ TestTokenizer("ƻfoo", new CSharpSymbol(0, 0, 0, "ƻfoo", CSharpSymbolType.Identifier));
+ }
+
+ [Fact]
+ public void Identifier_Can_Start_With_Number_Letter()
+ {
+ TestTokenizer("Ⅽool", new CSharpSymbol(0, 0, 0, "Ⅽool", CSharpSymbolType.Identifier));
+ }
+
+ [Fact]
+ public void Identifier_Can_Contain_Non_Spacing_Mark()
+ {
+ TestTokenizer("foo\u0300", new CSharpSymbol(0, 0, 0, "foo\u0300", CSharpSymbolType.Identifier));
+ }
+
+ [Fact]
+ public void Identifier_Can_Contain_Spacing_Combining_Mark()
+ {
+ TestTokenizer("fooः", new CSharpSymbol(0, 0, 0, "fooः", CSharpSymbolType.Identifier));
+ }
+
+ [Fact]
+ public void Identifier_Can_Contain_Non_English_Digit()
+ {
+ TestTokenizer("foo١", new CSharpSymbol(0, 0, 0, "foo١", CSharpSymbolType.Identifier));
+ }
+
+ [Fact]
+ public void Identifier_Can_Contain_Connector_Punctuation()
+ {
+ TestTokenizer("foo‿bar", new CSharpSymbol(0, 0, 0, "foo‿bar", CSharpSymbolType.Identifier));
+ }
+
+ [Fact]
+ public void Identifier_Can_Contain_Format_Character()
+ {
+ TestTokenizer("foobar", new CSharpSymbol(0, 0, 0, "foobar", CSharpSymbolType.Identifier));
+ }
+
+ [Fact]
+ public void Keywords_Are_Recognized_As_Keyword_Tokens()
+ {
+ TestKeyword("abstract", CSharpKeyword.Abstract);
+ TestKeyword("byte", CSharpKeyword.Byte);
+ TestKeyword("class", CSharpKeyword.Class);
+ TestKeyword("delegate", CSharpKeyword.Delegate);
+ TestKeyword("event", CSharpKeyword.Event);
+ TestKeyword("fixed", CSharpKeyword.Fixed);
+ TestKeyword("if", CSharpKeyword.If);
+ TestKeyword("internal", CSharpKeyword.Internal);
+ TestKeyword("new", CSharpKeyword.New);
+ TestKeyword("override", CSharpKeyword.Override);
+ TestKeyword("readonly", CSharpKeyword.Readonly);
+ TestKeyword("short", CSharpKeyword.Short);
+ TestKeyword("struct", CSharpKeyword.Struct);
+ TestKeyword("try", CSharpKeyword.Try);
+ TestKeyword("unsafe", CSharpKeyword.Unsafe);
+ TestKeyword("volatile", CSharpKeyword.Volatile);
+ TestKeyword("as", CSharpKeyword.As);
+ TestKeyword("do", CSharpKeyword.Do);
+ TestKeyword("is", CSharpKeyword.Is);
+ TestKeyword("params", CSharpKeyword.Params);
+ TestKeyword("ref", CSharpKeyword.Ref);
+ TestKeyword("switch", CSharpKeyword.Switch);
+ TestKeyword("ushort", CSharpKeyword.Ushort);
+ TestKeyword("while", CSharpKeyword.While);
+ TestKeyword("case", CSharpKeyword.Case);
+ TestKeyword("const", CSharpKeyword.Const);
+ TestKeyword("explicit", CSharpKeyword.Explicit);
+ TestKeyword("float", CSharpKeyword.Float);
+ TestKeyword("null", CSharpKeyword.Null);
+ TestKeyword("sizeof", CSharpKeyword.Sizeof);
+ TestKeyword("typeof", CSharpKeyword.Typeof);
+ TestKeyword("implicit", CSharpKeyword.Implicit);
+ TestKeyword("private", CSharpKeyword.Private);
+ TestKeyword("this", CSharpKeyword.This);
+ TestKeyword("using", CSharpKeyword.Using);
+ TestKeyword("extern", CSharpKeyword.Extern);
+ TestKeyword("return", CSharpKeyword.Return);
+ TestKeyword("stackalloc", CSharpKeyword.Stackalloc);
+ TestKeyword("uint", CSharpKeyword.Uint);
+ TestKeyword("base", CSharpKeyword.Base);
+ TestKeyword("catch", CSharpKeyword.Catch);
+ TestKeyword("continue", CSharpKeyword.Continue);
+ TestKeyword("double", CSharpKeyword.Double);
+ TestKeyword("for", CSharpKeyword.For);
+ TestKeyword("in", CSharpKeyword.In);
+ TestKeyword("lock", CSharpKeyword.Lock);
+ TestKeyword("object", CSharpKeyword.Object);
+ TestKeyword("protected", CSharpKeyword.Protected);
+ TestKeyword("static", CSharpKeyword.Static);
+ TestKeyword("false", CSharpKeyword.False);
+ TestKeyword("public", CSharpKeyword.Public);
+ TestKeyword("sbyte", CSharpKeyword.Sbyte);
+ TestKeyword("throw", CSharpKeyword.Throw);
+ TestKeyword("virtual", CSharpKeyword.Virtual);
+ TestKeyword("decimal", CSharpKeyword.Decimal);
+ TestKeyword("else", CSharpKeyword.Else);
+ TestKeyword("operator", CSharpKeyword.Operator);
+ TestKeyword("string", CSharpKeyword.String);
+ TestKeyword("ulong", CSharpKeyword.Ulong);
+ TestKeyword("bool", CSharpKeyword.Bool);
+ TestKeyword("char", CSharpKeyword.Char);
+ TestKeyword("default", CSharpKeyword.Default);
+ TestKeyword("foreach", CSharpKeyword.Foreach);
+ TestKeyword("long", CSharpKeyword.Long);
+ TestKeyword("void", CSharpKeyword.Void);
+ TestKeyword("enum", CSharpKeyword.Enum);
+ TestKeyword("finally", CSharpKeyword.Finally);
+ TestKeyword("int", CSharpKeyword.Int);
+ TestKeyword("out", CSharpKeyword.Out);
+ TestKeyword("sealed", CSharpKeyword.Sealed);
+ TestKeyword("true", CSharpKeyword.True);
+ TestKeyword("goto", CSharpKeyword.Goto);
+ TestKeyword("unchecked", CSharpKeyword.Unchecked);
+ TestKeyword("interface", CSharpKeyword.Interface);
+ TestKeyword("break", CSharpKeyword.Break);
+ TestKeyword("checked", CSharpKeyword.Checked);
+ TestKeyword("namespace", CSharpKeyword.Namespace);
+ TestKeyword("when", CSharpKeyword.When);
+ }
+
+ private void TestKeyword(string keyword, CSharpKeyword keywordType)
+ {
+ TestTokenizer(keyword, new CSharpSymbol(0, 0, 0, keyword, CSharpSymbolType.Keyword) { Keyword = keywordType });
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpTokenizerLiteralTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpTokenizerLiteralTest.cs
new file mode 100644
index 0000000000..7565fad137
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpTokenizerLiteralTest.cs
@@ -0,0 +1,285 @@
+// 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 Xunit;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal class CSharpTokenizerLiteralTest : CSharpTokenizerTestBase
+ {
+ [Fact]
+ public void Simple_Integer_Literal_Is_Recognized()
+ {
+ TestSingleToken("01189998819991197253", CSharpSymbolType.IntegerLiteral);
+ }
+
+ [Fact]
+ public void Integer_Type_Suffix_Is_Recognized()
+ {
+ TestSingleToken("42U", CSharpSymbolType.IntegerLiteral);
+ TestSingleToken("42u", CSharpSymbolType.IntegerLiteral);
+
+ TestSingleToken("42L", CSharpSymbolType.IntegerLiteral);
+ TestSingleToken("42l", CSharpSymbolType.IntegerLiteral);
+
+ TestSingleToken("42UL", CSharpSymbolType.IntegerLiteral);
+ TestSingleToken("42Ul", CSharpSymbolType.IntegerLiteral);
+
+ TestSingleToken("42uL", CSharpSymbolType.IntegerLiteral);
+ TestSingleToken("42ul", CSharpSymbolType.IntegerLiteral);
+
+ TestSingleToken("42LU", CSharpSymbolType.IntegerLiteral);
+ TestSingleToken("42Lu", CSharpSymbolType.IntegerLiteral);
+
+ TestSingleToken("42lU", CSharpSymbolType.IntegerLiteral);
+ TestSingleToken("42lu", CSharpSymbolType.IntegerLiteral);
+ }
+
+ [Fact]
+ public void Trailing_Letter_Is_Not_Part_Of_Integer_Literal_If_Not_Type_Sufix()
+ {
+ TestTokenizer("42a", new CSharpSymbol(0, 0, 0, "42", CSharpSymbolType.IntegerLiteral), IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Simple_Hex_Literal_Is_Recognized()
+ {
+ TestSingleToken("0x0123456789ABCDEF", CSharpSymbolType.IntegerLiteral);
+ }
+
+ [Fact]
+ public void Integer_Type_Suffix_Is_Recognized_In_Hex_Literal()
+ {
+ TestSingleToken("0xDEADBEEFU", CSharpSymbolType.IntegerLiteral);
+ TestSingleToken("0xDEADBEEFu", CSharpSymbolType.IntegerLiteral);
+
+ TestSingleToken("0xDEADBEEFL", CSharpSymbolType.IntegerLiteral);
+ TestSingleToken("0xDEADBEEFl", CSharpSymbolType.IntegerLiteral);
+
+ TestSingleToken("0xDEADBEEFUL", CSharpSymbolType.IntegerLiteral);
+ TestSingleToken("0xDEADBEEFUl", CSharpSymbolType.IntegerLiteral);
+
+ TestSingleToken("0xDEADBEEFuL", CSharpSymbolType.IntegerLiteral);
+ TestSingleToken("0xDEADBEEFul", CSharpSymbolType.IntegerLiteral);
+
+ TestSingleToken("0xDEADBEEFLU", CSharpSymbolType.IntegerLiteral);
+ TestSingleToken("0xDEADBEEFLu", CSharpSymbolType.IntegerLiteral);
+
+ TestSingleToken("0xDEADBEEFlU", CSharpSymbolType.IntegerLiteral);
+ TestSingleToken("0xDEADBEEFlu", CSharpSymbolType.IntegerLiteral);
+ }
+
+ [Fact]
+ public void Trailing_Letter_Is_Not_Part_Of_Hex_Literal_If_Not_Type_Sufix()
+ {
+ TestTokenizer("0xDEADBEEFz", new CSharpSymbol(0, 0, 0, "0xDEADBEEF", CSharpSymbolType.IntegerLiteral), IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Dot_Followed_By_Non_Digit_Is_Not_Part_Of_Real_Literal()
+ {
+ TestTokenizer("3.a", new CSharpSymbol(0, 0, 0, "3", CSharpSymbolType.IntegerLiteral), IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Simple_Real_Literal_Is_Recognized()
+ {
+ TestTokenizer("3.14159", new CSharpSymbol(0, 0, 0, "3.14159", CSharpSymbolType.RealLiteral));
+ }
+
+ [Fact]
+ public void Real_Literal_Between_Zero_And_One_Is_Recognized()
+ {
+ TestTokenizer(".14159", new CSharpSymbol(0, 0, 0, ".14159", CSharpSymbolType.RealLiteral));
+ }
+
+ [Fact]
+ public void Integer_With_Real_Type_Suffix_Is_Recognized()
+ {
+ TestSingleToken("42F", CSharpSymbolType.RealLiteral);
+ TestSingleToken("42f", CSharpSymbolType.RealLiteral);
+ TestSingleToken("42D", CSharpSymbolType.RealLiteral);
+ TestSingleToken("42d", CSharpSymbolType.RealLiteral);
+ TestSingleToken("42M", CSharpSymbolType.RealLiteral);
+ TestSingleToken("42m", CSharpSymbolType.RealLiteral);
+ }
+
+ [Fact]
+ public void Integer_With_Exponent_Is_Recognized()
+ {
+ TestSingleToken("1e10", CSharpSymbolType.RealLiteral);
+ TestSingleToken("1E10", CSharpSymbolType.RealLiteral);
+ TestSingleToken("1e+10", CSharpSymbolType.RealLiteral);
+ TestSingleToken("1E+10", CSharpSymbolType.RealLiteral);
+ TestSingleToken("1e-10", CSharpSymbolType.RealLiteral);
+ TestSingleToken("1E-10", CSharpSymbolType.RealLiteral);
+ }
+
+ [Fact]
+ public void Real_Number_With_Type_Suffix_Is_Recognized()
+ {
+ TestSingleToken("3.14F", CSharpSymbolType.RealLiteral);
+ TestSingleToken("3.14f", CSharpSymbolType.RealLiteral);
+ TestSingleToken("3.14D", CSharpSymbolType.RealLiteral);
+ TestSingleToken("3.14d", CSharpSymbolType.RealLiteral);
+ TestSingleToken("3.14M", CSharpSymbolType.RealLiteral);
+ TestSingleToken("3.14m", CSharpSymbolType.RealLiteral);
+ }
+
+ [Fact]
+ public void Real_Number_With_Exponent_Is_Recognized()
+ {
+ TestSingleToken("3.14E10", CSharpSymbolType.RealLiteral);
+ TestSingleToken("3.14e10", CSharpSymbolType.RealLiteral);
+ TestSingleToken("3.14E+10", CSharpSymbolType.RealLiteral);
+ TestSingleToken("3.14e+10", CSharpSymbolType.RealLiteral);
+ TestSingleToken("3.14E-10", CSharpSymbolType.RealLiteral);
+ TestSingleToken("3.14e-10", CSharpSymbolType.RealLiteral);
+ }
+
+ [Fact]
+ public void Real_Number_With_Exponent_And_Type_Suffix_Is_Recognized()
+ {
+ TestSingleToken("3.14E+10F", CSharpSymbolType.RealLiteral);
+ }
+
+ [Fact]
+ public void Single_Character_Literal_Is_Recognized()
+ {
+ TestSingleToken("'f'", CSharpSymbolType.CharacterLiteral);
+ }
+
+ [Fact]
+ public void Multi_Character_Literal_Is_Recognized()
+ {
+ TestSingleToken("'foo'", CSharpSymbolType.CharacterLiteral);
+ }
+
+ [Fact]
+ public void Character_Literal_Is_Terminated_By_EOF_If_Unterminated()
+ {
+ TestSingleToken("'foo bar", CSharpSymbolType.CharacterLiteral);
+ }
+
+ [Fact]
+ public void Character_Literal_Not_Terminated_By_Escaped_Quote()
+ {
+ TestSingleToken("'foo\\'bar'", CSharpSymbolType.CharacterLiteral);
+ }
+
+ [Fact]
+ public void Character_Literal_Is_Terminated_By_EOL_If_Unterminated()
+ {
+ TestTokenizer("'foo\n", new CSharpSymbol(0, 0, 0, "'foo", CSharpSymbolType.CharacterLiteral), IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Character_Literal_Terminated_By_EOL_Even_When_Last_Char_Is_Slash()
+ {
+ TestTokenizer("'foo\\\n", new CSharpSymbol(0, 0, 0, "'foo\\", CSharpSymbolType.CharacterLiteral), IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Character_Literal_Terminated_By_EOL_Even_When_Last_Char_Is_Slash_And_Followed_By_Stuff()
+ {
+ TestTokenizer("'foo\\\nflarg", new CSharpSymbol(0, 0, 0, "'foo\\", CSharpSymbolType.CharacterLiteral), IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Character_Literal_Terminated_By_CRLF_Even_When_Last_Char_Is_Slash()
+ {
+ TestTokenizer("'foo\\" + Environment.NewLine, new CSharpSymbol(0, 0, 0, "'foo\\", CSharpSymbolType.CharacterLiteral), IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Character_Literal_Terminated_By_CRLF_Even_When_Last_Char_Is_Slash_And_Followed_By_Stuff()
+ {
+ TestTokenizer($"'foo\\{Environment.NewLine}flarg", new CSharpSymbol(0, 0, 0, "'foo\\", CSharpSymbolType.CharacterLiteral), IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Character_Literal_Allows_Escaped_Escape()
+ {
+ TestTokenizer("'foo\\\\'blah", new CSharpSymbol(0, 0, 0, "'foo\\\\'", CSharpSymbolType.CharacterLiteral), IgnoreRemaining);
+ }
+
+ [Fact]
+ public void String_Literal_Is_Recognized()
+ {
+ TestSingleToken("\"foo\"", CSharpSymbolType.StringLiteral);
+ }
+
+ [Fact]
+ public void String_Literal_Is_Terminated_By_EOF_If_Unterminated()
+ {
+ TestSingleToken("\"foo bar", CSharpSymbolType.StringLiteral);
+ }
+
+ [Fact]
+ public void String_Literal_Not_Terminated_By_Escaped_Quote()
+ {
+ TestSingleToken("\"foo\\\"bar\"", CSharpSymbolType.StringLiteral);
+ }
+
+ [Fact]
+ public void String_Literal_Is_Terminated_By_EOL_If_Unterminated()
+ {
+ TestTokenizer("\"foo\n", new CSharpSymbol(0, 0, 0, "\"foo", CSharpSymbolType.StringLiteral), IgnoreRemaining);
+ }
+
+ [Fact]
+ public void String_Literal_Terminated_By_EOL_Even_When_Last_Char_Is_Slash()
+ {
+ TestTokenizer("\"foo\\\n", new CSharpSymbol(0, 0, 0, "\"foo\\", CSharpSymbolType.StringLiteral), IgnoreRemaining);
+ }
+
+ [Fact]
+ public void String_Literal_Terminated_By_EOL_Even_When_Last_Char_Is_Slash_And_Followed_By_Stuff()
+ {
+ TestTokenizer("\"foo\\\nflarg", new CSharpSymbol(0, 0, 0, "\"foo\\", CSharpSymbolType.StringLiteral), IgnoreRemaining);
+ }
+
+ [Fact]
+ public void String_Literal_Terminated_By_CRLF_Even_When_Last_Char_Is_Slash()
+ {
+ TestTokenizer("\"foo\\" + Environment.NewLine, new CSharpSymbol(0, 0, 0, "\"foo\\", CSharpSymbolType.StringLiteral), IgnoreRemaining);
+ }
+
+ [Fact]
+ public void String_Literal_Terminated_By_CRLF_Even_When_Last_Char_Is_Slash_And_Followed_By_Stuff()
+ {
+ TestTokenizer($"\"foo\\{Environment.NewLine}flarg", new CSharpSymbol(0, 0, 0, "\"foo\\", CSharpSymbolType.StringLiteral), IgnoreRemaining);
+ }
+
+ [Fact]
+ public void String_Literal_Allows_Escaped_Escape()
+ {
+ TestTokenizer("\"foo\\\\\"blah", new CSharpSymbol(0, 0, 0, "\"foo\\\\\"", CSharpSymbolType.StringLiteral), IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Verbatim_String_Literal_Can_Contain_Newlines()
+ {
+ TestSingleToken("@\"foo\nbar\nbaz\"", CSharpSymbolType.StringLiteral);
+ }
+
+ [Fact]
+ public void Verbatim_String_Literal_Not_Terminated_By_Escaped_Double_Quote()
+ {
+ TestSingleToken("@\"foo\"\"bar\"", CSharpSymbolType.StringLiteral);
+ }
+
+ [Fact]
+ public void Verbatim_String_Literal_Is_Terminated_By_Slash_Double_Quote()
+ {
+ TestTokenizer("@\"foo\\\"bar\"", new CSharpSymbol(0, 0, 0, "@\"foo\\\"", CSharpSymbolType.StringLiteral), IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Verbatim_String_Literal_Is_Terminated_By_EOF()
+ {
+ TestSingleToken("@\"foo", CSharpSymbolType.StringLiteral);
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpTokenizerOperatorsTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpTokenizerOperatorsTest.cs
new file mode 100644
index 0000000000..468aae09b3
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpTokenizerOperatorsTest.cs
@@ -0,0 +1,296 @@
+// 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 Xunit;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal class CSharpTokenizerOperatorsTest : CSharpTokenizerTestBase
+ {
+ [Fact]
+ public void LeftBrace_Is_Recognized()
+ {
+ TestSingleToken("{", CSharpSymbolType.LeftBrace);
+ }
+
+ [Fact]
+ public void Plus_Is_Recognized()
+ {
+ TestSingleToken("+", CSharpSymbolType.Plus);
+ }
+
+ [Fact]
+ public void Assign_Is_Recognized()
+ {
+ TestSingleToken("=", CSharpSymbolType.Assign);
+ }
+
+ [Fact]
+ public void Arrow_Is_Recognized()
+ {
+ TestSingleToken("->", CSharpSymbolType.Arrow);
+ }
+
+ [Fact]
+ public void AndAssign_Is_Recognized()
+ {
+ TestSingleToken("&=", CSharpSymbolType.AndAssign);
+ }
+
+ [Fact]
+ public void RightBrace_Is_Recognized()
+ {
+ TestSingleToken("}", CSharpSymbolType.RightBrace);
+ }
+
+ [Fact]
+ public void Minus_Is_Recognized()
+ {
+ TestSingleToken("-", CSharpSymbolType.Minus);
+ }
+
+ [Fact]
+ public void LessThan_Is_Recognized()
+ {
+ TestSingleToken("<", CSharpSymbolType.LessThan);
+ }
+
+ [Fact]
+ public void Equals_Is_Recognized()
+ {
+ TestSingleToken("==", CSharpSymbolType.Equals);
+ }
+
+ [Fact]
+ public void OrAssign_Is_Recognized()
+ {
+ TestSingleToken("|=", CSharpSymbolType.OrAssign);
+ }
+
+ [Fact]
+ public void LeftBracket_Is_Recognized()
+ {
+ TestSingleToken("[", CSharpSymbolType.LeftBracket);
+ }
+
+ [Fact]
+ public void Star_Is_Recognized()
+ {
+ TestSingleToken("*", CSharpSymbolType.Star);
+ }
+
+ [Fact]
+ public void GreaterThan_Is_Recognized()
+ {
+ TestSingleToken(">", CSharpSymbolType.GreaterThan);
+ }
+
+ [Fact]
+ public void NotEqual_Is_Recognized()
+ {
+ TestSingleToken("!=", CSharpSymbolType.NotEqual);
+ }
+
+ [Fact]
+ public void XorAssign_Is_Recognized()
+ {
+ TestSingleToken("^=", CSharpSymbolType.XorAssign);
+ }
+
+ [Fact]
+ public void RightBracket_Is_Recognized()
+ {
+ TestSingleToken("]", CSharpSymbolType.RightBracket);
+ }
+
+ [Fact]
+ public void Slash_Is_Recognized()
+ {
+ TestSingleToken("/", CSharpSymbolType.Slash);
+ }
+
+ [Fact]
+ public void QuestionMark_Is_Recognized()
+ {
+ TestSingleToken("?", CSharpSymbolType.QuestionMark);
+ }
+
+ [Fact]
+ public void LessThanEqual_Is_Recognized()
+ {
+ TestSingleToken("<=", CSharpSymbolType.LessThanEqual);
+ }
+
+ [Fact]
+ public void LeftShift_Is_Not_Specially_Recognized()
+ {
+ TestTokenizer("<<",
+ new CSharpSymbol(new SourceLocation(0, 0, 0), "<", CSharpSymbolType.LessThan),
+ new CSharpSymbol(new SourceLocation(1, 0, 1), "<", CSharpSymbolType.LessThan));
+ }
+
+ [Fact]
+ public void LeftParen_Is_Recognized()
+ {
+ TestSingleToken("(", CSharpSymbolType.LeftParenthesis);
+ }
+
+ [Fact]
+ public void Modulo_Is_Recognized()
+ {
+ TestSingleToken("%", CSharpSymbolType.Modulo);
+ }
+
+ [Fact]
+ public void NullCoalesce_Is_Recognized()
+ {
+ TestSingleToken("??", CSharpSymbolType.NullCoalesce);
+ }
+
+ [Fact]
+ public void GreaterThanEqual_Is_Recognized()
+ {
+ TestSingleToken(">=", CSharpSymbolType.GreaterThanEqual);
+ }
+
+ [Fact]
+ public void EqualGreaterThan_Is_Recognized()
+ {
+ TestSingleToken("=>", CSharpSymbolType.GreaterThanEqual);
+ }
+
+ [Fact]
+ public void RightParen_Is_Recognized()
+ {
+ TestSingleToken(")", CSharpSymbolType.RightParenthesis);
+ }
+
+ [Fact]
+ public void And_Is_Recognized()
+ {
+ TestSingleToken("&", CSharpSymbolType.And);
+ }
+
+ [Fact]
+ public void DoubleColon_Is_Recognized()
+ {
+ TestSingleToken("::", CSharpSymbolType.DoubleColon);
+ }
+
+ [Fact]
+ public void PlusAssign_Is_Recognized()
+ {
+ TestSingleToken("+=", CSharpSymbolType.PlusAssign);
+ }
+
+ [Fact]
+ public void Semicolon_Is_Recognized()
+ {
+ TestSingleToken(";", CSharpSymbolType.Semicolon);
+ }
+
+ [Fact]
+ public void Tilde_Is_Recognized()
+ {
+ TestSingleToken("~", CSharpSymbolType.Tilde);
+ }
+
+ [Fact]
+ public void DoubleOr_Is_Recognized()
+ {
+ TestSingleToken("||", CSharpSymbolType.DoubleOr);
+ }
+
+ [Fact]
+ public void ModuloAssign_Is_Recognized()
+ {
+ TestSingleToken("%=", CSharpSymbolType.ModuloAssign);
+ }
+
+ [Fact]
+ public void Colon_Is_Recognized()
+ {
+ TestSingleToken(":", CSharpSymbolType.Colon);
+ }
+
+ [Fact]
+ public void Not_Is_Recognized()
+ {
+ TestSingleToken("!", CSharpSymbolType.Not);
+ }
+
+ [Fact]
+ public void DoubleAnd_Is_Recognized()
+ {
+ TestSingleToken("&&", CSharpSymbolType.DoubleAnd);
+ }
+
+ [Fact]
+ public void DivideAssign_Is_Recognized()
+ {
+ TestSingleToken("/=", CSharpSymbolType.DivideAssign);
+ }
+
+ [Fact]
+ public void Comma_Is_Recognized()
+ {
+ TestSingleToken(",", CSharpSymbolType.Comma);
+ }
+
+ [Fact]
+ public void Xor_Is_Recognized()
+ {
+ TestSingleToken("^", CSharpSymbolType.Xor);
+ }
+
+ [Fact]
+ public void Decrement_Is_Recognized()
+ {
+ TestSingleToken("--", CSharpSymbolType.Decrement);
+ }
+
+ [Fact]
+ public void MultiplyAssign_Is_Recognized()
+ {
+ TestSingleToken("*=", CSharpSymbolType.MultiplyAssign);
+ }
+
+ [Fact]
+ public void Dot_Is_Recognized()
+ {
+ TestSingleToken(".", CSharpSymbolType.Dot);
+ }
+
+ [Fact]
+ public void Or_Is_Recognized()
+ {
+ TestSingleToken("|", CSharpSymbolType.Or);
+ }
+
+ [Fact]
+ public void Increment_Is_Recognized()
+ {
+ TestSingleToken("++", CSharpSymbolType.Increment);
+ }
+
+ [Fact]
+ public void MinusAssign_Is_Recognized()
+ {
+ TestSingleToken("-=", CSharpSymbolType.MinusAssign);
+ }
+
+ [Fact]
+ public void RightShift_Is_Not_Specially_Recognized()
+ {
+ TestTokenizer(">>",
+ new CSharpSymbol(0, 0, 0, ">", CSharpSymbolType.GreaterThan),
+ new CSharpSymbol(1, 0, 1, ">", CSharpSymbolType.GreaterThan));
+ }
+
+ [Fact]
+ public void Hash_Is_Recognized()
+ {
+ TestSingleToken("#", CSharpSymbolType.Hash);
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpTokenizerTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpTokenizerTest.cs
new file mode 100644
index 0000000000..e5e09daddf
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpTokenizerTest.cs
@@ -0,0 +1,96 @@
+// 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 Xunit;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal class CSharpTokenizerTest : CSharpTokenizerTestBase
+ {
+ [Fact]
+ public void Next_Returns_Null_When_EOF_Reached()
+ {
+ TestTokenizer("");
+ }
+
+ [Fact]
+ public void Next_Returns_Newline_Token_For_Single_CR()
+ {
+ TestTokenizer("\r\ra",
+ new CSharpSymbol(0, 0, 0, "\r", CSharpSymbolType.NewLine),
+ new CSharpSymbol(1, 1, 0, "\r", CSharpSymbolType.NewLine),
+ IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Next_Returns_Newline_Token_For_Single_LF()
+ {
+ TestTokenizer("\n\na",
+ new CSharpSymbol(0, 0, 0, "\n", CSharpSymbolType.NewLine),
+ new CSharpSymbol(1, 1, 0, "\n", CSharpSymbolType.NewLine),
+ IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Next_Returns_Newline_Token_For_Single_NEL()
+ {
+ // NEL: Unicode "Next Line" U+0085
+ TestTokenizer("\u0085\u0085a",
+ new CSharpSymbol(0, 0, 0, "\u0085", CSharpSymbolType.NewLine),
+ new CSharpSymbol(1, 1, 0, "\u0085", CSharpSymbolType.NewLine),
+ IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Next_Returns_Newline_Token_For_Single_Line_Separator()
+ {
+ // Unicode "Line Separator" U+2028
+ TestTokenizer("\u2028\u2028a",
+ new CSharpSymbol(0, 0, 0, "\u2028", CSharpSymbolType.NewLine),
+ new CSharpSymbol(1, 1, 0, "\u2028", CSharpSymbolType.NewLine),
+ IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Next_Returns_Newline_Token_For_Single_Paragraph_Separator()
+ {
+ // Unicode "Paragraph Separator" U+2029
+ TestTokenizer("\u2029\u2029a",
+ new CSharpSymbol(0, 0, 0, "\u2029", CSharpSymbolType.NewLine),
+ new CSharpSymbol(1, 1, 0, "\u2029", CSharpSymbolType.NewLine),
+ IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Next_Returns_Single_Newline_Token_For_CRLF()
+ {
+ TestTokenizer("\r\n\r\na",
+ new CSharpSymbol(0, 0, 0, "\r\n", CSharpSymbolType.NewLine),
+ new CSharpSymbol(2, 1, 0, "\r\n", CSharpSymbolType.NewLine),
+ IgnoreRemaining);
+ }
+
+ [Fact]
+ public void Next_Returns_Token_For_Whitespace_Characters()
+ {
+ TestTokenizer(" \f\t\u000B \n ",
+ new CSharpSymbol(0, 0, 0, " \f\t\u000B ", CSharpSymbolType.WhiteSpace),
+ new CSharpSymbol(5, 0, 5, "\n", CSharpSymbolType.NewLine),
+ new CSharpSymbol(6, 1, 0, " ", CSharpSymbolType.WhiteSpace));
+ }
+
+ [Fact]
+ public void Transition_Is_Recognized()
+ {
+ TestSingleToken("@", CSharpSymbolType.Transition);
+ }
+
+ [Fact]
+ public void Transition_Is_Recognized_As_SingleCharacter()
+ {
+ TestTokenizer("@(",
+ new CSharpSymbol(0, 0, 0, "@", CSharpSymbolType.Transition),
+ new CSharpSymbol(1, 0, 1, "(", CSharpSymbolType.LeftParenthesis));
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpTokenizerTestBase.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpTokenizerTestBase.cs
new file mode 100644
index 0000000000..7c7743dbcc
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpTokenizerTestBase.cs
@@ -0,0 +1,25 @@
+// 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.
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal abstract class CSharpTokenizerTestBase : TokenizerTestBase
+ {
+ private static CSharpSymbol _ignoreRemaining = new CSharpSymbol(0, 0, 0, string.Empty, CSharpSymbolType.Unknown);
+
+ protected override CSharpSymbol IgnoreRemaining
+ {
+ get { return _ignoreRemaining; }
+ }
+
+ protected override Tokenizer CreateTokenizer(ITextDocument source)
+ {
+ return new CSharpTokenizer(source);
+ }
+
+ protected void TestSingleToken(string text, CSharpSymbolType expectedSymbolType)
+ {
+ TestTokenizer(text, new CSharpSymbol(0, 0, 0, text, expectedSymbolType));
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpVerbatimBlockTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpVerbatimBlockTest.cs
new file mode 100644
index 0000000000..62d363a51f
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpVerbatimBlockTest.cs
@@ -0,0 +1,138 @@
+// 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 Xunit;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal class CSharpVerbatimBlockTest : CsHtmlCodeParserTestBase
+ {
+ private const string TestExtraKeyword = "model";
+
+ [Fact]
+ public void VerbatimBlock()
+ {
+ ParseBlockTest("@{ foo(); }",
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("{")
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code(" foo(); ")
+ .AsStatement()
+ .AutoCompleteWith(autoCompleteString: null),
+ Factory.MetaCode("}")
+ .Accepts(AcceptedCharacters.None)
+ ));
+ }
+
+ [Fact]
+ public void InnerImplicitExpressionWithOnlySingleAtOutputsZeroLengthCodeSpan()
+ {
+ ParseBlockTest("{@}",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.EmptyCSharp()
+ .AsStatement()
+ .AutoCompleteWith(autoCompleteString: null),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.EmptyCSharp().AsImplicitExpression(KeywordSet, acceptTrailingDot: true).Accepts(AcceptedCharacters.NonWhiteSpace)
+ ),
+ Factory.EmptyCSharp().AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
+ designTime: true,
+ expectedErrors: new[]
+ {
+ new RazorError(
+ LegacyResources.FormatParseError_Unexpected_Character_At_Start_Of_CodeBlock_CS("}"),
+ new SourceLocation(2, 0, 2),
+ length: 1)
+ });
+ }
+
+ [Fact]
+ public void InnerImplicitExpressionDoesNotAcceptDotAfterAt()
+ {
+ ParseBlockTest("{@.}",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.EmptyCSharp()
+ .AsStatement()
+ .AutoCompleteWith(autoCompleteString: null),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.EmptyCSharp().AsImplicitExpression(KeywordSet, acceptTrailingDot: true).Accepts(AcceptedCharacters.NonWhiteSpace)
+ ),
+ Factory.Code(".").AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
+ designTime: true,
+ expectedErrors: new[]
+ {
+ new RazorError(
+ LegacyResources.FormatParseError_Unexpected_Character_At_Start_Of_CodeBlock_CS("."),
+ new SourceLocation(2, 0, 2),
+ length: 1)
+ });
+ }
+
+ [Fact]
+ public void InnerImplicitExpressionWithOnlySingleAtAcceptsSingleSpaceOrNewlineAtDesignTime()
+ {
+ ParseBlockTest("{" + Environment.NewLine
+ + " @" + Environment.NewLine
+ + "}",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code(Environment.NewLine + " ")
+ .AsStatement()
+ .AutoCompleteWith(autoCompleteString: null),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.EmptyCSharp().AsImplicitExpression(KeywordSet, acceptTrailingDot: true).Accepts(AcceptedCharacters.NonWhiteSpace)
+ ),
+ Factory.Code(Environment.NewLine).AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
+ /* designTimeParser */ true,
+ new RazorError(
+ LegacyResources.ParseError_Unexpected_WhiteSpace_At_Start_Of_CodeBlock_CS,
+ new SourceLocation(6 + Environment.NewLine.Length, 1, 5),
+ Environment.NewLine.Length));
+ }
+
+ [Fact]
+ public void InnerImplicitExpressionDoesNotAcceptTrailingNewlineInRunTimeMode()
+ {
+ ParseBlockTest("{@foo." + Environment.NewLine
+ + "}",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.EmptyCSharp()
+ .AsStatement()
+ .AutoCompleteWith(autoCompleteString: null),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("foo.").AsImplicitExpression(KeywordSet, acceptTrailingDot: true).Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.Code(Environment.NewLine).AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)));
+ }
+
+ [Fact]
+ public void InnerImplicitExpressionAcceptsTrailingNewlineInDesignTimeMode()
+ {
+ ParseBlockTest("{@foo." + Environment.NewLine
+ + "}",
+ new StatementBlock(
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.EmptyCSharp()
+ .AsStatement()
+ .AutoCompleteWith(autoCompleteString: null),
+ new ExpressionBlock(
+ Factory.CodeTransition(),
+ Factory.Code("foo.").AsImplicitExpression(KeywordSet, acceptTrailingDot: true).Accepts(AcceptedCharacters.NonWhiteSpace)),
+ Factory.Code(Environment.NewLine).AsStatement(),
+ Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
+ designTime: true);
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpWhitespaceHandlingTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpWhitespaceHandlingTest.cs
new file mode 100644
index 0000000000..718436c3f6
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CSharpWhitespaceHandlingTest.cs
@@ -0,0 +1,34 @@
+// 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 Xunit;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal class CSharpWhitespaceHandlingTest : CsHtmlMarkupParserTestBase
+ {
+ [Fact]
+ public void StatementBlockDoesNotAcceptTrailingNewlineIfNewlinesAreSignificantToAncestor()
+ {
+ ParseBlockTest("@: @if (true) { }" + Environment.NewLine
+ + "}",
+ new MarkupBlock(
+ Factory.MarkupTransition()
+ .Accepts(AcceptedCharacters.None),
+ Factory.MetaMarkup(":", HtmlSymbolType.Colon),
+ Factory.Markup(" ")
+ .With(new SpanEditHandler(
+ CSharpLanguageCharacteristics.Instance.TokenizeString,
+ AcceptedCharacters.Any)),
+ new StatementBlock(
+ Factory.CodeTransition()
+ .Accepts(AcceptedCharacters.None),
+ Factory.Code("if (true) { }")
+ .AsStatement()
+ ),
+ Factory.Markup(Environment.NewLine)
+ .Accepts(AcceptedCharacters.None)));
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CodeParserTestBase.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CodeParserTestBase.cs
new file mode 100644
index 0000000000..34650fc33b
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CodeParserTestBase.cs
@@ -0,0 +1,75 @@
+// 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.Collections.Generic;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal abstract class CodeParserTestBase : ParserTestBase
+ {
+ protected abstract ISet KeywordSet { get; }
+
+ protected override RazorSyntaxTree ParseBlock(string document, bool designTime)
+ {
+ return ParseCodeBlock(document, designTime);
+ }
+
+ protected void ImplicitExpressionTest(string input, params RazorError[] errors)
+ {
+ ImplicitExpressionTest(input, AcceptedCharacters.NonWhiteSpace, errors);
+ }
+
+ protected void ImplicitExpressionTest(string input, AcceptedCharacters acceptedCharacters, params RazorError[] errors)
+ {
+ ImplicitExpressionTest(input, input, acceptedCharacters, errors);
+ }
+
+ protected void ImplicitExpressionTest(string input, string expected, params RazorError[] errors)
+ {
+ ImplicitExpressionTest(input, expected, AcceptedCharacters.NonWhiteSpace, errors);
+ }
+
+ protected override void SingleSpanBlockTest(string document, BlockType blockType, SpanKind spanType, AcceptedCharacters acceptedCharacters = AcceptedCharacters.Any)
+ {
+ SingleSpanBlockTest(document, blockType, spanType, acceptedCharacters, expectedError: null);
+ }
+
+ protected override void SingleSpanBlockTest(string document, string spanContent, BlockType blockType, SpanKind spanType, AcceptedCharacters acceptedCharacters = AcceptedCharacters.Any)
+ {
+ SingleSpanBlockTest(document, spanContent, blockType, spanType, acceptedCharacters, expectedErrors: null);
+ }
+
+ protected override void SingleSpanBlockTest(string document, BlockType blockType, SpanKind spanType, params RazorError[] expectedError)
+ {
+ SingleSpanBlockTest(document, document, blockType, spanType, expectedError);
+ }
+
+ protected override void SingleSpanBlockTest(string document, string spanContent, BlockType blockType, SpanKind spanType, params RazorError[] expectedErrors)
+ {
+ SingleSpanBlockTest(document, spanContent, blockType, spanType, AcceptedCharacters.Any, expectedErrors ?? new RazorError[0]);
+ }
+
+ protected override void SingleSpanBlockTest(string document, BlockType blockType, SpanKind spanType, AcceptedCharacters acceptedCharacters, params RazorError[] expectedError)
+ {
+ SingleSpanBlockTest(document, document, blockType, spanType, acceptedCharacters, expectedError);
+ }
+
+ protected override void SingleSpanBlockTest(string document, string spanContent, BlockType blockType, SpanKind spanType, AcceptedCharacters acceptedCharacters, params RazorError[] expectedErrors)
+ {
+ var b = CreateSimpleBlockAndSpan(spanContent, blockType, spanType, acceptedCharacters);
+ ParseBlockTest(document, b, expectedErrors ?? new RazorError[0]);
+ }
+
+ protected void ImplicitExpressionTest(string input, string expected, AcceptedCharacters acceptedCharacters, params RazorError[] errors)
+ {
+ var factory = CreateSpanFactory();
+ ParseBlockTest(SyntaxConstants.TransitionString + input,
+ new ExpressionBlock(
+ factory.CodeTransition(),
+ factory.Code(expected)
+ .AsImplicitExpression(KeywordSet)
+ .Accepts(acceptedCharacters)),
+ errors);
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CsHtmlCodeParserTestBase.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CsHtmlCodeParserTestBase.cs
new file mode 100644
index 0000000000..0e7586ed6c
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CsHtmlCodeParserTestBase.cs
@@ -0,0 +1,20 @@
+// 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.Collections.Generic;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal abstract class CsHtmlCodeParserTestBase : CodeParserTestBase
+ {
+ protected override ISet KeywordSet
+ {
+ get { return CSharpCodeParser.DefaultKeywords; }
+ }
+
+ protected override BlockFactory CreateBlockFactory()
+ {
+ return new BlockFactory(Factory ?? CreateSpanFactory());
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CsHtmlMarkupParserTestBase.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CsHtmlMarkupParserTestBase.cs
new file mode 100644
index 0000000000..e92ea0a374
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CsHtmlMarkupParserTestBase.cs
@@ -0,0 +1,20 @@
+// 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.Collections.Generic;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal abstract class CsHtmlMarkupParserTestBase : MarkupParserTestBase
+ {
+ protected override ISet KeywordSet
+ {
+ get { return CSharpCodeParser.DefaultKeywords; }
+ }
+
+ protected override BlockFactory CreateBlockFactory()
+ {
+ return new BlockFactory(Factory ?? CreateSpanFactory());
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/DisposableActionTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/DisposableActionTest.cs
new file mode 100644
index 0000000000..6044b34568
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/DisposableActionTest.cs
@@ -0,0 +1,25 @@
+// 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 Xunit;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal class DisposableActionTest
+ {
+ [Fact]
+ public void ActionIsExecutedOnDispose()
+ {
+ // Arrange
+ var called = false;
+ var action = new DisposableAction(() => { called = true; });
+
+ // Act
+ action.Dispose();
+
+ // Assert
+ Assert.True(called, "The action was not run when the DisposableAction was disposed");
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/ErrorCollector.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/ErrorCollector.cs
new file mode 100644
index 0000000000..31cc9afd0a
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/ErrorCollector.cs
@@ -0,0 +1,57 @@
+// 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.Text;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal class ErrorCollector
+ {
+ private StringBuilder _message = new StringBuilder();
+ private int _indent = 0;
+
+ public bool Success { get; private set; }
+
+ public string Message
+ {
+ get { return _message.ToString(); }
+ }
+
+ public ErrorCollector()
+ {
+ Success = true;
+ }
+
+ public void AddError(string msg, params object[] args)
+ {
+ Append("F", msg, args);
+ Success = false;
+ }
+
+ public void AddMessage(string msg, params object[] args)
+ {
+ Append("P", msg, args);
+ }
+
+ public IDisposable Indent()
+ {
+ _indent++;
+ return new DisposableAction(Unindent);
+ }
+
+ public void Unindent()
+ {
+ _indent--;
+ }
+
+ private void Append(string prefix, string msg, object[] args)
+ {
+ _message.Append(prefix);
+ _message.Append(":");
+ _message.Append(new String('\t', _indent));
+ _message.AppendFormat(msg, args);
+ _message.AppendLine();
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/ExceptionHelpers.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/ExceptionHelpers.cs
new file mode 100644
index 0000000000..982bce0b72
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/ExceptionHelpers.cs
@@ -0,0 +1,16 @@
+// 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 Xunit;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ public static class ExceptionHelpers
+ {
+ public static void ValidateArgumentException(string parameterName, string expectedMessage, ArgumentException exception)
+ {
+ Assert.Equal(string.Format("{0}{1}Parameter name: {2}", expectedMessage, Environment.NewLine, parameterName), exception.Message);
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/HtmlBlockTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/HtmlBlockTest.cs
new file mode 100644
index 0000000000..638b5f754f
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/HtmlBlockTest.cs
@@ -0,0 +1,639 @@
+// 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 Xunit;
+
+namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
+{
+ internal class HtmlBlockTest : CsHtmlMarkupParserTestBase
+ {
+ [Fact]
+ public void ParseBlockHandlesOpenAngleAtEof()
+ {
+ ParseDocumentTest("@{" + Environment.NewLine
+ + "<",
+ new MarkupBlock(
+ Factory.EmptyHtml(),
+ new StatementBlock(
+ Factory.CodeTransition(),
+ Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
+ Factory.Code(Environment.NewLine)
+ .AsStatement()
+ .AutoCompleteWith("}"),
+ new MarkupBlock(
+ new MarkupTagBlock(
+ Factory.Markup("<"))))),
+ new RazorError(
+ LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF(
+ LegacyResources.BlockName_Code, "}", "{"),
+ new SourceLocation(1, 0, 1),
+ length: 1));
+ }
+
+ [Fact]
+ public void ParseBlockHandlesOpenAngleWithProperTagFollowingIt()
+ {
+ ParseDocumentTest("@{" + Environment.NewLine
+ + "<" + Environment.NewLine
+ + "