diff --git a/src/Microsoft.AspNetCore.Razor.Language/ClassifiedSpanVisitor.cs b/src/Microsoft.AspNetCore.Razor.Language/ClassifiedSpanVisitor.cs
new file mode 100644
index 000000000..9c19dbe35
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Razor.Language/ClassifiedSpanVisitor.cs
@@ -0,0 +1,337 @@
+// 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 Microsoft.AspNetCore.Razor.Language.Legacy;
+using Microsoft.AspNetCore.Razor.Language.Syntax;
+
+namespace Microsoft.AspNetCore.Razor.Language
+{
+ internal class ClassifiedSpanVisitor : SyntaxRewriter
+ {
+ private RazorSourceDocument _source;
+ private List _spans;
+ private BlockKindInternal _currentBlockKind;
+ private SyntaxNode _currentBlock;
+
+ public ClassifiedSpanVisitor(RazorSourceDocument source)
+ {
+ _source = source;
+ _spans = new List();
+ _currentBlockKind = BlockKindInternal.Markup;
+ }
+
+ public IReadOnlyList ClassifiedSpans => _spans;
+
+ public override SyntaxNode VisitRazorCommentBlock(RazorCommentBlockSyntax node)
+ {
+ return WriteBlock(node, BlockKindInternal.Comment, razorCommentSyntax =>
+ {
+ WriteSpan(razorCommentSyntax.StartCommentTransition, SpanKindInternal.Transition, AcceptedCharactersInternal.None);
+ WriteSpan(razorCommentSyntax.StartCommentStar, SpanKindInternal.MetaCode, AcceptedCharactersInternal.None);
+
+ var comment = razorCommentSyntax.Comment;
+ if (comment.IsMissing)
+ {
+ // We need to generate a classified span at this position. So insert a marker in its place.
+ comment = (SyntaxToken)SyntaxFactory.Token(SyntaxKind.Marker, string.Empty).Green.CreateRed(razorCommentSyntax, razorCommentSyntax.StartCommentStar.EndPosition);
+ }
+ WriteSpan(comment, SpanKindInternal.Comment, AcceptedCharactersInternal.Any);
+
+ WriteSpan(razorCommentSyntax.EndCommentStar, SpanKindInternal.MetaCode, AcceptedCharactersInternal.None);
+ WriteSpan(razorCommentSyntax.EndCommentTransition, SpanKindInternal.Transition, AcceptedCharactersInternal.None);
+
+ return razorCommentSyntax;
+ });
+ }
+
+ public override SyntaxNode VisitCSharpCodeBlock(CSharpCodeBlockSyntax node)
+ {
+ if (node.Parent is CSharpStatementBodySyntax ||
+ node.Parent is CSharpExplicitExpressionBodySyntax ||
+ node.Parent is CSharpImplicitExpressionBodySyntax ||
+ node.Parent is RazorDirectiveBodySyntax ||
+ (_currentBlockKind == BlockKindInternal.Directive &&
+ node.Children.Count == 1 &&
+ node.Children[0] is CSharpStatementLiteralSyntax))
+ {
+ return base.VisitCSharpCodeBlock(node);
+ }
+
+ return WriteBlock(node, BlockKindInternal.Statement, base.VisitCSharpCodeBlock);
+ }
+
+ public override SyntaxNode VisitCSharpStatement(CSharpStatementSyntax node)
+ {
+ return WriteBlock(node, BlockKindInternal.Statement, base.VisitCSharpStatement);
+ }
+
+ public override SyntaxNode VisitCSharpExplicitExpression(CSharpExplicitExpressionSyntax node)
+ {
+ return WriteBlock(node, BlockKindInternal.Expression, base.VisitCSharpExplicitExpression);
+ }
+
+ public override SyntaxNode VisitCSharpImplicitExpression(CSharpImplicitExpressionSyntax node)
+ {
+ return WriteBlock(node, BlockKindInternal.Expression, base.VisitCSharpImplicitExpression);
+ }
+
+ public override SyntaxNode VisitRazorDirective(RazorDirectiveSyntax node)
+ {
+ return WriteBlock(node, BlockKindInternal.Directive, base.VisitRazorDirective);
+ }
+
+ public override SyntaxNode VisitCSharpTemplateBlock(CSharpTemplateBlockSyntax node)
+ {
+ return WriteBlock(node, BlockKindInternal.Template, base.VisitCSharpTemplateBlock);
+ }
+
+ public override SyntaxNode VisitMarkupBlock(MarkupBlockSyntax node)
+ {
+ return WriteBlock(node, BlockKindInternal.Markup, base.VisitMarkupBlock);
+ }
+
+ public override SyntaxNode VisitGenericBlock(GenericBlockSyntax node)
+ {
+ if (!(node.Parent is MarkupTagHelperAttributeValueSyntax) &&
+ node.FirstAncestorOrSelf(n => n is MarkupTagHelperAttributeValueSyntax) != null)
+ {
+ return WriteBlock(node, BlockKindInternal.Expression, base.VisitGenericBlock);
+ }
+
+ return base.VisitGenericBlock(node);
+ }
+
+ public override SyntaxNode VisitMarkupTagHelperAttributeValue(MarkupTagHelperAttributeValueSyntax node)
+ {
+ // We don't generate a classified span when the attribute value is a simple literal value.
+ if (node.Children.Count > 1 ||
+ (node.Children.Count == 1 && node.Children[0] is MarkupDynamicAttributeValueSyntax))
+ {
+ return WriteBlock(node, BlockKindInternal.Markup, base.VisitMarkupTagHelperAttributeValue);
+ }
+
+ return base.VisitMarkupTagHelperAttributeValue(node);
+ }
+
+ public override SyntaxNode VisitMarkupTagBlock(MarkupTagBlockSyntax node)
+ {
+ return WriteBlock(node, BlockKindInternal.Tag, base.VisitMarkupTagBlock);
+ }
+
+ public override SyntaxNode VisitMarkupTagHelperElement(MarkupTagHelperElementSyntax node)
+ {
+ return WriteBlock(node, BlockKindInternal.Tag, base.VisitMarkupTagHelperElement);
+ }
+
+ public override SyntaxNode VisitMarkupTagHelperStartTag(MarkupTagHelperStartTagSyntax node)
+ {
+ var rewritten = new List();
+ foreach (var child in node.Children)
+ {
+ if (child is MarkupTagHelperAttributeSyntax attribute)
+ {
+ var rewrittenAttribute = (MarkupTagHelperAttributeSyntax)Visit(attribute);
+ rewritten.Add(rewrittenAttribute);
+ }
+ else
+ {
+ rewritten.Add(child);
+ }
+ }
+
+ return node.Update(new SyntaxList(rewritten));
+ }
+
+ public override SyntaxNode VisitMarkupTagHelperEndTag(MarkupTagHelperEndTagSyntax node)
+ {
+ return node;
+ }
+
+ public override SyntaxNode VisitMarkupAttributeBlock(MarkupAttributeBlockSyntax node)
+ {
+ return WriteBlock(node, BlockKindInternal.Markup, n =>
+ {
+ var equalsSyntax = SyntaxFactory.MarkupTextLiteral(new SyntaxList(node.EqualsToken));
+ var mergedAttributePrefix = MergeTextLiteralSpans(node.NamePrefix, node.Name, node.NameSuffix, equalsSyntax, node.ValuePrefix);
+ Visit(mergedAttributePrefix);
+ Visit(node.Value);
+ Visit(node.ValueSuffix);
+
+ return n;
+ });
+ }
+
+ public override SyntaxNode VisitMarkupTagHelperAttribute(MarkupTagHelperAttributeSyntax node)
+ {
+ var rewrittenValue = (MarkupTagHelperAttributeValueSyntax)Visit(node.Value);
+
+ return node.Update(node.NamePrefix, node.Name, node.NameSuffix, node.EqualsToken, node.ValuePrefix, rewrittenValue, node.ValueSuffix);
+ }
+
+ public override SyntaxNode VisitMarkupMinimizedAttributeBlock(MarkupMinimizedAttributeBlockSyntax node)
+ {
+ return WriteBlock(node, BlockKindInternal.Markup, n =>
+ {
+ var mergedAttributePrefix = MergeTextLiteralSpans(node.NamePrefix, node.Name);
+ Visit(mergedAttributePrefix);
+
+ return n;
+ });
+ }
+
+ public override SyntaxNode VisitMarkupCommentBlock(MarkupCommentBlockSyntax node)
+ {
+ return WriteBlock(node, BlockKindInternal.HtmlComment, base.VisitMarkupCommentBlock);
+ }
+
+ public override SyntaxNode VisitMarkupDynamicAttributeValue(MarkupDynamicAttributeValueSyntax node)
+ {
+ return WriteBlock(node, BlockKindInternal.Markup, base.VisitMarkupDynamicAttributeValue);
+ }
+
+ public override SyntaxNode VisitRazorMetaCode(RazorMetaCodeSyntax node)
+ {
+ WriteSpan(node, SpanKindInternal.MetaCode);
+ return base.VisitRazorMetaCode(node);
+ }
+
+ public override SyntaxNode VisitCSharpTransition(CSharpTransitionSyntax node)
+ {
+ WriteSpan(node, SpanKindInternal.Transition);
+ return base.VisitCSharpTransition(node);
+ }
+
+ public override SyntaxNode VisitMarkupTransition(MarkupTransitionSyntax node)
+ {
+ WriteSpan(node, SpanKindInternal.Transition);
+ return base.VisitMarkupTransition(node);
+ }
+
+ public override SyntaxNode VisitCSharpStatementLiteral(CSharpStatementLiteralSyntax node)
+ {
+ WriteSpan(node, SpanKindInternal.Code);
+ return base.VisitCSharpStatementLiteral(node);
+ }
+
+ public override SyntaxNode VisitCSharpExpressionLiteral(CSharpExpressionLiteralSyntax node)
+ {
+ WriteSpan(node, SpanKindInternal.Code);
+ return base.VisitCSharpExpressionLiteral(node);
+ }
+
+ public override SyntaxNode VisitCSharpEphemeralTextLiteral(CSharpEphemeralTextLiteralSyntax node)
+ {
+ WriteSpan(node, SpanKindInternal.Code);
+ return base.VisitCSharpEphemeralTextLiteral(node);
+ }
+
+ public override SyntaxNode VisitUnclassifiedTextLiteral(UnclassifiedTextLiteralSyntax node)
+ {
+ WriteSpan(node, SpanKindInternal.None);
+ return base.VisitUnclassifiedTextLiteral(node);
+ }
+
+ public override SyntaxNode VisitMarkupLiteralAttributeValue(MarkupLiteralAttributeValueSyntax node)
+ {
+ WriteSpan(node, SpanKindInternal.Markup);
+ return base.VisitMarkupLiteralAttributeValue(node);
+ }
+
+ public override SyntaxNode VisitMarkupTextLiteral(MarkupTextLiteralSyntax node)
+ {
+ if (node.Parent is MarkupLiteralAttributeValueSyntax)
+ {
+ return base.VisitMarkupTextLiteral(node);
+ }
+
+ WriteSpan(node, SpanKindInternal.Markup);
+ return base.VisitMarkupTextLiteral(node);
+ }
+
+ public override SyntaxNode VisitMarkupEphemeralTextLiteral(MarkupEphemeralTextLiteralSyntax node)
+ {
+ WriteSpan(node, SpanKindInternal.Markup);
+ return base.VisitMarkupEphemeralTextLiteral(node);
+ }
+
+ private SyntaxNode WriteBlock(TNode node, BlockKindInternal kind, Func handler) where TNode : SyntaxNode
+ {
+ var previousBlock = _currentBlock;
+ var previousKind = _currentBlockKind;
+
+ _currentBlock = node;
+ _currentBlockKind = kind;
+
+ var result = handler(node);
+
+ _currentBlock = previousBlock;
+ _currentBlockKind = previousKind;
+
+ return result;
+ }
+
+ private void WriteSpan(SyntaxNode node, SpanKindInternal kind, AcceptedCharactersInternal? acceptedCharacters = null)
+ {
+ if (node.IsMissing)
+ {
+ return;
+ }
+
+ var spanSource = node.GetSourceSpan(_source);
+ var blockSource = _currentBlock.GetSourceSpan(_source);
+ if (!acceptedCharacters.HasValue)
+ {
+ acceptedCharacters = AcceptedCharactersInternal.Any;
+ var context = node.GetSpanContext();
+ if (context != null)
+ {
+ acceptedCharacters = context.EditHandler.AcceptedCharacters;
+ }
+ }
+
+ var span = new ClassifiedSpanInternal(spanSource, blockSource, kind, _currentBlockKind, acceptedCharacters.Value);
+ _spans.Add(span);
+ }
+
+ private MarkupTextLiteralSyntax MergeTextLiteralSpans(params MarkupTextLiteralSyntax[] literalSyntaxes)
+ {
+ if (literalSyntaxes == null || literalSyntaxes.Length == 0)
+ {
+ return null;
+ }
+
+ SyntaxNode parent = null;
+ var position = 0;
+ var seenFirstLiteral = false;
+ var builder = Syntax.InternalSyntax.SyntaxListBuilder.Create();
+
+ foreach (var syntax in literalSyntaxes)
+ {
+ if (syntax == null)
+ {
+ continue;
+ }
+ else if (!seenFirstLiteral)
+ {
+ // Set the parent and position of the merged literal to the value of the first non-null literal.
+ parent = syntax.Parent;
+ position = syntax.Position;
+ seenFirstLiteral = true;
+ }
+
+ foreach (var token in syntax.LiteralTokens)
+ {
+ builder.Add(token.Green);
+ }
+ }
+
+ var mergedLiteralSyntax = Syntax.InternalSyntax.SyntaxFactory.MarkupTextLiteral(
+ builder.ToList());
+
+ return (MarkupTextLiteralSyntax)mergedLiteralSyntax.CreateRed(parent, position);
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Razor.Language/DefaultDirectiveSyntaxTreePass.cs b/src/Microsoft.AspNetCore.Razor.Language/DefaultDirectiveSyntaxTreePass.cs
index 54c733e0f..3c6b5f1d8 100644
--- a/src/Microsoft.AspNetCore.Razor.Language/DefaultDirectiveSyntaxTreePass.cs
+++ b/src/Microsoft.AspNetCore.Razor.Language/DefaultDirectiveSyntaxTreePass.cs
@@ -5,6 +5,7 @@
using System.Linq;
using Microsoft.AspNetCore.Razor.Language.Extensions;
using Microsoft.AspNetCore.Razor.Language.Legacy;
+using Microsoft.AspNetCore.Razor.Language.Syntax;
namespace Microsoft.AspNetCore.Razor.Language
{
@@ -24,19 +25,58 @@ public RazorSyntaxTree Execute(RazorCodeDocument codeDocument, RazorSyntaxTree s
throw new ArgumentNullException(nameof(syntaxTree));
}
- var sectionVerifier = new NestedSectionVerifier();
- sectionVerifier.Verify(syntaxTree);
+ if (syntaxTree is LegacyRazorSyntaxTree)
+ {
+ var legacySectionVerifier = new LegacyNestedSectionVerifier();
+ legacySectionVerifier.Verify(syntaxTree);
+ return syntaxTree;
+ }
+
+ var sectionVerifier = new NestedSectionVerifier(syntaxTree);
+ return sectionVerifier.Verify();
+ }
+
+ private class NestedSectionVerifier : SyntaxRewriter
+ {
+ private int _nestedLevel;
+ private RazorSyntaxTree _syntaxTree;
+
+ public NestedSectionVerifier(RazorSyntaxTree syntaxTree)
+ {
+ _syntaxTree = syntaxTree;
+ }
+
+ public RazorSyntaxTree Verify()
+ {
+ var root = Visit(_syntaxTree.Root);
+ var rewrittenTree = new DefaultRazorSyntaxTree(root, _syntaxTree.Source, _syntaxTree.Diagnostics, _syntaxTree.Options);
+ return rewrittenTree;
+ }
- return syntaxTree;
+ public override SyntaxNode VisitRazorDirective(RazorDirectiveSyntax node)
+ {
+ if (_nestedLevel > 0)
+ {
+ var directiveStart = node.Transition.GetSourceLocation(_syntaxTree.Source);
+ var errorLength = /* @ */ 1 + SectionDirective.Directive.Directive.Length;
+ var error = RazorDiagnosticFactory.CreateParsing_SectionsCannotBeNested(new SourceSpan(directiveStart, errorLength));
+ node = node.AppendDiagnostic(error);
+ }
+ _nestedLevel++;
+ var result = base.VisitRazorDirective(node);
+ _nestedLevel--;
+
+ return result;
+ }
}
- private class NestedSectionVerifier : ParserVisitor
+ private class LegacyNestedSectionVerifier : ParserVisitor
{
private int _nestedLevel;
public void Verify(RazorSyntaxTree tree)
{
- tree.Root.Accept(this);
+ tree.LegacyRoot.Accept(this);
}
public override void VisitDirectiveBlock(DirectiveChunkGenerator chunkGenerator, Block block)
diff --git a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorIntermediateNodeLoweringPhase.cs b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorIntermediateNodeLoweringPhase.cs
index 762fd61d6..136bc6959 100644
--- a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorIntermediateNodeLoweringPhase.cs
+++ b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorIntermediateNodeLoweringPhase.cs
@@ -8,6 +8,7 @@
using Microsoft.AspNetCore.Razor.Language.Extensions;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Microsoft.AspNetCore.Razor.Language.Legacy;
+using Microsoft.AspNetCore.Razor.Language.Syntax;
namespace Microsoft.AspNetCore.Razor.Language
{
@@ -26,6 +27,12 @@ protected override void ExecuteCore(RazorCodeDocument codeDocument)
var syntaxTree = codeDocument.GetSyntaxTree();
ThrowForMissingDocumentDependency(syntaxTree);
+ if (syntaxTree is LegacyRazorSyntaxTree)
+ {
+ // Can't handle legacy tree.
+ return;
+ }
+
// This might not have been set if there are no tag helpers.
var tagHelperContext = codeDocument.GetTagHelperContext();
@@ -46,8 +53,8 @@ protected override void ExecuteCore(RazorCodeDocument codeDocument)
{
var import = imports[j];
- importsVisitor.FilePath = import.Source.FilePath;
- importsVisitor.VisitBlock(import.Root);
+ importsVisitor.SourceDocument = import.Source;
+ importsVisitor.Visit(import.Root);
}
importedUsings = importsVisitor.Usings;
@@ -56,10 +63,10 @@ protected override void ExecuteCore(RazorCodeDocument codeDocument)
var tagHelperPrefix = tagHelperContext?.Prefix;
var visitor = new MainSourceVisitor(document, builder, tagHelperPrefix, syntaxTree.Options.FeatureFlags)
{
- FilePath = syntaxTree.Source.FilePath,
+ SourceDocument = syntaxTree.Source,
};
- visitor.VisitBlock(syntaxTree.Root);
+ visitor.Visit(syntaxTree.Root);
// 1. Prioritize non-imported usings over imported ones.
// 2. Don't import usings that already exist in primary document.
@@ -189,7 +196,7 @@ public bool Equals(UsingReference other)
public override int GetHashCode() => Namespace.GetHashCode();
}
- private class LoweringVisitor : ParserVisitor
+ private class LoweringVisitor : SyntaxRewriter
{
protected readonly IntermediateNodeBuilder _builder;
protected readonly DocumentIntermediateNode _document;
@@ -206,195 +213,220 @@ public LoweringVisitor(DocumentIntermediateNode document, IntermediateNodeBuilde
public IReadOnlyList Usings => _usings;
- public string FilePath { get; set; }
+ public RazorSourceDocument SourceDocument { get; set; }
- public override void VisitDirectiveToken(DirectiveTokenChunkGenerator chunkGenerator, Span span)
- {
- _builder.Add(new DirectiveTokenIntermediateNode()
- {
- Content = span.Content,
- DirectiveToken = chunkGenerator.Descriptor,
- Source = BuildSourceSpanFromNode(span),
- });
- }
-
- public override void VisitDirectiveBlock(DirectiveChunkGenerator chunkGenerator, Block block)
+ public override SyntaxNode VisitRazorDirective(RazorDirectiveSyntax node)
{
IntermediateNode directiveNode;
- if (IsMalformed(chunkGenerator.Diagnostics))
+ var diagnostics = node.GetDiagnostics();
+ var descriptor = node.DirectiveDescriptor;
+
+ if (descriptor != null)
{
- directiveNode = new MalformedDirectiveIntermediateNode()
+ // This is an extensible directive.
+ if (IsMalformed(diagnostics))
{
- DirectiveName = chunkGenerator.Descriptor.Directive,
- Directive = chunkGenerator.Descriptor,
- Source = BuildSourceSpanFromNode(block),
- };
- }
- else
- {
- directiveNode = new DirectiveIntermediateNode()
+ directiveNode = new MalformedDirectiveIntermediateNode()
+ {
+ DirectiveName = descriptor.Directive,
+ Directive = descriptor,
+ Source = BuildSourceSpanFromNode(node),
+ };
+ }
+ else
{
- DirectiveName = chunkGenerator.Descriptor.Directive,
- Directive = chunkGenerator.Descriptor,
- Source = BuildSourceSpanFromNode(block),
- };
- }
+ directiveNode = new DirectiveIntermediateNode()
+ {
+ DirectiveName = descriptor.Directive,
+ Directive = descriptor,
+ Source = BuildSourceSpanFromNode(node),
+ };
+ }
- for (var i = 0; i < chunkGenerator.Diagnostics.Count; i++)
- {
- directiveNode.Diagnostics.Add(chunkGenerator.Diagnostics[i]);
- }
+ for (var i = 0; i < diagnostics.Length; i++)
+ {
+ directiveNode.Diagnostics.Add(diagnostics[i]);
+ }
- _builder.Push(directiveNode);
+ _builder.Push(directiveNode);
+ }
- VisitDefault(block);
+ Visit(node.Body);
- _builder.Pop();
- }
+ if (descriptor != null)
+ {
+ _builder.Pop();
+ }
- public override void VisitImportSpan(AddImportChunkGenerator chunkGenerator, Span span)
- {
- var namespaceImport = chunkGenerator.Namespace.Trim();
- var namespaceSpan = BuildSourceSpanFromNode(span);
- _usings.Add(new UsingReference(namespaceImport, namespaceSpan));
+ return node;
}
- public override void VisitAddTagHelperSpan(AddTagHelperChunkGenerator chunkGenerator, Span span)
+ public override SyntaxNode VisitCSharpStatementLiteral(CSharpStatementLiteralSyntax node)
{
- IntermediateNode directiveNode;
- if (IsMalformed(chunkGenerator.Diagnostics))
+ var context = node.GetSpanContext();
+ if (context == null)
{
- directiveNode = new MalformedDirectiveIntermediateNode()
- {
- DirectiveName = CSharpCodeParser.AddTagHelperDirectiveDescriptor.Directive,
- Directive = CSharpCodeParser.AddTagHelperDirectiveDescriptor,
- Source = BuildSourceSpanFromNode(span),
- };
+ return base.VisitCSharpStatementLiteral(node);
}
- else
+ else if (context.ChunkGenerator is DirectiveTokenChunkGenerator tokenChunkGenerator)
{
- directiveNode = new DirectiveIntermediateNode()
+ _builder.Add(new DirectiveTokenIntermediateNode()
{
- DirectiveName = CSharpCodeParser.AddTagHelperDirectiveDescriptor.Directive,
- Directive = CSharpCodeParser.AddTagHelperDirectiveDescriptor,
- Source = BuildSourceSpanFromNode(span),
- };
+ Content = node.GetContent(),
+ DirectiveToken = tokenChunkGenerator.Descriptor,
+ Source = BuildSourceSpanFromNode(node),
+ });
}
-
- for (var i = 0; i < chunkGenerator.Diagnostics.Count; i++)
+ else if (context.ChunkGenerator is AddImportChunkGenerator importChunkGenerator)
{
- directiveNode.Diagnostics.Add(chunkGenerator.Diagnostics[i]);
+ var namespaceImport = importChunkGenerator.Namespace.Trim();
+ var namespaceSpan = BuildSourceSpanFromNode(node);
+ _usings.Add(new UsingReference(namespaceImport, namespaceSpan));
}
-
- _builder.Push(directiveNode);
-
- _builder.Add(new DirectiveTokenIntermediateNode()
+ else if (context.ChunkGenerator is AddTagHelperChunkGenerator addTagHelperChunkGenerator)
{
- Content = chunkGenerator.LookupText,
- DirectiveToken = CSharpCodeParser.AddTagHelperDirectiveDescriptor.Tokens.First(),
- Source = BuildSourceSpanFromNode(span),
- });
+ IntermediateNode directiveNode;
+ if (IsMalformed(addTagHelperChunkGenerator.Diagnostics))
+ {
+ directiveNode = new MalformedDirectiveIntermediateNode()
+ {
+ DirectiveName = CSharpCodeParser.AddTagHelperDirectiveDescriptor.Directive,
+ Directive = CSharpCodeParser.AddTagHelperDirectiveDescriptor,
+ Source = BuildSourceSpanFromNode(node),
+ };
+ }
+ else
+ {
+ directiveNode = new DirectiveIntermediateNode()
+ {
+ DirectiveName = CSharpCodeParser.AddTagHelperDirectiveDescriptor.Directive,
+ Directive = CSharpCodeParser.AddTagHelperDirectiveDescriptor,
+ Source = BuildSourceSpanFromNode(node),
+ };
+ }
- _builder.Pop();
- }
+ for (var i = 0; i < addTagHelperChunkGenerator.Diagnostics.Count; i++)
+ {
+ directiveNode.Diagnostics.Add(addTagHelperChunkGenerator.Diagnostics[i]);
+ }
- public override void VisitRemoveTagHelperSpan(RemoveTagHelperChunkGenerator chunkGenerator, Span span)
- {
- IntermediateNode directiveNode;
- if (IsMalformed(chunkGenerator.Diagnostics))
- {
- directiveNode = new MalformedDirectiveIntermediateNode()
+ _builder.Push(directiveNode);
+
+ _builder.Add(new DirectiveTokenIntermediateNode()
{
- DirectiveName = CSharpCodeParser.RemoveTagHelperDirectiveDescriptor.Directive,
- Directive = CSharpCodeParser.RemoveTagHelperDirectiveDescriptor,
- Source = BuildSourceSpanFromNode(span),
- };
+ Content = addTagHelperChunkGenerator.LookupText,
+ DirectiveToken = CSharpCodeParser.AddTagHelperDirectiveDescriptor.Tokens.First(),
+ Source = BuildSourceSpanFromNode(node),
+ });
+
+ _builder.Pop();
}
- else
+ else if (context.ChunkGenerator is RemoveTagHelperChunkGenerator removeTagHelperChunkGenerator)
{
- directiveNode = new DirectiveIntermediateNode()
+ IntermediateNode directiveNode;
+ if (IsMalformed(removeTagHelperChunkGenerator.Diagnostics))
{
- DirectiveName = CSharpCodeParser.RemoveTagHelperDirectiveDescriptor.Directive,
- Directive = CSharpCodeParser.RemoveTagHelperDirectiveDescriptor,
- Source = BuildSourceSpanFromNode(span),
- };
- }
-
- for (var i = 0; i < chunkGenerator.Diagnostics.Count; i++)
- {
- directiveNode.Diagnostics.Add(chunkGenerator.Diagnostics[i]);
- }
-
- _builder.Push(directiveNode);
+ directiveNode = new MalformedDirectiveIntermediateNode()
+ {
+ DirectiveName = CSharpCodeParser.RemoveTagHelperDirectiveDescriptor.Directive,
+ Directive = CSharpCodeParser.RemoveTagHelperDirectiveDescriptor,
+ Source = BuildSourceSpanFromNode(node),
+ };
+ }
+ else
+ {
+ directiveNode = new DirectiveIntermediateNode()
+ {
+ DirectiveName = CSharpCodeParser.RemoveTagHelperDirectiveDescriptor.Directive,
+ Directive = CSharpCodeParser.RemoveTagHelperDirectiveDescriptor,
+ Source = BuildSourceSpanFromNode(node),
+ };
+ }
- _builder.Add(new DirectiveTokenIntermediateNode()
- {
- Content = chunkGenerator.LookupText,
- DirectiveToken = CSharpCodeParser.RemoveTagHelperDirectiveDescriptor.Tokens.First(),
- Source = BuildSourceSpanFromNode(span),
- });
+ for (var i = 0; i < removeTagHelperChunkGenerator.Diagnostics.Count; i++)
+ {
+ directiveNode.Diagnostics.Add(removeTagHelperChunkGenerator.Diagnostics[i]);
+ }
- _builder.Pop();
- }
+ _builder.Push(directiveNode);
- public override void VisitTagHelperPrefixDirectiveSpan(TagHelperPrefixDirectiveChunkGenerator chunkGenerator, Span span)
- {
- IntermediateNode directiveNode;
- if (IsMalformed(chunkGenerator.Diagnostics))
- {
- directiveNode = new MalformedDirectiveIntermediateNode()
+ _builder.Add(new DirectiveTokenIntermediateNode()
{
- DirectiveName = CSharpCodeParser.TagHelperPrefixDirectiveDescriptor.Directive,
- Directive = CSharpCodeParser.TagHelperPrefixDirectiveDescriptor,
- Source = BuildSourceSpanFromNode(span),
- };
+ Content = removeTagHelperChunkGenerator.LookupText,
+ DirectiveToken = CSharpCodeParser.RemoveTagHelperDirectiveDescriptor.Tokens.First(),
+ Source = BuildSourceSpanFromNode(node),
+ });
+
+ _builder.Pop();
}
- else
+ else if (context.ChunkGenerator is TagHelperPrefixDirectiveChunkGenerator tagHelperPrefixChunkGenerator)
{
- directiveNode = new DirectiveIntermediateNode()
+ IntermediateNode directiveNode;
+ if (IsMalformed(tagHelperPrefixChunkGenerator.Diagnostics))
{
- DirectiveName = CSharpCodeParser.TagHelperPrefixDirectiveDescriptor.Directive,
- Directive = CSharpCodeParser.TagHelperPrefixDirectiveDescriptor,
- Source = BuildSourceSpanFromNode(span),
- };
- }
+ directiveNode = new MalformedDirectiveIntermediateNode()
+ {
+ DirectiveName = CSharpCodeParser.TagHelperPrefixDirectiveDescriptor.Directive,
+ Directive = CSharpCodeParser.TagHelperPrefixDirectiveDescriptor,
+ Source = BuildSourceSpanFromNode(node),
+ };
+ }
+ else
+ {
+ directiveNode = new DirectiveIntermediateNode()
+ {
+ DirectiveName = CSharpCodeParser.TagHelperPrefixDirectiveDescriptor.Directive,
+ Directive = CSharpCodeParser.TagHelperPrefixDirectiveDescriptor,
+ Source = BuildSourceSpanFromNode(node),
+ };
+ }
- for (var i = 0; i < chunkGenerator.Diagnostics.Count; i++)
- {
- directiveNode.Diagnostics.Add(chunkGenerator.Diagnostics[i]);
- }
+ for (var i = 0; i < tagHelperPrefixChunkGenerator.Diagnostics.Count; i++)
+ {
+ directiveNode.Diagnostics.Add(tagHelperPrefixChunkGenerator.Diagnostics[i]);
+ }
- _builder.Push(directiveNode);
+ _builder.Push(directiveNode);
- _builder.Add(new DirectiveTokenIntermediateNode()
- {
- Content = chunkGenerator.Prefix,
- DirectiveToken = CSharpCodeParser.TagHelperPrefixDirectiveDescriptor.Tokens.First(),
- Source = BuildSourceSpanFromNode(span),
- });
+ _builder.Add(new DirectiveTokenIntermediateNode()
+ {
+ Content = tagHelperPrefixChunkGenerator.Prefix,
+ DirectiveToken = CSharpCodeParser.TagHelperPrefixDirectiveDescriptor.Tokens.First(),
+ Source = BuildSourceSpanFromNode(node),
+ });
- _builder.Pop();
+ _builder.Pop();
+ }
+
+ return base.VisitCSharpStatementLiteral(node);
}
- protected SourceSpan? BuildSourceSpanFromNode(SyntaxTreeNode node)
+ protected SourceSpan? BuildSourceSpanFromNode(SyntaxNode node)
{
- if (node == null || node.Start == SourceLocation.Undefined)
+ if (node == null)
{
return null;
}
- var span = new SourceSpan(
- node.Start.FilePath ?? FilePath,
- node.Start.AbsoluteIndex,
- node.Start.LineIndex,
- node.Start.CharacterIndex,
- node.Length);
- return span;
+ if (node.Position == SourceDocument.Length)
+ {
+ // This can happen when we have a marker symbol at the end of the syntax tree.
+ var location = SourceDocument.Lines.GetLocation(node.Position - 1);
+ return new SourceSpan(
+ SourceDocument.FilePath,
+ location.AbsoluteIndex + 1,
+ location.LineIndex,
+ location.CharacterIndex + 1,
+ length: 0);
+ }
+
+ return node.GetSourceSpan(SourceDocument);
}
}
private class MainSourceVisitor : LoweringVisitor
{
+ private readonly HashSet _renderedBoundAttributeNames = new HashSet(StringComparer.OrdinalIgnoreCase);
private readonly string _tagHelperPrefix;
public MainSourceVisitor(DocumentIntermediateNode document, IntermediateNodeBuilder builder, string tagHelperPrefix, RazorParserFeatureFlags featureFlags)
@@ -408,86 +440,149 @@ public MainSourceVisitor(DocumentIntermediateNode document, IntermediateNodeBuil
// Name=checked
// Prefix= checked="
// Suffix="
- public override void VisitAttributeBlock(AttributeBlockChunkGenerator chunkGenerator, Block block)
+ public override SyntaxNode VisitMarkupAttributeBlock(MarkupAttributeBlockSyntax node)
{
- _builder.Push(new HtmlAttributeIntermediateNode()
+ var prefixTokens = MergeLiterals(
+ node.NamePrefix?.LiteralTokens,
+ node.Name.LiteralTokens,
+ node.NameSuffix?.LiteralTokens,
+ node.EqualsToken == null ? new SyntaxList() : new SyntaxList(node.EqualsToken),
+ node.ValuePrefix?.LiteralTokens);
+ var prefix = (MarkupTextLiteralSyntax)SyntaxFactory.MarkupTextLiteral(prefixTokens).Green.CreateRed(node, node.NamePrefix?.Position ?? node.Name.Position);
+
+ var name = node.Name.GetContent();
+ if (name.StartsWith("data-", StringComparison.OrdinalIgnoreCase) &&
+ !_featureFlags.EXPERIMENTAL_AllowConditionalDataDashAttributes)
{
- AttributeName = chunkGenerator.Name,
- Prefix = chunkGenerator.Prefix,
- Suffix = chunkGenerator.Suffix,
- Source = BuildSourceSpanFromNode(block),
- });
+ Visit(prefix);
+ Visit(node.Value);
+ Visit(node.ValueSuffix);
+ }
+ else
+ {
+ if (node.Value != null && node.Value.ChildNodes().All(c => c is MarkupLiteralAttributeValueSyntax))
+ {
+ // We need to do what ConditionalAttributeCollapser used to do.
+ var literalAttributeValueNodes = node.Value.ChildNodes().Cast().ToArray();
+ var valueTokens = SyntaxListBuilder.Create();
+ for (var i = 0; i < literalAttributeValueNodes.Length; i++)
+ {
+ var mergedValue = MergeAttributeValue(literalAttributeValueNodes[i]);
+ valueTokens.AddRange(mergedValue.LiteralTokens);
+ }
+ var rewritten = SyntaxFactory.MarkupTextLiteral(valueTokens.ToList());
+
+ var mergedLiterals = MergeLiterals(prefix?.LiteralTokens, rewritten.LiteralTokens, node.ValueSuffix?.LiteralTokens);
+ var mergedAttribute = SyntaxFactory.MarkupTextLiteral(mergedLiterals).Green.CreateRed(node.Parent, node.Position);
+ Visit(mergedAttribute);
+ }
+ else
+ {
+ _builder.Push(new HtmlAttributeIntermediateNode()
+ {
+ AttributeName = node.Name.GetContent(),
+ Prefix = prefix.GetContent(),
+ Suffix = node.ValueSuffix?.GetContent() ?? string.Empty,
+ Source = BuildSourceSpanFromNode(node),
+ });
- VisitDefault(block);
+ VisitAttributeValue(node.Value);
- _builder.Pop();
+ _builder.Pop();
+ }
+ }
+
+ return node;
+ }
+
+ public override SyntaxNode VisitMarkupMinimizedAttributeBlock(MarkupMinimizedAttributeBlockSyntax node)
+ {
+ var name = node.Name.GetContent();
+ if (name.StartsWith("data-", StringComparison.OrdinalIgnoreCase) &&
+ !_featureFlags.EXPERIMENTAL_AllowConditionalDataDashAttributes)
+ {
+ return base.VisitMarkupMinimizedAttributeBlock(node);
+ }
+
+ // Minimized attributes are just html content.
+ var literals = MergeLiterals(
+ node.NamePrefix?.LiteralTokens,
+ node.Name?.LiteralTokens);
+ var literal = SyntaxFactory.MarkupTextLiteral(literals).Green.CreateRed(node.Parent, node.Position);
+
+ return Visit(literal);
}
// Example
//
// Prefix= (space)
// Children will contain a token for @false.
- public override void VisitDynamicAttributeBlock(DynamicAttributeBlockChunkGenerator chunkGenerator, Block block)
+ public override SyntaxNode VisitMarkupDynamicAttributeValue(MarkupDynamicAttributeValueSyntax node)
{
- var firstChild = block.Children.FirstOrDefault(c => c.IsBlock) as Block;
- if (firstChild == null || firstChild.Type == BlockKindInternal.Expression)
+ var containsExpression = false;
+ var descendantNodes = node.DescendantNodes(n =>
+ {
+ // Don't go into sub block. They may contain expressions but we only care about the top level.
+ return !(n.Parent is CSharpCodeBlockSyntax);
+ });
+ foreach (var child in descendantNodes)
+ {
+ if (child is CSharpImplicitExpressionSyntax || child is CSharpExplicitExpressionSyntax)
+ {
+ containsExpression = true;
+ }
+ }
+
+ if (containsExpression)
{
_builder.Push(new CSharpExpressionAttributeValueIntermediateNode()
{
- Prefix = chunkGenerator.Prefix,
- Source = BuildSourceSpanFromNode(block),
+ Prefix = node.Prefix?.GetContent() ?? string.Empty,
+ Source = BuildSourceSpanFromNode(node),
});
}
else
{
_builder.Push(new CSharpCodeAttributeValueIntermediateNode()
{
- Prefix = chunkGenerator.Prefix,
- Source = BuildSourceSpanFromNode(block),
+ Prefix = node.Prefix?.GetContent() ?? string.Empty,
+ Source = BuildSourceSpanFromNode(node),
});
}
- VisitDefault(block);
+ Visit(node.Value);
_builder.Pop();
+
+ return node;
}
- public override void VisitLiteralAttributeSpan(LiteralAttributeChunkGenerator chunkGenerator, Span span)
+ public override SyntaxNode VisitMarkupLiteralAttributeValue(MarkupLiteralAttributeValueSyntax node)
{
_builder.Push(new HtmlAttributeValueIntermediateNode()
{
- Prefix = chunkGenerator.Prefix,
- Source = BuildSourceSpanFromNode(span),
+ Prefix = node.Prefix?.GetContent() ?? string.Empty,
+ Source = BuildSourceSpanFromNode(node),
});
- var location = chunkGenerator.Value.Location;
- SourceSpan? valueSpan = null;
- if (location != SourceLocation.Undefined)
- {
- valueSpan = new SourceSpan(
- location.FilePath ?? FilePath,
- location.AbsoluteIndex,
- location.LineIndex,
- location.CharacterIndex,
- chunkGenerator.Value.Value.Length);
- }
-
_builder.Add(new IntermediateToken()
{
- Content = chunkGenerator.Value,
+ Content = node.Value?.GetContent() ?? string.Empty,
Kind = TokenKind.Html,
- Source = valueSpan
+ Source = BuildSourceSpanFromNode(node.Value)
});
_builder.Pop();
+
+ return node;
}
- public override void VisitTemplateBlock(TemplateBlockChunkGenerator chunkGenerator, Block block)
+ public override SyntaxNode VisitCSharpTemplateBlock(CSharpTemplateBlockSyntax node)
{
var templateNode = new TemplateIntermediateNode();
_builder.Push(templateNode);
- VisitDefault(block);
+ var result = base.VisitCSharpTemplateBlock(node);
_builder.Pop();
@@ -503,13 +598,15 @@ public override void VisitTemplateBlock(TemplateBlockChunkGenerator chunkGenerat
var contentLength = templateNode.Children.Sum(child => child.Source?.Length ?? 0);
templateNode.Source = new SourceSpan(
- sourceRangeStart.Value.FilePath ?? FilePath,
+ sourceRangeStart.Value.FilePath ?? SourceDocument.FilePath,
sourceRangeStart.Value.AbsoluteIndex,
sourceRangeStart.Value.LineIndex,
sourceRangeStart.Value.CharacterIndex,
contentLength);
}
}
+
+ return result;
}
// CSharp expressions are broken up into blocks and spans because Razor allows Razor comments
@@ -518,19 +615,18 @@ public override void VisitTemplateBlock(TemplateBlockChunkGenerator chunkGenerat
// @DateTime.@*This is a comment*@Now
//
// We need to capture this in the IR so that we can give each piece the correct source mappings
- public override void VisitExpressionBlock(ExpressionChunkGenerator chunkGenerator, Block block)
+ public override SyntaxNode VisitCSharpExplicitExpression(CSharpExplicitExpressionSyntax node)
{
if (_builder.Current is CSharpExpressionAttributeValueIntermediateNode)
{
- VisitDefault(block);
- return;
+ return base.VisitCSharpExplicitExpression(node);
}
var expressionNode = new CSharpExpressionIntermediateNode();
_builder.Push(expressionNode);
- VisitDefault(block);
+ var result = base.VisitCSharpExplicitExpression(node);
_builder.Pop();
@@ -546,66 +642,127 @@ public override void VisitExpressionBlock(ExpressionChunkGenerator chunkGenerato
var contentLength = expressionNode.Children.Sum(child => child.Source?.Length ?? 0);
expressionNode.Source = new SourceSpan(
- sourceRangeStart.Value.FilePath ?? FilePath,
+ sourceRangeStart.Value.FilePath ?? SourceDocument.FilePath,
sourceRangeStart.Value.AbsoluteIndex,
sourceRangeStart.Value.LineIndex,
sourceRangeStart.Value.CharacterIndex,
contentLength);
}
}
+
+ return result;
}
- public override void VisitExpressionSpan(ExpressionChunkGenerator chunkGenerator, Span span)
+ public override SyntaxNode VisitCSharpImplicitExpression(CSharpImplicitExpressionSyntax node)
{
- _builder.Add(new IntermediateToken()
+ if (_builder.Current is CSharpExpressionAttributeValueIntermediateNode)
{
- Content = span.Content,
- Kind = TokenKind.CSharp,
- Source = BuildSourceSpanFromNode(span),
- });
- }
+ return base.VisitCSharpImplicitExpression(node);
+ }
- public override void VisitStatementSpan(StatementChunkGenerator chunkGenerator, Span span)
- {
- var isAttributeValue = _builder.Current is CSharpCodeAttributeValueIntermediateNode;
+ var expressionNode = new CSharpExpressionIntermediateNode();
+
+ _builder.Push(expressionNode);
- if (!isAttributeValue)
+ var result = base.VisitCSharpImplicitExpression(node);
+
+ _builder.Pop();
+
+ if (expressionNode.Children.Count > 0)
{
- var statementNode = new CSharpCodeIntermediateNode()
+ var sourceRangeStart = expressionNode
+ .Children
+ .FirstOrDefault(child => child.Source != null)
+ ?.Source;
+
+ if (sourceRangeStart != null)
{
- Source = BuildSourceSpanFromNode(span)
- };
- _builder.Push(statementNode);
+ var contentLength = expressionNode.Children.Sum(child => child.Source?.Length ?? 0);
+
+ expressionNode.Source = new SourceSpan(
+ sourceRangeStart.Value.FilePath ?? SourceDocument.FilePath,
+ sourceRangeStart.Value.AbsoluteIndex,
+ sourceRangeStart.Value.LineIndex,
+ sourceRangeStart.Value.CharacterIndex,
+ contentLength);
+ }
+ }
+
+ return result;
+ }
+
+ public override SyntaxNode VisitCSharpExpressionLiteral(CSharpExpressionLiteralSyntax node)
+ {
+ if (_builder.Current is TagHelperHtmlAttributeIntermediateNode)
+ {
+ // If we are top level in a tag helper HTML attribute, we want to be rendered as markup.
+ var markupLiteral = SyntaxFactory.MarkupTextLiteral(node.LiteralTokens).Green.CreateRed(node.Parent, node.Position);
+ return Visit(markupLiteral);
}
_builder.Add(new IntermediateToken()
{
- Content = span.Content,
+ Content = node.GetContent(),
Kind = TokenKind.CSharp,
- Source = BuildSourceSpanFromNode(span),
+ Source = BuildSourceSpanFromNode(node),
});
- if (!isAttributeValue)
+ return base.VisitCSharpExpressionLiteral(node);
+ }
+
+ public override SyntaxNode VisitCSharpStatementLiteral(CSharpStatementLiteralSyntax node)
+ {
+ var context = node.GetSpanContext();
+ if (context == null || context.ChunkGenerator is StatementChunkGenerator)
{
- _builder.Pop();
+ var isAttributeValue = _builder.Current is CSharpCodeAttributeValueIntermediateNode;
+
+ if (!isAttributeValue)
+ {
+ var statementNode = new CSharpCodeIntermediateNode()
+ {
+ Source = BuildSourceSpanFromNode(node)
+ };
+ _builder.Push(statementNode);
+ }
+
+ _builder.Add(new IntermediateToken()
+ {
+ Content = node.GetContent(),
+ Kind = TokenKind.CSharp,
+ Source = BuildSourceSpanFromNode(node),
+ });
+
+ if (!isAttributeValue)
+ {
+ _builder.Pop();
+ }
}
+
+ return base.VisitCSharpStatementLiteral(node);
}
- public override void VisitMarkupSpan(MarkupChunkGenerator chunkGenerator, Span span)
+ public override SyntaxNode VisitMarkupTextLiteral(MarkupTextLiteralSyntax node)
{
- if (span.Tokens.Count == 1)
+ var context = node.GetSpanContext();
+ if (context != null && context.ChunkGenerator == SpanChunkGenerator.Null)
{
- var token = span.Tokens[0];
+ return base.VisitMarkupTextLiteral(node);
+ }
+
+ if (node.LiteralTokens.Count == 1)
+ {
+ var token = node.LiteralTokens[0];
if (token != null &&
- token.Kind == SyntaxKind.Unknown &&
+ token.Kind == SyntaxKind.Marker &&
token.Content.Length == 0)
{
// We don't want to create IR nodes for marker tokens.
- return;
+ return base.VisitMarkupTextLiteral(node);
}
}
- var source = BuildSourceSpanFromNode(span);
+ var source = BuildSourceSpanFromNode(node);
var currentChildren = _builder.Current.Children;
if (currentChildren.Count > 0 && currentChildren[currentChildren.Count - 1] is HtmlContentIntermediateNode)
{
@@ -613,8 +770,8 @@ public override void VisitMarkupSpan(MarkupChunkGenerator chunkGenerator, Span s
if (existingHtmlContent.Source == null && source == null)
{
- Combine(existingHtmlContent, span);
- return;
+ Combine(existingHtmlContent, node);
+ return base.VisitMarkupTextLiteral(node);
}
if (source != null &&
@@ -622,8 +779,8 @@ public override void VisitMarkupSpan(MarkupChunkGenerator chunkGenerator, Span s
existingHtmlContent.Source.Value.FilePath == source.Value.FilePath &&
existingHtmlContent.Source.Value.AbsoluteIndex + existingHtmlContent.Source.Value.Length == source.Value.AbsoluteIndex)
{
- Combine(existingHtmlContent, span);
- return;
+ Combine(existingHtmlContent, node);
+ return base.VisitMarkupTextLiteral(node);
}
}
@@ -635,23 +792,20 @@ public override void VisitMarkupSpan(MarkupChunkGenerator chunkGenerator, Span s
_builder.Add(new IntermediateToken()
{
- Content = span.Content,
+ Content = node.GetContent(),
Kind = TokenKind.Html,
Source = source,
});
_builder.Pop();
+
+ return base.VisitMarkupTextLiteral(node);
}
- public override void VisitTagHelperBlock(TagHelperChunkGenerator chunkGenerator, Block block)
+ public override SyntaxNode VisitMarkupTagHelperElement(MarkupTagHelperElementSyntax node)
{
- var tagHelperBlock = block as TagHelperBlock;
- if (tagHelperBlock == null)
- {
- return;
- }
-
- var tagName = tagHelperBlock.TagName;
+ var info = node.TagHelperInfo;
+ var tagName = info.TagName;
if (_tagHelperPrefix != null)
{
tagName = tagName.Substring(_tagHelperPrefix.Length);
@@ -660,11 +814,11 @@ public override void VisitTagHelperBlock(TagHelperChunkGenerator chunkGenerator,
var tagHelperNode = new TagHelperIntermediateNode()
{
TagName = tagName,
- TagMode = tagHelperBlock.TagMode,
- Source = BuildSourceSpanFromNode(block)
+ TagMode = info.TagMode,
+ Source = BuildSourceSpanFromNode(node)
};
- foreach (var tagHelper in tagHelperBlock.Binding.Descriptors)
+ foreach (var tagHelper in info.BindingResult.Descriptors)
{
tagHelperNode.TagHelpers.Add(tagHelper);
}
@@ -673,22 +827,229 @@ public override void VisitTagHelperBlock(TagHelperChunkGenerator chunkGenerator,
_builder.Push(new TagHelperBodyIntermediateNode());
- VisitDefault(block);
+ VisitList(node.Body);
_builder.Pop(); // Pop InitializeTagHelperStructureIntermediateNode
- AddTagHelperAttributes(tagHelperBlock.Attributes, tagHelperBlock.Binding);
+ Visit(node.StartTag);
_builder.Pop(); // Pop TagHelperIntermediateNode
+
+ // No need to visit the end tag because we don't write any IR for it.
+
+ // We don't want to track attributes from a previous tag helper element.
+ _renderedBoundAttributeNames.Clear();
+
+ return node;
}
- private void Combine(HtmlContentIntermediateNode node, Span span)
+ public override SyntaxNode VisitMarkupTagHelperStartTag(MarkupTagHelperStartTagSyntax node)
+ {
+ foreach (var child in node.Children)
+ {
+ if (child is MarkupTagHelperAttributeSyntax || child is MarkupMinimizedTagHelperAttributeSyntax)
+ {
+ Visit(child);
+ }
+ }
+
+ return node;
+ }
+
+ public override SyntaxNode VisitMarkupMinimizedTagHelperAttribute(MarkupMinimizedTagHelperAttributeSyntax node)
+ {
+ if (!_featureFlags.AllowMinimizedBooleanTagHelperAttributes)
+ {
+ // Minimized attributes are not valid for non-boolean bound attributes. TagHelperBlockRewriter
+ // has already logged an error if it was a non-boolean bound attribute; so we can skip.
+ return node;
+ }
+
+ var element = node.FirstAncestorOrSelf();
+ var descriptors = element.TagHelperInfo.BindingResult.Descriptors;
+ var attributeName = node.Name.GetContent();
+ var associatedDescriptors = descriptors.Where(descriptor =>
+ descriptor.BoundAttributes.Any(attributeDescriptor => TagHelperMatchingConventions.CanSatisfyBoundAttribute(attributeName, attributeDescriptor)));
+
+ if (associatedDescriptors.Any() && _renderedBoundAttributeNames.Add(attributeName))
+ {
+ foreach (var associatedDescriptor in associatedDescriptors)
+ {
+ var associatedAttributeDescriptor = associatedDescriptor.BoundAttributes.First(a =>
+ {
+ return TagHelperMatchingConventions.CanSatisfyBoundAttribute(attributeName, a);
+ });
+
+ var expectsBooleanValue = associatedAttributeDescriptor.ExpectsBooleanValue(attributeName);
+
+ if (!expectsBooleanValue)
+ {
+ // We do not allow minimized non-boolean bound attributes.
+ return node;
+ }
+
+ var setTagHelperProperty = new TagHelperPropertyIntermediateNode()
+ {
+ AttributeName = attributeName,
+ BoundAttribute = associatedAttributeDescriptor,
+ TagHelper = associatedDescriptor,
+ AttributeStructure = node.TagHelperAttributeInfo.AttributeStructure,
+ Source = null,
+ IsIndexerNameMatch = TagHelperMatchingConventions.SatisfiesBoundAttributeIndexer(attributeName, associatedAttributeDescriptor),
+ };
+
+ _builder.Add(setTagHelperProperty);
+ }
+ }
+ else
+ {
+ var addHtmlAttribute = new TagHelperHtmlAttributeIntermediateNode()
+ {
+ AttributeName = attributeName,
+ AttributeStructure = node.TagHelperAttributeInfo.AttributeStructure
+ };
+
+ _builder.Add(addHtmlAttribute);
+ }
+
+ return node;
+ }
+
+ public override SyntaxNode VisitMarkupTagHelperAttribute(MarkupTagHelperAttributeSyntax node)
+ {
+ var element = node.FirstAncestorOrSelf();
+ var descriptors = element.TagHelperInfo.BindingResult.Descriptors;
+ var attributeName = node.Name.GetContent();
+ var attributeValueNode = node.Value;
+ var associatedDescriptors = descriptors.Where(descriptor =>
+ descriptor.BoundAttributes.Any(attributeDescriptor => TagHelperMatchingConventions.CanSatisfyBoundAttribute(attributeName, attributeDescriptor)));
+
+ if (associatedDescriptors.Any() && _renderedBoundAttributeNames.Add(attributeName))
+ {
+ foreach (var associatedDescriptor in associatedDescriptors)
+ {
+ var associatedAttributeDescriptor = associatedDescriptor.BoundAttributes.First(a =>
+ {
+ return TagHelperMatchingConventions.CanSatisfyBoundAttribute(attributeName, a);
+ });
+
+ var setTagHelperProperty = new TagHelperPropertyIntermediateNode()
+ {
+ AttributeName = attributeName,
+ BoundAttribute = associatedAttributeDescriptor,
+ TagHelper = associatedDescriptor,
+ AttributeStructure = node.TagHelperAttributeInfo.AttributeStructure,
+ Source = BuildSourceSpanFromNode(attributeValueNode),
+ IsIndexerNameMatch = TagHelperMatchingConventions.SatisfiesBoundAttributeIndexer(attributeName, associatedAttributeDescriptor),
+ };
+
+ _builder.Push(setTagHelperProperty);
+ VisitAttributeValue(attributeValueNode);
+ _builder.Pop();
+ }
+ }
+ else
+ {
+ var addHtmlAttribute = new TagHelperHtmlAttributeIntermediateNode()
+ {
+ AttributeName = attributeName,
+ AttributeStructure = node.TagHelperAttributeInfo.AttributeStructure
+ };
+
+ _builder.Push(addHtmlAttribute);
+ VisitAttributeValue(attributeValueNode);
+ _builder.Pop();
+ }
+
+ return node;
+ }
+
+ private void VisitAttributeValue(SyntaxNode node)
+ {
+ if (node == null)
+ {
+ return;
+ }
+
+
+ IReadOnlyList children = node.ChildNodes();
+ var position = node.Position;
+ if (children.First() is MarkupBlockSyntax markupBlock &&
+ markupBlock.Children.Count == 2 &&
+ markupBlock.Children[0] is MarkupTextLiteralSyntax &&
+ markupBlock.Children[1] is MarkupEphemeralTextLiteralSyntax)
+ {
+ // This is a special case when we have an attribute like attr="@@foo".
+ // In this case, we want the foo to be written out as HtmlContent and not HtmlAttributeValue.
+ Visit(markupBlock);
+ children = children.Skip(1).ToList();
+ position = children.Count > 0 ? children[0].Position : position;
+ }
+
+ if (children.All(c => c is MarkupLiteralAttributeValueSyntax))
+ {
+ var literalAttributeValueNodes = children.Cast().ToArray();
+ var valueTokens = SyntaxListBuilder.Create();
+ for (var i = 0; i < literalAttributeValueNodes.Length; i++)
+ {
+ var mergedValue = MergeAttributeValue(literalAttributeValueNodes[i]);
+ valueTokens.AddRange(mergedValue.LiteralTokens);
+ }
+ var rewritten = SyntaxFactory.MarkupTextLiteral(valueTokens.ToList()).Green.CreateRed(node.Parent, position);
+ Visit(rewritten);
+ }
+ else if (children.All(c => c is MarkupTextLiteralSyntax))
+ {
+ var builder = SyntaxListBuilder.Create();
+ var markupLiteralArray = children.Cast();
+ foreach (var literal in markupLiteralArray)
+ {
+ builder.AddRange(literal.LiteralTokens);
+ }
+ var rewritten = SyntaxFactory.MarkupTextLiteral(builder.ToList()).Green.CreateRed(node.Parent, position);
+ Visit(rewritten);
+ }
+ else if (children.All(c => c is CSharpExpressionLiteralSyntax))
+ {
+ var builder = SyntaxListBuilder.Create();
+ var expressionLiteralArray = children.Cast();
+ SpanContext context = null;
+ foreach (var literal in expressionLiteralArray)
+ {
+ context = literal.GetSpanContext();
+ builder.AddRange(literal.LiteralTokens);
+ }
+ var rewritten = SyntaxFactory.CSharpExpressionLiteral(builder.ToList()).Green.CreateRed(node.Parent, position);
+ rewritten = context != null ? rewritten.WithSpanContext(context) : rewritten;
+ Visit(rewritten);
+ }
+ else
+ {
+ Visit(node);
+ }
+ }
+
+ private MarkupTextLiteralSyntax MergeAttributeValue(MarkupLiteralAttributeValueSyntax node)
+ {
+ var valueTokens = MergeLiterals(node.Prefix?.LiteralTokens, node.Value?.LiteralTokens);
+ var rewritten = node.Prefix?.Update(valueTokens) ?? node.Value?.Update(valueTokens);
+ rewritten = (MarkupTextLiteralSyntax)rewritten?.Green.CreateRed(node, node.Position);
+ var originalContext = rewritten.GetSpanContext();
+ if (originalContext != null)
+ {
+ rewritten = rewritten.WithSpanContext(new SpanContext(new MarkupChunkGenerator(), originalContext.EditHandler));
+ }
+
+ return rewritten;
+ }
+
+ private void Combine(HtmlContentIntermediateNode node, SyntaxNode item)
{
node.Children.Add(new IntermediateToken()
{
- Content = span.Content,
+ Content = item.GetContent(),
Kind = TokenKind.Html,
- Source = BuildSourceSpanFromNode(span),
+ Source = BuildSourceSpanFromNode(item),
});
if (node.Source != null)
@@ -700,76 +1061,25 @@ private void Combine(HtmlContentIntermediateNode node, Span span)
node.Source.Value.AbsoluteIndex,
node.Source.Value.LineIndex,
node.Source.Value.CharacterIndex,
- node.Source.Value.Length + span.Content.Length);
+ node.Source.Value.Length + item.FullWidth);
}
}
- private void AddTagHelperAttributes(IList attributes, TagHelperBinding tagHelperBinding)
+ private SyntaxList MergeLiterals(params SyntaxList?[] literals)
{
- var descriptors = tagHelperBinding.Descriptors;
- var renderedBoundAttributeNames = new HashSet(StringComparer.OrdinalIgnoreCase);
- foreach (var attribute in attributes)
+ var builder = SyntaxListBuilder.Create();
+ for (var i = 0; i < literals.Length; i++)
{
- var attributeValueNode = attribute.Value;
- var associatedDescriptors = descriptors.Where(descriptor =>
- descriptor.BoundAttributes.Any(attributeDescriptor => TagHelperMatchingConventions.CanSatisfyBoundAttribute(attribute.Name, attributeDescriptor)));
-
- if (associatedDescriptors.Any() && renderedBoundAttributeNames.Add(attribute.Name))
+ var literal = literals[i];
+ if (!literal.HasValue)
{
- var isMinimizedAttribute = attributeValueNode == null;
- if (isMinimizedAttribute && !_featureFlags.AllowMinimizedBooleanTagHelperAttributes)
- {
- // Minimized attributes are not valid for non-boolean bound attributes. TagHelperBlockRewriter
- // has already logged an error if it was a non-boolean bound attribute; so we can skip.
- continue;
- }
-
- foreach (var associatedDescriptor in associatedDescriptors)
- {
- var associatedAttributeDescriptor = associatedDescriptor.BoundAttributes.First(a =>
- {
- return TagHelperMatchingConventions.CanSatisfyBoundAttribute(attribute.Name, a);
- });
-
- var expectsBooleanValue = associatedAttributeDescriptor.ExpectsBooleanValue(attribute.Name);
-
- if (isMinimizedAttribute && !expectsBooleanValue)
- {
- // We do not allow minimized non-boolean bound attributes.
- continue;
- }
-
- var setTagHelperProperty = new TagHelperPropertyIntermediateNode()
- {
- AttributeName = attribute.Name,
- BoundAttribute = associatedAttributeDescriptor,
- TagHelper = associatedDescriptor,
- AttributeStructure = attribute.AttributeStructure,
- Source = BuildSourceSpanFromNode(attributeValueNode),
- IsIndexerNameMatch = TagHelperMatchingConventions.SatisfiesBoundAttributeIndexer(attribute.Name, associatedAttributeDescriptor),
- };
-
- _builder.Push(setTagHelperProperty);
- attributeValueNode?.Accept(this);
- _builder.Pop();
- }
+ continue;
}
- else
- {
- var addHtmlAttribute = new TagHelperHtmlAttributeIntermediateNode()
- {
- AttributeName = attribute.Name,
- AttributeStructure = attribute.AttributeStructure
- };
- _builder.Push(addHtmlAttribute);
- if (attributeValueNode != null)
- {
- attributeValueNode.Accept(this);
- }
- _builder.Pop();
- }
+ builder.AddRange(literal.Value);
}
+
+ return builder.ToList();
}
}
@@ -827,8 +1137,8 @@ public override void VisitDirective(DirectiveIntermediateNode node)
}
}
- private static bool IsMalformed(List diagnostics)
- => diagnostics.Count > 0 && diagnostics.Any(diagnostic => diagnostic.Severity == RazorDiagnosticSeverity.Error);
+ private static bool IsMalformed(IEnumerable diagnostics)
+ => diagnostics.Any(diagnostic => diagnostic.Severity == RazorDiagnosticSeverity.Error);
}
#pragma warning restore CS0618 // Type or member is obsolete
}
diff --git a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorParsingPhase.cs b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorParsingPhase.cs
index 826420127..cff35e508 100644
--- a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorParsingPhase.cs
+++ b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorParsingPhase.cs
@@ -16,13 +16,13 @@ protected override void OnIntialized()
protected override void ExecuteCore(RazorCodeDocument codeDocument)
{
var options = codeDocument.GetParserOptions() ??_optionsFeature.GetOptions();
- var syntaxTree = RazorSyntaxTree.Parse(codeDocument.Source, options);
+ var syntaxTree = RazorSyntaxTree.Parse(codeDocument.Source, options, legacy: false);
codeDocument.SetSyntaxTree(syntaxTree);
var importSyntaxTrees = new RazorSyntaxTree[codeDocument.Imports.Count];
for (var i = 0; i < codeDocument.Imports.Count; i++)
{
- importSyntaxTrees[i] = RazorSyntaxTree.Parse(codeDocument.Imports[i], options);
+ importSyntaxTrees[i] = RazorSyntaxTree.Parse(codeDocument.Imports[i], options, legacy: false);
}
codeDocument.SetImportSyntaxTrees(importSyntaxTrees);
}
diff --git a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorSyntaxTree.cs b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorSyntaxTree.cs
index 4b6d43fe0..ec9522f3c 100644
--- a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorSyntaxTree.cs
+++ b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorSyntaxTree.cs
@@ -1,15 +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;
using System.Collections.Generic;
using Microsoft.AspNetCore.Razor.Language.Legacy;
+using Microsoft.AspNetCore.Razor.Language.Syntax;
namespace Microsoft.AspNetCore.Razor.Language
{
internal class DefaultRazorSyntaxTree : RazorSyntaxTree
{
public DefaultRazorSyntaxTree(
- Block root,
+ SyntaxNode root,
RazorSourceDocument source,
IReadOnlyList diagnostics,
RazorParserOptions options)
@@ -24,8 +26,11 @@ public DefaultRazorSyntaxTree(
public override RazorParserOptions Options { get; }
- internal override Block Root { get; }
+ internal override SyntaxNode Root { get; }
public override RazorSourceDocument Source { get; }
+
+ // Temporary
+ internal override Block LegacyRoot => throw new NotImplementedException();
}
}
diff --git a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorTagHelperBinderPhase.cs b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorTagHelperBinderPhase.cs
index aa0668a53..74fd5b323 100644
--- a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorTagHelperBinderPhase.cs
+++ b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorTagHelperBinderPhase.cs
@@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Razor.Language.Legacy;
+using Microsoft.AspNetCore.Razor.Language.Syntax;
namespace Microsoft.AspNetCore.Razor.Language
{
@@ -15,6 +16,12 @@ protected override void ExecuteCore(RazorCodeDocument codeDocument)
var syntaxTree = codeDocument.GetSyntaxTree();
ThrowForMissingDocumentDependency(syntaxTree);
+ if (syntaxTree is LegacyRazorSyntaxTree)
+ {
+ LegacyExecuteCore(codeDocument);
+ return;
+ }
+
var descriptors = codeDocument.GetTagHelpers();
if (descriptors == null)
{
@@ -39,11 +46,62 @@ protected override void ExecuteCore(RazorCodeDocument codeDocument)
for (var i = 0; i < imports.Count; i++)
{
var import = imports[i];
- visitor.VisitBlock(import.Root);
+ visitor.Visit(import.Root);
+ }
+ }
+
+ visitor.Visit(syntaxTree.Root);
+
+ var tagHelperPrefix = visitor.TagHelperPrefix;
+ descriptors = visitor.Matches.ToArray();
+
+ var context = TagHelperDocumentContext.Create(tagHelperPrefix, descriptors);
+ codeDocument.SetTagHelperContext(context);
+
+ if (descriptors.Count == 0)
+ {
+ // No descriptors, no-op.
+ return;
+ }
+
+ var rewrittenSyntaxTree = TagHelperParseTreeRewriter.Rewrite(syntaxTree, tagHelperPrefix, descriptors);
+
+ codeDocument.SetSyntaxTree(rewrittenSyntaxTree);
+ }
+
+ private void LegacyExecuteCore(RazorCodeDocument codeDocument)
+ {
+ var syntaxTree = codeDocument.GetSyntaxTree();
+
+ var descriptors = codeDocument.GetTagHelpers();
+ if (descriptors == null)
+ {
+ var feature = Engine.Features.OfType().FirstOrDefault();
+ if (feature == null)
+ {
+ // No feature, nothing to do.
+ return;
+ }
+
+ descriptors = feature.GetDescriptors();
+ }
+
+ // We need to find directives in all of the *imports* as well as in the main razor file
+ //
+ // The imports come logically before the main razor file and are in the order they
+ // should be processed.
+ var visitor = new LegacyDirectiveVisitor(descriptors);
+ var imports = codeDocument.GetImportSyntaxTrees();
+ if (imports != null)
+ {
+ for (var i = 0; i < imports.Count; i++)
+ {
+ var import = imports[i];
+ visitor.VisitBlock(import.LegacyRoot);
}
}
- visitor.VisitBlock(syntaxTree.Root);
+ visitor.VisitBlock(syntaxTree.LegacyRoot);
var tagHelperPrefix = visitor.TagHelperPrefix;
descriptors = visitor.Matches.ToArray();
@@ -58,9 +116,9 @@ protected override void ExecuteCore(RazorCodeDocument codeDocument)
}
var errorSink = new ErrorSink();
- var rewriter = new TagHelperParseTreeRewriter(tagHelperPrefix, descriptors, syntaxTree.Options.FeatureFlags);
+ var rewriter = new LegacyTagHelperParseTreeRewriter(tagHelperPrefix, descriptors, syntaxTree.Options.FeatureFlags);
- var root = syntaxTree.Root;
+ var root = syntaxTree.LegacyRoot;
root = rewriter.Rewrite(root, errorSink);
var errorList = new List();
@@ -114,8 +172,7 @@ private IReadOnlyList CombineErrors(IReadOnlyList _tagHelpers;
@@ -128,6 +185,110 @@ public DirectiveVisitor(IReadOnlyList tagHelpers)
public HashSet Matches { get; } = new HashSet();
+ public override SyntaxNode VisitRazorDirective(RazorDirectiveSyntax node)
+ {
+ var descendantLiterals = node.DescendantNodes();
+ foreach (var child in descendantLiterals)
+ {
+ if (!(child is CSharpStatementLiteralSyntax literal))
+ {
+ continue;
+ }
+
+ var context = literal.GetSpanContext();
+ if (context == null)
+ {
+ // We can't find a chunk generator.
+ continue;
+ }
+ else if (context.ChunkGenerator is AddTagHelperChunkGenerator addTagHelper)
+ {
+ if (addTagHelper.AssemblyName == null)
+ {
+ // Skip this one, it's an error
+ continue;
+ }
+
+ if (!AssemblyContainsTagHelpers(addTagHelper.AssemblyName, _tagHelpers))
+ {
+ // No tag helpers in the assembly.
+ continue;
+ }
+
+ for (var i = 0; i < _tagHelpers.Count; i++)
+ {
+ var tagHelper = _tagHelpers[i];
+ if (MatchesDirective(tagHelper, addTagHelper.TypePattern, addTagHelper.AssemblyName))
+ {
+ Matches.Add(tagHelper);
+ }
+ }
+ }
+ else if (context.ChunkGenerator is RemoveTagHelperChunkGenerator removeTagHelper)
+ {
+ if (removeTagHelper.AssemblyName == null)
+ {
+ // Skip this one, it's an error
+ continue;
+ }
+
+
+ if (!AssemblyContainsTagHelpers(removeTagHelper.AssemblyName, _tagHelpers))
+ {
+ // No tag helpers in the assembly.
+ continue;
+ }
+
+ for (var i = 0; i < _tagHelpers.Count; i++)
+ {
+ var tagHelper = _tagHelpers[i];
+ if (MatchesDirective(tagHelper, removeTagHelper.TypePattern, removeTagHelper.AssemblyName))
+ {
+ Matches.Remove(tagHelper);
+ }
+ }
+ }
+ else if (context.ChunkGenerator is TagHelperPrefixDirectiveChunkGenerator tagHelperPrefix)
+ {
+ if (!string.IsNullOrEmpty(tagHelperPrefix.DirectiveText))
+ {
+ // We only expect to see a single one of these per file, but that's enforced at another level.
+ TagHelperPrefix = tagHelperPrefix.DirectiveText;
+ }
+ }
+ }
+
+ return base.VisitRazorDirective(node);
+ }
+
+ private bool AssemblyContainsTagHelpers(string assemblyName, IReadOnlyList tagHelpers)
+ {
+ for (var i = 0; i < tagHelpers.Count; i++)
+ {
+ if (string.Equals(tagHelpers[i].AssemblyName, assemblyName, StringComparison.Ordinal))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+
+ // Internal for testing.
+ internal class LegacyDirectiveVisitor : ParserVisitor
+ {
+ private IReadOnlyList _tagHelpers;
+
+ public LegacyDirectiveVisitor(IReadOnlyList tagHelpers)
+ {
+ _tagHelpers = tagHelpers;
+ }
+
+ public string TagHelperPrefix { get; private set; }
+
+ public HashSet Matches { get; } = new HashSet();
+
public override void VisitAddTagHelperSpan(AddTagHelperChunkGenerator chunkGenerator, Span span)
{
if (chunkGenerator.AssemblyName == null)
diff --git a/src/Microsoft.AspNetCore.Razor.Language/HtmlNodeOptimizationPass.cs b/src/Microsoft.AspNetCore.Razor.Language/HtmlNodeOptimizationPass.cs
index ea3d75455..a7be0462f 100644
--- a/src/Microsoft.AspNetCore.Razor.Language/HtmlNodeOptimizationPass.cs
+++ b/src/Microsoft.AspNetCore.Razor.Language/HtmlNodeOptimizationPass.cs
@@ -22,14 +22,28 @@ public RazorSyntaxTree Execute(RazorCodeDocument codeDocument, RazorSyntaxTree s
throw new ArgumentNullException(nameof(syntaxTree));
}
- var conditionalAttributeCollapser = new ConditionalAttributeCollapser();
- var rewritten = conditionalAttributeCollapser.Rewrite(syntaxTree.Root);
+ if (syntaxTree is LegacyRazorSyntaxTree)
+ {
+ return LegacyExecute(codeDocument, syntaxTree);
+ }
+
+ var whitespaceRewriter = new WhitespaceRewriter();
+ var rewritten = whitespaceRewriter.Visit(syntaxTree.Root);
+
+ var rewrittenSyntaxTree = RazorSyntaxTree.Create(rewritten, syntaxTree.Source, syntaxTree.Diagnostics, syntaxTree.Options);
+ return rewrittenSyntaxTree;
+ }
+
+ private RazorSyntaxTree LegacyExecute(RazorCodeDocument codeDocument, RazorSyntaxTree syntaxTree)
+ {
+ var conditionalAttributeCollapser = new LegacyConditionalAttributeCollapser();
+ var rewritten = conditionalAttributeCollapser.Rewrite(syntaxTree.LegacyRoot);
- var whitespaceRewriter = new WhiteSpaceRewriter();
+ var whitespaceRewriter = new LegacyWhitespaceRewriter();
rewritten = whitespaceRewriter.Rewrite(rewritten);
var rewrittenSyntaxTree = RazorSyntaxTree.Create(rewritten, syntaxTree.Source, syntaxTree.Diagnostics, syntaxTree.Options);
return rewrittenSyntaxTree;
}
}
-}
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpCodeParser.cs b/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpCodeParser.cs
index 126970945..6a08e68d8 100644
--- a/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpCodeParser.cs
+++ b/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpCodeParser.cs
@@ -9,7 +9,7 @@
namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
- internal class CSharpCodeParser : TokenizerBackedParser
+ internal partial class CSharpCodeParser : TokenizerBackedParser
{
private static HashSet InvalidNonWhitespaceNameCharacters = new HashSet(new[]
{
@@ -95,6 +95,10 @@ public CSharpCodeParser(IEnumerable directives, ParserConte
SetUpKeywords();
SetupDirectives(directives);
SetUpExpressions();
+
+ SetupKeywordParsers();
+ SetupExpressionParsers();
+ SetupDirectiveParsers(directives);
}
public HtmlMarkupParser HtmlParser { get; set; }
@@ -193,7 +197,7 @@ protected static Func IsSpacingToken(bool includeNewLines, bo
(includeComments && token.Kind == SyntaxKind.CSharpComment);
}
- public override void ParseBlock()
+ public override void ParseBlock1()
{
using (PushSpanConfig(DefaultSpanConfig))
{
@@ -268,8 +272,8 @@ private void AtTransition(SyntaxToken current)
private void AfterTransition()
{
- using (PushSpanConfig(DefaultSpanConfig))
- {
+ //using (PushSpanConfig(DefaultSpanConfig))
+ //{
EnsureCurrent();
try
{
@@ -367,7 +371,7 @@ private void AfterTransition()
// Always put current character back in the buffer for the next parser.
PutCurrentBack();
}
- }
+ //}
}
private void VerbatimBlock()
@@ -553,17 +557,17 @@ private bool MethodCallOrArrayIndex(AcceptedCharactersInternal acceptedCharacter
return false;
}
- protected void CompleteBlock()
+ protected void CompleteBlock1()
{
- CompleteBlock(insertMarkerIfNecessary: true);
+ CompleteBlock1(insertMarkerIfNecessary: true);
}
- protected void CompleteBlock(bool insertMarkerIfNecessary)
+ protected void CompleteBlock1(bool insertMarkerIfNecessary)
{
- CompleteBlock(insertMarkerIfNecessary, captureWhitespaceToEndOfLine: insertMarkerIfNecessary);
+ CompleteBlock1(insertMarkerIfNecessary, captureWhitespaceToEndOfLine: insertMarkerIfNecessary);
}
- protected void CompleteBlock(bool insertMarkerIfNecessary, bool captureWhitespaceToEndOfLine)
+ protected void CompleteBlock1(bool insertMarkerIfNecessary, bool captureWhitespaceToEndOfLine)
{
if (insertMarkerIfNecessary && Context.Builder.LastAcceptedCharacters != AcceptedCharactersInternal.Any)
{
@@ -580,7 +584,7 @@ protected void CompleteBlock(bool insertMarkerIfNecessary, bool captureWhitespac
!Context.DesignTimeMode &&
!IsNested)
{
- CaptureWhitespaceAtEndOfCodeOnlyLine();
+ CaptureWhitespaceAtEndOfCodeOnlyLine1();
}
else
{
@@ -588,7 +592,7 @@ protected void CompleteBlock(bool insertMarkerIfNecessary, bool captureWhitespac
}
}
- private void CaptureWhitespaceAtEndOfCodeOnlyLine()
+ private void CaptureWhitespaceAtEndOfCodeOnlyLine1()
{
var whitespace = ReadWhile(token => token.Kind == SyntaxKind.Whitespace);
if (At(SyntaxKind.NewLine))
@@ -639,7 +643,7 @@ private void ExplicitExpression()
// If necessary, put an empty-content marker token here
if (Span.Tokens.Count == 0)
{
- Accept(SyntaxFactory.Token(SyntaxKind.Unknown, string.Empty));
+ Accept(SyntaxFactory.Token(SyntaxKind.Marker, string.Empty));
}
// Output the content span and then capture the ")"
@@ -652,7 +656,7 @@ private void ExplicitExpression()
}
Span.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.None;
Span.ChunkGenerator = SpanChunkGenerator.Null;
- CompleteBlock(insertMarkerIfNecessary: false);
+ CompleteBlock1(insertMarkerIfNecessary: false); // This is unnecessary
Output(SpanKindInternal.MetaCode);
}
@@ -675,12 +679,12 @@ private void Template()
private void OtherParserBlock()
{
- ParseWithOtherParser(p => p.ParseBlock());
+ ParseWithOtherParser(p => p.ParseBlock1());
}
private void SectionBlock(string left, string right, bool caseSensitive)
{
- ParseWithOtherParser(p => p.ParseRazorBlock(Tuple.Create(left, right), caseSensitive));
+ ParseWithOtherParser(p => p.ParseRazorBlock1(Tuple.Create(left, right), caseSensitive));
}
private void NestedBlock()
@@ -691,7 +695,7 @@ private void NestedBlock()
IsNested = true;
using (PushSpanConfig())
{
- ParseBlock();
+ ParseBlock1();
}
Span.Start = CurrentLocation;
@@ -700,15 +704,6 @@ private void NestedBlock()
NextToken();
}
- protected override bool IsAtEmbeddedTransition(bool allowTemplatesAndComments, bool allowTransitions)
- {
- // No embedded transitions in C#, so ignore that param
- return allowTemplatesAndComments
- && ((Language.IsTransition(CurrentToken)
- && NextIs(SyntaxKind.LessThan, SyntaxKind.Colon, SyntaxKind.DoubleColon))
- || Language.IsCommentStart(CurrentToken));
- }
-
protected override void HandleEmbeddedTransition()
{
if (Language.IsTransition(CurrentToken))
@@ -770,7 +765,7 @@ protected virtual void ReservedDirective(bool topLevel)
Span.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.None;
Span.ChunkGenerator = SpanChunkGenerator.Null;
Context.Builder.CurrentBlock.Type = BlockKindInternal.Directive;
- CompleteBlock();
+ CompleteBlock1();
Output(SpanKindInternal.MetaCode);
}
@@ -802,7 +797,7 @@ private void DoStatement(bool topLevel)
WhileClause();
if (topLevel)
{
- CompleteBlock();
+ CompleteBlock1();
}
}
@@ -859,7 +854,7 @@ private void UsingKeyword(bool topLevel)
if (topLevel)
{
- CompleteBlock();
+ CompleteBlock1();
}
}
@@ -1070,7 +1065,7 @@ private void TryStatement(bool topLevel)
AfterTryClause();
if (topLevel)
{
- CompleteBlock();
+ CompleteBlock1();
}
}
@@ -1081,7 +1076,7 @@ private void IfStatement(bool topLevel)
AfterIfClause();
if (topLevel)
{
- CompleteBlock();
+ CompleteBlock1();
}
}
@@ -1228,7 +1223,7 @@ private void ConditionalBlock(bool topLevel)
ConditionalBlock(block);
if (topLevel)
{
- CompleteBlock();
+ CompleteBlock1();
}
}
@@ -1353,7 +1348,7 @@ private void HandleStatement(Block block, SyntaxKind type)
switch (type)
{
case SyntaxKind.RazorCommentTransition:
- Output(SpanKindInternal.Code);
+ Output(SpanKindInternal.Code); // Not needed
RazorComment();
Statement(block);
break;
@@ -1645,7 +1640,7 @@ private void HandleDirective(DirectiveDescriptor descriptor)
Output(SpanKindInternal.MetaCode, AcceptedCharactersInternal.None);
// Even if an error was logged do not bail out early. If a directive was used incorrectly it doesn't mean it can't be parsed.
- ValidateDirectiveUsage(descriptor);
+ ValidateDirectiveUsage1(descriptor);
for (var i = 0; i < descriptor.Tokens.Count; i++)
{
@@ -1803,7 +1798,7 @@ private void HandleDirective(DirectiveDescriptor descriptor)
using (PushSpanConfig())
{
- HtmlParser.ParseRazorBlock(Tuple.Create("{", "}"), caseSensitive: true);
+ HtmlParser.ParseRazorBlock1(Tuple.Create("{", "}"), caseSensitive: true);
}
Span.Start = CurrentLocation;
@@ -1847,7 +1842,7 @@ private void HandleDirective(DirectiveDescriptor descriptor)
}
- private void ValidateDirectiveUsage(DirectiveDescriptor descriptor)
+ private void ValidateDirectiveUsage1(DirectiveDescriptor descriptor)
{
if (descriptor.Usage == DirectiveUsage.FileScopedSinglyOccurring)
{
@@ -1902,7 +1897,7 @@ private void ParseDirectiveBlock(DirectiveDescriptor descriptor, Action
{
- var parsedDirective = ParseDirective(lookupText, Span.Start, TagHelperDirectiveType.AddTagHelper, errors);
+ var parsedDirective = ParseDirective1(lookupText, Span.Start, TagHelperDirectiveType.AddTagHelper, errors);
return new AddTagHelperChunkGenerator(
lookupText,
@@ -2072,7 +2067,7 @@ protected virtual void RemoveTagHelperDirective()
SyntaxConstants.CSharp.RemoveTagHelperKeyword,
(lookupText, errors) =>
{
- var parsedDirective = ParseDirective(lookupText, Span.Start, TagHelperDirectiveType.RemoveTagHelper, errors);
+ var parsedDirective = ParseDirective1(lookupText, Span.Start, TagHelperDirectiveType.RemoveTagHelper, errors);
return new RemoveTagHelperChunkGenerator(
lookupText,
@@ -2163,7 +2158,7 @@ private void TagHelperDirective(string keyword, Func, CSharpTransitionSyntax>> _keywordParserMap = new Dictionary, CSharpTransitionSyntax>>();
+ private Dictionary, CSharpTransitionSyntax>> _directiveParserMap = new Dictionary, CSharpTransitionSyntax>>(StringComparer.Ordinal);
+
+ public CSharpCodeBlockSyntax ParseBlock()
+ {
+ if (Context == null)
+ {
+ throw new InvalidOperationException(Resources.Parser_Context_Not_Set);
+ }
+
+ if (EndOfFile)
+ {
+ // Nothing to parse.
+ return null;
+ }
+
+ using (var pooledResult = Pool.Allocate())
+ using (PushSpanContextConfig(DefaultSpanContextConfig))
+ {
+ var builder = pooledResult.Builder;
+ try
+ {
+ NextToken();
+
+ // Unless changed, the block is a statement block
+ AcceptTokenWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
+ builder.Add(OutputTokensAsStatementLiteral());
+
+ // We are usually called when the other parser sees a transition '@'. Look for it.
+ SyntaxToken transitionToken = null;
+ if (At(SyntaxKind.StringLiteral) &&
+ CurrentToken.Content.Length > 0 &&
+ CurrentToken.Content[0] == SyntaxConstants.TransitionCharacter)
+ {
+ var split = Language.SplitToken(CurrentToken, 1, SyntaxKind.Transition);
+ transitionToken = split.Item1;
+
+ // Back up to the end of the transition
+ Context.Source.Position -= split.Item2.Content.Length;
+ NextToken();
+ }
+ else if (At(SyntaxKind.Transition))
+ {
+ transitionToken = EatCurrentToken();
+ }
+
+ if (transitionToken == null)
+ {
+ transitionToken = SyntaxFactory.MissingToken(SyntaxKind.Transition);
+ }
+
+ SpanContext.ChunkGenerator = SpanChunkGenerator.Null;
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.None;
+ var transition = GetNodeWithSpanContext(SyntaxFactory.CSharpTransition(transitionToken));
+
+ if (At(SyntaxKind.LeftBrace))
+ {
+ var statementBody = ParseStatementBody();
+ var statement = SyntaxFactory.CSharpStatement(transition, statementBody);
+ builder.Add(statement);
+ }
+ else if (At(SyntaxKind.LeftParenthesis))
+ {
+ var expressionBody = ParseExplicitExpressionBody();
+ var expression = SyntaxFactory.CSharpExplicitExpression(transition, expressionBody);
+ builder.Add(expression);
+ }
+ else if (At(SyntaxKind.Identifier))
+ {
+ if (!TryParseDirective(builder, transition, CurrentToken.Content))
+ {
+ if (string.Equals(
+ CurrentToken.Content,
+ SyntaxConstants.CSharp.HelperKeyword,
+ StringComparison.Ordinal))
+ {
+ var diagnostic = RazorDiagnosticFactory.CreateParsing_HelperDirectiveNotAvailable(
+ new SourceSpan(CurrentStart, CurrentToken.Content.Length));
+ CurrentToken.SetDiagnostics(new[] { diagnostic });
+ Context.ErrorSink.OnError(diagnostic);
+ }
+
+ var implicitExpressionBody = ParseImplicitExpressionBody();
+ var implicitExpression = SyntaxFactory.CSharpImplicitExpression(transition, implicitExpressionBody);
+ builder.Add(implicitExpression);
+ }
+ }
+ else if (At(SyntaxKind.Keyword))
+ {
+ if (!TryParseDirective(builder, transition, CurrentToken.Content) &&
+ !TryParseKeyword(builder, transition))
+ {
+ // Not a directive or a special keyword. Just parse as an implicit expression.
+ var implicitExpressionBody = ParseImplicitExpressionBody();
+ var implicitExpression = SyntaxFactory.CSharpImplicitExpression(transition, implicitExpressionBody);
+ builder.Add(implicitExpression);
+ }
+
+ builder.Add(OutputTokensAsStatementLiteral());
+ }
+ else
+ {
+ // Invalid character
+ SpanContext.ChunkGenerator = new ExpressionChunkGenerator();
+ SpanContext.EditHandler = new ImplicitExpressionEditHandler(
+ Language.TokenizeString,
+ CurrentKeywords,
+ acceptTrailingDot: IsNested)
+ {
+ AcceptedCharacters = AcceptedCharactersInternal.NonWhitespace
+ };
+
+ AcceptMarkerTokenIfNecessary();
+ var expressionLiteral = SyntaxFactory.CSharpCodeBlock(OutputTokensAsExpressionLiteral());
+ var expressionBody = SyntaxFactory.CSharpImplicitExpressionBody(expressionLiteral);
+ var expressionBlock = SyntaxFactory.CSharpImplicitExpression(transition, expressionBody);
+ builder.Add(expressionBlock);
+
+ if (At(SyntaxKind.Whitespace) || At(SyntaxKind.NewLine))
+ {
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_UnexpectedWhiteSpaceAtStartOfCodeBlock(
+ new SourceSpan(CurrentStart, CurrentToken.Content.Length)));
+ }
+ else if (EndOfFile)
+ {
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_UnexpectedEndOfFileAtStartOfCodeBlock(
+ new SourceSpan(CurrentStart, contentLength: 1 /* end of file */)));
+ }
+ else
+ {
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_UnexpectedCharacterAtStartOfCodeBlock(
+ new SourceSpan(CurrentStart, CurrentToken.Content.Length),
+ CurrentToken.Content));
+ }
+ }
+
+ Debug.Assert(TokenBuilder.Count == 0, "We should not have any tokens left.");
+
+ var codeBlock = SyntaxFactory.CSharpCodeBlock(builder.ToList());
+ return codeBlock;
+ }
+ finally
+ {
+ // Always put current character back in the buffer for the next parser.
+ PutCurrentBack();
+ }
+ }
+ }
+
+ private CSharpExplicitExpressionBodySyntax ParseExplicitExpressionBody()
+ {
+ var block = new Block(Resources.BlockName_ExplicitExpression, CurrentStart);
+ Assert(SyntaxKind.LeftParenthesis);
+ var leftParenToken = EatCurrentToken();
+ var leftParen = OutputAsMetaCode(leftParenToken);
+
+ using (var pooledResult = Pool.Allocate())
+ {
+ var expressionBuilder = pooledResult.Builder;
+ using (PushSpanContextConfig(ExplicitExpressionSpanContextConfig))
+ {
+ var success = BalanceToken(
+ expressionBuilder,
+ BalancingModes.BacktrackOnFailure |
+ BalancingModes.NoErrorOnFailure |
+ BalancingModes.AllowCommentsAndTemplates,
+ SyntaxKind.LeftParenthesis,
+ SyntaxKind.RightParenthesis,
+ block.Start);
+
+ if (!success)
+ {
+ AcceptTokenUntil(SyntaxKind.LessThan);
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_ExpectedEndOfBlockBeforeEOF(
+ new SourceSpan(block.Start, contentLength: 1 /* ( */), block.Name, ")", "("));
+ }
+
+ // If necessary, put an empty-content marker token here
+ AcceptMarkerTokenIfNecessary();
+ expressionBuilder.Add(OutputTokensAsExpressionLiteral());
+ }
+
+ var expressionBlock = SyntaxFactory.CSharpCodeBlock(expressionBuilder.ToList());
+
+ RazorMetaCodeSyntax rightParen = null;
+ if (At(SyntaxKind.RightParenthesis))
+ {
+ rightParen = OutputAsMetaCode(EatCurrentToken());
+ }
+ else
+ {
+ var missingToken = SyntaxFactory.MissingToken(SyntaxKind.RightParenthesis);
+ rightParen = OutputAsMetaCode(missingToken, SpanContext.EditHandler.AcceptedCharacters);
+ }
+ if (!EndOfFile)
+ {
+ PutCurrentBack();
+ }
+
+ return SyntaxFactory.CSharpExplicitExpressionBody(leftParen, expressionBlock, rightParen);
+ }
+ }
+
+ private CSharpImplicitExpressionBodySyntax ParseImplicitExpressionBody(bool async = false)
+ {
+ var accepted = AcceptedCharactersInternal.NonWhitespace;
+ if (async)
+ {
+ // Async implicit expressions include the "await" keyword and therefore need to allow spaces to
+ // separate the "await" and the following code.
+ accepted = AcceptedCharactersInternal.AnyExceptNewline;
+ }
+
+ using (var pooledResult = Pool.Allocate())
+ {
+ var expressionBuilder = pooledResult.Builder;
+ ParseImplicitExpression(expressionBuilder, accepted);
+ var codeBlock = SyntaxFactory.CSharpCodeBlock(expressionBuilder.ToList());
+ return SyntaxFactory.CSharpImplicitExpressionBody(codeBlock);
+ }
+ }
+
+ private void ParseImplicitExpression(in SyntaxListBuilder builder, AcceptedCharactersInternal acceptedCharacters)
+ {
+ using (PushSpanContextConfig(spanContext =>
+ {
+ spanContext.EditHandler = new ImplicitExpressionEditHandler(
+ Language.TokenizeString,
+ Keywords,
+ acceptTrailingDot: IsNested);
+ spanContext.EditHandler.AcceptedCharacters = acceptedCharacters;
+ spanContext.ChunkGenerator = new ExpressionChunkGenerator();
+ }))
+ {
+ do
+ {
+ if (AtIdentifier(allowKeywords: true))
+ {
+ AcceptTokenAndMoveNext();
+ }
+ }
+ while (ParseMethodCallOrArrayIndex(builder, acceptedCharacters));
+
+ PutCurrentBack();
+ builder.Add(OutputTokensAsExpressionLiteral());
+ }
+ }
+
+ private bool ParseMethodCallOrArrayIndex(in SyntaxListBuilder builder, AcceptedCharactersInternal acceptedCharacters)
+ {
+ if (!EndOfFile)
+ {
+ if (CurrentToken.Kind == SyntaxKind.LeftParenthesis ||
+ CurrentToken.Kind == SyntaxKind.LeftBracket)
+ {
+ // If we end within "(", whitespace is fine
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.Any;
+
+ SyntaxKind right;
+ bool success;
+
+ using (PushSpanContextConfig((spanContext, prev) =>
+ {
+ prev(spanContext);
+ spanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.Any;
+ }))
+ {
+ right = Language.FlipBracket(CurrentToken.Kind);
+ success = BalanceToken(builder, BalancingModes.BacktrackOnFailure | BalancingModes.AllowCommentsAndTemplates);
+ }
+
+ if (!success)
+ {
+ AcceptTokenUntil(SyntaxKind.LessThan);
+ }
+ if (At(right))
+ {
+ AcceptTokenAndMoveNext();
+
+ // At the ending brace, restore the initial accepted characters.
+ SpanContext.EditHandler.AcceptedCharacters = acceptedCharacters;
+ }
+ return ParseMethodCallOrArrayIndex(builder, acceptedCharacters);
+ }
+ if (At(SyntaxKind.QuestionMark))
+ {
+ var next = Lookahead(count: 1);
+
+ if (next != null)
+ {
+ if (next.Kind == SyntaxKind.Dot)
+ {
+ // Accept null conditional dot operator (?.).
+ AcceptTokenAndMoveNext();
+ AcceptTokenAndMoveNext();
+
+ // If the next piece after the ?. is a keyword or identifier then we want to continue.
+ return At(SyntaxKind.Identifier) || At(SyntaxKind.Keyword);
+ }
+ else if (next.Kind == SyntaxKind.LeftBracket)
+ {
+ // We're at the ? for a null conditional bracket operator (?[).
+ AcceptTokenAndMoveNext();
+
+ // Accept the [ and any content inside (it will attempt to balance).
+ return ParseMethodCallOrArrayIndex(builder, acceptedCharacters);
+ }
+ }
+ }
+ else if (At(SyntaxKind.Dot))
+ {
+ var dot = CurrentToken;
+ if (NextToken())
+ {
+ if (At(SyntaxKind.Identifier) || At(SyntaxKind.Keyword))
+ {
+ // Accept the dot and return to the start
+ AcceptToken(dot);
+ return true; // continue
+ }
+ else
+ {
+ // Put the token back
+ PutCurrentBack();
+ }
+ }
+ if (!IsNested)
+ {
+ // Put the "." back
+ PutBack(dot);
+ }
+ else
+ {
+ AcceptToken(dot);
+ }
+ }
+ else if (!At(SyntaxKind.Whitespace) && !At(SyntaxKind.NewLine))
+ {
+ PutCurrentBack();
+ }
+ }
+
+ // Implicit Expression is complete
+ return false;
+ }
+
+ private CSharpStatementBodySyntax ParseStatementBody(Block block = null)
+ {
+ Assert(SyntaxKind.LeftBrace);
+ block = block ?? new Block(Resources.BlockName_Code, CurrentStart);
+ var leftBrace = OutputAsMetaCode(GetExpectedToken(SyntaxKind.LeftBrace));
+ CSharpCodeBlockSyntax codeBlock = null;
+ using (var pooledResult = Pool.Allocate())
+ {
+ var builder = pooledResult.Builder;
+ // Set up auto-complete and parse the code block
+ var editHandler = new AutoCompleteEditHandler(Language.TokenizeString);
+ SpanContext.EditHandler = editHandler;
+ ParseCodeBlock(builder, block, acceptTerminatingBrace: false);
+
+ EnsureCurrent();
+ SpanContext.ChunkGenerator = new StatementChunkGenerator();
+ AcceptMarkerTokenIfNecessary();
+ if (!At(SyntaxKind.RightBrace))
+ {
+ editHandler.AutoCompleteString = "}";
+ }
+ builder.Add(OutputTokensAsStatementLiteral());
+
+ codeBlock = SyntaxFactory.CSharpCodeBlock(builder.ToList());
+ }
+
+ RazorMetaCodeSyntax rightBrace = null;
+ if (At(SyntaxKind.RightBrace))
+ {
+ rightBrace = OutputAsMetaCode(EatCurrentToken());
+ }
+ else
+ {
+ rightBrace = OutputAsMetaCode(
+ SyntaxFactory.MissingToken(SyntaxKind.RightBrace),
+ SpanContext.EditHandler.AcceptedCharacters);
+ }
+
+ if (!IsNested)
+ {
+ EnsureCurrent();
+ if (At(SyntaxKind.NewLine) ||
+ (At(SyntaxKind.Whitespace) && NextIs(SyntaxKind.NewLine)))
+ {
+ Context.NullGenerateWhitespaceAndNewLine = true;
+ }
+ }
+
+ return SyntaxFactory.CSharpStatementBody(leftBrace, codeBlock, rightBrace);
+ }
+
+ private void ParseCodeBlock(in SyntaxListBuilder builder, Block block, bool acceptTerminatingBrace = true)
+ {
+ EnsureCurrent();
+ while (!EndOfFile && !At(SyntaxKind.RightBrace))
+ {
+ // Parse a statement, then return here
+ ParseStatement(builder, block: block);
+ EnsureCurrent();
+ }
+
+ if (EndOfFile)
+ {
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_ExpectedEndOfBlockBeforeEOF(
+ new SourceSpan(block.Start, contentLength: 1 /* { OR } */), block.Name, "}", "{"));
+ }
+ else if (acceptTerminatingBrace)
+ {
+ Assert(SyntaxKind.RightBrace);
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.None;
+ AcceptTokenAndMoveNext();
+ }
+ }
+
+ private void ParseStatement(in SyntaxListBuilder builder, Block block)
+ {
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.Any;
+ // Accept whitespace but always keep the last whitespace node so we can put it back if necessary
+ var lastWhitespace = AcceptWhitespaceTokensInLines();
+ if (EndOfFile)
+ {
+ if (lastWhitespace != null)
+ {
+ AcceptToken(lastWhitespace);
+ }
+
+ builder.Add(OutputTokensAsStatementLiteral());
+ return;
+ }
+
+ var kind = CurrentToken.Kind;
+ var location = CurrentStart;
+
+ // Both cases @: and @:: are triggered as markup, second colon in second case will be triggered as a plain text
+ var isSingleLineMarkup = kind == SyntaxKind.Transition &&
+ (NextIs(SyntaxKind.Colon, SyntaxKind.DoubleColon));
+
+ var isMarkup = isSingleLineMarkup ||
+ kind == SyntaxKind.LessThan ||
+ (kind == SyntaxKind.Transition && NextIs(SyntaxKind.LessThan));
+
+ if (Context.DesignTimeMode || !isMarkup)
+ {
+ // CODE owns whitespace, MARKUP owns it ONLY in DesignTimeMode.
+ if (lastWhitespace != null)
+ {
+ AcceptToken(lastWhitespace);
+ }
+ }
+ else
+ {
+ var nextToken = Lookahead(1);
+
+ // MARKUP owns whitespace EXCEPT in DesignTimeMode.
+ PutCurrentBack();
+
+ // Put back the whitespace unless it precedes a '' tag.
+ if (nextToken != null &&
+ !string.Equals(nextToken.Content, SyntaxConstants.TextTagName, StringComparison.Ordinal))
+ {
+ PutBack(lastWhitespace);
+ }
+ else
+ {
+ // If it precedes a '' tag, it should be accepted as code.
+ AcceptToken(lastWhitespace);
+ }
+ }
+
+ if (isMarkup)
+ {
+ if (kind == SyntaxKind.Transition && !isSingleLineMarkup)
+ {
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_AtInCodeMustBeFollowedByColonParenOrIdentifierStart(
+ new SourceSpan(location, contentLength: 1 /* @ */)));
+ }
+
+ // Markup block
+ builder.Add(OutputTokensAsStatementLiteral());
+ if (Context.DesignTimeMode && CurrentToken != null &&
+ (CurrentToken.Kind == SyntaxKind.LessThan || CurrentToken.Kind == SyntaxKind.Transition))
+ {
+ PutCurrentBack();
+ }
+ OtherParserBlock(builder);
+ }
+ else
+ {
+ // What kind of statement is this?
+ switch (kind)
+ {
+ case SyntaxKind.RazorCommentTransition:
+ AcceptMarkerTokenIfNecessary();
+ builder.Add(OutputTokensAsStatementLiteral());
+ var comment = ParseRazorComment();
+ builder.Add(comment);
+ ParseStatement(builder, block);
+ break;
+ case SyntaxKind.LeftBrace:
+ // Verbatim Block
+ AcceptTokenAndMoveNext();
+ ParseCodeBlock(builder, block);
+ break;
+ case SyntaxKind.Keyword:
+ if (!TryParseKeyword(builder, transition: null))
+ {
+ ParseStandardStatement(builder);
+ }
+ break;
+ case SyntaxKind.Transition:
+ // Embedded Expression block
+ ParseEmbeddedExpression(builder);
+ break;
+ case SyntaxKind.RightBrace:
+ // Possible end of Code Block, just run the continuation
+ break;
+ case SyntaxKind.CSharpComment:
+ AcceptToken(CurrentToken);
+ NextToken();
+ break;
+ default:
+ // Other statement
+ ParseStandardStatement(builder);
+ break;
+ }
+ }
+ }
+
+ private void ParseEmbeddedExpression(in SyntaxListBuilder builder)
+ {
+ // First, verify the type of the block
+ Assert(SyntaxKind.Transition);
+ var transition = CurrentToken;
+ NextToken();
+
+ if (At(SyntaxKind.Transition))
+ {
+ // Escaped "@"
+ builder.Add(OutputTokensAsStatementLiteral());
+
+ // Output "@" as hidden span
+ AcceptToken(transition);
+ SpanContext.ChunkGenerator = SpanChunkGenerator.Null;
+ builder.Add(OutputTokensAsEphemeralLiteral());
+
+ Assert(SyntaxKind.Transition);
+ AcceptTokenAndMoveNext();
+ ParseStandardStatement(builder);
+ }
+ else
+ {
+ // Throw errors as necessary, but continue parsing
+ if (At(SyntaxKind.LeftBrace))
+ {
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_UnexpectedNestedCodeBlock(
+ new SourceSpan(CurrentStart, contentLength: 1 /* { */)));
+ }
+
+ // @( or @foo - Nested expression, parse a child block
+ PutCurrentBack();
+ PutBack(transition);
+
+ // Before exiting, add a marker span if necessary
+ AcceptMarkerTokenIfNecessary();
+ builder.Add(OutputTokensAsStatementLiteral());
+
+ var nestedBlock = ParseNestedBlock();
+ builder.Add(nestedBlock);
+ }
+ }
+
+ private RazorSyntaxNode ParseNestedBlock()
+ {
+ var wasNested = IsNested;
+ IsNested = true;
+
+ RazorSyntaxNode nestedBlock;
+ using (PushSpanContextConfig())
+ {
+ nestedBlock = ParseBlock();
+ }
+
+ InitializeContext(SpanContext);
+ IsNested = wasNested;
+ NextToken();
+
+ return nestedBlock;
+ }
+
+ private void ParseStandardStatement(in SyntaxListBuilder builder)
+ {
+ while (!EndOfFile)
+ {
+ var bookmark = CurrentStart.AbsoluteIndex;
+ var read = ReadWhile(token =>
+ token.Kind != SyntaxKind.Semicolon &&
+ token.Kind != SyntaxKind.RazorCommentTransition &&
+ token.Kind != SyntaxKind.Transition &&
+ token.Kind != SyntaxKind.LeftBrace &&
+ token.Kind != SyntaxKind.LeftParenthesis &&
+ token.Kind != SyntaxKind.LeftBracket &&
+ token.Kind != SyntaxKind.RightBrace);
+
+ if (At(SyntaxKind.LeftBrace) ||
+ At(SyntaxKind.LeftParenthesis) ||
+ At(SyntaxKind.LeftBracket))
+ {
+ AcceptToken(read);
+ if (BalanceToken(builder, BalancingModes.AllowCommentsAndTemplates | BalancingModes.BacktrackOnFailure))
+ {
+ OptionalToken(SyntaxKind.RightBrace);
+ }
+ else
+ {
+ // Recovery
+ AcceptTokenUntil(SyntaxKind.LessThan, SyntaxKind.RightBrace);
+ return;
+ }
+ }
+ else if (At(SyntaxKind.Transition) && (NextIs(SyntaxKind.LessThan, SyntaxKind.Colon)))
+ {
+ AcceptToken(read);
+ builder.Add(OutputTokensAsStatementLiteral());
+ ParseTemplate(builder);
+ }
+ else if (At(SyntaxKind.RazorCommentTransition))
+ {
+ AcceptToken(read);
+ AcceptMarkerTokenIfNecessary();
+ builder.Add(OutputTokensAsStatementLiteral());
+ builder.Add(ParseRazorComment());
+ }
+ else if (At(SyntaxKind.Semicolon))
+ {
+ AcceptToken(read);
+ AcceptTokenAndMoveNext();
+ return;
+ }
+ else if (At(SyntaxKind.RightBrace))
+ {
+ AcceptToken(read);
+ return;
+ }
+ else
+ {
+ Context.Source.Position = bookmark;
+ NextToken();
+ AcceptTokenUntil(SyntaxKind.LessThan, SyntaxKind.LeftBrace, SyntaxKind.RightBrace);
+ return;
+ }
+ }
+ }
+
+ private void ParseTemplate(in SyntaxListBuilder builder)
+ {
+ if (Context.InTemplateContext)
+ {
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_InlineMarkupBlocksCannotBeNested(
+ new SourceSpan(CurrentStart, contentLength: 1 /* @ */)));
+ }
+ if (SpanContext.ChunkGenerator is ExpressionChunkGenerator)
+ {
+ builder.Add(OutputTokensAsExpressionLiteral());
+ }
+ else
+ {
+ builder.Add(OutputTokensAsStatementLiteral());
+ }
+
+ using (var pooledResult = Pool.Allocate())
+ {
+ var templateBuilder = pooledResult.Builder;
+ Context.InTemplateContext = true;
+ PutCurrentBack();
+ OtherParserBlock(templateBuilder);
+
+ var template = SyntaxFactory.CSharpTemplateBlock(templateBuilder.ToList());
+ builder.Add(template);
+
+ Context.InTemplateContext = false;
+ }
+ }
+
+ protected bool TryParseDirective(in SyntaxListBuilder builder, CSharpTransitionSyntax transition, string directive)
+ {
+ if (_directiveParserMap.TryGetValue(directive, out var handler))
+ {
+ SpanContext.ChunkGenerator = SpanChunkGenerator.Null;
+ handler(builder, transition);
+ return true;
+ }
+
+ return false;
+ }
+
+ private void SetupDirectiveParsers(IEnumerable directiveDescriptors)
+ {
+ var allDirectives = directiveDescriptors.Concat(DefaultDirectiveDescriptors).ToList();
+
+ for (var i = 0; i < allDirectives.Count; i++)
+ {
+ var directiveDescriptor = allDirectives[i];
+ CurrentKeywords.Add(directiveDescriptor.Directive);
+ MapDirectives((builder, transition) => ParseExtensibleDirective(builder, transition, directiveDescriptor), directiveDescriptor.Directive);
+ }
+
+ MapDirectives(ParseTagHelperPrefixDirective, SyntaxConstants.CSharp.TagHelperPrefixKeyword);
+ MapDirectives(ParseAddTagHelperDirective, SyntaxConstants.CSharp.AddTagHelperKeyword);
+ MapDirectives(ParseRemoveTagHelperDirective, SyntaxConstants.CSharp.RemoveTagHelperKeyword);
+ }
+
+ protected void MapDirectives(Action, CSharpTransitionSyntax> handler, params string[] directives)
+ {
+ foreach (var directive in directives)
+ {
+ _directiveParserMap.Add(directive, (builder, transition) =>
+ {
+ handler(builder, transition);
+ Context.SeenDirectives.Add(directive);
+ });
+
+ Keywords.Add(directive);
+
+ // These C# keywords are reserved for use in directives. It's an error to use them outside of
+ // a directive. This code removes the error generation if the directive *is* registered.
+ if (string.Equals(directive, "class", StringComparison.OrdinalIgnoreCase))
+ {
+ _keywordParserMap.Remove(CSharpKeyword.Class);
+ }
+ else if (string.Equals(directive, "namespace", StringComparison.OrdinalIgnoreCase))
+ {
+ _keywordParserMap.Remove(CSharpKeyword.Namespace);
+ }
+ }
+ }
+
+ private void ParseTagHelperPrefixDirective(SyntaxListBuilder builder, CSharpTransitionSyntax transition)
+ {
+ RazorDiagnostic duplicateDiagnostic = null;
+ if (Context.SeenDirectives.Contains(SyntaxConstants.CSharp.TagHelperPrefixKeyword))
+ {
+ var directiveStart = CurrentStart;
+ if (transition != null)
+ {
+ // Start the error from the Transition '@'.
+ directiveStart = new SourceLocation(
+ directiveStart.FilePath,
+ directiveStart.AbsoluteIndex - 1,
+ directiveStart.LineIndex,
+ directiveStart.CharacterIndex - 1);
+ }
+ var errorLength = /* @ */ 1 + SyntaxConstants.CSharp.TagHelperPrefixKeyword.Length;
+ duplicateDiagnostic = RazorDiagnosticFactory.CreateParsing_DuplicateDirective(
+ new SourceSpan(directiveStart, errorLength),
+ SyntaxConstants.CSharp.TagHelperPrefixKeyword);
+ }
+
+ var directiveBody = ParseTagHelperDirective(
+ SyntaxConstants.CSharp.TagHelperPrefixKeyword,
+ (prefix, errors, startLocation) =>
+ {
+ if (duplicateDiagnostic != null)
+ {
+ errors.Add(duplicateDiagnostic);
+ }
+
+ var parsedDirective = ParseDirective(prefix, startLocation, TagHelperDirectiveType.TagHelperPrefix, errors);
+
+ return new TagHelperPrefixDirectiveChunkGenerator(
+ prefix,
+ parsedDirective.DirectiveText,
+ errors);
+ });
+
+ var directive = SyntaxFactory.RazorDirective(transition, directiveBody);
+ builder.Add(directive);
+ }
+
+ private void ParseAddTagHelperDirective(SyntaxListBuilder builder, CSharpTransitionSyntax transition)
+ {
+ var directiveBody = ParseTagHelperDirective(
+ SyntaxConstants.CSharp.AddTagHelperKeyword,
+ (lookupText, errors, startLocation) =>
+ {
+ var parsedDirective = ParseDirective(lookupText, startLocation, TagHelperDirectiveType.AddTagHelper, errors);
+
+ return new AddTagHelperChunkGenerator(
+ lookupText,
+ parsedDirective.DirectiveText,
+ parsedDirective.TypePattern,
+ parsedDirective.AssemblyName,
+ errors);
+ });
+
+ var directive = SyntaxFactory.RazorDirective(transition, directiveBody);
+ builder.Add(directive);
+ }
+
+ private void ParseRemoveTagHelperDirective(SyntaxListBuilder builder, CSharpTransitionSyntax transition)
+ {
+ var directiveBody = ParseTagHelperDirective(
+ SyntaxConstants.CSharp.RemoveTagHelperKeyword,
+ (lookupText, errors, startLocation) =>
+ {
+ var parsedDirective = ParseDirective(lookupText, startLocation, TagHelperDirectiveType.RemoveTagHelper, errors);
+
+ return new RemoveTagHelperChunkGenerator(
+ lookupText,
+ parsedDirective.DirectiveText,
+ parsedDirective.TypePattern,
+ parsedDirective.AssemblyName,
+ errors);
+ });
+
+ var directive = SyntaxFactory.RazorDirective(transition, directiveBody);
+ builder.Add(directive);
+ }
+
+ private RazorDirectiveBodySyntax ParseTagHelperDirective(
+ string keyword,
+ Func, SourceLocation, ISpanChunkGenerator> chunkGeneratorFactory)
+ {
+ AssertDirective(keyword);
+
+ var savedErrorSink = Context.ErrorSink;
+ var directiveErrorSink = new ErrorSink();
+ RazorMetaCodeSyntax keywordBlock = null;
+ using (var pooledResult = Pool.Allocate())
+ {
+ var directiveBuilder = pooledResult.Builder;
+ Context.ErrorSink = directiveErrorSink;
+
+ string directiveValue = null;
+ SourceLocation? valueStartLocation = null;
+ try
+ {
+ EnsureDirectiveIsAtStartOfLine();
+
+ var keywordStartLocation = CurrentStart;
+
+ // Accept the directive name
+ var keywordToken = EatCurrentToken();
+ var keywordLength = keywordToken.FullWidth + 1 /* @ */;
+
+ var foundWhitespace = At(SyntaxKind.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.
+ var acceptedCharacters = foundWhitespace ? AcceptedCharactersInternal.None : AcceptedCharactersInternal.AnyExceptNewline;
+ AcceptToken(keywordToken);
+ keywordBlock = OutputAsMetaCode(OutputTokens(), acceptedCharacters);
+
+ AcceptTokenWhile(SyntaxKind.Whitespace);
+ SpanContext.ChunkGenerator = SpanChunkGenerator.Null;
+ SpanContext.EditHandler.AcceptedCharacters = acceptedCharacters;
+ directiveBuilder.Add(OutputTokensAsMarkupLiteral());
+
+ if (EndOfFile || At(SyntaxKind.NewLine))
+ {
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_DirectiveMustHaveValue(
+ new SourceSpan(keywordStartLocation, keywordLength), keyword));
+
+ directiveValue = string.Empty;
+ }
+ else
+ {
+ // Need to grab the current location before we accept until the end of the line.
+ valueStartLocation = CurrentStart;
+
+ // Parse to the end of the line. Essentially accepts anything until end of line, comments, invalid code
+ // etc.
+ AcceptTokenUntil(SyntaxKind.NewLine);
+
+ // Pull out the value and remove whitespaces and optional quotes
+ var rawValue = string.Concat(TokenBuilder.ToList().Nodes.Select(s => s.Content)).Trim();
+
+ var startsWithQuote = rawValue.StartsWith("\"", StringComparison.Ordinal);
+ var endsWithQuote = rawValue.EndsWith("\"", StringComparison.Ordinal);
+ if (startsWithQuote != endsWithQuote)
+ {
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_IncompleteQuotesAroundDirective(
+ new SourceSpan(valueStartLocation.Value, rawValue.Length), keyword));
+ }
+
+ directiveValue = rawValue;
+ }
+ }
+ finally
+ {
+ SpanContext.ChunkGenerator = chunkGeneratorFactory(
+ directiveValue,
+ directiveErrorSink.Errors.ToList(),
+ valueStartLocation ?? CurrentStart);
+ Context.ErrorSink = savedErrorSink;
+ }
+
+ // Finish the block and output the tokens
+ CompleteBlock();
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.AnyExceptNewline;
+
+ directiveBuilder.Add(OutputTokensAsStatementLiteral());
+ var directiveCodeBlock = SyntaxFactory.CSharpCodeBlock(directiveBuilder.ToList());
+
+ return SyntaxFactory.RazorDirectiveBody(keywordBlock, directiveCodeBlock);
+ }
+ }
+
+ private ParsedDirective ParseDirective(
+ string directiveText,
+ SourceLocation directiveLocation,
+ TagHelperDirectiveType directiveType,
+ List errors)
+ {
+ var offset = 0;
+ directiveText = directiveText.Trim();
+ if (directiveText.Length >= 2 &&
+ directiveText.StartsWith("\"", StringComparison.Ordinal) &&
+ directiveText.EndsWith("\"", StringComparison.Ordinal))
+ {
+ directiveText = directiveText.Substring(1, directiveText.Length - 2);
+ if (string.IsNullOrEmpty(directiveText))
+ {
+ offset = 1;
+ }
+ }
+
+ // If this is the "string literal" form of a directive, we'll need to postprocess the location
+ // and content.
+ //
+ // Ex: @addTagHelper "*, Microsoft.AspNetCore.CoolLibrary"
+ // ^ ^
+ // Start End
+ if (TokenBuilder.Count == 1 &&
+ TokenBuilder[0] is SyntaxToken token &&
+ token.Kind == SyntaxKind.StringLiteral)
+ {
+ offset += token.Content.IndexOf(directiveText, StringComparison.Ordinal);
+
+ // This is safe because inside one of these directives all of the text needs to be on the
+ // same line.
+ var original = directiveLocation;
+ directiveLocation = new SourceLocation(
+ original.FilePath,
+ original.AbsoluteIndex + offset,
+ original.LineIndex,
+ original.CharacterIndex + offset);
+ }
+
+ var parsedDirective = new ParsedDirective()
+ {
+ DirectiveText = directiveText
+ };
+
+ if (directiveType == TagHelperDirectiveType.TagHelperPrefix)
+ {
+ ValidateTagHelperPrefix(parsedDirective.DirectiveText, directiveLocation, errors);
+
+ return parsedDirective;
+ }
+
+ return ParseAddOrRemoveDirective(parsedDirective, directiveLocation, errors);
+ }
+
+ private void ParseExtensibleDirective(in SyntaxListBuilder builder, CSharpTransitionSyntax transition, DirectiveDescriptor descriptor)
+ {
+ AssertDirective(descriptor.Directive);
+
+ var directiveErrorSink = new ErrorSink();
+ var savedErrorSink = Context.ErrorSink;
+ Context.ErrorSink = directiveErrorSink;
+
+ using (var pooledResult = Pool.Allocate())
+ {
+ var directiveBuilder = pooledResult.Builder;
+ var directiveChunkGenerator = new DirectiveChunkGenerator(descriptor);
+ RazorMetaCodeSyntax keywordBlock = null;
+
+ try
+ {
+ EnsureDirectiveIsAtStartOfLine();
+ var directiveStart = CurrentStart;
+ if (transition != null)
+ {
+ // Start the error from the Transition '@'.
+ directiveStart = new SourceLocation(
+ directiveStart.FilePath,
+ directiveStart.AbsoluteIndex - 1,
+ directiveStart.LineIndex,
+ directiveStart.CharacterIndex - 1);
+ }
+
+ AcceptTokenAndMoveNext();
+ keywordBlock = OutputAsMetaCode(OutputTokens());
+
+ // Even if an error was logged do not bail out early. If a directive was used incorrectly it doesn't mean it can't be parsed.
+ ValidateDirectiveUsage(descriptor, directiveStart);
+
+ for (var i = 0; i < descriptor.Tokens.Count; i++)
+ {
+ if (!At(SyntaxKind.Whitespace) &&
+ !At(SyntaxKind.NewLine) &&
+ !EndOfFile)
+ {
+ // This case should never happen in a real scenario. We're just being defensive.
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_DirectiveTokensMustBeSeparatedByWhitespace(
+ new SourceSpan(CurrentStart, CurrentToken.Content.Length), descriptor.Directive));
+
+ builder.Add(BuildDirective());
+ return;
+ }
+
+ var tokenDescriptor = descriptor.Tokens[i];
+ AcceptTokenWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
+
+ if (tokenDescriptor.Kind == DirectiveTokenKind.Member ||
+ tokenDescriptor.Kind == DirectiveTokenKind.Namespace ||
+ tokenDescriptor.Kind == DirectiveTokenKind.Type)
+ {
+ SpanContext.ChunkGenerator = SpanChunkGenerator.Null;
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.Whitespace;
+ directiveBuilder.Add(OutputTokensAsStatementLiteral());
+
+ if (EndOfFile || At(SyntaxKind.NewLine))
+ {
+ // Add a marker token to provide CSharp intellisense when we start typing the directive token.
+ AcceptMarkerTokenIfNecessary();
+ SpanContext.ChunkGenerator = new DirectiveTokenChunkGenerator(tokenDescriptor);
+ SpanContext.EditHandler = new DirectiveTokenEditHandler(Language.TokenizeString);
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.NonWhitespace;
+ directiveBuilder.Add(OutputTokensAsStatementLiteral());
+ }
+ }
+ else
+ {
+ SpanContext.ChunkGenerator = SpanChunkGenerator.Null;
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.Whitespace;
+ directiveBuilder.Add(OutputTokensAsMarkupEphemeralLiteral());
+ }
+
+ if (tokenDescriptor.Optional && (EndOfFile || At(SyntaxKind.NewLine)))
+ {
+ break;
+ }
+ else if (EndOfFile)
+ {
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_UnexpectedEOFAfterDirective(
+ new SourceSpan(CurrentStart, contentLength: 1),
+ descriptor.Directive,
+ tokenDescriptor.Kind.ToString().ToLowerInvariant()));
+ builder.Add(BuildDirective());
+ return;
+ }
+
+ switch (tokenDescriptor.Kind)
+ {
+ case DirectiveTokenKind.Type:
+ if (!TryParseNamespaceOrTypeName(directiveBuilder))
+ {
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_DirectiveExpectsTypeName(
+ new SourceSpan(CurrentStart, CurrentToken.Content.Length), descriptor.Directive));
+
+ builder.Add(BuildDirective());
+ return;
+ }
+ break;
+
+ case DirectiveTokenKind.Namespace:
+ if (!TryParseQualifiedIdentifier(out var identifierLength))
+ {
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_DirectiveExpectsNamespace(
+ new SourceSpan(CurrentStart, identifierLength), descriptor.Directive));
+
+ builder.Add(BuildDirective());
+ return;
+ }
+ break;
+
+ case DirectiveTokenKind.Member:
+ if (At(SyntaxKind.Identifier))
+ {
+ AcceptTokenAndMoveNext();
+ }
+ else
+ {
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_DirectiveExpectsIdentifier(
+ new SourceSpan(CurrentStart, CurrentToken.Content.Length), descriptor.Directive));
+ builder.Add(BuildDirective());
+ return;
+ }
+ break;
+
+ case DirectiveTokenKind.String:
+ if (At(SyntaxKind.StringLiteral) && !CurrentToken.ContainsDiagnostics)
+ {
+ AcceptTokenAndMoveNext();
+ }
+ else
+ {
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_DirectiveExpectsQuotedStringLiteral(
+ new SourceSpan(CurrentStart, CurrentToken.Content.Length), descriptor.Directive));
+ builder.Add(BuildDirective());
+ return;
+ }
+ break;
+ }
+
+ SpanContext.ChunkGenerator = new DirectiveTokenChunkGenerator(tokenDescriptor);
+ SpanContext.EditHandler = new DirectiveTokenEditHandler(Language.TokenizeString);
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.NonWhitespace;
+ directiveBuilder.Add(OutputTokensAsStatementLiteral());
+ }
+
+ AcceptTokenWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
+ SpanContext.ChunkGenerator = SpanChunkGenerator.Null;
+
+ switch (descriptor.Kind)
+ {
+ case DirectiveKind.SingleLine:
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.Whitespace;
+ directiveBuilder.Add(OutputTokensAsUnclassifiedLiteral());
+
+ OptionalToken(SyntaxKind.Semicolon);
+ directiveBuilder.Add(OutputAsMetaCode(OutputTokens(), AcceptedCharactersInternal.Whitespace));
+
+ AcceptTokenWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
+
+ if (At(SyntaxKind.NewLine))
+ {
+ AcceptTokenAndMoveNext();
+ }
+ else if (!EndOfFile)
+ {
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_UnexpectedDirectiveLiteral(
+ new SourceSpan(CurrentStart, CurrentToken.Content.Length),
+ descriptor.Directive,
+ Resources.ErrorComponent_Newline));
+ }
+
+
+ // This should contain the optional whitespace after the optional semicolon and the new line.
+ // Output as Markup as we want intellisense here.
+ SpanContext.ChunkGenerator = SpanChunkGenerator.Null;
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.Whitespace;
+ directiveBuilder.Add(OutputTokensAsMarkupEphemeralLiteral());
+ break;
+ case DirectiveKind.RazorBlock:
+ AcceptTokenWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.AllWhitespace;
+ directiveBuilder.Add(OutputTokensAsMarkupLiteral());
+
+ ParseDirectiveBlock(directiveBuilder, descriptor, parseChildren: (childBuilder, startingBraceLocation) =>
+ {
+ // 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 (PushSpanContextConfig())
+ {
+ var razorBlock = HtmlParser.ParseRazorBlock(Tuple.Create("{", "}"), caseSensitive: true);
+ directiveBuilder.Add(razorBlock);
+ }
+
+ InitializeContext(SpanContext);
+ IsNested = wasNested;
+ NextToken();
+ });
+ break;
+ case DirectiveKind.CodeBlock:
+ AcceptTokenWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.AllWhitespace;
+ directiveBuilder.Add(OutputTokensAsMarkupLiteral());
+
+ ParseDirectiveBlock(directiveBuilder, descriptor, parseChildren: (childBuilder, startingBraceLocation) =>
+ {
+ NextToken();
+ BalanceToken(childBuilder, BalancingModes.NoErrorOnFailure, SyntaxKind.LeftBrace, SyntaxKind.RightBrace, startingBraceLocation);
+ SpanContext.ChunkGenerator = new StatementChunkGenerator();
+ var existingEditHandler = SpanContext.EditHandler;
+ SpanContext.EditHandler = new CodeBlockEditHandler(Language.TokenizeString);
+
+ AcceptMarkerTokenIfNecessary();
+
+ childBuilder.Add(OutputTokensAsStatementLiteral());
+
+ SpanContext.EditHandler = existingEditHandler;
+ });
+ break;
+ }
+ }
+ finally
+ {
+ if (directiveErrorSink.Errors.Count > 0)
+ {
+ directiveChunkGenerator.Diagnostics.AddRange(directiveErrorSink.Errors);
+ }
+
+ Context.ErrorSink = savedErrorSink;
+ }
+
+ builder.Add(BuildDirective());
+
+ RazorDirectiveSyntax BuildDirective()
+ {
+ directiveBuilder.Add(OutputTokensAsStatementLiteral());
+ var directiveCodeBlock = SyntaxFactory.CSharpCodeBlock(directiveBuilder.ToList());
+
+ var directiveBody = SyntaxFactory.RazorDirectiveBody(keywordBlock, directiveCodeBlock);
+ var directive = SyntaxFactory.RazorDirective(transition, directiveBody);
+ directive = (RazorDirectiveSyntax)directive.SetDiagnostics(directiveErrorSink.Errors.ToArray());
+ directive = directive.WithDirectiveDescriptor(descriptor);
+ return directive;
+ }
+ }
+ }
+
+ private void ValidateDirectiveUsage(DirectiveDescriptor descriptor, SourceLocation directiveStart)
+ {
+ if (descriptor.Usage == DirectiveUsage.FileScopedSinglyOccurring)
+ {
+ if (Context.SeenDirectives.Contains(descriptor.Directive))
+ {
+ // There will always be at least 1 child because of the `@` transition.
+ var errorLength = /* @ */ 1 + descriptor.Directive.Length;
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_DuplicateDirective(
+ new SourceSpan(directiveStart, errorLength), descriptor.Directive));
+
+ return;
+ }
+ }
+ }
+
+ // Used for parsing a qualified name like that which follows the `namespace` keyword.
+ //
+ // qualified-identifier:
+ // identifier
+ // qualified-identifier . identifier
+ protected bool TryParseQualifiedIdentifier(out int identifierLength)
+ {
+ var currentIdentifierLength = 0;
+ var expectingDot = false;
+ var tokens = ReadWhile(token =>
+ {
+ var type = token.Kind;
+ if ((expectingDot && type == SyntaxKind.Dot) ||
+ (!expectingDot && type == SyntaxKind.Identifier))
+ {
+ expectingDot = !expectingDot;
+ return true;
+ }
+
+ if (type != SyntaxKind.Whitespace &&
+ type != SyntaxKind.NewLine)
+ {
+ expectingDot = false;
+ currentIdentifierLength += token.Content.Length;
+ }
+
+ return false;
+ });
+
+ identifierLength = currentIdentifierLength;
+ var validQualifiedIdentifier = expectingDot;
+ if (validQualifiedIdentifier)
+ {
+ foreach (var token in tokens)
+ {
+ identifierLength += token.Content.Length;
+ AcceptToken(token);
+ }
+
+ return true;
+ }
+ else
+ {
+ PutCurrentBack();
+
+ foreach (var token in tokens)
+ {
+ identifierLength += token.Content.Length;
+ PutBack(token);
+ }
+
+ EnsureCurrent();
+ return false;
+ }
+ }
+
+ private void ParseDirectiveBlock(in SyntaxListBuilder builder, DirectiveDescriptor descriptor, Action, SourceLocation> parseChildren)
+ {
+ if (EndOfFile)
+ {
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_UnexpectedEOFAfterDirective(
+ new SourceSpan(CurrentStart, contentLength: 1 /* { */), descriptor.Directive, "{"));
+ }
+ else if (!At(SyntaxKind.LeftBrace))
+ {
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_UnexpectedDirectiveLiteral(
+ new SourceSpan(CurrentStart, CurrentToken.Content.Length), descriptor.Directive, "{"));
+ }
+ else
+ {
+ var editHandler = new AutoCompleteEditHandler(Language.TokenizeString, autoCompleteAtEndOfSpan: true);
+ SpanContext.EditHandler = editHandler;
+ var startingBraceLocation = CurrentStart;
+ AcceptToken(CurrentToken);
+ builder.Add(OutputAsMetaCode(OutputTokens()));
+
+ using (var pooledResult = Pool.Allocate())
+ {
+ var childBuilder = pooledResult.Builder;
+ parseChildren(childBuilder, startingBraceLocation);
+ if (childBuilder.Count > 0)
+ {
+ builder.Add(SyntaxFactory.CSharpCodeBlock(childBuilder.ToList()));
+ }
+ }
+
+ SpanContext.ChunkGenerator = SpanChunkGenerator.Null;
+ if (!OptionalToken(SyntaxKind.RightBrace))
+ {
+ editHandler.AutoCompleteString = "}";
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_ExpectedEndOfBlockBeforeEOF(
+ new SourceSpan(startingBraceLocation, contentLength: 1 /* } */), descriptor.Directive, "}", "{"));
+
+ AcceptToken(SyntaxFactory.MissingToken(SyntaxKind.RightBrace));
+ }
+ else
+ {
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.None;
+ }
+ CompleteBlock(insertMarkerIfNecessary: false, captureWhitespaceToEndOfLine: true);
+ builder.Add(OutputAsMetaCode(OutputTokens(), SpanContext.EditHandler.AcceptedCharacters));
+ }
+ }
+
+ private bool TryParseKeyword(in SyntaxListBuilder builder, CSharpTransitionSyntax transition)
+ {
+ var result = CSharpTokenizer.GetTokenKeyword(CurrentToken);
+ Debug.Assert(CurrentToken.Kind == SyntaxKind.Keyword && result.HasValue);
+ if (_keywordParserMap.TryGetValue(result.Value, out var handler))
+ {
+ handler(builder, transition);
+ return true;
+ }
+
+ return false;
+ }
+
+ private void SetupExpressionParsers()
+ {
+ MapExpressionKeyword(ParseAwaitExpression, CSharpKeyword.Await);
+ }
+
+ private void SetupKeywordParsers()
+ {
+ MapKeywords(
+ ParseConditionalBlock,
+ CSharpKeyword.For,
+ CSharpKeyword.Foreach,
+ CSharpKeyword.While,
+ CSharpKeyword.Switch,
+ CSharpKeyword.Lock);
+ MapKeywords(ParseCaseStatement, false, CSharpKeyword.Case, CSharpKeyword.Default);
+ MapKeywords(ParseIfStatement, CSharpKeyword.If);
+ MapKeywords(ParseTryStatement, CSharpKeyword.Try);
+ MapKeywords(ParseDoStatement, CSharpKeyword.Do);
+ MapKeywords(ParseUsingKeyword, CSharpKeyword.Using);
+ MapKeywords(ParseReservedDirective, CSharpKeyword.Class, CSharpKeyword.Namespace);
+ }
+
+ private void MapExpressionKeyword(Action, CSharpTransitionSyntax> handler, CSharpKeyword keyword)
+ {
+ _keywordParserMap.Add(keyword, handler);
+
+ // Expression keywords don't belong in the regular keyword list
+ }
+
+ private void MapKeywords(Action, CSharpTransitionSyntax> handler, params CSharpKeyword[] keywords)
+ {
+ MapKeywords(handler, topLevel: true, keywords: keywords);
+ }
+
+ private void MapKeywords(Action, CSharpTransitionSyntax> handler, bool topLevel, params CSharpKeyword[] keywords)
+ {
+ foreach (var keyword in keywords)
+ {
+ _keywordParserMap.Add(keyword, handler);
+ if (topLevel)
+ {
+ Keywords.Add(CSharpLanguageCharacteristics.GetKeyword(keyword));
+ }
+ }
+ }
+
+ private void ParseAwaitExpression(SyntaxListBuilder builder, CSharpTransitionSyntax transition)
+ {
+ // Ensure that we're on the await statement (only runs in debug)
+ Assert(CSharpKeyword.Await);
+
+ // Accept the "await" and move on
+ AcceptTokenAndMoveNext();
+
+ // Accept 1 or more spaces between the await and the following code.
+ AcceptTokenWhile(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.
+ var topLevel = transition != null;
+ if (topLevel)
+ {
+ // Setup the Span to be an async implicit expression (an implicit expresison that allows spaces).
+ // Spaces are allowed because of "@await Foo()".
+ var implicitExpressionBody = ParseImplicitExpressionBody(async: true);
+ builder.Add(SyntaxFactory.CSharpImplicitExpression(transition, implicitExpressionBody));
+ }
+ }
+
+ private void ParseConditionalBlock(SyntaxListBuilder builder, CSharpTransitionSyntax transition)
+ {
+ var topLevel = transition != null;
+ ParseConditionalBlock(builder, transition, topLevel);
+ }
+
+ private void ParseConditionalBlock(in SyntaxListBuilder builder, CSharpTransitionSyntax transition, bool topLevel)
+ {
+ Assert(SyntaxKind.Keyword);
+ if (transition != null)
+ {
+ builder.Add(transition);
+ }
+
+ var block = new Block(CurrentToken, CurrentStart);
+ ParseConditionalBlock(builder, block);
+ if (topLevel)
+ {
+ CompleteBlock();
+ }
+ }
+
+ private void ParseConditionalBlock(in SyntaxListBuilder builder, Block block)
+ {
+ AcceptTokenAndMoveNext();
+ AcceptTokenWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
+
+ // Parse the condition, if present (if not present, we'll let the C# compiler complain)
+ if (TryParseCondition(builder))
+ {
+ AcceptTokenWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
+
+ ParseExpectedCodeBlock(builder, block);
+ }
+ }
+
+ private bool TryParseCondition(in SyntaxListBuilder builder)
+ {
+ if (At(SyntaxKind.LeftParenthesis))
+ {
+ var complete = BalanceToken(builder, BalancingModes.BacktrackOnFailure | BalancingModes.AllowCommentsAndTemplates);
+ if (!complete)
+ {
+ AcceptTokenUntil(SyntaxKind.NewLine);
+ }
+ else
+ {
+ OptionalToken(SyntaxKind.RightParenthesis);
+ }
+ return complete;
+ }
+ return true;
+ }
+
+ private void ParseExpectedCodeBlock(in SyntaxListBuilder builder, Block block)
+ {
+ if (!EndOfFile)
+ {
+ // Check for "{" to make sure we're at a block
+ if (!At(SyntaxKind.LeftBrace))
+ {
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_SingleLineControlFlowStatementsNotAllowed(
+ new SourceSpan(CurrentStart, CurrentToken.Content.Length),
+ Language.GetSample(SyntaxKind.LeftBrace),
+ CurrentToken.Content));
+ }
+
+ // Parse the statement and then we're done
+ ParseStatement(builder, block);
+ }
+ }
+
+ private void ParseUnconditionalBlock(in SyntaxListBuilder builder)
+ {
+ Assert(SyntaxKind.Keyword);
+ var block = new Block(CurrentToken, CurrentStart);
+ AcceptTokenAndMoveNext();
+ AcceptTokenWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
+ ParseExpectedCodeBlock(builder, block);
+ }
+
+ private void ParseCaseStatement(SyntaxListBuilder builder, CSharpTransitionSyntax transition)
+ {
+ Assert(SyntaxKind.Keyword);
+ if (transition != null)
+ {
+ // Normally, case statement won't start with a transition in a valid scenario.
+ // If it does, just accept it and let the compiler complain.
+ builder.Add(transition);
+ }
+ var result = CSharpTokenizer.GetTokenKeyword(CurrentToken);
+ Debug.Assert(result.HasValue &&
+ (result.Value == CSharpKeyword.Case ||
+ result.Value == CSharpKeyword.Default));
+ AcceptTokenUntil(SyntaxKind.Colon);
+ OptionalToken(SyntaxKind.Colon);
+ }
+
+ private void ParseIfStatement(SyntaxListBuilder builder, CSharpTransitionSyntax transition)
+ {
+ Assert(CSharpKeyword.If);
+ ParseConditionalBlock(builder, transition, topLevel: false);
+ ParseAfterIfClause(builder);
+ var topLevel = transition != null;
+ if (topLevel)
+ {
+ CompleteBlock();
+ }
+ }
+
+ private void ParseAfterIfClause(SyntaxListBuilder builder)
+ {
+ // Grab whitespace and razor comments
+ var whitespace = SkipToNextImportantToken(builder);
+
+ // Check for an else part
+ if (At(CSharpKeyword.Else))
+ {
+ AcceptToken(whitespace);
+ Assert(CSharpKeyword.Else);
+ ParseElseClause(builder);
+ }
+ else
+ {
+ // No else, return whitespace
+ PutCurrentBack();
+ PutBack(whitespace);
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.Any;
+ }
+ }
+
+ private void ParseElseClause(in SyntaxListBuilder builder)
+ {
+ if (!At(CSharpKeyword.Else))
+ {
+ return;
+ }
+ var block = new Block(CurrentToken, CurrentStart);
+
+ AcceptTokenAndMoveNext();
+ AcceptTokenWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
+ if (At(CSharpKeyword.If))
+ {
+ // ElseIf
+ block.Name = SyntaxConstants.CSharp.ElseIfKeyword;
+ ParseConditionalBlock(builder, block);
+ ParseAfterIfClause(builder);
+ }
+ else if (!EndOfFile)
+ {
+ // Else
+ ParseExpectedCodeBlock(builder, block);
+ }
+ }
+
+ private void ParseTryStatement(SyntaxListBuilder builder, CSharpTransitionSyntax transition)
+ {
+ Assert(CSharpKeyword.Try);
+ var topLevel = transition != null;
+ if (topLevel)
+ {
+ builder.Add(transition);
+ }
+
+ ParseUnconditionalBlock(builder);
+ ParseAfterTryClause(builder);
+ if (topLevel)
+ {
+ CompleteBlock();
+ }
+ }
+
+ private void ParseAfterTryClause(in SyntaxListBuilder builder)
+ {
+ // Grab whitespace
+ var whitespace = SkipToNextImportantToken(builder);
+
+ // Check for a catch or finally part
+ if (At(CSharpKeyword.Catch))
+ {
+ AcceptToken(whitespace);
+ Assert(CSharpKeyword.Catch);
+ ParseFilterableCatchBlock(builder);
+ ParseAfterTryClause(builder);
+ }
+ else if (At(CSharpKeyword.Finally))
+ {
+ AcceptToken(whitespace);
+ Assert(CSharpKeyword.Finally);
+ ParseUnconditionalBlock(builder);
+ }
+ else
+ {
+ // Return whitespace and end the block
+ PutCurrentBack();
+ PutBack(whitespace);
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.Any;
+ }
+ }
+
+ private void ParseFilterableCatchBlock(in SyntaxListBuilder builder)
+ {
+ Assert(CSharpKeyword.Catch);
+
+ var block = new Block(CurrentToken, CurrentStart);
+
+ // Accept "catch"
+ AcceptTokenAndMoveNext();
+ AcceptTokenWhile(IsValidStatementSpacingToken);
+
+ // Parse the catch condition if present. If not present, let the C# compiler complain.
+ if (TryParseCondition(builder))
+ {
+ AcceptTokenWhile(IsValidStatementSpacingToken);
+
+ if (At(CSharpKeyword.When))
+ {
+ // Accept "when".
+ AcceptTokenAndMoveNext();
+ AcceptTokenWhile(IsValidStatementSpacingToken);
+
+ // Parse the filter condition if present. If not present, let the C# compiler complain.
+ if (!TryParseCondition(builder))
+ {
+ // Incomplete condition.
+ return;
+ }
+
+ AcceptTokenWhile(IsValidStatementSpacingToken);
+ }
+
+ ParseExpectedCodeBlock(builder, block);
+ }
+ }
+
+ private void ParseDoStatement(SyntaxListBuilder builder, CSharpTransitionSyntax transition)
+ {
+ Assert(CSharpKeyword.Do);
+ if (transition != null)
+ {
+ builder.Add(transition);
+ }
+
+ ParseUnconditionalBlock(builder);
+ ParseWhileClause(builder);
+ var topLevel = transition != null;
+ if (topLevel)
+ {
+ CompleteBlock();
+ }
+ }
+
+ private void ParseWhileClause(in SyntaxListBuilder builder)
+ {
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.Any;
+ var whitespace = SkipToNextImportantToken(builder);
+
+ if (At(CSharpKeyword.While))
+ {
+ AcceptToken(whitespace);
+ Assert(CSharpKeyword.While);
+ AcceptTokenAndMoveNext();
+ AcceptTokenWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
+ if (TryParseCondition(builder) && OptionalToken(SyntaxKind.Semicolon))
+ {
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.None;
+ }
+ }
+ else
+ {
+ PutCurrentBack();
+ PutBack(whitespace);
+ }
+ }
+
+ private void ParseUsingKeyword(SyntaxListBuilder builder, CSharpTransitionSyntax transition)
+ {
+ Assert(CSharpKeyword.Using);
+ var topLevel = transition != null;
+ var block = new Block(CurrentToken, CurrentStart);
+ var usingToken = EatCurrentToken();
+ var whitespaceOrComments = ReadWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
+ var atLeftParen = At(SyntaxKind.LeftParenthesis);
+ var atIdentifier = At(SyntaxKind.Identifier);
+ var atStatic = At(CSharpKeyword.Static);
+
+ // Put the read tokens back and let them be handled later.
+ PutCurrentBack();
+ PutBack(whitespaceOrComments);
+ PutBack(usingToken);
+ EnsureCurrent();
+
+ if (atLeftParen)
+ {
+ // using ( ==> Using Statement
+ ParseUsingStatement(builder, transition, block);
+ }
+ else if (atIdentifier || atStatic)
+ {
+ // using Identifier ==> Using Declaration
+ if (!topLevel)
+ {
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_NamespaceImportAndTypeAliasCannotExistWithinCodeBlock(
+ new SourceSpan(block.Start, block.Name.Length)));
+ if (transition != null)
+ {
+ builder.Add(transition);
+ }
+ AcceptTokenAndMoveNext();
+ AcceptTokenWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
+ ParseStandardStatement(builder);
+ }
+ else
+ {
+ ParseUsingDeclaration(builder, transition);
+ return;
+ }
+ }
+ else
+ {
+ AcceptTokenAndMoveNext();
+ AcceptTokenWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
+ }
+
+ if (topLevel)
+ {
+ CompleteBlock();
+ }
+ }
+
+ private void ParseUsingStatement(in SyntaxListBuilder builder, CSharpTransitionSyntax transition, Block block)
+ {
+ Assert(CSharpKeyword.Using);
+ AcceptTokenAndMoveNext();
+ AcceptTokenWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
+
+ Assert(SyntaxKind.LeftParenthesis);
+ if (transition != null)
+ {
+ builder.Add(transition);
+ }
+
+ // Parse condition
+ if (TryParseCondition(builder))
+ {
+ AcceptTokenWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
+
+ // Parse code block
+ ParseExpectedCodeBlock(builder, block);
+ }
+ }
+
+ private void ParseUsingDeclaration(in SyntaxListBuilder builder, CSharpTransitionSyntax transition)
+ {
+ // Using declarations should always be top level. The error case is handled in a different code path.
+ Debug.Assert(transition != null);
+ using (var pooledResult = Pool.Allocate())
+ {
+ var directiveBuilder = pooledResult.Builder;
+ Assert(CSharpKeyword.Using);
+ AcceptTokenAndMoveNext();
+ AcceptTokenWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
+ var start = CurrentStart;
+ if (At(SyntaxKind.Identifier))
+ {
+ // non-static using
+ TryParseNamespaceOrTypeName(directiveBuilder);
+ var whitespace = ReadWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
+ if (At(SyntaxKind.Assign))
+ {
+ // Alias
+ AcceptToken(whitespace);
+ Assert(SyntaxKind.Assign);
+ AcceptTokenAndMoveNext();
+
+ AcceptTokenWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
+
+ // One more namespace or type name
+ TryParseNamespaceOrTypeName(directiveBuilder);
+ }
+ else
+ {
+ PutCurrentBack();
+ PutBack(whitespace);
+ }
+ }
+ else if (At(CSharpKeyword.Static))
+ {
+ // static using
+ AcceptTokenAndMoveNext();
+ AcceptTokenWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
+ TryParseNamespaceOrTypeName(directiveBuilder);
+ }
+
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.AnyExceptNewline;
+ SpanContext.ChunkGenerator = new AddImportChunkGenerator(new LocationTagged(
+ string.Concat(TokenBuilder.ToList().Nodes.Skip(1).Select(s => s.Content)),
+ start));
+
+ // Optional ";"
+ if (EnsureCurrent())
+ {
+ OptionalToken(SyntaxKind.Semicolon);
+ }
+
+ CompleteBlock();
+ Debug.Assert(directiveBuilder.Count == 0, "We should not have built any blocks so far.");
+ var keywordTokens = OutputTokensAsStatementLiteral();
+ var directiveBody = SyntaxFactory.RazorDirectiveBody(keywordTokens, null);
+ builder.Add(SyntaxFactory.RazorDirective(transition, directiveBody));
+ }
+ }
+
+ private bool TryParseNamespaceOrTypeName(in SyntaxListBuilder builder)
+ {
+ if (OptionalToken(SyntaxKind.LeftParenthesis))
+ {
+ while (!OptionalToken(SyntaxKind.RightParenthesis) && !EndOfFile)
+ {
+ OptionalToken(SyntaxKind.Whitespace);
+
+ if (!TryParseNamespaceOrTypeName(builder))
+ {
+ return false;
+ }
+
+ OptionalToken(SyntaxKind.Whitespace);
+ OptionalToken(SyntaxKind.Identifier);
+ OptionalToken(SyntaxKind.Whitespace);
+ OptionalToken(SyntaxKind.Comma);
+ }
+
+ if (At(SyntaxKind.Whitespace) && NextIs(SyntaxKind.QuestionMark))
+ {
+ // Only accept the whitespace if we are going to consume the next token.
+ AcceptTokenAndMoveNext();
+ }
+
+ OptionalToken(SyntaxKind.QuestionMark); // Nullable
+
+ return true;
+ }
+ else if (OptionalToken(SyntaxKind.Identifier) || OptionalToken(SyntaxKind.Keyword))
+ {
+ if (OptionalToken(SyntaxKind.DoubleColon))
+ {
+ if (!OptionalToken(SyntaxKind.Identifier))
+ {
+ OptionalToken(SyntaxKind.Keyword);
+ }
+ }
+ if (At(SyntaxKind.LessThan))
+ {
+ ParseTypeArgumentList(builder);
+ }
+ if (OptionalToken(SyntaxKind.Dot))
+ {
+ TryParseNamespaceOrTypeName(builder);
+ }
+
+ if (At(SyntaxKind.Whitespace) && NextIs(SyntaxKind.QuestionMark))
+ {
+ // Only accept the whitespace if we are going to consume the next token.
+ AcceptTokenAndMoveNext();
+ }
+
+ OptionalToken(SyntaxKind.QuestionMark); // Nullable
+
+ if (At(SyntaxKind.Whitespace) && NextIs(SyntaxKind.LeftBracket))
+ {
+ // Only accept the whitespace if we are going to consume the next token.
+ AcceptTokenAndMoveNext();
+ }
+
+ while (At(SyntaxKind.LeftBracket))
+ {
+ BalanceToken(builder, BalancingModes.None);
+ if (!OptionalToken(SyntaxKind.RightBracket))
+ {
+ AcceptToken(SyntaxFactory.MissingToken(SyntaxKind.RightBracket));
+ }
+ }
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ private void ParseTypeArgumentList(in SyntaxListBuilder builder)
+ {
+ Assert(SyntaxKind.LessThan);
+ BalanceToken(builder, BalancingModes.None);
+ if (!OptionalToken(SyntaxKind.GreaterThan))
+ {
+ AcceptToken(SyntaxFactory.MissingToken(SyntaxKind.GreaterThan));
+ }
+ }
+
+ private void ParseReservedDirective(SyntaxListBuilder builder, CSharpTransitionSyntax transition)
+ {
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_ReservedWord(
+ new SourceSpan(CurrentStart, CurrentToken.Content.Length), CurrentToken.Content));
+
+ AcceptTokenAndMoveNext();
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.None;
+ SpanContext.ChunkGenerator = SpanChunkGenerator.Null;
+ CompleteBlock();
+ var keyword = OutputAsMetaCode(OutputTokens());
+ var directiveBody = SyntaxFactory.RazorDirectiveBody(keyword, cSharpCode: null);
+ var directive = SyntaxFactory.RazorDirective(transition, directiveBody);
+ builder.Add(directive);
+ }
+
+ 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.LastAcceptedCharacters != AcceptedCharactersInternal.Any)
+ {
+ AcceptMarkerTokenIfNecessary();
+ }
+
+ EnsureCurrent();
+
+ // Read whitespace, but not newlines
+ // If we're not inserting a marker span, we don't need to capture whitespace
+ if (!Context.WhiteSpaceIsSignificantToAncestorBlock &&
+ captureWhitespaceToEndOfLine &&
+ !Context.DesignTimeMode &&
+ !IsNested)
+ {
+ CaptureWhitespaceAtEndOfCodeOnlyLine();
+ }
+ else
+ {
+ PutCurrentBack();
+ }
+ }
+
+ private IEnumerable SkipToNextImportantToken(in SyntaxListBuilder builder)
+ {
+ while (!EndOfFile)
+ {
+ var whitespace = ReadWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
+ if (At(SyntaxKind.RazorCommentTransition))
+ {
+ AcceptToken(whitespace);
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.Any;
+ AcceptMarkerTokenIfNecessary();
+ builder.Add(OutputTokensAsStatementLiteral());
+ var comment = ParseRazorComment();
+ builder.Add(comment);
+ }
+ else
+ {
+ return whitespace;
+ }
+ }
+ return Enumerable.Empty();
+ }
+
+ private void CaptureWhitespaceAtEndOfCodeOnlyLine()
+ {
+ var whitespace = ReadWhile(token => token.Kind == SyntaxKind.Whitespace);
+ if (At(SyntaxKind.NewLine))
+ {
+ AcceptToken(whitespace);
+ AcceptTokenAndMoveNext();
+ PutCurrentBack();
+ }
+ else
+ {
+ PutCurrentBack();
+ PutBack(whitespace);
+ }
+ }
+
+ protected override bool IsAtEmbeddedTransition(bool allowTemplatesAndComments, bool allowTransitions)
+ {
+ // No embedded transitions in C#, so ignore that param
+ return allowTemplatesAndComments
+ && ((Language.IsTransition(CurrentToken)
+ && NextIs(SyntaxKind.LessThan, SyntaxKind.Colon, SyntaxKind.DoubleColon))
+ || Language.IsCommentStart(CurrentToken));
+ }
+
+ protected override void ParseEmbeddedTransition(in SyntaxListBuilder builder)
+ {
+ if (Language.IsTransition(CurrentToken))
+ {
+ PutCurrentBack();
+ ParseTemplate(builder);
+ }
+ else if (Language.IsCommentStart(CurrentToken))
+ {
+ // Output tokens before parsing the comment.
+ AcceptMarkerTokenIfNecessary();
+ if (SpanContext.ChunkGenerator is ExpressionChunkGenerator)
+ {
+ builder.Add(OutputTokensAsExpressionLiteral());
+ }
+ else
+ {
+ builder.Add(OutputTokensAsStatementLiteral());
+ }
+
+ var comment = ParseRazorComment();
+ builder.Add(comment);
+ }
+ }
+
+ private void DefaultSpanContextConfig(SpanContextBuilder spanContext)
+ {
+ spanContext.EditHandler = SpanEditHandler.CreateDefault(Language.TokenizeString);
+ spanContext.ChunkGenerator = new StatementChunkGenerator();
+ }
+
+ private void ExplicitExpressionSpanContextConfig(SpanContextBuilder spanContext)
+ {
+ spanContext.EditHandler = SpanEditHandler.CreateDefault(Language.TokenizeString);
+ spanContext.ChunkGenerator = new ExpressionChunkGenerator();
+ }
+
+ private CSharpStatementLiteralSyntax OutputTokensAsStatementLiteral()
+ {
+ var tokens = OutputTokens();
+ if (tokens.Count == 0)
+ {
+ return null;
+ }
+
+ return GetNodeWithSpanContext(SyntaxFactory.CSharpStatementLiteral(tokens));
+ }
+
+ private CSharpExpressionLiteralSyntax OutputTokensAsExpressionLiteral()
+ {
+ var tokens = OutputTokens();
+ if (tokens.Count == 0)
+ {
+ return null;
+ }
+
+ return GetNodeWithSpanContext(SyntaxFactory.CSharpExpressionLiteral(tokens));
+ }
+
+ private CSharpEphemeralTextLiteralSyntax OutputTokensAsEphemeralLiteral()
+ {
+ var tokens = OutputTokens();
+ if (tokens.Count == 0)
+ {
+ return null;
+ }
+
+ return GetNodeWithSpanContext(SyntaxFactory.CSharpEphemeralTextLiteral(tokens));
+ }
+
+ private UnclassifiedTextLiteralSyntax OutputTokensAsUnclassifiedLiteral()
+ {
+ var tokens = OutputTokens();
+ if (tokens.Count == 0)
+ {
+ return null;
+ }
+
+
+ return GetNodeWithSpanContext(SyntaxFactory.UnclassifiedTextLiteral(tokens));
+ }
+
+ private void OtherParserBlock(in SyntaxListBuilder builder)
+ {
+ // 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;
+
+ RazorSyntaxNode htmlBlock = null;
+ using (PushSpanContextConfig())
+ {
+ htmlBlock = HtmlParser.ParseBlock();
+ }
+
+ builder.Add(htmlBlock);
+ InitializeContext(SpanContext);
+
+ IsNested = wasNested;
+ NextToken();
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpTokenizer.cs b/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpTokenizer.cs
index 8d29ab0fe..21cd0dc00 100644
--- a/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpTokenizer.cs
+++ b/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpTokenizer.cs
@@ -458,7 +458,7 @@ private SyntaxKind Operator()
{
return handler();
}
- return SyntaxKind.Unknown;
+ return SyntaxKind.Marker;
}
private SyntaxKind LessThanOperator()
diff --git a/src/Microsoft.AspNetCore.Razor.Language/Legacy/HtmlLanguageCharacteristics.cs b/src/Microsoft.AspNetCore.Razor.Language/Legacy/HtmlLanguageCharacteristics.cs
index c404183ed..5efd2b80a 100644
--- a/src/Microsoft.AspNetCore.Razor.Language/Legacy/HtmlLanguageCharacteristics.cs
+++ b/src/Microsoft.AspNetCore.Razor.Language/Legacy/HtmlLanguageCharacteristics.cs
@@ -86,13 +86,13 @@ public override SyntaxKind FlipBracket(SyntaxKind bracket)
return SyntaxKind.OpenAngle;
default:
Debug.Fail("FlipBracket must be called with a bracket character");
- return SyntaxKind.Unknown;
+ return SyntaxKind.Marker;
}
}
public override SyntaxToken CreateMarkerToken()
{
- return SyntaxFactory.Token(SyntaxKind.Unknown, string.Empty);
+ return SyntaxFactory.Token(SyntaxKind.Marker, string.Empty);
}
public override SyntaxKind GetKnownTokenType(KnownTokenType type)
@@ -113,10 +113,10 @@ public override SyntaxKind GetKnownTokenType(KnownTokenType type)
return SyntaxKind.NewLine;
case KnownTokenType.Transition:
return SyntaxKind.Transition;
- case KnownTokenType.WhiteSpace:
+ case KnownTokenType.Whitespace:
return SyntaxKind.Whitespace;
default:
- return SyntaxKind.Unknown;
+ return SyntaxKind.Marker;
}
}
diff --git a/src/Microsoft.AspNetCore.Razor.Language/Legacy/HtmlMarkupParser.cs b/src/Microsoft.AspNetCore.Razor.Language/Legacy/HtmlMarkupParser.cs
index efc0d2759..4e325e41d 100644
--- a/src/Microsoft.AspNetCore.Razor.Language/Legacy/HtmlMarkupParser.cs
+++ b/src/Microsoft.AspNetCore.Razor.Language/Legacy/HtmlMarkupParser.cs
@@ -9,7 +9,7 @@
namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
- internal class HtmlMarkupParser : TokenizerBackedParser
+ internal partial class HtmlMarkupParser : TokenizerBackedParser
{
private const string ScriptTagName = "script";
@@ -54,7 +54,7 @@ public HtmlMarkupParser(ParserContext context)
{
}
- public ParserBase CodeParser { get; set; }
+ public CSharpCodeParser CodeParser { get; set; }
public ISet VoidElements
{
@@ -234,7 +234,7 @@ private void OtherParserBlock()
using (PushSpanConfig())
{
- CodeParser.ParseBlock();
+ CodeParser.ParseBlock1();
}
Span.Start = CurrentLocation;
@@ -275,7 +275,7 @@ private void OptionalBangEscape()
}
}
- public override void ParseBlock()
+ public override void ParseBlock1()
{
if (Context == null)
{
@@ -418,6 +418,11 @@ private void TagBlock(Stack> tags)
Accept(_bufferedOpenAngle);
EndTagBlock(tags, complete: false);
}
+ else if (atSpecialTag && At(SyntaxKind.Bang))
+ {
+ Accept(_bufferedOpenAngle);
+ complete = BangTag();
+ }
else
{
complete = AfterTagStart(tagStart, tags, atSpecialTag, tagBlockWrapper);
@@ -460,7 +465,7 @@ private bool AfterTagStart(SourceLocation tagStart,
case SyntaxKind.ForwardSlash:
// End Tag
return EndTag(tagStart, tags, tagBlockWrapper);
- case SyntaxKind.Bang:
+ case SyntaxKind.Bang: // Dead code. This case will never be hit.
// Comment, CDATA, DOCTYPE, or a parser-escaped HTML tag.
if (atSpecialTag)
{
@@ -516,7 +521,7 @@ private bool BangTag()
while (!EndOfFile)
{
SkipToAndParseCode(SyntaxKind.DoubleHyphen);
- var lastDoubleHyphen = AcceptAllButLastDoubleHyphens();
+ var lastDoubleHyphen = AcceptAllButLastDoubleHyphens1();
if (At(SyntaxKind.CloseAngle))
{
@@ -553,7 +558,7 @@ private bool BangTag()
return false;
}
- protected SyntaxToken AcceptAllButLastDoubleHyphens()
+ protected SyntaxToken AcceptAllButLastDoubleHyphens1()
{
var lastDoubleHyphen = CurrentToken;
AcceptWhile(s =>
@@ -732,7 +737,7 @@ private bool EndTag(SourceLocation tagStart,
tagName = CurrentToken.Content;
}
- var matched = RemoveTag(tags, tagName, tagStart);
+ var matched = RemoveTag1(tags, tagName, tagStart);
if (tags.Count == 0 &&
// Note tagName may contain a '!' escape character. This ensures !text> doesn't match here.
@@ -754,7 +759,7 @@ private bool EndTag(SourceLocation tagStart,
}
}
- private void RecoverTextTag()
+ private void RecoverTextTag1()
{
// We don't want to skip-to and parse because there shouldn't be anything in the body of text tags.
AcceptUntil(SyntaxKind.CloseAngle, SyntaxKind.NewLine);
@@ -781,7 +786,7 @@ private bool EndTextTag(SyntaxToken solidus, IDisposable tagBlockWrapper)
new SourceSpan(textLocation, contentLength: 4 /* text */)));
Span.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.Any;
- RecoverTextTag();
+ RecoverTextTag1();
}
else
{
@@ -833,14 +838,14 @@ private void TagContent()
else
{
// We are here ($):
- while (!EndOfFile && !IsEndOfTag())
+ while (!EndOfFile && !IsEndOfTag1())
{
BeforeAttribute();
}
}
}
- private bool IsEndOfTag()
+ private bool IsEndOfTag1()
{
if (At(SyntaxKind.ForwardSlash))
{
@@ -951,7 +956,7 @@ private void AttributePrefix(
AcceptAndMoveNext();
var whitespaceAfterEquals = ReadWhile(token => token.Kind == SyntaxKind.Whitespace || token.Kind == SyntaxKind.NewLine);
- var quote = SyntaxKind.Unknown;
+ var quote = SyntaxKind.Marker;
if (At(SyntaxKind.SingleQuote) || At(SyntaxKind.DoubleQuote))
{
// Found a quote, the whitespace belongs to this attribute.
@@ -976,7 +981,7 @@ private void AttributePrefix(
// Read the attribute value only if the value is quoted
// or if there is no whitespace between '=' and the unquoted value.
- if (quote != SyntaxKind.Unknown || !whitespaceAfterEquals.Any())
+ if (quote != SyntaxKind.Marker || !whitespaceAfterEquals.Any())
{
// Read the attribute value.
while (!EndOfFile && !IsEndOfAttributeValue(quote, CurrentToken))
@@ -987,7 +992,7 @@ private void AttributePrefix(
// Capture the suffix
var suffix = new LocationTagged(string.Empty, CurrentStart);
- if (quote != SyntaxKind.Unknown && At(quote))
+ if (quote != SyntaxKind.Marker && At(quote))
{
suffix = new LocationTagged(CurrentToken.Content, CurrentStart);
AcceptAndMoveNext();
@@ -1009,7 +1014,7 @@ private void AttributePrefix(
// Output the attribute name, the equals and optional quote. Ex: foo="
Output(SpanKindInternal.Markup);
- if (quote == SyntaxKind.Unknown && whitespaceAfterEquals.Any())
+ if (quote == SyntaxKind.Marker && whitespaceAfterEquals.Any())
{
return;
}
@@ -1020,7 +1025,7 @@ private void AttributePrefix(
// Output the attribute value (will include everything in-between the attribute's quotes).
Output(SpanKindInternal.Markup);
- if (quote != SyntaxKind.Unknown)
+ if (quote != SyntaxKind.Marker)
{
Optional(quote);
}
@@ -1102,7 +1107,7 @@ private void AttributeValue(SyntaxKind quote)
private bool IsEndOfAttributeValue(SyntaxKind quote, SyntaxToken token)
{
return EndOfFile || token == null ||
- (quote != SyntaxKind.Unknown
+ (quote != SyntaxKind.Marker
? token.Kind == quote // If quoted, just wait for the quote
: IsUnquotedEndOfAttributeValue(token));
}
@@ -1189,7 +1194,7 @@ private bool StartTag(Stack> tags, IDisposabl
if (potentialTagNameToken == null || potentialTagNameToken.Kind != SyntaxKind.Text)
{
- tagName = SyntaxFactory.Token(SyntaxKind.Unknown, string.Empty);
+ tagName = SyntaxFactory.Token(SyntaxKind.Marker, string.Empty);
}
else if (bangToken != null)
{
@@ -1236,7 +1241,7 @@ private bool StartTag(Stack> tags, IDisposabl
RazorDiagnosticFactory.CreateParsing_TextTagCannotContainAttributes(
new SourceSpan(textLocation, contentLength: 4 /* text */)));
- RecoverTextTag();
+ RecoverTextTag1();
}
else
{
@@ -1353,7 +1358,7 @@ private bool RestOfTag(Tuple tag,
}
else if (string.Equals(tagName, ScriptTagName, StringComparison.OrdinalIgnoreCase))
{
- if (!CurrentScriptTagExpectsHtml())
+ if (!CurrentScriptTagExpectsHtml1())
{
CompleteTagBlockWithSpan(tagBlockWrapper, AcceptedCharactersInternal.None, SpanKindInternal.Markup);
@@ -1465,7 +1470,7 @@ private bool AcceptUntilAll(params SyntaxKind[] endSequence)
return false;
}
- private bool RemoveTag(Stack> tags, string tagName, SourceLocation tagStart)
+ private bool RemoveTag1(Stack> tags, string tagName, SourceLocation tagStart)
{
Tuple currentTag = null;
while (tags.Count > 0)
@@ -1580,10 +1585,10 @@ internal static bool IsValidAttributeNameToken(SyntaxToken token)
tokenType != SyntaxKind.DoubleQuote &&
tokenType != SyntaxKind.SingleQuote &&
tokenType != SyntaxKind.Equals &&
- tokenType != SyntaxKind.Unknown;
+ tokenType != SyntaxKind.Marker;
}
- public void ParseDocument()
+ public void ParseDocument1()
{
if (Context == null)
{
@@ -1645,7 +1650,7 @@ private void ScanTagInDocumentContext()
if (ParserState == ParserState.Content)
{
- Output(SpanKindInternal.Markup, SyntaxKind.HtmlTextLiteral);
+ Output(SpanKindInternal.Markup, SyntaxKind.MarkupTextLiteral);
}
else
{
@@ -1674,7 +1679,7 @@ private void ScanTagInDocumentContext()
// If the script tag expects javascript content then we should do minimal parsing until we reach
// the end script tag. Don't want to incorrectly parse a "var tag = '';" as an HTML tag.
- if (scriptTag && !CurrentScriptTagExpectsHtml())
+ if (scriptTag && !CurrentScriptTagExpectsHtml1())
{
Output(SpanKindInternal.Markup);
tagBlock.Dispose();
@@ -1705,7 +1710,7 @@ private void ScanTagInDocumentContext()
}
}
- private bool CurrentScriptTagExpectsHtml()
+ private bool CurrentScriptTagExpectsHtml1()
{
var blockBuilder = Context.Builder.CurrentBlock;
@@ -1753,7 +1758,7 @@ private static bool IsTypeAttribute(Block block)
return false;
}
- public void ParseRazorBlock(Tuple nestingSequences, bool caseSensitive)
+ public void ParseRazorBlock1(Tuple nestingSequences, bool caseSensitive)
{
if (Context == null)
{
@@ -1786,9 +1791,9 @@ private void NonNestingSection(string[] nestingSequenceComponents)
{
do
{
- SkipToAndParseCode(token => token.Kind == SyntaxKind.OpenAngle || AtEnd(nestingSequenceComponents));
+ SkipToAndParseCode(token => token.Kind == SyntaxKind.OpenAngle || AtEnd1(nestingSequenceComponents));
ScanTagInDocumentContext();
- if (!EndOfFile && AtEnd(nestingSequenceComponents))
+ if (!EndOfFile && AtEnd1(nestingSequenceComponents))
{
break;
}
@@ -1825,7 +1830,7 @@ private void NestingSection(Tuple nestingSequences)
}
}
- private bool AtEnd(string[] nestingSequenceComponents)
+ private bool AtEnd1(string[] nestingSequenceComponents)
{
EnsureCurrent();
if (string.Equals(CurrentToken.Content, nestingSequenceComponents[0], Comparison))
diff --git a/src/Microsoft.AspNetCore.Razor.Language/Legacy/HtmlParser.cs b/src/Microsoft.AspNetCore.Razor.Language/Legacy/HtmlParser.cs
new file mode 100644
index 000000000..72306a76d
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Razor.Language/Legacy/HtmlParser.cs
@@ -0,0 +1,1765 @@
+// 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;
+using Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax;
+
+namespace Microsoft.AspNetCore.Razor.Language.Legacy
+{
+ internal partial class HtmlMarkupParser
+ {
+ public RazorDocumentSyntax ParseDocument()
+ {
+ if (Context == null)
+ {
+ throw new InvalidOperationException(Resources.Parser_Context_Not_Set);
+ }
+
+ using (var pooledResult = Pool.Allocate())
+ using (PushSpanContextConfig(DefaultMarkupSpanContext))
+ {
+ var builder = pooledResult.Builder;
+ NextToken();
+ while (!EndOfFile)
+ {
+ SkipToAndParseCode(builder, SyntaxKind.OpenAngle);
+ ParseTagInDocumentContext(builder);
+ }
+ AcceptMarkerTokenIfNecessary();
+ builder.Add(OutputTokensAsMarkupLiteral());
+
+ var markup = SyntaxFactory.MarkupBlock(builder.ToList());
+
+ return SyntaxFactory.RazorDocument(markup);
+ }
+ }
+
+ private void SkipToAndParseCode(in SyntaxListBuilder builder, SyntaxKind type)
+ {
+ SkipToAndParseCode(builder, token => token.Kind == type);
+ }
+
+ private void SkipToAndParseCode(in SyntaxListBuilder builder, Func condition)
+ {
+ SyntaxToken last = null;
+ var startOfLine = false;
+ while (!EndOfFile && !condition(CurrentToken))
+ {
+ if (Context.NullGenerateWhitespaceAndNewLine)
+ {
+ Context.NullGenerateWhitespaceAndNewLine = false;
+ SpanContext.ChunkGenerator = SpanChunkGenerator.Null;
+ AcceptTokenWhile(token => token.Kind == SyntaxKind.Whitespace);
+ if (At(SyntaxKind.NewLine))
+ {
+ AcceptTokenAndMoveNext();
+ }
+
+ builder.Add(OutputTokensAsMarkupEphemeralLiteral());
+ }
+ else if (At(SyntaxKind.NewLine))
+ {
+ if (last != null)
+ {
+ AcceptToken(last);
+ }
+
+ // Mark the start of a new line
+ startOfLine = true;
+ last = null;
+ AcceptTokenAndMoveNext();
+ }
+ else if (At(SyntaxKind.Transition))
+ {
+ var transition = CurrentToken;
+ NextToken();
+ if (At(SyntaxKind.Transition))
+ {
+ if (last != null)
+ {
+ AcceptToken(last);
+ last = null;
+ }
+ builder.Add(OutputTokensAsMarkupLiteral());
+ AcceptToken(transition);
+ SpanContext.ChunkGenerator = SpanChunkGenerator.Null;
+ builder.Add(OutputTokensAsMarkupEphemeralLiteral());
+ AcceptTokenAndMoveNext();
+ continue; // while
+ }
+ else
+ {
+ if (!EndOfFile)
+ {
+ PutCurrentBack();
+ }
+ PutBack(transition);
+ }
+
+ // Handle whitespace rewriting
+ if (last != null)
+ {
+ if (!Context.DesignTimeMode && last.Kind == SyntaxKind.Whitespace && startOfLine)
+ {
+ // Put the whitespace back too
+ startOfLine = false;
+ PutBack(last);
+ last = null;
+ }
+ else
+ {
+ // Accept last
+ AcceptToken(last);
+ last = null;
+ }
+ }
+
+ OtherParserBlock(builder);
+ }
+ else if (At(SyntaxKind.RazorCommentTransition))
+ {
+ var shouldRenderWhitespace = true;
+ if (last != null)
+ {
+ // Don't render the whitespace between the start of the line and the razor comment.
+ if (startOfLine && last.Kind == SyntaxKind.Whitespace)
+ {
+ AcceptMarkerTokenIfNecessary();
+ // Output the tokens that may have been accepted prior to the whitespace.
+ builder.Add(OutputTokensAsMarkupLiteral());
+
+ SpanContext.ChunkGenerator = SpanChunkGenerator.Null;
+ shouldRenderWhitespace = false;
+ }
+
+ AcceptToken(last);
+ last = null;
+ }
+
+ AcceptMarkerTokenIfNecessary();
+ if (shouldRenderWhitespace)
+ {
+ builder.Add(OutputTokensAsMarkupLiteral());
+ }
+ else
+ {
+ builder.Add(OutputTokensAsMarkupEphemeralLiteral());
+ }
+
+ var comment = ParseRazorComment();
+ builder.Add(comment);
+
+ // Handle the whitespace and newline at the end of a razor comment.
+ if (startOfLine &&
+ (At(SyntaxKind.NewLine) ||
+ (At(SyntaxKind.Whitespace) && NextIs(SyntaxKind.NewLine))))
+ {
+ AcceptTokenWhile(IsSpacingToken(includeNewLines: false));
+ AcceptTokenAndMoveNext();
+ SpanContext.ChunkGenerator = SpanChunkGenerator.Null;
+ builder.Add(OutputTokensAsMarkupEphemeralLiteral());
+ }
+ }
+ else
+ {
+ // As long as we see whitespace, we're still at the "start" of the line
+ startOfLine &= At(SyntaxKind.Whitespace);
+
+ // If there's a last token, accept it
+ if (last != null)
+ {
+ AcceptToken(last);
+ last = null;
+ }
+
+ // Advance
+ last = CurrentToken;
+ NextToken();
+ }
+ }
+
+ if (last != null)
+ {
+ AcceptToken(last);
+ }
+ }
+
+ ///
+ /// Reads the content of a tag (if present) in the MarkupDocument (or MarkupSection) context,
+ /// where we don't care about maintaining a stack of tags.
+ ///
+ private void ParseTagInDocumentContext(in SyntaxListBuilder builder)
+ {
+ if (At(SyntaxKind.OpenAngle))
+ {
+ if (NextIs(SyntaxKind.Bang))
+ {
+ // Checking to see if we meet the conditions of a special '!' tag: ())
+ {
+ var tagBuilder = pooledResult.Builder;
+ AcceptTokenAndMoveNext(); // Accept '<'
+
+ if (!At(SyntaxKind.ForwardSlash))
+ {
+ ParseOptionalBangEscape(tagBuilder);
+
+ // Parsing a start tag
+ var scriptTag = At(SyntaxKind.Text) &&
+ string.Equals(CurrentToken.Content, "script", StringComparison.OrdinalIgnoreCase);
+ OptionalToken(SyntaxKind.Text);
+ ParseTagContent(tagBuilder); // Parse the tag, don't care about the content
+ OptionalToken(SyntaxKind.ForwardSlash);
+ OptionalToken(SyntaxKind.CloseAngle);
+
+ // If the script tag expects javascript content then we should do minimal parsing until we reach
+ // the end script tag. Don't want to incorrectly parse a "var tag = '';" as an HTML tag.
+ if (scriptTag && !CurrentScriptTagExpectsHtml(tagBuilder))
+ {
+ tagBuilder.Add(OutputTokensAsMarkupLiteral());
+ var block = SyntaxFactory.MarkupTagBlock(tagBuilder.ToList());
+ builder.Add(block);
+
+ SkipToEndScriptAndParseCode(builder);
+ return;
+ }
+ }
+ else
+ {
+ // Parsing an end tag
+ // This section can accept things like: '
' or '' etc.
+ ParserState = ParserState.EndTag;
+ OptionalToken(SyntaxKind.ForwardSlash);
+
+ // Whitespace here is invalid (according to the spec)
+ ParseOptionalBangEscape(tagBuilder);
+ OptionalToken(SyntaxKind.Text);
+ OptionalToken(SyntaxKind.Whitespace);
+ OptionalToken(SyntaxKind.CloseAngle);
+ ParserState = ParserState.Content;
+ }
+
+ tagBuilder.Add(OutputTokensAsMarkupLiteral());
+
+ // End tag block
+ var tagBlock = SyntaxFactory.MarkupTagBlock(tagBuilder.ToList());
+ builder.Add(tagBlock);
+ }
+ }
+ }
+
+ private void ParseTagContent(in SyntaxListBuilder builder)
+ {
+ if (!At(SyntaxKind.Whitespace) && !At(SyntaxKind.NewLine))
+ {
+ // We should be right after the tag name, so if there's no whitespace or new line, something is wrong
+ RecoverToEndOfTag(builder);
+ }
+ else
+ {
+ // We are here ($):
+ while (!EndOfFile && !IsEndOfTag())
+ {
+ BeforeAttribute(builder);
+ }
+ }
+ }
+
+ private bool IsEndOfTag()
+ {
+ if (At(SyntaxKind.ForwardSlash))
+ {
+ if (NextIs(SyntaxKind.CloseAngle))
+ {
+ return true;
+ }
+ else
+ {
+ AcceptTokenAndMoveNext();
+ }
+ }
+ return At(SyntaxKind.CloseAngle) || At(SyntaxKind.OpenAngle);
+ }
+
+ private void BeforeAttribute(in SyntaxListBuilder builder)
+ {
+ // http://dev.w3.org/html5/spec/tokenization.html#before-attribute-name-state
+ // Capture whitespace
+ var whitespace = ReadWhile(token => token.Kind == SyntaxKind.Whitespace || token.Kind == SyntaxKind.NewLine);
+
+ if (At(SyntaxKind.Transition) || At(SyntaxKind.RazorCommentTransition))
+ {
+ // Transition outside of attribute value => Switch to recovery mode
+ AcceptToken(whitespace);
+ RecoverToEndOfTag(builder);
+ return;
+ }
+
+ // http://dev.w3.org/html5/spec/tokenization.html#attribute-name-state
+ // Read the 'name' (i.e. read until the '=' or whitespace/newline)
+ var nameTokens = Enumerable.Empty();
+ var whitespaceAfterAttributeName = Enumerable.Empty();
+ if (IsValidAttributeNameToken(CurrentToken))
+ {
+ nameTokens = ReadWhile(token =>
+ token.Kind != SyntaxKind.Whitespace &&
+ token.Kind != SyntaxKind.NewLine &&
+ token.Kind != SyntaxKind.Equals &&
+ token.Kind != SyntaxKind.CloseAngle &&
+ token.Kind != SyntaxKind.OpenAngle &&
+ (token.Kind != SyntaxKind.ForwardSlash || !NextIs(SyntaxKind.CloseAngle)));
+
+ // capture whitespace after attribute name (if any)
+ whitespaceAfterAttributeName = ReadWhile(
+ token => token.Kind == SyntaxKind.Whitespace || token.Kind == SyntaxKind.NewLine);
+ }
+ else
+ {
+ // Unexpected character in tag, enter recovery
+ AcceptToken(whitespace);
+ RecoverToEndOfTag(builder);
+ return;
+ }
+
+ if (!At(SyntaxKind.Equals))
+ {
+ // Minimized attribute
+
+ // We are at the prefix of the next attribute or the end of tag. Put it back so it is parsed later.
+ PutCurrentBack();
+ PutBack(whitespaceAfterAttributeName);
+
+ // Output anything prior to the attribute, in most cases this will be the tag name:
+ // |. If in-between other attributes this will noop or output malformed attribute
+ // content (if the previous attribute was malformed).
+ builder.Add(OutputTokensAsMarkupLiteral());
+
+ AcceptToken(whitespace);
+ var namePrefix = OutputTokensAsMarkupLiteral();
+ AcceptToken(nameTokens);
+ var name = OutputTokensAsMarkupLiteral();
+
+ var minimizedAttributeBlock = SyntaxFactory.MarkupMinimizedAttributeBlock(namePrefix, name);
+ builder.Add(minimizedAttributeBlock);
+
+ return;
+ }
+
+ // Not a minimized attribute, parse as if it were well-formed (if attribute turns out to be malformed we
+ // will go into recovery).
+ builder.Add(OutputTokensAsMarkupLiteral());
+
+ var attributeBlock = ParseAttributePrefix(whitespace, nameTokens, whitespaceAfterAttributeName);
+
+ builder.Add(attributeBlock);
+ }
+
+ private MarkupAttributeBlockSyntax ParseAttributePrefix(
+ IEnumerable whitespace,
+ IEnumerable nameTokens,
+ IEnumerable whitespaceAfterAttributeName)
+ {
+ // First, determine if this is a 'data-' attribute (since those can't use conditional attributes)
+ var nameContent = string.Concat(nameTokens.Select(s => s.Content));
+ var attributeCanBeConditional =
+ Context.FeatureFlags.EXPERIMENTAL_AllowConditionalDataDashAttributes ||
+ !nameContent.StartsWith("data-", StringComparison.OrdinalIgnoreCase);
+
+ // Accept the whitespace and name
+ AcceptToken(whitespace);
+ var namePrefix = OutputTokensAsMarkupLiteral();
+ AcceptToken(nameTokens);
+ var name = OutputTokensAsMarkupLiteral();
+
+ // Since this is not a minimized attribute, the whitespace after attribute name belongs to this attribute.
+ AcceptToken(whitespaceAfterAttributeName);
+ var nameSuffix = OutputTokensAsMarkupLiteral();
+ Assert(SyntaxKind.Equals); // We should be at "="
+ var equalsToken = EatCurrentToken();
+
+ var whitespaceAfterEquals = ReadWhile(token => token.Kind == SyntaxKind.Whitespace || token.Kind == SyntaxKind.NewLine);
+ var quote = SyntaxKind.Marker;
+ if (At(SyntaxKind.SingleQuote) || At(SyntaxKind.DoubleQuote))
+ {
+ // Found a quote, the whitespace belongs to this attribute.
+ AcceptToken(whitespaceAfterEquals);
+ quote = CurrentToken.Kind;
+ AcceptTokenAndMoveNext();
+ }
+ else if (whitespaceAfterEquals.Any())
+ {
+ // No quotes found after the whitespace. Put it back so that it can be parsed later.
+ PutCurrentBack();
+ PutBack(whitespaceAfterEquals);
+ }
+
+ MarkupTextLiteralSyntax valuePrefix = null;
+ RazorBlockSyntax attributeValue = null;
+ MarkupTextLiteralSyntax valueSuffix = null;
+
+ if (attributeCanBeConditional)
+ {
+ SpanContext.ChunkGenerator = SpanChunkGenerator.Null; // The block chunk generator will render the prefix
+
+ // We now have the value prefix which is usually whitespace and/or a quote
+ valuePrefix = OutputTokensAsMarkupLiteral();
+
+ // Read the attribute value only if the value is quoted
+ // or if there is no whitespace between '=' and the unquoted value.
+ if (quote != SyntaxKind.Marker || !whitespaceAfterEquals.Any())
+ {
+ using (var pooledResult = Pool.Allocate())
+ {
+ var attributeValueBuilder = pooledResult.Builder;
+ // Read the attribute value.
+ while (!EndOfFile && !IsEndOfAttributeValue(quote, CurrentToken))
+ {
+ ParseAttributeValue(attributeValueBuilder, quote);
+ }
+
+ if (attributeValueBuilder.Count > 0)
+ {
+ attributeValue = SyntaxFactory.GenericBlock(attributeValueBuilder.ToList());
+ }
+ }
+ }
+
+ // Capture the suffix
+ if (quote != SyntaxKind.Marker && At(quote))
+ {
+ AcceptTokenAndMoveNext();
+ // Again, block chunk generator will render the suffix
+ SpanContext.ChunkGenerator = SpanChunkGenerator.Null;
+ valueSuffix = OutputTokensAsMarkupLiteral();
+ }
+ }
+ else if (quote != SyntaxKind.Marker || !whitespaceAfterEquals.Any())
+ {
+ valuePrefix = OutputTokensAsMarkupLiteral();
+
+ using (var pooledResult = Pool.Allocate())
+ {
+ var attributeValueBuilder = pooledResult.Builder;
+ // Not a "conditional" attribute, so just read the value
+ SkipToAndParseCode(attributeValueBuilder, token => IsEndOfAttributeValue(quote, token));
+
+ // Output already accepted tokens if any as markup literal
+ var literalValue = OutputTokensAsMarkupLiteral();
+ attributeValueBuilder.Add(literalValue);
+
+ // Capture the attribute value (will include everything in-between the attribute's quotes).
+ attributeValue = SyntaxFactory.GenericBlock(attributeValueBuilder.ToList());
+ }
+
+ if (quote != SyntaxKind.Marker)
+ {
+ OptionalToken(quote);
+ valueSuffix = OutputTokensAsMarkupLiteral();
+ }
+ }
+ else
+ {
+ // There is no quote and there is whitespace after equals. There is no attribute value.
+ }
+
+ return SyntaxFactory.MarkupAttributeBlock(namePrefix, name, nameSuffix, equalsToken, valuePrefix, attributeValue, valueSuffix);
+ }
+
+ private void ParseAttributeValue(in SyntaxListBuilder builder, SyntaxKind quote)
+ {
+ var prefixStart = CurrentStart;
+ var prefixTokens = ReadWhile(token => token.Kind == SyntaxKind.Whitespace || token.Kind == SyntaxKind.NewLine);
+
+ if (At(SyntaxKind.Transition))
+ {
+ if (NextIs(SyntaxKind.Transition))
+ {
+ // Wrapping this in a block so that the ConditionalAttributeCollapser doesn't rewrite it.
+ using (var pooledResult = Pool.Allocate())
+ {
+ var markupBuilder = pooledResult.Builder;
+ AcceptToken(prefixTokens);
+
+ // Render a single "@" in place of "@@".
+ SpanContext.ChunkGenerator = new LiteralAttributeChunkGenerator(
+ new LocationTagged(string.Concat(prefixTokens.Select(s => s.Content)), prefixStart),
+ new LocationTagged(CurrentToken.Content, CurrentStart));
+ AcceptTokenAndMoveNext();
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.None;
+ markupBuilder.Add(OutputTokensAsMarkupLiteral());
+
+ SpanContext.ChunkGenerator = SpanChunkGenerator.Null;
+ AcceptTokenAndMoveNext();
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.None;
+ markupBuilder.Add(OutputTokensAsMarkupEphemeralLiteral());
+
+ var markupBlock = SyntaxFactory.MarkupBlock(markupBuilder.ToList());
+ builder.Add(markupBlock);
+ }
+ }
+ else
+ {
+ AcceptToken(prefixTokens);
+ var valueStart = CurrentStart;
+ PutCurrentBack();
+
+ var prefix = OutputTokensAsMarkupLiteral();
+
+ // Dynamic value, start a new block and set the chunk generator
+ using (var pooledResult = Pool.Allocate())
+ {
+ var dynamicAttributeValueBuilder = pooledResult.Builder;
+
+ OtherParserBlock(dynamicAttributeValueBuilder);
+ var value = SyntaxFactory.MarkupDynamicAttributeValue(prefix, SyntaxFactory.GenericBlock(dynamicAttributeValueBuilder.ToList()));
+ builder.Add(value);
+ }
+ }
+ }
+ else
+ {
+ AcceptToken(prefixTokens);
+ var prefix = OutputTokensAsMarkupLiteral();
+
+ // Literal value
+ // 'quote' should be "Unknown" if not quoted and tokens coming from the tokenizer should never have
+ // "Unknown" type.
+ var valueTokens = ReadWhile(token =>
+ // These three conditions find separators which break the attribute value into portions
+ token.Kind != SyntaxKind.Whitespace &&
+ token.Kind != SyntaxKind.NewLine &&
+ token.Kind != SyntaxKind.Transition &&
+ // This condition checks for the end of the attribute value (it repeats some of the checks above
+ // but for now that's ok)
+ !IsEndOfAttributeValue(quote, token));
+ AcceptToken(valueTokens);
+ var value = OutputTokensAsMarkupLiteral();
+
+ var literalAttributeValue = SyntaxFactory.MarkupLiteralAttributeValue(prefix, value);
+ builder.Add(literalAttributeValue);
+ }
+ }
+
+ private void RecoverToEndOfTag(in SyntaxListBuilder builder)
+ {
+ // Accept until ">", "/" or "<", but parse code
+ while (!EndOfFile)
+ {
+ SkipToAndParseCode(builder, IsTagRecoveryStopPoint);
+ if (!EndOfFile)
+ {
+ EnsureCurrent();
+ switch (CurrentToken.Kind)
+ {
+ case SyntaxKind.SingleQuote:
+ case SyntaxKind.DoubleQuote:
+ ParseQuoted(builder);
+ break;
+ case SyntaxKind.OpenAngle:
+ // Another "<" means this tag is invalid.
+ case SyntaxKind.ForwardSlash:
+ // Empty tag
+ case SyntaxKind.CloseAngle:
+ // End of tag
+ return;
+ default:
+ AcceptTokenAndMoveNext();
+ break;
+ }
+ }
+ }
+ }
+
+ private void ParseQuoted(in SyntaxListBuilder builder)
+ {
+ var type = CurrentToken.Kind;
+ AcceptTokenAndMoveNext();
+ ParseQuoted(builder, type);
+ }
+
+ private void ParseQuoted(in SyntaxListBuilder builder, SyntaxKind type)
+ {
+ SkipToAndParseCode(builder, type);
+ if (!EndOfFile)
+ {
+ Assert(type);
+ AcceptTokenAndMoveNext();
+ }
+ }
+
+ private bool ParseBangTag(in SyntaxListBuilder builder)
+ {
+ // Accept "!"
+ Assert(SyntaxKind.Bang);
+
+ if (AcceptTokenAndMoveNext())
+ {
+ if (IsHtmlCommentAhead())
+ {
+ using (var pooledResult = Pool.Allocate())
+ {
+ var htmlCommentBuilder = pooledResult.Builder;
+
+ // Accept the double-hyphen token at the beginning of the comment block.
+ AcceptTokenAndMoveNext();
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.None;
+ htmlCommentBuilder.Add(OutputTokensAsMarkupLiteral());
+
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.Whitespace;
+ while (!EndOfFile)
+ {
+ SkipToAndParseCode(htmlCommentBuilder, SyntaxKind.DoubleHyphen);
+ var lastDoubleHyphen = AcceptAllButLastDoubleHyphens();
+
+ if (At(SyntaxKind.CloseAngle))
+ {
+ // Output the content in the comment block as a separate markup
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.Whitespace;
+ htmlCommentBuilder.Add(OutputTokensAsMarkupLiteral());
+
+ // This is the end of a comment block
+ AcceptToken(lastDoubleHyphen);
+ AcceptTokenAndMoveNext();
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.None;
+ htmlCommentBuilder.Add(OutputTokensAsMarkupLiteral());
+ var commentBlock = SyntaxFactory.MarkupCommentBlock(htmlCommentBuilder.ToList());
+ builder.Add(commentBlock);
+ return true;
+ }
+ else if (lastDoubleHyphen != null)
+ {
+ AcceptToken(lastDoubleHyphen);
+ }
+ }
+ }
+ }
+ else if (CurrentToken.Kind == SyntaxKind.LeftBracket)
+ {
+ if (AcceptTokenAndMoveNext())
+ {
+ return TryParseCData(builder);
+ }
+ }
+ else
+ {
+ AcceptTokenAndMoveNext();
+ return AcceptTokenUntilAll(builder, SyntaxKind.CloseAngle);
+ }
+ }
+
+ return false;
+ }
+
+ private bool TryParseCData(in SyntaxListBuilder builder)
+ {
+ if (CurrentToken.Kind == SyntaxKind.Text && string.Equals(CurrentToken.Content, "cdata", StringComparison.OrdinalIgnoreCase))
+ {
+ if (AcceptTokenAndMoveNext())
+ {
+ if (CurrentToken.Kind == SyntaxKind.LeftBracket)
+ {
+ return AcceptTokenUntilAll(builder, SyntaxKind.RightBracket, SyntaxKind.RightBracket, SyntaxKind.CloseAngle);
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private bool TryParseXmlPI(in SyntaxListBuilder builder)
+ {
+ // Accept "?"
+ Assert(SyntaxKind.QuestionMark);
+ AcceptTokenAndMoveNext();
+ return AcceptTokenUntilAll(builder, SyntaxKind.QuestionMark, SyntaxKind.CloseAngle);
+ }
+
+ private void ParseOptionalBangEscape(in SyntaxListBuilder builder)
+ {
+ if (IsBangEscape(lookahead: 0))
+ {
+ builder.Add(OutputTokensAsMarkupLiteral());
+
+ // Accept the parser escape character '!'.
+ Assert(SyntaxKind.Bang);
+ AcceptTokenAndMoveNext();
+
+ // Setup the metacode span that we will be outputing.
+ SpanContext.ChunkGenerator = SpanChunkGenerator.Null;
+ builder.Add(OutputAsMetaCode(OutputTokens()));
+ }
+ }
+
+ private void SkipToEndScriptAndParseCode(in SyntaxListBuilder builder, AcceptedCharactersInternal endTagAcceptedCharacters = AcceptedCharactersInternal.Any)
+ {
+ // Special case for ] - SpanEditHandler;Accepts:None - (27:0,27) - Tokens:4
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.ForwardSlash;[/];
- SyntaxKind.Text;[script];
- SyntaxKind.CloseAngle;[>];
+MarkupBlock - [0..36)::36 - []
+ MarkupTagBlock - [0..8)::8 - []
+ MarkupTextLiteral - [27..36)::9 - [] - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ ForwardSlash;[/];
+ Text;[script];
+ CloseAngle;[>];
diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/OnlyTerminatesCommentOnFullEndSequence.stree.txt b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/OnlyTerminatesCommentOnFullEndSequence.stree.txt
index 22d402b96..965a65db7 100644
--- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/OnlyTerminatesCommentOnFullEndSequence.stree.txt
+++ b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/OnlyTerminatesCommentOnFullEndSequence.stree.txt
@@ -1,18 +1,18 @@
-Markup block - Gen - 20 - (0:0,0)
- HtmlComment block - Gen - 20 - (0:0,0)
- Markup span - Gen - [] - SpanEditHandler;Accepts:None - (17:0,17) - Tokens:2
- SyntaxKind.DoubleHyphen;[--];
- SyntaxKind.CloseAngle;[>];
+MarkupBlock - [0..20)::20 - []
+ MarkupCommentBlock - [0..20)::20
+ MarkupTextLiteral - [0..4)::4 - [] - Gen - SpanEditHandler;Accepts:None
+ DoubleHyphen;[--];
+ CloseAngle;[>];
diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/ParsesSGMLDeclarationAsEmptyTag.stree.txt b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/ParsesSGMLDeclarationAsEmptyTag.stree.txt
index b3b77f162..228dacf05 100644
--- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/ParsesSGMLDeclarationAsEmptyTag.stree.txt
+++ b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/ParsesSGMLDeclarationAsEmptyTag.stree.txt
@@ -1,23 +1,23 @@
-Markup block - Gen - 33 - (0:0,0)
- Tag block - Gen - 5 - (0:0,0)
- Markup span - Gen - [] - SpanEditHandler;Accepts:None - (0:0,0) - Tokens:3
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.Text;[foo];
- SyntaxKind.CloseAngle;[>];
- Markup span - Gen - [] - SpanEditHandler;Accepts:None - (5:0,5) - Tokens:10
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.Bang;[!];
- SyntaxKind.Text;[DOCTYPE];
- SyntaxKind.Whitespace;[ ];
- SyntaxKind.Text;[foo];
- SyntaxKind.Whitespace;[ ];
- SyntaxKind.Text;[bar];
- SyntaxKind.Whitespace;[ ];
- SyntaxKind.Text;[baz];
- SyntaxKind.CloseAngle;[>];
- Tag block - Gen - 6 - (27:0,27)
- Markup span - Gen - [] - SpanEditHandler;Accepts:None - (27:0,27) - Tokens:4
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.ForwardSlash;[/];
- SyntaxKind.Text;[foo];
- SyntaxKind.CloseAngle;[>];
+MarkupBlock - [0..33)::33 - []
+ MarkupTagBlock - [0..5)::5 - []
+ MarkupTextLiteral - [0..5)::5 - [] - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ Text;[foo];
+ CloseAngle;[>];
+ MarkupTextLiteral - [5..27)::22 - [] - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ Bang;[!];
+ Text;[DOCTYPE];
+ Whitespace;[ ];
+ Text;[foo];
+ Whitespace;[ ];
+ Text;[bar];
+ Whitespace;[ ];
+ Text;[baz];
+ CloseAngle;[>];
+ MarkupTagBlock - [27..33)::6 - []
+ MarkupTextLiteral - [27..33)::6 - [] - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ ForwardSlash;[/];
+ Text;[foo];
+ CloseAngle;[>];
diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/ParsesUntilMatchingEndTagIfFirstNonWhitespaceCharacterIsStartTag.stree.txt b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/ParsesUntilMatchingEndTagIfFirstNonWhitespaceCharacterIsStartTag.stree.txt
index 42f0bb5e2..89954be67 100644
--- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/ParsesUntilMatchingEndTagIfFirstNonWhitespaceCharacterIsStartTag.stree.txt
+++ b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/ParsesUntilMatchingEndTagIfFirstNonWhitespaceCharacterIsStartTag.stree.txt
@@ -1,34 +1,34 @@
-Markup block - Gen - 33 - (0:0,0)
- Tag block - Gen - 5 - (0:0,0)
- Markup span - Gen - [] - SpanEditHandler;Accepts:None - (0:0,0) - Tokens:3
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.Text;[baz];
- SyntaxKind.CloseAngle;[>];
- Tag block - Gen - 5 - (5:0,5)
- Markup span - Gen - [] - SpanEditHandler;Accepts:None - (5:0,5) - Tokens:3
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.Text;[boz];
- SyntaxKind.CloseAngle;[>];
- Tag block - Gen - 5 - (10:0,10)
- Markup span - Gen - [] - SpanEditHandler;Accepts:None - (10:0,10) - Tokens:3
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.Text;[biz];
- SyntaxKind.CloseAngle;[>];
- Tag block - Gen - 6 - (15:0,15)
- Markup span - Gen - [] - SpanEditHandler;Accepts:None - (15:0,15) - Tokens:4
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.ForwardSlash;[/];
- SyntaxKind.Text;[biz];
- SyntaxKind.CloseAngle;[>];
- Tag block - Gen - 6 - (21:0,21)
- Markup span - Gen - [] - SpanEditHandler;Accepts:None - (21:0,21) - Tokens:4
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.ForwardSlash;[/];
- SyntaxKind.Text;[boz];
- SyntaxKind.CloseAngle;[>];
- Tag block - Gen - 6 - (27:0,27)
- Markup span - Gen - [] - SpanEditHandler;Accepts:None - (27:0,27) - Tokens:4
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.ForwardSlash;[/];
- SyntaxKind.Text;[baz];
- SyntaxKind.CloseAngle;[>];
+MarkupBlock - [0..33)::33 - []
+ MarkupTagBlock - [0..5)::5 - []
+ MarkupTextLiteral - [0..5)::5 - [] - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ Text;[baz];
+ CloseAngle;[>];
+ MarkupTagBlock - [5..10)::5 - []
+ MarkupTextLiteral - [5..10)::5 - [] - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ Text;[boz];
+ CloseAngle;[>];
+ MarkupTagBlock - [10..15)::5 - []
+ MarkupTextLiteral - [10..15)::5 - [] - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ Text;[biz];
+ CloseAngle;[>];
+ MarkupTagBlock - [15..21)::6 - []
+ MarkupTextLiteral - [15..21)::6 - [] - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ ForwardSlash;[/];
+ Text;[biz];
+ CloseAngle;[>];
+ MarkupTagBlock - [21..27)::6 - []
+ MarkupTextLiteral - [21..27)::6 - [] - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ ForwardSlash;[/];
+ Text;[boz];
+ CloseAngle;[>];
+ MarkupTagBlock - [27..33)::6 - []
+ MarkupTextLiteral - [27..33)::6 - [] - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ ForwardSlash;[/];
+ Text;[baz];
+ CloseAngle;[>];
diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/ParsesXMLProcessingInstructionAsEmptyTag.stree.txt b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/ParsesXMLProcessingInstructionAsEmptyTag.stree.txt
index e8ab55b89..b416a6b86 100644
--- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/ParsesXMLProcessingInstructionAsEmptyTag.stree.txt
+++ b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/ParsesXMLProcessingInstructionAsEmptyTag.stree.txt
@@ -1,24 +1,24 @@
-Markup block - Gen - 30 - (0:0,0)
- Tag block - Gen - 5 - (0:0,0)
- Markup span - Gen - [] - SpanEditHandler;Accepts:None - (0:0,0) - Tokens:3
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.Text;[foo];
- SyntaxKind.CloseAngle;[>];
- Markup span - Gen - [] - SpanEditHandler;Accepts:None - (5:0,5) - Tokens:11
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.QuestionMark;[?];
- SyntaxKind.Text;[xml];
- SyntaxKind.Whitespace;[ ];
- SyntaxKind.Text;[foo];
- SyntaxKind.Whitespace;[ ];
- SyntaxKind.Text;[bar];
- SyntaxKind.Whitespace;[ ];
- SyntaxKind.Text;[baz];
- SyntaxKind.QuestionMark;[?];
- SyntaxKind.CloseAngle;[>];
- Tag block - Gen - 6 - (24:0,24)
- Markup span - Gen - [] - SpanEditHandler;Accepts:None - (24:0,24) - Tokens:4
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.ForwardSlash;[/];
- SyntaxKind.Text;[foo];
- SyntaxKind.CloseAngle;[>];
+MarkupBlock - [0..30)::30 - []
+ MarkupTagBlock - [0..5)::5 - []
+ MarkupTextLiteral - [0..5)::5 - [] - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ Text;[foo];
+ CloseAngle;[>];
+ MarkupTextLiteral - [5..24)::19 - [] - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ QuestionMark;[?];
+ Text;[xml];
+ Whitespace;[ ];
+ Text;[foo];
+ Whitespace;[ ];
+ Text;[bar];
+ Whitespace;[ ];
+ Text;[baz];
+ QuestionMark;[?];
+ CloseAngle;[>];
+ MarkupTagBlock - [24..30)::6 - []
+ MarkupTextLiteral - [24..30)::6 - [] - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ ForwardSlash;[/];
+ Text;[foo];
+ CloseAngle;[>];
diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/ProperlyBalancesCommentStartAndEndTags.stree.txt b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/ProperlyBalancesCommentStartAndEndTags.stree.txt
index b302b41ea..9a7e05221 100644
--- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/ProperlyBalancesCommentStartAndEndTags.stree.txt
+++ b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/ProperlyBalancesCommentStartAndEndTags.stree.txt
@@ -1,17 +1,17 @@
-Markup block - Gen - 18 - (0:0,0)
- HtmlComment block - Gen - 18 - (0:0,0)
- Markup span - Gen - [] - SpanEditHandler;Accepts:None - (15:0,15) - Tokens:2
- SyntaxKind.DoubleHyphen;[--];
- SyntaxKind.CloseAngle;[>];
+MarkupBlock - [0..18)::18 - []
+ MarkupCommentBlock - [0..18)::18
+ MarkupTextLiteral - [0..4)::4 - [] - Gen - SpanEditHandler;Accepts:None
+ DoubleHyphen;[--];
+ CloseAngle;[>];
diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/ReadsToEndOfLineIfFirstCharacterAfterTransitionIsColon.stree.txt b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/ReadsToEndOfLineIfFirstCharacterAfterTransitionIsColon.stree.txt
index 7ec39929e..1c7838e43 100644
--- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/ReadsToEndOfLineIfFirstCharacterAfterTransitionIsColon.stree.txt
+++ b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/ReadsToEndOfLineIfFirstCharacterAfterTransitionIsColon.stree.txt
@@ -1,15 +1,15 @@
-Markup block - Gen - 19 - (0:0,0)
- Transition span - Gen - [@] - SpanEditHandler;Accepts:None - (0:0,0) - Tokens:1
- SyntaxKind.Transition;[@];
- MetaCode span - Gen - [:] - SpanEditHandler;Accepts:Any - (1:0,1) - Tokens:1
- SyntaxKind.Colon;[:];
- Markup span - Gen - [Foo Bar BazLF] - SpanEditHandler;Accepts:None - (2:0,2) - Tokens:9
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.Text;[li];
- SyntaxKind.CloseAngle;[>];
- SyntaxKind.Text;[Foo];
- SyntaxKind.Whitespace;[ ];
- SyntaxKind.Text;[Bar];
- SyntaxKind.Whitespace;[ ];
- SyntaxKind.Text;[Baz];
- SyntaxKind.NewLine;[LF];
+MarkupBlock - [0..19)::19 - [@:Foo Bar BazLF]
+ MarkupTransition - [0..1)::1 - Gen - SpanEditHandler;Accepts:None
+ Transition;[@];
+ RazorMetaCode - [1..2)::1 - Gen - SpanEditHandler;Accepts:Any
+ Colon;[:];
+ MarkupTextLiteral - [2..19)::17 - [Foo Bar BazLF] - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ Text;[li];
+ CloseAngle;[>];
+ Text;[Foo];
+ Whitespace;[ ];
+ Text;[Bar];
+ Whitespace;[ ];
+ Text;[Baz];
+ NewLine;[LF];
diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/RendersLiteralTextTagIfDoubled.stree.txt b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/RendersLiteralTextTagIfDoubled.stree.txt
index d67192745..ef9daab3a 100644
--- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/RendersLiteralTextTagIfDoubled.stree.txt
+++ b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/RendersLiteralTextTagIfDoubled.stree.txt
@@ -1,36 +1,36 @@
-Markup block - Gen - 43 - (0:0,0)
- Tag block - Gen - 6 - (0:0,0)
- Transition span - Gen - [] - SpanEditHandler;Accepts:None - (0:0,0) - Tokens:3
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.Text;[text];
- SyntaxKind.CloseAngle;[>];
- Tag block - Gen - 6 - (6:0,6)
- Markup span - Gen - [] - SpanEditHandler;Accepts:None - (6:0,6) - Tokens:3
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.Text;[text];
- SyntaxKind.CloseAngle;[>];
- Markup span - Gen - [Foo Bar ] - SpanEditHandler;Accepts:Any - (12:0,12) - Tokens:4
- SyntaxKind.Text;[Foo];
- SyntaxKind.Whitespace;[ ];
- SyntaxKind.Text;[Bar];
- SyntaxKind.Whitespace;[ ];
- Tag block - Gen - 5 - (20:0,20)
- Markup span - Gen - [] - SpanEditHandler;Accepts:None - (20:0,20) - Tokens:3
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.Text;[foo];
- SyntaxKind.CloseAngle;[>];
- Markup span - Gen - [ Baz] - SpanEditHandler;Accepts:Any - (25:0,25) - Tokens:2
- SyntaxKind.Whitespace;[ ];
- SyntaxKind.Text;[Baz];
- Tag block - Gen - 7 - (29:0,29)
- Markup span - Gen - [] - SpanEditHandler;Accepts:None - (29:0,29) - Tokens:4
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.ForwardSlash;[/];
- SyntaxKind.Text;[text];
- SyntaxKind.CloseAngle;[>];
- Tag block - Gen - 7 - (36:0,36)
- Transition span - Gen - [] - SpanEditHandler;Accepts:None - (36:0,36) - Tokens:4
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.ForwardSlash;[/];
- SyntaxKind.Text;[text];
- SyntaxKind.CloseAngle;[>];
+MarkupBlock - [0..43)::43 - [Foo Bar Baz]
+ MarkupTagBlock - [0..6)::6 - []
+ MarkupTransition - [0..6)::6 - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ Text;[text];
+ CloseAngle;[>];
+ MarkupTagBlock - [6..12)::6 - []
+ MarkupTextLiteral - [6..12)::6 - [] - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ Text;[text];
+ CloseAngle;[>];
+ MarkupTextLiteral - [12..20)::8 - [Foo Bar ] - Gen - SpanEditHandler;Accepts:Any
+ Text;[Foo];
+ Whitespace;[ ];
+ Text;[Bar];
+ Whitespace;[ ];
+ MarkupTagBlock - [20..25)::5 - []
+ MarkupTextLiteral - [20..25)::5 - [] - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ Text;[foo];
+ CloseAngle;[>];
+ MarkupTextLiteral - [25..29)::4 - [ Baz] - Gen - SpanEditHandler;Accepts:Any
+ Whitespace;[ ];
+ Text;[Baz];
+ MarkupTagBlock - [29..36)::7 - []
+ MarkupTextLiteral - [29..36)::7 - [] - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ ForwardSlash;[/];
+ Text;[text];
+ CloseAngle;[>];
+ MarkupTagBlock - [36..43)::7 - []
+ MarkupTransition - [36..43)::7 - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ ForwardSlash;[/];
+ Text;[text];
+ CloseAngle;[>];
diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/StopsAtMatchingCloseTagToStartTag.stree.txt b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/StopsAtMatchingCloseTagToStartTag.stree.txt
index b89cf18f9..d70b799bf 100644
--- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/StopsAtMatchingCloseTagToStartTag.stree.txt
+++ b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/StopsAtMatchingCloseTagToStartTag.stree.txt
@@ -1,23 +1,23 @@
-Markup block - Gen - 14 - (0:0,0)
- Tag block - Gen - 3 - (0:0,0)
- Markup span - Gen - [] - SpanEditHandler;Accepts:None - (0:0,0) - Tokens:3
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.Text;[a];
- SyntaxKind.CloseAngle;[>];
- Tag block - Gen - 3 - (3:0,3)
- Markup span - Gen - [] - SpanEditHandler;Accepts:None - (3:0,3) - Tokens:3
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.Text;[b];
- SyntaxKind.CloseAngle;[>];
- Tag block - Gen - 4 - (6:0,6)
- Markup span - Gen - [] - SpanEditHandler;Accepts:None - (6:0,6) - Tokens:4
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.ForwardSlash;[/];
- SyntaxKind.Text;[b];
- SyntaxKind.CloseAngle;[>];
- Tag block - Gen - 4 - (10:0,10)
- Markup span - Gen - [] - SpanEditHandler;Accepts:None - (10:0,10) - Tokens:4
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.ForwardSlash;[/];
- SyntaxKind.Text;[a];
- SyntaxKind.CloseAngle;[>];
+MarkupBlock - [0..14)::14 - []
+ MarkupTagBlock - [0..3)::3 - []
+ MarkupTextLiteral - [0..3)::3 - [] - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ Text;[a];
+ CloseAngle;[>];
+ MarkupTagBlock - [3..6)::3 - []
+ MarkupTextLiteral - [3..6)::3 - [] - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ Text;[b];
+ CloseAngle;[>];
+ MarkupTagBlock - [6..10)::4 - []
+ MarkupTextLiteral - [6..10)::4 - [] - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ ForwardSlash;[/];
+ Text;[b];
+ CloseAngle;[>];
+ MarkupTagBlock - [10..14)::4 - []
+ MarkupTextLiteral - [10..14)::4 - [] - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ ForwardSlash;[/];
+ Text;[a];
+ CloseAngle;[>];
diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/StopsParsingMidEmptyTagIfEOFReached.stree.txt b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/StopsParsingMidEmptyTagIfEOFReached.stree.txt
index c2064cbe7..6eb28acbc 100644
--- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/StopsParsingMidEmptyTagIfEOFReached.stree.txt
+++ b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/StopsParsingMidEmptyTagIfEOFReached.stree.txt
@@ -1,6 +1,6 @@
-Markup block - Gen - 4 - (0:0,0)
- Tag block - Gen - 4 - (0:0,0)
- Markup span - Gen - [
- SpanEditHandler;Accepts:Any
+ OpenAngle;[<];
+ Text;[br];
+ ForwardSlash;[/];
diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/StopsParsingSingleLineBlockAtEOFIfNoEOLReached.stree.txt b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/StopsParsingSingleLineBlockAtEOFIfNoEOLReached.stree.txt
index 3f3c688a0..aeba50839 100644
--- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/StopsParsingSingleLineBlockAtEOFIfNoEOLReached.stree.txt
+++ b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/StopsParsingSingleLineBlockAtEOFIfNoEOLReached.stree.txt
@@ -1,9 +1,9 @@
-Markup block - Gen - 9 - (0:0,0)
- Transition span - Gen - [@] - SpanEditHandler;Accepts:None - (0:0,0) - Tokens:1
- SyntaxKind.Transition;[@];
- MetaCode span - Gen - [:] - SpanEditHandler;Accepts:Any - (1:0,1) - Tokens:1
- SyntaxKind.Colon;[:];
- Markup span - Gen - [foo bar] - SpanEditHandler;Accepts:Any - (2:0,2) - Tokens:3
- SyntaxKind.Text;[foo];
- SyntaxKind.Whitespace;[ ];
- SyntaxKind.Text;[bar];
+MarkupBlock - [0..9)::9 - [@:foo bar]
+ MarkupTransition - [0..1)::1 - Gen - SpanEditHandler;Accepts:None
+ Transition;[@];
+ RazorMetaCode - [1..2)::1 - Gen - SpanEditHandler;Accepts:Any
+ Colon;[:];
+ MarkupTextLiteral - [2..9)::7 - [foo bar] - Gen - SpanEditHandler;Accepts:Any
+ Text;[foo];
+ Whitespace;[ ];
+ Text;[bar];
diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/SupportsCommentAsBlock.stree.txt b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/SupportsCommentAsBlock.stree.txt
index 9e2f6045d..d503ae4cc 100644
--- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/SupportsCommentAsBlock.stree.txt
+++ b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/SupportsCommentAsBlock.stree.txt
@@ -1,13 +1,13 @@
-Markup block - Gen - 12 - (0:0,0)
- HtmlComment block - Gen - 12 - (0:0,0)
- Markup span - Gen - [] - SpanEditHandler;Accepts:None - (9:0,9) - Tokens:2
- SyntaxKind.DoubleHyphen;[--];
- SyntaxKind.CloseAngle;[>];
+MarkupBlock - [0..12)::12 - []
+ MarkupCommentBlock - [0..12)::12
+ MarkupTextLiteral - [0..4)::4 - [] - Gen - SpanEditHandler;Accepts:None
+ DoubleHyphen;[--];
+ CloseAngle;[>];
diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/SupportsCommentWithExtraDashAsBlock.stree.txt b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/SupportsCommentWithExtraDashAsBlock.stree.txt
index 1e7fcc43a..05979ffa7 100644
--- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/SupportsCommentWithExtraDashAsBlock.stree.txt
+++ b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/SupportsCommentWithExtraDashAsBlock.stree.txt
@@ -1,14 +1,14 @@
-Markup block - Gen - 13 - (0:0,0)
- HtmlComment block - Gen - 13 - (0:0,0)
- Markup span - Gen - [] - SpanEditHandler;Accepts:None - (10:0,10) - Tokens:2
- SyntaxKind.DoubleHyphen;[--];
- SyntaxKind.CloseAngle;[>];
+MarkupBlock - [0..13)::13 - []
+ MarkupCommentBlock - [0..13)::13
+ MarkupTextLiteral - [0..4)::4 - [] - Gen - SpanEditHandler;Accepts:None
+ DoubleHyphen;[--];
+ CloseAngle;[>];
diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/SupportsCommentWithinBlock.stree.txt b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/SupportsCommentWithinBlock.stree.txt
index 464f70dd0..0fb89ab28 100644
--- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/SupportsCommentWithinBlock.stree.txt
+++ b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/SupportsCommentWithinBlock.stree.txt
@@ -1,28 +1,28 @@
-Markup block - Gen - 30 - (0:0,0)
- Tag block - Gen - 5 - (0:0,0)
- Markup span - Gen - [] - SpanEditHandler;Accepts:None - (0:0,0) - Tokens:3
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.Text;[foo];
- SyntaxKind.CloseAngle;[>];
- Markup span - Gen - [bar] - SpanEditHandler;Accepts:Any - (5:0,5) - Tokens:1
- SyntaxKind.Text;[bar];
- HtmlComment block - Gen - 13 - (8:0,8)
- Markup span - Gen - [] - SpanEditHandler;Accepts:None - (18:0,18) - Tokens:2
- SyntaxKind.DoubleHyphen;[--];
- SyntaxKind.CloseAngle;[>];
- Markup span - Gen - [baz] - SpanEditHandler;Accepts:None - (21:0,21) - Tokens:1
- SyntaxKind.Text;[baz];
- Tag block - Gen - 6 - (24:0,24)
- Markup span - Gen - [] - SpanEditHandler;Accepts:None - (24:0,24) - Tokens:4
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.ForwardSlash;[/];
- SyntaxKind.Text;[foo];
- SyntaxKind.CloseAngle;[>];
+MarkupBlock - [0..30)::30 - [barbaz]
+ MarkupTagBlock - [0..5)::5 - []
+ MarkupTextLiteral - [0..5)::5 - [] - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ Text;[foo];
+ CloseAngle;[>];
+ MarkupTextLiteral - [5..8)::3 - [bar] - Gen - SpanEditHandler;Accepts:Any
+ Text;[bar];
+ MarkupCommentBlock - [8..21)::13
+ MarkupTextLiteral - [8..12)::4 - [] - Gen - SpanEditHandler;Accepts:None
+ DoubleHyphen;[--];
+ CloseAngle;[>];
+ MarkupTextLiteral - [21..24)::3 - [baz] - Gen - SpanEditHandler;Accepts:None
+ Text;[baz];
+ MarkupTagBlock - [24..30)::6 - []
+ MarkupTextLiteral - [24..30)::6 - [] - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ ForwardSlash;[/];
+ Text;[foo];
+ CloseAngle;[>];
diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/SupportsScriptTagsWithLessThanSignsInThem.stree.txt b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/SupportsScriptTagsWithLessThanSignsInThem.stree.txt
index bc3722e35..5e513098e 100644
--- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/SupportsScriptTagsWithLessThanSignsInThem.stree.txt
+++ b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/SupportsScriptTagsWithLessThanSignsInThem.stree.txt
@@ -1,24 +1,24 @@
-Markup block - Gen - 45 - (0:0,0)
- Tag block - Gen - 8 - (0:0,0)
- Markup span - Gen - [] - SpanEditHandler;Accepts:None - (36:0,36) - Tokens:4
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.ForwardSlash;[/];
- SyntaxKind.Text;[script];
- SyntaxKind.CloseAngle;[>];
+MarkupBlock - [0..45)::45 - []
+ MarkupTagBlock - [0..8)::8 - []
+ MarkupTextLiteral - [36..45)::9 - [] - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ ForwardSlash;[/];
+ Text;[script];
+ CloseAngle;[>];
diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/SupportsScriptTagsWithSpacedLessThanSignsInThem.stree.txt b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/SupportsScriptTagsWithSpacedLessThanSignsInThem.stree.txt
index 0435cc4ef..dfec73907 100644
--- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/SupportsScriptTagsWithSpacedLessThanSignsInThem.stree.txt
+++ b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/SupportsScriptTagsWithSpacedLessThanSignsInThem.stree.txt
@@ -1,26 +1,26 @@
-Markup block - Gen - 47 - (0:0,0)
- Tag block - Gen - 8 - (0:0,0)
- Markup span - Gen - [] - SpanEditHandler;Accepts:None - (38:0,38) - Tokens:4
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.ForwardSlash;[/];
- SyntaxKind.Text;[script];
- SyntaxKind.CloseAngle;[>];
+MarkupBlock - [0..47)::47 - []
+ MarkupTagBlock - [0..8)::8 - []
+ MarkupTextLiteral - [38..47)::9 - [] - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ ForwardSlash;[/];
+ Text;[script];
+ CloseAngle;[>];
diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/SupportsTagsWithAttributes.stree.txt b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/SupportsTagsWithAttributes.stree.txt
index e7bcdaaaa..3331b9547 100644
--- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/SupportsTagsWithAttributes.stree.txt
+++ b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/SupportsTagsWithAttributes.stree.txt
@@ -1,48 +1,55 @@
-Markup block - Gen - 48 - (0:0,0)
- Tag block - Gen - 15 - (0:0,0)
- Markup span - Gen - [ - 10 - (4:0,4)
- Markup span - Gen - [ bar="] - SpanEditHandler;Accepts:Any - (4:0,4) - Tokens:4
- SyntaxKind.Whitespace;[ ];
- SyntaxKind.Text;[bar];
- SyntaxKind.Equals;[=];
- SyntaxKind.DoubleQuote;["];
- Markup span - Gen - [baz] - SpanEditHandler;Accepts:Any - (10:0,10) - Tokens:1
- SyntaxKind.Text;[baz];
- Markup span - Gen - ["] - SpanEditHandler;Accepts:Any - (13:0,13) - Tokens:1
- SyntaxKind.DoubleQuote;["];
- Markup span - Gen - [>] - SpanEditHandler;Accepts:None - (14:0,14) - Tokens:1
- SyntaxKind.CloseAngle;[>];
- Tag block - Gen - 5 - (15:0,15)
- Markup span - Gen - [] - SpanEditHandler;Accepts:None - (15:0,15) - Tokens:3
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.Text;[biz];
- SyntaxKind.CloseAngle;[>];
- Tag block - Gen - 16 - (20:0,20)
- Markup span - Gen - [ - 10 - (24:0,24)
- Markup span - Gen - [ zoop=] - SpanEditHandler;Accepts:Any - (24:0,24) - Tokens:3
- SyntaxKind.Whitespace;[ ];
- SyntaxKind.Text;[zoop];
- SyntaxKind.Equals;[=];
- Markup span - Gen - [zork] - SpanEditHandler;Accepts:Any - (30:0,30) - Tokens:1
- SyntaxKind.Text;[zork];
- Markup span - Gen - [/>] - SpanEditHandler;Accepts:None - (34:0,34) - Tokens:2
- SyntaxKind.ForwardSlash;[/];
- SyntaxKind.CloseAngle;[>];
- Tag block - Gen - 6 - (36:0,36)
- Markup span - Gen - [] - SpanEditHandler;Accepts:None - (36:0,36) - Tokens:4
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.ForwardSlash;[/];
- SyntaxKind.Text;[biz];
- SyntaxKind.CloseAngle;[>];
- Tag block - Gen - 6 - (42:0,42)
- Markup span - Gen - [] - SpanEditHandler;Accepts:None - (42:0,42) - Tokens:4
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.ForwardSlash;[/];
- SyntaxKind.Text;[foo];
- SyntaxKind.CloseAngle;[>];
+MarkupBlock - [0..48)::48 - []
+ MarkupTagBlock - [0..15)::15 - []
+ MarkupTextLiteral - [0..4)::4 - [ - SpanEditHandler;Accepts:Any
+ OpenAngle;[<];
+ Text;[foo];
+ MarkupAttributeBlock - [4..14)::10 - [ bar="baz"]
+ MarkupTextLiteral - [4..5)::1 - [ ] - Gen - SpanEditHandler;Accepts:Any
+ Whitespace;[ ];
+ MarkupTextLiteral - [5..8)::3 - [bar] - Gen - SpanEditHandler;Accepts:Any
+ Text;[bar];
+ Equals;[=];
+ MarkupTextLiteral - [9..10)::1 - ["] - Gen - SpanEditHandler;Accepts:Any
+ DoubleQuote;["];
+ GenericBlock - [10..13)::3
+ MarkupLiteralAttributeValue - [10..13)::3 - [baz]
+ MarkupTextLiteral - [10..13)::3 - [baz] - Gen - SpanEditHandler;Accepts:Any
+ Text;[baz];
+ MarkupTextLiteral - [13..14)::1 - ["] - Gen - SpanEditHandler;Accepts:Any
+ DoubleQuote;["];
+ MarkupTextLiteral - [14..15)::1 - [>] - Gen - SpanEditHandler;Accepts:None
+ CloseAngle;[>];
+ MarkupTagBlock - [15..20)::5 - []
+ MarkupTextLiteral - [15..20)::5 - [] - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ Text;[biz];
+ CloseAngle;[>];
+ MarkupTagBlock - [20..36)::16 - []
+ MarkupTextLiteral - [20..24)::4 - [ - SpanEditHandler;Accepts:Any
+ OpenAngle;[<];
+ Text;[boz];
+ MarkupAttributeBlock - [24..34)::10 - [ zoop=zork]
+ MarkupTextLiteral - [24..25)::1 - [ ] - Gen - SpanEditHandler;Accepts:Any
+ Whitespace;[ ];
+ MarkupTextLiteral - [25..29)::4 - [zoop] - Gen - SpanEditHandler;Accepts:Any
+ Text;[zoop];
+ Equals;[=];
+ GenericBlock - [30..34)::4
+ MarkupLiteralAttributeValue - [30..34)::4 - [zork]
+ MarkupTextLiteral - [30..34)::4 - [zork] - Gen - SpanEditHandler;Accepts:Any
+ Text;[zork];
+ MarkupTextLiteral - [34..36)::2 - [/>] - Gen - SpanEditHandler;Accepts:None
+ ForwardSlash;[/];
+ CloseAngle;[>];
+ MarkupTagBlock - [36..42)::6 - []
+ MarkupTextLiteral - [36..42)::6 - [] - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ ForwardSlash;[/];
+ Text;[biz];
+ CloseAngle;[>];
+ MarkupTagBlock - [42..48)::6 - []
+ MarkupTextLiteral - [42..48)::6 - [] - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ ForwardSlash;[/];
+ Text;[foo];
+ CloseAngle;[>];
diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/TagWithoutCloseAngleDoesNotTerminateBlock.stree.txt b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/TagWithoutCloseAngleDoesNotTerminateBlock.stree.txt
index f8dc74898..cc9623a3d 100644
--- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/TagWithoutCloseAngleDoesNotTerminateBlock.stree.txt
+++ b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/TagWithoutCloseAngleDoesNotTerminateBlock.stree.txt
@@ -1,7 +1,7 @@
-Markup block - Gen - 28 - (0:0,0)
- Tag block - Gen - 28 - (0:0,0)
- Markup span - Gen - [< LF ] - SpanEditHandler;Accepts:Any - (0:0,0) - Tokens:4
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.Whitespace;[ ];
- SyntaxKind.NewLine;[LF];
- SyntaxKind.Whitespace;[ ];
+MarkupBlock - [0..28)::28 - [< LF ]
+ MarkupTagBlock - [0..28)::28 - [< LF ]
+ MarkupTextLiteral - [0..28)::28 - [< LF ] - Gen - SpanEditHandler;Accepts:Any
+ OpenAngle;[<];
+ Whitespace;[ ];
+ NewLine;[LF];
+ Whitespace;[ ];
diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/TerminatesAtEOF.stree.txt b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/TerminatesAtEOF.stree.txt
index a4bd0fbd1..6e363fb26 100644
--- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/TerminatesAtEOF.stree.txt
+++ b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/TerminatesAtEOF.stree.txt
@@ -1,6 +1,6 @@
-Markup block - Gen - 5 - (0:0,0)
- Tag block - Gen - 5 - (0:0,0)
- Markup span - Gen - [] - SpanEditHandler;Accepts:None - (0:0,0) - Tokens:3
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.Text;[foo];
- SyntaxKind.CloseAngle;[>];
+MarkupBlock - [0..5)::5 - []
+ MarkupTagBlock - [0..5)::5 - []
+ MarkupTextLiteral - [0..5)::5 - [] - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ Text;[foo];
+ CloseAngle;[>];
diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/TerminatesAtEOFWhenParsingComment.stree.txt b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/TerminatesAtEOFWhenParsingComment.stree.txt
index ba8a70b4c..cf1cb3ed7 100644
--- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/TerminatesAtEOFWhenParsingComment.stree.txt
+++ b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/TerminatesAtEOFWhenParsingComment.stree.txt
@@ -1,8 +1,8 @@
-Markup block - Gen - 9 - (0:0,0)
- Markup span - Gen - [] - SpanEditHandler;Accepts:None - (19:0,19) - Tokens:2
- SyntaxKind.DoubleHyphen;[--];
- SyntaxKind.CloseAngle;[>];
- Markup span - Gen - [-->] - SpanEditHandler;Accepts:None - (22:0,22) - Tokens:2
- SyntaxKind.DoubleHyphen;[--];
- SyntaxKind.CloseAngle;[>];
- Tag block - Gen - 6 - (25:0,25)
- Markup span - Gen - [] - SpanEditHandler;Accepts:None - (25:0,25) - Tokens:4
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.ForwardSlash;[/];
- SyntaxKind.Text;[foo];
- SyntaxKind.CloseAngle;[>];
+MarkupBlock - [0..31)::31 - [-->]
+ MarkupTagBlock - [0..5)::5 - []
+ MarkupTextLiteral - [0..5)::5 - [] - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ Text;[foo];
+ CloseAngle;[>];
+ MarkupCommentBlock - [5..22)::17
+ MarkupTextLiteral - [5..9)::4 - [] - Gen - SpanEditHandler;Accepts:None
+ DoubleHyphen;[--];
+ CloseAngle;[>];
+ MarkupTextLiteral - [22..25)::3 - [-->] - Gen - SpanEditHandler;Accepts:None
+ DoubleHyphen;[--];
+ CloseAngle;[>];
+ MarkupTagBlock - [25..31)::6 - []
+ MarkupTextLiteral - [25..31)::6 - [] - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ ForwardSlash;[/];
+ Text;[foo];
+ CloseAngle;[>];
diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/TerminatesSGMLDeclarationAtFirstCloseAngle.stree.txt b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/TerminatesSGMLDeclarationAtFirstCloseAngle.stree.txt
index c779f841b..372a19319 100644
--- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/TerminatesSGMLDeclarationAtFirstCloseAngle.stree.txt
+++ b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/TerminatesSGMLDeclarationAtFirstCloseAngle.stree.txt
@@ -1,25 +1,25 @@
-Markup block - Gen - 34 - (0:0,0)
- Tag block - Gen - 5 - (0:0,0)
- Markup span - Gen - [] - SpanEditHandler;Accepts:None - (0:0,0) - Tokens:3
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.Text;[foo];
- SyntaxKind.CloseAngle;[>];
- Markup span - Gen - [] - SpanEditHandler;Accepts:None - (5:0,5) - Tokens:8
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.Bang;[!];
- SyntaxKind.Text;[DOCTYPE];
- SyntaxKind.Whitespace;[ ];
- SyntaxKind.Text;[foo];
- SyntaxKind.Whitespace;[ ];
- SyntaxKind.Text;[bar];
- SyntaxKind.CloseAngle;[>];
- Markup span - Gen - [ baz>] - SpanEditHandler;Accepts:Any - (23:0,23) - Tokens:3
- SyntaxKind.Whitespace;[ ];
- SyntaxKind.Text;[baz];
- SyntaxKind.CloseAngle;[>];
- Tag block - Gen - 6 - (28:0,28)
- Markup span - Gen - [] - SpanEditHandler;Accepts:None - (28:0,28) - Tokens:4
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.ForwardSlash;[/];
- SyntaxKind.Text;[foo];
- SyntaxKind.CloseAngle;[>];
+MarkupBlock - [0..34)::34 - [ baz>]
+ MarkupTagBlock - [0..5)::5 - []
+ MarkupTextLiteral - [0..5)::5 - [] - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ Text;[foo];
+ CloseAngle;[>];
+ MarkupTextLiteral - [5..23)::18 - [] - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ Bang;[!];
+ Text;[DOCTYPE];
+ Whitespace;[ ];
+ Text;[foo];
+ Whitespace;[ ];
+ Text;[bar];
+ CloseAngle;[>];
+ MarkupTextLiteral - [23..28)::5 - [ baz>] - Gen - SpanEditHandler;Accepts:Any
+ Whitespace;[ ];
+ Text;[baz];
+ CloseAngle;[>];
+ MarkupTagBlock - [28..34)::6 - []
+ MarkupTextLiteral - [28..34)::6 - [] - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ ForwardSlash;[/];
+ Text;[foo];
+ CloseAngle;[>];
diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/TerminatesXMLProcessingInstructionAtQuestionMarkCloseAnglePair.stree.txt b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/TerminatesXMLProcessingInstructionAtQuestionMarkCloseAnglePair.stree.txt
index 50bc77683..caf96df47 100644
--- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/TerminatesXMLProcessingInstructionAtQuestionMarkCloseAnglePair.stree.txt
+++ b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/TerminatesXMLProcessingInstructionAtQuestionMarkCloseAnglePair.stree.txt
@@ -1,27 +1,27 @@
-Markup block - Gen - 34 - (0:0,0)
- Tag block - Gen - 5 - (0:0,0)
- Markup span - Gen - [] - SpanEditHandler;Accepts:None - (0:0,0) - Tokens:3
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.Text;[foo];
- SyntaxKind.CloseAngle;[>];
- Markup span - Gen - [] - SpanEditHandler;Accepts:None - (5:0,5) - Tokens:11
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.QuestionMark;[?];
- SyntaxKind.Text;[xml];
- SyntaxKind.Whitespace;[ ];
- SyntaxKind.Text;[foo];
- SyntaxKind.Whitespace;[ ];
- SyntaxKind.Text;[bar];
- SyntaxKind.Whitespace;[ ];
- SyntaxKind.Text;[baz];
- SyntaxKind.QuestionMark;[?];
- SyntaxKind.CloseAngle;[>];
- Markup span - Gen - [ baz] - SpanEditHandler;Accepts:Any - (24:0,24) - Tokens:2
- SyntaxKind.Whitespace;[ ];
- SyntaxKind.Text;[baz];
- Tag block - Gen - 6 - (28:0,28)
- Markup span - Gen - [] - SpanEditHandler;Accepts:None - (28:0,28) - Tokens:4
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.ForwardSlash;[/];
- SyntaxKind.Text;[foo];
- SyntaxKind.CloseAngle;[>];
+MarkupBlock - [0..34)::34 - [ baz]
+ MarkupTagBlock - [0..5)::5 - []
+ MarkupTextLiteral - [0..5)::5 - [] - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ Text;[foo];
+ CloseAngle;[>];
+ MarkupTextLiteral - [5..24)::19 - [] - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ QuestionMark;[?];
+ Text;[xml];
+ Whitespace;[ ];
+ Text;[foo];
+ Whitespace;[ ];
+ Text;[bar];
+ Whitespace;[ ];
+ Text;[baz];
+ QuestionMark;[?];
+ CloseAngle;[>];
+ MarkupTextLiteral - [24..28)::4 - [ baz] - Gen - SpanEditHandler;Accepts:Any
+ Whitespace;[ ];
+ Text;[baz];
+ MarkupTagBlock - [28..34)::6 - []
+ MarkupTextLiteral - [28..34)::6 - [] - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ ForwardSlash;[/];
+ Text;[foo];
+ CloseAngle;[>];
diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/TreatsMalformedTagsAsContent.stree.txt b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/TreatsMalformedTagsAsContent.stree.txt
index 786946036..3ec892f4b 100644
--- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/TreatsMalformedTagsAsContent.stree.txt
+++ b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/TreatsMalformedTagsAsContent.stree.txt
@@ -1,17 +1,17 @@
-Markup block - Gen - 18 - (0:0,0)
- Tag block - Gen - 5 - (0:0,0)
- Markup span - Gen - [] - SpanEditHandler;Accepts:None - (0:0,0) - Tokens:3
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.Text;[foo];
- SyntaxKind.CloseAngle;[>];
- Tag block - Gen - 13 - (5:0,5)
- Markup span - Gen - [!-- bar -->] - SpanEditHandler;Accepts:None - (5:0,5) - Tokens:9
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.ForwardSlash;[/];
- SyntaxKind.Bang;[!];
- SyntaxKind.DoubleHyphen;[--];
- SyntaxKind.Whitespace;[ ];
- SyntaxKind.Text;[bar];
- SyntaxKind.Whitespace;[ ];
- SyntaxKind.DoubleHyphen;[--];
- SyntaxKind.CloseAngle;[>];
+MarkupBlock - [0..18)::18 - [!-- bar -->]
+ MarkupTagBlock - [0..5)::5 - []
+ MarkupTextLiteral - [0..5)::5 - [] - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ Text;[foo];
+ CloseAngle;[>];
+ MarkupTagBlock - [5..18)::13 - [!-- bar -->]
+ MarkupTextLiteral - [5..18)::13 - [!-- bar -->] - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ ForwardSlash;[/];
+ Bang;[!];
+ DoubleHyphen;[--];
+ Whitespace;[ ];
+ Text;[bar];
+ Whitespace;[ ];
+ DoubleHyphen;[--];
+ CloseAngle;[>];
diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/WithSelfClosingTagJustEmitsTag.stree.txt b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/WithSelfClosingTagJustEmitsTag.stree.txt
index a29dbc5d2..1e2015375 100644
--- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/WithSelfClosingTagJustEmitsTag.stree.txt
+++ b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlBlockTest/WithSelfClosingTagJustEmitsTag.stree.txt
@@ -1,8 +1,8 @@
-Markup block - Gen - 7 - (0:0,0)
- Tag block - Gen - 7 - (0:0,0)
- Markup span - Gen - [] - SpanEditHandler;Accepts:None - (0:0,0) - Tokens:5
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.Text;[foo];
- SyntaxKind.Whitespace;[ ];
- SyntaxKind.ForwardSlash;[/];
- SyntaxKind.CloseAngle;[>];
+MarkupBlock - [0..7)::7 - []
+ MarkupTagBlock - [0..7)::7 - []
+ MarkupTextLiteral - [0..7)::7 - [] - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ Text;[foo];
+ Whitespace;[ ];
+ ForwardSlash;[/];
+ CloseAngle;[>];
diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlDocumentTest/AcceptsEndTagWithNoMatchingStartTag.stree.txt b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlDocumentTest/AcceptsEndTagWithNoMatchingStartTag.stree.txt
index 0279f883d..eb62248d2 100644
--- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlDocumentTest/AcceptsEndTagWithNoMatchingStartTag.stree.txt
+++ b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlDocumentTest/AcceptsEndTagWithNoMatchingStartTag.stree.txt
@@ -1,13 +1,14 @@
-Markup block - Gen - 14 - (0:0,0)
- Markup span - Gen - [Foo ] - SpanEditHandler;Accepts:Any - (0:0,0) - Tokens:2
- SyntaxKind.Text;[Foo];
- SyntaxKind.Whitespace;[ ];
- Tag block - Gen - 6 - (4:0,4)
- Markup span - Gen - [] - SpanEditHandler;Accepts:Any - (4:0,4) - Tokens:4
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.ForwardSlash;[/];
- SyntaxKind.Text;[div];
- SyntaxKind.CloseAngle;[>];
- Markup span - Gen - [ Bar] - SpanEditHandler;Accepts:Any - (10:0,10) - Tokens:2
- SyntaxKind.Whitespace;[ ];
- SyntaxKind.Text;[Bar];
+RazorDocument - [0..14)::14 - [Foo Bar]
+ MarkupBlock - [0..14)::14
+ MarkupTextLiteral - [0..4)::4 - [Foo ] - Gen - SpanEditHandler;Accepts:Any
+ Text;[Foo];
+ Whitespace;[ ];
+ MarkupTagBlock - [4..10)::6 - []
+ MarkupTextLiteral - [4..10)::6 - [] - Gen - SpanEditHandler;Accepts:Any
+ OpenAngle;[<];
+ ForwardSlash;[/];
+ Text;[div];
+ CloseAngle;[>];
+ MarkupTextLiteral - [10..14)::4 - [ Bar] - Gen - SpanEditHandler;Accepts:Any
+ Whitespace;[ ];
+ Text;[Bar];
diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlDocumentTest/AcceptsSwapTokenAtEndOfFileAndOutputsZeroLengthCodeSpan.stree.txt b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlDocumentTest/AcceptsSwapTokenAtEndOfFileAndOutputsZeroLengthCodeSpan.stree.txt
index 111e681d8..1b3a186cb 100644
--- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlDocumentTest/AcceptsSwapTokenAtEndOfFileAndOutputsZeroLengthCodeSpan.stree.txt
+++ b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlDocumentTest/AcceptsSwapTokenAtEndOfFileAndOutputsZeroLengthCodeSpan.stree.txt
@@ -1,10 +1,14 @@
-Markup block - Gen - 1 - (0:0,0)
- Markup span - Gen - [] - SpanEditHandler;Accepts:Any - (0:0,0) - Tokens:1
- SyntaxKind.Unknown;[];
- Expression block - Gen - 1 - (0:0,0)
- Transition span - Gen - [@] - SpanEditHandler;Accepts:None - (0:0,0) - Tokens:1
- SyntaxKind.Transition;[@];
- Code span - Gen - [] - ImplicitExpressionEditHandler;Accepts:NonWhitespace;ImplicitExpression[RTD];K14 - (1:0,1) - Tokens:1
- SyntaxKind.Unknown;[];
- Markup span - Gen - [] - SpanEditHandler;Accepts:Any - (1:0,1) - Tokens:1
- SyntaxKind.Unknown;[];
+RazorDocument - [0..1)::1 - [@]
+ MarkupBlock - [0..1)::1
+ MarkupTextLiteral - [0..0)::0 - [] - Gen - SpanEditHandler;Accepts:Any
+ Marker;[];
+ CSharpCodeBlock - [0..1)::1
+ CSharpImplicitExpression - [0..1)::1
+ CSharpTransition - [0..1)::1 - Gen - SpanEditHandler;Accepts:None
+ Transition;[@];
+ CSharpImplicitExpressionBody - [1..1)::0
+ CSharpCodeBlock - [1..1)::0
+ CSharpExpressionLiteral - [1..1)::0 - [] - Gen - ImplicitExpressionEditHandler;Accepts:NonWhitespace;ImplicitExpression[RTD];K14
+ Marker;[];
+ MarkupTextLiteral - [1..1)::0 - [] - Gen - SpanEditHandler;Accepts:Any
+ Marker;[];
diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlDocumentTest/CorrectlyHandlesOddlySpacedHTMLElements.stree.txt b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlDocumentTest/CorrectlyHandlesOddlySpacedHTMLElements.stree.txt
index 9e676d2db..62726a289 100644
--- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlDocumentTest/CorrectlyHandlesOddlySpacedHTMLElements.stree.txt
+++ b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlDocumentTest/CorrectlyHandlesOddlySpacedHTMLElements.stree.txt
@@ -1,43 +1,48 @@
-Markup block - Gen - 39 - (0:0,0)
- Tag block - Gen - 6 - (0:0,0)
- Markup span - Gen - [] - SpanEditHandler;Accepts:Any - (0:0,0) - Tokens:4
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.Text;[div];
- SyntaxKind.Whitespace;[ ];
- SyntaxKind.CloseAngle;[>];
- Tag block - Gen
- 17 - (6:0,6)
- Markup span - Gen - [ - 14 - (8:0,8)
- Markup span - Gen - [ class = '] - SpanEditHandler;Accepts:Any - (8:0,8) - Tokens:6
- SyntaxKind.Whitespace;[ ];
- SyntaxKind.Text;[class];
- SyntaxKind.Whitespace;[ ];
- SyntaxKind.Equals;[=];
- SyntaxKind.Whitespace;[ ];
- SyntaxKind.SingleQuote;['];
- Markup span - Gen - [bar] - SpanEditHandler;Accepts:Any - (18:0,18) - Tokens:1
- SyntaxKind.Text;[bar];
- Markup span - Gen - ['] - SpanEditHandler;Accepts:Any - (21:0,21) - Tokens:1
- SyntaxKind.SingleQuote;['];
- Markup span - Gen - [>] - SpanEditHandler;Accepts:Any - (22:0,22) - Tokens:1
- SyntaxKind.CloseAngle;[>];
- SyntaxKind.HtmlTextLiteral - [ Foo ] - [23..28) - FullWidth: 5 - Slots: 1
- SyntaxKind.List - [ Foo ] - [23..28) - FullWidth: 5 - Slots: 3
- SyntaxKind.Whitespace;[ ];
- SyntaxKind.Text;[Foo];
- SyntaxKind.Whitespace;[ ];
- Tag block - Gen - 4 - (28:0,28)
- Markup span - Gen - [
] - SpanEditHandler;Accepts:Any - (28:0,28) - Tokens:4
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.ForwardSlash;[/];
- SyntaxKind.Text;[p];
- SyntaxKind.CloseAngle;[>];
- Tag block - Gen - 7 - (32:0,32)
- Markup span - Gen - [ ] - SpanEditHandler;Accepts:Any - (32:0,32) - Tokens:5
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.ForwardSlash;[/];
- SyntaxKind.Text;[div];
- SyntaxKind.Whitespace;[ ];
- SyntaxKind.CloseAngle;[>];
+RazorDocument - [0..39)::39 - []
+ MarkupBlock - [0..39)::39
+ MarkupTagBlock - [0..6)::6 - []
+ MarkupTextLiteral - [0..6)::6 - [
] - Gen
- SpanEditHandler;Accepts:Any
+ OpenAngle;[<];
+ Text;[div];
+ Whitespace;[ ];
+ CloseAngle;[>];
+ MarkupTagBlock - [6..23)::17 - []
+ MarkupTextLiteral - [6..8)::2 - [
- SpanEditHandler;Accepts:Any
+ OpenAngle;[<];
+ Text;[p];
+ MarkupAttributeBlock - [8..22)::14 - [ class = 'bar']
+ MarkupTextLiteral - [8..9)::1 - [ ] - Gen - SpanEditHandler;Accepts:Any
+ Whitespace;[ ];
+ MarkupTextLiteral - [9..14)::5 - [class] - Gen - SpanEditHandler;Accepts:Any
+ Text;[class];
+ MarkupTextLiteral - [14..15)::1 - [ ] - Gen - SpanEditHandler;Accepts:Any
+ Whitespace;[ ];
+ Equals;[=];
+ MarkupTextLiteral - [16..18)::2 - [ '] - Gen - SpanEditHandler;Accepts:Any
+ Whitespace;[ ];
+ SingleQuote;['];
+ GenericBlock - [18..21)::3
+ MarkupLiteralAttributeValue - [18..21)::3 - [bar]
+ MarkupTextLiteral - [18..21)::3 - [bar] - Gen - SpanEditHandler;Accepts:Any
+ Text;[bar];
+ MarkupTextLiteral - [21..22)::1 - ['] - Gen - SpanEditHandler;Accepts:Any
+ SingleQuote;['];
+ MarkupTextLiteral - [22..23)::1 - [>] - Gen - SpanEditHandler;Accepts:Any
+ CloseAngle;[>];
+ MarkupTextLiteral - [23..28)::5 - [ Foo ] - Gen - SpanEditHandler;Accepts:Any
+ Whitespace;[ ];
+ Text;[Foo];
+ Whitespace;[ ];
+ MarkupTagBlock - [28..32)::4 - [
]
+ MarkupTextLiteral - [28..32)::4 - [] - Gen - SpanEditHandler;Accepts:Any
+ OpenAngle;[<];
+ ForwardSlash;[/];
+ Text;[p];
+ CloseAngle;[>];
+ MarkupTagBlock - [32..39)::7 - []
+ MarkupTextLiteral - [32..39)::7 - [
] - Gen - SpanEditHandler;Accepts:Any
+ OpenAngle;[<];
+ ForwardSlash;[/];
+ Text;[div];
+ Whitespace;[ ];
+ CloseAngle;[>];
diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlDocumentTest/CorrectlyHandlesSingleLineOfMarkupWithEmbeddedStatement.stree.txt b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlDocumentTest/CorrectlyHandlesSingleLineOfMarkupWithEmbeddedStatement.stree.txt
index b57b9fee5..0822f48a4 100644
--- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlDocumentTest/CorrectlyHandlesSingleLineOfMarkupWithEmbeddedStatement.stree.txt
+++ b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlDocumentTest/CorrectlyHandlesSingleLineOfMarkupWithEmbeddedStatement.stree.txt
@@ -1,30 +1,30 @@
-Markup block - Gen - 31 - (0:0,0)
- Tag block - Gen - 5 - (0:0,0)
- Markup span - Gen - [] - SpanEditHandler;Accepts:Any - (0:0,0) - Tokens:3
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.Text;[div];
- SyntaxKind.CloseAngle;[>];
- Markup span - Gen - [Foo ] - SpanEditHandler;Accepts:Any - (5:0,5) - Tokens:2
- SyntaxKind.Text;[Foo];
- SyntaxKind.Whitespace;[ ];
- Statement block - Gen - 12 - (9:0,9)
- Transition span - Gen - [@] - SpanEditHandler;Accepts:None - (9:0,9) - Tokens:1
- SyntaxKind.Transition;[@];
- Code span - Gen - [if(true) {}] - SpanEditHandler;Accepts:Any - (10:0,10) - Tokens:7
- SyntaxKind.Keyword;[if];
- SyntaxKind.LeftParenthesis;[(];
- SyntaxKind.Keyword;[true];
- SyntaxKind.RightParenthesis;[)];
- SyntaxKind.Whitespace;[ ];
- SyntaxKind.LeftBrace;[{];
- SyntaxKind.RightBrace;[}];
- SyntaxKind.HtmlTextLiteral - [ Bar] - [21..25) - FullWidth: 4 - Slots: 1
- SyntaxKind.List - [ Bar] - [21..25) - FullWidth: 4 - Slots: 2
- SyntaxKind.Whitespace;[ ];
- SyntaxKind.Text;[Bar];
- Tag block - Gen - 6 - (25:0,25)
- Markup span - Gen - [
] - SpanEditHandler;Accepts:Any - (25:0,25) - Tokens:4
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.ForwardSlash;[/];
- SyntaxKind.Text;[div];
- SyntaxKind.CloseAngle;[>];
+RazorDocument - [0..31)::31 - [Foo @if(true) {} Bar
]
+ MarkupBlock - [0..31)::31
+ MarkupTagBlock - [0..5)::5 - []
+ MarkupTextLiteral - [0..5)::5 - [
] - Gen - SpanEditHandler;Accepts:Any
+ OpenAngle;[<];
+ Text;[div];
+ CloseAngle;[>];
+ MarkupTextLiteral - [5..9)::4 - [Foo ] - Gen - SpanEditHandler;Accepts:Any
+ Text;[Foo];
+ Whitespace;[ ];
+ CSharpCodeBlock - [9..21)::12
+ CSharpTransition - [9..10)::1 - Gen - SpanEditHandler;Accepts:None
+ Transition;[@];
+ CSharpStatementLiteral - [10..21)::11 - [if(true) {}] - Gen - SpanEditHandler;Accepts:Any
+ Keyword;[if];
+ LeftParenthesis;[(];
+ Keyword;[true];
+ RightParenthesis;[)];
+ Whitespace;[ ];
+ LeftBrace;[{];
+ RightBrace;[}];
+ MarkupTextLiteral - [21..25)::4 - [ Bar] - Gen - SpanEditHandler;Accepts:Any
+ Whitespace;[ ];
+ Text;[Bar];
+ MarkupTagBlock - [25..31)::6 - [
]
+ MarkupTextLiteral - [25..31)::6 - [
] - Gen - SpanEditHandler;Accepts:Any
+ OpenAngle;[<];
+ ForwardSlash;[/];
+ Text;[div];
+ CloseAngle;[>];
diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlDocumentTest/DoesNotIgnoreNewLineAtTheEndOfMarkupBlock.stree.txt b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlDocumentTest/DoesNotIgnoreNewLineAtTheEndOfMarkupBlock.stree.txt
index 39886635d..2f8c946bd 100644
--- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlDocumentTest/DoesNotIgnoreNewLineAtTheEndOfMarkupBlock.stree.txt
+++ b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlDocumentTest/DoesNotIgnoreNewLineAtTheEndOfMarkupBlock.stree.txt
@@ -1,21 +1,25 @@
-Markup block - Gen - 15 - (0:0,0)
- Markup span - Gen - [] - SpanEditHandler;Accepts:Any - (0:0,0) - Tokens:1
- SyntaxKind.Unknown;[];
- Statement block - Gen - 5 - (0:0,0)
- Transition span - Gen - [@] - SpanEditHandler;Accepts:None - (0:0,0) - Tokens:1
- SyntaxKind.Transition;[@];
- MetaCode span - Gen - [{] - SpanEditHandler;Accepts:None - (1:0,1) - Tokens:1
- SyntaxKind.LeftBrace;[{];
- Code span - Gen - [LF] - AutoCompleteEditHandler;Accepts:Any,AutoComplete:[];AtEOL - (2:0,2) - Tokens:1
- SyntaxKind.NewLine;[LF];
- MetaCode span - Gen - [}] - SpanEditHandler;Accepts:None - (4:1,0) - Tokens:1
- SyntaxKind.RightBrace;[}];
- Markup span - Gen - [LF] - SpanEditHandler;Accepts:Any - (5:1,1) - Tokens:1
- SyntaxKind.NewLine;[LF];
- Tag block - Gen - 6 - (7:2,0)
- Markup span - Gen - [] - SpanEditHandler;Accepts:Any - (7:2,0) - Tokens:3
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.Text;[html];
- SyntaxKind.CloseAngle;[>];
- Markup span - Gen - [LF] - SpanEditHandler;Accepts:Any - (13:2,6) - Tokens:1
- SyntaxKind.NewLine;[LF];
+RazorDocument - [0..15)::15 - [@{LF}LFLF]
+ MarkupBlock - [0..15)::15
+ MarkupTextLiteral - [0..0)::0 - [] - Gen - SpanEditHandler;Accepts:Any
+ Marker;[];
+ CSharpCodeBlock - [0..5)::5
+ CSharpStatement - [0..5)::5
+ CSharpTransition - [0..1)::1 - Gen - SpanEditHandler;Accepts:None
+ Transition;[@];
+ CSharpStatementBody - [1..5)::4
+ RazorMetaCode - [1..2)::1 - Gen - SpanEditHandler;Accepts:None
+ LeftBrace;[{];
+ CSharpCodeBlock - [2..4)::2
+ CSharpStatementLiteral - [2..4)::2 - [LF] - Gen - AutoCompleteEditHandler;Accepts:Any,AutoComplete:[];AtEOL
+ NewLine;[LF];
+ RazorMetaCode - [4..5)::1 - Gen - SpanEditHandler;Accepts:None
+ RightBrace;[}];
+ MarkupEphemeralTextLiteral - [5..7)::2 - [LF] - Gen - SpanEditHandler;Accepts:Any
+ NewLine;[LF];
+ MarkupTagBlock - [7..13)::6 - []
+ MarkupTextLiteral - [7..13)::6 - [] - Gen - SpanEditHandler;Accepts:Any
+ OpenAngle;[<];
+ Text;[html];
+ CloseAngle;[>];
+ MarkupTextLiteral - [13..15)::2 - [LF] - Gen - SpanEditHandler;Accepts:Any
+ NewLine;[LF];
diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlDocumentTest/DoesNotIgnoreWhitespaceAtTheEndOfVerbatimBlockIfNoNewlinePresent.stree.txt b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlDocumentTest/DoesNotIgnoreWhitespaceAtTheEndOfVerbatimBlockIfNoNewlinePresent.stree.txt
index e6d7acb56..da3662cee 100644
--- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlDocumentTest/DoesNotIgnoreWhitespaceAtTheEndOfVerbatimBlockIfNoNewlinePresent.stree.txt
+++ b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlDocumentTest/DoesNotIgnoreWhitespaceAtTheEndOfVerbatimBlockIfNoNewlinePresent.stree.txt
@@ -1,21 +1,25 @@
-Markup block - Gen - 17 - (0:0,0)
- Markup span - Gen - [] - SpanEditHandler;Accepts:Any - (0:0,0) - Tokens:1
- SyntaxKind.Unknown;[];
- Statement block - Gen - 5 - (0:0,0)
- Transition span - Gen - [@] - SpanEditHandler;Accepts:None - (0:0,0) - Tokens:1
- SyntaxKind.Transition;[@];
- MetaCode span - Gen - [{] - SpanEditHandler;Accepts:None - (1:0,1) - Tokens:1
- SyntaxKind.LeftBrace;[{];
- Code span - Gen - [LF] - AutoCompleteEditHandler;Accepts:Any,AutoComplete:[];AtEOL - (2:0,2) - Tokens:1
- SyntaxKind.NewLine;[LF];
- MetaCode span - Gen - [}] - SpanEditHandler;Accepts:None - (4:1,0) - Tokens:1
- SyntaxKind.RightBrace;[}];
- Markup span - Gen - [ ] - SpanEditHandler;Accepts:Any - (5:1,1) - Tokens:1
- SyntaxKind.Whitespace;[ ];
- Tag block - Gen - 6 - (9:1,5)
- Markup span - Gen - [] - SpanEditHandler;Accepts:Any - (9:1,5) - Tokens:3
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.Text;[html];
- SyntaxKind.CloseAngle;[>];
- Markup span - Gen - [LF] - SpanEditHandler;Accepts:Any - (15:1,11) - Tokens:1
- SyntaxKind.NewLine;[LF];
+RazorDocument - [0..17)::17 - [@{LF} LF]
+ MarkupBlock - [0..17)::17
+ MarkupTextLiteral - [0..0)::0 - [] - Gen - SpanEditHandler;Accepts:Any
+ Marker;[];
+ CSharpCodeBlock - [0..5)::5
+ CSharpStatement - [0..5)::5
+ CSharpTransition - [0..1)::1 - Gen - SpanEditHandler;Accepts:None
+ Transition;[@];
+ CSharpStatementBody - [1..5)::4
+ RazorMetaCode - [1..2)::1 - Gen - SpanEditHandler;Accepts:None
+ LeftBrace;[{];
+ CSharpCodeBlock - [2..4)::2
+ CSharpStatementLiteral - [2..4)::2 - [LF] - Gen - AutoCompleteEditHandler;Accepts:Any,AutoComplete:[];AtEOL
+ NewLine;[LF];
+ RazorMetaCode - [4..5)::1 - Gen - SpanEditHandler;Accepts:None
+ RightBrace;[}];
+ MarkupTextLiteral - [5..9)::4 - [ ] - Gen - SpanEditHandler;Accepts:Any
+ Whitespace;[ ];
+ MarkupTagBlock - [9..15)::6 - []
+ MarkupTextLiteral - [9..15)::6 - [] - Gen - SpanEditHandler;Accepts:Any
+ OpenAngle;[<];
+ Text;[html];
+ CloseAngle;[>];
+ MarkupTextLiteral - [15..17)::2 - [LF] - Gen - SpanEditHandler;Accepts:Any
+ NewLine;[LF];
diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlDocumentTest/DoesNotRenderExtraNewLineAtTheEndOfVerbatimBlock.stree.txt b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlDocumentTest/DoesNotRenderExtraNewLineAtTheEndOfVerbatimBlock.stree.txt
index 9a418f929..fcbe43d6d 100644
--- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlDocumentTest/DoesNotRenderExtraNewLineAtTheEndOfVerbatimBlock.stree.txt
+++ b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlDocumentTest/DoesNotRenderExtraNewLineAtTheEndOfVerbatimBlock.stree.txt
@@ -1,19 +1,23 @@
-Markup block - Gen - 13 - (0:0,0)
- Markup span - Gen - [] - SpanEditHandler;Accepts:Any - (0:0,0) - Tokens:1
- SyntaxKind.Unknown;[];
- Statement block - Gen - 5 - (0:0,0)
- Transition span - Gen - [@] - SpanEditHandler;Accepts:None - (0:0,0) - Tokens:1
- SyntaxKind.Transition;[@];
- MetaCode span - Gen - [{] - SpanEditHandler;Accepts:None - (1:0,1) - Tokens:1
- SyntaxKind.LeftBrace;[{];
- Code span - Gen - [LF] - AutoCompleteEditHandler;Accepts:Any,AutoComplete:[];AtEOL - (2:0,2) - Tokens:1
- SyntaxKind.NewLine;[LF];
- MetaCode span - Gen - [}] - SpanEditHandler;Accepts:None - (4:1,0) - Tokens:1
- SyntaxKind.RightBrace;[}];
- Markup span - Gen - [LF] - SpanEditHandler;Accepts:Any - (5:1,1) - Tokens:1
- SyntaxKind.NewLine;[LF];
- Tag block - Gen - 6 - (7:2,0)
- Markup span - Gen - [] - SpanEditHandler;Accepts:Any - (7:2,0) - Tokens:3
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.Text;[html];
- SyntaxKind.CloseAngle;[>];
+RazorDocument - [0..13)::13 - [@{LF}LF]
+ MarkupBlock - [0..13)::13
+ MarkupTextLiteral - [0..0)::0 - [] - Gen - SpanEditHandler;Accepts:Any
+ Marker;[];
+ CSharpCodeBlock - [0..5)::5
+ CSharpStatement - [0..5)::5
+ CSharpTransition - [0..1)::1 - Gen - SpanEditHandler;Accepts:None
+ Transition;[@];
+ CSharpStatementBody - [1..5)::4
+ RazorMetaCode - [1..2)::1 - Gen - SpanEditHandler;Accepts:None
+ LeftBrace;[{];
+ CSharpCodeBlock - [2..4)::2
+ CSharpStatementLiteral - [2..4)::2 - [LF] - Gen - AutoCompleteEditHandler;Accepts:Any,AutoComplete:[];AtEOL
+ NewLine;[LF];
+ RazorMetaCode - [4..5)::1 - Gen - SpanEditHandler;Accepts:None
+ RightBrace;[}];
+ MarkupEphemeralTextLiteral - [5..7)::2 - [LF] - Gen - SpanEditHandler;Accepts:Any
+ NewLine;[LF];
+ MarkupTagBlock - [7..13)::6 - []
+ MarkupTextLiteral - [7..13)::6 - [] - Gen - SpanEditHandler;Accepts:Any
+ OpenAngle;[<];
+ Text;[html];
+ CloseAngle;[>];
diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlDocumentTest/DoesNotRenderExtraWhitespaceAndNewLineAtTheEndOfVerbatimBlock.stree.txt b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlDocumentTest/DoesNotRenderExtraWhitespaceAndNewLineAtTheEndOfVerbatimBlock.stree.txt
index ef473190e..446309a65 100644
--- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlDocumentTest/DoesNotRenderExtraWhitespaceAndNewLineAtTheEndOfVerbatimBlock.stree.txt
+++ b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlDocumentTest/DoesNotRenderExtraWhitespaceAndNewLineAtTheEndOfVerbatimBlock.stree.txt
@@ -1,20 +1,24 @@
-Markup block - Gen - 15 - (0:0,0)
- Markup span - Gen - [] - SpanEditHandler;Accepts:Any - (0:0,0) - Tokens:1
- SyntaxKind.Unknown;[];
- Statement block - Gen - 5 - (0:0,0)
- Transition span - Gen - [@] - SpanEditHandler;Accepts:None - (0:0,0) - Tokens:1
- SyntaxKind.Transition;[@];
- MetaCode span - Gen - [{] - SpanEditHandler;Accepts:None - (1:0,1) - Tokens:1
- SyntaxKind.LeftBrace;[{];
- Code span - Gen - [LF] - AutoCompleteEditHandler;Accepts:Any,AutoComplete:[];AtEOL - (2:0,2) - Tokens:1
- SyntaxKind.NewLine;[LF];
- MetaCode span - Gen - [}] - SpanEditHandler;Accepts:None - (4:1,0) - Tokens:1
- SyntaxKind.RightBrace;[}];
- Markup span - Gen - [ LF] - SpanEditHandler;Accepts:Any - (5:1,1) - Tokens:2
- SyntaxKind.Whitespace;[ ];
- SyntaxKind.NewLine;[LF];
- Tag block - Gen - 6 - (9:2,0)
- Markup span - Gen - [] - SpanEditHandler;Accepts:Any - (9:2,0) - Tokens:3
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.Text;[html];
- SyntaxKind.CloseAngle;[>];
+RazorDocument - [0..15)::15 - [@{LF} LF]
+ MarkupBlock - [0..15)::15
+ MarkupTextLiteral - [0..0)::0 - [] - Gen - SpanEditHandler;Accepts:Any
+ Marker;[];
+ CSharpCodeBlock - [0..5)::5
+ CSharpStatement - [0..5)::5
+ CSharpTransition - [0..1)::1 - Gen - SpanEditHandler;Accepts:None
+ Transition;[@];
+ CSharpStatementBody - [1..5)::4
+ RazorMetaCode - [1..2)::1 - Gen - SpanEditHandler;Accepts:None
+ LeftBrace;[{];
+ CSharpCodeBlock - [2..4)::2
+ CSharpStatementLiteral - [2..4)::2 - [LF] - Gen - AutoCompleteEditHandler;Accepts:Any,AutoComplete:[];AtEOL
+ NewLine;[LF];
+ RazorMetaCode - [4..5)::1 - Gen - SpanEditHandler;Accepts:None
+ RightBrace;[}];
+ MarkupEphemeralTextLiteral - [5..9)::4 - [ LF] - Gen - SpanEditHandler;Accepts:Any
+ Whitespace;[ ];
+ NewLine;[LF];
+ MarkupTagBlock - [9..15)::6 - []
+ MarkupTextLiteral - [9..15)::6 - [] - Gen - SpanEditHandler;Accepts:Any
+ OpenAngle;[<];
+ Text;[html];
+ CloseAngle;[>];
diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlDocumentTest/DoesNotRenderNewlineAfterTextTagInVerbatimBlockIfFollowedByCSharp.stree.txt b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlDocumentTest/DoesNotRenderNewlineAfterTextTagInVerbatimBlockIfFollowedByCSharp.stree.txt
index a0171c8cd..99cd6a1e7 100644
--- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlDocumentTest/DoesNotRenderNewlineAfterTextTagInVerbatimBlockIfFollowedByCSharp.stree.txt
+++ b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/ParserTests/HtmlDocumentTest/DoesNotRenderNewlineAfterTextTagInVerbatimBlockIfFollowedByCSharp.stree.txt
@@ -1,32 +1,36 @@
-Markup block - Gen - 30 - (0:0,0)
- Markup span - Gen - [] - SpanEditHandler;Accepts:Any - (0:0,0) - Tokens:1
- SyntaxKind.Unknown;[];
- Statement block - Gen - 24 - (0:0,0)
- Transition span - Gen - [@] - SpanEditHandler;Accepts:None - (0:0,0) - Tokens:1
- SyntaxKind.Transition;[@];
- MetaCode span - Gen - [{] - SpanEditHandler;Accepts:None - (1:0,1) - Tokens:1
- SyntaxKind.LeftBrace;[{];
- Markup block - Gen - 17 - (2:0,2)
- Tag block - Gen - 6 - (2:0,2)
- Transition span - Gen - [] - SpanEditHandler;Accepts:None - (2:0,2) - Tokens:3
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.Text;[text];
- SyntaxKind.CloseAngle;[>];
- Markup span - Gen - [Blah] - SpanEditHandler;Accepts:None - (8:0,8) - Tokens:1
- SyntaxKind.Text;[Blah];
- Tag block - Gen - 7 - (12:0,12)
- Transition span - Gen - [] - SpanEditHandler;Accepts:None - (12:0,12) - Tokens:4
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.ForwardSlash;[/];
- SyntaxKind.Text;[text];
- SyntaxKind.CloseAngle;[>];
- Code span - Gen - [LFLF] - SpanEditHandler;Accepts:Any - (19:0,19) - Tokens:2
- SyntaxKind.NewLine;[LF];
- SyntaxKind.NewLine;[LF];
- MetaCode span - Gen - [}] - SpanEditHandler;Accepts:None - (23:2,0) - Tokens:1
- SyntaxKind.RightBrace;[}];
- Tag block - Gen - 6 - (24:2,1)
- Markup span - Gen - [] - SpanEditHandler;Accepts:Any - (24:2,1) - Tokens:3
- SyntaxKind.OpenAngle;[<];
- SyntaxKind.Text;[html];
- SyntaxKind.CloseAngle;[>];
+RazorDocument - [0..30)::30 - [@{BlahLFLF}]
+ MarkupBlock - [0..30)::30
+ MarkupTextLiteral - [0..0)::0 - [] - Gen - SpanEditHandler;Accepts:Any
+ Marker;[];
+ CSharpCodeBlock - [0..24)::24
+ CSharpStatement - [0..24)::24
+ CSharpTransition - [0..1)::1 - Gen - SpanEditHandler;Accepts:None
+ Transition;[@];
+ CSharpStatementBody - [1..24)::23
+ RazorMetaCode - [1..2)::1 - Gen - SpanEditHandler;Accepts:None
+ LeftBrace;[{];
+ CSharpCodeBlock - [2..23)::21
+ MarkupBlock - [2..19)::17
+ MarkupTagBlock - [2..8)::6 - []
+ MarkupTransition - [2..8)::6 - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ Text;[text];
+ CloseAngle;[>];
+ MarkupTextLiteral - [8..12)::4 - [Blah] - Gen - SpanEditHandler;Accepts:None
+ Text;[Blah];
+ MarkupTagBlock - [12..19)::7 - []
+ MarkupTransition - [12..19)::7 - Gen - SpanEditHandler;Accepts:None
+ OpenAngle;[<];
+ ForwardSlash;[/];
+ Text;[text];
+ CloseAngle;[>];
+ CSharpStatementLiteral - [19..23)::4 - [LFLF] - Gen - SpanEditHandler;Accepts:Any
+ NewLine;[LF];
+ NewLine;[LF];
+ RazorMetaCode - [23..24)::1 - Gen - SpanEditHandler;Accepts:None
+ RightBrace;[}];
+ MarkupTagBlock - [24..30)::6 - []
+ MarkupTextLiteral - [24..30)::6 - [] - Gen