diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/ClassifiedSpanRewriter.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/ClassifiedSpanRewriter.cs deleted file mode 100644 index 724203bd0a..0000000000 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/ClassifiedSpanRewriter.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Generic; -using Microsoft.AspNetCore.Razor.Language.Legacy; -using Microsoft.AspNetCore.Razor.Language.Syntax; - -namespace Microsoft.AspNetCore.Razor.Language -{ - internal class ClassifiedSpanRewriter : SyntaxRewriter - { - public override SyntaxNode VisitMarkupStartTag(MarkupStartTagSyntax node) - { - SpanContext latestSpanContext = null; - var newChildren = SyntaxListBuilder.Create(); - var literals = new List(); - foreach (var child in node.Children) - { - if (child is MarkupTextLiteralSyntax literal) - { - literals.Add(literal); - latestSpanContext = literal.GetSpanContext() ?? latestSpanContext; - } - else if (child is MarkupMiscAttributeContentSyntax miscContent) - { - foreach (var contentChild in miscContent.Children) - { - if (contentChild is MarkupTextLiteralSyntax contentLiteral) - { - literals.Add(contentLiteral); - latestSpanContext = contentLiteral.GetSpanContext() ?? latestSpanContext; - } - else - { - // Pop stack - AddLiteralIfExists(); - newChildren.Add(contentChild); - } - } - } - else - { - AddLiteralIfExists(); - newChildren.Add(child); - } - } - - AddLiteralIfExists(); - - return SyntaxFactory.MarkupStartTag(newChildren.ToList()).Green.CreateRed(node.Parent, node.Position); - - void AddLiteralIfExists() - { - if (literals.Count > 0) - { - var mergedLiteral = SyntaxUtilities.MergeTextLiterals(literals.ToArray()); - mergedLiteral = mergedLiteral.WithSpanContext(latestSpanContext); - literals.Clear(); - latestSpanContext = null; - newChildren.Add(mergedLiteral); - } - } - } - } -} diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/ClassifiedSpanVisitor.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/ClassifiedSpanVisitor.cs index f6711c0ed5..46d69157f9 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/ClassifiedSpanVisitor.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/ClassifiedSpanVisitor.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using Microsoft.AspNetCore.Razor.Language.Legacy; using Microsoft.AspNetCore.Razor.Language.Syntax; @@ -108,12 +109,26 @@ namespace Microsoft.AspNetCore.Razor.Language public override void VisitMarkupStartTag(MarkupStartTagSyntax node) { - WriteBlock(node, BlockKindInternal.Tag, base.VisitMarkupStartTag); + WriteBlock(node, BlockKindInternal.Tag, n => + { + var children = GetRewrittenMarkupStartTagChildren(node); + foreach (var child in children) + { + Visit(child); + } + }); } public override void VisitMarkupEndTag(MarkupEndTagSyntax node) { - WriteBlock(node, BlockKindInternal.Tag, base.VisitMarkupEndTag); + WriteBlock(node, BlockKindInternal.Tag, n => + { + var children = GetRewrittenMarkupEndTagChildren(node); + foreach (var child in children) + { + Visit(child); + } + }); } public override void VisitMarkupTagHelperElement(MarkupTagHelperElementSyntax node) @@ -275,5 +290,100 @@ namespace Microsoft.AspNetCore.Razor.Language var span = new ClassifiedSpanInternal(spanSource, blockSource, kind, _currentBlockKind, acceptedCharacters.Value); _spans.Add(span); } + + private static SyntaxList GetRewrittenMarkupStartTagChildren(MarkupStartTagSyntax node) + { + // Rewrites the children of the start tag to look like the legacy syntax tree. + if (node.IsMarkupTransition) + { + var tokens = node.DescendantNodes().Where(n => n is SyntaxToken token && !token.IsMissing).Cast().ToArray(); + var tokenBuilder = SyntaxListBuilder.Create(); + tokenBuilder.AddRange(tokens, 0, tokens.Length); + var markupTransition = SyntaxFactory.MarkupTransition(tokenBuilder.ToList()).Green.CreateRed(node, node.Position); + var spanContext = node.GetSpanContext(); + if (spanContext != null) + { + markupTransition = markupTransition.WithSpanContext(spanContext); + } + + var builder = new SyntaxListBuilder(1); + builder.Add(markupTransition); + return new SyntaxList(builder.ToListNode().CreateRed(node, node.Position)); + } + + SpanContext latestSpanContext = null; + var children = node.Children; + var newChildren = new SyntaxListBuilder(children.Count); + var literals = new List(); + foreach (var child in children) + { + if (child is MarkupTextLiteralSyntax literal) + { + literals.Add(literal); + latestSpanContext = literal.GetSpanContext() ?? latestSpanContext; + } + else if (child is MarkupMiscAttributeContentSyntax miscContent) + { + foreach (var contentChild in miscContent.Children) + { + if (contentChild is MarkupTextLiteralSyntax contentLiteral) + { + literals.Add(contentLiteral); + latestSpanContext = contentLiteral.GetSpanContext() ?? latestSpanContext; + } + else + { + // Pop stack + AddLiteralIfExists(); + newChildren.Add(contentChild); + } + } + } + else + { + AddLiteralIfExists(); + newChildren.Add(child); + } + } + + AddLiteralIfExists(); + + return new SyntaxList(newChildren.ToListNode().CreateRed(node, node.Position)); + + void AddLiteralIfExists() + { + if (literals.Count > 0) + { + var mergedLiteral = SyntaxUtilities.MergeTextLiterals(literals.ToArray()); + mergedLiteral = mergedLiteral.WithSpanContext(latestSpanContext); + literals.Clear(); + latestSpanContext = null; + newChildren.Add(mergedLiteral); + } + } + } + + private static SyntaxList GetRewrittenMarkupEndTagChildren(MarkupEndTagSyntax node) + { + // Rewrites the children of the end tag to look like the legacy syntax tree. + if (node.IsMarkupTransition) + { + var tokens = node.DescendantNodes().Where(n => n is SyntaxToken token && !token.IsMissing).Cast().ToArray(); + var tokenBuilder = SyntaxListBuilder.Create(); + tokenBuilder.AddRange(tokens, 0, tokens.Length); + var markupTransition = SyntaxFactory.MarkupTransition(tokenBuilder.ToList()).Green.CreateRed(node, node.Position); + var spanContext = node.GetSpanContext(); + if (spanContext != null) + { + markupTransition = markupTransition.WithSpanContext(spanContext); + } + + var builder = new SyntaxListBuilder(1); + builder.Add(markupTransition); + return new SyntaxList(builder.ToListNode().CreateRed(node, node.Position)); + } + + return node.Children; + } } } diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorIntermediateNodeLoweringPhase.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorIntermediateNodeLoweringPhase.cs index 290b9d3bd9..c92f08c1b4 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorIntermediateNodeLoweringPhase.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorIntermediateNodeLoweringPhase.cs @@ -810,6 +810,22 @@ namespace Microsoft.AspNetCore.Razor.Language base.VisitMarkupTextLiteral(node); } + public override void VisitMarkupStartTag(MarkupStartTagSyntax node) + { + foreach (var child in node.Children) + { + Visit(child); + } + } + + public override void VisitMarkupEndTag(MarkupEndTagSyntax node) + { + foreach (var child in node.Children) + { + Visit(child); + } + } + public override void VisitMarkupTagHelperElement(MarkupTagHelperElementSyntax node) { var info = node.TagHelperInfo; @@ -1110,7 +1126,7 @@ namespace Microsoft.AspNetCore.Razor.Language Source = BuildSourceSpanFromNode(node), // Could be empty while the tag is being typed in. - TagName = node.StartTag?.GetTagName() ?? node.EndTag?.GetTagName() ?? string.Empty, + TagName = node.StartTag?.GetTagNameWithOptionalBang() ?? node.EndTag?.GetTagName() ?? string.Empty, }); base.VisitMarkupElement(node); diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Legacy/HtmlMarkupParser.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Legacy/HtmlMarkupParser.cs index 3286cbbc66..ed20cc9617 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Legacy/HtmlMarkupParser.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Legacy/HtmlMarkupParser.cs @@ -322,7 +322,9 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy // Check if the previous span was a transition. var previousSpan = builder.Count > 0 ? GetLastSpan(builder[builder.Count - 1]) : null; - if (previousSpan != null && previousSpan.Kind == SyntaxKind.MarkupTransition) + if (previousSpan != null && + ((previousSpan is MarkupStartTagSyntax startTag && startTag.IsMarkupTransition) || + (previousSpan is MarkupEndTagSyntax endTag && endTag.IsMarkupTransition))) { var tokens = ReadWhile( f => (f.Kind == SyntaxKind.Whitespace) || (f.Kind == SyntaxKind.NewLine)); @@ -576,179 +578,201 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy tagName = null; tagMode = MarkupTagMode.Invalid; + isWellFormed = false; + + var openAngleToken = EatCurrentToken(); // Accept '<' + var isBangEscape = TryParseBangEscape(out var bangToken); + + if (At(SyntaxKind.Text)) + { + tagName = CurrentToken.Content; + tagMode = MarkupTagMode.Normal; + + if (isBangEscape) + { + // We don't want to group

and together. + tagName = "!" + tagName; + } + } + + if (mode == ParseMode.MarkupInCodeBlock && + _tagTracker.Count == 0 && + string.Equals(tagName, SyntaxConstants.TextTagName, StringComparison.OrdinalIgnoreCase)) + { + // "" tag is special only if it is the outermost tag. + return ParseStartTextTag(openAngleToken, out tagMode, out isWellFormed); + } + + var tagNameToken = At(SyntaxKind.Text) ? EatCurrentToken() : SyntaxFactory.MissingToken(SyntaxKind.Text); + + var attributes = EmptySyntaxList; using (var pooledResult = Pool.Allocate()) { - var tagBuilder = pooledResult.Builder; + var attributeBuilder = pooledResult.Builder; + + // Parse the contents of a tag like attributes. + ParseAttributes(attributeBuilder); + attributes = attributeBuilder.ToList(); + } - AcceptAndMoveNext(); // Accept '<' - var isBangEscape = TryParseBangEscape(tagBuilder); + SyntaxToken forwardSlashToken = null; + if (At(SyntaxKind.ForwardSlash)) + { + // This is a self closing tag. + tagMode = MarkupTagMode.SelfClosing; + forwardSlashToken = EatCurrentToken(); + } - if (At(SyntaxKind.Text)) + var closeAngleToken = SyntaxFactory.MissingToken(SyntaxKind.CloseAngle); + if (mode == ParseMode.MarkupInCodeBlock) + { + if (EndOfFile || !At(SyntaxKind.CloseAngle)) { - tagName = CurrentToken.Content; - tagMode = MarkupTagMode.Normal; - - if (isBangEscape) + // Unfinished tag + Context.ErrorSink.OnError( + RazorDiagnosticFactory.CreateParsing_UnfinishedTag( + new SourceSpan( + tagName == null ? tagStartLocation : SourceLocationTracker.Advance(tagStartLocation, "<"), + Math.Max(tagName?.Length ?? 0, 1)), + tagName ?? string.Empty)); + } + else + { + if (At(SyntaxKind.CloseAngle)) { - // We don't want to group

and together. - tagName = "!" + tagName; + isWellFormed = true; + closeAngleToken = EatCurrentToken(); } - } - if (mode == ParseMode.MarkupInCodeBlock && - _tagTracker.Count == 0 && - string.Equals(tagName, SyntaxConstants.TextTagName, StringComparison.OrdinalIgnoreCase)) - { - // "" tag is special only if it is the outermost tag. - return ParseStartTextTag(out tagMode, out isWellFormed); - } - - TryAccept(SyntaxKind.Text); - - if (At(SyntaxKind.CloseAngle) && mode == ParseMode.MarkupInCodeBlock) - { // Completed tags in code blocks have no accepted characters. SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.None; - } - // Output open angle and tag name - tagBuilder.Add(OutputAsMarkupLiteral()); - - // Parse the contents of a tag like attributes. - ParseAttributes(tagBuilder); - - if (TryAccept(SyntaxKind.ForwardSlash)) - { - // This is a self closing tag. - tagMode = MarkupTagMode.SelfClosing; - } - - if (mode == ParseMode.MarkupInCodeBlock) - { - if (EndOfFile || !At(SyntaxKind.CloseAngle)) + if (tagMode != MarkupTagMode.SelfClosing && ParserHelpers.VoidElements.Contains(tagName)) { - // Unfinished tag - isWellFormed = false; - Context.ErrorSink.OnError( - RazorDiagnosticFactory.CreateParsing_UnfinishedTag( - new SourceSpan( - tagName == null ? tagStartLocation : SourceLocationTracker.Advance(tagStartLocation, "<"), - Math.Max(tagName?.Length ?? 0, 1)), - tagName ?? string.Empty)); - } - else - { - isWellFormed = TryAccept(SyntaxKind.CloseAngle); + // This is a void element. + // Technically, void elements like "meta" are not allowed to have end tags. Just in case they do, + // we need to look ahead at the next set of tokens. - // Completed tags in code blocks have no accepted characters. - SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.None; + // Place a bookmark + var bookmark = CurrentStart.AbsoluteIndex; - if (tagMode != MarkupTagMode.SelfClosing && ParserHelpers.VoidElements.Contains(tagName)) + // Skip whitespace + ReadWhile(IsSpacingToken(includeNewLines: true)); + + // Open Angle + if (At(SyntaxKind.OpenAngle) && NextIs(SyntaxKind.ForwardSlash)) { - // This is a void element. - // Technically, void elements like "meta" are not allowed to have end tags. Just in case they do, - // we need to look ahead at the next set of tokens. - - // Place a bookmark - var bookmark = CurrentStart.AbsoluteIndex; - - // Skip whitespace - var whiteSpace = ReadWhile(IsSpacingToken(includeNewLines: true)); - - // Open Angle - if (At(SyntaxKind.OpenAngle) && NextIs(SyntaxKind.ForwardSlash)) - { - var openAngle = CurrentToken; - NextToken(); - Assert(SyntaxKind.ForwardSlash); - var forwardSlash = CurrentToken; - NextToken(); - if (!At(SyntaxKind.Text) || !string.Equals(CurrentToken.Content, tagName, StringComparison.OrdinalIgnoreCase)) - { - // There is no matching end void tag. - tagMode = MarkupTagMode.Void; - } - } - else + NextToken(); + Assert(SyntaxKind.ForwardSlash); + NextToken(); + if (!At(SyntaxKind.Text) || !string.Equals(CurrentToken.Content, tagName, StringComparison.OrdinalIgnoreCase)) { // There is no matching end void tag. tagMode = MarkupTagMode.Void; } - - // Go back to the bookmark and just finish this tag at the close angle - Context.Source.Position = bookmark; - NextToken(); } + else + { + // There is no matching end void tag. + tagMode = MarkupTagMode.Void; + } + + // Go back to the bookmark and just finish this tag at the close angle + Context.Source.Position = bookmark; + NextToken(); } } - else - { - isWellFormed = TryAccept(SyntaxKind.CloseAngle); - } - - // End tag block - tagBuilder.Add(OutputAsMarkupLiteral()); - var tagBlock = SyntaxFactory.MarkupStartTag(tagBuilder.ToList()); - if (string.Equals(tagName, ScriptTagName, StringComparison.OrdinalIgnoreCase)) - { - // 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 (!ScriptTagExpectsHtml(tagBlock)) - { - tagMode = MarkupTagMode.Script; - } - } - - return tagBlock; } + else if (At(SyntaxKind.CloseAngle)) + { + isWellFormed = true; + closeAngleToken = EatCurrentToken(); + } + + // End tag block + var startTag = SyntaxFactory.MarkupStartTag(openAngleToken, bangToken, tagNameToken, attributes, forwardSlashToken, closeAngleToken); + if (string.Equals(tagName, ScriptTagName, StringComparison.OrdinalIgnoreCase)) + { + // 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 (!ScriptTagExpectsHtml(startTag)) + { + tagMode = MarkupTagMode.Script; + } + } + + return GetNodeWithSpanContext(startTag); } - private MarkupStartTagSyntax ParseStartTextTag(out MarkupTagMode tagMode, out bool isWellFormed) + private MarkupStartTagSyntax ParseStartTextTag(SyntaxToken openAngleToken, out MarkupTagMode tagMode, out bool isWellFormed) { // At this point, we should have already accepted the open angle. We won't get here if the tag is escaped. tagMode = MarkupTagMode.Normal; var textLocation = CurrentStart; Assert(SyntaxKind.Text); - AcceptAndMoveNext(); + var tagNameToken = EatCurrentToken(); - AcceptWhile(IsSpacingToken(includeNewLines: false)); - if (At(SyntaxKind.CloseAngle) || - (At(SyntaxKind.ForwardSlash) && NextIs(SyntaxKind.CloseAngle))) + using (var pooledResult = Pool.Allocate()) { - if (At(SyntaxKind.ForwardSlash)) + var miscAttributeContentBuilder = pooledResult.Builder; + SyntaxToken forwardSlashToken = null; + SyntaxToken closeAngleToken = null; + + AcceptWhile(IsSpacingToken(includeNewLines: false)); + miscAttributeContentBuilder.Add(OutputAsMarkupLiteral()); + + if (At(SyntaxKind.CloseAngle) || + (At(SyntaxKind.ForwardSlash) && NextIs(SyntaxKind.CloseAngle))) { - tagMode = MarkupTagMode.SelfClosing; - AcceptAndMoveNext(); // '/' + if (At(SyntaxKind.ForwardSlash)) + { + tagMode = MarkupTagMode.SelfClosing; + forwardSlashToken = EatCurrentToken(); + } + + closeAngleToken = EatCurrentToken(); + SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.None; + } + else + { + Context.ErrorSink.OnError( + RazorDiagnosticFactory.CreateParsing_TextTagCannotContainAttributes( + new SourceSpan(textLocation, contentLength: 4 /* text */))); + + RecoverTextTag(out var miscContent, out closeAngleToken); + miscAttributeContentBuilder.Add(miscContent); } - AcceptAndMoveNext(); // '>' - SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.None; + isWellFormed = true; + SpanContext.ChunkGenerator = SpanChunkGenerator.Null; + var startTextTag = SyntaxFactory.MarkupStartTag( + openAngleToken, + bang: null, + name: tagNameToken, + attributes: miscAttributeContentBuilder.ToList(), + forwardSlash: forwardSlashToken, + closeAngle: closeAngleToken); + + return GetNodeWithSpanContext(startTextTag).AsMarkupTransition(); } - else - { - Context.ErrorSink.OnError( - RazorDiagnosticFactory.CreateParsing_TextTagCannotContainAttributes( - new SourceSpan(textLocation, contentLength: 4 /* text */))); - - RecoverTextTag(); - } - - isWellFormed = true; - SpanContext.ChunkGenerator = SpanChunkGenerator.Null; - var transition = GetNodeWithSpanContext(SyntaxFactory.MarkupTransition(Output())); - var startTextTag = SyntaxFactory.MarkupStartTag(transition); - - return startTextTag; } - private void RecoverTextTag() + private void RecoverTextTag(out MarkupTextLiteralSyntax miscContent, out SyntaxToken closeAngleToken) { // 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); + miscContent = OutputAsMarkupLiteral(); // Include the close angle in the text tag block if it's there, otherwise just move on - TryAccept(SyntaxKind.CloseAngle); + if (At(SyntaxKind.CloseAngle)) + { + closeAngleToken = EatCurrentToken(); + } + else + { + closeAngleToken = SyntaxFactory.MissingToken(SyntaxKind.CloseAngle); + } } private MarkupEndTagSyntax ParseEndTag(ParseMode mode, out string tagName, out bool isWellFormed) @@ -757,51 +781,61 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy Assert(SyntaxKind.OpenAngle); tagName = null; + SyntaxToken tagNameToken = null; - using (var pooledResult = Pool.Allocate()) + var openAngleToken = EatCurrentToken(); // Accept '<' + var forwardSlashToken = At(SyntaxKind.ForwardSlash) ? EatCurrentToken() : SyntaxFactory.MissingToken(SyntaxKind.ForwardSlash); + + // Whitespace here is invalid (according to the spec) + var isBangEscape = TryParseBangEscape(out var bangToken); + if (At(SyntaxKind.Text)) { - var tagBuilder = pooledResult.Builder; + tagName = isBangEscape ? "!" : string.Empty; + tagName += CurrentToken.Content; - AcceptAndMoveNext(); // Accept '<' - TryAccept(SyntaxKind.ForwardSlash); - - // Whitespace here is invalid (according to the spec) - var isBangEscape = TryParseBangEscape(tagBuilder); - if (At(SyntaxKind.Text)) + if (mode == ParseMode.MarkupInCodeBlock && + string.Equals(tagName, SyntaxConstants.TextTagName, StringComparison.OrdinalIgnoreCase)) { - tagName = isBangEscape ? "!" : string.Empty; - tagName += CurrentToken.Content; - - if (mode == ParseMode.MarkupInCodeBlock && - string.Equals(tagName, SyntaxConstants.TextTagName, StringComparison.OrdinalIgnoreCase)) + // "" tag is special only if it is the outermost tag. We need to figure out if the current end text tag + // matches the outermost start text tag. + var openTextTagCount = 0; + foreach (var tracker in _tagTracker) { - // "" tag is special only if it is the outermost tag. We need to figure out if the current end text tag - // matches the outermost start text tag. - var openTextTagCount = 0; - foreach (var tracker in _tagTracker) + if (string.Equals(tracker.TagName, SyntaxConstants.TextTagName, StringComparison.OrdinalIgnoreCase)) { - if (string.Equals(tracker.TagName, SyntaxConstants.TextTagName, StringComparison.OrdinalIgnoreCase)) - { - openTextTagCount++; - } - } - - if (openTextTagCount == 1 && - string.Equals(_tagTracker.Last().TagName, SyntaxConstants.TextTagName, StringComparison.OrdinalIgnoreCase)) - { - // This means there is only one open text tag and it is the outermost tag. - return ParseEndTextTag(out isWellFormed); + openTextTagCount++; } } - AcceptAndMoveNext(); + if (openTextTagCount == 1 && + string.Equals(_tagTracker.Last().TagName, SyntaxConstants.TextTagName, StringComparison.OrdinalIgnoreCase)) + { + // This means there is only one open text tag and it is the outermost tag. + return ParseEndTextTag(openAngleToken, forwardSlashToken, out isWellFormed); + } } - TryAccept(SyntaxKind.Whitespace); + + tagNameToken = EatCurrentToken(); + } + else + { + tagNameToken = SyntaxFactory.MissingToken(SyntaxKind.Text); + } + + SyntaxToken closeAngleToken = null; + MarkupMiscAttributeContentSyntax miscAttributeContent = null; + using (var pooledResult = Pool.Allocate()) + { + var miscAttributeBuilder = pooledResult.Builder; + + AcceptWhile(SyntaxKind.Whitespace); + miscAttributeBuilder.Add(OutputAsMarkupLiteral()); if (mode == ParseMode.MarkupInCodeBlock) { // We want to accept malformed end tags as content. AcceptUntil(SyntaxKind.CloseAngle, SyntaxKind.OpenAngle); + miscAttributeBuilder.Add(OutputAsMarkupLiteral()); if (At(SyntaxKind.CloseAngle)) { @@ -810,42 +844,73 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy } } - isWellFormed = TryAccept(SyntaxKind.CloseAngle); - - // End tag block - tagBuilder.Add(OutputAsMarkupLiteral()); - var tagBlock = SyntaxFactory.MarkupEndTag(tagBuilder.ToList()); - return tagBlock; + if (miscAttributeBuilder.Count > 0) + { + miscAttributeContent = SyntaxFactory.MarkupMiscAttributeContent(miscAttributeBuilder.ToList()); + } } + + if (At(SyntaxKind.CloseAngle)) + { + isWellFormed = true; + closeAngleToken = EatCurrentToken(); + } + else + { + isWellFormed = false; + closeAngleToken = SyntaxFactory.MissingToken(SyntaxKind.CloseAngle); + } + + // End tag block + var endTag = SyntaxFactory.MarkupEndTag(openAngleToken, forwardSlashToken, bangToken, tagNameToken, miscAttributeContent, closeAngleToken); + return GetNodeWithSpanContext(endTag); } - private MarkupEndTagSyntax ParseEndTextTag(out bool isWellFormed) + private MarkupEndTagSyntax ParseEndTextTag(SyntaxToken openAngleToken, SyntaxToken forwardSlashToken, out bool isWellFormed) { // At this point, we should have already accepted the open angle and forward slash. We won't get here if the tag is escaped. var textLocation = CurrentStart; Assert(SyntaxKind.Text); - AcceptAndMoveNext(); + var tagNameToken = EatCurrentToken(); - isWellFormed = TryAccept(SyntaxKind.CloseAngle); - if (!isWellFormed) + MarkupMiscAttributeContentSyntax miscAttributeContent = null; + SyntaxToken closeAngleToken = null; + using (var pooledResult = Pool.Allocate()) { - Context.ErrorSink.OnError( - RazorDiagnosticFactory.CreateParsing_TextTagCannotContainAttributes( - new SourceSpan(textLocation, contentLength: 4 /* text */))); + var miscAttributeBuilder = pooledResult.Builder; - SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.Any; - RecoverTextTag(); - } - else - { - SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.None; + isWellFormed = At(SyntaxKind.CloseAngle); + if (!isWellFormed) + { + Context.ErrorSink.OnError( + RazorDiagnosticFactory.CreateParsing_TextTagCannotContainAttributes( + new SourceSpan(textLocation, contentLength: 4 /* text */))); + + SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.Any; + RecoverTextTag(out var miscContent, out closeAngleToken); + miscAttributeBuilder.Add(miscContent); + } + else + { + SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.None; + closeAngleToken = EatCurrentToken(); + } + + if (miscAttributeBuilder.Count > 0) + { + miscAttributeContent = SyntaxFactory.MarkupMiscAttributeContent(miscAttributeBuilder.ToList()); + } } SpanContext.ChunkGenerator = SpanChunkGenerator.Null; - - var transition = GetNodeWithSpanContext(SyntaxFactory.MarkupTransition(Output())); - var endTextTag = SyntaxFactory.MarkupEndTag(transition); - return endTextTag; + var endTextTag = SyntaxFactory.MarkupEndTag( + openAngleToken, + forwardSlashToken, + bang: null, + name: tagNameToken, + miscAttributeContent: miscAttributeContent, + closeAngle: closeAngleToken); + return GetNodeWithSpanContext(endTextTag).AsMarkupTransition(); } private void ParseAttributes(in SyntaxListBuilder builder) @@ -1257,26 +1322,47 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy var tagStart = CurrentStart; builder.Add(OutputAsMarkupLiteral()); + SpanContext.EditHandler.AcceptedCharacters = endTagAcceptedCharacters; + + var openAngleToken = EatCurrentToken(); // '<' + var forwardSlashToken = EatCurrentToken(); // '/' + var tagNameToken = EatCurrentToken(); // 'script' + MarkupMiscAttributeContentSyntax miscContent = null; + SyntaxToken closeAngleToken = null; + using (var pooledResult = Pool.Allocate()) { - var tagBuilder = pooledResult.Builder; - SpanContext.EditHandler.AcceptedCharacters = endTagAcceptedCharacters; + var miscAttributeBuilder = pooledResult.Builder; - AcceptAndMoveNext(); // '<' - AcceptAndMoveNext(); // '/' - ParseMarkupNodes(tagBuilder, ParseMode.Text, token => token.Kind == SyntaxKind.CloseAngle); - if (!TryAccept(SyntaxKind.CloseAngle)) + ParseMarkupNodes(miscAttributeBuilder, ParseMode.Text, token => token.Kind == SyntaxKind.CloseAngle); + miscAttributeBuilder.Add(OutputAsMarkupLiteral()); + if (miscAttributeBuilder.Count > 0) + { + miscContent = SyntaxFactory.MarkupMiscAttributeContent(miscAttributeBuilder.ToList()); + } + + if (!At(SyntaxKind.CloseAngle)) { Context.ErrorSink.OnError( RazorDiagnosticFactory.CreateParsing_UnfinishedTag( new SourceSpan(SourceLocationTracker.Advance(tagStart, " 0 && @@ -1678,19 +1760,15 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy } } - private bool TryParseBangEscape(in SyntaxListBuilder builder) + private bool TryParseBangEscape(out SyntaxToken bangToken) { + bangToken = null; if (IsBangEscape(lookahead: 0)) { - builder.Add(OutputAsMarkupLiteral()); - // Accept the parser escape character '!'. Assert(SyntaxKind.Bang); - AcceptAndMoveNext(); + bangToken = EatCurrentToken(); - // Setup the metacode span that we will be outputing. - SpanContext.ChunkGenerator = SpanChunkGenerator.Null; - builder.Add(OutputAsMetaCode(Output())); return true; } diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Legacy/LegacySyntaxNodeExtensions.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Legacy/LegacySyntaxNodeExtensions.cs index cfe840d4f3..9949ce8a62 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Legacy/LegacySyntaxNodeExtensions.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Legacy/LegacySyntaxNodeExtensions.cs @@ -100,7 +100,20 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy } SyntaxNode owner = null; - var children = node.ChildNodes(); + IEnumerable children = null; + if (node is MarkupStartTagSyntax startTag) + { + children = startTag.Children; + } + else if (node is MarkupEndTagSyntax endTag) + { + children = endTag.Children; + } + else + { + children = node.ChildNodes(); + } + foreach (var child in children) { owner = LocateOwner(child, change); @@ -192,7 +205,27 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy foreach (var child in node.DescendantNodes()) { - if (child.IsSpanKind()) + if (child is MarkupStartTagSyntax startTag) + { + foreach (var tagChild in startTag.Children) + { + if (tagChild.IsSpanKind()) + { + yield return tagChild; + } + } + } + else if (child is MarkupEndTagSyntax endTag) + { + foreach (var tagChild in endTag.Children) + { + if (tagChild.IsSpanKind()) + { + yield return tagChild; + } + } + } + else if (child.IsSpanKind()) { yield return child; } diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Legacy/RazorSyntaxTreeExtensions.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Legacy/RazorSyntaxTreeExtensions.cs index de1945316e..2a3d016224 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Legacy/RazorSyntaxTreeExtensions.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Legacy/RazorSyntaxTreeExtensions.cs @@ -16,10 +16,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy throw new ArgumentNullException(nameof(syntaxTree)); } - var rewriter = new ClassifiedSpanRewriter(); - var rewritten = rewriter.Visit(syntaxTree.Root); var visitor = new ClassifiedSpanVisitor(syntaxTree.Source); - visitor.Visit(rewritten); + visitor.Visit(syntaxTree.Root); return visitor.ClassifiedSpans; } diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Legacy/TagHelperParseTreeRewriter.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Legacy/TagHelperParseTreeRewriter.cs index c46a0b0bab..13a6a89cdc 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Legacy/TagHelperParseTreeRewriter.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Legacy/TagHelperParseTreeRewriter.cs @@ -103,7 +103,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy var startTag = (MarkupStartTagSyntax)Visit(node.StartTag); if (startTag != null) { - var tagName = startTag.GetTagName(); + var tagName = startTag.GetTagNameWithOptionalBang(); if (TryRewriteTagHelperStart(startTag, out tagHelperStart, out tagHelperInfo)) { // This is a tag helper. @@ -218,10 +218,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy tagHelperInfo = null; // Get tag name of the current block - var tagName = startTag.GetTagName(); + var tagName = startTag.GetTagNameWithOptionalBang(); // Could not determine tag name, it can't be a TagHelper, continue on and track the element. - if (tagName == null || tagName.StartsWith("!")) + if (string.IsNullOrEmpty(tagName) || tagName.StartsWith("!")) { return false; } @@ -288,7 +288,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy rewritten = null; var tagName = tagBlock.GetTagName(); // Could not determine tag name, it can't be a TagHelper, continue on and track the element. - if (tagName == null || tagName.StartsWith("!")) + if (string.IsNullOrEmpty(tagName) || tagName.StartsWith("!")) { return false; } @@ -510,22 +510,16 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy return true; } - private bool IsPotentialTagHelperStart(string tagName, MarkupStartTagSyntax childBlock) + private bool IsPotentialTagHelperStart(string tagName, MarkupStartTagSyntax startTag) { - Debug.Assert(childBlock.Children.Count > 0); - var child = childBlock.Children[0]; - return !string.Equals(tagName, SyntaxConstants.TextTagName, StringComparison.OrdinalIgnoreCase) || - child.Kind != SyntaxKind.MarkupTransition; + !startTag.IsMarkupTransition; } - private bool IsPotentialTagHelperEnd(string tagName, MarkupEndTagSyntax childBlock) + private bool IsPotentialTagHelperEnd(string tagName, MarkupEndTagSyntax endTag) { - Debug.Assert(childBlock.Children.Count > 0); - var child = childBlock.Children[0]; - return !string.Equals(tagName, SyntaxConstants.TextTagName, StringComparison.OrdinalIgnoreCase) || - child.Kind != SyntaxKind.MarkupTransition; + !endTag.IsMarkupTransition; } private static bool IsPartialStartTag(MarkupStartTagSyntax tagBlock) @@ -605,7 +599,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy private void ValidateParentAllowsPlainStartTag(MarkupStartTagSyntax tagBlock) { - var tagName = tagBlock.GetTagName(); + var tagName = tagBlock.GetTagNameWithOptionalBang(); // Treat partial tags such as ' Children { get { return new SyntaxList(_children); } } - - internal override GreenNode GetSlot(int index) - { - switch (index) - { - case 0: return _children; - default: return null; - } - } - - internal override SyntaxNode CreateRed(SyntaxNode parent, int position) - { - return new Syntax.MarkupTagBlockSyntax(this, parent, position); - } - - public override TResult Accept(SyntaxVisitor visitor) - { - return visitor.VisitMarkupTagBlock(this); - } - - public override void Accept(SyntaxVisitor visitor) - { - visitor.VisitMarkupTagBlock(this); - } - - public MarkupTagBlockSyntax Update(Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax.SyntaxList children) - { - if (children != Children) - { - var newNode = SyntaxFactory.MarkupTagBlock(children); - var diags = GetDiagnostics(); - if (diags != null && diags.Length > 0) - newNode = newNode.WithDiagnosticsGreen(diags); - var annotations = GetAnnotations(); - if (annotations != null && annotations.Length > 0) - newNode = newNode.WithAnnotationsGreen(annotations); - return newNode; - } - - return this; - } - - internal override GreenNode SetDiagnostics(RazorDiagnostic[] diagnostics) - { - return new MarkupTagBlockSyntax(Kind, _children, diagnostics, GetAnnotations()); - } - - internal override GreenNode SetAnnotations(SyntaxAnnotation[] annotations) - { - return new MarkupTagBlockSyntax(Kind, _children, GetDiagnostics(), annotations); - } - } - internal sealed partial class MarkupMinimizedAttributeBlockSyntax : MarkupSyntaxNode { private readonly MarkupTextLiteralSyntax _namePrefix; @@ -1546,40 +1465,87 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax } } - internal sealed partial class MarkupStartTagSyntax : RazorBlockSyntax + internal sealed partial class MarkupStartTagSyntax : MarkupSyntaxNode { - private readonly GreenNode _children; + private readonly SyntaxToken _openAngle; + private readonly SyntaxToken _bang; + private readonly SyntaxToken _name; + private readonly GreenNode _attributes; + private readonly SyntaxToken _forwardSlash; + private readonly SyntaxToken _closeAngle; - internal MarkupStartTagSyntax(SyntaxKind kind, GreenNode children, RazorDiagnostic[] diagnostics, SyntaxAnnotation[] annotations) + internal MarkupStartTagSyntax(SyntaxKind kind, SyntaxToken openAngle, SyntaxToken bang, SyntaxToken name, GreenNode attributes, SyntaxToken forwardSlash, SyntaxToken closeAngle, RazorDiagnostic[] diagnostics, SyntaxAnnotation[] annotations) : base(kind, diagnostics, annotations) { - SlotCount = 1; - if (children != null) + SlotCount = 6; + AdjustFlagsAndWidth(openAngle); + _openAngle = openAngle; + if (bang != null) { - AdjustFlagsAndWidth(children); - _children = children; + AdjustFlagsAndWidth(bang); + _bang = bang; } + AdjustFlagsAndWidth(name); + _name = name; + if (attributes != null) + { + AdjustFlagsAndWidth(attributes); + _attributes = attributes; + } + if (forwardSlash != null) + { + AdjustFlagsAndWidth(forwardSlash); + _forwardSlash = forwardSlash; + } + AdjustFlagsAndWidth(closeAngle); + _closeAngle = closeAngle; } - internal MarkupStartTagSyntax(SyntaxKind kind, GreenNode children) + internal MarkupStartTagSyntax(SyntaxKind kind, SyntaxToken openAngle, SyntaxToken bang, SyntaxToken name, GreenNode attributes, SyntaxToken forwardSlash, SyntaxToken closeAngle) : base(kind) { - SlotCount = 1; - if (children != null) + SlotCount = 6; + AdjustFlagsAndWidth(openAngle); + _openAngle = openAngle; + if (bang != null) { - AdjustFlagsAndWidth(children); - _children = children; + AdjustFlagsAndWidth(bang); + _bang = bang; } + AdjustFlagsAndWidth(name); + _name = name; + if (attributes != null) + { + AdjustFlagsAndWidth(attributes); + _attributes = attributes; + } + if (forwardSlash != null) + { + AdjustFlagsAndWidth(forwardSlash); + _forwardSlash = forwardSlash; + } + AdjustFlagsAndWidth(closeAngle); + _closeAngle = closeAngle; } - public override SyntaxList Children { get { return new SyntaxList(_children); } } + public SyntaxToken OpenAngle { get { return _openAngle; } } + public SyntaxToken Bang { get { return _bang; } } + public SyntaxToken Name { get { return _name; } } + public SyntaxList Attributes { get { return new SyntaxList(_attributes); } } + public SyntaxToken ForwardSlash { get { return _forwardSlash; } } + public SyntaxToken CloseAngle { get { return _closeAngle; } } internal override GreenNode GetSlot(int index) { switch (index) { - case 0: return _children; + case 0: return _openAngle; + case 1: return _bang; + case 2: return _name; + case 3: return _attributes; + case 4: return _forwardSlash; + case 5: return _closeAngle; default: return null; } } @@ -1599,11 +1565,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax visitor.VisitMarkupStartTag(this); } - public MarkupStartTagSyntax Update(Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax.SyntaxList children) + public MarkupStartTagSyntax Update(SyntaxToken openAngle, SyntaxToken bang, SyntaxToken name, Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax.SyntaxList attributes, SyntaxToken forwardSlash, SyntaxToken closeAngle) { - if (children != Children) + if (openAngle != OpenAngle || bang != Bang || name != Name || attributes != Attributes || forwardSlash != ForwardSlash || closeAngle != CloseAngle) { - var newNode = SyntaxFactory.MarkupStartTag(children); + var newNode = SyntaxFactory.MarkupStartTag(openAngle, bang, name, attributes, forwardSlash, closeAngle); var diags = GetDiagnostics(); if (diags != null && diags.Length > 0) newNode = newNode.WithDiagnosticsGreen(diags); @@ -1618,49 +1584,90 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax internal override GreenNode SetDiagnostics(RazorDiagnostic[] diagnostics) { - return new MarkupStartTagSyntax(Kind, _children, diagnostics, GetAnnotations()); + return new MarkupStartTagSyntax(Kind, _openAngle, _bang, _name, _attributes, _forwardSlash, _closeAngle, diagnostics, GetAnnotations()); } internal override GreenNode SetAnnotations(SyntaxAnnotation[] annotations) { - return new MarkupStartTagSyntax(Kind, _children, GetDiagnostics(), annotations); + return new MarkupStartTagSyntax(Kind, _openAngle, _bang, _name, _attributes, _forwardSlash, _closeAngle, GetDiagnostics(), annotations); } } - internal sealed partial class MarkupEndTagSyntax : RazorBlockSyntax + internal sealed partial class MarkupEndTagSyntax : MarkupSyntaxNode { - private readonly GreenNode _children; + private readonly SyntaxToken _openAngle; + private readonly SyntaxToken _forwardSlash; + private readonly SyntaxToken _bang; + private readonly SyntaxToken _name; + private readonly MarkupMiscAttributeContentSyntax _miscAttributeContent; + private readonly SyntaxToken _closeAngle; - internal MarkupEndTagSyntax(SyntaxKind kind, GreenNode children, RazorDiagnostic[] diagnostics, SyntaxAnnotation[] annotations) + internal MarkupEndTagSyntax(SyntaxKind kind, SyntaxToken openAngle, SyntaxToken forwardSlash, SyntaxToken bang, SyntaxToken name, MarkupMiscAttributeContentSyntax miscAttributeContent, SyntaxToken closeAngle, RazorDiagnostic[] diagnostics, SyntaxAnnotation[] annotations) : base(kind, diagnostics, annotations) { - SlotCount = 1; - if (children != null) + SlotCount = 6; + AdjustFlagsAndWidth(openAngle); + _openAngle = openAngle; + AdjustFlagsAndWidth(forwardSlash); + _forwardSlash = forwardSlash; + if (bang != null) { - AdjustFlagsAndWidth(children); - _children = children; + AdjustFlagsAndWidth(bang); + _bang = bang; } + AdjustFlagsAndWidth(name); + _name = name; + if (miscAttributeContent != null) + { + AdjustFlagsAndWidth(miscAttributeContent); + _miscAttributeContent = miscAttributeContent; + } + AdjustFlagsAndWidth(closeAngle); + _closeAngle = closeAngle; } - internal MarkupEndTagSyntax(SyntaxKind kind, GreenNode children) + internal MarkupEndTagSyntax(SyntaxKind kind, SyntaxToken openAngle, SyntaxToken forwardSlash, SyntaxToken bang, SyntaxToken name, MarkupMiscAttributeContentSyntax miscAttributeContent, SyntaxToken closeAngle) : base(kind) { - SlotCount = 1; - if (children != null) + SlotCount = 6; + AdjustFlagsAndWidth(openAngle); + _openAngle = openAngle; + AdjustFlagsAndWidth(forwardSlash); + _forwardSlash = forwardSlash; + if (bang != null) { - AdjustFlagsAndWidth(children); - _children = children; + AdjustFlagsAndWidth(bang); + _bang = bang; } + AdjustFlagsAndWidth(name); + _name = name; + if (miscAttributeContent != null) + { + AdjustFlagsAndWidth(miscAttributeContent); + _miscAttributeContent = miscAttributeContent; + } + AdjustFlagsAndWidth(closeAngle); + _closeAngle = closeAngle; } - public override SyntaxList Children { get { return new SyntaxList(_children); } } + public SyntaxToken OpenAngle { get { return _openAngle; } } + public SyntaxToken ForwardSlash { get { return _forwardSlash; } } + public SyntaxToken Bang { get { return _bang; } } + public SyntaxToken Name { get { return _name; } } + public MarkupMiscAttributeContentSyntax MiscAttributeContent { get { return _miscAttributeContent; } } + public SyntaxToken CloseAngle { get { return _closeAngle; } } internal override GreenNode GetSlot(int index) { switch (index) { - case 0: return _children; + case 0: return _openAngle; + case 1: return _forwardSlash; + case 2: return _bang; + case 3: return _name; + case 4: return _miscAttributeContent; + case 5: return _closeAngle; default: return null; } } @@ -1680,11 +1687,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax visitor.VisitMarkupEndTag(this); } - public MarkupEndTagSyntax Update(Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax.SyntaxList children) + public MarkupEndTagSyntax Update(SyntaxToken openAngle, SyntaxToken forwardSlash, SyntaxToken bang, SyntaxToken name, MarkupMiscAttributeContentSyntax miscAttributeContent, SyntaxToken closeAngle) { - if (children != Children) + if (openAngle != OpenAngle || forwardSlash != ForwardSlash || bang != Bang || name != Name || miscAttributeContent != MiscAttributeContent || closeAngle != CloseAngle) { - var newNode = SyntaxFactory.MarkupEndTag(children); + var newNode = SyntaxFactory.MarkupEndTag(openAngle, forwardSlash, bang, name, miscAttributeContent, closeAngle); var diags = GetDiagnostics(); if (diags != null && diags.Length > 0) newNode = newNode.WithDiagnosticsGreen(diags); @@ -1699,12 +1706,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax internal override GreenNode SetDiagnostics(RazorDiagnostic[] diagnostics) { - return new MarkupEndTagSyntax(Kind, _children, diagnostics, GetAnnotations()); + return new MarkupEndTagSyntax(Kind, _openAngle, _forwardSlash, _bang, _name, _miscAttributeContent, _closeAngle, diagnostics, GetAnnotations()); } internal override GreenNode SetAnnotations(SyntaxAnnotation[] annotations) { - return new MarkupEndTagSyntax(Kind, _children, GetDiagnostics(), annotations); + return new MarkupEndTagSyntax(Kind, _openAngle, _forwardSlash, _bang, _name, _miscAttributeContent, _closeAngle, GetDiagnostics(), annotations); } } @@ -3510,11 +3517,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax return DefaultVisit(node); } - public virtual TResult VisitMarkupTagBlock(MarkupTagBlockSyntax node) - { - return DefaultVisit(node); - } - public virtual TResult VisitMarkupMinimizedAttributeBlock(MarkupMinimizedAttributeBlockSyntax node) { return DefaultVisit(node); @@ -3709,11 +3711,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax DefaultVisit(node); } - public virtual void VisitMarkupTagBlock(MarkupTagBlockSyntax node) - { - DefaultVisit(node); - } - public virtual void VisitMarkupMinimizedAttributeBlock(MarkupMinimizedAttributeBlockSyntax node) { DefaultVisit(node); @@ -3921,12 +3918,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax return node.Update(children); } - public override GreenNode VisitMarkupTagBlock(MarkupTagBlockSyntax node) - { - var children = VisitList(node.Children); - return node.Update(children); - } - public override GreenNode VisitMarkupMinimizedAttributeBlock(MarkupMinimizedAttributeBlockSyntax node) { var namePrefix = (MarkupTextLiteralSyntax)Visit(node.NamePrefix); @@ -3976,14 +3967,24 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax public override GreenNode VisitMarkupStartTag(MarkupStartTagSyntax node) { - var children = VisitList(node.Children); - return node.Update(children); + var openAngle = (SyntaxToken)Visit(node.OpenAngle); + var bang = (SyntaxToken)Visit(node.Bang); + var name = (SyntaxToken)Visit(node.Name); + var attributes = VisitList(node.Attributes); + var forwardSlash = (SyntaxToken)Visit(node.ForwardSlash); + var closeAngle = (SyntaxToken)Visit(node.CloseAngle); + return node.Update(openAngle, bang, name, attributes, forwardSlash, closeAngle); } public override GreenNode VisitMarkupEndTag(MarkupEndTagSyntax node) { - var children = VisitList(node.Children); - return node.Update(children); + var openAngle = (SyntaxToken)Visit(node.OpenAngle); + var forwardSlash = (SyntaxToken)Visit(node.ForwardSlash); + var bang = (SyntaxToken)Visit(node.Bang); + var name = (SyntaxToken)Visit(node.Name); + var miscAttributeContent = (MarkupMiscAttributeContentSyntax)Visit(node.MiscAttributeContent); + var closeAngle = (SyntaxToken)Visit(node.CloseAngle); + return node.Update(openAngle, forwardSlash, bang, name, miscAttributeContent, closeAngle); } public override GreenNode VisitMarkupTagHelperElement(MarkupTagHelperElementSyntax node) @@ -4244,13 +4245,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax return result; } - public static MarkupTagBlockSyntax MarkupTagBlock(Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax.SyntaxList children) - { - var result = new MarkupTagBlockSyntax(SyntaxKind.MarkupTagBlock, children.Node); - - return result; - } - public static MarkupMinimizedAttributeBlockSyntax MarkupMinimizedAttributeBlock(MarkupTextLiteralSyntax namePrefix, MarkupTextLiteralSyntax name) { if (name == null) @@ -4309,18 +4303,112 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax return result; } - public static MarkupStartTagSyntax MarkupStartTag(Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax.SyntaxList children) + public static MarkupStartTagSyntax MarkupStartTag(SyntaxToken openAngle, SyntaxToken bang, SyntaxToken name, Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax.SyntaxList attributes, SyntaxToken forwardSlash, SyntaxToken closeAngle) { - var result = new MarkupStartTagSyntax(SyntaxKind.MarkupStartTag, children.Node); + if (openAngle == null) + throw new ArgumentNullException(nameof(openAngle)); + switch (openAngle.Kind) + { + case SyntaxKind.OpenAngle: + break; + default: + throw new ArgumentException("openAngle"); + } + if (bang != null) + { + switch (bang.Kind) + { + case SyntaxKind.Bang: + case SyntaxKind.None: + break; + default: + throw new ArgumentException("bang"); + } + } + if (name == null) + throw new ArgumentNullException(nameof(name)); + switch (name.Kind) + { + case SyntaxKind.Text: + break; + default: + throw new ArgumentException("name"); + } + if (forwardSlash != null) + { + switch (forwardSlash.Kind) + { + case SyntaxKind.ForwardSlash: + case SyntaxKind.None: + break; + default: + throw new ArgumentException("forwardSlash"); + } + } + if (closeAngle == null) + throw new ArgumentNullException(nameof(closeAngle)); + switch (closeAngle.Kind) + { + case SyntaxKind.CloseAngle: + break; + default: + throw new ArgumentException("closeAngle"); + } - return result; + return new MarkupStartTagSyntax(SyntaxKind.MarkupStartTag, openAngle, bang, name, attributes.Node, forwardSlash, closeAngle); } - public static MarkupEndTagSyntax MarkupEndTag(Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax.SyntaxList children) + public static MarkupEndTagSyntax MarkupEndTag(SyntaxToken openAngle, SyntaxToken forwardSlash, SyntaxToken bang, SyntaxToken name, MarkupMiscAttributeContentSyntax miscAttributeContent, SyntaxToken closeAngle) { - var result = new MarkupEndTagSyntax(SyntaxKind.MarkupEndTag, children.Node); + if (openAngle == null) + throw new ArgumentNullException(nameof(openAngle)); + switch (openAngle.Kind) + { + case SyntaxKind.OpenAngle: + break; + default: + throw new ArgumentException("openAngle"); + } + if (forwardSlash == null) + throw new ArgumentNullException(nameof(forwardSlash)); + switch (forwardSlash.Kind) + { + case SyntaxKind.ForwardSlash: + break; + default: + throw new ArgumentException("forwardSlash"); + } + if (bang != null) + { + switch (bang.Kind) + { + case SyntaxKind.Bang: + case SyntaxKind.None: + break; + default: + throw new ArgumentException("bang"); + } + } + if (name == null) + throw new ArgumentNullException(nameof(name)); + switch (name.Kind) + { + case SyntaxKind.Text: + break; + default: + throw new ArgumentException("name"); + } + if (closeAngle == null) + throw new ArgumentNullException(nameof(closeAngle)); + switch (closeAngle.Kind) + { + case SyntaxKind.CloseAngle: + break; + default: + throw new ArgumentException("closeAngle"); + } - return result; + return new MarkupEndTagSyntax(SyntaxKind.MarkupEndTag, openAngle, forwardSlash, bang, name, miscAttributeContent, closeAngle); } public static MarkupTagHelperElementSyntax MarkupTagHelperElement(MarkupTagHelperStartTagSyntax startTag, Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax.SyntaxList body, MarkupTagHelperEndTagSyntax endTag) @@ -4544,7 +4632,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax typeof(MarkupTextLiteralSyntax), typeof(MarkupEphemeralTextLiteralSyntax), typeof(MarkupCommentBlockSyntax), - typeof(MarkupTagBlockSyntax), typeof(MarkupMinimizedAttributeBlockSyntax), typeof(MarkupAttributeBlockSyntax), typeof(MarkupMiscAttributeContentSyntax), diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/Generated/Syntax.xml.Main.Generated.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/Generated/Syntax.xml.Main.Generated.cs index a5f908438a..c33baf1f06 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/Generated/Syntax.xml.Main.Generated.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/Generated/Syntax.xml.Main.Generated.cs @@ -71,12 +71,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax return DefaultVisit(node); } - ///

Called when the visitor visits a MarkupTagBlockSyntax node. - public virtual TResult VisitMarkupTagBlock(MarkupTagBlockSyntax node) - { - return DefaultVisit(node); - } - /// Called when the visitor visits a MarkupMinimizedAttributeBlockSyntax node. public virtual TResult VisitMarkupMinimizedAttributeBlock(MarkupMinimizedAttributeBlockSyntax node) { @@ -308,12 +302,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax DefaultVisit(node); } - /// Called when the visitor visits a MarkupTagBlockSyntax node. - public virtual void VisitMarkupTagBlock(MarkupTagBlockSyntax node) - { - DefaultVisit(node); - } - /// Called when the visitor visits a MarkupMinimizedAttributeBlockSyntax node. public virtual void VisitMarkupMinimizedAttributeBlock(MarkupMinimizedAttributeBlockSyntax node) { @@ -549,12 +537,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax return node.Update(children); } - public override SyntaxNode VisitMarkupTagBlock(MarkupTagBlockSyntax node) - { - var children = VisitList(node.Children); - return node.Update(children); - } - public override SyntaxNode VisitMarkupMinimizedAttributeBlock(MarkupMinimizedAttributeBlockSyntax node) { var namePrefix = (MarkupTextLiteralSyntax)Visit(node.NamePrefix); @@ -604,14 +586,24 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax public override SyntaxNode VisitMarkupStartTag(MarkupStartTagSyntax node) { - var children = VisitList(node.Children); - return node.Update(children); + var openAngle = (SyntaxToken)VisitToken(node.OpenAngle); + var bang = (SyntaxToken)VisitToken(node.Bang); + var name = (SyntaxToken)VisitToken(node.Name); + var attributes = VisitList(node.Attributes); + var forwardSlash = (SyntaxToken)VisitToken(node.ForwardSlash); + var closeAngle = (SyntaxToken)VisitToken(node.CloseAngle); + return node.Update(openAngle, bang, name, attributes, forwardSlash, closeAngle); } public override SyntaxNode VisitMarkupEndTag(MarkupEndTagSyntax node) { - var children = VisitList(node.Children); - return node.Update(children); + var openAngle = (SyntaxToken)VisitToken(node.OpenAngle); + var forwardSlash = (SyntaxToken)VisitToken(node.ForwardSlash); + var bang = (SyntaxToken)VisitToken(node.Bang); + var name = (SyntaxToken)VisitToken(node.Name); + var miscAttributeContent = (MarkupMiscAttributeContentSyntax)Visit(node.MiscAttributeContent); + var closeAngle = (SyntaxToken)VisitToken(node.CloseAngle); + return node.Update(openAngle, forwardSlash, bang, name, miscAttributeContent, closeAngle); } public override SyntaxNode VisitMarkupTagHelperElement(MarkupTagHelperElementSyntax node) @@ -906,18 +898,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax return SyntaxFactory.MarkupCommentBlock(default(SyntaxList)); } - /// Creates a new MarkupTagBlockSyntax instance. - public static MarkupTagBlockSyntax MarkupTagBlock(SyntaxList children) - { - return (MarkupTagBlockSyntax)InternalSyntax.SyntaxFactory.MarkupTagBlock(children.Node.ToGreenList()).CreateRed(); - } - - /// Creates a new MarkupTagBlockSyntax instance. - public static MarkupTagBlockSyntax MarkupTagBlock() - { - return SyntaxFactory.MarkupTagBlock(default(SyntaxList)); - } - /// Creates a new MarkupMinimizedAttributeBlockSyntax instance. public static MarkupMinimizedAttributeBlockSyntax MarkupMinimizedAttributeBlock(MarkupTextLiteralSyntax namePrefix, MarkupTextLiteralSyntax name) { @@ -1010,27 +990,100 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax } /// Creates a new MarkupStartTagSyntax instance. - public static MarkupStartTagSyntax MarkupStartTag(SyntaxList children) + public static MarkupStartTagSyntax MarkupStartTag(SyntaxToken openAngle, SyntaxToken bang, SyntaxToken name, SyntaxList attributes, SyntaxToken forwardSlash, SyntaxToken closeAngle) { - return (MarkupStartTagSyntax)InternalSyntax.SyntaxFactory.MarkupStartTag(children.Node.ToGreenList()).CreateRed(); + switch (openAngle.Kind) + { + case SyntaxKind.OpenAngle: + break; + default: + throw new ArgumentException("openAngle"); + } + switch (bang.Kind) + { + case SyntaxKind.Bang: + case SyntaxKind.None: + break; + default: + throw new ArgumentException("bang"); + } + switch (name.Kind) + { + case SyntaxKind.Text: + break; + default: + throw new ArgumentException("name"); + } + switch (forwardSlash.Kind) + { + case SyntaxKind.ForwardSlash: + case SyntaxKind.None: + break; + default: + throw new ArgumentException("forwardSlash"); + } + switch (closeAngle.Kind) + { + case SyntaxKind.CloseAngle: + break; + default: + throw new ArgumentException("closeAngle"); + } + return (MarkupStartTagSyntax)InternalSyntax.SyntaxFactory.MarkupStartTag((Syntax.InternalSyntax.SyntaxToken)openAngle.Green, (Syntax.InternalSyntax.SyntaxToken)bang.Green, (Syntax.InternalSyntax.SyntaxToken)name.Green, attributes.Node.ToGreenList(), (Syntax.InternalSyntax.SyntaxToken)forwardSlash.Green, (Syntax.InternalSyntax.SyntaxToken)closeAngle.Green).CreateRed(); } /// Creates a new MarkupStartTagSyntax instance. - public static MarkupStartTagSyntax MarkupStartTag() + public static MarkupStartTagSyntax MarkupStartTag(SyntaxList attributes = default(SyntaxList)) { - return SyntaxFactory.MarkupStartTag(default(SyntaxList)); + return SyntaxFactory.MarkupStartTag(SyntaxFactory.Token(SyntaxKind.OpenAngle), default(SyntaxToken), SyntaxFactory.Token(SyntaxKind.Text), attributes, default(SyntaxToken), SyntaxFactory.Token(SyntaxKind.CloseAngle)); } /// Creates a new MarkupEndTagSyntax instance. - public static MarkupEndTagSyntax MarkupEndTag(SyntaxList children) + public static MarkupEndTagSyntax MarkupEndTag(SyntaxToken openAngle, SyntaxToken forwardSlash, SyntaxToken bang, SyntaxToken name, MarkupMiscAttributeContentSyntax miscAttributeContent, SyntaxToken closeAngle) { - return (MarkupEndTagSyntax)InternalSyntax.SyntaxFactory.MarkupEndTag(children.Node.ToGreenList()).CreateRed(); + switch (openAngle.Kind) + { + case SyntaxKind.OpenAngle: + break; + default: + throw new ArgumentException("openAngle"); + } + switch (forwardSlash.Kind) + { + case SyntaxKind.ForwardSlash: + break; + default: + throw new ArgumentException("forwardSlash"); + } + switch (bang.Kind) + { + case SyntaxKind.Bang: + case SyntaxKind.None: + break; + default: + throw new ArgumentException("bang"); + } + switch (name.Kind) + { + case SyntaxKind.Text: + break; + default: + throw new ArgumentException("name"); + } + switch (closeAngle.Kind) + { + case SyntaxKind.CloseAngle: + break; + default: + throw new ArgumentException("closeAngle"); + } + return (MarkupEndTagSyntax)InternalSyntax.SyntaxFactory.MarkupEndTag((Syntax.InternalSyntax.SyntaxToken)openAngle.Green, (Syntax.InternalSyntax.SyntaxToken)forwardSlash.Green, (Syntax.InternalSyntax.SyntaxToken)bang.Green, (Syntax.InternalSyntax.SyntaxToken)name.Green, miscAttributeContent == null ? null : (InternalSyntax.MarkupMiscAttributeContentSyntax)miscAttributeContent.Green, (Syntax.InternalSyntax.SyntaxToken)closeAngle.Green).CreateRed(); } /// Creates a new MarkupEndTagSyntax instance. - public static MarkupEndTagSyntax MarkupEndTag() + public static MarkupEndTagSyntax MarkupEndTag(MarkupMiscAttributeContentSyntax miscAttributeContent = default(MarkupMiscAttributeContentSyntax)) { - return SyntaxFactory.MarkupEndTag(default(SyntaxList)); + return SyntaxFactory.MarkupEndTag(SyntaxFactory.Token(SyntaxKind.OpenAngle), SyntaxFactory.Token(SyntaxKind.ForwardSlash), default(SyntaxToken), SyntaxFactory.Token(SyntaxKind.Text), miscAttributeContent, SyntaxFactory.Token(SyntaxKind.CloseAngle)); } /// Creates a new MarkupTagHelperElementSyntax instance. diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/Generated/Syntax.xml.Syntax.Generated.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/Generated/Syntax.xml.Syntax.Generated.cs index 92ba8867d0..42cf2587aa 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/Generated/Syntax.xml.Syntax.Generated.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/Generated/Syntax.xml.Syntax.Generated.cs @@ -782,77 +782,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax } } - internal sealed partial class MarkupTagBlockSyntax : RazorBlockSyntax - { - private SyntaxNode _children; - - internal MarkupTagBlockSyntax(GreenNode green, SyntaxNode parent, int position) - : base(green, parent, position) - { - } - - public override SyntaxList Children - { - get - { - return new SyntaxList(GetRed(ref _children, 0)); - } - } - - internal override SyntaxNode GetNodeSlot(int index) - { - switch (index) - { - case 0: return GetRedAtZero(ref _children); - default: return null; - } - } - internal override SyntaxNode GetCachedSlot(int index) - { - switch (index) - { - case 0: return _children; - default: return null; - } - } - - public override TResult Accept(SyntaxVisitor visitor) - { - return visitor.VisitMarkupTagBlock(this); - } - - public override void Accept(SyntaxVisitor visitor) - { - visitor.VisitMarkupTagBlock(this); - } - - public MarkupTagBlockSyntax Update(SyntaxList children) - { - if (children != Children) - { - var newNode = SyntaxFactory.MarkupTagBlock(children); - var annotations = GetAnnotations(); - if (annotations != null && annotations.Length > 0) - return newNode.WithAnnotations(annotations); - return newNode; - } - - return this; - } - - internal override RazorBlockSyntax WithChildrenCore(SyntaxList children) => WithChildren(children); - public new MarkupTagBlockSyntax WithChildren(SyntaxList children) - { - return Update(children); - } - internal override RazorBlockSyntax AddChildrenCore(params RazorSyntaxNode[] items) => AddChildren(items); - - public new MarkupTagBlockSyntax AddChildren(params RazorSyntaxNode[] items) - { - return WithChildren(this.Children.AddRange(items)); - } - } - internal sealed partial class MarkupMinimizedAttributeBlockSyntax : MarkupSyntaxNode { private MarkupTextLiteralSyntax _namePrefix; @@ -1475,38 +1404,77 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax return Update(StartTag, Body, endTag); } - public MarkupElementSyntax AddStartTagChildren(params RazorSyntaxNode[] items) + public MarkupElementSyntax AddStartTagAttributes(params RazorSyntaxNode[] items) { var _startTag = this.StartTag ?? SyntaxFactory.MarkupStartTag(); - return this.WithStartTag(_startTag.WithChildren(_startTag.Children.AddRange(items))); + return this.WithStartTag(_startTag.WithAttributes(_startTag.Attributes.AddRange(items))); } public MarkupElementSyntax AddBody(params RazorSyntaxNode[] items) { return WithBody(this.Body.AddRange(items)); } - - public MarkupElementSyntax AddEndTagChildren(params RazorSyntaxNode[] items) - { - var _endTag = this.EndTag ?? SyntaxFactory.MarkupEndTag(); - return this.WithEndTag(_endTag.WithChildren(_endTag.Children.AddRange(items))); - } } - internal sealed partial class MarkupStartTagSyntax : RazorBlockSyntax + internal sealed partial class MarkupStartTagSyntax : MarkupSyntaxNode { - private SyntaxNode _children; + private SyntaxToken _openAngle; + private SyntaxToken _bang; + private SyntaxToken _name; + private SyntaxNode _attributes; + private SyntaxToken _forwardSlash; + private SyntaxToken _closeAngle; internal MarkupStartTagSyntax(GreenNode green, SyntaxNode parent, int position) : base(green, parent, position) { } - public override SyntaxList Children + public SyntaxToken OpenAngle { get { - return new SyntaxList(GetRed(ref _children, 0)); + return GetRedAtZero(ref _openAngle); + } + } + + public SyntaxToken Bang + { + get + { + return GetRed(ref _bang, 1); + } + } + + public SyntaxToken Name + { + get + { + return GetRed(ref _name, 2); + } + } + + public SyntaxList Attributes + { + get + { + return new SyntaxList(GetRed(ref _attributes, 3)); + } + } + + public SyntaxToken ForwardSlash + { + get + { + return GetRed(ref _forwardSlash, 4); + } + } + + public SyntaxToken CloseAngle + { + get + { + return GetRed(ref _closeAngle, 5); } } @@ -1514,7 +1482,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax { switch (index) { - case 0: return GetRedAtZero(ref _children); + case 0: return GetRedAtZero(ref _openAngle); + case 1: return GetRed(ref _bang, 1); + case 2: return GetRed(ref _name, 2); + case 3: return GetRed(ref _attributes, 3); + case 4: return GetRed(ref _forwardSlash, 4); + case 5: return GetRed(ref _closeAngle, 5); default: return null; } } @@ -1522,7 +1495,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax { switch (index) { - case 0: return _children; + case 0: return _openAngle; + case 1: return _bang; + case 2: return _name; + case 3: return _attributes; + case 4: return _forwardSlash; + case 5: return _closeAngle; default: return null; } } @@ -1537,11 +1515,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax visitor.VisitMarkupStartTag(this); } - public MarkupStartTagSyntax Update(SyntaxList children) + public MarkupStartTagSyntax Update(SyntaxToken openAngle, SyntaxToken bang, SyntaxToken name, SyntaxList attributes, SyntaxToken forwardSlash, SyntaxToken closeAngle) { - if (children != Children) + if (openAngle != OpenAngle || bang != Bang || name != Name || attributes != Attributes || forwardSlash != ForwardSlash || closeAngle != CloseAngle) { - var newNode = SyntaxFactory.MarkupStartTag(children); + var newNode = SyntaxFactory.MarkupStartTag(openAngle, bang, name, attributes, forwardSlash, closeAngle); var annotations = GetAnnotations(); if (annotations != null && annotations.Length > 0) return newNode.WithAnnotations(annotations); @@ -1551,33 +1529,101 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax return this; } - internal override RazorBlockSyntax WithChildrenCore(SyntaxList children) => WithChildren(children); - public new MarkupStartTagSyntax WithChildren(SyntaxList children) + public MarkupStartTagSyntax WithOpenAngle(SyntaxToken openAngle) { - return Update(children); + return Update(openAngle, Bang, Name, Attributes, ForwardSlash, CloseAngle); } - internal override RazorBlockSyntax AddChildrenCore(params RazorSyntaxNode[] items) => AddChildren(items); - public new MarkupStartTagSyntax AddChildren(params RazorSyntaxNode[] items) + public MarkupStartTagSyntax WithBang(SyntaxToken bang) { - return WithChildren(this.Children.AddRange(items)); + return Update(OpenAngle, bang, Name, Attributes, ForwardSlash, CloseAngle); + } + + public MarkupStartTagSyntax WithName(SyntaxToken name) + { + return Update(OpenAngle, Bang, name, Attributes, ForwardSlash, CloseAngle); + } + + public MarkupStartTagSyntax WithAttributes(SyntaxList attributes) + { + return Update(OpenAngle, Bang, Name, attributes, ForwardSlash, CloseAngle); + } + + public MarkupStartTagSyntax WithForwardSlash(SyntaxToken forwardSlash) + { + return Update(OpenAngle, Bang, Name, Attributes, forwardSlash, CloseAngle); + } + + public MarkupStartTagSyntax WithCloseAngle(SyntaxToken closeAngle) + { + return Update(OpenAngle, Bang, Name, Attributes, ForwardSlash, closeAngle); + } + + public MarkupStartTagSyntax AddAttributes(params RazorSyntaxNode[] items) + { + return WithAttributes(this.Attributes.AddRange(items)); } } - internal sealed partial class MarkupEndTagSyntax : RazorBlockSyntax + internal sealed partial class MarkupEndTagSyntax : MarkupSyntaxNode { - private SyntaxNode _children; + private SyntaxToken _openAngle; + private SyntaxToken _forwardSlash; + private SyntaxToken _bang; + private SyntaxToken _name; + private MarkupMiscAttributeContentSyntax _miscAttributeContent; + private SyntaxToken _closeAngle; internal MarkupEndTagSyntax(GreenNode green, SyntaxNode parent, int position) : base(green, parent, position) { } - public override SyntaxList Children + public SyntaxToken OpenAngle { get { - return new SyntaxList(GetRed(ref _children, 0)); + return GetRedAtZero(ref _openAngle); + } + } + + public SyntaxToken ForwardSlash + { + get + { + return GetRed(ref _forwardSlash, 1); + } + } + + public SyntaxToken Bang + { + get + { + return GetRed(ref _bang, 2); + } + } + + public SyntaxToken Name + { + get + { + return GetRed(ref _name, 3); + } + } + + public MarkupMiscAttributeContentSyntax MiscAttributeContent + { + get + { + return GetRed(ref _miscAttributeContent, 4); + } + } + + public SyntaxToken CloseAngle + { + get + { + return GetRed(ref _closeAngle, 5); } } @@ -1585,7 +1631,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax { switch (index) { - case 0: return GetRedAtZero(ref _children); + case 0: return GetRedAtZero(ref _openAngle); + case 1: return GetRed(ref _forwardSlash, 1); + case 2: return GetRed(ref _bang, 2); + case 3: return GetRed(ref _name, 3); + case 4: return GetRed(ref _miscAttributeContent, 4); + case 5: return GetRed(ref _closeAngle, 5); default: return null; } } @@ -1593,7 +1644,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax { switch (index) { - case 0: return _children; + case 0: return _openAngle; + case 1: return _forwardSlash; + case 2: return _bang; + case 3: return _name; + case 4: return _miscAttributeContent; + case 5: return _closeAngle; default: return null; } } @@ -1608,11 +1664,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax visitor.VisitMarkupEndTag(this); } - public MarkupEndTagSyntax Update(SyntaxList children) + public MarkupEndTagSyntax Update(SyntaxToken openAngle, SyntaxToken forwardSlash, SyntaxToken bang, SyntaxToken name, MarkupMiscAttributeContentSyntax miscAttributeContent, SyntaxToken closeAngle) { - if (children != Children) + if (openAngle != OpenAngle || forwardSlash != ForwardSlash || bang != Bang || name != Name || miscAttributeContent != MiscAttributeContent || closeAngle != CloseAngle) { - var newNode = SyntaxFactory.MarkupEndTag(children); + var newNode = SyntaxFactory.MarkupEndTag(openAngle, forwardSlash, bang, name, miscAttributeContent, closeAngle); var annotations = GetAnnotations(); if (annotations != null && annotations.Length > 0) return newNode.WithAnnotations(annotations); @@ -1622,16 +1678,40 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax return this; } - internal override RazorBlockSyntax WithChildrenCore(SyntaxList children) => WithChildren(children); - public new MarkupEndTagSyntax WithChildren(SyntaxList children) + public MarkupEndTagSyntax WithOpenAngle(SyntaxToken openAngle) { - return Update(children); + return Update(openAngle, ForwardSlash, Bang, Name, MiscAttributeContent, CloseAngle); } - internal override RazorBlockSyntax AddChildrenCore(params RazorSyntaxNode[] items) => AddChildren(items); - public new MarkupEndTagSyntax AddChildren(params RazorSyntaxNode[] items) + public MarkupEndTagSyntax WithForwardSlash(SyntaxToken forwardSlash) { - return WithChildren(this.Children.AddRange(items)); + return Update(OpenAngle, forwardSlash, Bang, Name, MiscAttributeContent, CloseAngle); + } + + public MarkupEndTagSyntax WithBang(SyntaxToken bang) + { + return Update(OpenAngle, ForwardSlash, bang, Name, MiscAttributeContent, CloseAngle); + } + + public MarkupEndTagSyntax WithName(SyntaxToken name) + { + return Update(OpenAngle, ForwardSlash, Bang, name, MiscAttributeContent, CloseAngle); + } + + public MarkupEndTagSyntax WithMiscAttributeContent(MarkupMiscAttributeContentSyntax miscAttributeContent) + { + return Update(OpenAngle, ForwardSlash, Bang, Name, miscAttributeContent, CloseAngle); + } + + public MarkupEndTagSyntax WithCloseAngle(SyntaxToken closeAngle) + { + return Update(OpenAngle, ForwardSlash, Bang, Name, MiscAttributeContent, closeAngle); + } + + public MarkupEndTagSyntax AddMiscAttributeContentChildren(params RazorSyntaxNode[] items) + { + var _miscAttributeContent = this.MiscAttributeContent ?? SyntaxFactory.MarkupMiscAttributeContent(); + return this.WithMiscAttributeContent(_miscAttributeContent.WithChildren(_miscAttributeContent.Children.AddRange(items))); } } diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/InternalSyntax/MarkupEndTagSyntax.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/InternalSyntax/MarkupEndTagSyntax.cs new file mode 100644 index 0000000000..2f0277287a --- /dev/null +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/InternalSyntax/MarkupEndTagSyntax.cs @@ -0,0 +1,34 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax +{ + internal sealed partial class MarkupEndTagSyntax + { + private static readonly string MarkupTransitionKey = "MarkupTransition"; + + public bool IsMarkupTransition + { + get + { + var annotation = GetAnnotations().FirstOrDefault(n => n.Kind == MarkupTransitionKey); + return annotation != null; + } + } + + public MarkupEndTagSyntax AsMarkupTransition() + { + var annotations = new List(GetAnnotations()) + { + new SyntaxAnnotation(MarkupTransitionKey, new object()) + }; + + var newGreen = this.WithAnnotationsGreen(annotations.ToArray()); + + return newGreen; + } + } +} diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/InternalSyntax/MarkupStartTagSyntax.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/InternalSyntax/MarkupStartTagSyntax.cs new file mode 100644 index 0000000000..47565e6e93 --- /dev/null +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/InternalSyntax/MarkupStartTagSyntax.cs @@ -0,0 +1,34 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax +{ + internal sealed partial class MarkupStartTagSyntax + { + private static readonly string MarkupTransitionKey = "MarkupTransition"; + + public bool IsMarkupTransition + { + get + { + var annotation = GetAnnotations().FirstOrDefault(n => n.Kind == MarkupTransitionKey); + return annotation != null; + } + } + + public MarkupStartTagSyntax AsMarkupTransition() + { + var annotations = new List(GetAnnotations()) + { + new SyntaxAnnotation(MarkupTransitionKey, new object()) + }; + + var newGreen = this.WithAnnotationsGreen(annotations.ToArray()); + + return newGreen; + } + } +} diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/MarkupEndTagSyntax.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/MarkupEndTagSyntax.cs new file mode 100644 index 0000000000..2f2a529f55 --- /dev/null +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/MarkupEndTagSyntax.cs @@ -0,0 +1,72 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Razor.Language.Legacy; + +namespace Microsoft.AspNetCore.Razor.Language.Syntax +{ + internal partial class MarkupEndTagSyntax + { + public bool IsMarkupTransition + { + get + { + return ((InternalSyntax.MarkupEndTagSyntax)Green).IsMarkupTransition; + } + } + + public SyntaxList Children => GetLegacyChildren(); + + public string GetTagName() + { + return Name.IsMissing ? string.Empty : Bang?.Content + Name.Content; + } + + private SyntaxList GetLegacyChildren() + { + // This method returns the children of this end tag in legacy format. + // This is needed to generate the same classified spans as the legacy syntax tree. + var builder = new SyntaxListBuilder(3); + var tokens = SyntaxListBuilder.Create(); + var context = this.GetSpanContext(); + if (!OpenAngle.IsMissing) + { + tokens.Add(OpenAngle); + } + if (!ForwardSlash.IsMissing) + { + tokens.Add(ForwardSlash); + } + if (Bang != null) + { + // The prefix of an end tag(E.g '|') will have 'Any' accepted characters if a bang exists. + var acceptsAnyContext = new SpanContext(context.ChunkGenerator, SpanEditHandler.CreateDefault()); + acceptsAnyContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.Any; + builder.Add(SyntaxFactory.MarkupTextLiteral(tokens.Consume()).WithSpanContext(acceptsAnyContext)); + + tokens.Add(Bang); + var acceptsNoneContext = new SpanContext(context.ChunkGenerator, SpanEditHandler.CreateDefault()); + acceptsNoneContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.None; + builder.Add(SyntaxFactory.RazorMetaCode(tokens.Consume()).WithSpanContext(acceptsNoneContext)); + } + if (!Name.IsMissing) + { + tokens.Add(Name); + } + if (MiscAttributeContent?.Children != null && MiscAttributeContent.Children.Count > 0) + { + foreach (var content in MiscAttributeContent.Children) + { + tokens.AddRange(((MarkupTextLiteralSyntax)content).LiteralTokens); + } + } + if (!CloseAngle.IsMissing) + { + tokens.Add(CloseAngle); + } + builder.Add(SyntaxFactory.MarkupTextLiteral(tokens.Consume()).WithSpanContext(context)); + + return new SyntaxList(builder.ToListNode().CreateRed(this, Position)); + } + } +} diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/MarkupStartTagSyntax.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/MarkupStartTagSyntax.cs new file mode 100644 index 0000000000..dc5d10c39f --- /dev/null +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/MarkupStartTagSyntax.cs @@ -0,0 +1,98 @@ +// 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 Microsoft.AspNetCore.Razor.Language.Legacy; + +namespace Microsoft.AspNetCore.Razor.Language.Syntax +{ + internal partial class MarkupStartTagSyntax + { + public bool IsMarkupTransition + { + get + { + return ((InternalSyntax.MarkupStartTagSyntax)Green).IsMarkupTransition; + } + } + + public SyntaxList Children => GetLegacyChildren(); + + public string GetTagNameWithOptionalBang() + { + return Name.IsMissing ? string.Empty : Bang?.Content + Name.Content; + } + + public bool IsSelfClosing() + { + return ForwardSlash != null && + !ForwardSlash.IsMissing && + !CloseAngle.IsMissing; + } + + public bool IsVoidElement() + { + return ParserHelpers.VoidElements.Contains(Name.Content); + } + + private SyntaxList GetLegacyChildren() + { + // This method returns the children of this start tag in legacy format. + // This is needed to generate the same classified spans as the legacy syntax tree. + var builder = new SyntaxListBuilder(5); + var tokens = SyntaxListBuilder.Create(); + var context = this.GetSpanContext(); + + // We want to know if this tag contains non-whitespace attribute content to set the appropriate AcceptedCharacters. + // The prefix of a start tag(E.g '|') will have 'Any' accepted characters if non-whitespace attribute content exists. + var acceptsAnyContext = new SpanContext(context.ChunkGenerator, SpanEditHandler.CreateDefault()); + acceptsAnyContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.Any; + var containsAttributesContent = false; + foreach (var attribute in Attributes) + { + if (!string.IsNullOrWhiteSpace(attribute.GetContent())) + { + containsAttributesContent = true; + break; + } + } + + if (!OpenAngle.IsMissing) + { + tokens.Add(OpenAngle); + } + if (Bang != null) + { + builder.Add(SyntaxFactory.MarkupTextLiteral(tokens.Consume()).WithSpanContext(acceptsAnyContext)); + + tokens.Add(Bang); + var acceptsNoneContext = new SpanContext(context.ChunkGenerator, SpanEditHandler.CreateDefault()); + acceptsNoneContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.None; + builder.Add(SyntaxFactory.RazorMetaCode(tokens.Consume()).WithSpanContext(acceptsNoneContext)); + } + if (!Name.IsMissing) + { + tokens.Add(Name); + } + + builder.Add(SyntaxFactory.MarkupTextLiteral(tokens.Consume()).WithSpanContext(containsAttributesContent ? acceptsAnyContext : context)); + + builder.AddRange(Attributes); + + if (ForwardSlash != null) + { + tokens.Add(ForwardSlash); + } + if (!CloseAngle.IsMissing) + { + tokens.Add(CloseAngle); + } + + if (tokens.Count > 0) + { + builder.Add(SyntaxFactory.MarkupTextLiteral(tokens.Consume()).WithSpanContext(context)); + } + + return new SyntaxList(builder.ToListNode().CreateRed(this, Position)); + } + } +} diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/Syntax.xml b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/Syntax.xml index 5aa4852271..89cc6623e7 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/Syntax.xml +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/Syntax.xml @@ -65,10 +65,6 @@ - - - - @@ -106,13 +102,43 @@ - + - + + + + + + + + + + + + + + + + - + - + + + + + + + + + + + + + + + + diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/SyntaxListBuilderOfT.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/SyntaxListBuilderOfT.cs index 8fcd9f2b3a..1c9e4435a9 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/SyntaxListBuilderOfT.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/SyntaxListBuilderOfT.cs @@ -75,6 +75,13 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax return _builder.ToList(); } + public SyntaxList Consume() + { + var list = ToList(); + Clear(); + return list; + } + public static implicit operator SyntaxListBuilder(SyntaxListBuilder builder) { return builder._builder; diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/SyntaxNodeExtensions.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/SyntaxNodeExtensions.cs index b696e221da..1b916ea75e 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/SyntaxNodeExtensions.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/SyntaxNodeExtensions.cs @@ -220,109 +220,5 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax var content = string.Concat(tokens.Select(t => t.Content)); return content; } - - public static string GetTagName(this MarkupTagBlockSyntax tagBlock) - { - return GetTagNameCore(tagBlock); - } - - public static string GetTagName(this MarkupStartTagSyntax startTag) - { - return GetTagNameCore(startTag); - } - - public static string GetTagName(this MarkupEndTagSyntax endTag) - { - return GetTagNameCore(endTag); - } - - private static string GetTagNameCore(RazorBlockSyntax tagBlock) - { - if (tagBlock == null) - { - throw new ArgumentNullException(nameof(tagBlock)); - } - - MarkupTextLiteralSyntax nameLiteral = null; - var isBangTag = false; - if (tagBlock.Children.Count > 0 && tagBlock.Children[0] is MarkupTextLiteralSyntax firstChild) - { - if (firstChild.GetContent().StartsWith("<") && - tagBlock.Children.Count >= 3 && - tagBlock.Children[1] is RazorMetaCodeSyntax && - tagBlock.Children[2] is MarkupTextLiteralSyntax potentialBangTagName) - { - isBangTag = true; - nameLiteral = potentialBangTagName; - } - else - { - nameLiteral = firstChild; - } - } - - if (nameLiteral == null) - { - return null; - } - - SyntaxToken textToken = null; - for (var i = 0; i < nameLiteral.LiteralTokens.Count; i++) - { - var token = nameLiteral.LiteralTokens[i]; - - if (token != null && - (token.Kind == SyntaxKind.Whitespace || token.Kind == SyntaxKind.Text)) - { - textToken = token; - break; - } - } - - if (textToken == null) - { - return null; - } - - var name = textToken.Kind == SyntaxKind.Whitespace ? null : textToken.Content; - if (name != null && isBangTag) - { - name = "!" + name; - } - - return name; - } - - public static bool IsSelfClosing(this MarkupTagBlockSyntax tagBlock) - { - return IsSelfClosingCore(tagBlock); - } - - public static bool IsSelfClosing(this MarkupStartTagSyntax startTag) - { - return IsSelfClosingCore(startTag); - } - - private static bool IsSelfClosingCore(RazorBlockSyntax tagBlock) - { - if (tagBlock == null) - { - throw new ArgumentNullException(nameof(tagBlock)); - } - - var lastChild = tagBlock.ChildNodes().LastOrDefault(); - - return lastChild?.GetContent().EndsWith("/>", StringComparison.Ordinal) ?? false; - } - - public static bool IsVoidElement(this MarkupStartTagSyntax startTag) - { - if (startTag == null) - { - throw new ArgumentNullException(nameof(startTag)); - } - - return ParserHelpers.VoidElements.Contains(startTag.GetTagName()); - } } } diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/SyntaxSerializer.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/SyntaxSerializer.cs index a811d79aa2..77052dcd18 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/SyntaxSerializer.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/SyntaxSerializer.cs @@ -151,6 +151,22 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax { WriteTagHelperAttributeInfo(minimizedTagHelperAttribute.TagHelperAttributeInfo); } + else if (node is MarkupStartTagSyntax startTag) + { + if (startTag.IsMarkupTransition) + { + WriteSeparator(); + Write("MarkupTransition"); + } + } + else if (node is MarkupEndTagSyntax endTag) + { + if (endTag.IsMarkupTransition) + { + WriteSeparator(); + Write("MarkupTransition"); + } + } if (ShouldDisplayNodeContent(node)) { diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common/Language/Legacy/SyntaxNodeWriter.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common/Language/Legacy/SyntaxNodeWriter.cs index 0974b6a7b9..33fef39d49 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common/Language/Legacy/SyntaxNodeWriter.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common/Language/Legacy/SyntaxNodeWriter.cs @@ -67,6 +67,22 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax { WriteTagHelperAttributeInfo(minimizedTagHelperAttribute.TagHelperAttributeInfo); } + else if (node is MarkupStartTagSyntax startTag) + { + if (startTag.IsMarkupTransition) + { + WriteSeparator(); + Write("MarkupTransition"); + } + } + else if (node is MarkupEndTagSyntax endTag) + { + if (endTag.IsMarkupTransition) + { + WriteSeparator(); + Write("MarkupTransition"); + } + } if (ShouldDisplayNodeContent(node)) {