Razor parser rewrite (#2590)
* Razor parser rewrite
- Rewrite CSharp parser
- Basic rewrite of HTML parser (More improvements to follow)
- Define and generate syntax nodes and boilerplate
- Rewrite ClassifiedSpan and TagHelperSpan generation logic
- Rewrite TagHelper phase
- Rewrite Intermediate phase
- Rewrite other miscellaneous features and bug fixes
- Rewrite partial parsing
- Added some syntax manipulation APIs
- Removed unused legacy types
* Test changes
- Update parser test infrastructure
- Update tests
- Regenerated baselines
- Removed unused legacy types
This commit is contained in:
parent
61565f61f9
commit
6c8e900d11
|
|
@ -0,0 +1,312 @@
|
||||||
|
// 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 ClassifiedSpanVisitor : SyntaxWalker
|
||||||
|
{
|
||||||
|
private RazorSourceDocument _source;
|
||||||
|
private List<ClassifiedSpanInternal> _spans;
|
||||||
|
private BlockKindInternal _currentBlockKind;
|
||||||
|
private SyntaxNode _currentBlock;
|
||||||
|
|
||||||
|
public ClassifiedSpanVisitor(RazorSourceDocument source)
|
||||||
|
{
|
||||||
|
_source = source;
|
||||||
|
_spans = new List<ClassifiedSpanInternal>();
|
||||||
|
_currentBlockKind = BlockKindInternal.Markup;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<ClassifiedSpanInternal> ClassifiedSpans => _spans;
|
||||||
|
|
||||||
|
public override void VisitRazorCommentBlock(RazorCommentBlockSyntax node)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void 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))
|
||||||
|
{
|
||||||
|
base.VisitCSharpCodeBlock(node);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteBlock(node, BlockKindInternal.Statement, base.VisitCSharpCodeBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitCSharpStatement(CSharpStatementSyntax node)
|
||||||
|
{
|
||||||
|
WriteBlock(node, BlockKindInternal.Statement, base.VisitCSharpStatement);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitCSharpExplicitExpression(CSharpExplicitExpressionSyntax node)
|
||||||
|
{
|
||||||
|
WriteBlock(node, BlockKindInternal.Expression, base.VisitCSharpExplicitExpression);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitCSharpImplicitExpression(CSharpImplicitExpressionSyntax node)
|
||||||
|
{
|
||||||
|
WriteBlock(node, BlockKindInternal.Expression, base.VisitCSharpImplicitExpression);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitRazorDirective(RazorDirectiveSyntax node)
|
||||||
|
{
|
||||||
|
WriteBlock(node, BlockKindInternal.Directive, base.VisitRazorDirective);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitCSharpTemplateBlock(CSharpTemplateBlockSyntax node)
|
||||||
|
{
|
||||||
|
WriteBlock(node, BlockKindInternal.Template, base.VisitCSharpTemplateBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitMarkupBlock(MarkupBlockSyntax node)
|
||||||
|
{
|
||||||
|
WriteBlock(node, BlockKindInternal.Markup, base.VisitMarkupBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitMarkupTagHelperAttributeValue(MarkupTagHelperAttributeValueSyntax node)
|
||||||
|
{
|
||||||
|
// We don't generate a classified span when the attribute value is a simple literal value.
|
||||||
|
// This is done so we maintain the classified spans generated in 2.x which
|
||||||
|
// used ConditionalAttributeCollapser (combines markup literal attribute values into one span with no block parent).
|
||||||
|
if (node.Children.Count > 1 ||
|
||||||
|
(node.Children.Count == 1 && node.Children[0] is MarkupDynamicAttributeValueSyntax))
|
||||||
|
{
|
||||||
|
WriteBlock(node, BlockKindInternal.Markup, base.VisitMarkupTagHelperAttributeValue);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
base.VisitMarkupTagHelperAttributeValue(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitMarkupTagBlock(MarkupTagBlockSyntax node)
|
||||||
|
{
|
||||||
|
WriteBlock(node, BlockKindInternal.Tag, base.VisitMarkupTagBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitMarkupTagHelperElement(MarkupTagHelperElementSyntax node)
|
||||||
|
{
|
||||||
|
WriteBlock(node, BlockKindInternal.Tag, base.VisitMarkupTagHelperElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitMarkupTagHelperStartTag(MarkupTagHelperStartTagSyntax node)
|
||||||
|
{
|
||||||
|
foreach (var child in node.Children)
|
||||||
|
{
|
||||||
|
if (child is MarkupTagHelperAttributeSyntax attribute)
|
||||||
|
{
|
||||||
|
Visit(attribute);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitMarkupTagHelperEndTag(MarkupTagHelperEndTagSyntax node)
|
||||||
|
{
|
||||||
|
// We don't want to generate a classified span for a tag helper end tag. Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitMarkupAttributeBlock(MarkupAttributeBlockSyntax node)
|
||||||
|
{
|
||||||
|
WriteBlock(node, BlockKindInternal.Markup, n =>
|
||||||
|
{
|
||||||
|
var equalsSyntax = SyntaxFactory.MarkupTextLiteral(new SyntaxList<SyntaxToken>(node.EqualsToken));
|
||||||
|
var mergedAttributePrefix = MergeTextLiteralSpans(node.NamePrefix, node.Name, node.NameSuffix, equalsSyntax, node.ValuePrefix);
|
||||||
|
Visit(mergedAttributePrefix);
|
||||||
|
Visit(node.Value);
|
||||||
|
Visit(node.ValueSuffix);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitMarkupTagHelperAttribute(MarkupTagHelperAttributeSyntax node)
|
||||||
|
{
|
||||||
|
Visit(node.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitMarkupMinimizedAttributeBlock(MarkupMinimizedAttributeBlockSyntax node)
|
||||||
|
{
|
||||||
|
WriteBlock(node, BlockKindInternal.Markup, n =>
|
||||||
|
{
|
||||||
|
var mergedAttributePrefix = MergeTextLiteralSpans(node.NamePrefix, node.Name);
|
||||||
|
Visit(mergedAttributePrefix);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitMarkupCommentBlock(MarkupCommentBlockSyntax node)
|
||||||
|
{
|
||||||
|
WriteBlock(node, BlockKindInternal.HtmlComment, base.VisitMarkupCommentBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitMarkupDynamicAttributeValue(MarkupDynamicAttributeValueSyntax node)
|
||||||
|
{
|
||||||
|
WriteBlock(node, BlockKindInternal.Markup, base.VisitMarkupDynamicAttributeValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitRazorMetaCode(RazorMetaCodeSyntax node)
|
||||||
|
{
|
||||||
|
WriteSpan(node, SpanKindInternal.MetaCode);
|
||||||
|
base.VisitRazorMetaCode(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitCSharpTransition(CSharpTransitionSyntax node)
|
||||||
|
{
|
||||||
|
WriteSpan(node, SpanKindInternal.Transition);
|
||||||
|
base.VisitCSharpTransition(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitMarkupTransition(MarkupTransitionSyntax node)
|
||||||
|
{
|
||||||
|
WriteSpan(node, SpanKindInternal.Transition);
|
||||||
|
base.VisitMarkupTransition(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitCSharpStatementLiteral(CSharpStatementLiteralSyntax node)
|
||||||
|
{
|
||||||
|
WriteSpan(node, SpanKindInternal.Code);
|
||||||
|
base.VisitCSharpStatementLiteral(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitCSharpExpressionLiteral(CSharpExpressionLiteralSyntax node)
|
||||||
|
{
|
||||||
|
WriteSpan(node, SpanKindInternal.Code);
|
||||||
|
base.VisitCSharpExpressionLiteral(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitCSharpEphemeralTextLiteral(CSharpEphemeralTextLiteralSyntax node)
|
||||||
|
{
|
||||||
|
WriteSpan(node, SpanKindInternal.Code);
|
||||||
|
base.VisitCSharpEphemeralTextLiteral(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitUnclassifiedTextLiteral(UnclassifiedTextLiteralSyntax node)
|
||||||
|
{
|
||||||
|
WriteSpan(node, SpanKindInternal.None);
|
||||||
|
base.VisitUnclassifiedTextLiteral(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitMarkupLiteralAttributeValue(MarkupLiteralAttributeValueSyntax node)
|
||||||
|
{
|
||||||
|
WriteSpan(node, SpanKindInternal.Markup);
|
||||||
|
base.VisitMarkupLiteralAttributeValue(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitMarkupTextLiteral(MarkupTextLiteralSyntax node)
|
||||||
|
{
|
||||||
|
if (node.Parent is MarkupLiteralAttributeValueSyntax)
|
||||||
|
{
|
||||||
|
base.VisitMarkupTextLiteral(node);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteSpan(node, SpanKindInternal.Markup);
|
||||||
|
base.VisitMarkupTextLiteral(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitMarkupEphemeralTextLiteral(MarkupEphemeralTextLiteralSyntax node)
|
||||||
|
{
|
||||||
|
WriteSpan(node, SpanKindInternal.Markup);
|
||||||
|
base.VisitMarkupEphemeralTextLiteral(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteBlock<TNode>(TNode node, BlockKindInternal kind, Action<TNode> handler) where TNode : SyntaxNode
|
||||||
|
{
|
||||||
|
var previousBlock = _currentBlock;
|
||||||
|
var previousKind = _currentBlockKind;
|
||||||
|
|
||||||
|
_currentBlock = node;
|
||||||
|
_currentBlockKind = kind;
|
||||||
|
|
||||||
|
handler(node);
|
||||||
|
|
||||||
|
_currentBlock = previousBlock;
|
||||||
|
_currentBlockKind = previousKind;
|
||||||
|
}
|
||||||
|
|
||||||
|
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<Syntax.InternalSyntax.SyntaxToken>());
|
||||||
|
|
||||||
|
return (MarkupTextLiteralSyntax)mergedLiteralSyntax.CreateRed(parent, position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.AspNetCore.Razor.Language.Extensions;
|
using Microsoft.AspNetCore.Razor.Language.Extensions;
|
||||||
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||||
|
using Microsoft.AspNetCore.Razor.Language.Syntax;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language
|
namespace Microsoft.AspNetCore.Razor.Language
|
||||||
{
|
{
|
||||||
|
|
@ -23,37 +24,42 @@ namespace Microsoft.AspNetCore.Razor.Language
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(syntaxTree));
|
throw new ArgumentNullException(nameof(syntaxTree));
|
||||||
}
|
}
|
||||||
|
|
||||||
var sectionVerifier = new NestedSectionVerifier();
|
var sectionVerifier = new NestedSectionVerifier(syntaxTree);
|
||||||
sectionVerifier.Verify(syntaxTree);
|
return sectionVerifier.Verify();
|
||||||
|
|
||||||
return syntaxTree;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class NestedSectionVerifier : ParserVisitor
|
private class NestedSectionVerifier : SyntaxRewriter
|
||||||
{
|
{
|
||||||
private int _nestedLevel;
|
private int _nestedLevel;
|
||||||
|
private RazorSyntaxTree _syntaxTree;
|
||||||
|
|
||||||
public void Verify(RazorSyntaxTree tree)
|
public NestedSectionVerifier(RazorSyntaxTree syntaxTree)
|
||||||
{
|
{
|
||||||
tree.Root.Accept(this);
|
_syntaxTree = syntaxTree;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void VisitDirectiveBlock(DirectiveChunkGenerator chunkGenerator, Block block)
|
public RazorSyntaxTree Verify()
|
||||||
|
{
|
||||||
|
var root = Visit(_syntaxTree.Root);
|
||||||
|
var rewrittenTree = new DefaultRazorSyntaxTree(root, _syntaxTree.Source, _syntaxTree.Diagnostics, _syntaxTree.Options);
|
||||||
|
return rewrittenTree;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override SyntaxNode VisitRazorDirective(RazorDirectiveSyntax node)
|
||||||
{
|
{
|
||||||
if (_nestedLevel > 0)
|
if (_nestedLevel > 0)
|
||||||
{
|
{
|
||||||
var directiveStart = block.Children.First(child => !child.IsBlock && ((Span)child).Kind == SpanKindInternal.Transition).Start;
|
var directiveStart = node.Transition.GetSourceLocation(_syntaxTree.Source);
|
||||||
var errorLength = /* @ */ 1 + SectionDirective.Directive.Directive.Length;
|
var errorLength = /* @ */ 1 + SectionDirective.Directive.Directive.Length;
|
||||||
var error = RazorDiagnosticFactory.CreateParsing_SectionsCannotBeNested(new SourceSpan(directiveStart, errorLength));
|
var error = RazorDiagnosticFactory.CreateParsing_SectionsCannotBeNested(new SourceSpan(directiveStart, errorLength));
|
||||||
chunkGenerator.Diagnostics.Add(error);
|
node = node.AppendDiagnostic(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
_nestedLevel++;
|
_nestedLevel++;
|
||||||
|
var result = base.VisitRazorDirective(node);
|
||||||
VisitDefault(block);
|
|
||||||
|
|
||||||
_nestedLevel--;
|
_nestedLevel--;
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,15 +1,17 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// 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.Collections.Generic;
|
||||||
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||||
|
using Microsoft.AspNetCore.Razor.Language.Syntax;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language
|
namespace Microsoft.AspNetCore.Razor.Language
|
||||||
{
|
{
|
||||||
internal class DefaultRazorSyntaxTree : RazorSyntaxTree
|
internal class DefaultRazorSyntaxTree : RazorSyntaxTree
|
||||||
{
|
{
|
||||||
public DefaultRazorSyntaxTree(
|
public DefaultRazorSyntaxTree(
|
||||||
Block root,
|
SyntaxNode root,
|
||||||
RazorSourceDocument source,
|
RazorSourceDocument source,
|
||||||
IReadOnlyList<RazorDiagnostic> diagnostics,
|
IReadOnlyList<RazorDiagnostic> diagnostics,
|
||||||
RazorParserOptions options)
|
RazorParserOptions options)
|
||||||
|
|
@ -24,7 +26,7 @@ namespace Microsoft.AspNetCore.Razor.Language
|
||||||
|
|
||||||
public override RazorParserOptions Options { get; }
|
public override RazorParserOptions Options { get; }
|
||||||
|
|
||||||
internal override Block Root { get; }
|
internal override SyntaxNode Root { get; }
|
||||||
|
|
||||||
public override RazorSourceDocument Source { get; }
|
public override RazorSourceDocument Source { get; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||||
|
using Microsoft.AspNetCore.Razor.Language.Syntax;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language
|
namespace Microsoft.AspNetCore.Razor.Language
|
||||||
{
|
{
|
||||||
|
|
@ -39,11 +40,11 @@ namespace Microsoft.AspNetCore.Razor.Language
|
||||||
for (var i = 0; i < imports.Count; i++)
|
for (var i = 0; i < imports.Count; i++)
|
||||||
{
|
{
|
||||||
var import = imports[i];
|
var import = imports[i];
|
||||||
visitor.VisitBlock(import.Root);
|
visitor.Visit(import.Root);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
visitor.VisitBlock(syntaxTree.Root);
|
visitor.Visit(syntaxTree.Root);
|
||||||
|
|
||||||
var tagHelperPrefix = visitor.TagHelperPrefix;
|
var tagHelperPrefix = visitor.TagHelperPrefix;
|
||||||
descriptors = visitor.Matches.ToArray();
|
descriptors = visitor.Matches.ToArray();
|
||||||
|
|
@ -57,21 +58,9 @@ namespace Microsoft.AspNetCore.Razor.Language
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var errorSink = new ErrorSink();
|
var rewrittenSyntaxTree = TagHelperParseTreeRewriter.Rewrite(syntaxTree, tagHelperPrefix, descriptors);
|
||||||
var rewriter = new TagHelperParseTreeRewriter(tagHelperPrefix, descriptors, syntaxTree.Options.FeatureFlags);
|
|
||||||
|
codeDocument.SetSyntaxTree(rewrittenSyntaxTree);
|
||||||
var root = syntaxTree.Root;
|
|
||||||
root = rewriter.Rewrite(root, errorSink);
|
|
||||||
|
|
||||||
var errorList = new List<RazorDiagnostic>();
|
|
||||||
errorList.AddRange(errorSink.Errors);
|
|
||||||
|
|
||||||
errorList.AddRange(descriptors.SelectMany(d => d.GetAllDiagnostics()));
|
|
||||||
|
|
||||||
var diagnostics = CombineErrors(syntaxTree.Diagnostics, errorList);
|
|
||||||
|
|
||||||
var newSyntaxTree = RazorSyntaxTree.Create(root, syntaxTree.Source, diagnostics, syntaxTree.Options);
|
|
||||||
codeDocument.SetSyntaxTree(newSyntaxTree);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool MatchesDirective(TagHelperDescriptor descriptor, string typePattern, string assemblyName)
|
private static bool MatchesDirective(TagHelperDescriptor descriptor, string typePattern, string assemblyName)
|
||||||
|
|
@ -97,25 +86,7 @@ namespace Microsoft.AspNetCore.Razor.Language
|
||||||
return string.Equals(descriptor.Name, typePattern, StringComparison.Ordinal);
|
return string.Equals(descriptor.Name, typePattern, StringComparison.Ordinal);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int GetErrorLength(string directiveText)
|
internal class DirectiveVisitor : SyntaxRewriter
|
||||||
{
|
|
||||||
var nonNullLength = directiveText == null ? 1 : directiveText.Length;
|
|
||||||
var normalizeEmptyStringLength = Math.Max(nonNullLength, 1);
|
|
||||||
|
|
||||||
return normalizeEmptyStringLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
private IReadOnlyList<RazorDiagnostic> CombineErrors(IReadOnlyList<RazorDiagnostic> errors1, IReadOnlyList<RazorDiagnostic> errors2)
|
|
||||||
{
|
|
||||||
var combinedErrors = new List<RazorDiagnostic>(errors1.Count + errors2.Count);
|
|
||||||
combinedErrors.AddRange(errors1);
|
|
||||||
combinedErrors.AddRange(errors2);
|
|
||||||
|
|
||||||
return combinedErrors;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Internal for testing.
|
|
||||||
internal class DirectiveVisitor : ParserVisitor
|
|
||||||
{
|
{
|
||||||
private IReadOnlyList<TagHelperDescriptor> _tagHelpers;
|
private IReadOnlyList<TagHelperDescriptor> _tagHelpers;
|
||||||
|
|
||||||
|
|
@ -128,62 +99,80 @@ namespace Microsoft.AspNetCore.Razor.Language
|
||||||
|
|
||||||
public HashSet<TagHelperDescriptor> Matches { get; } = new HashSet<TagHelperDescriptor>();
|
public HashSet<TagHelperDescriptor> Matches { get; } = new HashSet<TagHelperDescriptor>();
|
||||||
|
|
||||||
public override void VisitAddTagHelperSpan(AddTagHelperChunkGenerator chunkGenerator, Span span)
|
public override SyntaxNode VisitRazorDirective(RazorDirectiveSyntax node)
|
||||||
{
|
{
|
||||||
if (chunkGenerator.AssemblyName == null)
|
var descendantLiterals = node.DescendantNodes();
|
||||||
|
foreach (var child in descendantLiterals)
|
||||||
{
|
{
|
||||||
// Skip this one, it's an error
|
if (!(child is CSharpStatementLiteralSyntax literal))
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!AssemblyContainsTagHelpers(chunkGenerator.AssemblyName, _tagHelpers))
|
|
||||||
{
|
|
||||||
// No tag helpers in the assembly.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < _tagHelpers.Count; i++)
|
|
||||||
{
|
|
||||||
var tagHelper = _tagHelpers[i];
|
|
||||||
if (MatchesDirective(tagHelper, chunkGenerator.TypePattern, chunkGenerator.AssemblyName))
|
|
||||||
{
|
{
|
||||||
Matches.Add(tagHelper);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public override void VisitRemoveTagHelperSpan(RemoveTagHelperChunkGenerator chunkGenerator, Span span)
|
return base.VisitRazorDirective(node);
|
||||||
{
|
|
||||||
if (chunkGenerator.AssemblyName == null)
|
|
||||||
{
|
|
||||||
// Skip this one, it's an error
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (!AssemblyContainsTagHelpers(chunkGenerator.AssemblyName, _tagHelpers))
|
|
||||||
{
|
|
||||||
// No tag helpers in the assembly.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < _tagHelpers.Count; i++)
|
|
||||||
{
|
|
||||||
var tagHelper = _tagHelpers[i];
|
|
||||||
if (MatchesDirective(tagHelper, chunkGenerator.TypePattern, chunkGenerator.AssemblyName))
|
|
||||||
{
|
|
||||||
Matches.Remove(tagHelper);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void VisitTagHelperPrefixDirectiveSpan(TagHelperPrefixDirectiveChunkGenerator chunkGenerator, Span span)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(chunkGenerator.DirectiveText))
|
|
||||||
{
|
|
||||||
// We only expect to see a single one of these per file, but that's enforced at another level.
|
|
||||||
TagHelperPrefix = chunkGenerator.DirectiveText;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool AssemblyContainsTagHelpers(string assemblyName, IReadOnlyList<TagHelperDescriptor> tagHelpers)
|
private bool AssemblyContainsTagHelpers(string assemblyName, IReadOnlyList<TagHelperDescriptor> tagHelpers)
|
||||||
|
|
|
||||||
|
|
@ -4,17 +4,17 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||||
using Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax;
|
using Microsoft.AspNetCore.Razor.Language.Syntax;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language
|
namespace Microsoft.AspNetCore.Razor.Language
|
||||||
{
|
{
|
||||||
internal class DirectiveTokenEditHandler : SpanEditHandler
|
internal class DirectiveTokenEditHandler : SpanEditHandler
|
||||||
{
|
{
|
||||||
public DirectiveTokenEditHandler(Func<string, IEnumerable<SyntaxToken>> tokenizer) : base(tokenizer)
|
public DirectiveTokenEditHandler(Func<string, IEnumerable<Syntax.InternalSyntax.SyntaxToken>> tokenizer) : base(tokenizer)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override PartialParseResultInternal CanAcceptChange(Span target, SourceChange change)
|
protected override PartialParseResultInternal CanAcceptChange(SyntaxNode target, SourceChange change)
|
||||||
{
|
{
|
||||||
if (AcceptedCharacters == AcceptedCharactersInternal.NonWhitespace)
|
if (AcceptedCharacters == AcceptedCharactersInternal.NonWhitespace)
|
||||||
{
|
{
|
||||||
|
|
@ -31,7 +31,6 @@ namespace Microsoft.AspNetCore.Razor.Language
|
||||||
}
|
}
|
||||||
|
|
||||||
return PartialParseResultInternal.Rejected;
|
return PartialParseResultInternal.Rejected;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool ContainsWhitespace(string content)
|
private static bool ContainsWhitespace(string content)
|
||||||
|
|
|
||||||
|
|
@ -22,14 +22,11 @@ namespace Microsoft.AspNetCore.Razor.Language
|
||||||
throw new ArgumentNullException(nameof(syntaxTree));
|
throw new ArgumentNullException(nameof(syntaxTree));
|
||||||
}
|
}
|
||||||
|
|
||||||
var conditionalAttributeCollapser = new ConditionalAttributeCollapser();
|
var whitespaceRewriter = new WhitespaceRewriter();
|
||||||
var rewritten = conditionalAttributeCollapser.Rewrite(syntaxTree.Root);
|
var rewritten = whitespaceRewriter.Visit(syntaxTree.Root);
|
||||||
|
|
||||||
var whitespaceRewriter = new WhiteSpaceRewriter();
|
|
||||||
rewritten = whitespaceRewriter.Rewrite(rewritten);
|
|
||||||
|
|
||||||
var rewrittenSyntaxTree = RazorSyntaxTree.Create(rewritten, syntaxTree.Source, syntaxTree.Diagnostics, syntaxTree.Options);
|
var rewrittenSyntaxTree = RazorSyntaxTree.Create(rewritten, syntaxTree.Source, syntaxTree.Diagnostics, syntaxTree.Options);
|
||||||
return rewrittenSyntaxTree;
|
return rewrittenSyntaxTree;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -14,23 +14,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
|
|
||||||
public string Namespace { get; }
|
public string Namespace { get; }
|
||||||
|
|
||||||
public override void Accept(ParserVisitor visitor, Span span)
|
|
||||||
{
|
|
||||||
visitor.VisitImportSpan(this, span);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void GenerateChunk(Span target, ChunkGeneratorContext context)
|
|
||||||
{
|
|
||||||
var ns = Namespace;
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(ns) && char.IsWhiteSpace(ns[0]))
|
|
||||||
{
|
|
||||||
ns = ns.Substring(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
//context.ChunkTreeBuilder.AddUsingChunk(ns, target);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return "Import:" + Namespace + ";";
|
return "Import:" + Namespace + ";";
|
||||||
|
|
|
||||||
|
|
@ -35,11 +35,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
|
|
||||||
public List<RazorDiagnostic> Diagnostics { get; }
|
public List<RazorDiagnostic> Diagnostics { get; }
|
||||||
|
|
||||||
public override void Accept(ParserVisitor visitor, Span span)
|
|
||||||
{
|
|
||||||
visitor.VisitAddTagHelperSpan(this, span);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override bool Equals(object obj)
|
public override bool Equals(object obj)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,68 +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;
|
|
||||||
using System.Globalization;
|
|
||||||
using Microsoft.Extensions.Internal;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|
||||||
{
|
|
||||||
internal class AttributeBlockChunkGenerator : ParentChunkGenerator
|
|
||||||
{
|
|
||||||
public AttributeBlockChunkGenerator(string name, LocationTagged<string> prefix, LocationTagged<string> suffix)
|
|
||||||
{
|
|
||||||
Name = name;
|
|
||||||
Prefix = prefix;
|
|
||||||
Suffix = suffix;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Name { get; }
|
|
||||||
|
|
||||||
public LocationTagged<string> Prefix { get; }
|
|
||||||
|
|
||||||
public LocationTagged<string> Suffix { get; }
|
|
||||||
|
|
||||||
public override void GenerateStartParentChunk(Block target, ChunkGeneratorContext context)
|
|
||||||
{
|
|
||||||
//var chunk = context.ChunkTreeBuilder.StartParentChunk<CodeAttributeChunk>(target);
|
|
||||||
|
|
||||||
//chunk.Attribute = Name;
|
|
||||||
//chunk.Prefix = Prefix;
|
|
||||||
//chunk.Suffix = Suffix;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void GenerateEndParentChunk(Block target, ChunkGeneratorContext context)
|
|
||||||
{
|
|
||||||
//context.ChunkTreeBuilder.EndParentChunk();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Accept(ParserVisitor visitor, Block block)
|
|
||||||
{
|
|
||||||
visitor.VisitAttributeBlock(this, block);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return string.Format(CultureInfo.CurrentCulture, "Attr:{0},{1:F},{2:F}", Name, Prefix, Suffix);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool Equals(object obj)
|
|
||||||
{
|
|
||||||
var other = obj as AttributeBlockChunkGenerator;
|
|
||||||
return other != null &&
|
|
||||||
string.Equals(other.Name, Name, StringComparison.Ordinal) &&
|
|
||||||
Equals(other.Prefix, Prefix) &&
|
|
||||||
Equals(other.Suffix, Suffix);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
var hashCodeCombiner = HashCodeCombiner.Start();
|
|
||||||
hashCodeCombiner.Add(Name, StringComparer.Ordinal);
|
|
||||||
hashCodeCombiner.Add(Prefix);
|
|
||||||
hashCodeCombiner.Add(Suffix);
|
|
||||||
|
|
||||||
return hashCodeCombiner;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax;
|
using Microsoft.AspNetCore.Razor.Language.Syntax;
|
||||||
using Microsoft.Extensions.Internal;
|
using Microsoft.Extensions.Internal;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
|
|
@ -12,18 +12,18 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
{
|
{
|
||||||
private static readonly int TypeHashCode = typeof(AutoCompleteEditHandler).GetHashCode();
|
private static readonly int TypeHashCode = typeof(AutoCompleteEditHandler).GetHashCode();
|
||||||
|
|
||||||
public AutoCompleteEditHandler(Func<string, IEnumerable<SyntaxToken>> tokenizer)
|
public AutoCompleteEditHandler(Func<string, IEnumerable<Syntax.InternalSyntax.SyntaxToken>> tokenizer)
|
||||||
: base(tokenizer)
|
: base(tokenizer)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public AutoCompleteEditHandler(Func<string, IEnumerable<SyntaxToken>> tokenizer, bool autoCompleteAtEndOfSpan)
|
public AutoCompleteEditHandler(Func<string, IEnumerable<Syntax.InternalSyntax.SyntaxToken>> tokenizer, bool autoCompleteAtEndOfSpan)
|
||||||
: this(tokenizer)
|
: this(tokenizer)
|
||||||
{
|
{
|
||||||
AutoCompleteAtEndOfSpan = autoCompleteAtEndOfSpan;
|
AutoCompleteAtEndOfSpan = autoCompleteAtEndOfSpan;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AutoCompleteEditHandler(Func<string, IEnumerable<SyntaxToken>> tokenizer, AcceptedCharactersInternal accepted)
|
public AutoCompleteEditHandler(Func<string, IEnumerable<Syntax.InternalSyntax.SyntaxToken>> tokenizer, AcceptedCharactersInternal accepted)
|
||||||
: base(tokenizer, accepted)
|
: base(tokenizer, accepted)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
|
|
||||||
public string AutoCompleteString { get; set; }
|
public string AutoCompleteString { get; set; }
|
||||||
|
|
||||||
protected override PartialParseResultInternal CanAcceptChange(Span target, SourceChange change)
|
protected override PartialParseResultInternal CanAcceptChange(SyntaxNode target, SourceChange change)
|
||||||
{
|
{
|
||||||
if (((AutoCompleteAtEndOfSpan && IsAtEndOfSpan(target, change)) || IsAtEndOfFirstLine(target, change)) &&
|
if (((AutoCompleteAtEndOfSpan && IsAtEndOfSpan(target, change)) || IsAtEndOfFirstLine(target, change)) &&
|
||||||
change.IsInsert &&
|
change.IsInsert &&
|
||||||
|
|
|
||||||
|
|
@ -1,288 +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;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
|
||||||
using Microsoft.Extensions.Internal;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|
||||||
{
|
|
||||||
internal class Block : SyntaxTreeNode
|
|
||||||
{
|
|
||||||
private int? _length;
|
|
||||||
|
|
||||||
public Block(BlockBuilder source)
|
|
||||||
: this(source.Type, source.Children, source.ChunkGenerator)
|
|
||||||
{
|
|
||||||
source.Reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Block(BlockKindInternal? type, IReadOnlyList<SyntaxTreeNode> children, IParentChunkGenerator generator)
|
|
||||||
{
|
|
||||||
if (type == null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(Resources.Block_Type_Not_Specified);
|
|
||||||
}
|
|
||||||
|
|
||||||
Type = type.Value;
|
|
||||||
Children = children;
|
|
||||||
ChunkGenerator = generator;
|
|
||||||
|
|
||||||
// Perf: Avoid allocating an enumerator.
|
|
||||||
for (var i = 0; i < Children.Count; i++)
|
|
||||||
{
|
|
||||||
Children[i].Parent = this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public IParentChunkGenerator ChunkGenerator { get; }
|
|
||||||
|
|
||||||
public BlockKindInternal Type { get; }
|
|
||||||
|
|
||||||
public IReadOnlyList<SyntaxTreeNode> Children { get; }
|
|
||||||
|
|
||||||
public override bool IsBlock => true;
|
|
||||||
|
|
||||||
public override SourceLocation Start
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var child = Children.FirstOrDefault();
|
|
||||||
if (child == null)
|
|
||||||
{
|
|
||||||
return SourceLocation.Zero;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return child.Start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int Length
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_length == null)
|
|
||||||
{
|
|
||||||
var length = 0;
|
|
||||||
for (var i = 0; i < Children.Count; i++)
|
|
||||||
{
|
|
||||||
length += Children[i].Length;
|
|
||||||
}
|
|
||||||
|
|
||||||
_length = length;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _length.Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public virtual IEnumerable<Span> Flatten()
|
|
||||||
{
|
|
||||||
// Perf: Avoid allocating an enumerator.
|
|
||||||
for (var i = 0; i < Children.Count; i++)
|
|
||||||
{
|
|
||||||
var element = Children[i];
|
|
||||||
var span = element as Span;
|
|
||||||
if (span != null)
|
|
||||||
{
|
|
||||||
yield return span;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var block = element as Block;
|
|
||||||
foreach (Span childSpan in block.Flatten())
|
|
||||||
{
|
|
||||||
yield return childSpan;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Span FindFirstDescendentSpan()
|
|
||||||
{
|
|
||||||
SyntaxTreeNode current = this;
|
|
||||||
while (current != null && current.IsBlock)
|
|
||||||
{
|
|
||||||
current = ((Block)current).Children.FirstOrDefault();
|
|
||||||
}
|
|
||||||
return current as Span;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Span FindLastDescendentSpan()
|
|
||||||
{
|
|
||||||
SyntaxTreeNode current = this;
|
|
||||||
while (current != null && current.IsBlock)
|
|
||||||
{
|
|
||||||
current = ((Block)current).Children.LastOrDefault();
|
|
||||||
}
|
|
||||||
return current as Span;
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual Span LocateOwner(SourceChange change) => LocateOwner(change, Children);
|
|
||||||
|
|
||||||
protected static Span LocateOwner(SourceChange change, IEnumerable<SyntaxTreeNode> elements)
|
|
||||||
{
|
|
||||||
// Ask each child recursively
|
|
||||||
Span owner = null;
|
|
||||||
foreach (var element in elements)
|
|
||||||
{
|
|
||||||
var span = element as Span;
|
|
||||||
if (span == null)
|
|
||||||
{
|
|
||||||
owner = ((Block)element).LocateOwner(change);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (change.Span.AbsoluteIndex < span.Start.AbsoluteIndex)
|
|
||||||
{
|
|
||||||
// Early escape for cases where changes overlap multiple spans
|
|
||||||
// In those cases, the span will return false, and we don't want to search the whole tree
|
|
||||||
// So if the current span starts after the change, we know we've searched as far as we need to
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
owner = span.EditHandler.OwnsChange(span, change) ? span : owner;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (owner != null)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return owner;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return string.Format(
|
|
||||||
CultureInfo.CurrentCulture,
|
|
||||||
"{0} Block at {1}::{2} (Gen:{3})",
|
|
||||||
Type,
|
|
||||||
Start,
|
|
||||||
Length,
|
|
||||||
ChunkGenerator);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool Equals(object obj)
|
|
||||||
{
|
|
||||||
var other = obj as Block;
|
|
||||||
return other != null &&
|
|
||||||
Type == other.Type &&
|
|
||||||
Equals(ChunkGenerator, other.ChunkGenerator) &&
|
|
||||||
ChildrenEqual(Children, other.Children);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
var hashCodeCombiner = HashCodeCombiner.Start();
|
|
||||||
hashCodeCombiner.Add(Type);
|
|
||||||
hashCodeCombiner.Add(ChunkGenerator);
|
|
||||||
hashCodeCombiner.Add(Children);
|
|
||||||
|
|
||||||
return hashCodeCombiner;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool ChildrenEqual(IEnumerable<SyntaxTreeNode> left, IEnumerable<SyntaxTreeNode> right)
|
|
||||||
{
|
|
||||||
IEnumerator<SyntaxTreeNode> leftEnum = left.GetEnumerator();
|
|
||||||
IEnumerator<SyntaxTreeNode> rightEnum = right.GetEnumerator();
|
|
||||||
while (leftEnum.MoveNext())
|
|
||||||
{
|
|
||||||
if (!rightEnum.MoveNext() || // More items in left than in right
|
|
||||||
!Equals(leftEnum.Current, rightEnum.Current))
|
|
||||||
{
|
|
||||||
// Nodes are not equal
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (rightEnum.MoveNext())
|
|
||||||
{
|
|
||||||
// More items in right than left
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool EquivalentTo(SyntaxTreeNode node)
|
|
||||||
{
|
|
||||||
var other = node as Block;
|
|
||||||
if (other == null || other.Type != Type)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Enumerable.SequenceEqual(Children, other.Children, EquivalenceComparer.Default);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int GetEquivalenceHash()
|
|
||||||
{
|
|
||||||
var hashCodeCombiner = HashCodeCombiner.Start();
|
|
||||||
hashCodeCombiner.Add(Type);
|
|
||||||
foreach (var child in Children)
|
|
||||||
{
|
|
||||||
hashCodeCombiner.Add(child.GetEquivalenceHash());
|
|
||||||
}
|
|
||||||
|
|
||||||
return hashCodeCombiner.CombinedHash;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Accept(ParserVisitor visitor)
|
|
||||||
{
|
|
||||||
visitor.VisitBlock(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override SyntaxTreeNode Clone()
|
|
||||||
{
|
|
||||||
var blockBuilder = new BlockBuilder(this);
|
|
||||||
|
|
||||||
blockBuilder.Children.Clear();
|
|
||||||
for (var i = 0; i < Children.Count; i++)
|
|
||||||
{
|
|
||||||
var clonedChild = Children[i].Clone();
|
|
||||||
blockBuilder.Children.Add(clonedChild);
|
|
||||||
}
|
|
||||||
|
|
||||||
return blockBuilder.Build();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void ChildChanged()
|
|
||||||
{
|
|
||||||
// A node in our graph has changed. We'll need to recompute our length the next time we're asked for it.
|
|
||||||
_length = null;
|
|
||||||
|
|
||||||
Parent?.ChildChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
private class EquivalenceComparer : IEqualityComparer<SyntaxTreeNode>
|
|
||||||
{
|
|
||||||
public static readonly EquivalenceComparer Default = new EquivalenceComparer();
|
|
||||||
|
|
||||||
private EquivalenceComparer()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Equals(SyntaxTreeNode nodeX, SyntaxTreeNode nodeY)
|
|
||||||
{
|
|
||||||
if (nodeX == nodeY)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nodeX != null && nodeX.EquivalentTo(nodeY);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int GetHashCode(SyntaxTreeNode node)
|
|
||||||
{
|
|
||||||
if (node == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(node));
|
|
||||||
}
|
|
||||||
|
|
||||||
return node.GetEquivalenceHash();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,40 +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;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|
||||||
{
|
|
||||||
internal class BlockBuilder
|
|
||||||
{
|
|
||||||
public BlockBuilder()
|
|
||||||
{
|
|
||||||
Reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
public BlockBuilder(Block original)
|
|
||||||
{
|
|
||||||
Type = original.Type;
|
|
||||||
Children = new List<SyntaxTreeNode>(original.Children);
|
|
||||||
ChunkGenerator = original.ChunkGenerator;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IParentChunkGenerator ChunkGenerator { get; set; }
|
|
||||||
|
|
||||||
public BlockKindInternal? Type { get; set; }
|
|
||||||
|
|
||||||
public List<SyntaxTreeNode> Children { get; private set; }
|
|
||||||
|
|
||||||
public virtual Block Build()
|
|
||||||
{
|
|
||||||
return new Block(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void Reset()
|
|
||||||
{
|
|
||||||
Type = null;
|
|
||||||
Children = new List<SyntaxTreeNode>();
|
|
||||||
ChunkGenerator = ParentChunkGenerator.Null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +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.
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|
||||||
{
|
|
||||||
internal static class BlockExtensions
|
|
||||||
{
|
|
||||||
public static void LinkNodes(this Block self)
|
|
||||||
{
|
|
||||||
Span first = null;
|
|
||||||
Span previous = null;
|
|
||||||
foreach (Span span in self.Flatten())
|
|
||||||
{
|
|
||||||
if (first == null)
|
|
||||||
{
|
|
||||||
first = span;
|
|
||||||
}
|
|
||||||
span.Previous = previous;
|
|
||||||
|
|
||||||
if (previous != null)
|
|
||||||
{
|
|
||||||
previous.Next = span;
|
|
||||||
}
|
|
||||||
previous = span;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -114,7 +114,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
|
|
||||||
public override SyntaxToken CreateMarkerToken()
|
public override SyntaxToken CreateMarkerToken()
|
||||||
{
|
{
|
||||||
return SyntaxFactory.Token(SyntaxKind.Unknown, string.Empty);
|
return SyntaxFactory.Token(SyntaxKind.Marker, string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override SyntaxKind GetKnownTokenType(KnownTokenType type)
|
public override SyntaxKind GetKnownTokenType(KnownTokenType type)
|
||||||
|
|
@ -127,7 +127,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
return SyntaxKind.Keyword;
|
return SyntaxKind.Keyword;
|
||||||
case KnownTokenType.NewLine:
|
case KnownTokenType.NewLine:
|
||||||
return SyntaxKind.NewLine;
|
return SyntaxKind.NewLine;
|
||||||
case KnownTokenType.WhiteSpace:
|
case KnownTokenType.Whitespace:
|
||||||
return SyntaxKind.Whitespace;
|
return SyntaxKind.Whitespace;
|
||||||
case KnownTokenType.Transition:
|
case KnownTokenType.Transition:
|
||||||
return SyntaxKind.Transition;
|
return SyntaxKind.Transition;
|
||||||
|
|
@ -138,7 +138,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
case KnownTokenType.CommentBody:
|
case KnownTokenType.CommentBody:
|
||||||
return SyntaxKind.RazorCommentLiteral;
|
return SyntaxKind.RazorCommentLiteral;
|
||||||
default:
|
default:
|
||||||
return SyntaxKind.Unknown;
|
return SyntaxKind.Marker;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -164,7 +164,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
return SyntaxKind.LessThan;
|
return SyntaxKind.LessThan;
|
||||||
default:
|
default:
|
||||||
Debug.Fail("FlipBracket must be called with a bracket character");
|
Debug.Fail("FlipBracket must be called with a bracket character");
|
||||||
return SyntaxKind.Unknown;
|
return SyntaxKind.Marker;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -458,7 +458,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
{
|
{
|
||||||
return handler();
|
return handler();
|
||||||
}
|
}
|
||||||
return SyntaxKind.Unknown;
|
return SyntaxKind.Marker;
|
||||||
}
|
}
|
||||||
|
|
||||||
private SyntaxKind LessThanOperator()
|
private SyntaxKind LessThanOperator()
|
||||||
|
|
|
||||||
|
|
@ -1,25 +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.
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|
||||||
{
|
|
||||||
internal class ChunkGeneratorContext
|
|
||||||
{
|
|
||||||
public ChunkGeneratorContext(
|
|
||||||
string className,
|
|
||||||
string rootNamespace,
|
|
||||||
string sourceFile,
|
|
||||||
bool shouldGenerateLinePragmas)
|
|
||||||
{
|
|
||||||
SourceFile = shouldGenerateLinePragmas ? sourceFile : null;
|
|
||||||
RootNamespace = rootNamespace;
|
|
||||||
ClassName = className;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string SourceFile { get; internal set; }
|
|
||||||
|
|
||||||
public string RootNamespace { get; }
|
|
||||||
|
|
||||||
public string ClassName { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -4,17 +4,17 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax;
|
using Microsoft.AspNetCore.Razor.Language.Syntax;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
{
|
{
|
||||||
internal class CodeBlockEditHandler : SpanEditHandler
|
internal class CodeBlockEditHandler : SpanEditHandler
|
||||||
{
|
{
|
||||||
public CodeBlockEditHandler(Func<string, IEnumerable<SyntaxToken>> tokenizer) : base(tokenizer)
|
public CodeBlockEditHandler(Func<string, IEnumerable<Syntax.InternalSyntax.SyntaxToken>> tokenizer) : base(tokenizer)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override PartialParseResultInternal CanAcceptChange(Span target, SourceChange change)
|
protected override PartialParseResultInternal CanAcceptChange(SyntaxNode target, SourceChange change)
|
||||||
{
|
{
|
||||||
if (IsAcceptableDeletion(target, change))
|
if (IsAcceptableDeletion(target, change))
|
||||||
{
|
{
|
||||||
|
|
@ -35,7 +35,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal for testing
|
// Internal for testing
|
||||||
internal static bool IsAcceptableReplacement(Span target, SourceChange change)
|
internal static bool IsAcceptableReplacement(SyntaxNode target, SourceChange change)
|
||||||
{
|
{
|
||||||
if (!change.IsReplace)
|
if (!change.IsReplace)
|
||||||
{
|
{
|
||||||
|
|
@ -56,7 +56,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal for testing
|
// Internal for testing
|
||||||
internal static bool IsAcceptableDeletion(Span target, SourceChange change)
|
internal static bool IsAcceptableDeletion(SyntaxNode target, SourceChange change)
|
||||||
{
|
{
|
||||||
if (!change.IsDelete)
|
if (!change.IsDelete)
|
||||||
{
|
{
|
||||||
|
|
@ -72,11 +72,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal for testing
|
// Internal for testing
|
||||||
internal static bool ModifiesInvalidContent(Span target, SourceChange change)
|
internal static bool ModifiesInvalidContent(SyntaxNode target, SourceChange change)
|
||||||
{
|
{
|
||||||
var relativePosition = change.Span.AbsoluteIndex - target.Start.AbsoluteIndex;
|
var relativePosition = change.Span.AbsoluteIndex - target.Position;
|
||||||
|
|
||||||
if (target.Content.IndexOfAny(new[] { '{', '}' }, relativePosition, change.Span.Length) >= 0)
|
if (target.GetContent().IndexOfAny(new[] { '{', '}' }, relativePosition, change.Span.Length) >= 0)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,67 +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.Diagnostics;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|
||||||
{
|
|
||||||
internal class ConditionalAttributeCollapser : MarkupRewriter
|
|
||||||
{
|
|
||||||
protected override bool CanRewrite(Block block)
|
|
||||||
{
|
|
||||||
var generator = block.ChunkGenerator as AttributeBlockChunkGenerator;
|
|
||||||
if (generator != null && block.Children.Count > 0)
|
|
||||||
{
|
|
||||||
// Perf: Avoid allocating an enumerator.
|
|
||||||
for (var i = 0; i < block.Children.Count; i++)
|
|
||||||
{
|
|
||||||
if (!IsLiteralAttributeValue(block.Children[i]))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override SyntaxTreeNode RewriteBlock(BlockBuilder parent, Block block)
|
|
||||||
{
|
|
||||||
// Collect the content of this node
|
|
||||||
var builder = new StringBuilder();
|
|
||||||
for (var i = 0; i < block.Children.Count; i++)
|
|
||||||
{
|
|
||||||
var childSpan = (Span)block.Children[i];
|
|
||||||
builder.Append(childSpan.Content);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new span containing this content
|
|
||||||
var span = new SpanBuilder(block.Children[0].Start);
|
|
||||||
|
|
||||||
span.EditHandler = SpanEditHandler.CreateDefault(HtmlLanguageCharacteristics.Instance.TokenizeString);
|
|
||||||
Debug.Assert(block.Children.Count > 0);
|
|
||||||
var start = ((Span)block.Children[0]).Start;
|
|
||||||
FillSpan(span, start, builder.ToString());
|
|
||||||
return span.Build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool IsLiteralAttributeValue(SyntaxTreeNode node)
|
|
||||||
{
|
|
||||||
if (node.IsBlock)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var span = node as Span;
|
|
||||||
Debug.Assert(span != null);
|
|
||||||
|
|
||||||
return span != null &&
|
|
||||||
(span.ChunkGenerator is LiteralAttributeChunkGenerator ||
|
|
||||||
span.ChunkGenerator is MarkupChunkGenerator ||
|
|
||||||
span.ChunkGenerator == SpanChunkGenerator.Null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,82 +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;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using Microsoft.Extensions.Internal;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|
||||||
{
|
|
||||||
internal class DirectiveChunkGenerator : ParentChunkGenerator
|
|
||||||
{
|
|
||||||
private static readonly Type Type = typeof(DirectiveChunkGenerator);
|
|
||||||
private List<RazorDiagnostic> _diagnostics;
|
|
||||||
|
|
||||||
public DirectiveChunkGenerator(DirectiveDescriptor descriptor)
|
|
||||||
{
|
|
||||||
Descriptor = descriptor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DirectiveDescriptor Descriptor { get; }
|
|
||||||
|
|
||||||
public List<RazorDiagnostic> Diagnostics
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_diagnostics == null)
|
|
||||||
{
|
|
||||||
_diagnostics = new List<RazorDiagnostic>();
|
|
||||||
}
|
|
||||||
|
|
||||||
return _diagnostics;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Accept(ParserVisitor visitor, Block block)
|
|
||||||
{
|
|
||||||
visitor.VisitDirectiveBlock(this, block);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool Equals(object obj)
|
|
||||||
{
|
|
||||||
var other = obj as DirectiveChunkGenerator;
|
|
||||||
return base.Equals(other) &&
|
|
||||||
Enumerable.SequenceEqual(Diagnostics, other.Diagnostics) &&
|
|
||||||
DirectiveDescriptorComparer.Default.Equals(Descriptor, other.Descriptor);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
var combiner = HashCodeCombiner.Start();
|
|
||||||
combiner.Add(base.GetHashCode());
|
|
||||||
combiner.Add(Type);
|
|
||||||
|
|
||||||
return combiner.CombinedHash;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
// This is used primarily at test time to show an identifiable representation of the chunk generator.
|
|
||||||
|
|
||||||
var builder = new StringBuilder("Directive:{");
|
|
||||||
builder.Append(Descriptor.Directive);
|
|
||||||
builder.Append(";");
|
|
||||||
builder.Append(Descriptor.Kind);
|
|
||||||
builder.Append(";");
|
|
||||||
builder.Append(Descriptor.Usage);
|
|
||||||
builder.Append("}");
|
|
||||||
|
|
||||||
if (Diagnostics.Count > 0)
|
|
||||||
{
|
|
||||||
builder.Append(" [");
|
|
||||||
var ids = string.Join(", ", Diagnostics.Select(diagnostic => $"{diagnostic.Id}{diagnostic.Span}"));
|
|
||||||
builder.Append(ids);
|
|
||||||
builder.Append("]");
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -18,11 +18,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
|
|
||||||
public DirectiveTokenDescriptor Descriptor { get; }
|
public DirectiveTokenDescriptor Descriptor { get; }
|
||||||
|
|
||||||
public override void Accept(ParserVisitor visitor, Span span)
|
|
||||||
{
|
|
||||||
visitor.VisitDirectiveToken(this, span);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool Equals(object obj)
|
public override bool Equals(object obj)
|
||||||
{
|
{
|
||||||
var other = obj as DirectiveTokenChunkGenerator;
|
var other = obj as DirectiveTokenChunkGenerator;
|
||||||
|
|
|
||||||
|
|
@ -1,60 +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;
|
|
||||||
using System.Globalization;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|
||||||
{
|
|
||||||
internal class DynamicAttributeBlockChunkGenerator : ParentChunkGenerator
|
|
||||||
{
|
|
||||||
public DynamicAttributeBlockChunkGenerator(LocationTagged<string> prefix, int offset, int line, int col)
|
|
||||||
: this(prefix, new SourceLocation(offset, line, col))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public DynamicAttributeBlockChunkGenerator(LocationTagged<string> prefix, SourceLocation valueStart)
|
|
||||||
{
|
|
||||||
Prefix = prefix;
|
|
||||||
ValueStart = valueStart;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LocationTagged<string> Prefix { get; }
|
|
||||||
|
|
||||||
public SourceLocation ValueStart { get; }
|
|
||||||
|
|
||||||
public override void Accept(ParserVisitor visitor, Block block)
|
|
||||||
{
|
|
||||||
visitor.VisitDynamicAttributeBlock(this, block);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void GenerateStartParentChunk(Block target, ChunkGeneratorContext context)
|
|
||||||
{
|
|
||||||
//var chunk = context.ChunkTreeBuilder.StartParentChunk<DynamicCodeAttributeChunk>(target);
|
|
||||||
//chunk.Start = ValueStart;
|
|
||||||
//chunk.Prefix = Prefix;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void GenerateEndParentChunk(Block target, ChunkGeneratorContext context)
|
|
||||||
{
|
|
||||||
//context.ChunkTreeBuilder.EndParentChunk();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return string.Format(CultureInfo.CurrentCulture, "DynAttr:{0:F}", Prefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool Equals(object obj)
|
|
||||||
{
|
|
||||||
var other = obj as DynamicAttributeBlockChunkGenerator;
|
|
||||||
return other != null &&
|
|
||||||
Equals(other.Prefix, Prefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
return Prefix == null ? 0 : Prefix.GetHashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +1,19 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Razor.Language.Syntax;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
{
|
{
|
||||||
internal class EditResult
|
internal class EditResult
|
||||||
{
|
{
|
||||||
public EditResult(PartialParseResultInternal result, SpanBuilder editedSpan)
|
public EditResult(PartialParseResultInternal result, SyntaxNode editedNode)
|
||||||
{
|
{
|
||||||
Result = result;
|
Result = result;
|
||||||
EditedSpan = editedSpan;
|
EditedNode = editedNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PartialParseResultInternal Result { get; set; }
|
public PartialParseResultInternal Result { get; set; }
|
||||||
public SpanBuilder EditedSpan { get; set; }
|
public SyntaxNode EditedNode { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,35 +5,10 @@ using System;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
{
|
{
|
||||||
internal class ExpressionChunkGenerator : ISpanChunkGenerator, IParentChunkGenerator
|
internal class ExpressionChunkGenerator : ISpanChunkGenerator
|
||||||
{
|
{
|
||||||
private static readonly int TypeHashCode = typeof(ExpressionChunkGenerator).GetHashCode();
|
private static readonly int TypeHashCode = typeof(ExpressionChunkGenerator).GetHashCode();
|
||||||
|
|
||||||
public void GenerateStartParentChunk(Block target, ChunkGeneratorContext context)
|
|
||||||
{
|
|
||||||
//context.ChunkTreeBuilder.StartParentChunk<ExpressionBlockChunk>(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void GenerateChunk(Span target, ChunkGeneratorContext context)
|
|
||||||
{
|
|
||||||
//context.ChunkTreeBuilder.AddExpressionChunk(target.Content, target);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void GenerateEndParentChunk(Block target, ChunkGeneratorContext context)
|
|
||||||
{
|
|
||||||
//context.ChunkTreeBuilder.EndParentChunk();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Accept(ParserVisitor visitor, Span span)
|
|
||||||
{
|
|
||||||
visitor.VisitExpressionSpan(this, span);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Accept(ParserVisitor visitor, Block block)
|
|
||||||
{
|
|
||||||
visitor.VisitExpressionBlock(this, block);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return "Expr";
|
return "Expr";
|
||||||
|
|
|
||||||
|
|
@ -86,13 +86,13 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
return SyntaxKind.OpenAngle;
|
return SyntaxKind.OpenAngle;
|
||||||
default:
|
default:
|
||||||
Debug.Fail("FlipBracket must be called with a bracket character");
|
Debug.Fail("FlipBracket must be called with a bracket character");
|
||||||
return SyntaxKind.Unknown;
|
return SyntaxKind.Marker;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override SyntaxToken CreateMarkerToken()
|
public override SyntaxToken CreateMarkerToken()
|
||||||
{
|
{
|
||||||
return SyntaxFactory.Token(SyntaxKind.Unknown, string.Empty);
|
return SyntaxFactory.Token(SyntaxKind.Marker, string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override SyntaxKind GetKnownTokenType(KnownTokenType type)
|
public override SyntaxKind GetKnownTokenType(KnownTokenType type)
|
||||||
|
|
@ -113,10 +113,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
return SyntaxKind.NewLine;
|
return SyntaxKind.NewLine;
|
||||||
case KnownTokenType.Transition:
|
case KnownTokenType.Transition:
|
||||||
return SyntaxKind.Transition;
|
return SyntaxKind.Transition;
|
||||||
case KnownTokenType.WhiteSpace:
|
case KnownTokenType.Whitespace:
|
||||||
return SyntaxKind.Whitespace;
|
return SyntaxKind.Whitespace;
|
||||||
default:
|
default:
|
||||||
return SyntaxKind.Unknown;
|
return SyntaxKind.Marker;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -227,7 +227,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
return EndToken(SyntaxKind.DoubleHyphen);
|
return EndToken(SyntaxKind.DoubleHyphen);
|
||||||
default:
|
default:
|
||||||
Debug.Fail("Unexpected token!");
|
Debug.Fail("Unexpected token!");
|
||||||
return EndToken(SyntaxKind.Unknown);
|
return EndToken(SyntaxKind.Marker);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +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.
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|
||||||
{
|
|
||||||
internal interface IParentChunkGenerator
|
|
||||||
{
|
|
||||||
void GenerateStartParentChunk(Block target, ChunkGeneratorContext context);
|
|
||||||
void GenerateEndParentChunk(Block target, ChunkGeneratorContext context);
|
|
||||||
|
|
||||||
void Accept(ParserVisitor visitor, Block block);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -5,8 +5,5 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
{
|
{
|
||||||
internal interface ISpanChunkGenerator
|
internal interface ISpanChunkGenerator
|
||||||
{
|
{
|
||||||
void GenerateChunk(Span target, ChunkGeneratorContext context);
|
|
||||||
|
|
||||||
void Accept(ParserVisitor visitor, Span span);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
return hashCodeCombiner;
|
return hashCodeCombiner;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override PartialParseResultInternal CanAcceptChange(Span target, SourceChange change)
|
protected override PartialParseResultInternal CanAcceptChange(SyntaxNode target, SourceChange change)
|
||||||
{
|
{
|
||||||
if (AcceptedCharacters == AcceptedCharactersInternal.Any)
|
if (AcceptedCharacters == AcceptedCharactersInternal.Any)
|
||||||
{
|
{
|
||||||
|
|
@ -88,13 +88,13 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
{
|
{
|
||||||
return HandleReplacement(target, change);
|
return HandleReplacement(target, change);
|
||||||
}
|
}
|
||||||
var changeRelativePosition = change.Span.AbsoluteIndex - target.Start.AbsoluteIndex;
|
var changeRelativePosition = change.Span.AbsoluteIndex - target.Position;
|
||||||
|
|
||||||
// Get the edit context
|
// Get the edit context
|
||||||
char? lastChar = null;
|
char? lastChar = null;
|
||||||
if (changeRelativePosition > 0 && target.Content.Length > 0)
|
if (changeRelativePosition > 0 && target.FullWidth > 0)
|
||||||
{
|
{
|
||||||
lastChar = target.Content[changeRelativePosition - 1];
|
lastChar = target.GetContent()[changeRelativePosition - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't support 0->1 length edits
|
// Don't support 0->1 length edits
|
||||||
|
|
@ -129,18 +129,18 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
}
|
}
|
||||||
|
|
||||||
// A dotless commit is the process of inserting a '.' with an intellisense selection.
|
// A dotless commit is the process of inserting a '.' with an intellisense selection.
|
||||||
private static bool IsDotlessCommitInsertion(Span target, SourceChange change)
|
private static bool IsDotlessCommitInsertion(SyntaxNode target, SourceChange change)
|
||||||
{
|
{
|
||||||
return IsNewDotlessCommitInsertion(target, change) || IsSecondaryDotlessCommitInsertion(target, change);
|
return IsNewDotlessCommitInsertion(target, change) || IsSecondaryDotlessCommitInsertion(target, change);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Completing 'DateTime' in intellisense with a '.' could result in: '@DateT' -> '@DateT.' -> '@DateTime.' which is accepted.
|
// Completing 'DateTime' in intellisense with a '.' could result in: '@DateT' -> '@DateT.' -> '@DateTime.' which is accepted.
|
||||||
private static bool IsNewDotlessCommitInsertion(Span target, SourceChange change)
|
private static bool IsNewDotlessCommitInsertion(SyntaxNode target, SourceChange change)
|
||||||
{
|
{
|
||||||
return !IsAtEndOfSpan(target, change) &&
|
return !IsAtEndOfSpan(target, change) &&
|
||||||
change.Span.AbsoluteIndex > 0 &&
|
change.Span.AbsoluteIndex > 0 &&
|
||||||
change.NewText.Length > 0 &&
|
change.NewText.Length > 0 &&
|
||||||
target.Content.Last() == '.' &&
|
target.GetContent().Last() == '.' &&
|
||||||
ParserHelpers.IsIdentifier(change.NewText, requireIdentifierStart: false) &&
|
ParserHelpers.IsIdentifier(change.NewText, requireIdentifierStart: false) &&
|
||||||
(change.Span.Length == 0 || ParserHelpers.IsIdentifier(change.GetOriginalText(target), requireIdentifierStart: false));
|
(change.Span.Length == 0 || ParserHelpers.IsIdentifier(change.GetOriginalText(target), requireIdentifierStart: false));
|
||||||
}
|
}
|
||||||
|
|
@ -148,32 +148,33 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
// Once a dotless commit has been performed you then have something like '@DateTime.'. This scenario is used to detect the
|
// Once a dotless commit has been performed you then have something like '@DateTime.'. This scenario is used to detect the
|
||||||
// situation when you try to perform another dotless commit resulting in a textchange with '..'. Completing 'DateTime.Now'
|
// situation when you try to perform another dotless commit resulting in a textchange with '..'. Completing 'DateTime.Now'
|
||||||
// in intellisense with a '.' could result in: '@DateTime.' -> '@DateTime..' -> '@DateTime.Now.' which is accepted.
|
// in intellisense with a '.' could result in: '@DateTime.' -> '@DateTime..' -> '@DateTime.Now.' which is accepted.
|
||||||
private static bool IsSecondaryDotlessCommitInsertion(Span target, SourceChange change)
|
private static bool IsSecondaryDotlessCommitInsertion(SyntaxNode target, SourceChange change)
|
||||||
{
|
{
|
||||||
// Do not need to worry about other punctuation, just looking for double '.' (after change)
|
// Do not need to worry about other punctuation, just looking for double '.' (after change)
|
||||||
return change.NewText.Length == 1 &&
|
return change.NewText.Length == 1 &&
|
||||||
change.NewText == "." &&
|
change.NewText == "." &&
|
||||||
!string.IsNullOrEmpty(target.Content) &&
|
!string.IsNullOrEmpty(target.GetContent()) &&
|
||||||
target.Content.Last() == '.' &&
|
target.GetContent().Last() == '.' &&
|
||||||
change.Span.Length == 0;
|
change.Span.Length == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsAcceptableReplace(Span target, SourceChange change)
|
private static bool IsAcceptableReplace(SyntaxNode target, SourceChange change)
|
||||||
{
|
{
|
||||||
return IsEndReplace(target, change) ||
|
return IsEndReplace(target, change) ||
|
||||||
(change.IsReplace && RemainingIsWhitespace(target, change));
|
(change.IsReplace && RemainingIsWhitespace(target, change));
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsAcceptableIdentifierReplacement(Span target, SourceChange change)
|
private bool IsAcceptableIdentifierReplacement(SyntaxNode target, SourceChange change)
|
||||||
{
|
{
|
||||||
if (!change.IsReplace)
|
if (!change.IsReplace)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < target.Tokens.Count; i++)
|
var tokens = target.DescendantNodes().Where(n => n.IsToken).Cast<SyntaxToken>().ToArray();
|
||||||
|
for (var i = 0; i < tokens.Length; i++)
|
||||||
{
|
{
|
||||||
var token = target.Tokens[i];
|
var token = tokens[i];
|
||||||
|
|
||||||
if (token == null)
|
if (token == null)
|
||||||
{
|
{
|
||||||
|
|
@ -217,14 +218,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsAcceptableDeletion(Span target, SourceChange change)
|
private static bool IsAcceptableDeletion(SyntaxNode target, SourceChange change)
|
||||||
{
|
{
|
||||||
return IsEndDeletion(target, change) ||
|
return IsEndDeletion(target, change) ||
|
||||||
(change.IsDelete && RemainingIsWhitespace(target, change));
|
(change.IsDelete && RemainingIsWhitespace(target, change));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Acceptable insertions can occur at the end of a span or when a '.' is inserted within a span.
|
// Acceptable insertions can occur at the end of a span or when a '.' is inserted within a span.
|
||||||
private static bool IsAcceptableInsertion(Span target, SourceChange change)
|
private static bool IsAcceptableInsertion(SyntaxNode target, SourceChange change)
|
||||||
{
|
{
|
||||||
return change.IsInsert &&
|
return change.IsInsert &&
|
||||||
(IsAcceptableEndInsertion(target, change) ||
|
(IsAcceptableEndInsertion(target, change) ||
|
||||||
|
|
@ -232,7 +233,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal for testing
|
// Internal for testing
|
||||||
internal static bool IsAcceptableDeletionInBalancedParenthesis(Span target, SourceChange change)
|
internal static bool IsAcceptableDeletionInBalancedParenthesis(SyntaxNode target, SourceChange change)
|
||||||
{
|
{
|
||||||
if (!change.IsDelete)
|
if (!change.IsDelete)
|
||||||
{
|
{
|
||||||
|
|
@ -242,14 +243,15 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
var changeStart = change.Span.AbsoluteIndex;
|
var changeStart = change.Span.AbsoluteIndex;
|
||||||
var changeLength = change.Span.Length;
|
var changeLength = change.Span.Length;
|
||||||
var changeEnd = changeStart + changeLength;
|
var changeEnd = changeStart + changeLength;
|
||||||
if (!IsInsideParenthesis(changeStart, target.Tokens) || !IsInsideParenthesis(changeEnd, target.Tokens))
|
var tokens = target.DescendantNodes().Where(n => n.IsToken).Cast<SyntaxToken>().ToArray();
|
||||||
|
if (!IsInsideParenthesis(changeStart, tokens) || !IsInsideParenthesis(changeEnd, tokens))
|
||||||
{
|
{
|
||||||
// Either the start or end of the delete does not fall inside of parenthesis, unacceptable inner deletion.
|
// Either the start or end of the delete does not fall inside of parenthesis, unacceptable inner deletion.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var relativePosition = changeStart - target.Start.AbsoluteIndex;
|
var relativePosition = changeStart - target.Position;
|
||||||
var deletionContent = target.Content.Substring(relativePosition, changeLength);
|
var deletionContent = target.GetContent().Substring(relativePosition, changeLength);
|
||||||
|
|
||||||
if (deletionContent.IndexOfAny(new[] { '(', ')' }) >= 0)
|
if (deletionContent.IndexOfAny(new[] { '(', ')' }) >= 0)
|
||||||
{
|
{
|
||||||
|
|
@ -261,7 +263,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal for testing
|
// Internal for testing
|
||||||
internal static bool IsAcceptableInsertionInBalancedParenthesis(Span target, SourceChange change)
|
internal static bool IsAcceptableInsertionInBalancedParenthesis(SyntaxNode target, SourceChange change)
|
||||||
{
|
{
|
||||||
if (!change.IsInsert)
|
if (!change.IsInsert)
|
||||||
{
|
{
|
||||||
|
|
@ -274,7 +276,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsInsideParenthesis(change.Span.AbsoluteIndex, target.Tokens))
|
var tokens = target.DescendantNodes().Where(n => n.IsToken).Cast<SyntaxToken>().ToArray();
|
||||||
|
if (IsInsideParenthesis(change.Span.AbsoluteIndex, tokens))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -418,7 +421,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
}
|
}
|
||||||
|
|
||||||
// Accepts character insertions at the end of spans. AKA: '@foo' -> '@fooo' or '@foo' -> '@foo ' etc.
|
// Accepts character insertions at the end of spans. AKA: '@foo' -> '@fooo' or '@foo' -> '@foo ' etc.
|
||||||
private static bool IsAcceptableEndInsertion(Span target, SourceChange change)
|
private static bool IsAcceptableEndInsertion(SyntaxNode target, SourceChange change)
|
||||||
{
|
{
|
||||||
Debug.Assert(change.IsInsert);
|
Debug.Assert(change.IsInsert);
|
||||||
|
|
||||||
|
|
@ -428,7 +431,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
|
|
||||||
// Accepts '.' insertions in the middle of spans. Ex: '@foo.baz.bar' -> '@foo..baz.bar'
|
// Accepts '.' insertions in the middle of spans. Ex: '@foo.baz.bar' -> '@foo..baz.bar'
|
||||||
// This is meant to allow intellisense when editing a span.
|
// This is meant to allow intellisense when editing a span.
|
||||||
private static bool IsAcceptableInnerInsertion(Span target, SourceChange change)
|
private static bool IsAcceptableInnerInsertion(SyntaxNode target, SourceChange change)
|
||||||
{
|
{
|
||||||
Debug.Assert(change.IsInsert);
|
Debug.Assert(change.IsInsert);
|
||||||
|
|
||||||
|
|
@ -440,23 +443,23 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
change.NewText == ".";
|
change.NewText == ".";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool RemainingIsWhitespace(Span target, SourceChange change)
|
private static bool RemainingIsWhitespace(SyntaxNode target, SourceChange change)
|
||||||
{
|
{
|
||||||
var offset = (change.Span.AbsoluteIndex - target.Start.AbsoluteIndex) + change.Span.Length;
|
var offset = (change.Span.AbsoluteIndex - target.Position) + change.Span.Length;
|
||||||
return string.IsNullOrWhiteSpace(target.Content.Substring(offset));
|
return string.IsNullOrWhiteSpace(target.GetContent().Substring(offset));
|
||||||
}
|
}
|
||||||
|
|
||||||
private PartialParseResultInternal HandleDotlessCommitInsertion(Span target)
|
private PartialParseResultInternal HandleDotlessCommitInsertion(SyntaxNode target)
|
||||||
{
|
{
|
||||||
var result = PartialParseResultInternal.Accepted;
|
var result = PartialParseResultInternal.Accepted;
|
||||||
if (!AcceptTrailingDot && target.Content.LastOrDefault() == '.')
|
if (!AcceptTrailingDot && target.GetContent().LastOrDefault() == '.')
|
||||||
{
|
{
|
||||||
result |= PartialParseResultInternal.Provisional;
|
result |= PartialParseResultInternal.Provisional;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private PartialParseResultInternal HandleReplacement(Span target, SourceChange change)
|
private PartialParseResultInternal HandleReplacement(SyntaxNode target, SourceChange change)
|
||||||
{
|
{
|
||||||
// Special Case for IntelliSense commits.
|
// Special Case for IntelliSense commits.
|
||||||
// When IntelliSense commits, we get two changes (for example user typed "Date", then committed "DateTime" by pressing ".")
|
// When IntelliSense commits, we get two changes (for example user typed "Date", then committed "DateTime" by pressing ".")
|
||||||
|
|
@ -477,7 +480,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private PartialParseResultInternal HandleDeletion(Span target, char previousChar, SourceChange change)
|
private PartialParseResultInternal HandleDeletion(SyntaxNode target, char previousChar, SourceChange change)
|
||||||
{
|
{
|
||||||
// What's left after deleting?
|
// What's left after deleting?
|
||||||
if (previousChar == '.')
|
if (previousChar == '.')
|
||||||
|
|
@ -490,8 +493,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
}
|
}
|
||||||
else if (previousChar == '(')
|
else if (previousChar == '(')
|
||||||
{
|
{
|
||||||
var changeRelativePosition = change.Span.AbsoluteIndex - target.Start.AbsoluteIndex;
|
var changeRelativePosition = change.Span.AbsoluteIndex - target.Position;
|
||||||
if (target.Content[changeRelativePosition] == ')')
|
if (target.GetContent()[changeRelativePosition] == ')')
|
||||||
{
|
{
|
||||||
return PartialParseResultInternal.Accepted | PartialParseResultInternal.Provisional;
|
return PartialParseResultInternal.Accepted | PartialParseResultInternal.Provisional;
|
||||||
}
|
}
|
||||||
|
|
@ -500,7 +503,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
return PartialParseResultInternal.Rejected;
|
return PartialParseResultInternal.Rejected;
|
||||||
}
|
}
|
||||||
|
|
||||||
private PartialParseResultInternal HandleInsertion(Span target, char previousChar, SourceChange change)
|
private PartialParseResultInternal HandleInsertion(SyntaxNode target, char previousChar, SourceChange change)
|
||||||
{
|
{
|
||||||
// What are we inserting after?
|
// What are we inserting after?
|
||||||
if (previousChar == '.')
|
if (previousChar == '.')
|
||||||
|
|
@ -521,7 +524,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private PartialParseResultInternal HandleInsertionAfterIdPart(Span target, SourceChange change)
|
private PartialParseResultInternal HandleInsertionAfterIdPart(SyntaxNode target, SourceChange change)
|
||||||
{
|
{
|
||||||
// If the insertion is a full identifier part, accept it
|
// If the insertion is a full identifier part, accept it
|
||||||
if (ParserHelpers.IsIdentifier(change.NewText, requireIdentifierStart: false))
|
if (ParserHelpers.IsIdentifier(change.NewText, requireIdentifierStart: false))
|
||||||
|
|
@ -550,7 +553,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private PartialParseResultInternal HandleInsertionAfterOpenParenthesis(Span target, SourceChange change)
|
private PartialParseResultInternal HandleInsertionAfterOpenParenthesis(SyntaxNode target, SourceChange change)
|
||||||
{
|
{
|
||||||
if (IsCloseParenthesisInsertion(change))
|
if (IsCloseParenthesisInsertion(change))
|
||||||
{
|
{
|
||||||
|
|
@ -560,6 +563,27 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
return PartialParseResultInternal.Rejected;
|
return PartialParseResultInternal.Rejected;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private PartialParseResultInternal HandleInsertionAfterDot(SyntaxNode target, SourceChange change)
|
||||||
|
{
|
||||||
|
// If the insertion is a full identifier or another dot, accept it
|
||||||
|
if (ParserHelpers.IsIdentifier(change.NewText) || change.NewText == ".")
|
||||||
|
{
|
||||||
|
return TryAcceptChange(target, change);
|
||||||
|
}
|
||||||
|
return PartialParseResultInternal.Rejected;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PartialParseResultInternal TryAcceptChange(SyntaxNode target, SourceChange change, PartialParseResultInternal acceptResult = PartialParseResultInternal.Accepted)
|
||||||
|
{
|
||||||
|
var content = change.GetEditedContent(target);
|
||||||
|
if (StartsWithKeyword(content))
|
||||||
|
{
|
||||||
|
return PartialParseResultInternal.Rejected | PartialParseResultInternal.SpanContextChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
return acceptResult;
|
||||||
|
}
|
||||||
|
|
||||||
private static bool IsDoubleParenthesisInsertion(SourceChange change)
|
private static bool IsDoubleParenthesisInsertion(SourceChange change)
|
||||||
{
|
{
|
||||||
return
|
return
|
||||||
|
|
@ -591,27 +615,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
content.Take(content.Length - 1).All(ParserHelpers.IsIdentifierPart));
|
content.Take(content.Length - 1).All(ParserHelpers.IsIdentifierPart));
|
||||||
}
|
}
|
||||||
|
|
||||||
private PartialParseResultInternal HandleInsertionAfterDot(Span target, SourceChange change)
|
|
||||||
{
|
|
||||||
// If the insertion is a full identifier or another dot, accept it
|
|
||||||
if (ParserHelpers.IsIdentifier(change.NewText) || change.NewText == ".")
|
|
||||||
{
|
|
||||||
return TryAcceptChange(target, change);
|
|
||||||
}
|
|
||||||
return PartialParseResultInternal.Rejected;
|
|
||||||
}
|
|
||||||
|
|
||||||
private PartialParseResultInternal TryAcceptChange(Span target, SourceChange change, PartialParseResultInternal acceptResult = PartialParseResultInternal.Accepted)
|
|
||||||
{
|
|
||||||
var content = change.GetEditedContent(target);
|
|
||||||
if (StartsWithKeyword(content))
|
|
||||||
{
|
|
||||||
return PartialParseResultInternal.Rejected | PartialParseResultInternal.SpanContextChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
return acceptResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool StartsWithKeyword(string newContent)
|
private bool StartsWithKeyword(string newContent)
|
||||||
{
|
{
|
||||||
using (var reader = new StringReader(newContent))
|
using (var reader = new StringReader(newContent))
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
{
|
{
|
||||||
internal enum KnownTokenType
|
internal enum KnownTokenType
|
||||||
{
|
{
|
||||||
WhiteSpace,
|
Whitespace,
|
||||||
NewLine,
|
NewLine,
|
||||||
Identifier,
|
Identifier,
|
||||||
Keyword,
|
Keyword,
|
||||||
|
|
|
||||||
|
|
@ -33,9 +33,9 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual bool IsWhiteSpace(SyntaxToken token)
|
public virtual bool IsWhitespace(SyntaxToken token)
|
||||||
{
|
{
|
||||||
return IsKnownTokenType(token, KnownTokenType.WhiteSpace);
|
return IsKnownTokenType(token, KnownTokenType.Whitespace);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual bool IsNewLine(SyntaxToken token)
|
public virtual bool IsNewLine(SyntaxToken token)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,248 @@
|
||||||
|
// 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.Linq;
|
||||||
|
using Microsoft.AspNetCore.Razor.Language.Syntax;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
|
{
|
||||||
|
internal static class LegacySyntaxNodeExtensions
|
||||||
|
{
|
||||||
|
private static readonly SyntaxKind[] TransitionSpanKinds = new SyntaxKind[]
|
||||||
|
{
|
||||||
|
SyntaxKind.CSharpTransition,
|
||||||
|
SyntaxKind.MarkupTransition,
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly SyntaxKind[] MetaCodeSpanKinds = new SyntaxKind[]
|
||||||
|
{
|
||||||
|
SyntaxKind.RazorMetaCode,
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly SyntaxKind[] CommentSpanKinds = new SyntaxKind[]
|
||||||
|
{
|
||||||
|
SyntaxKind.RazorCommentLiteral,
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly SyntaxKind[] CodeSpanKinds = new SyntaxKind[]
|
||||||
|
{
|
||||||
|
SyntaxKind.CSharpStatementLiteral,
|
||||||
|
SyntaxKind.CSharpExpressionLiteral,
|
||||||
|
SyntaxKind.CSharpEphemeralTextLiteral,
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly SyntaxKind[] MarkupSpanKinds = new SyntaxKind[]
|
||||||
|
{
|
||||||
|
SyntaxKind.MarkupTextLiteral,
|
||||||
|
SyntaxKind.MarkupEphemeralTextLiteral,
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly SyntaxKind[] NoneSpanKinds = new SyntaxKind[]
|
||||||
|
{
|
||||||
|
SyntaxKind.UnclassifiedTextLiteral,
|
||||||
|
};
|
||||||
|
|
||||||
|
public static SpanContext GetSpanContext(this SyntaxNode node)
|
||||||
|
{
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
var context = node.GetAnnotationValue(SyntaxConstants.SpanContextKind);
|
||||||
|
|
||||||
|
return context is SpanContext ? (SpanContext)context : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TNode WithSpanContext<TNode>(this TNode node, SpanContext spanContext) where TNode : SyntaxNode
|
||||||
|
{
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
var newAnnotation = new SyntaxAnnotation(SyntaxConstants.SpanContextKind, spanContext);
|
||||||
|
|
||||||
|
var newAnnotations = new List<SyntaxAnnotation>();
|
||||||
|
newAnnotations.Add(newAnnotation);
|
||||||
|
foreach (var annotation in node.GetAnnotations())
|
||||||
|
{
|
||||||
|
if (annotation.Kind != newAnnotation.Kind)
|
||||||
|
{
|
||||||
|
newAnnotations.Add(annotation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return node.WithAnnotations(newAnnotations.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SyntaxNode LocateOwner(this SyntaxNode node, SourceChange change)
|
||||||
|
{
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (change.Span.AbsoluteIndex < node.Position)
|
||||||
|
{
|
||||||
|
// Early escape for cases where changes overlap multiple spans
|
||||||
|
// In those cases, the span will return false, and we don't want to search the whole tree
|
||||||
|
// So if the current span starts after the change, we know we've searched as far as we need to
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsSpanKind(node))
|
||||||
|
{
|
||||||
|
var editHandler = node.GetSpanContext()?.EditHandler ?? SpanEditHandler.CreateDefault();
|
||||||
|
return editHandler.OwnsChange(node, change) ? node : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
SyntaxNode owner = null;
|
||||||
|
var children = node.ChildNodes();
|
||||||
|
foreach (var child in children)
|
||||||
|
{
|
||||||
|
owner = LocateOwner(child, change);
|
||||||
|
if (owner != null)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsTransitionSpanKind(this SyntaxNode node)
|
||||||
|
{
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
return TransitionSpanKinds.Contains(node.Kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsMetaCodeSpanKind(this SyntaxNode node)
|
||||||
|
{
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
return MetaCodeSpanKinds.Contains(node.Kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsCommentSpanKind(this SyntaxNode node)
|
||||||
|
{
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
return CommentSpanKinds.Contains(node.Kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsCodeSpanKind(this SyntaxNode node)
|
||||||
|
{
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
return CodeSpanKinds.Contains(node.Kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsMarkupSpanKind(this SyntaxNode node)
|
||||||
|
{
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
return MarkupSpanKinds.Contains(node.Kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsNoneSpanKind(this SyntaxNode node)
|
||||||
|
{
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
return NoneSpanKinds.Contains(node.Kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsSpanKind(this SyntaxNode node)
|
||||||
|
{
|
||||||
|
return IsTransitionSpanKind(node) ||
|
||||||
|
IsMetaCodeSpanKind(node) ||
|
||||||
|
IsCommentSpanKind(node) ||
|
||||||
|
IsCodeSpanKind(node) ||
|
||||||
|
IsMarkupSpanKind(node) ||
|
||||||
|
IsNoneSpanKind(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<SyntaxNode> FlattenSpans(this SyntaxNode node)
|
||||||
|
{
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var child in node.DescendantNodes())
|
||||||
|
{
|
||||||
|
if (child.IsSpanKind())
|
||||||
|
{
|
||||||
|
yield return child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SyntaxNode PreviousSpan(this SyntaxNode node)
|
||||||
|
{
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
var parent = node.Parent;
|
||||||
|
while (parent != null)
|
||||||
|
{
|
||||||
|
var flattenedSpans = parent.FlattenSpans();
|
||||||
|
var prevSpan = flattenedSpans.LastOrDefault(n => n.EndPosition <= node.Position && n != node);
|
||||||
|
if (prevSpan != null)
|
||||||
|
{
|
||||||
|
return prevSpan;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent = parent.Parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SyntaxNode NextSpan(this SyntaxNode node)
|
||||||
|
{
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
var parent = node.Parent;
|
||||||
|
while (parent != null)
|
||||||
|
{
|
||||||
|
var flattenedSpans = parent.FlattenSpans();
|
||||||
|
var nextSpan = flattenedSpans.FirstOrDefault(n => n.Position >= node.EndPosition && n != node);
|
||||||
|
if (nextSpan != null)
|
||||||
|
{
|
||||||
|
return nextSpan;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent = parent.Parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -18,29 +18,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
|
|
||||||
public LocationTagged<string> Value { get; }
|
public LocationTagged<string> Value { get; }
|
||||||
|
|
||||||
public override void Accept(ParserVisitor visitor, Span span)
|
|
||||||
{
|
|
||||||
visitor.VisitLiteralAttributeSpan(this, span);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void GenerateChunk(Span target, ChunkGeneratorContext context)
|
|
||||||
{
|
|
||||||
//var chunk = context.ChunkTreeBuilder.StartParentChunk<LiteralCodeAttributeChunk>(target);
|
|
||||||
//chunk.Prefix = Prefix;
|
|
||||||
//chunk.Value = Value;
|
|
||||||
|
|
||||||
//if (ValueGenerator != null)
|
|
||||||
//{
|
|
||||||
// chunk.ValueLocation = ValueGenerator.Location;
|
|
||||||
|
|
||||||
// ValueGenerator.Value.GenerateChunk(target, context);
|
|
||||||
|
|
||||||
// chunk.ValueLocation = ValueGenerator.Location;
|
|
||||||
//}
|
|
||||||
|
|
||||||
//context.ChunkTreeBuilder.EndParentChunk();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return string.Format(CultureInfo.CurrentCulture, "LitAttr:{0:F}", Prefix);
|
return string.Format(CultureInfo.CurrentCulture, "LitAttr:{0:F}", Prefix);
|
||||||
|
|
|
||||||
|
|
@ -5,16 +5,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
{
|
{
|
||||||
internal class MarkupChunkGenerator : SpanChunkGenerator
|
internal class MarkupChunkGenerator : SpanChunkGenerator
|
||||||
{
|
{
|
||||||
public override void GenerateChunk(Span target, ChunkGeneratorContext context)
|
|
||||||
{
|
|
||||||
//context.ChunkTreeBuilder.AddLiteralChunk(target.Content, target);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Accept(ParserVisitor visitor, Span span)
|
|
||||||
{
|
|
||||||
visitor.VisitMarkupSpan(this, span);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return "Markup";
|
return "Markup";
|
||||||
|
|
|
||||||
|
|
@ -1,76 +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 System.Diagnostics;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|
||||||
{
|
|
||||||
internal abstract class MarkupRewriter : ParserVisitor
|
|
||||||
{
|
|
||||||
private Stack<BlockBuilder> _blocks;
|
|
||||||
|
|
||||||
protected MarkupRewriter()
|
|
||||||
{
|
|
||||||
_blocks = new Stack<BlockBuilder>();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected BlockBuilder Parent => _blocks.Count > 0 ? _blocks.Peek() : null;
|
|
||||||
|
|
||||||
public Block Rewrite(Block root)
|
|
||||||
{
|
|
||||||
root.Accept(this);
|
|
||||||
Debug.Assert(_blocks.Count == 1);
|
|
||||||
var rewrittenRoot = _blocks.Pop().Build();
|
|
||||||
|
|
||||||
return rewrittenRoot;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void VisitBlock(Block block)
|
|
||||||
{
|
|
||||||
if (CanRewrite(block))
|
|
||||||
{
|
|
||||||
var newNode = RewriteBlock(Parent, block);
|
|
||||||
if (newNode != null)
|
|
||||||
{
|
|
||||||
Parent.Children.Add(newNode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Not rewritable.
|
|
||||||
var builder = new BlockBuilder(block);
|
|
||||||
builder.Children.Clear();
|
|
||||||
_blocks.Push(builder);
|
|
||||||
base.VisitBlock(block);
|
|
||||||
Debug.Assert(ReferenceEquals(builder, Parent));
|
|
||||||
|
|
||||||
if (_blocks.Count > 1)
|
|
||||||
{
|
|
||||||
_blocks.Pop();
|
|
||||||
Parent.Children.Add(builder.Build());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract bool CanRewrite(Block block);
|
|
||||||
|
|
||||||
protected abstract SyntaxTreeNode RewriteBlock(BlockBuilder parent, Block block);
|
|
||||||
|
|
||||||
public override void VisitSpan(Span span)
|
|
||||||
{
|
|
||||||
Parent.Children.Add(span);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void FillSpan(SpanBuilder builder, SourceLocation start, string content)
|
|
||||||
{
|
|
||||||
builder.Kind = SpanKindInternal.Markup;
|
|
||||||
builder.ChunkGenerator = new MarkupChunkGenerator();
|
|
||||||
|
|
||||||
foreach (var token in HtmlLanguageCharacteristics.Instance.TokenizeString(start, content))
|
|
||||||
{
|
|
||||||
builder.Accept(token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,54 +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.
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|
||||||
{
|
|
||||||
internal abstract class ParentChunkGenerator : IParentChunkGenerator
|
|
||||||
{
|
|
||||||
private static readonly int TypeHashCode = typeof(ParentChunkGenerator).GetHashCode();
|
|
||||||
|
|
||||||
public static readonly IParentChunkGenerator Null = new NullParentChunkGenerator();
|
|
||||||
|
|
||||||
public abstract void Accept(ParserVisitor visitor, Block block);
|
|
||||||
|
|
||||||
public virtual void GenerateStartParentChunk(Block target, ChunkGeneratorContext context)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void GenerateEndParentChunk(Block target, ChunkGeneratorContext context)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool Equals(object obj)
|
|
||||||
{
|
|
||||||
return obj != null &&
|
|
||||||
GetType() == obj.GetType();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
return TypeHashCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class NullParentChunkGenerator : IParentChunkGenerator
|
|
||||||
{
|
|
||||||
public void GenerateStartParentChunk(Block target, ChunkGeneratorContext context)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void GenerateEndParentChunk(Block target, ChunkGeneratorContext context)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return "None";
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Accept(ParserVisitor visitor, Block block)
|
|
||||||
{
|
|
||||||
visitor.VisitDefault(block);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -11,9 +11,5 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
}
|
}
|
||||||
|
|
||||||
public ParserContext Context { get; }
|
public ParserContext Context { get; }
|
||||||
|
|
||||||
public abstract void BuildSpan(SpanBuilder span, SourceLocation start, string content);
|
|
||||||
|
|
||||||
public abstract void ParseBlock();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,13 +25,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
DesignTimeMode = options.DesignTime;
|
DesignTimeMode = options.DesignTime;
|
||||||
FeatureFlags = options.FeatureFlags;
|
FeatureFlags = options.FeatureFlags;
|
||||||
ParseLeadingDirectives = options.ParseLeadingDirectives;
|
ParseLeadingDirectives = options.ParseLeadingDirectives;
|
||||||
Builder = new SyntaxTreeBuilder();
|
|
||||||
ErrorSink = new ErrorSink();
|
ErrorSink = new ErrorSink();
|
||||||
SeenDirectives = new HashSet<string>(StringComparer.Ordinal);
|
SeenDirectives = new HashSet<string>(StringComparer.Ordinal);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SyntaxTreeBuilder Builder { get; }
|
|
||||||
|
|
||||||
public ErrorSink ErrorSink { get; set; }
|
public ErrorSink ErrorSink { get; set; }
|
||||||
|
|
||||||
public RazorParserFeatureFlags FeatureFlags { get; }
|
public RazorParserFeatureFlags FeatureFlags { get; }
|
||||||
|
|
@ -50,6 +47,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
|
|
||||||
public bool NullGenerateWhitespaceAndNewLine { get; set; }
|
public bool NullGenerateWhitespaceAndNewLine { get; set; }
|
||||||
|
|
||||||
|
public bool InTemplateContext { get; set; }
|
||||||
|
|
||||||
|
public AcceptedCharactersInternal LastAcceptedCharacters { get; set; } = AcceptedCharactersInternal.None;
|
||||||
|
|
||||||
public bool EndOfFile
|
public bool EndOfFile
|
||||||
{
|
{
|
||||||
get { return Source.Peek() == -1; }
|
get { return Source.Peek() == -1; }
|
||||||
|
|
|
||||||
|
|
@ -1,121 +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.
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|
||||||
{
|
|
||||||
internal abstract class ParserVisitor
|
|
||||||
{
|
|
||||||
public virtual void Visit(SyntaxTreeNode node)
|
|
||||||
{
|
|
||||||
node.Accept(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void VisitDefault(Block block)
|
|
||||||
{
|
|
||||||
for (var i = 0; i < block.Children.Count; i++)
|
|
||||||
{
|
|
||||||
block.Children[i].Accept(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void VisitDefault(Span span)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void VisitBlock(Block block)
|
|
||||||
{
|
|
||||||
if (block.ChunkGenerator != null)
|
|
||||||
{
|
|
||||||
block.ChunkGenerator.Accept(this, block);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void VisitSpan(Span span)
|
|
||||||
{
|
|
||||||
if (span.ChunkGenerator != null)
|
|
||||||
{
|
|
||||||
span.ChunkGenerator.Accept(this, span);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void VisitDynamicAttributeBlock(DynamicAttributeBlockChunkGenerator chunkGenerator, Block block)
|
|
||||||
{
|
|
||||||
VisitDefault(block);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void VisitExpressionBlock(ExpressionChunkGenerator chunkGenerator, Block block)
|
|
||||||
{
|
|
||||||
VisitDefault(block);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void VisitAttributeBlock(AttributeBlockChunkGenerator chunkGenerator, Block block)
|
|
||||||
{
|
|
||||||
VisitDefault(block);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void VisitTemplateBlock(TemplateBlockChunkGenerator chunkGenerator, Block block)
|
|
||||||
{
|
|
||||||
VisitDefault(block);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void VisitCommentBlock(RazorCommentChunkGenerator chunkGenerator, Block block)
|
|
||||||
{
|
|
||||||
VisitDefault(block);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void VisitTagHelperBlock(TagHelperChunkGenerator chunkGenerator, Block block)
|
|
||||||
{
|
|
||||||
VisitDefault(block);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void VisitDirectiveBlock(DirectiveChunkGenerator chunkGenerator, Block block)
|
|
||||||
{
|
|
||||||
VisitDefault(block);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void VisitExpressionSpan(ExpressionChunkGenerator chunkGenerator, Span span)
|
|
||||||
{
|
|
||||||
VisitDefault(span);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void VisitMarkupSpan(MarkupChunkGenerator chunkGenerator, Span span)
|
|
||||||
{
|
|
||||||
VisitDefault(span);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void VisitImportSpan(AddImportChunkGenerator chunkGenerator, Span span)
|
|
||||||
{
|
|
||||||
VisitDefault(span);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void VisitStatementSpan(StatementChunkGenerator chunkGenerator, Span span)
|
|
||||||
{
|
|
||||||
VisitDefault(span);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void VisitLiteralAttributeSpan(LiteralAttributeChunkGenerator chunkGenerator, Span span)
|
|
||||||
{
|
|
||||||
VisitDefault(span);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void VisitDirectiveToken(DirectiveTokenChunkGenerator chunkGenerator, Span span)
|
|
||||||
{
|
|
||||||
VisitDefault(span);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void VisitAddTagHelperSpan(AddTagHelperChunkGenerator chunkGenerator, Span span)
|
|
||||||
{
|
|
||||||
VisitDefault(span);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void VisitRemoveTagHelperSpan(RemoveTagHelperChunkGenerator chunkGenerator, Span span)
|
|
||||||
{
|
|
||||||
VisitDefault(span);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void VisitTagHelperPrefixDirectiveSpan(TagHelperPrefixDirectiveChunkGenerator chunkGenerator, Span span)
|
|
||||||
{
|
|
||||||
VisitDefault(span);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +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.
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|
||||||
{
|
|
||||||
internal class RazorCommentChunkGenerator : ParentChunkGenerator
|
|
||||||
{
|
|
||||||
public override void Accept(ParserVisitor visitor, Block block)
|
|
||||||
{
|
|
||||||
visitor.VisitCommentBlock(this, block);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return "RazorComment";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
{
|
{
|
||||||
|
|
@ -39,12 +38,9 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
codeParser.HtmlParser = markupParser;
|
codeParser.HtmlParser = markupParser;
|
||||||
markupParser.CodeParser = codeParser;
|
markupParser.CodeParser = codeParser;
|
||||||
|
|
||||||
markupParser.ParseDocument();
|
|
||||||
|
|
||||||
var root = context.Builder.Build();
|
|
||||||
|
|
||||||
var diagnostics = context.ErrorSink.Errors;
|
var diagnostics = context.ErrorSink.Errors;
|
||||||
|
|
||||||
|
var root = markupParser.ParseDocument().CreateRed();
|
||||||
return RazorSyntaxTree.Create(root, source, diagnostics, Options);
|
return RazorSyntaxTree.Create(root, source, diagnostics, Options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,31 +16,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
throw new ArgumentNullException(nameof(syntaxTree));
|
throw new ArgumentNullException(nameof(syntaxTree));
|
||||||
}
|
}
|
||||||
|
|
||||||
var spans = Flatten(syntaxTree);
|
var visitor = new ClassifiedSpanVisitor(syntaxTree.Source);
|
||||||
|
visitor.Visit(syntaxTree.Root);
|
||||||
|
|
||||||
var result = new ClassifiedSpanInternal[spans.Count];
|
return visitor.ClassifiedSpans;
|
||||||
for (var i = 0; i < spans.Count; i++)
|
|
||||||
{
|
|
||||||
var span = spans[i];
|
|
||||||
result[i] = new ClassifiedSpanInternal(
|
|
||||||
new SourceSpan(
|
|
||||||
span.Start.FilePath ?? syntaxTree.Source.FilePath,
|
|
||||||
span.Start.AbsoluteIndex,
|
|
||||||
span.Start.LineIndex,
|
|
||||||
span.Start.CharacterIndex,
|
|
||||||
span.Length),
|
|
||||||
new SourceSpan(
|
|
||||||
span.Parent.Start.FilePath ?? syntaxTree.Source.FilePath,
|
|
||||||
span.Parent.Start.AbsoluteIndex,
|
|
||||||
span.Parent.Start.LineIndex,
|
|
||||||
span.Parent.Start.CharacterIndex,
|
|
||||||
span.Parent.Length),
|
|
||||||
span.Kind,
|
|
||||||
span.Parent.Type,
|
|
||||||
span.EditHandler.AcceptedCharacters);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IReadOnlyList<TagHelperSpanInternal> GetTagHelperSpans(this RazorSyntaxTree syntaxTree)
|
public static IReadOnlyList<TagHelperSpanInternal> GetTagHelperSpans(this RazorSyntaxTree syntaxTree)
|
||||||
|
|
@ -50,81 +29,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
throw new ArgumentNullException(nameof(syntaxTree));
|
throw new ArgumentNullException(nameof(syntaxTree));
|
||||||
}
|
}
|
||||||
|
|
||||||
var results = new List<TagHelperSpanInternal>();
|
var visitor = new TagHelperSpanVisitor(syntaxTree.Source);
|
||||||
|
visitor.Visit(syntaxTree.Root);
|
||||||
|
|
||||||
var toProcess = new List<Block>();
|
return visitor.TagHelperSpans;
|
||||||
var blockChildren = new List<Block>();
|
|
||||||
toProcess.Add(syntaxTree.Root);
|
|
||||||
|
|
||||||
for (var i = 0; i < toProcess.Count; i++)
|
|
||||||
{
|
|
||||||
var blockNode = toProcess[i];
|
|
||||||
if (blockNode is TagHelperBlock tagHelperNode)
|
|
||||||
{
|
|
||||||
results.Add(new TagHelperSpanInternal(
|
|
||||||
new SourceSpan(
|
|
||||||
tagHelperNode.Start.FilePath ?? syntaxTree.Source.FilePath,
|
|
||||||
tagHelperNode.Start.AbsoluteIndex,
|
|
||||||
tagHelperNode.Start.LineIndex,
|
|
||||||
tagHelperNode.Start.CharacterIndex,
|
|
||||||
tagHelperNode.Length),
|
|
||||||
tagHelperNode.Binding));
|
|
||||||
}
|
|
||||||
|
|
||||||
// collect all child blocks and inject into toProcess as a single InsertRange
|
|
||||||
foreach (var child in blockNode.Children)
|
|
||||||
{
|
|
||||||
if (child is Block block)
|
|
||||||
{
|
|
||||||
blockChildren.Add(block);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (blockChildren.Count > 0)
|
|
||||||
{
|
|
||||||
toProcess.InsertRange(i + 1, blockChildren);
|
|
||||||
blockChildren.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<Span> Flatten(RazorSyntaxTree syntaxTree)
|
|
||||||
{
|
|
||||||
var result = new List<Span>();
|
|
||||||
AppendFlattenedSpans(syntaxTree.Root, result);
|
|
||||||
return result;
|
|
||||||
|
|
||||||
void AppendFlattenedSpans(SyntaxTreeNode node, List<Span> foundSpans)
|
|
||||||
{
|
|
||||||
if (node is Span spanNode)
|
|
||||||
{
|
|
||||||
foundSpans.Add(spanNode);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (node is TagHelperBlock tagHelperNode)
|
|
||||||
{
|
|
||||||
// These aren't in document order, sort them first and then dig in
|
|
||||||
var attributeNodes = tagHelperNode.Attributes.Select(kvp => kvp.Value).Where(att => att != null).ToList();
|
|
||||||
attributeNodes.Sort((x, y) => x.Start.AbsoluteIndex.CompareTo(y.Start.AbsoluteIndex));
|
|
||||||
|
|
||||||
foreach (var attributeNode in attributeNodes)
|
|
||||||
{
|
|
||||||
AppendFlattenedSpans(attributeNode, foundSpans);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node is Block block)
|
|
||||||
{
|
|
||||||
foreach (var child in block.Children)
|
|
||||||
{
|
|
||||||
AppendFlattenedSpans(child, foundSpans);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,11 +35,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
|
|
||||||
public List<RazorDiagnostic> Diagnostics { get; }
|
public List<RazorDiagnostic> Diagnostics { get; }
|
||||||
|
|
||||||
public override void Accept(ParserVisitor visitor, Span span)
|
|
||||||
{
|
|
||||||
visitor.VisitRemoveTagHelperSpan(this, span);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override bool Equals(object obj)
|
public override bool Equals(object obj)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,227 +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;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using Microsoft.AspNetCore.Razor.Language.Syntax;
|
|
||||||
using Microsoft.Extensions.Internal;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|
||||||
{
|
|
||||||
internal class Span : SyntaxTreeNode
|
|
||||||
{
|
|
||||||
private static readonly List<SyntaxToken> EmptyTokenList = new List<SyntaxToken>(0);
|
|
||||||
private static readonly int TypeHashCode = typeof(Span).GetHashCode();
|
|
||||||
private IReadOnlyList<Syntax.InternalSyntax.SyntaxToken> _greenTokens;
|
|
||||||
private string _content;
|
|
||||||
private int? _length;
|
|
||||||
private SourceLocation _start;
|
|
||||||
|
|
||||||
public Span(SpanBuilder builder)
|
|
||||||
{
|
|
||||||
ReplaceWith(builder);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ISpanChunkGenerator ChunkGenerator { get; private set; }
|
|
||||||
|
|
||||||
public SpanKindInternal Kind { get; private set; }
|
|
||||||
|
|
||||||
public IReadOnlyList<SyntaxToken> Tokens { get; private set; }
|
|
||||||
|
|
||||||
// Allow test code to re-link spans
|
|
||||||
public Span Previous { get; internal set; }
|
|
||||||
public Span Next { get; internal set; }
|
|
||||||
|
|
||||||
public SpanEditHandler EditHandler { get; private set; }
|
|
||||||
|
|
||||||
public SyntaxNode SyntaxNode { get; private set; }
|
|
||||||
|
|
||||||
public override bool IsBlock => false;
|
|
||||||
|
|
||||||
public override int Length
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_length == null)
|
|
||||||
{
|
|
||||||
var length = 0;
|
|
||||||
if (_content == null)
|
|
||||||
{
|
|
||||||
for (var i = 0; i < Tokens.Count; i++)
|
|
||||||
{
|
|
||||||
length += Tokens[i].Content.Length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
length = _content.Length;
|
|
||||||
}
|
|
||||||
|
|
||||||
_length = length;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _length.Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override SourceLocation Start => _start;
|
|
||||||
|
|
||||||
public string Content
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_content == null)
|
|
||||||
{
|
|
||||||
var tokenCount = Tokens.Count;
|
|
||||||
if (tokenCount == 1)
|
|
||||||
{
|
|
||||||
// Perf: no StringBuilder allocation if not necessary
|
|
||||||
_content = Tokens[0].Content;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var builder = new StringBuilder();
|
|
||||||
for (var i = 0; i < tokenCount; i++)
|
|
||||||
{
|
|
||||||
var token = Tokens[i];
|
|
||||||
builder.Append(token.Content);
|
|
||||||
}
|
|
||||||
|
|
||||||
_content = builder.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return _content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ReplaceWith(SpanBuilder builder)
|
|
||||||
{
|
|
||||||
Kind = builder.Kind;
|
|
||||||
_greenTokens = builder.Tokens;
|
|
||||||
EditHandler = builder.EditHandler;
|
|
||||||
ChunkGenerator = builder.ChunkGenerator ?? SpanChunkGenerator.Null;
|
|
||||||
_start = builder.Start;
|
|
||||||
SyntaxNode = builder.SyntaxNode?.CreateRed(parent: null, position: _start.AbsoluteIndex);
|
|
||||||
_content = null;
|
|
||||||
_length = null;
|
|
||||||
|
|
||||||
var tokens = EmptyTokenList;
|
|
||||||
if (_greenTokens.Count > 0)
|
|
||||||
{
|
|
||||||
tokens = new List<SyntaxToken>();
|
|
||||||
var currentStart = _start.AbsoluteIndex;
|
|
||||||
for (var i = 0; i < _greenTokens.Count; i++)
|
|
||||||
{
|
|
||||||
var token = new SyntaxToken(_greenTokens[i], parent: SyntaxNode, parentSpan: this, position: currentStart);
|
|
||||||
tokens.Add(token);
|
|
||||||
currentStart += token.FullWidth;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Tokens = tokens;
|
|
||||||
|
|
||||||
Parent?.ChildChanged();
|
|
||||||
|
|
||||||
// Since we took references to the values in SpanBuilder, clear its references out
|
|
||||||
builder.Reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
var builder = new StringBuilder();
|
|
||||||
builder.Append(Kind);
|
|
||||||
builder.AppendFormat(" Span at {0}::{1} - [{2}]", Start, Length, Content);
|
|
||||||
builder.Append(" Edit: <");
|
|
||||||
builder.Append(EditHandler.ToString());
|
|
||||||
builder.Append("> Gen: <");
|
|
||||||
builder.Append(ChunkGenerator.ToString());
|
|
||||||
builder.Append("> {");
|
|
||||||
builder.Append(string.Join(";", Tokens.GroupBy(sym => sym.GetType()).Select(grp => string.Concat(grp.Key.Name, ":", grp.Count()))));
|
|
||||||
builder.Append("}");
|
|
||||||
return builder.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ChangeStart(SourceLocation newStart)
|
|
||||||
{
|
|
||||||
_start = newStart;
|
|
||||||
var current = this;
|
|
||||||
var tracker = new SourceLocationTracker(newStart);
|
|
||||||
tracker.UpdateLocation(Content);
|
|
||||||
while ((current = current.Next) != null)
|
|
||||||
{
|
|
||||||
current._start = tracker.CurrentLocation;
|
|
||||||
tracker.UpdateLocation(current.Content);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks that the specified span is equivalent to the other in that it has the same start point and content.
|
|
||||||
/// </summary>
|
|
||||||
public override bool EquivalentTo(SyntaxTreeNode node)
|
|
||||||
{
|
|
||||||
return node is Span other &&
|
|
||||||
Kind.Equals(other.Kind) &&
|
|
||||||
Start.Equals(other.Start) &&
|
|
||||||
EditHandler.Equals(other.EditHandler) &&
|
|
||||||
string.Equals(other.Content, Content, StringComparison.Ordinal);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int GetEquivalenceHash()
|
|
||||||
{
|
|
||||||
// Hash code should include only immutable properties but EquivalentTo also checks the type.
|
|
||||||
return TypeHashCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool Equals(object obj)
|
|
||||||
{
|
|
||||||
return obj is Span other &&
|
|
||||||
Kind.Equals(other.Kind) &&
|
|
||||||
EditHandler.Equals(other.EditHandler) &&
|
|
||||||
ChunkGenerator.Equals(other.ChunkGenerator) &&
|
|
||||||
Tokens.SequenceEqual(other.Tokens, SyntaxTokenComparer.Default);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
// Hash code should include only immutable properties but Equals also checks the type.
|
|
||||||
return TypeHashCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Accept(ParserVisitor visitor)
|
|
||||||
{
|
|
||||||
visitor.VisitSpan(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override SyntaxTreeNode Clone()
|
|
||||||
{
|
|
||||||
var spanBuilder = new SpanBuilder(this);
|
|
||||||
return spanBuilder.Build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private class SyntaxTokenComparer : IEqualityComparer<SyntaxToken>
|
|
||||||
{
|
|
||||||
public static readonly SyntaxTokenComparer Default = new SyntaxTokenComparer();
|
|
||||||
|
|
||||||
private SyntaxTokenComparer()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Equals(SyntaxToken x, SyntaxToken y)
|
|
||||||
{
|
|
||||||
return x.IsEquivalentTo(y);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int GetHashCode(SyntaxToken obj)
|
|
||||||
{
|
|
||||||
var hash = HashCodeCombiner.Start();
|
|
||||||
hash.Add(obj.Content, StringComparer.Ordinal);
|
|
||||||
hash.Add(obj.Kind);
|
|
||||||
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,135 +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;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
|
||||||
using Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|
||||||
{
|
|
||||||
internal class SpanBuilder
|
|
||||||
{
|
|
||||||
private SourceLocation _start;
|
|
||||||
private List<SyntaxToken> _tokens;
|
|
||||||
private SourceLocationTracker _tracker;
|
|
||||||
|
|
||||||
public SpanBuilder(Span original)
|
|
||||||
{
|
|
||||||
Kind = original.Kind;
|
|
||||||
EditHandler = original.EditHandler;
|
|
||||||
_start = original.Start;
|
|
||||||
ChunkGenerator = original.ChunkGenerator;
|
|
||||||
|
|
||||||
_tokens = new List<SyntaxToken>(original.Tokens.Select(t =>t.Green));
|
|
||||||
_tracker = new SourceLocationTracker(original.Start);
|
|
||||||
}
|
|
||||||
|
|
||||||
public SpanBuilder(SourceLocation location)
|
|
||||||
{
|
|
||||||
_tracker = new SourceLocationTracker();
|
|
||||||
|
|
||||||
Reset();
|
|
||||||
|
|
||||||
Start = location;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Syntax.GreenNode SyntaxNode { get; private set; }
|
|
||||||
|
|
||||||
public ISpanChunkGenerator ChunkGenerator { get; set; }
|
|
||||||
|
|
||||||
public SourceLocation Start
|
|
||||||
{
|
|
||||||
get { return _start; }
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_start = value;
|
|
||||||
_tracker.CurrentLocation = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public SourceLocation End => _tracker.CurrentLocation;
|
|
||||||
|
|
||||||
public SpanKindInternal Kind { get; set; }
|
|
||||||
|
|
||||||
public IReadOnlyList<SyntaxToken> Tokens
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_tokens == null)
|
|
||||||
{
|
|
||||||
_tokens = new List<SyntaxToken>();
|
|
||||||
}
|
|
||||||
|
|
||||||
return _tokens;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public SpanEditHandler EditHandler { get; set; }
|
|
||||||
|
|
||||||
public void Reset()
|
|
||||||
{
|
|
||||||
// Need to potentially allocate a new list because Span.ReplaceWith takes ownership
|
|
||||||
// of the original list.
|
|
||||||
_tokens = null;
|
|
||||||
_tokens = new List<SyntaxToken>();
|
|
||||||
|
|
||||||
EditHandler = SpanEditHandler.CreateDefault((content) => Enumerable.Empty<SyntaxToken>());
|
|
||||||
ChunkGenerator = SpanChunkGenerator.Null;
|
|
||||||
Start = SourceLocation.Undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Span Build(SyntaxKind syntaxKind = SyntaxKind.Unknown)
|
|
||||||
{
|
|
||||||
SyntaxNode = GetSyntaxNode(syntaxKind);
|
|
||||||
|
|
||||||
var span = new Span(this);
|
|
||||||
|
|
||||||
return span;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ClearTokens()
|
|
||||||
{
|
|
||||||
_tokens?.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Accept(SyntaxToken token)
|
|
||||||
{
|
|
||||||
if (token == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Start.Equals(SourceLocation.Undefined))
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("SpanBuilder must have a valid location");
|
|
||||||
}
|
|
||||||
|
|
||||||
_tokens.Add(token);
|
|
||||||
_tracker.UpdateLocation(token.Content);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Syntax.GreenNode GetSyntaxNode(SyntaxKind syntaxKind)
|
|
||||||
{
|
|
||||||
if (syntaxKind == SyntaxKind.HtmlTextLiteral)
|
|
||||||
{
|
|
||||||
var textTokens = new SyntaxListBuilder<SyntaxToken>(SyntaxListBuilder.Create());
|
|
||||||
foreach (var token in Tokens)
|
|
||||||
{
|
|
||||||
if (token.Kind == SyntaxKind.Unknown)
|
|
||||||
{
|
|
||||||
Debug.Assert(false, $"Unexpected token {token.Kind}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
textTokens.Add(token);
|
|
||||||
}
|
|
||||||
var textResult = textTokens.ToList();
|
|
||||||
return SyntaxFactory.HtmlTextLiteral(new SyntaxList<SyntaxToken>(textResult.Node));
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -11,12 +11,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
|
|
||||||
public static readonly ISpanChunkGenerator Null = new NullSpanChunkGenerator();
|
public static readonly ISpanChunkGenerator Null = new NullSpanChunkGenerator();
|
||||||
|
|
||||||
public abstract void Accept(ParserVisitor visitor, Span span);
|
|
||||||
|
|
||||||
public virtual void GenerateChunk(Span target, ChunkGeneratorContext context)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool Equals(object obj)
|
public override bool Equals(object obj)
|
||||||
{
|
{
|
||||||
return obj != null &&
|
return obj != null &&
|
||||||
|
|
@ -30,15 +24,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
|
|
||||||
private class NullSpanChunkGenerator : ISpanChunkGenerator
|
private class NullSpanChunkGenerator : ISpanChunkGenerator
|
||||||
{
|
{
|
||||||
public void Accept(ParserVisitor visitor, Span span)
|
|
||||||
{
|
|
||||||
visitor.VisitDefault(span);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void GenerateChunk(Span target, ChunkGeneratorContext context)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return "None";
|
return "None";
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
|
{
|
||||||
|
internal class SpanContext
|
||||||
|
{
|
||||||
|
public SpanContext(ISpanChunkGenerator chunkGenerator, SpanEditHandler editHandler)
|
||||||
|
{
|
||||||
|
ChunkGenerator = chunkGenerator;
|
||||||
|
EditHandler = editHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ISpanChunkGenerator ChunkGenerator { get; }
|
||||||
|
|
||||||
|
public SpanEditHandler EditHandler { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class SpanContextBuilder
|
||||||
|
{
|
||||||
|
public SpanContextBuilder()
|
||||||
|
{
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpanContextBuilder(SpanContext context)
|
||||||
|
{
|
||||||
|
EditHandler = context.EditHandler;
|
||||||
|
ChunkGenerator = context.ChunkGenerator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ISpanChunkGenerator ChunkGenerator { get; set; }
|
||||||
|
|
||||||
|
public SpanEditHandler EditHandler { get; set; }
|
||||||
|
|
||||||
|
public SpanContext Build()
|
||||||
|
{
|
||||||
|
var result = new SpanContext(ChunkGenerator, EditHandler);
|
||||||
|
Reset();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
EditHandler = SpanEditHandler.CreateDefault((content) => Enumerable.Empty<SyntaxToken>());
|
||||||
|
ChunkGenerator = SpanChunkGenerator.Null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,9 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax;
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.AspNetCore.Razor.Language.Syntax;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
{
|
{
|
||||||
|
|
@ -11,12 +13,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
{
|
{
|
||||||
private static readonly int TypeHashCode = typeof(SpanEditHandler).GetHashCode();
|
private static readonly int TypeHashCode = typeof(SpanEditHandler).GetHashCode();
|
||||||
|
|
||||||
public SpanEditHandler(Func<string, IEnumerable<SyntaxToken>> tokenizer)
|
public SpanEditHandler(Func<string, IEnumerable<Syntax.InternalSyntax.SyntaxToken>> tokenizer)
|
||||||
: this(tokenizer, AcceptedCharactersInternal.Any)
|
: this(tokenizer, AcceptedCharactersInternal.Any)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public SpanEditHandler(Func<string, IEnumerable<SyntaxToken>> tokenizer, AcceptedCharactersInternal accepted)
|
public SpanEditHandler(Func<string, IEnumerable<Syntax.InternalSyntax.SyntaxToken>> tokenizer, AcceptedCharactersInternal accepted)
|
||||||
{
|
{
|
||||||
AcceptedCharacters = accepted;
|
AcceptedCharacters = accepted;
|
||||||
Tokenizer = tokenizer;
|
Tokenizer = tokenizer;
|
||||||
|
|
@ -24,19 +26,24 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
|
|
||||||
public AcceptedCharactersInternal AcceptedCharacters { get; set; }
|
public AcceptedCharactersInternal AcceptedCharacters { get; set; }
|
||||||
|
|
||||||
public Func<string, IEnumerable<SyntaxToken>> Tokenizer { get; set; }
|
public Func<string, IEnumerable<Syntax.InternalSyntax.SyntaxToken>> Tokenizer { get; set; }
|
||||||
|
|
||||||
public static SpanEditHandler CreateDefault(Func<string, IEnumerable<SyntaxToken>> tokenizer)
|
public static SpanEditHandler CreateDefault()
|
||||||
|
{
|
||||||
|
return CreateDefault(c => Enumerable.Empty<Syntax.InternalSyntax.SyntaxToken>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SpanEditHandler CreateDefault(Func<string, IEnumerable<Syntax.InternalSyntax.SyntaxToken>> tokenizer)
|
||||||
{
|
{
|
||||||
return new SpanEditHandler(tokenizer);
|
return new SpanEditHandler(tokenizer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual EditResult ApplyChange(Span target, SourceChange change)
|
public virtual EditResult ApplyChange(SyntaxNode target, SourceChange change)
|
||||||
{
|
{
|
||||||
return ApplyChange(target, change, force: false);
|
return ApplyChange(target, change, force: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual EditResult ApplyChange(Span target, SourceChange change, bool force)
|
public virtual EditResult ApplyChange(SyntaxNode target, SourceChange change, bool force)
|
||||||
{
|
{
|
||||||
var result = PartialParseResultInternal.Accepted;
|
var result = PartialParseResultInternal.Accepted;
|
||||||
if (!force)
|
if (!force)
|
||||||
|
|
@ -49,49 +56,81 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
{
|
{
|
||||||
return new EditResult(result, UpdateSpan(target, change));
|
return new EditResult(result, UpdateSpan(target, change));
|
||||||
}
|
}
|
||||||
return new EditResult(result, new SpanBuilder(target));
|
return new EditResult(result, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual bool OwnsChange(Span target, SourceChange change)
|
public virtual bool OwnsChange(SyntaxNode target, SourceChange change)
|
||||||
{
|
{
|
||||||
var end = target.Start.AbsoluteIndex + target.Length;
|
var end = target.EndPosition;
|
||||||
var changeOldEnd = change.Span.AbsoluteIndex + change.Span.Length;
|
var changeOldEnd = change.Span.AbsoluteIndex + change.Span.Length;
|
||||||
return change.Span.AbsoluteIndex >= target.Start.AbsoluteIndex &&
|
return change.Span.AbsoluteIndex >= target.Position &&
|
||||||
(changeOldEnd < end || (changeOldEnd == end && AcceptedCharacters != AcceptedCharactersInternal.None));
|
(changeOldEnd < end || (changeOldEnd == end && AcceptedCharacters != AcceptedCharactersInternal.None));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual PartialParseResultInternal CanAcceptChange(Span target, SourceChange change)
|
protected virtual PartialParseResultInternal CanAcceptChange(SyntaxNode target, SourceChange change)
|
||||||
{
|
{
|
||||||
return PartialParseResultInternal.Rejected;
|
return PartialParseResultInternal.Rejected;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual SpanBuilder UpdateSpan(Span target, SourceChange change)
|
protected virtual SyntaxNode UpdateSpan(SyntaxNode target, SourceChange change)
|
||||||
{
|
{
|
||||||
var newContent = change.GetEditedContent(target);
|
var newContent = change.GetEditedContent(target);
|
||||||
var newSpan = new SpanBuilder(target);
|
var builder = Syntax.InternalSyntax.SyntaxListBuilder<Syntax.InternalSyntax.SyntaxToken>.Create();
|
||||||
newSpan.ClearTokens();
|
|
||||||
foreach (var token in Tokenizer(newContent))
|
foreach (var token in Tokenizer(newContent))
|
||||||
{
|
{
|
||||||
newSpan.Accept(token);
|
builder.Add(token);
|
||||||
}
|
}
|
||||||
if (target.Next != null)
|
|
||||||
|
SyntaxNode newTarget = null;
|
||||||
|
if (target is RazorMetaCodeSyntax)
|
||||||
{
|
{
|
||||||
var newEnd = SourceLocationTracker.CalculateNewLocation(target.Start, newContent);
|
newTarget = Syntax.InternalSyntax.SyntaxFactory.RazorMetaCode(builder.ToList()).CreateRed(target.Parent, target.Position);
|
||||||
target.Next.ChangeStart(newEnd);
|
|
||||||
}
|
}
|
||||||
return newSpan;
|
else if (target is MarkupTextLiteralSyntax)
|
||||||
|
{
|
||||||
|
newTarget = Syntax.InternalSyntax.SyntaxFactory.MarkupTextLiteral(builder.ToList()).CreateRed(target.Parent, target.Position);
|
||||||
|
}
|
||||||
|
else if (target is MarkupEphemeralTextLiteralSyntax)
|
||||||
|
{
|
||||||
|
newTarget = Syntax.InternalSyntax.SyntaxFactory.MarkupEphemeralTextLiteral(builder.ToList()).CreateRed(target.Parent, target.Position);
|
||||||
|
}
|
||||||
|
else if (target is CSharpStatementLiteralSyntax)
|
||||||
|
{
|
||||||
|
newTarget = Syntax.InternalSyntax.SyntaxFactory.CSharpStatementLiteral(builder.ToList()).CreateRed(target.Parent, target.Position);
|
||||||
|
}
|
||||||
|
else if (target is CSharpExpressionLiteralSyntax)
|
||||||
|
{
|
||||||
|
newTarget = Syntax.InternalSyntax.SyntaxFactory.CSharpExpressionLiteral(builder.ToList()).CreateRed(target.Parent, target.Position);
|
||||||
|
}
|
||||||
|
else if (target is CSharpEphemeralTextLiteralSyntax)
|
||||||
|
{
|
||||||
|
newTarget = Syntax.InternalSyntax.SyntaxFactory.CSharpEphemeralTextLiteral(builder.ToList()).CreateRed(target.Parent, target.Position);
|
||||||
|
}
|
||||||
|
else if (target is UnclassifiedTextLiteralSyntax)
|
||||||
|
{
|
||||||
|
newTarget = Syntax.InternalSyntax.SyntaxFactory.UnclassifiedTextLiteral(builder.ToList()).CreateRed(target.Parent, target.Position);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.Fail($"The type {target?.GetType().Name} is not a supported span node.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var context = target.GetSpanContext();
|
||||||
|
newTarget = context != null ? newTarget?.WithSpanContext(context) : newTarget;
|
||||||
|
|
||||||
|
return newTarget;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected internal static bool IsAtEndOfFirstLine(Span target, SourceChange change)
|
protected internal static bool IsAtEndOfFirstLine(SyntaxNode target, SourceChange change)
|
||||||
{
|
{
|
||||||
var endOfFirstLine = target.Content.IndexOfAny(new char[] { (char)0x000d, (char)0x000a, (char)0x2028, (char)0x2029 });
|
var endOfFirstLine = target.GetContent().IndexOfAny(new char[] { (char)0x000d, (char)0x000a, (char)0x2028, (char)0x2029 });
|
||||||
return (endOfFirstLine == -1 || (change.Span.AbsoluteIndex - target.Start.AbsoluteIndex) <= endOfFirstLine);
|
return (endOfFirstLine == -1 || (change.Span.AbsoluteIndex - target.Position) <= endOfFirstLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns true if the specified change is an insertion of text at the end of this span.
|
/// Returns true if the specified change is an insertion of text at the end of this span.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected internal static bool IsEndDeletion(Span target, SourceChange change)
|
protected internal static bool IsEndDeletion(SyntaxNode target, SourceChange change)
|
||||||
{
|
{
|
||||||
return change.IsDelete && IsAtEndOfSpan(target, change);
|
return change.IsDelete && IsAtEndOfSpan(target, change);
|
||||||
}
|
}
|
||||||
|
|
@ -99,14 +138,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns true if the specified change is a replacement of text at the end of this span.
|
/// Returns true if the specified change is a replacement of text at the end of this span.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected internal static bool IsEndReplace(Span target, SourceChange change)
|
protected internal static bool IsEndReplace(SyntaxNode target, SourceChange change)
|
||||||
{
|
{
|
||||||
return change.IsReplace && IsAtEndOfSpan(target, change);
|
return change.IsReplace && IsAtEndOfSpan(target, change);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected internal static bool IsAtEndOfSpan(Span target, SourceChange change)
|
protected internal static bool IsAtEndOfSpan(SyntaxNode target, SourceChange change)
|
||||||
{
|
{
|
||||||
return (change.Span.AbsoluteIndex + change.Span.Length) == (target.Start.AbsoluteIndex + target.Length);
|
return (change.Span.AbsoluteIndex + change.Span.Length) == target.EndPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
|
|
|
||||||
|
|
@ -5,16 +5,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
{
|
{
|
||||||
internal class StatementChunkGenerator : SpanChunkGenerator
|
internal class StatementChunkGenerator : SpanChunkGenerator
|
||||||
{
|
{
|
||||||
public override void Accept(ParserVisitor visitor, Span span)
|
|
||||||
{
|
|
||||||
visitor.VisitStatementSpan(this, span);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void GenerateChunk(Span target, ChunkGeneratorContext context)
|
|
||||||
{
|
|
||||||
//context.ChunkTreeBuilder.AddStatementChunk(target.Content, target);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return "Stmt";
|
return "Stmt";
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
public static readonly string TransitionString = "@";
|
public static readonly string TransitionString = "@";
|
||||||
public static readonly string StartCommentSequence = "@*";
|
public static readonly string StartCommentSequence = "@*";
|
||||||
public static readonly string EndCommentSequence = "*@";
|
public static readonly string EndCommentSequence = "*@";
|
||||||
|
public static readonly string SpanContextKind = "SpanData";
|
||||||
|
|
||||||
public static class CSharp
|
public static class CSharp
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,96 +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;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|
||||||
{
|
|
||||||
internal class SyntaxTreeBuilder
|
|
||||||
{
|
|
||||||
private readonly Stack<BlockBuilder> _blockStack;
|
|
||||||
private readonly Action _endBlock;
|
|
||||||
|
|
||||||
public SyntaxTreeBuilder()
|
|
||||||
{
|
|
||||||
_blockStack = new Stack<BlockBuilder>();
|
|
||||||
_endBlock = EndBlock;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IReadOnlyCollection<BlockBuilder> ActiveBlocks => _blockStack;
|
|
||||||
|
|
||||||
public BlockBuilder CurrentBlock => _blockStack.Peek();
|
|
||||||
|
|
||||||
public Span LastSpan { get; private set; }
|
|
||||||
|
|
||||||
public AcceptedCharactersInternal LastAcceptedCharacters
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (LastSpan == null)
|
|
||||||
{
|
|
||||||
return AcceptedCharactersInternal.None;
|
|
||||||
}
|
|
||||||
return LastSpan.EditHandler.AcceptedCharacters;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Add(Span span)
|
|
||||||
{
|
|
||||||
if (_blockStack.Count == 0)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(Resources.ParserContext_NoCurrentBlock);
|
|
||||||
}
|
|
||||||
CurrentBlock.Children.Add(span);
|
|
||||||
LastSpan = span;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Starts a block of the specified type
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="blockType">The type of the block to start</param>
|
|
||||||
public IDisposable StartBlock(BlockKindInternal blockType)
|
|
||||||
{
|
|
||||||
var builder = new BlockBuilder() { Type = blockType };
|
|
||||||
_blockStack.Push(builder);
|
|
||||||
return new DisposableAction(_endBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Ends the current block
|
|
||||||
/// </summary>
|
|
||||||
public void EndBlock()
|
|
||||||
{
|
|
||||||
if (_blockStack.Count == 0)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(Resources.EndBlock_Called_Without_Matching_StartBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_blockStack.Count > 1)
|
|
||||||
{
|
|
||||||
var initialBlockBuilder = _blockStack.Pop();
|
|
||||||
var initialBlock = initialBlockBuilder.Build();
|
|
||||||
CurrentBlock.Children.Add(initialBlock);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Block Build()
|
|
||||||
{
|
|
||||||
if (_blockStack.Count == 0)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(Resources.ParserContext_CannotCompleteTree_NoRootBlock);
|
|
||||||
}
|
|
||||||
if (_blockStack.Count != 1)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(Resources.ParserContext_CannotCompleteTree_OutstandingBlocks);
|
|
||||||
}
|
|
||||||
|
|
||||||
var rootBuilder = _blockStack.Pop();
|
|
||||||
var root = rootBuilder.Build();
|
|
||||||
|
|
||||||
root.LinkNodes();
|
|
||||||
|
|
||||||
return root;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,49 +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.
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|
||||||
{
|
|
||||||
internal abstract class SyntaxTreeNode
|
|
||||||
{
|
|
||||||
public Block Parent { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns true if this element is a block (to avoid casting)
|
|
||||||
/// </summary>
|
|
||||||
public abstract bool IsBlock { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The length of all the content contained in this node
|
|
||||||
/// </summary>
|
|
||||||
public abstract int Length { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The start point of this node
|
|
||||||
/// </summary>
|
|
||||||
public abstract SourceLocation Start { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines if the specified node is equivalent to this node
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="node">The node to compare this node with</param>
|
|
||||||
/// <returns>
|
|
||||||
/// true if the provided node has all the same content and metadata, though the specific quantity and type of
|
|
||||||
/// tokens may be different.
|
|
||||||
/// </returns>
|
|
||||||
public abstract bool EquivalentTo(SyntaxTreeNode node);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines a hash code for the <see cref="SyntaxTreeNode"/> using only information relevant in
|
|
||||||
/// <see cref="EquivalentTo"/> comparisons.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>
|
|
||||||
/// A hash code for the <see cref="SyntaxTreeNode"/> using only information relevant in
|
|
||||||
/// <see cref="EquivalentTo"/> comparisons.
|
|
||||||
/// </returns>
|
|
||||||
public abstract int GetEquivalenceHash();
|
|
||||||
|
|
||||||
public abstract void Accept(ParserVisitor visitor);
|
|
||||||
|
|
||||||
public abstract SyntaxTreeNode Clone();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Razor.Language.Syntax;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
{
|
{
|
||||||
internal class TagHelperAttributeNode
|
internal class TagHelperAttributeNode
|
||||||
{
|
{
|
||||||
public TagHelperAttributeNode(string name, SyntaxTreeNode value, AttributeStructure attributeStructure)
|
public TagHelperAttributeNode(string name, SyntaxNode value, AttributeStructure attributeStructure)
|
||||||
{
|
{
|
||||||
Name = name;
|
Name = name;
|
||||||
Value = value;
|
Value = value;
|
||||||
|
|
@ -13,14 +15,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal for testing
|
// Internal for testing
|
||||||
internal TagHelperAttributeNode(string name, SyntaxTreeNode value)
|
internal TagHelperAttributeNode(string name, SyntaxNode value)
|
||||||
: this(name, value, AttributeStructure.DoubleQuotes)
|
: this(name, value, AttributeStructure.DoubleQuotes)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
|
|
||||||
public SyntaxTreeNode Value { get; }
|
public SyntaxNode Value { get; }
|
||||||
|
|
||||||
public AttributeStructure AttributeStructure { get; }
|
public AttributeStructure AttributeStructure { get; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,233 +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;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
|
||||||
using Microsoft.Extensions.Internal;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A <see cref="Block"/> that reprents a special HTML element.
|
|
||||||
/// </summary>
|
|
||||||
internal class TagHelperBlock : Block, IEquatable<TagHelperBlock>
|
|
||||||
{
|
|
||||||
private readonly SourceLocation _start;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Instantiates a new instance of a <see cref="TagHelperBlock"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="source">A <see cref="TagHelperBlockBuilder"/> used to construct a valid
|
|
||||||
/// <see cref="TagHelperBlock"/>.</param>
|
|
||||||
public TagHelperBlock(TagHelperBlockBuilder source)
|
|
||||||
: base(source.Type, source.Children, source.ChunkGenerator)
|
|
||||||
{
|
|
||||||
TagName = source.TagName;
|
|
||||||
Binding = source.BindingResult;
|
|
||||||
Attributes = new List<TagHelperAttributeNode>(source.Attributes);
|
|
||||||
_start = source.Start;
|
|
||||||
TagMode = source.TagMode;
|
|
||||||
SourceStartTag = source.SourceStartTag;
|
|
||||||
SourceEndTag = source.SourceEndTag;
|
|
||||||
|
|
||||||
source.Reset();
|
|
||||||
|
|
||||||
foreach (var attributeChildren in Attributes)
|
|
||||||
{
|
|
||||||
if (attributeChildren.Value != null)
|
|
||||||
{
|
|
||||||
attributeChildren.Value.Parent = this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the unrewritten source start tag.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>This is used by design time to properly format <see cref="TagHelperBlock"/>s.</remarks>
|
|
||||||
public Block SourceStartTag { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the unrewritten source end tag.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>This is used by design time to properly format <see cref="TagHelperBlock"/>s.</remarks>
|
|
||||||
public Block SourceEndTag { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the HTML syntax of the element in the Razor source.
|
|
||||||
/// </summary>
|
|
||||||
public TagMode TagMode { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// <see cref="TagHelperDescriptor"/> bindings for the HTML element.
|
|
||||||
/// </summary>
|
|
||||||
public TagHelperBinding Binding { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The HTML attributes.
|
|
||||||
/// </summary>
|
|
||||||
public IList<TagHelperAttributeNode> Attributes { get; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override SourceLocation Start
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return _start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The HTML tag name.
|
|
||||||
/// </summary>
|
|
||||||
public string TagName { get; }
|
|
||||||
|
|
||||||
public override int Length
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var startTagLength = SourceStartTag?.Length ?? 0;
|
|
||||||
var childrenLength = base.Length;
|
|
||||||
var endTagLength = SourceEndTag?.Length ?? 0;
|
|
||||||
|
|
||||||
return startTagLength + childrenLength + endTagLength;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override IEnumerable<Span> Flatten()
|
|
||||||
{
|
|
||||||
if (SourceStartTag != null)
|
|
||||||
{
|
|
||||||
foreach (var childSpan in SourceStartTag.Flatten())
|
|
||||||
{
|
|
||||||
yield return childSpan;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var childSpan in base.Flatten())
|
|
||||||
{
|
|
||||||
yield return childSpan;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SourceEndTag != null)
|
|
||||||
{
|
|
||||||
foreach (var childSpan in SourceEndTag.Flatten())
|
|
||||||
{
|
|
||||||
yield return childSpan;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Span LocateOwner(SourceChange change)
|
|
||||||
{
|
|
||||||
var oldPosition = change.Span.AbsoluteIndex;
|
|
||||||
if (oldPosition < Start.AbsoluteIndex)
|
|
||||||
{
|
|
||||||
// Change occurs prior to the TagHelper.
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var bodyEndLocation = SourceStartTag?.Start.AbsoluteIndex + SourceStartTag?.Length + base.Length;
|
|
||||||
if (oldPosition > bodyEndLocation)
|
|
||||||
{
|
|
||||||
// Change occurs after the TagHelpers body. End tags for TagHelpers cannot claim ownership of changes
|
|
||||||
// because any change to them impacts whether or not a tag is a TagHelper.
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var startTagEndLocation = Start.AbsoluteIndex + SourceStartTag?.Length;
|
|
||||||
if (oldPosition < startTagEndLocation)
|
|
||||||
{
|
|
||||||
// Change occurs in the start tag.
|
|
||||||
|
|
||||||
var attributeElements = Attributes
|
|
||||||
.Select(attribute => attribute.Value)
|
|
||||||
.Where(value => value != null);
|
|
||||||
|
|
||||||
return LocateOwner(change, attributeElements);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldPosition < bodyEndLocation)
|
|
||||||
{
|
|
||||||
// Change occurs in the body
|
|
||||||
return base.LocateOwner(change);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TagHelper does not contain a Span that can claim ownership.
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override SyntaxTreeNode Clone()
|
|
||||||
{
|
|
||||||
var tagHelperBlockBuilder = new TagHelperBlockBuilder(this);
|
|
||||||
|
|
||||||
tagHelperBlockBuilder.Children.Clear();
|
|
||||||
for (var i = 0; i < Children.Count; i++)
|
|
||||||
{
|
|
||||||
var clonedChild = Children[i].Clone();
|
|
||||||
tagHelperBlockBuilder.Children.Add(clonedChild);
|
|
||||||
}
|
|
||||||
|
|
||||||
tagHelperBlockBuilder.Attributes.Clear();
|
|
||||||
for (var i = 0; i < Attributes.Count; i++)
|
|
||||||
{
|
|
||||||
var existingAttribute = Attributes[i];
|
|
||||||
var clonedValue = existingAttribute.Value != null ? existingAttribute.Value.Clone() : null;
|
|
||||||
tagHelperBlockBuilder.Attributes.Add(
|
|
||||||
new TagHelperAttributeNode(existingAttribute.Name, clonedValue, existingAttribute.AttributeStructure));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SourceStartTag != null)
|
|
||||||
{
|
|
||||||
var clonedStartTag = (Block)SourceStartTag.Clone();
|
|
||||||
tagHelperBlockBuilder.SourceStartTag = clonedStartTag;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SourceEndTag != null)
|
|
||||||
{
|
|
||||||
var clonedEndTag = (Block)SourceEndTag.Clone();
|
|
||||||
tagHelperBlockBuilder.SourceEndTag = clonedEndTag;
|
|
||||||
}
|
|
||||||
|
|
||||||
return tagHelperBlockBuilder.Build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return string.Format(CultureInfo.CurrentCulture,
|
|
||||||
"'{0}' (Attrs: {1}) Tag Helper Block at {2}::{3} (Gen:{4})",
|
|
||||||
TagName, Attributes.Count, Start, Length, ChunkGenerator);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines whether two <see cref="TagHelperBlock"/>s are equal by comparing the <see cref="TagName"/>,
|
|
||||||
/// <see cref="Attributes"/>, <see cref="Block.Type"/>, <see cref="Block.ChunkGenerator"/> and
|
|
||||||
/// <see cref="Block.Children"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="other">The <see cref="TagHelperBlock"/> to check equality against.</param>
|
|
||||||
/// <returns>
|
|
||||||
/// <c>true</c> if the current <see cref="TagHelperBlock"/> is equivalent to the given
|
|
||||||
/// <paramref name="other"/>, <c>false</c> otherwise.
|
|
||||||
/// </returns>
|
|
||||||
public bool Equals(TagHelperBlock other)
|
|
||||||
{
|
|
||||||
return base.Equals(other) &&
|
|
||||||
string.Equals(TagName, other.TagName, StringComparison.OrdinalIgnoreCase) &&
|
|
||||||
Attributes.SequenceEqual(other.Attributes);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
var hashCodeCombiner = HashCodeCombiner.Start();
|
|
||||||
hashCodeCombiner.Add(base.GetHashCode());
|
|
||||||
hashCodeCombiner.Add(TagName, StringComparer.OrdinalIgnoreCase);
|
|
||||||
hashCodeCombiner.Add(Attributes);
|
|
||||||
|
|
||||||
return hashCodeCombiner;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,136 +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;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A <see cref="BlockBuilder"/> used to create <see cref="TagHelperBlock"/>s.
|
|
||||||
/// </summary>
|
|
||||||
internal class TagHelperBlockBuilder : BlockBuilder
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Instantiates a new <see cref="TagHelperBlockBuilder"/> instance based on the given
|
|
||||||
/// <paramref name="original"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="original">The original <see cref="TagHelperBlock"/> to copy data from.</param>
|
|
||||||
public TagHelperBlockBuilder(TagHelperBlock original)
|
|
||||||
: base(original)
|
|
||||||
{
|
|
||||||
SourceStartTag = original.SourceStartTag;
|
|
||||||
SourceEndTag = original.SourceEndTag;
|
|
||||||
TagMode = original.TagMode;
|
|
||||||
BindingResult = original.Binding;
|
|
||||||
Attributes = new List<TagHelperAttributeNode>(original.Attributes);
|
|
||||||
TagName = original.TagName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Instantiates a new instance of the <see cref="TagHelperBlockBuilder"/> class
|
|
||||||
/// with the provided values.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="tagName">An HTML tag name.</param>
|
|
||||||
/// <param name="tagMode">HTML syntax of the element in the Razor source.</param>
|
|
||||||
/// <param name="start">Starting location of the <see cref="TagHelperBlock"/>.</param>
|
|
||||||
/// <param name="attributes">Attributes of the <see cref="TagHelperBlock"/>.</param>
|
|
||||||
/// <param name="bindingResult"></param>
|
|
||||||
public TagHelperBlockBuilder(
|
|
||||||
string tagName,
|
|
||||||
TagMode tagMode,
|
|
||||||
SourceLocation start,
|
|
||||||
IList<TagHelperAttributeNode> attributes,
|
|
||||||
TagHelperBinding bindingResult)
|
|
||||||
{
|
|
||||||
TagName = tagName;
|
|
||||||
TagMode = tagMode;
|
|
||||||
Start = start;
|
|
||||||
BindingResult = bindingResult;
|
|
||||||
Attributes = new List<TagHelperAttributeNode>(attributes);
|
|
||||||
Type = BlockKindInternal.Tag;
|
|
||||||
ChunkGenerator = new TagHelperChunkGenerator();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Internal for testing
|
|
||||||
internal TagHelperBlockBuilder(
|
|
||||||
string tagName,
|
|
||||||
TagMode tagMode,
|
|
||||||
IList<TagHelperAttributeNode> attributes,
|
|
||||||
IEnumerable<SyntaxTreeNode> children)
|
|
||||||
{
|
|
||||||
TagName = tagName;
|
|
||||||
TagMode = tagMode;
|
|
||||||
Attributes = attributes;
|
|
||||||
Type = BlockKindInternal.Tag;
|
|
||||||
ChunkGenerator = new TagHelperChunkGenerator();
|
|
||||||
|
|
||||||
// Children is IList, no AddRange
|
|
||||||
foreach (var child in children)
|
|
||||||
{
|
|
||||||
Children.Add(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the unrewritten source start tag.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>This is used by design time to properly format <see cref="TagHelperBlock"/>s.</remarks>
|
|
||||||
public Block SourceStartTag { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the unrewritten source end tag.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>This is used by design time to properly format <see cref="TagHelperBlock"/>s.</remarks>
|
|
||||||
public Block SourceEndTag { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the HTML syntax of the element in the Razor source.
|
|
||||||
/// </summary>
|
|
||||||
public TagMode TagMode { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// <see cref="TagHelperDescriptor"/>s for the HTML element.
|
|
||||||
/// </summary>
|
|
||||||
public TagHelperBinding BindingResult { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The HTML attributes.
|
|
||||||
/// </summary>
|
|
||||||
public IList<TagHelperAttributeNode> Attributes { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The HTML tag name.
|
|
||||||
/// </summary>
|
|
||||||
public string TagName { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructs a new <see cref="TagHelperBlock"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A <see cref="TagHelperBlock"/>.</returns>
|
|
||||||
public override Block Build()
|
|
||||||
{
|
|
||||||
return new TagHelperBlock(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
/// <remarks>
|
|
||||||
/// Sets the <see cref="TagName"/> to <c>null</c> and clears the <see cref="Attributes"/>.
|
|
||||||
/// </remarks>
|
|
||||||
public override void Reset()
|
|
||||||
{
|
|
||||||
TagName = null;
|
|
||||||
|
|
||||||
if (Attributes != null)
|
|
||||||
{
|
|
||||||
Attributes.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
base.Reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The starting <see cref="SourceLocation"/> of the tag helper.
|
|
||||||
/// </summary>
|
|
||||||
public SourceLocation Start { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,82 +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.
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|
||||||
{
|
|
||||||
internal class TagHelperChunkGenerator : ParentChunkGenerator
|
|
||||||
{
|
|
||||||
public override void GenerateStartParentChunk(Block target, ChunkGeneratorContext context)
|
|
||||||
{
|
|
||||||
//var tagHelperBlock = target as TagHelperBlock;
|
|
||||||
|
|
||||||
//Debug.Assert(
|
|
||||||
// tagHelperBlock != null,
|
|
||||||
// $"A {nameof(TagHelperChunkGenerator)} must only be used with {nameof(TagHelperBlock)}s.");
|
|
||||||
|
|
||||||
//var attributes = new List<TagHelperAttributeTracker>();
|
|
||||||
|
|
||||||
//// We need to create a chunk generator to create chunks for each of the attributes.
|
|
||||||
//var chunkGenerator = context.Host.CreateChunkGenerator(
|
|
||||||
// context.ClassName,
|
|
||||||
// context.RootNamespace,
|
|
||||||
// context.SourceFile);
|
|
||||||
|
|
||||||
//foreach (var attribute in tagHelperBlock.Attributes)
|
|
||||||
//{
|
|
||||||
// ParentChunk attributeChunkValue = null;
|
|
||||||
|
|
||||||
// if (attribute.Value != null)
|
|
||||||
// {
|
|
||||||
// // Populates the chunk tree with chunks associated with attributes
|
|
||||||
// attribute.Value.Accept(chunkGenerator);
|
|
||||||
|
|
||||||
// var chunks = chunkGenerator.Context.ChunkTreeBuilder.Root.Children;
|
|
||||||
// var first = chunks.FirstOrDefault();
|
|
||||||
|
|
||||||
// attributeChunkValue = new ParentChunk
|
|
||||||
// {
|
|
||||||
// Association = first?.Association,
|
|
||||||
// Children = chunks,
|
|
||||||
// Start = first == null ? SourceLocation.Zero : first.Start
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
|
|
||||||
// var attributeChunk = new TagHelperAttributeTracker(
|
|
||||||
// attribute.Name,
|
|
||||||
// attributeChunkValue,
|
|
||||||
// attribute.ValueStyle);
|
|
||||||
|
|
||||||
// attributes.Add(attributeChunk);
|
|
||||||
|
|
||||||
// // Reset the chunk tree builder so we can build a new one for the next attribute
|
|
||||||
// chunkGenerator.Context.ChunkTreeBuilder = new ChunkTreeBuilder();
|
|
||||||
//}
|
|
||||||
|
|
||||||
//var unprefixedTagName = tagHelperBlock.TagName.Substring(_tagHelperDescriptors.First().Prefix.Length);
|
|
||||||
|
|
||||||
//context.ChunkTreeBuilder.StartParentChunk(
|
|
||||||
// new TagHelperChunk(
|
|
||||||
// unprefixedTagName,
|
|
||||||
// tagHelperBlock.TagMode,
|
|
||||||
// attributes,
|
|
||||||
// _tagHelperDescriptors),
|
|
||||||
// target,
|
|
||||||
// topLevel: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void GenerateEndParentChunk(Block target, ChunkGeneratorContext context)
|
|
||||||
{
|
|
||||||
//context.ChunkTreeBuilder.EndParentChunk();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Accept(ParserVisitor visitor, Block block)
|
|
||||||
{
|
|
||||||
visitor.VisitTagHelperBlock(this, block);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return "TagHelper";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -24,11 +24,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
|
|
||||||
public List<RazorDiagnostic> Diagnostics { get; }
|
public List<RazorDiagnostic> Diagnostics { get; }
|
||||||
|
|
||||||
public override void Accept(ParserVisitor visitor, Span span)
|
|
||||||
{
|
|
||||||
visitor.VisitTagHelperPrefixDirectiveSpan(this, span);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override bool Equals(object obj)
|
public override bool Equals(object obj)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,28 +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.
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|
||||||
{
|
|
||||||
internal class TemplateBlockChunkGenerator : ParentChunkGenerator
|
|
||||||
{
|
|
||||||
public override void Accept(ParserVisitor visitor, Block block)
|
|
||||||
{
|
|
||||||
visitor.VisitTemplateBlock(this, block);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void GenerateStartParentChunk(Block target, ChunkGeneratorContext context)
|
|
||||||
{
|
|
||||||
//context.ChunkTreeBuilder.StartParentChunk<TemplateChunk>(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void GenerateEndParentChunk(Block target, ChunkGeneratorContext context)
|
|
||||||
{
|
|
||||||
//context.ChunkTreeBuilder.EndParentChunk();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return "Template";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -12,7 +12,9 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
internal abstract class TokenizerBackedParser<TTokenizer> : ParserBase
|
internal abstract class TokenizerBackedParser<TTokenizer> : ParserBase
|
||||||
where TTokenizer : Tokenizer
|
where TTokenizer : Tokenizer
|
||||||
{
|
{
|
||||||
|
private readonly SyntaxListPool _pool = new SyntaxListPool();
|
||||||
private readonly TokenizerView<TTokenizer> _tokenizer;
|
private readonly TokenizerView<TTokenizer> _tokenizer;
|
||||||
|
private SyntaxListBuilder<SyntaxToken>? _tokenBuilder;
|
||||||
|
|
||||||
protected TokenizerBackedParser(LanguageCharacteristics<TTokenizer> language, ParserContext context)
|
protected TokenizerBackedParser(LanguageCharacteristics<TTokenizer> language, ParserContext context)
|
||||||
: base(context)
|
: base(context)
|
||||||
|
|
@ -21,14 +23,28 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
|
|
||||||
var languageTokenizer = Language.CreateTokenizer(Context.Source);
|
var languageTokenizer = Language.CreateTokenizer(Context.Source);
|
||||||
_tokenizer = new TokenizerView<TTokenizer>(languageTokenizer);
|
_tokenizer = new TokenizerView<TTokenizer>(languageTokenizer);
|
||||||
Span = new SpanBuilder(CurrentLocation);
|
SpanContext = new SpanContextBuilder();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ParserState ParserState { get; set; }
|
protected SyntaxListPool Pool => _pool;
|
||||||
|
|
||||||
protected SpanBuilder Span { get; private set; }
|
protected SyntaxListBuilder<SyntaxToken> TokenBuilder
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_tokenBuilder == null)
|
||||||
|
{
|
||||||
|
var result = _pool.Allocate<SyntaxToken>();
|
||||||
|
_tokenBuilder = result.Builder;
|
||||||
|
}
|
||||||
|
|
||||||
protected Action<SpanBuilder> SpanConfig { get; set; }
|
return _tokenBuilder.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SpanContextBuilder SpanContext { get; private set; }
|
||||||
|
|
||||||
|
protected Action<SpanContextBuilder> SpanContextConfig { get; set; }
|
||||||
|
|
||||||
protected SyntaxToken CurrentToken
|
protected SyntaxToken CurrentToken
|
||||||
{
|
{
|
||||||
|
|
@ -37,8 +53,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
|
|
||||||
protected SyntaxToken PreviousToken { get; private set; }
|
protected SyntaxToken PreviousToken { get; private set; }
|
||||||
|
|
||||||
protected SourceLocation CurrentLocation => _tokenizer.Tokenizer.CurrentLocation;
|
|
||||||
|
|
||||||
protected SourceLocation CurrentStart => _tokenizer.Tokenizer.CurrentStart;
|
protected SourceLocation CurrentStart => _tokenizer.Tokenizer.CurrentStart;
|
||||||
|
|
||||||
protected bool EndOfFile
|
protected bool EndOfFile
|
||||||
|
|
@ -48,28 +62,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
|
|
||||||
protected LanguageCharacteristics<TTokenizer> Language { get; }
|
protected LanguageCharacteristics<TTokenizer> Language { get; }
|
||||||
|
|
||||||
protected virtual void HandleEmbeddedTransition()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual bool IsAtEmbeddedTransition(bool allowTemplatesAndComments, bool allowTransitions)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void BuildSpan(SpanBuilder span, SourceLocation start, string content)
|
|
||||||
{
|
|
||||||
foreach (var token in Language.TokenizeString(start, content))
|
|
||||||
{
|
|
||||||
span.Accept(token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void Initialize(SpanBuilder span)
|
|
||||||
{
|
|
||||||
SpanConfig?.Invoke(span);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected SyntaxToken Lookahead(int count)
|
protected SyntaxToken Lookahead(int count)
|
||||||
{
|
{
|
||||||
if (count < 0)
|
if (count < 0)
|
||||||
|
|
@ -163,11 +155,9 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
[Conditional("DEBUG")]
|
[Conditional("DEBUG")]
|
||||||
internal void Assert(SyntaxKind expectedType)
|
internal void Assert(SyntaxKind expectedType)
|
||||||
{
|
{
|
||||||
Debug.Assert(!EndOfFile && TokenKindEquals(CurrentToken.Kind, expectedType));
|
Debug.Assert(!EndOfFile && CurrentToken.Kind == expectedType);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract bool TokenKindEquals(SyntaxKind x, SyntaxKind y);
|
|
||||||
|
|
||||||
protected internal void PutBack(SyntaxToken token)
|
protected internal void PutBack(SyntaxToken token)
|
||||||
{
|
{
|
||||||
if (token != null)
|
if (token != null)
|
||||||
|
|
@ -203,96 +193,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected internal bool Balance(BalancingModes mode)
|
|
||||||
{
|
|
||||||
var left = CurrentToken.Kind;
|
|
||||||
var right = Language.FlipBracket(left);
|
|
||||||
var start = CurrentStart;
|
|
||||||
AcceptAndMoveNext();
|
|
||||||
if (EndOfFile && ((mode & BalancingModes.NoErrorOnFailure) != BalancingModes.NoErrorOnFailure))
|
|
||||||
{
|
|
||||||
Context.ErrorSink.OnError(
|
|
||||||
RazorDiagnosticFactory.CreateParsing_ExpectedCloseBracketBeforeEOF(
|
|
||||||
new SourceSpan(start, contentLength: 1 /* { OR } */),
|
|
||||||
Language.GetSample(left),
|
|
||||||
Language.GetSample(right)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Balance(mode, left, right, start);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected internal bool Balance(BalancingModes mode, SyntaxKind left, SyntaxKind right, SourceLocation start)
|
|
||||||
{
|
|
||||||
var startPosition = CurrentStart.AbsoluteIndex;
|
|
||||||
var nesting = 1;
|
|
||||||
if (!EndOfFile)
|
|
||||||
{
|
|
||||||
var tokens = new List<SyntaxToken>();
|
|
||||||
do
|
|
||||||
{
|
|
||||||
if (IsAtEmbeddedTransition(
|
|
||||||
(mode & BalancingModes.AllowCommentsAndTemplates) == BalancingModes.AllowCommentsAndTemplates,
|
|
||||||
(mode & BalancingModes.AllowEmbeddedTransitions) == BalancingModes.AllowEmbeddedTransitions))
|
|
||||||
{
|
|
||||||
Accept(tokens);
|
|
||||||
tokens.Clear();
|
|
||||||
HandleEmbeddedTransition();
|
|
||||||
|
|
||||||
// Reset backtracking since we've already outputted some spans.
|
|
||||||
startPosition = CurrentStart.AbsoluteIndex;
|
|
||||||
}
|
|
||||||
if (At(left))
|
|
||||||
{
|
|
||||||
nesting++;
|
|
||||||
}
|
|
||||||
else if (At(right))
|
|
||||||
{
|
|
||||||
nesting--;
|
|
||||||
}
|
|
||||||
if (nesting > 0)
|
|
||||||
{
|
|
||||||
tokens.Add(CurrentToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while (nesting > 0 && NextToken());
|
|
||||||
|
|
||||||
if (nesting > 0)
|
|
||||||
{
|
|
||||||
if ((mode & BalancingModes.NoErrorOnFailure) != BalancingModes.NoErrorOnFailure)
|
|
||||||
{
|
|
||||||
Context.ErrorSink.OnError(
|
|
||||||
RazorDiagnosticFactory.CreateParsing_ExpectedCloseBracketBeforeEOF(
|
|
||||||
new SourceSpan(start, contentLength: 1 /* { OR } */),
|
|
||||||
Language.GetSample(left),
|
|
||||||
Language.GetSample(right)));
|
|
||||||
}
|
|
||||||
if ((mode & BalancingModes.BacktrackOnFailure) == BalancingModes.BacktrackOnFailure)
|
|
||||||
{
|
|
||||||
Context.Source.Position = startPosition;
|
|
||||||
NextToken();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Accept(tokens);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Accept all the tokens we saw
|
|
||||||
Accept(tokens);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nesting == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected internal bool NextIs(SyntaxKind type)
|
protected internal bool NextIs(SyntaxKind type)
|
||||||
{
|
{
|
||||||
return NextIs(token => token != null && TokenKindEquals(type, token.Kind));
|
return NextIs(token => token != null && type == token.Kind);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected internal bool NextIs(params SyntaxKind[] types)
|
protected internal bool NextIs(params SyntaxKind[] types)
|
||||||
{
|
{
|
||||||
return NextIs(token => token != null && types.Any(t => TokenKindEquals(t, token.Kind)));
|
return NextIs(token => token != null && types.Any(t => t == token.Kind));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected internal bool NextIs(Func<SyntaxToken, bool> condition)
|
protected internal bool NextIs(Func<SyntaxToken, bool> condition)
|
||||||
|
|
@ -317,173 +225,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
|
|
||||||
protected internal bool Was(SyntaxKind type)
|
protected internal bool Was(SyntaxKind type)
|
||||||
{
|
{
|
||||||
return PreviousToken != null && TokenKindEquals(PreviousToken.Kind, type);
|
return PreviousToken != null && PreviousToken.Kind == type;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected internal bool At(SyntaxKind type)
|
protected internal bool At(SyntaxKind type)
|
||||||
{
|
{
|
||||||
return !EndOfFile && CurrentToken != null && TokenKindEquals(CurrentToken.Kind, type);
|
return !EndOfFile && CurrentToken != null && CurrentToken.Kind == type;
|
||||||
}
|
|
||||||
|
|
||||||
protected internal bool AcceptAndMoveNext()
|
|
||||||
{
|
|
||||||
Accept(CurrentToken);
|
|
||||||
return NextToken();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected SyntaxToken AcceptSingleWhiteSpaceCharacter()
|
|
||||||
{
|
|
||||||
if (Language.IsWhiteSpace(CurrentToken))
|
|
||||||
{
|
|
||||||
var pair = Language.SplitToken(CurrentToken, 1, Language.GetKnownTokenType(KnownTokenType.WhiteSpace));
|
|
||||||
Accept(pair.Item1);
|
|
||||||
Span.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.None;
|
|
||||||
NextToken();
|
|
||||||
return pair.Item2;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected internal void Accept(IEnumerable<SyntaxToken> tokens)
|
|
||||||
{
|
|
||||||
foreach (var token in tokens)
|
|
||||||
{
|
|
||||||
Accept(token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected internal void Accept(SyntaxToken token)
|
|
||||||
{
|
|
||||||
if (token != null)
|
|
||||||
{
|
|
||||||
foreach (var error in token.GetDiagnostics())
|
|
||||||
{
|
|
||||||
Context.ErrorSink.OnError(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
Span.Accept(token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected internal bool AcceptAll(params SyntaxKind[] kinds)
|
|
||||||
{
|
|
||||||
foreach (var kind in kinds)
|
|
||||||
{
|
|
||||||
if (CurrentToken == null || !TokenKindEquals(CurrentToken.Kind, kind))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
AcceptAndMoveNext();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected internal void AddMarkerTokenIfNecessary()
|
|
||||||
{
|
|
||||||
if (Span.Tokens.Count == 0 && Context.Builder.LastAcceptedCharacters != AcceptedCharactersInternal.Any)
|
|
||||||
{
|
|
||||||
Accept(Language.CreateMarkerToken());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected internal void Output(SpanKindInternal kind, SyntaxKind syntaxKind = SyntaxKind.Unknown)
|
|
||||||
{
|
|
||||||
Configure(kind, null);
|
|
||||||
Output(syntaxKind);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected internal void Output(SpanKindInternal kind, AcceptedCharactersInternal accepts, SyntaxKind syntaxKind = SyntaxKind.Unknown)
|
|
||||||
{
|
|
||||||
Configure(kind, accepts);
|
|
||||||
Output(syntaxKind);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected internal void Output(AcceptedCharactersInternal accepts, SyntaxKind syntaxKind = SyntaxKind.Unknown)
|
|
||||||
{
|
|
||||||
Configure(null, accepts);
|
|
||||||
Output(syntaxKind);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Output(SyntaxKind syntaxKind)
|
|
||||||
{
|
|
||||||
if (Span.Tokens.Count > 0)
|
|
||||||
{
|
|
||||||
var nextStart = Span.End;
|
|
||||||
|
|
||||||
var builtSpan = Span.Build(syntaxKind);
|
|
||||||
Context.Builder.Add(builtSpan);
|
|
||||||
Initialize(Span);
|
|
||||||
|
|
||||||
// Ensure spans are contiguous.
|
|
||||||
//
|
|
||||||
// Note: Using Span.End here to avoid CurrentLocation. CurrentLocation will
|
|
||||||
// vary depending on what tokens have been read. We often read a token and *then*
|
|
||||||
// make a decision about whether to include it in the current span.
|
|
||||||
Span.Start = nextStart;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected IDisposable PushSpanConfig()
|
|
||||||
{
|
|
||||||
return PushSpanConfig(newConfig: (Action<SpanBuilder, Action<SpanBuilder>>)null);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected IDisposable PushSpanConfig(Action<SpanBuilder> newConfig)
|
|
||||||
{
|
|
||||||
return PushSpanConfig(newConfig == null ? (Action<SpanBuilder, Action<SpanBuilder>>)null : (span, _) => newConfig(span));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected IDisposable PushSpanConfig(Action<SpanBuilder, Action<SpanBuilder>> newConfig)
|
|
||||||
{
|
|
||||||
var old = SpanConfig;
|
|
||||||
ConfigureSpan(newConfig);
|
|
||||||
return new DisposableAction(() => SpanConfig = old);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void ConfigureSpan(Action<SpanBuilder> config)
|
|
||||||
{
|
|
||||||
SpanConfig = config;
|
|
||||||
Initialize(Span);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void ConfigureSpan(Action<SpanBuilder, Action<SpanBuilder>> config)
|
|
||||||
{
|
|
||||||
var prev = SpanConfig;
|
|
||||||
if (config == null)
|
|
||||||
{
|
|
||||||
SpanConfig = null;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
SpanConfig = span => config(span, prev);
|
|
||||||
}
|
|
||||||
Initialize(Span);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected internal void Expected(KnownTokenType type)
|
|
||||||
{
|
|
||||||
Expected(Language.GetKnownTokenType(type));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected internal void Expected(params SyntaxKind[] types)
|
|
||||||
{
|
|
||||||
Debug.Assert(!EndOfFile && CurrentToken != null && types.Contains(CurrentToken.Kind));
|
|
||||||
AcceptAndMoveNext();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected internal bool Optional(KnownTokenType type)
|
|
||||||
{
|
|
||||||
return Optional(Language.GetKnownTokenType(type));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected internal bool Optional(SyntaxKind type)
|
|
||||||
{
|
|
||||||
if (At(type))
|
|
||||||
{
|
|
||||||
AcceptAndMoveNext();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected bool EnsureCurrent()
|
protected bool EnsureCurrent()
|
||||||
|
|
@ -496,85 +243,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected internal void AcceptWhile(SyntaxKind type)
|
|
||||||
{
|
|
||||||
AcceptWhile(token => TokenKindEquals(type, token.Kind));
|
|
||||||
}
|
|
||||||
|
|
||||||
// We want to avoid array allocations and enumeration where possible, so we use the same technique as string.Format
|
|
||||||
protected internal void AcceptWhile(SyntaxKind type1, SyntaxKind type2)
|
|
||||||
{
|
|
||||||
AcceptWhile(token => TokenKindEquals(type1, token.Kind) || TokenKindEquals(type2, token.Kind));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected internal void AcceptWhile(SyntaxKind type1, SyntaxKind type2, SyntaxKind type3)
|
|
||||||
{
|
|
||||||
AcceptWhile(token => TokenKindEquals(type1, token.Kind) || TokenKindEquals(type2, token.Kind) || TokenKindEquals(type3, token.Kind));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected internal void AcceptWhile(params SyntaxKind[] types)
|
|
||||||
{
|
|
||||||
AcceptWhile(token => types.Any(expected => TokenKindEquals(expected, token.Kind)));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected internal void AcceptUntil(SyntaxKind type)
|
|
||||||
{
|
|
||||||
AcceptWhile(token => !TokenKindEquals(type, token.Kind));
|
|
||||||
}
|
|
||||||
|
|
||||||
// We want to avoid array allocations and enumeration where possible, so we use the same technique as string.Format
|
|
||||||
protected internal void AcceptUntil(SyntaxKind type1, SyntaxKind type2)
|
|
||||||
{
|
|
||||||
AcceptWhile(token => !TokenKindEquals(type1, token.Kind) && !TokenKindEquals(type2, token.Kind));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected internal void AcceptUntil(SyntaxKind type1, SyntaxKind type2, SyntaxKind type3)
|
|
||||||
{
|
|
||||||
AcceptWhile(token => !TokenKindEquals(type1, token.Kind) && !TokenKindEquals(type2, token.Kind) && !TokenKindEquals(type3, token.Kind));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected internal void AcceptUntil(params SyntaxKind[] types)
|
|
||||||
{
|
|
||||||
AcceptWhile(token => types.All(expected => !TokenKindEquals(expected, token.Kind)));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected internal void AcceptWhile(Func<SyntaxToken, bool> condition)
|
|
||||||
{
|
|
||||||
Accept(ReadWhileLazy(condition));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected internal IEnumerable<SyntaxToken> ReadWhile(Func<SyntaxToken, bool> condition)
|
protected internal IEnumerable<SyntaxToken> ReadWhile(Func<SyntaxToken, bool> condition)
|
||||||
{
|
{
|
||||||
return ReadWhileLazy(condition).ToList();
|
return ReadWhileLazy(condition).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected SyntaxToken AcceptWhiteSpaceInLines()
|
|
||||||
{
|
|
||||||
SyntaxToken lastWs = null;
|
|
||||||
while (Language.IsWhiteSpace(CurrentToken) || Language.IsNewLine(CurrentToken))
|
|
||||||
{
|
|
||||||
// Capture the previous whitespace node
|
|
||||||
if (lastWs != null)
|
|
||||||
{
|
|
||||||
Accept(lastWs);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Language.IsWhiteSpace(CurrentToken))
|
|
||||||
{
|
|
||||||
lastWs = CurrentToken;
|
|
||||||
}
|
|
||||||
else if (Language.IsNewLine(CurrentToken))
|
|
||||||
{
|
|
||||||
// Accept newline and reset last whitespace tracker
|
|
||||||
Accept(CurrentToken);
|
|
||||||
lastWs = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
_tokenizer.Next();
|
|
||||||
}
|
|
||||||
return lastWs;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected bool AtIdentifier(bool allowKeywords)
|
protected bool AtIdentifier(bool allowKeywords)
|
||||||
{
|
{
|
||||||
return CurrentToken != null &&
|
return CurrentToken != null &&
|
||||||
|
|
@ -593,30 +266,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Configure(SpanKindInternal? kind, AcceptedCharactersInternal? accepts)
|
protected RazorCommentBlockSyntax ParseRazorComment()
|
||||||
{
|
|
||||||
if (kind != null)
|
|
||||||
{
|
|
||||||
Span.Kind = kind.Value;
|
|
||||||
}
|
|
||||||
if (accepts != null)
|
|
||||||
{
|
|
||||||
Span.EditHandler.AcceptedCharacters = accepts.Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void OutputSpanBeforeRazorComment()
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(Resources.Language_Does_Not_Support_RazorComment);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CommentSpanConfig(SpanBuilder span)
|
|
||||||
{
|
|
||||||
span.ChunkGenerator = SpanChunkGenerator.Null;
|
|
||||||
span.EditHandler = SpanEditHandler.CreateDefault(Language.TokenizeString);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void RazorComment()
|
|
||||||
{
|
{
|
||||||
if (!Language.KnowsTokenType(KnownTokenType.CommentStart) ||
|
if (!Language.KnowsTokenType(KnownTokenType.CommentStart) ||
|
||||||
!Language.KnowsTokenType(KnownTokenType.CommentStar) ||
|
!Language.KnowsTokenType(KnownTokenType.CommentStar) ||
|
||||||
|
|
@ -624,54 +274,321 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException(Resources.Language_Does_Not_Support_RazorComment);
|
throw new InvalidOperationException(Resources.Language_Does_Not_Support_RazorComment);
|
||||||
}
|
}
|
||||||
OutputSpanBeforeRazorComment();
|
|
||||||
using (PushSpanConfig(CommentSpanConfig))
|
RazorCommentBlockSyntax commentBlock;
|
||||||
|
using (PushSpanContextConfig(CommentSpanContextConfig))
|
||||||
{
|
{
|
||||||
using (Context.Builder.StartBlock(BlockKindInternal.Comment))
|
EnsureCurrent();
|
||||||
|
var start = CurrentStart;
|
||||||
|
Debug.Assert(At(SyntaxKind.RazorCommentTransition));
|
||||||
|
var startTransition = EatExpectedToken(SyntaxKind.RazorCommentTransition);
|
||||||
|
var startStar = EatExpectedToken(SyntaxKind.RazorCommentStar);
|
||||||
|
var comment = GetOptionalToken(SyntaxKind.RazorCommentLiteral);
|
||||||
|
if (comment == null)
|
||||||
{
|
{
|
||||||
Context.Builder.CurrentBlock.ChunkGenerator = new RazorCommentChunkGenerator();
|
comment = SyntaxFactory.MissingToken(SyntaxKind.RazorCommentLiteral);
|
||||||
var start = CurrentStart;
|
}
|
||||||
|
var endStar = GetOptionalToken(SyntaxKind.RazorCommentStar);
|
||||||
Expected(KnownTokenType.CommentStart);
|
if (endStar == null)
|
||||||
Output(SpanKindInternal.Transition, AcceptedCharactersInternal.None);
|
{
|
||||||
|
var diagnostic = RazorDiagnosticFactory.CreateParsing_RazorCommentNotTerminated(
|
||||||
Expected(KnownTokenType.CommentStar);
|
new SourceSpan(start, contentLength: 2 /* @* */));
|
||||||
Output(SpanKindInternal.MetaCode, AcceptedCharactersInternal.None);
|
endStar = SyntaxFactory.MissingToken(SyntaxKind.RazorCommentStar, diagnostic);
|
||||||
|
Context.ErrorSink.OnError(diagnostic);
|
||||||
Optional(KnownTokenType.CommentBody);
|
}
|
||||||
AddMarkerTokenIfNecessary();
|
var endTransition = GetOptionalToken(SyntaxKind.RazorCommentTransition);
|
||||||
Output(SpanKindInternal.Comment);
|
if (endTransition == null)
|
||||||
|
{
|
||||||
var errorReported = false;
|
if (!endStar.IsMissing)
|
||||||
if (!Optional(KnownTokenType.CommentStar))
|
|
||||||
{
|
{
|
||||||
errorReported = true;
|
var diagnostic = RazorDiagnosticFactory.CreateParsing_RazorCommentNotTerminated(
|
||||||
Context.ErrorSink.OnError(
|
new SourceSpan(start, contentLength: 2 /* @* */));
|
||||||
RazorDiagnosticFactory.CreateParsing_RazorCommentNotTerminated(
|
Context.ErrorSink.OnError(diagnostic);
|
||||||
new SourceSpan(start, contentLength: 2 /* @* */)));
|
endTransition = SyntaxFactory.MissingToken(SyntaxKind.RazorCommentTransition, diagnostic);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Output(SpanKindInternal.MetaCode, AcceptedCharactersInternal.None);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Optional(KnownTokenType.CommentStart))
|
endTransition = SyntaxFactory.MissingToken(SyntaxKind.RazorCommentTransition);
|
||||||
{
|
}
|
||||||
if (!errorReported)
|
|
||||||
{
|
commentBlock = SyntaxFactory.RazorCommentBlock(startTransition, startStar, comment, endStar, endTransition);
|
||||||
errorReported = true;
|
|
||||||
Context.ErrorSink.OnError(
|
// Make sure we generate a marker symbol after a comment if necessary.
|
||||||
RazorDiagnosticFactory.CreateParsing_RazorCommentNotTerminated(
|
if (!comment.IsMissing || !endStar.IsMissing || !endTransition.IsMissing)
|
||||||
new SourceSpan(start, contentLength: 2 /* @* */)));
|
{
|
||||||
}
|
Context.LastAcceptedCharacters = AcceptedCharactersInternal.None;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Output(SpanKindInternal.Transition, AcceptedCharactersInternal.None);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Initialize(Span);
|
|
||||||
|
InitializeContext(SpanContext);
|
||||||
|
|
||||||
|
return commentBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CommentSpanContextConfig(SpanContextBuilder spanContext)
|
||||||
|
{
|
||||||
|
spanContext.ChunkGenerator = SpanChunkGenerator.Null;
|
||||||
|
spanContext.EditHandler = SpanEditHandler.CreateDefault(Language.TokenizeString);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SyntaxToken EatCurrentToken()
|
||||||
|
{
|
||||||
|
Debug.Assert(!EndOfFile && CurrentToken != null);
|
||||||
|
var token = CurrentToken;
|
||||||
|
NextToken();
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SyntaxToken EatExpectedToken(params SyntaxKind[] kinds)
|
||||||
|
{
|
||||||
|
Debug.Assert(!EndOfFile && CurrentToken != null && kinds.Contains(CurrentToken.Kind));
|
||||||
|
var token = CurrentToken;
|
||||||
|
NextToken();
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SyntaxToken GetOptionalToken(SyntaxKind kind)
|
||||||
|
{
|
||||||
|
if (At(kind))
|
||||||
|
{
|
||||||
|
var token = CurrentToken;
|
||||||
|
NextToken();
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected internal void AcceptWhile(SyntaxKind type)
|
||||||
|
{
|
||||||
|
AcceptWhile(token => type == token.Kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We want to avoid array allocations and enumeration where possible, so we use the same technique as string.Format
|
||||||
|
protected internal void AcceptWhile(SyntaxKind type1, SyntaxKind type2)
|
||||||
|
{
|
||||||
|
AcceptWhile(token => type1 == token.Kind || type2 == token.Kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected internal void AcceptWhile(SyntaxKind type1, SyntaxKind type2, SyntaxKind type3)
|
||||||
|
{
|
||||||
|
AcceptWhile(token => type1 == token.Kind || type2 == token.Kind || type3 == token.Kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected internal void AcceptWhile(params SyntaxKind[] types)
|
||||||
|
{
|
||||||
|
AcceptWhile(token => types.Any(expected => expected == token.Kind));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected internal void AcceptUntil(SyntaxKind type)
|
||||||
|
{
|
||||||
|
AcceptWhile(token => type != token.Kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We want to avoid array allocations and enumeration where possible, so we use the same technique as string.Format
|
||||||
|
protected internal void AcceptUntil(SyntaxKind type1, SyntaxKind type2)
|
||||||
|
{
|
||||||
|
AcceptWhile(token => type1 != token.Kind && type2 != token.Kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected internal void AcceptUntil(SyntaxKind type1, SyntaxKind type2, SyntaxKind type3)
|
||||||
|
{
|
||||||
|
AcceptWhile(token => type1 != token.Kind && type2 != token.Kind && type3 != token.Kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected internal void AcceptUntil(params SyntaxKind[] types)
|
||||||
|
{
|
||||||
|
AcceptWhile(token => types.All(expected => expected != token.Kind));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected internal void AcceptWhile(Func<SyntaxToken, bool> condition)
|
||||||
|
{
|
||||||
|
Accept(ReadWhileLazy(condition));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected internal void Accept(IEnumerable<SyntaxToken> tokens)
|
||||||
|
{
|
||||||
|
foreach (var token in tokens)
|
||||||
|
{
|
||||||
|
foreach (var error in token.GetDiagnostics())
|
||||||
|
{
|
||||||
|
Context.ErrorSink.OnError(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
TokenBuilder.Add(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected internal void Accept(SyntaxToken token)
|
||||||
|
{
|
||||||
|
if (token != null)
|
||||||
|
{
|
||||||
|
foreach (var error in token.GetDiagnostics())
|
||||||
|
{
|
||||||
|
Context.ErrorSink.OnError(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
TokenBuilder.Add(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected internal bool AcceptAll(params SyntaxKind[] kinds)
|
||||||
|
{
|
||||||
|
foreach (var kind in kinds)
|
||||||
|
{
|
||||||
|
if (CurrentToken == null || CurrentToken.Kind != kind)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
AcceptAndMoveNext();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected internal bool AcceptAndMoveNext()
|
||||||
|
{
|
||||||
|
Accept(CurrentToken);
|
||||||
|
return NextToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SyntaxList<SyntaxToken> Output()
|
||||||
|
{
|
||||||
|
var list = TokenBuilder.ToList();
|
||||||
|
TokenBuilder.Clear();
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SyntaxToken AcceptWhitespaceInLines()
|
||||||
|
{
|
||||||
|
SyntaxToken lastWs = null;
|
||||||
|
while (Language.IsWhitespace(CurrentToken) || Language.IsNewLine(CurrentToken))
|
||||||
|
{
|
||||||
|
// Capture the previous whitespace node
|
||||||
|
if (lastWs != null)
|
||||||
|
{
|
||||||
|
Accept(lastWs);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Language.IsWhitespace(CurrentToken))
|
||||||
|
{
|
||||||
|
lastWs = CurrentToken;
|
||||||
|
}
|
||||||
|
else if (Language.IsNewLine(CurrentToken))
|
||||||
|
{
|
||||||
|
// Accept newline and reset last whitespace tracker
|
||||||
|
Accept(CurrentToken);
|
||||||
|
lastWs = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
NextToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
return lastWs;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected internal bool TryAccept(SyntaxKind type)
|
||||||
|
{
|
||||||
|
if (At(type))
|
||||||
|
{
|
||||||
|
AcceptAndMoveNext();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected internal void AcceptMarkerTokenIfNecessary()
|
||||||
|
{
|
||||||
|
if (TokenBuilder.Count == 0 && Context.LastAcceptedCharacters != AcceptedCharactersInternal.Any)
|
||||||
|
{
|
||||||
|
Accept(Language.CreateMarkerToken());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected MarkupTextLiteralSyntax OutputAsMarkupLiteral()
|
||||||
|
{
|
||||||
|
var tokens = Output();
|
||||||
|
if (tokens.Count == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetNodeWithSpanContext(SyntaxFactory.MarkupTextLiteral(tokens));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected MarkupEphemeralTextLiteralSyntax OutputAsMarkupEphemeralLiteral()
|
||||||
|
{
|
||||||
|
var tokens = Output();
|
||||||
|
if (tokens.Count == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetNodeWithSpanContext(SyntaxFactory.MarkupEphemeralTextLiteral(tokens));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected RazorMetaCodeSyntax OutputAsMetaCode(SyntaxList<SyntaxToken> tokens, AcceptedCharactersInternal? accepted = null)
|
||||||
|
{
|
||||||
|
if (tokens.Count == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var metacode = SyntaxFactory.RazorMetaCode(tokens);
|
||||||
|
SpanContext.ChunkGenerator = SpanChunkGenerator.Null;
|
||||||
|
SpanContext.EditHandler.AcceptedCharacters = accepted ?? AcceptedCharactersInternal.None;
|
||||||
|
|
||||||
|
return GetNodeWithSpanContext(metacode);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected TNode GetNodeWithSpanContext<TNode>(TNode node) where TNode : Syntax.GreenNode
|
||||||
|
{
|
||||||
|
var spanContext = SpanContext.Build();
|
||||||
|
Context.LastAcceptedCharacters = spanContext.EditHandler.AcceptedCharacters;
|
||||||
|
InitializeContext(SpanContext);
|
||||||
|
var annotation = new Syntax.SyntaxAnnotation(SyntaxConstants.SpanContextKind, spanContext);
|
||||||
|
|
||||||
|
return (TNode)node.SetAnnotations(new[] { annotation });
|
||||||
|
}
|
||||||
|
|
||||||
|
protected IDisposable PushSpanContextConfig()
|
||||||
|
{
|
||||||
|
return PushSpanContextConfig(newConfig: (Action<SpanContextBuilder, Action<SpanContextBuilder>>)null);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected IDisposable PushSpanContextConfig(Action<SpanContextBuilder> newConfig)
|
||||||
|
{
|
||||||
|
return PushSpanContextConfig(newConfig == null ? (Action<SpanContextBuilder, Action<SpanContextBuilder>>)null : (span, _) => newConfig(span));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected IDisposable PushSpanContextConfig(Action<SpanContextBuilder, Action<SpanContextBuilder>> newConfig)
|
||||||
|
{
|
||||||
|
var old = SpanContextConfig;
|
||||||
|
ConfigureSpanContext(newConfig);
|
||||||
|
return new DisposableAction(() => SpanContextConfig = old);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void ConfigureSpanContext(Action<SpanContextBuilder> config)
|
||||||
|
{
|
||||||
|
SpanContextConfig = config;
|
||||||
|
InitializeContext(SpanContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void ConfigureSpanContext(Action<SpanContextBuilder, Action<SpanContextBuilder>> config)
|
||||||
|
{
|
||||||
|
var prev = SpanContextConfig;
|
||||||
|
if (config == null)
|
||||||
|
{
|
||||||
|
SpanContextConfig = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SpanContextConfig = span => config(span, prev);
|
||||||
|
}
|
||||||
|
InitializeContext(SpanContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void InitializeContext(SpanContextBuilder spanContext)
|
||||||
|
{
|
||||||
|
SpanContextConfig?.Invoke(spanContext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,41 +1,70 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// 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;
|
using System.Linq;
|
||||||
|
using Microsoft.AspNetCore.Razor.Language.Syntax;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
{
|
{
|
||||||
internal class WhiteSpaceRewriter : MarkupRewriter
|
internal class WhitespaceRewriter : SyntaxRewriter
|
||||||
{
|
{
|
||||||
protected override bool CanRewrite(Block block)
|
public override SyntaxNode Visit(SyntaxNode node)
|
||||||
{
|
{
|
||||||
return block.Type == BlockKindInternal.Expression && Parent != null;
|
if (node == null)
|
||||||
|
{
|
||||||
|
return base.Visit(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
var children = node.ChildNodes();
|
||||||
|
for (var i = 0; i < children.Count; i++)
|
||||||
|
{
|
||||||
|
var child = children[i];
|
||||||
|
if (child is CSharpCodeBlockSyntax codeBlock &&
|
||||||
|
TryRewriteWhitespace(codeBlock, out var rewritten, out var whitespaceLiteral))
|
||||||
|
{
|
||||||
|
// Replace the existing code block with the whitespace literal
|
||||||
|
// followed by the rewritten code block (with the code whitespace removed).
|
||||||
|
node = node.ReplaceNode(codeBlock, new SyntaxNode[] { whitespaceLiteral, rewritten });
|
||||||
|
|
||||||
|
// Since we replaced node, its children are different. Update our collection.
|
||||||
|
children = node.ChildNodes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.Visit(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override SyntaxTreeNode RewriteBlock(BlockBuilder parent, Block block)
|
private bool TryRewriteWhitespace(CSharpCodeBlockSyntax codeBlock, out CSharpCodeBlockSyntax rewritten, out SyntaxNode whitespaceLiteral)
|
||||||
{
|
{
|
||||||
var newBlock = new BlockBuilder(block);
|
// Rewrite any whitespace represented as code at the start of a line preceding an expression block.
|
||||||
newBlock.Children.Clear();
|
// We want it to be rendered as Markup.
|
||||||
var ws = block.Children.FirstOrDefault() as Span;
|
|
||||||
IEnumerable<SyntaxTreeNode> newNodes = block.Children;
|
|
||||||
if (ws.Content.All(char.IsWhiteSpace))
|
|
||||||
{
|
|
||||||
// Add this node to the parent
|
|
||||||
var builder = new SpanBuilder(ws);
|
|
||||||
builder.ClearTokens();
|
|
||||||
FillSpan(builder, ws.Start, ws.Content);
|
|
||||||
parent.Children.Add(builder.Build());
|
|
||||||
|
|
||||||
// Remove the old whitespace node
|
rewritten = null;
|
||||||
newNodes = block.Children.Skip(1);
|
whitespaceLiteral = null;
|
||||||
|
var children = codeBlock.ChildNodes();
|
||||||
|
if (children.Count < 2)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (SyntaxTreeNode node in newNodes)
|
if (children[0] is CSharpStatementLiteralSyntax literal &&
|
||||||
|
(children[1] is CSharpExplicitExpressionSyntax || children[1] is CSharpImplicitExpressionSyntax))
|
||||||
{
|
{
|
||||||
newBlock.Children.Add(node);
|
var containsNonWhitespace = literal.DescendantNodes()
|
||||||
|
.Where(n => n.IsToken)
|
||||||
|
.Cast<SyntaxToken>()
|
||||||
|
.Any(t => !string.IsNullOrWhiteSpace(t.Content));
|
||||||
|
|
||||||
|
if (!containsNonWhitespace)
|
||||||
|
{
|
||||||
|
// Literal node is all whitespace. Can rewrite.
|
||||||
|
whitespaceLiteral = SyntaxFactory.MarkupTextLiteral(literal.LiteralTokens);
|
||||||
|
rewritten = codeBlock.ReplaceNode(literal, newNode: null);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return newBlock.Build();
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,14 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||||
|
using Microsoft.AspNetCore.Razor.Language.Syntax;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language
|
namespace Microsoft.AspNetCore.Razor.Language
|
||||||
{
|
{
|
||||||
public abstract class RazorSyntaxTree
|
public abstract class RazorSyntaxTree
|
||||||
{
|
{
|
||||||
internal static RazorSyntaxTree Create(
|
internal static RazorSyntaxTree Create(
|
||||||
Block root,
|
SyntaxNode root,
|
||||||
RazorSourceDocument source,
|
RazorSourceDocument source,
|
||||||
IEnumerable<RazorDiagnostic> diagnostics,
|
IEnumerable<RazorDiagnostic> diagnostics,
|
||||||
RazorParserOptions options)
|
RazorParserOptions options)
|
||||||
|
|
@ -63,7 +64,7 @@ namespace Microsoft.AspNetCore.Razor.Language
|
||||||
|
|
||||||
public abstract RazorParserOptions Options { get; }
|
public abstract RazorParserOptions Options { get; }
|
||||||
|
|
||||||
internal abstract Block Root { get; }
|
internal abstract SyntaxNode Root { get; }
|
||||||
|
|
||||||
public abstract RazorSourceDocument Source { get; }
|
public abstract RazorSourceDocument Source { get; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||||
|
using Microsoft.AspNetCore.Razor.Language.Syntax;
|
||||||
using Microsoft.Extensions.Internal;
|
using Microsoft.Extensions.Internal;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language
|
namespace Microsoft.AspNetCore.Razor.Language
|
||||||
|
|
@ -51,15 +52,15 @@ namespace Microsoft.AspNetCore.Razor.Language
|
||||||
|
|
||||||
public string NewText { get; }
|
public string NewText { get; }
|
||||||
|
|
||||||
internal string GetEditedContent(Span span)
|
internal string GetEditedContent(SyntaxNode node)
|
||||||
{
|
{
|
||||||
if (span == null)
|
if (node == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(span));
|
throw new ArgumentNullException(nameof(node));
|
||||||
}
|
}
|
||||||
|
|
||||||
var offset = GetOffset(span);
|
var offset = GetOffset(node);
|
||||||
return GetEditedContent(span.Content, offset);
|
return GetEditedContent(node.GetContent(), offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal string GetEditedContent(string text, int offset)
|
internal string GetEditedContent(string text, int offset)
|
||||||
|
|
@ -72,41 +73,41 @@ namespace Microsoft.AspNetCore.Razor.Language
|
||||||
return text.Remove(offset, Span.Length).Insert(offset, NewText);
|
return text.Remove(offset, Span.Length).Insert(offset, NewText);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal int GetOffset(Span span)
|
internal int GetOffset(SyntaxNode node)
|
||||||
{
|
{
|
||||||
if (span == null)
|
if (node == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(span));
|
throw new ArgumentNullException(nameof(node));
|
||||||
}
|
}
|
||||||
|
|
||||||
var start = Span.AbsoluteIndex;
|
var start = Span.AbsoluteIndex;
|
||||||
var end = Span.AbsoluteIndex + Span.Length;
|
var end = Span.AbsoluteIndex + Span.Length;
|
||||||
|
|
||||||
if (start < span.Start.AbsoluteIndex ||
|
if (start < node.Position ||
|
||||||
start > span.Start.AbsoluteIndex + span.Length ||
|
start > node.EndPosition ||
|
||||||
end < span.Start.AbsoluteIndex ||
|
end < node.Position ||
|
||||||
end > span.Start.AbsoluteIndex + span.Length)
|
end > node.EndPosition)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException(Resources.FormatInvalidOperation_SpanIsNotChangeOwner(span, this));
|
throw new InvalidOperationException(Resources.FormatInvalidOperation_SpanIsNotChangeOwner(node, this));
|
||||||
}
|
}
|
||||||
|
|
||||||
return start - span.Start.AbsoluteIndex;
|
return start - node.Position;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal string GetOriginalText(Span span)
|
internal string GetOriginalText(SyntaxNode node)
|
||||||
{
|
{
|
||||||
if (span == null)
|
if (node == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(span));
|
throw new ArgumentNullException(nameof(node));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (span.Length == 0)
|
if (node.FullWidth == 0)
|
||||||
{
|
{
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
var offset = GetOffset(span);
|
var offset = GetOffset(node);
|
||||||
return span.Content.Substring(offset, Span.Length);
|
return node.GetContent().Substring(offset, Span.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Equals(SourceChange other)
|
public bool Equals(SourceChange other)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,710 @@
|
||||||
|
// 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;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.Extensions.Internal;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
||||||
|
{
|
||||||
|
internal readonly struct ChildSyntaxList : IEquatable<ChildSyntaxList>, IReadOnlyList<SyntaxNode>
|
||||||
|
{
|
||||||
|
private readonly SyntaxNode _node;
|
||||||
|
private readonly int _count;
|
||||||
|
|
||||||
|
internal ChildSyntaxList(SyntaxNode node)
|
||||||
|
{
|
||||||
|
_node = node;
|
||||||
|
_count = CountNodes(node.Green);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the number of children contained in the <see cref="ChildSyntaxList"/>.
|
||||||
|
/// </summary>
|
||||||
|
public int Count
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static int CountNodes(GreenNode green)
|
||||||
|
{
|
||||||
|
var n = 0;
|
||||||
|
|
||||||
|
for (int i = 0, s = green.SlotCount; i < s; i++)
|
||||||
|
{
|
||||||
|
var child = green.GetSlot(i);
|
||||||
|
if (child != null)
|
||||||
|
{
|
||||||
|
if (!child.IsList)
|
||||||
|
{
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
n += child.SlotCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Gets the child at the specified index.</summary>
|
||||||
|
/// <param name="index">The zero-based index of the child to get.</param>
|
||||||
|
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||||
|
/// <paramref name="index"/> is less than 0.-or-<paramref name="index" /> is equal to or greater than <see cref="ChildSyntaxList.Count"/>. </exception>
|
||||||
|
public SyntaxNode this[int index]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (unchecked((uint)index < (uint)_count))
|
||||||
|
{
|
||||||
|
return ItemInternal(_node, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal SyntaxNode Node
|
||||||
|
{
|
||||||
|
get { return _node; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int Occupancy(GreenNode green)
|
||||||
|
{
|
||||||
|
return green.IsList ? green.SlotCount : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// internal indexer that does not verify index.
|
||||||
|
/// Used when caller has already ensured that index is within bounds.
|
||||||
|
/// </summary>
|
||||||
|
internal static SyntaxNode ItemInternal(SyntaxNode node, int index)
|
||||||
|
{
|
||||||
|
GreenNode greenChild;
|
||||||
|
var green = node.Green;
|
||||||
|
var idx = index;
|
||||||
|
var slotIndex = 0;
|
||||||
|
var position = node.Position;
|
||||||
|
|
||||||
|
// find a slot that contains the node or its parent list (if node is in a list)
|
||||||
|
// we will be skipping whole slots here so we will not loop for long
|
||||||
|
//
|
||||||
|
// at the end of this loop we will have
|
||||||
|
// 1) slot index - slotIdx
|
||||||
|
// 2) if the slot is a list, node index in the list - idx
|
||||||
|
// 3) slot position - position
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
greenChild = green.GetSlot(slotIndex);
|
||||||
|
if (greenChild != null)
|
||||||
|
{
|
||||||
|
var currentOccupancy = Occupancy(greenChild);
|
||||||
|
if (idx < currentOccupancy)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
idx -= currentOccupancy;
|
||||||
|
position += greenChild.FullWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
slotIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get node that represents this slot
|
||||||
|
var red = node.GetNodeSlot(slotIndex);
|
||||||
|
if (!greenChild.IsList)
|
||||||
|
{
|
||||||
|
// this is a single node
|
||||||
|
// if it is a node, we are done
|
||||||
|
if (red != null)
|
||||||
|
{
|
||||||
|
return red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (red != null)
|
||||||
|
{
|
||||||
|
// it is a red list of nodes (separated or not), most common case
|
||||||
|
var redChild = red.GetNodeSlot(idx);
|
||||||
|
if (redChild != null)
|
||||||
|
{
|
||||||
|
// this is our node
|
||||||
|
return redChild;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Locate the node that is a child of the given <see cref="SyntaxNode"/> and contains the given position.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="node">The <see cref="SyntaxNode"/> to search.</param>
|
||||||
|
/// <param name="targetPosition">The position.</param>
|
||||||
|
/// <returns>The node that spans the given position.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// Assumes that <paramref name="targetPosition"/> is within the span of <paramref name="node"/>.
|
||||||
|
/// </remarks>
|
||||||
|
internal static SyntaxNode ChildThatContainsPosition(SyntaxNode node, int targetPosition)
|
||||||
|
{
|
||||||
|
// The targetPosition must already be within this node
|
||||||
|
Debug.Assert(node.FullSpan.Contains(targetPosition));
|
||||||
|
|
||||||
|
var green = node.Green;
|
||||||
|
var position = node.Position;
|
||||||
|
var index = 0;
|
||||||
|
|
||||||
|
Debug.Assert(!green.IsList);
|
||||||
|
|
||||||
|
// Find the green node that spans the target position.
|
||||||
|
// We will be skipping whole slots here so we will not loop for long
|
||||||
|
int slot;
|
||||||
|
for (slot = 0; ; slot++)
|
||||||
|
{
|
||||||
|
var greenChild = green.GetSlot(slot);
|
||||||
|
if (greenChild != null)
|
||||||
|
{
|
||||||
|
var endPosition = position + greenChild.FullWidth;
|
||||||
|
if (targetPosition < endPosition)
|
||||||
|
{
|
||||||
|
// Descend into the child element
|
||||||
|
green = greenChild;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
position = endPosition;
|
||||||
|
index += Occupancy(greenChild);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Realize the red node (if any)
|
||||||
|
var red = node.GetNodeSlot(slot);
|
||||||
|
if (!green.IsList)
|
||||||
|
{
|
||||||
|
// This is a single node.
|
||||||
|
// If it is a node, we are done.
|
||||||
|
if (red != null)
|
||||||
|
{
|
||||||
|
return red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
slot = green.FindSlotIndexContainingOffset(targetPosition - position);
|
||||||
|
|
||||||
|
// Realize the red node (if any)
|
||||||
|
if (red != null)
|
||||||
|
{
|
||||||
|
// It is a red list of nodes
|
||||||
|
red = red.GetNodeSlot(slot);
|
||||||
|
if (red != null)
|
||||||
|
{
|
||||||
|
return red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since we can't have "lists of lists", the Occupancy calculation for
|
||||||
|
// child elements in a list is simple.
|
||||||
|
index += slot;
|
||||||
|
}
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// internal indexer that does not verify index.
|
||||||
|
/// Used when caller has already ensured that index is within bounds.
|
||||||
|
/// </summary>
|
||||||
|
internal static SyntaxNode ItemInternalAsNode(SyntaxNode node, int index)
|
||||||
|
{
|
||||||
|
GreenNode greenChild;
|
||||||
|
var green = node.Green;
|
||||||
|
var idx = index;
|
||||||
|
var slotIndex = 0;
|
||||||
|
|
||||||
|
// find a slot that contains the node or its parent list (if node is in a list)
|
||||||
|
// we will be skipping whole slots here so we will not loop for long
|
||||||
|
//
|
||||||
|
// at the end of this loop we will have
|
||||||
|
// 1) slot index - slotIdx
|
||||||
|
// 2) if the slot is a list, node index in the list - idx
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
greenChild = green.GetSlot(slotIndex);
|
||||||
|
if (greenChild != null)
|
||||||
|
{
|
||||||
|
var currentOccupancy = Occupancy(greenChild);
|
||||||
|
if (idx < currentOccupancy)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
idx -= currentOccupancy;
|
||||||
|
}
|
||||||
|
|
||||||
|
slotIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get node that represents this slot
|
||||||
|
var red = node.GetNodeSlot(slotIndex);
|
||||||
|
if (greenChild.IsList && red != null)
|
||||||
|
{
|
||||||
|
// it is a red list of nodes, most common case
|
||||||
|
return red.GetNodeSlot(idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is a single node
|
||||||
|
return red;
|
||||||
|
}
|
||||||
|
|
||||||
|
// for debugging
|
||||||
|
private SyntaxNode[] Nodes
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Any()
|
||||||
|
{
|
||||||
|
return _count != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the first child in the list.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The first child in the list.</returns>
|
||||||
|
/// <exception cref="System.InvalidOperationException">The list is empty.</exception>
|
||||||
|
public SyntaxNode First()
|
||||||
|
{
|
||||||
|
if (Any())
|
||||||
|
{
|
||||||
|
return this[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the last child in the list.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The last child in the list.</returns>
|
||||||
|
/// <exception cref="System.InvalidOperationException">The list is empty.</exception>
|
||||||
|
public SyntaxNode Last()
|
||||||
|
{
|
||||||
|
if (Any())
|
||||||
|
{
|
||||||
|
return this[_count - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a list which contains all children of <see cref="ChildSyntaxList"/> in reversed order.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns><see cref="Reversed"/> which contains all children of <see cref="ChildSyntaxList"/> in reversed order</returns>
|
||||||
|
public Reversed Reverse()
|
||||||
|
{
|
||||||
|
return new Reversed(_node, _count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Returns an enumerator that iterates through the <see cref="ChildSyntaxList"/>.</summary>
|
||||||
|
/// <returns>A <see cref="Enumerator"/> for the <see cref="ChildSyntaxList"/>.</returns>
|
||||||
|
public Enumerator GetEnumerator()
|
||||||
|
{
|
||||||
|
if (_node == null)
|
||||||
|
{
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Enumerator(_node, _count);
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator<SyntaxNode> IEnumerable<SyntaxNode>.GetEnumerator()
|
||||||
|
{
|
||||||
|
if (_node == null)
|
||||||
|
{
|
||||||
|
return EmptyEnumerator<SyntaxNode>.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new EnumeratorImpl(_node, _count);
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
if (_node == null)
|
||||||
|
{
|
||||||
|
return EmptyEnumerator<SyntaxNode>.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new EnumeratorImpl(_node, _count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Determines whether the specified object is equal to the current instance.</summary>
|
||||||
|
/// <returns>true if the specified object is a <see cref="ChildSyntaxList" /> structure and is equal to the current instance; otherwise, false.</returns>
|
||||||
|
/// <param name="obj">The object to be compared with the current instance.</param>
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
return obj is ChildSyntaxList && Equals((ChildSyntaxList)obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Determines whether the specified <see cref="ChildSyntaxList" /> structure is equal to the current instance.</summary>
|
||||||
|
/// <returns>true if the specified <see cref="ChildSyntaxList" /> structure is equal to the current instance; otherwise, false.</returns>
|
||||||
|
/// <param name="other">The <see cref="ChildSyntaxList" /> structure to be compared with the current instance.</param>
|
||||||
|
public bool Equals(ChildSyntaxList other)
|
||||||
|
{
|
||||||
|
return _node == other._node;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Returns the hash code for the current instance.</summary>
|
||||||
|
/// <returns>A 32-bit signed integer hash code.</returns>
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return _node?.GetHashCode() ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Indicates whether two <see cref="ChildSyntaxList" /> structures are equal.</summary>
|
||||||
|
/// <returns>true if <paramref name="list1" /> is equal to <paramref name="list2" />; otherwise, false.</returns>
|
||||||
|
/// <param name="list1">The <see cref="ChildSyntaxList" /> structure on the left side of the equality operator.</param>
|
||||||
|
/// <param name="list2">The <see cref="ChildSyntaxList" /> structure on the right side of the equality operator.</param>
|
||||||
|
public static bool operator ==(ChildSyntaxList list1, ChildSyntaxList list2)
|
||||||
|
{
|
||||||
|
return list1.Equals(list2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Indicates whether two <see cref="ChildSyntaxList" /> structures are unequal.</summary>
|
||||||
|
/// <returns>true if <paramref name="list1" /> is equal to <paramref name="list2" />; otherwise, false.</returns>
|
||||||
|
/// <param name="list1">The <see cref="ChildSyntaxList" /> structure on the left side of the inequality operator.</param>
|
||||||
|
/// <param name="list2">The <see cref="ChildSyntaxList" /> structure on the right side of the inequality operator.</param>
|
||||||
|
public static bool operator !=(ChildSyntaxList list1, ChildSyntaxList list2)
|
||||||
|
{
|
||||||
|
return !list1.Equals(list2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Enumerates the elements of a <see cref="ChildSyntaxList" />.</summary>
|
||||||
|
public struct Enumerator
|
||||||
|
{
|
||||||
|
private SyntaxNode _node;
|
||||||
|
private int _count;
|
||||||
|
private int _childIndex;
|
||||||
|
|
||||||
|
internal Enumerator(SyntaxNode node, int count)
|
||||||
|
{
|
||||||
|
_node = node;
|
||||||
|
_count = count;
|
||||||
|
_childIndex = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// PERF: Initialize an Enumerator directly from a SyntaxNode without going
|
||||||
|
// via ChildNodes. This saves constructing an intermediate ChildSyntaxList
|
||||||
|
internal void InitializeFrom(SyntaxNode node)
|
||||||
|
{
|
||||||
|
_node = node;
|
||||||
|
_count = CountNodes(node.Green);
|
||||||
|
_childIndex = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Advances the enumerator to the next element of the <see cref="ChildSyntaxList" />.</summary>
|
||||||
|
/// <returns>true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.</returns>
|
||||||
|
public bool MoveNext()
|
||||||
|
{
|
||||||
|
var newIndex = _childIndex + 1;
|
||||||
|
if (newIndex < _count)
|
||||||
|
{
|
||||||
|
_childIndex = newIndex;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Gets the element at the current position of the enumerator.</summary>
|
||||||
|
/// <returns>The element in the <see cref="ChildSyntaxList" /> at the current position of the enumerator.</returns>
|
||||||
|
public SyntaxNode Current
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return ItemInternal(_node, _childIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Sets the enumerator to its initial position, which is before the first element in the collection.</summary>
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
_childIndex = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool TryMoveNextAndGetCurrent(out SyntaxNode current)
|
||||||
|
{
|
||||||
|
if (!MoveNext())
|
||||||
|
{
|
||||||
|
current = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
current = ItemInternal(_node, _childIndex);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal SyntaxNode TryMoveNextAndGetCurrentAsNode()
|
||||||
|
{
|
||||||
|
while (MoveNext())
|
||||||
|
{
|
||||||
|
var nodeValue = ItemInternalAsNode(_node, _childIndex);
|
||||||
|
if (nodeValue != null)
|
||||||
|
{
|
||||||
|
return nodeValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class EnumeratorImpl : IEnumerator<SyntaxNode>
|
||||||
|
{
|
||||||
|
private Enumerator _enumerator;
|
||||||
|
|
||||||
|
internal EnumeratorImpl(SyntaxNode node, int count)
|
||||||
|
{
|
||||||
|
_enumerator = new Enumerator(node, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the element in the collection at the current position of the enumerator.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// The element in the collection at the current position of the enumerator.
|
||||||
|
/// </returns>
|
||||||
|
public SyntaxNode Current
|
||||||
|
{
|
||||||
|
get { return _enumerator.Current; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the element in the collection at the current position of the enumerator.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// The element in the collection at the current position of the enumerator.
|
||||||
|
/// </returns>
|
||||||
|
object IEnumerator.Current
|
||||||
|
{
|
||||||
|
get { return _enumerator.Current; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Advances the enumerator to the next element of the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.
|
||||||
|
/// </returns>
|
||||||
|
public bool MoveNext()
|
||||||
|
{
|
||||||
|
return _enumerator.MoveNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the enumerator to its initial position, which is before the first element in the collection.
|
||||||
|
/// </summary>
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
_enumerator.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{ }
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly partial struct Reversed : IEnumerable<SyntaxNode>, IEquatable<Reversed>
|
||||||
|
{
|
||||||
|
private readonly SyntaxNode _node;
|
||||||
|
private readonly int _count;
|
||||||
|
|
||||||
|
internal Reversed(SyntaxNode node, int count)
|
||||||
|
{
|
||||||
|
_node = node;
|
||||||
|
_count = count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Enumerator GetEnumerator()
|
||||||
|
{
|
||||||
|
return new Enumerator(_node, _count);
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator<SyntaxNode> IEnumerable<SyntaxNode>.GetEnumerator()
|
||||||
|
{
|
||||||
|
if (_node == null)
|
||||||
|
{
|
||||||
|
return EmptyEnumerator<SyntaxNode>.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new EnumeratorImpl(_node, _count);
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
if (_node == null)
|
||||||
|
{
|
||||||
|
return EmptyEnumerator<SyntaxNode>.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new EnumeratorImpl(_node, _count);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
if (_node == null)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var hash = HashCodeCombiner.Start();
|
||||||
|
hash.Add(_node.GetHashCode());
|
||||||
|
hash.Add(_count);
|
||||||
|
return hash.CombinedHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
return (obj is Reversed) && Equals((Reversed)obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(Reversed other)
|
||||||
|
{
|
||||||
|
return _node == other._node
|
||||||
|
&& _count == other._count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct Enumerator
|
||||||
|
{
|
||||||
|
private readonly SyntaxNode _node;
|
||||||
|
private readonly int _count;
|
||||||
|
private int _childIndex;
|
||||||
|
|
||||||
|
internal Enumerator(SyntaxNode node, int count)
|
||||||
|
{
|
||||||
|
_node = node;
|
||||||
|
_count = count;
|
||||||
|
_childIndex = count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool MoveNext()
|
||||||
|
{
|
||||||
|
return --_childIndex >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SyntaxNode Current
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return ItemInternal(_node, _childIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
_childIndex = _count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class EnumeratorImpl : IEnumerator<SyntaxNode>
|
||||||
|
{
|
||||||
|
private Enumerator _enumerator;
|
||||||
|
|
||||||
|
internal EnumeratorImpl(SyntaxNode node, int count)
|
||||||
|
{
|
||||||
|
_enumerator = new Enumerator(node, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the element in the collection at the current position of the enumerator.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// The element in the collection at the current position of the enumerator.
|
||||||
|
/// </returns>
|
||||||
|
public SyntaxNode Current
|
||||||
|
{
|
||||||
|
get { return _enumerator.Current; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the element in the collection at the current position of the enumerator.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// The element in the collection at the current position of the enumerator.
|
||||||
|
/// </returns>
|
||||||
|
object IEnumerator.Current
|
||||||
|
{
|
||||||
|
get { return _enumerator.Current; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Advances the enumerator to the next element of the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.
|
||||||
|
/// </returns>
|
||||||
|
/// <exception cref="InvalidOperationException">The collection was modified after the enumerator was created. </exception>
|
||||||
|
public bool MoveNext()
|
||||||
|
{
|
||||||
|
return _enumerator.MoveNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the enumerator to its initial position, which is before the first element in the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="InvalidOperationException">The collection was modified after the enumerator was created. </exception>
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
_enumerator.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class EmptyEnumerator<T> : IEnumerator<T>
|
||||||
|
{
|
||||||
|
public static readonly IEnumerator<T> Instance = new EmptyEnumerator<T>();
|
||||||
|
|
||||||
|
protected EmptyEnumerator()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public T Current => throw new InvalidOperationException();
|
||||||
|
|
||||||
|
object IEnumerator.Current => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool MoveNext()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -19,8 +19,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
||||||
new ConditionalWeakTable<GreenNode, RazorDiagnostic[]>();
|
new ConditionalWeakTable<GreenNode, RazorDiagnostic[]>();
|
||||||
private static readonly ConditionalWeakTable<GreenNode, SyntaxAnnotation[]> AnnotationsTable =
|
private static readonly ConditionalWeakTable<GreenNode, SyntaxAnnotation[]> AnnotationsTable =
|
||||||
new ConditionalWeakTable<GreenNode, SyntaxAnnotation[]>();
|
new ConditionalWeakTable<GreenNode, SyntaxAnnotation[]>();
|
||||||
|
|
||||||
private NodeFlags _flags;
|
|
||||||
private byte _slotCount;
|
private byte _slotCount;
|
||||||
|
|
||||||
protected GreenNode(SyntaxKind kind)
|
protected GreenNode(SyntaxKind kind)
|
||||||
|
|
@ -44,7 +42,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
||||||
{
|
{
|
||||||
if (diagnostics?.Length > 0)
|
if (diagnostics?.Length > 0)
|
||||||
{
|
{
|
||||||
_flags |= NodeFlags.ContainsDiagnostics;
|
Flags |= NodeFlags.ContainsDiagnostics;
|
||||||
DiagnosticsTable.Add(this, diagnostics);
|
DiagnosticsTable.Add(this, diagnostics);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,7 +56,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_flags |= NodeFlags.ContainsAnnotations;
|
Flags |= NodeFlags.ContainsAnnotations;
|
||||||
AnnotationsTable.Add(this, annotations);
|
AnnotationsTable.Add(this, annotations);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -70,7 +68,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_flags |= (node.Flags & NodeFlags.InheritMask);
|
Flags |= (node.Flags & NodeFlags.InheritMask);
|
||||||
FullWidth += node.FullWidth;
|
FullWidth += node.FullWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -150,25 +148,25 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Flags
|
#region Flags
|
||||||
internal NodeFlags Flags => _flags;
|
public NodeFlags Flags { get; protected set; }
|
||||||
|
|
||||||
internal void SetFlags(NodeFlags flags)
|
internal void SetFlags(NodeFlags flags)
|
||||||
{
|
{
|
||||||
_flags |= flags;
|
Flags |= flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void ClearFlags(NodeFlags flags)
|
internal void ClearFlags(NodeFlags flags)
|
||||||
{
|
{
|
||||||
_flags &= ~flags;
|
Flags &= ~flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal virtual bool IsMissing => (_flags & NodeFlags.IsMissing) != 0;
|
internal virtual bool IsMissing => (Flags & NodeFlags.IsMissing) != 0;
|
||||||
|
|
||||||
public bool ContainsDiagnostics
|
public bool ContainsDiagnostics
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return (_flags & NodeFlags.ContainsDiagnostics) != 0;
|
return (Flags & NodeFlags.ContainsDiagnostics) != 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -176,7 +174,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return (_flags & NodeFlags.ContainsAnnotations) != 0;
|
return (Flags & NodeFlags.ContainsAnnotations) != 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
@ -255,6 +253,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Text
|
#region Text
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
builder.AppendFormat("{0}<{1}>", GetType().Name, Kind);
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
public virtual string ToFullString()
|
public virtual string ToFullString()
|
||||||
{
|
{
|
||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
|
|
@ -342,7 +348,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
||||||
for (int i = 0, n = node.SlotCount; i < n; i++)
|
for (int i = 0, n = node.SlotCount; i < n; i++)
|
||||||
{
|
{
|
||||||
var child = node.GetSlot(i);
|
var child = node.GetSlot(i);
|
||||||
if (child != null)
|
if (child != null && child.FullWidth > 0)
|
||||||
{
|
{
|
||||||
firstChild = child;
|
firstChild = child;
|
||||||
break;
|
break;
|
||||||
|
|
@ -364,7 +370,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
||||||
for (var i = node.SlotCount - 1; i >= 0; i--)
|
for (var i = node.SlotCount - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
var child = node.GetSlot(i);
|
var child = node.GetSlot(i);
|
||||||
if (child != null)
|
if (child != null && child.FullWidth > 0)
|
||||||
{
|
{
|
||||||
lastChild = child;
|
lastChild = child;
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// 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.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
||||||
{
|
{
|
||||||
|
|
@ -19,7 +21,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
||||||
return new InternalSyntax.SyntaxList<T>(node);
|
return new InternalSyntax.SyntaxList<T>(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TNode WithAnnotationsGreen<TNode>(this TNode node, IEnumerable<SyntaxAnnotation> annotations) where TNode : GreenNode
|
public static TNode WithAnnotationsGreen<TNode>(this TNode node, params SyntaxAnnotation[] annotations) where TNode : GreenNode
|
||||||
{
|
{
|
||||||
var newAnnotations = new List<SyntaxAnnotation>();
|
var newAnnotations = new List<SyntaxAnnotation>();
|
||||||
foreach (var candidate in annotations)
|
foreach (var candidate in annotations)
|
||||||
|
|
@ -48,7 +50,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TNode WithDiagnosticsGreen<TNode>(this TNode node, RazorDiagnostic[] diagnostics) where TNode : GreenNode
|
public static TNode WithDiagnosticsGreen<TNode>(this TNode node, params RazorDiagnostic[] diagnostics) where TNode : GreenNode
|
||||||
{
|
{
|
||||||
return (TNode)node.SetDiagnostics(diagnostics);
|
return (TNode)node.SetDiagnostics(diagnostics);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 RazorDirectiveSyntax
|
||||||
|
{
|
||||||
|
private static readonly string DirectiveDescriptorKey = typeof(DirectiveDescriptor).Name;
|
||||||
|
|
||||||
|
public DirectiveDescriptor DirectiveDescriptor
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var annotation = GetAnnotations().FirstOrDefault(n => n.Kind == DirectiveDescriptorKey);
|
||||||
|
return annotation?.Data as DirectiveDescriptor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public RazorDirectiveSyntax WithDirectiveDescriptor(DirectiveDescriptor descriptor)
|
||||||
|
{
|
||||||
|
var annotations = new List<SyntaxAnnotation>(GetAnnotations())
|
||||||
|
{
|
||||||
|
new SyntaxAnnotation(DirectiveDescriptorKey, descriptor)
|
||||||
|
};
|
||||||
|
|
||||||
|
var newGreen = this.WithAnnotationsGreen(annotations.ToArray());
|
||||||
|
|
||||||
|
return newGreen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax
|
||||||
|
{
|
||||||
|
internal abstract partial class RazorSyntaxNode : GreenNode
|
||||||
|
{
|
||||||
|
protected RazorSyntaxNode(SyntaxKind kind) : base(kind)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected RazorSyntaxNode(SyntaxKind kind, int fullWidth)
|
||||||
|
: base(kind, fullWidth)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected RazorSyntaxNode(SyntaxKind kind, RazorDiagnostic[] diagnostics, SyntaxAnnotation[] annotations)
|
||||||
|
: base(kind, diagnostics, annotations)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected RazorSyntaxNode(SyntaxKind kind, int fullWidth, RazorDiagnostic[] diagnostics, SyntaxAnnotation[] annotations)
|
||||||
|
: base(kind, fullWidth, diagnostics, annotations)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,5 +17,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax
|
||||||
{
|
{
|
||||||
return new SyntaxToken(kind, content, diagnostics);
|
return new SyntaxToken(kind, content, diagnostics);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static SyntaxToken MissingToken(SyntaxKind kind, params RazorDiagnostic[] diagnostics)
|
||||||
|
{
|
||||||
|
return SyntaxToken.CreateMissing(kind, diagnostics);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax
|
namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax
|
||||||
{
|
{
|
||||||
internal readonly struct SyntaxList<TNode>
|
internal struct SyntaxList<TNode> : IEquatable<SyntaxList<TNode>>
|
||||||
where TNode : GreenNode
|
where TNode : GreenNode
|
||||||
{
|
{
|
||||||
private readonly GreenNode _node;
|
private readonly GreenNode _node;
|
||||||
|
|
@ -15,13 +16,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax
|
||||||
_node = node;
|
_node = node;
|
||||||
}
|
}
|
||||||
|
|
||||||
public GreenNode Node
|
internal GreenNode Node => _node;
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return ((GreenNode)_node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Count
|
public int Count
|
||||||
{
|
{
|
||||||
|
|
@ -31,33 +26,30 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public TNode Last
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var node = _node;
|
|
||||||
if (node.IsList)
|
|
||||||
{
|
|
||||||
return ((TNode)node.GetSlot(node.SlotCount - 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
return ((TNode)node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Not Implemented: Default */
|
|
||||||
public TNode this[int index]
|
public TNode this[int index]
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
var node = _node;
|
if (_node == null)
|
||||||
if (node.IsList)
|
|
||||||
{
|
{
|
||||||
return ((TNode)node.GetSlot(index));
|
return null;
|
||||||
}
|
}
|
||||||
|
else if (_node.IsList)
|
||||||
|
{
|
||||||
|
Debug.Assert(index >= 0);
|
||||||
|
Debug.Assert(index <= _node.SlotCount);
|
||||||
|
|
||||||
Debug.Assert(index == 0);
|
return ((TNode)_node.GetSlot(index));
|
||||||
return ((TNode)node);
|
}
|
||||||
|
else if (index == 0)
|
||||||
|
{
|
||||||
|
Debug.Assert(index == 0);
|
||||||
|
return ((TNode)_node);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("This program location is thought to be unreachable.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,6 +98,25 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TNode Last
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var node = _node;
|
||||||
|
if (node.IsList)
|
||||||
|
{
|
||||||
|
return ((TNode)node.GetSlot(node.SlotCount - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ((TNode)node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Enumerator GetEnumerator()
|
||||||
|
{
|
||||||
|
return new Enumerator(this);
|
||||||
|
}
|
||||||
|
|
||||||
public static bool operator ==(SyntaxList<TNode> left, SyntaxList<TNode> right)
|
public static bool operator ==(SyntaxList<TNode> left, SyntaxList<TNode> right)
|
||||||
{
|
{
|
||||||
return (left._node == right._node);
|
return (left._node == right._node);
|
||||||
|
|
@ -116,6 +127,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax
|
||||||
return !(left._node == right._node);
|
return !(left._node == right._node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool Equals(SyntaxList<TNode> other)
|
||||||
|
{
|
||||||
|
return _node == other._node;
|
||||||
|
}
|
||||||
|
|
||||||
public override bool Equals(object obj)
|
public override bool Equals(object obj)
|
||||||
{
|
{
|
||||||
return (obj is SyntaxList<TNode> && (_node == ((SyntaxList<TNode>)obj)._node));
|
return (obj is SyntaxList<TNode> && (_node == ((SyntaxList<TNode>)obj)._node));
|
||||||
|
|
@ -135,5 +151,37 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax
|
||||||
{
|
{
|
||||||
return new SyntaxList<GreenNode>(nodes._node);
|
return new SyntaxList<GreenNode>(nodes._node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal struct Enumerator
|
||||||
|
{
|
||||||
|
private SyntaxList<TNode> _list;
|
||||||
|
private int _index;
|
||||||
|
|
||||||
|
internal Enumerator(SyntaxList<TNode> list)
|
||||||
|
{
|
||||||
|
_list = list;
|
||||||
|
_index = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool MoveNext()
|
||||||
|
{
|
||||||
|
var newIndex = _index + 1;
|
||||||
|
if (newIndex < _list.Count)
|
||||||
|
{
|
||||||
|
_index = newIndex;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TNode Current
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _list[_index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax
|
||||||
|
{
|
||||||
|
internal class SyntaxListPool
|
||||||
|
{
|
||||||
|
private ArrayElement<SyntaxListBuilder>[] _freeList = new ArrayElement<SyntaxListBuilder>[10];
|
||||||
|
private int _freeIndex;
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
private readonly List<SyntaxListBuilder> _allocated = new List<SyntaxListBuilder>();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
internal SyntaxListPool()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal SyntaxListBuilder Allocate()
|
||||||
|
{
|
||||||
|
SyntaxListBuilder item;
|
||||||
|
if (_freeIndex > 0)
|
||||||
|
{
|
||||||
|
_freeIndex--;
|
||||||
|
item = _freeList[_freeIndex].Value;
|
||||||
|
_freeList[_freeIndex].Value = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
item = new SyntaxListBuilder(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
Debug.Assert(!_allocated.Contains(item));
|
||||||
|
_allocated.Add(item);
|
||||||
|
#endif
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal PooledResult<TNode> Allocate<TNode>() where TNode : GreenNode
|
||||||
|
{
|
||||||
|
var builder = new SyntaxListBuilder<TNode>(this.Allocate());
|
||||||
|
return new PooledResult<TNode>(this, builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Free(SyntaxListBuilder item)
|
||||||
|
{
|
||||||
|
item.Clear();
|
||||||
|
if (_freeIndex >= _freeList.Length)
|
||||||
|
{
|
||||||
|
this.Grow();
|
||||||
|
}
|
||||||
|
#if DEBUG
|
||||||
|
Debug.Assert(_allocated.Contains(item));
|
||||||
|
|
||||||
|
_allocated.Remove(item);
|
||||||
|
#endif
|
||||||
|
_freeList[_freeIndex].Value = item;
|
||||||
|
_freeIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Grow()
|
||||||
|
{
|
||||||
|
var tmp = new ArrayElement<SyntaxListBuilder>[_freeList.Length * 2];
|
||||||
|
Array.Copy(_freeList, tmp, _freeList.Length);
|
||||||
|
_freeList = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SyntaxList<TNode> ToListAndFree<TNode>(SyntaxListBuilder<TNode> item)
|
||||||
|
where TNode : GreenNode
|
||||||
|
{
|
||||||
|
var list = item.ToList();
|
||||||
|
Free(item);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct PooledResult<TNode> : IDisposable where TNode : GreenNode
|
||||||
|
{
|
||||||
|
private readonly SyntaxListBuilder<TNode> _builder;
|
||||||
|
private readonly SyntaxListPool _pool;
|
||||||
|
|
||||||
|
public PooledResult(SyntaxListPool pool, in SyntaxListBuilder<TNode> builder)
|
||||||
|
{
|
||||||
|
_pool = pool;
|
||||||
|
_builder = builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SyntaxListBuilder<TNode> Builder => _builder;
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_pool.Free(_builder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax
|
||||||
|
{
|
||||||
|
internal abstract partial class SyntaxRewriter : SyntaxVisitor<GreenNode>
|
||||||
|
{
|
||||||
|
public override GreenNode VisitToken(SyntaxToken token)
|
||||||
|
{
|
||||||
|
var leading = VisitList(token.LeadingTrivia);
|
||||||
|
var trailing = VisitList(token.TrailingTrivia);
|
||||||
|
|
||||||
|
if (leading != token.LeadingTrivia || trailing != token.TrailingTrivia)
|
||||||
|
{
|
||||||
|
if (leading != token.LeadingTrivia)
|
||||||
|
{
|
||||||
|
token = token.TokenWithLeadingTrivia(leading.Node);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trailing != token.TrailingTrivia)
|
||||||
|
{
|
||||||
|
token = token.TokenWithTrailingTrivia(trailing.Node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SyntaxList<TNode> VisitList<TNode>(SyntaxList<TNode> list) where TNode : GreenNode
|
||||||
|
{
|
||||||
|
SyntaxListBuilder alternate = null;
|
||||||
|
for (int i = 0, n = list.Count; i < n; i++)
|
||||||
|
{
|
||||||
|
var item = list[i];
|
||||||
|
var visited = Visit(item);
|
||||||
|
if (item != visited && alternate == null)
|
||||||
|
{
|
||||||
|
alternate = new SyntaxListBuilder(n);
|
||||||
|
alternate.AddRange(list, 0, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alternate != null)
|
||||||
|
{
|
||||||
|
alternate.Add(visited);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alternate != null)
|
||||||
|
{
|
||||||
|
return alternate.ToList<TNode>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,14 +2,15 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax
|
namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax
|
||||||
{
|
{
|
||||||
internal class SyntaxToken : GreenNode
|
internal class SyntaxToken : RazorSyntaxNode
|
||||||
{
|
{
|
||||||
|
private readonly GreenNode _leadingTrivia;
|
||||||
|
private readonly GreenNode _trailingTrivia;
|
||||||
|
|
||||||
internal SyntaxToken(SyntaxKind kind, string content, RazorDiagnostic[] diagnostics)
|
internal SyntaxToken(SyntaxKind kind, string content, RazorDiagnostic[] diagnostics)
|
||||||
: base(kind, content.Length, diagnostics, annotations: null)
|
: base(kind, content.Length, diagnostics, annotations: null)
|
||||||
{
|
{
|
||||||
|
|
@ -20,9 +21,9 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax
|
||||||
: base(kind, content.Length)
|
: base(kind, content.Length)
|
||||||
{
|
{
|
||||||
Content = content;
|
Content = content;
|
||||||
LeadingTrivia = leadingTrivia;
|
_leadingTrivia = leadingTrivia;
|
||||||
AdjustFlagsAndWidth(leadingTrivia);
|
AdjustFlagsAndWidth(leadingTrivia);
|
||||||
TrailingTrivia = trailingTrivia;
|
_trailingTrivia = trailingTrivia;
|
||||||
AdjustFlagsAndWidth(trailingTrivia);
|
AdjustFlagsAndWidth(trailingTrivia);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -30,17 +31,23 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax
|
||||||
: base(kind, content.Length, diagnostics, annotations)
|
: base(kind, content.Length, diagnostics, annotations)
|
||||||
{
|
{
|
||||||
Content = content;
|
Content = content;
|
||||||
LeadingTrivia = leadingTrivia;
|
_leadingTrivia = leadingTrivia;
|
||||||
AdjustFlagsAndWidth(leadingTrivia);
|
AdjustFlagsAndWidth(leadingTrivia);
|
||||||
TrailingTrivia = trailingTrivia;
|
_trailingTrivia = trailingTrivia;
|
||||||
AdjustFlagsAndWidth(trailingTrivia);
|
AdjustFlagsAndWidth(trailingTrivia);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Content { get; }
|
public string Content { get; }
|
||||||
|
|
||||||
public GreenNode LeadingTrivia { get; }
|
public SyntaxList<GreenNode> LeadingTrivia
|
||||||
|
{
|
||||||
|
get { return new SyntaxList<GreenNode>(GetLeadingTrivia()); }
|
||||||
|
}
|
||||||
|
|
||||||
public GreenNode TrailingTrivia { get; }
|
public SyntaxList<GreenNode> TrailingTrivia
|
||||||
|
{
|
||||||
|
get { return new SyntaxList<GreenNode>(GetTrailingTrivia()); }
|
||||||
|
}
|
||||||
|
|
||||||
internal override bool IsToken => true;
|
internal override bool IsToken => true;
|
||||||
|
|
||||||
|
|
@ -76,22 +83,22 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax
|
||||||
|
|
||||||
public override sealed GreenNode GetLeadingTrivia()
|
public override sealed GreenNode GetLeadingTrivia()
|
||||||
{
|
{
|
||||||
return LeadingTrivia;
|
return _leadingTrivia;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override int GetLeadingTriviaWidth()
|
public override int GetLeadingTriviaWidth()
|
||||||
{
|
{
|
||||||
return LeadingTrivia == null ? 0 : LeadingTrivia.FullWidth;
|
return _leadingTrivia == null ? 0 : _leadingTrivia.FullWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override sealed GreenNode GetTrailingTrivia()
|
public override sealed GreenNode GetTrailingTrivia()
|
||||||
{
|
{
|
||||||
return TrailingTrivia;
|
return _trailingTrivia;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override int GetTrailingTriviaWidth()
|
public override int GetTrailingTriviaWidth()
|
||||||
{
|
{
|
||||||
return TrailingTrivia == null ? 0 : TrailingTrivia.FullWidth;
|
return _trailingTrivia == null ? 0 : _trailingTrivia.FullWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed override GreenNode WithLeadingTrivia(GreenNode trivia)
|
public sealed override GreenNode WithLeadingTrivia(GreenNode trivia)
|
||||||
|
|
@ -101,7 +108,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax
|
||||||
|
|
||||||
public virtual SyntaxToken TokenWithLeadingTrivia(GreenNode trivia)
|
public virtual SyntaxToken TokenWithLeadingTrivia(GreenNode trivia)
|
||||||
{
|
{
|
||||||
return new SyntaxToken(Kind, Content, trivia, TrailingTrivia, GetDiagnostics(), GetAnnotations());
|
return new SyntaxToken(Kind, Content, trivia, _trailingTrivia, GetDiagnostics(), GetAnnotations());
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed override GreenNode WithTrailingTrivia(GreenNode trivia)
|
public sealed override GreenNode WithTrailingTrivia(GreenNode trivia)
|
||||||
|
|
@ -111,17 +118,17 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax
|
||||||
|
|
||||||
public virtual SyntaxToken TokenWithTrailingTrivia(GreenNode trivia)
|
public virtual SyntaxToken TokenWithTrailingTrivia(GreenNode trivia)
|
||||||
{
|
{
|
||||||
return new SyntaxToken(Kind, Content, LeadingTrivia, trivia, GetDiagnostics(), GetAnnotations());
|
return new SyntaxToken(Kind, Content, _leadingTrivia, trivia, GetDiagnostics(), GetAnnotations());
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override GreenNode SetDiagnostics(RazorDiagnostic[] diagnostics)
|
internal override GreenNode SetDiagnostics(RazorDiagnostic[] diagnostics)
|
||||||
{
|
{
|
||||||
return new SyntaxToken(Kind, Content, LeadingTrivia, TrailingTrivia, diagnostics, GetAnnotations());
|
return new SyntaxToken(Kind, Content, _leadingTrivia, _trailingTrivia, diagnostics, GetAnnotations());
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override GreenNode SetAnnotations(SyntaxAnnotation[] annotations)
|
internal override GreenNode SetAnnotations(SyntaxAnnotation[] annotations)
|
||||||
{
|
{
|
||||||
return new SyntaxToken(Kind, Content, LeadingTrivia, TrailingTrivia, GetDiagnostics(), annotations);
|
return new SyntaxToken(Kind, Content, _leadingTrivia, _trailingTrivia, GetDiagnostics(), annotations);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override sealed int GetSlotCount()
|
protected override sealed int GetSlotCount()
|
||||||
|
|
@ -195,5 +202,25 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax
|
||||||
{
|
{
|
||||||
return Content;
|
return Content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static SyntaxToken CreateMissing(SyntaxKind kind, params RazorDiagnostic[] diagnostics)
|
||||||
|
{
|
||||||
|
return new MissingToken(kind, diagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MissingToken : SyntaxToken
|
||||||
|
{
|
||||||
|
internal MissingToken(SyntaxKind kind, RazorDiagnostic[] diagnostics)
|
||||||
|
: base(kind, string.Empty, diagnostics)
|
||||||
|
{
|
||||||
|
Flags |= NodeFlags.IsMissing;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal MissingToken(SyntaxKind kind, GreenNode leading, GreenNode trailing, RazorDiagnostic[] diagnostics, SyntaxAnnotation[] annotations)
|
||||||
|
: base(kind, string.Empty, leading, trailing, diagnostics, annotations)
|
||||||
|
{
|
||||||
|
Flags |= NodeFlags.IsMissing;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,290 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
||||||
|
{
|
||||||
|
internal static class MarkupElementRewriter
|
||||||
|
{
|
||||||
|
public static RazorSyntaxTree AddMarkupElements(RazorSyntaxTree syntaxTree)
|
||||||
|
{
|
||||||
|
var rewriter = new AddMarkupElementRewriter();
|
||||||
|
var rewrittenRoot = rewriter.Visit(syntaxTree.Root);
|
||||||
|
|
||||||
|
var newSyntaxTree = RazorSyntaxTree.Create(rewrittenRoot, syntaxTree.Source, syntaxTree.Diagnostics, syntaxTree.Options);
|
||||||
|
return newSyntaxTree;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RazorSyntaxTree RemoveMarkupElements(RazorSyntaxTree syntaxTree)
|
||||||
|
{
|
||||||
|
var rewriter = new RemoveMarkupElementRewriter();
|
||||||
|
var rewrittenRoot = rewriter.Visit(syntaxTree.Root);
|
||||||
|
|
||||||
|
var newSyntaxTree = RazorSyntaxTree.Create(rewrittenRoot, syntaxTree.Source, syntaxTree.Diagnostics, syntaxTree.Options);
|
||||||
|
return newSyntaxTree;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AddMarkupElementRewriter : SyntaxRewriter
|
||||||
|
{
|
||||||
|
private readonly Stack<TagBlockTracker> _startTagTracker = new Stack<TagBlockTracker>();
|
||||||
|
|
||||||
|
private TagBlockTracker CurrentTracker => _startTagTracker.Count > 0 ? _startTagTracker.Peek() : null;
|
||||||
|
|
||||||
|
private string CurrentStartTagName => CurrentTracker?.TagName;
|
||||||
|
|
||||||
|
public override SyntaxNode Visit(SyntaxNode node)
|
||||||
|
{
|
||||||
|
node = base.Visit(node);
|
||||||
|
|
||||||
|
if (node != null)
|
||||||
|
{
|
||||||
|
node = RewriteNode(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SyntaxNode RewriteNode(SyntaxNode node)
|
||||||
|
{
|
||||||
|
if (node.IsToken)
|
||||||
|
{
|
||||||
|
// Tokens don't have children.
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
_startTagTracker.Clear();
|
||||||
|
var children = node.ChildNodes().ToList();
|
||||||
|
var rewrittenChildren = new List<SyntaxNode>(children.Count);
|
||||||
|
for (var i = 0; i < children.Count; i++)
|
||||||
|
{
|
||||||
|
var child = children[i];
|
||||||
|
if (!(child is MarkupTagBlockSyntax tagBlock))
|
||||||
|
{
|
||||||
|
TrackChild(child, rewrittenChildren);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var tagName = tagBlock.GetTagName();
|
||||||
|
if (string.IsNullOrWhiteSpace(tagName) || tagBlock.IsSelfClosing())
|
||||||
|
{
|
||||||
|
// Don't want to track incomplete, invalid (Eg. </>, < >), void or self-closing tags.
|
||||||
|
// Simply wrap it in a block with no body or start/end tag.
|
||||||
|
if (IsEndTag(tagBlock))
|
||||||
|
{
|
||||||
|
// This is an error case.
|
||||||
|
BuildMarkupElement(rewrittenChildren, startTag: null, tagChildren: new List<RazorSyntaxNode>(), endTag: tagBlock);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BuildMarkupElement(rewrittenChildren, startTag: tagBlock, tagChildren: new List<RazorSyntaxNode>(), endTag: null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (IsEndTag(tagBlock))
|
||||||
|
{
|
||||||
|
if (string.Equals(CurrentStartTagName, tagName, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var startTagTracker = _startTagTracker.Pop();
|
||||||
|
var startTag = startTagTracker.TagBlock;
|
||||||
|
|
||||||
|
// Get the nodes between the start and the end tag.
|
||||||
|
var tagChildren = startTagTracker.Children;
|
||||||
|
|
||||||
|
BuildMarkupElement(rewrittenChildren, startTag, tagChildren, endTag: tagBlock);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Current tag scope does not match the end tag. Attempt to recover the start tag
|
||||||
|
// by looking up the previous tag scopes for a matching start tag.
|
||||||
|
if (!TryRecoverStartTag(rewrittenChildren, tagName, tagBlock))
|
||||||
|
{
|
||||||
|
// Could not recover. The end tag doesn't have a corresponding start tag. Wrap it in a block and move on.
|
||||||
|
var rewritten = SyntaxFactory.MarkupElement(startTag: null, body: new SyntaxList<RazorSyntaxNode>(), endTag: tagBlock);
|
||||||
|
TrackChild(rewritten, rewrittenChildren);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// This is a start tag. Keep track of it.
|
||||||
|
_startTagTracker.Push(new TagBlockTracker(tagBlock));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (_startTagTracker.Count > 0)
|
||||||
|
{
|
||||||
|
// We reached the end of the list and still have unmatched start tags
|
||||||
|
var startTagTracker = _startTagTracker.Pop();
|
||||||
|
var startTag = startTagTracker.TagBlock;
|
||||||
|
var tagChildren = startTagTracker.Children;
|
||||||
|
BuildMarkupElement(rewrittenChildren, startTag, tagChildren, endTag: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We now have finished building our list of rewritten Children.
|
||||||
|
// At this point, We should have a one to one replacement for every child. The replacement can be null.
|
||||||
|
Debug.Assert(children.Count == rewrittenChildren.Count);
|
||||||
|
node = node.ReplaceNodes(children, (original, rewritten) =>
|
||||||
|
{
|
||||||
|
var originalIndex = children.IndexOf(original);
|
||||||
|
if (originalIndex != -1)
|
||||||
|
{
|
||||||
|
// If this returns null, that node will be removed.
|
||||||
|
return rewrittenChildren[originalIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
return original;
|
||||||
|
});
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BuildMarkupElement(List<SyntaxNode> rewrittenChildren, MarkupTagBlockSyntax startTag, List<RazorSyntaxNode> tagChildren, MarkupTagBlockSyntax endTag)
|
||||||
|
{
|
||||||
|
// We are trying to replace multiple nodes (including the start/end tag) with one rewritten node.
|
||||||
|
// Since we need to have each child node accounted for in our rewritten list,
|
||||||
|
// we'll add "null" in place of them.
|
||||||
|
// The call to SyntaxNode.ReplaceNodes() later will take care removing the nodes whose replacement is null.
|
||||||
|
|
||||||
|
var body = tagChildren.Where(t => t != null).ToList();
|
||||||
|
var rewritten = SyntaxFactory.MarkupElement(startTag, new SyntaxList<RazorSyntaxNode>(body), endTag);
|
||||||
|
if (startTag != null)
|
||||||
|
{
|
||||||
|
// If there was a start tag, that is where we want to put our new element.
|
||||||
|
TrackChild(rewritten, rewrittenChildren);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var child in tagChildren)
|
||||||
|
{
|
||||||
|
TrackChild(null, rewrittenChildren);
|
||||||
|
}
|
||||||
|
if (endTag != null)
|
||||||
|
{
|
||||||
|
TrackChild(startTag == null ? rewritten : null, rewrittenChildren);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TrackChild(SyntaxNode child, List<SyntaxNode> rewrittenChildren)
|
||||||
|
{
|
||||||
|
if (CurrentTracker != null)
|
||||||
|
{
|
||||||
|
CurrentTracker.Children.Add((RazorSyntaxNode)child);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
rewrittenChildren.Add(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryRecoverStartTag(List<SyntaxNode> rewrittenChildren, string tagName, MarkupTagBlockSyntax endTag)
|
||||||
|
{
|
||||||
|
var malformedTagCount = 0;
|
||||||
|
foreach (var tracker in _startTagTracker)
|
||||||
|
{
|
||||||
|
if (tracker.TagName.Equals(tagName, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
malformedTagCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_startTagTracker.Count > malformedTagCount)
|
||||||
|
{
|
||||||
|
RewriteMalformedTags(rewrittenChildren, malformedTagCount);
|
||||||
|
|
||||||
|
// One final rewrite, this is the rewrite that completes our target tag which is not malformed.
|
||||||
|
var startTagTracker = _startTagTracker.Pop();
|
||||||
|
var startTag = startTagTracker.TagBlock;
|
||||||
|
var tagChildren = startTagTracker.Children;
|
||||||
|
|
||||||
|
BuildMarkupElement(rewrittenChildren, startTag, tagChildren, endTag);
|
||||||
|
|
||||||
|
// We were able to recover
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Could not recover tag. Aka we found an end tag without a corresponding start tag.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RewriteMalformedTags(List<SyntaxNode> rewrittenChildren, int malformedTagCount)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < malformedTagCount; i++)
|
||||||
|
{
|
||||||
|
var startTagTracker = _startTagTracker.Pop();
|
||||||
|
var startTag = startTagTracker.TagBlock;
|
||||||
|
|
||||||
|
BuildMarkupElement(rewrittenChildren, startTag, startTagTracker.Children, endTag: null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsEndTag(MarkupTagBlockSyntax tagBlock)
|
||||||
|
{
|
||||||
|
var childContent = tagBlock.Children.First().GetContent();
|
||||||
|
if (string.IsNullOrEmpty(childContent))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We grab the token that could be forward slash
|
||||||
|
return childContent.StartsWith("</") || childContent.StartsWith("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TagBlockTracker
|
||||||
|
{
|
||||||
|
public TagBlockTracker(MarkupTagBlockSyntax tagBlock)
|
||||||
|
{
|
||||||
|
TagBlock = tagBlock;
|
||||||
|
TagName = tagBlock.GetTagName();
|
||||||
|
Children = new List<RazorSyntaxNode>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MarkupTagBlockSyntax TagBlock { get; }
|
||||||
|
|
||||||
|
public List<RazorSyntaxNode> Children { get; }
|
||||||
|
|
||||||
|
public string TagName { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RemoveMarkupElementRewriter : SyntaxRewriter
|
||||||
|
{
|
||||||
|
public override SyntaxNode Visit(SyntaxNode node)
|
||||||
|
{
|
||||||
|
if (node != null)
|
||||||
|
{
|
||||||
|
node = RewriteNode(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.Visit(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SyntaxNode RewriteNode(SyntaxNode node)
|
||||||
|
{
|
||||||
|
if (node.IsToken)
|
||||||
|
{
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
var children = node.ChildNodes();
|
||||||
|
for (var i = 0; i < children.Count; i++)
|
||||||
|
{
|
||||||
|
var child = children[i];
|
||||||
|
if (!(child is MarkupElementSyntax tagElement))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
node = node.ReplaceNode(tagElement, tagElement.ChildNodes());
|
||||||
|
|
||||||
|
// Since we rewrote 'node', it's children are different. Update our collection.
|
||||||
|
children = node.ChildNodes();
|
||||||
|
}
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
||||||
|
{
|
||||||
|
internal sealed partial class MarkupMinimizedTagHelperAttributeSyntax
|
||||||
|
{
|
||||||
|
private static readonly string TagHelperAttributeInfoKey = typeof(TagHelperAttributeInfo).Name;
|
||||||
|
|
||||||
|
public TagHelperAttributeInfo TagHelperAttributeInfo
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var tagHelperAttributeInfo = this.GetAnnotationValue(TagHelperAttributeInfoKey) as TagHelperAttributeInfo;
|
||||||
|
return tagHelperAttributeInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public MarkupMinimizedTagHelperAttributeSyntax WithTagHelperAttributeInfo(TagHelperAttributeInfo info)
|
||||||
|
{
|
||||||
|
var annotations = new List<SyntaxAnnotation>(GetAnnotations())
|
||||||
|
{
|
||||||
|
new SyntaxAnnotation(TagHelperAttributeInfoKey, info)
|
||||||
|
};
|
||||||
|
|
||||||
|
var newGreen = Green.WithAnnotationsGreen(annotations.ToArray());
|
||||||
|
|
||||||
|
return (MarkupMinimizedTagHelperAttributeSyntax)newGreen.CreateRed(Parent, Position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
||||||
|
{
|
||||||
|
internal sealed partial class MarkupTagHelperAttributeSyntax
|
||||||
|
{
|
||||||
|
private static readonly string TagHelperAttributeInfoKey = typeof(TagHelperAttributeInfo).Name;
|
||||||
|
|
||||||
|
public TagHelperAttributeInfo TagHelperAttributeInfo
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var tagHelperAttributeInfo = this.GetAnnotationValue(TagHelperAttributeInfoKey) as TagHelperAttributeInfo;
|
||||||
|
return tagHelperAttributeInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public MarkupTagHelperAttributeSyntax WithTagHelperAttributeInfo(TagHelperAttributeInfo info)
|
||||||
|
{
|
||||||
|
var annotations = new List<SyntaxAnnotation>(GetAnnotations())
|
||||||
|
{
|
||||||
|
new SyntaxAnnotation(TagHelperAttributeInfoKey, info)
|
||||||
|
};
|
||||||
|
|
||||||
|
var newGreen = Green.WithAnnotationsGreen(annotations.ToArray());
|
||||||
|
|
||||||
|
return (MarkupTagHelperAttributeSyntax)newGreen.CreateRed(Parent, Position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
||||||
|
{
|
||||||
|
internal sealed partial class MarkupTagHelperElementSyntax
|
||||||
|
{
|
||||||
|
private static readonly string TagHelperInfoKey = typeof(TagHelperInfo).Name;
|
||||||
|
|
||||||
|
public TagHelperInfo TagHelperInfo
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var tagHelperInfo = this.GetAnnotationValue(TagHelperInfoKey) as TagHelperInfo;
|
||||||
|
return tagHelperInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public MarkupTagHelperElementSyntax WithTagHelperInfo(TagHelperInfo info)
|
||||||
|
{
|
||||||
|
var annotations = new List<SyntaxAnnotation>(GetAnnotations())
|
||||||
|
{
|
||||||
|
new SyntaxAnnotation(TagHelperInfoKey, info)
|
||||||
|
};
|
||||||
|
|
||||||
|
var newGreen = Green.WithAnnotationsGreen(annotations.ToArray());
|
||||||
|
|
||||||
|
return (MarkupTagHelperElementSyntax)newGreen.CreateRed(Parent, Position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,14 +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.
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language
|
|
||||||
{
|
|
||||||
internal enum ParserState
|
|
||||||
{
|
|
||||||
Unknown,
|
|
||||||
Misc,
|
|
||||||
Content,
|
|
||||||
StartTag,
|
|
||||||
EndTag,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
||||||
|
{
|
||||||
|
internal sealed partial class RazorDirectiveSyntax
|
||||||
|
{
|
||||||
|
private static readonly string DirectiveDescriptorKey = typeof(DirectiveDescriptor).Name;
|
||||||
|
|
||||||
|
public DirectiveDescriptor DirectiveDescriptor
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var descriptor = this.GetAnnotationValue(DirectiveDescriptorKey) as DirectiveDescriptor;
|
||||||
|
return descriptor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public RazorDirectiveSyntax WithDirectiveDescriptor(DirectiveDescriptor descriptor)
|
||||||
|
{
|
||||||
|
var annotations = new List<SyntaxAnnotation>(GetAnnotations())
|
||||||
|
{
|
||||||
|
new SyntaxAnnotation(DirectiveDescriptorKey, descriptor)
|
||||||
|
};
|
||||||
|
|
||||||
|
var newGreen = Green.WithAnnotationsGreen(annotations.ToArray());
|
||||||
|
|
||||||
|
return (RazorDirectiveSyntax)newGreen.CreateRed(Parent, Position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
||||||
|
{
|
||||||
|
internal abstract partial class RazorSyntaxNode : SyntaxNode
|
||||||
|
{
|
||||||
|
public RazorSyntaxNode(GreenNode green, SyntaxNode parent, int position)
|
||||||
|
: base(green, parent, position)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,17 @@
|
||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
|
||||||
<Tree Root="SyntaxNode">
|
<Tree Root="SyntaxNode">
|
||||||
<PredefinedNode Name="SyntaxToken" Base="SyntaxNode" />
|
<PredefinedNode Name="RazorSyntaxNode" Base="SyntaxNode" />
|
||||||
|
<PredefinedNode Name="SyntaxToken" Base="RazorSyntaxNode" />
|
||||||
|
|
||||||
<!-- Common -->
|
<!-- Common -->
|
||||||
<AbstractNode Name="RazorSyntaxNode" Base="SyntaxNode" />
|
<AbstractNode Name="RazorBlockSyntax" Base="RazorSyntaxNode">
|
||||||
|
<Field Name="Children" Type="SyntaxList<RazorSyntaxNode>" />
|
||||||
|
</AbstractNode>
|
||||||
|
<Node Name="RazorDocumentSyntax" Base="RazorSyntaxNode">
|
||||||
|
<Kind Name="RazorDocument" />
|
||||||
|
<Field Name="Document" Type="RazorBlockSyntax" />
|
||||||
|
</Node>
|
||||||
<Node Name="RazorCommentBlockSyntax" Base="RazorSyntaxNode">
|
<Node Name="RazorCommentBlockSyntax" Base="RazorSyntaxNode">
|
||||||
<Kind Name="RazorComment" />
|
<Kind Name="RazorComment" />
|
||||||
<Field Name="StartCommentTransition" Type="SyntaxToken">
|
<Field Name="StartCommentTransition" Type="SyntaxToken">
|
||||||
|
|
@ -13,8 +20,8 @@
|
||||||
<Field Name="StartCommentStar" Type="SyntaxToken">
|
<Field Name="StartCommentStar" Type="SyntaxToken">
|
||||||
<Kind Name="RazorCommentStar" />
|
<Kind Name="RazorCommentStar" />
|
||||||
</Field>
|
</Field>
|
||||||
<Field Name="Comment" Type="SyntaxToken" Optional="true">
|
<Field Name="Comment" Type="SyntaxToken">
|
||||||
<Kind Name="RazorComment" />
|
<Kind Name="RazorCommentLiteral" />
|
||||||
</Field>
|
</Field>
|
||||||
<Field Name="EndCommentStar" Type="SyntaxToken">
|
<Field Name="EndCommentStar" Type="SyntaxToken">
|
||||||
<Kind Name="RazorCommentStar" />
|
<Kind Name="RazorCommentStar" />
|
||||||
|
|
@ -23,39 +30,147 @@
|
||||||
<Kind Name="RazorCommentTransition" />
|
<Kind Name="RazorCommentTransition" />
|
||||||
</Field>
|
</Field>
|
||||||
</Node>
|
</Node>
|
||||||
|
<Node Name="RazorMetaCodeSyntax" Base="RazorSyntaxNode">
|
||||||
|
<Kind Name="RazorMetaCode" />
|
||||||
|
<Field Name="MetaCode" Type="SyntaxList<SyntaxToken>" />
|
||||||
|
</Node>
|
||||||
|
<Node Name="GenericBlockSyntax" Base="RazorBlockSyntax">
|
||||||
|
<Kind Name="GenericBlock" />
|
||||||
|
<Field Name="Children" Type="SyntaxList<RazorSyntaxNode>" Override="true" />
|
||||||
|
</Node>
|
||||||
|
<Node Name="UnclassifiedTextLiteralSyntax" Base="RazorSyntaxNode">
|
||||||
|
<Kind Name="UnclassifiedTextLiteral" />
|
||||||
|
<Field Name="LiteralTokens" Type="SyntaxList<SyntaxToken>" />
|
||||||
|
</Node>
|
||||||
|
|
||||||
<!-- HTML -->
|
<!-- Markup -->
|
||||||
<AbstractNode Name="HtmlSyntaxNode" Base="RazorSyntaxNode" />
|
<AbstractNode Name="MarkupSyntaxNode" Base="RazorSyntaxNode" />
|
||||||
<Node Name="HtmlTextLiteralSyntax" Base="HtmlSyntaxNode">
|
<Node Name="MarkupBlockSyntax" Base="RazorBlockSyntax">
|
||||||
<Kind Name="HtmlTextLiteral" />
|
<Kind Name="MarkupBlock" />
|
||||||
<Field Name="TextTokens" Type="SyntaxList<SyntaxToken>" />
|
<Field Name="Children" Type="SyntaxList<RazorSyntaxNode>" Override="true" />
|
||||||
|
</Node>
|
||||||
|
<Node Name="MarkupTransitionSyntax" Base="MarkupSyntaxNode">
|
||||||
|
<Kind Name="MarkupTransition" />
|
||||||
|
<Field Name="TransitionTokens" Type="SyntaxList<SyntaxToken>" />
|
||||||
|
</Node>
|
||||||
|
<Node Name="MarkupTextLiteralSyntax" Base="MarkupSyntaxNode">
|
||||||
|
<Kind Name="MarkupTextLiteral" />
|
||||||
|
<Field Name="LiteralTokens" Type="SyntaxList<SyntaxToken>" />
|
||||||
|
</Node>
|
||||||
|
<Node Name="MarkupEphemeralTextLiteralSyntax" Base="MarkupSyntaxNode">
|
||||||
|
<Kind Name="MarkupEphemeralTextLiteral" />
|
||||||
|
<Field Name="LiteralTokens" Type="SyntaxList<SyntaxToken>" />
|
||||||
|
</Node>
|
||||||
|
<Node Name="MarkupCommentBlockSyntax" Base="RazorBlockSyntax">
|
||||||
|
<Kind Name="MarkupCommentBlock" />
|
||||||
|
<Field Name="Children" Type="SyntaxList<RazorSyntaxNode>" Override="true" />
|
||||||
|
</Node>
|
||||||
|
<Node Name="MarkupTagBlockSyntax" Base="RazorBlockSyntax">
|
||||||
|
<Kind Name="MarkupTagBlock" />
|
||||||
|
<Field Name="Children" Type="SyntaxList<RazorSyntaxNode>" Override="true" />
|
||||||
|
</Node>
|
||||||
|
<Node Name="MarkupMinimizedAttributeBlockSyntax" Base="MarkupSyntaxNode">
|
||||||
|
<Kind Name="MarkupMinimizedAttributeBlock" />
|
||||||
|
<Field Name="NamePrefix" Type="MarkupTextLiteralSyntax" Optional="true" />
|
||||||
|
<Field Name="Name" Type="MarkupTextLiteralSyntax" />
|
||||||
|
</Node>
|
||||||
|
<Node Name="MarkupAttributeBlockSyntax" Base="MarkupSyntaxNode">
|
||||||
|
<Kind Name="MarkupAttributeBlock" />
|
||||||
|
<Field Name="NamePrefix" Type="MarkupTextLiteralSyntax" Optional="true" />
|
||||||
|
<Field Name="Name" Type="MarkupTextLiteralSyntax" />
|
||||||
|
<Field Name="NameSuffix" Type="MarkupTextLiteralSyntax" Optional="true" />
|
||||||
|
<Field Name="EqualsToken" Type="SyntaxToken">
|
||||||
|
<Kind Name="Equals" />
|
||||||
|
</Field>
|
||||||
|
<Field Name="ValuePrefix" Type="MarkupTextLiteralSyntax" Optional="true" />
|
||||||
|
<Field Name="Value" Type="RazorBlockSyntax" Optional="true" />
|
||||||
|
<Field Name="ValueSuffix" Type="MarkupTextLiteralSyntax" Optional="true" />
|
||||||
|
</Node>
|
||||||
|
<Node Name="MarkupLiteralAttributeValueSyntax" Base="MarkupSyntaxNode">
|
||||||
|
<Kind Name="MarkupLiteralAttributeValue" />
|
||||||
|
<Field Name="Prefix" Type="MarkupTextLiteralSyntax" Optional="true" />
|
||||||
|
<Field Name="Value" Type="MarkupTextLiteralSyntax" Optional="true" />
|
||||||
|
</Node>
|
||||||
|
<Node Name="MarkupDynamicAttributeValueSyntax" Base="MarkupSyntaxNode">
|
||||||
|
<Kind Name="MarkupDynamicAttributeValue" />
|
||||||
|
<Field Name="Prefix" Type="MarkupTextLiteralSyntax" Optional="true" />
|
||||||
|
<Field Name="Value" Type="RazorBlockSyntax" />
|
||||||
|
</Node>
|
||||||
|
<Node Name="MarkupElementSyntax" Base="MarkupSyntaxNode">
|
||||||
|
<Kind Name="MarkupElement" />
|
||||||
|
<Field Name="StartTag" Type="MarkupTagBlockSyntax" Optional="true" />
|
||||||
|
<Field Name="Body" Type="SyntaxList<RazorSyntaxNode>" Optional="true" />
|
||||||
|
<Field Name="EndTag" Type="MarkupTagBlockSyntax" Optional="true" />
|
||||||
|
</Node>
|
||||||
|
<Node Name="MarkupTagHelperElementSyntax" Base="MarkupSyntaxNode">
|
||||||
|
<Kind Name="MarkupTagHelperElement" />
|
||||||
|
<Field Name="StartTag" Type="MarkupTagHelperStartTagSyntax" />
|
||||||
|
<Field Name="Body" Type="SyntaxList<RazorSyntaxNode>" Optional="true" />
|
||||||
|
<Field Name="EndTag" Type="MarkupTagHelperEndTagSyntax" Optional="true" />
|
||||||
|
</Node>
|
||||||
|
<Node Name="MarkupTagHelperStartTagSyntax" Base="RazorBlockSyntax">
|
||||||
|
<Kind Name="MarkupTagHelperStartTag" />
|
||||||
|
<Field Name="Children" Type="SyntaxList<RazorSyntaxNode>" Override="true" />
|
||||||
|
</Node>
|
||||||
|
<Node Name="MarkupTagHelperEndTagSyntax" Base="RazorBlockSyntax">
|
||||||
|
<Kind Name="MarkupTagHelperEndTag" />
|
||||||
|
<Field Name="Children" Type="SyntaxList<RazorSyntaxNode>" Override="true" />
|
||||||
|
</Node>
|
||||||
|
<Node Name="MarkupTagHelperAttributeSyntax" Base="MarkupSyntaxNode">
|
||||||
|
<Kind Name="MarkupTagHelperAttribute" />
|
||||||
|
<Field Name="NamePrefix" Type="MarkupTextLiteralSyntax" Optional="true" />
|
||||||
|
<Field Name="Name" Type="MarkupTextLiteralSyntax" />
|
||||||
|
<Field Name="NameSuffix" Type="MarkupTextLiteralSyntax" Optional="true" />
|
||||||
|
<Field Name="EqualsToken" Type="SyntaxToken">
|
||||||
|
<Kind Name="Equals" />
|
||||||
|
</Field>
|
||||||
|
<Field Name="ValuePrefix" Type="MarkupTextLiteralSyntax" Optional="true" />
|
||||||
|
<Field Name="Value" Type="MarkupTagHelperAttributeValueSyntax" />
|
||||||
|
<Field Name="ValueSuffix" Type="MarkupTextLiteralSyntax" Optional="true" />
|
||||||
|
</Node>
|
||||||
|
<Node Name="MarkupMinimizedTagHelperAttributeSyntax" Base="MarkupSyntaxNode">
|
||||||
|
<Kind Name="MarkupMinimizedTagHelperAttribute" />
|
||||||
|
<Field Name="NamePrefix" Type="MarkupTextLiteralSyntax" Optional="true" />
|
||||||
|
<Field Name="Name" Type="MarkupTextLiteralSyntax" />
|
||||||
|
</Node>
|
||||||
|
<Node Name="MarkupTagHelperAttributeValueSyntax" Base="RazorBlockSyntax">
|
||||||
|
<Kind Name="MarkupTagHelperAttributeValue" />
|
||||||
|
<Field Name="Children" Type="SyntaxList<RazorSyntaxNode>" Override="true" />
|
||||||
</Node>
|
</Node>
|
||||||
|
|
||||||
<!-- CSharp -->
|
<!-- CSharp -->
|
||||||
<AbstractNode Name="CSharpSyntaxNode" Base="RazorSyntaxNode" />
|
<AbstractNode Name="CSharpSyntaxNode" Base="RazorSyntaxNode" />
|
||||||
|
<Node Name="CSharpCodeBlockSyntax" Base="RazorBlockSyntax">
|
||||||
|
<Kind Name="CSharpCodeBlock" />
|
||||||
|
<Field Name="Children" Type="SyntaxList<RazorSyntaxNode>" Override="true" />
|
||||||
|
</Node>
|
||||||
<Node Name="CSharpTransitionSyntax" Base="CSharpSyntaxNode">
|
<Node Name="CSharpTransitionSyntax" Base="CSharpSyntaxNode">
|
||||||
<Kind Name="CSharpTransition" />
|
<Kind Name="CSharpTransition" />
|
||||||
<Field Name="Transition" Type="SyntaxToken">
|
<Field Name="Transition" Type="SyntaxToken">
|
||||||
<Kind Name="Transition" />
|
<Kind Name="Transition" />
|
||||||
</Field>
|
</Field>
|
||||||
</Node>
|
</Node>
|
||||||
<Node Name="CSharpMetaCodeSyntax" Base="CSharpSyntaxNode">
|
<Node Name="CSharpStatementLiteralSyntax" Base="CSharpSyntaxNode">
|
||||||
<Kind Name="CSharpMetaCode" />
|
<Kind Name="CSharpStatementLiteral" />
|
||||||
<Field Name="MetaCode" Type="SyntaxList<SyntaxToken>" />
|
<Field Name="LiteralTokens" Type="SyntaxList<SyntaxToken>" />
|
||||||
</Node>
|
</Node>
|
||||||
<Node Name="CSharpCodeLiteralSyntax" Base="CSharpSyntaxNode">
|
<Node Name="CSharpExpressionLiteralSyntax" Base="CSharpSyntaxNode">
|
||||||
<Kind Name="CSharpCodeLiteral" />
|
<Kind Name="CSharpExpressionLiteral" />
|
||||||
<Field Name="CSharpTokens" Type="SyntaxList<SyntaxToken>" />
|
<Field Name="LiteralTokens" Type="SyntaxList<SyntaxToken>" />
|
||||||
</Node>
|
</Node>
|
||||||
<Node Name="CSharpCodeBlockSyntax" Base="CSharpSyntaxNode">
|
<Node Name="CSharpEphemeralTextLiteralSyntax" Base="CSharpSyntaxNode">
|
||||||
<Kind Name="CSharpCodeBlock" />
|
<Kind Name="CSharpEphemeralTextLiteral" />
|
||||||
<Field Name="Children" Type="SyntaxList<RazorSyntaxNode>" />
|
<Field Name="LiteralTokens" Type="SyntaxList<SyntaxToken>" />
|
||||||
</Node>
|
</Node>
|
||||||
<AbstractNode Name="CSharpBlockSyntax" Base="CSharpSyntaxNode">
|
<Node Name="CSharpTemplateBlockSyntax" Base="RazorBlockSyntax">
|
||||||
|
<Kind Name="CSharpTemplateBlock" />
|
||||||
|
<Field Name="Children" Type="SyntaxList<RazorSyntaxNode>" Override="true" />
|
||||||
|
</Node>
|
||||||
|
<AbstractNode Name="CSharpRazorBlockSyntax" Base="CSharpSyntaxNode">
|
||||||
<Field Name="Transition" Type="CSharpTransitionSyntax" />
|
<Field Name="Transition" Type="CSharpTransitionSyntax" />
|
||||||
<Field Name="Body" Type="CSharpSyntaxNode" />
|
<Field Name="Body" Type="CSharpSyntaxNode" />
|
||||||
</AbstractNode>
|
</AbstractNode>
|
||||||
<Node Name="CSharpStatement" Base="CSharpBlockSyntax">
|
<Node Name="CSharpStatementSyntax" Base="CSharpRazorBlockSyntax">
|
||||||
<Kind Name="CSharpStatement" />
|
<Kind Name="CSharpStatement" />
|
||||||
<Field Name="Transition" Type="CSharpTransitionSyntax" Override="true">
|
<Field Name="Transition" Type="CSharpTransitionSyntax" Override="true">
|
||||||
<Kind Name="CSharpTransition" />
|
<Kind Name="CSharpTransition" />
|
||||||
|
|
@ -66,37 +181,50 @@
|
||||||
</Node>
|
</Node>
|
||||||
<Node Name="CSharpStatementBodySyntax" Base="CSharpSyntaxNode">
|
<Node Name="CSharpStatementBodySyntax" Base="CSharpSyntaxNode">
|
||||||
<Kind Name="CSharpStatementBody" />
|
<Kind Name="CSharpStatementBody" />
|
||||||
<Field Name="OpenBrace" Type="CSharpMetaCodeSyntax" />
|
<Field Name="OpenBrace" Type="RazorMetaCodeSyntax" />
|
||||||
<Field Name="CSharpCode" Type="CSharpCodeBlockSyntax" />
|
<Field Name="CSharpCode" Type="CSharpCodeBlockSyntax" />
|
||||||
<Field Name="CloseBrace" Type="CSharpMetaCodeSyntax" />
|
<Field Name="CloseBrace" Type="RazorMetaCodeSyntax" />
|
||||||
</Node>
|
</Node>
|
||||||
<Node Name="CSharpExpression" Base="CSharpBlockSyntax">
|
<Node Name="CSharpExplicitExpressionSyntax" Base="CSharpRazorBlockSyntax">
|
||||||
<Kind Name="CSharpExpression" />
|
<Kind Name="CSharpExplicitExpression" />
|
||||||
<Field Name="Transition" Type="CSharpTransitionSyntax" Override="true">
|
<Field Name="Transition" Type="CSharpTransitionSyntax" Override="true">
|
||||||
<Kind Name="CSharpTransition" />
|
<Kind Name="CSharpTransition" />
|
||||||
</Field>
|
</Field>
|
||||||
<Field Name="Body" Type="CSharpSyntaxNode" Override="true">
|
<Field Name="Body" Type="CSharpSyntaxNode" Override="true">
|
||||||
<Kind Name="CSharpExpressionBody" />
|
<Kind Name="CSharpExplicitExpressionBody" />
|
||||||
</Field>
|
</Field>
|
||||||
</Node>
|
</Node>
|
||||||
<Node Name="CSharpExpressionBodySyntax" Base="CSharpSyntaxNode">
|
<Node Name="CSharpExplicitExpressionBodySyntax" Base="CSharpSyntaxNode">
|
||||||
<Kind Name="CSharpExpressionBody" />
|
<Kind Name="CSharpExplicitExpressionBody" />
|
||||||
<Field Name="OpenParen" Type="CSharpMetaCodeSyntax" Optional="true" />
|
<Field Name="OpenParen" Type="RazorMetaCodeSyntax" />
|
||||||
<Field Name="CSharpCode" Type="CSharpCodeBlockSyntax" />
|
<Field Name="CSharpCode" Type="CSharpCodeBlockSyntax" />
|
||||||
<Field Name="CloseParen" Type="CSharpMetaCodeSyntax" Optional="true" />
|
<Field Name="CloseParen" Type="RazorMetaCodeSyntax" />
|
||||||
</Node>
|
</Node>
|
||||||
<Node Name="CSharpDirectiveSyntax" Base="CSharpBlockSyntax">
|
<Node Name="CSharpImplicitExpressionSyntax" Base="CSharpRazorBlockSyntax">
|
||||||
<Kind Name="CSharpDirective" />
|
<Kind Name="CSharpImplicitExpression" />
|
||||||
<Field Name="Transition" Type="CSharpTransitionSyntax" Override="true">
|
<Field Name="Transition" Type="CSharpTransitionSyntax" Override="true">
|
||||||
<Kind Name="CSharpTransition" />
|
<Kind Name="CSharpTransition" />
|
||||||
</Field>
|
</Field>
|
||||||
<Field Name="Body" Type="CSharpSyntaxNode" Override="true">
|
<Field Name="Body" Type="CSharpSyntaxNode" Override="true">
|
||||||
<Kind Name="CSharpDirectiveBody" />
|
<Kind Name="CSharpImplicitExpressionBody" />
|
||||||
</Field>
|
</Field>
|
||||||
</Node>
|
</Node>
|
||||||
<Node Name="CSharpDirectiveBodySyntax" Base="CSharpSyntaxNode">
|
<Node Name="CSharpImplicitExpressionBodySyntax" Base="CSharpSyntaxNode">
|
||||||
<Kind Name="CSharpDirectiveBody" />
|
<Kind Name="CSharpImplicitExpressionBody" />
|
||||||
<Field Name="Keyword" Type="CSharpMetaCodeSyntax" />
|
|
||||||
<Field Name="CSharpCode" Type="CSharpCodeBlockSyntax" />
|
<Field Name="CSharpCode" Type="CSharpCodeBlockSyntax" />
|
||||||
</Node>
|
</Node>
|
||||||
|
<Node Name="RazorDirectiveSyntax" Base="CSharpRazorBlockSyntax">
|
||||||
|
<Kind Name="RazorDirective" />
|
||||||
|
<Field Name="Transition" Type="CSharpTransitionSyntax" Override="true">
|
||||||
|
<Kind Name="CSharpTransition" />
|
||||||
|
</Field>
|
||||||
|
<Field Name="Body" Type="CSharpSyntaxNode" Override="true">
|
||||||
|
<Kind Name="RazorDirectiveBody" />
|
||||||
|
</Field>
|
||||||
|
</Node>
|
||||||
|
<Node Name="RazorDirectiveBodySyntax" Base="CSharpSyntaxNode">
|
||||||
|
<Kind Name="RazorDirectiveBody" />
|
||||||
|
<Field Name="Keyword" Type="RazorSyntaxNode" />
|
||||||
|
<Field Name="CSharpCode" Type="CSharpCodeBlockSyntax" Optional="true" />
|
||||||
|
</Node>
|
||||||
</Tree>
|
</Tree>
|
||||||
|
|
@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
||||||
|
|
||||||
// use a value identity instead of object identity so a deserialized instance matches the original instance.
|
// use a value identity instead of object identity so a deserialized instance matches the original instance.
|
||||||
public string Kind { get; }
|
public string Kind { get; }
|
||||||
public string Data { get; }
|
public object Data { get; }
|
||||||
|
|
||||||
public SyntaxAnnotation()
|
public SyntaxAnnotation()
|
||||||
{
|
{
|
||||||
|
|
@ -34,7 +34,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
||||||
Kind = kind;
|
Kind = kind;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SyntaxAnnotation(string kind, string data)
|
public SyntaxAnnotation(string kind, object data)
|
||||||
: this(kind)
|
: this(kind)
|
||||||
{
|
{
|
||||||
Data = data;
|
Data = data;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
||||||
|
{
|
||||||
|
internal partial class MarkupTextLiteralSyntax
|
||||||
|
{
|
||||||
|
protected override string GetDebuggerDisplay()
|
||||||
|
{
|
||||||
|
return string.Format("{0} [{1}]", base.GetDebuggerDisplay(), this.GetContent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal partial class MarkupEphemeralTextLiteralSyntax
|
||||||
|
{
|
||||||
|
protected override string GetDebuggerDisplay()
|
||||||
|
{
|
||||||
|
return string.Format("{0} [{1}]", base.GetDebuggerDisplay(), this.GetContent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal partial class CSharpStatementLiteralSyntax
|
||||||
|
{
|
||||||
|
protected override string GetDebuggerDisplay()
|
||||||
|
{
|
||||||
|
return string.Format("{0} [{1}]", base.GetDebuggerDisplay(), this.GetContent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal partial class CSharpExpressionLiteralSyntax
|
||||||
|
{
|
||||||
|
protected override string GetDebuggerDisplay()
|
||||||
|
{
|
||||||
|
return string.Format("{0} [{1}]", base.GetDebuggerDisplay(), this.GetContent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal partial class CSharpEphemeralTextLiteralSyntax
|
||||||
|
{
|
||||||
|
protected override string GetDebuggerDisplay()
|
||||||
|
{
|
||||||
|
return string.Format("{0} [{1}]", base.GetDebuggerDisplay(), this.GetContent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal partial class UnclassifiedTextLiteralSyntax
|
||||||
|
{
|
||||||
|
protected override string GetDebuggerDisplay()
|
||||||
|
{
|
||||||
|
return string.Format("{0} [{1}]", base.GetDebuggerDisplay(), this.GetContent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,5 +14,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
||||||
{
|
{
|
||||||
return new SyntaxToken(InternalSyntax.SyntaxFactory.Token(kind, content), parent: null, position: 0);
|
return new SyntaxToken(InternalSyntax.SyntaxFactory.Token(kind, content), parent: null, position: 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static SyntaxToken MissingToken(SyntaxKind kind, params RazorDiagnostic[] diagnostics)
|
||||||
|
{
|
||||||
|
return new SyntaxToken(InternalSyntax.SyntaxFactory.MissingToken(kind, diagnostics), parent: null, position: 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,31 +6,53 @@ namespace Microsoft.AspNetCore.Razor.Language
|
||||||
internal enum SyntaxKind : byte
|
internal enum SyntaxKind : byte
|
||||||
{
|
{
|
||||||
#region Nodes
|
#region Nodes
|
||||||
// HTML
|
// Common
|
||||||
HtmlTextLiteral,
|
RazorDocument,
|
||||||
HtmlDocument,
|
GenericBlock,
|
||||||
HtmlDeclaration,
|
RazorComment,
|
||||||
|
RazorMetaCode,
|
||||||
|
RazorDirective,
|
||||||
|
RazorDirectiveBody,
|
||||||
|
UnclassifiedTextLiteral,
|
||||||
|
|
||||||
|
// Markup
|
||||||
|
MarkupBlock,
|
||||||
|
MarkupTransition,
|
||||||
|
MarkupElement,
|
||||||
|
MarkupTagBlock,
|
||||||
|
MarkupTextLiteral,
|
||||||
|
MarkupEphemeralTextLiteral,
|
||||||
|
MarkupCommentBlock,
|
||||||
|
MarkupAttributeBlock,
|
||||||
|
MarkupMinimizedAttributeBlock,
|
||||||
|
MarkupLiteralAttributeValue,
|
||||||
|
MarkupDynamicAttributeValue,
|
||||||
|
MarkupTagHelperElement,
|
||||||
|
MarkupTagHelperStartTag,
|
||||||
|
MarkupTagHelperEndTag,
|
||||||
|
MarkupTagHelperAttribute,
|
||||||
|
MarkupMinimizedTagHelperAttribute,
|
||||||
|
MarkupTagHelperAttributeValue,
|
||||||
|
|
||||||
// CSharp
|
// CSharp
|
||||||
CSharpBlock,
|
|
||||||
CSharpStatement,
|
CSharpStatement,
|
||||||
CSharpStatementBody,
|
CSharpStatementBody,
|
||||||
CSharpExpression,
|
CSharpExplicitExpression,
|
||||||
CSharpExpressionBody,
|
CSharpExplicitExpressionBody,
|
||||||
CSharpDirective,
|
CSharpImplicitExpression,
|
||||||
CSharpDirectiveBody,
|
CSharpImplicitExpressionBody,
|
||||||
CSharpCodeBlock,
|
CSharpCodeBlock,
|
||||||
CSharpCodeLiteral,
|
CSharpTemplateBlock,
|
||||||
CSharpMetaCode,
|
CSharpStatementLiteral,
|
||||||
|
CSharpExpressionLiteral,
|
||||||
|
CSharpEphemeralTextLiteral,
|
||||||
CSharpTransition,
|
CSharpTransition,
|
||||||
|
|
||||||
// Common
|
|
||||||
RazorComment,
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Tokens
|
#region Tokens
|
||||||
// Common
|
// Common
|
||||||
Unknown,
|
None,
|
||||||
|
Marker,
|
||||||
List,
|
List,
|
||||||
Whitespace,
|
Whitespace,
|
||||||
NewLine,
|
NewLine,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,126 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
||||||
|
{
|
||||||
|
internal abstract partial class SyntaxNode
|
||||||
|
{
|
||||||
|
private IEnumerable<SyntaxNode> DescendantNodesImpl(TextSpan span, Func<SyntaxNode, bool> descendIntoChildren, bool includeSelf)
|
||||||
|
{
|
||||||
|
if (includeSelf && IsInSpan(in span, FullSpan))
|
||||||
|
{
|
||||||
|
yield return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var stack = new ChildSyntaxListEnumeratorStack(this, descendIntoChildren))
|
||||||
|
{
|
||||||
|
while (stack.IsNotEmpty)
|
||||||
|
{
|
||||||
|
var nodeValue = stack.TryGetNextAsNodeInSpan(in span);
|
||||||
|
if (nodeValue != null)
|
||||||
|
{
|
||||||
|
// PERF: Push before yield return so that "nodeValue" is 'dead' after the yield
|
||||||
|
// and therefore doesn't need to be stored in the iterator state machine. This
|
||||||
|
// saves a field.
|
||||||
|
stack.PushChildren(nodeValue, descendIntoChildren);
|
||||||
|
|
||||||
|
yield return nodeValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsInSpan(in TextSpan span, TextSpan childSpan)
|
||||||
|
{
|
||||||
|
return span.OverlapsWith(childSpan)
|
||||||
|
// special case for zero-width tokens (OverlapsWith never returns true for these)
|
||||||
|
|| (childSpan.Length == 0 && span.IntersectsWith(childSpan));
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct ChildSyntaxListEnumeratorStack : IDisposable
|
||||||
|
{
|
||||||
|
private static readonly ObjectPool<ChildSyntaxList.Enumerator[]> StackPool = new ObjectPool<ChildSyntaxList.Enumerator[]>(() => new ChildSyntaxList.Enumerator[16]);
|
||||||
|
|
||||||
|
private ChildSyntaxList.Enumerator[] _stack;
|
||||||
|
private int _stackPtr;
|
||||||
|
|
||||||
|
public ChildSyntaxListEnumeratorStack(SyntaxNode startingNode, Func<SyntaxNode, bool> descendIntoChildren)
|
||||||
|
{
|
||||||
|
if (descendIntoChildren == null || descendIntoChildren(startingNode))
|
||||||
|
{
|
||||||
|
_stack = StackPool.Allocate();
|
||||||
|
_stackPtr = 0;
|
||||||
|
_stack[0].InitializeFrom(startingNode);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_stack = null;
|
||||||
|
_stackPtr = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsNotEmpty { get { return _stackPtr >= 0; } }
|
||||||
|
|
||||||
|
public bool TryGetNextInSpan(in TextSpan span, out SyntaxNode value)
|
||||||
|
{
|
||||||
|
while (_stack[_stackPtr].TryMoveNextAndGetCurrent(out value))
|
||||||
|
{
|
||||||
|
if (IsInSpan(in span, value.FullSpan))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_stackPtr--;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SyntaxNode TryGetNextAsNodeInSpan(in TextSpan span)
|
||||||
|
{
|
||||||
|
SyntaxNode nodeValue;
|
||||||
|
while ((nodeValue = _stack[_stackPtr].TryMoveNextAndGetCurrentAsNode()) != null)
|
||||||
|
{
|
||||||
|
if (IsInSpan(in span, nodeValue.FullSpan))
|
||||||
|
{
|
||||||
|
return nodeValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_stackPtr--;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PushChildren(SyntaxNode node)
|
||||||
|
{
|
||||||
|
if (++_stackPtr >= _stack.Length)
|
||||||
|
{
|
||||||
|
// Geometric growth
|
||||||
|
Array.Resize(ref _stack, checked(_stackPtr * 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
_stack[_stackPtr].InitializeFrom(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PushChildren(SyntaxNode node, Func<SyntaxNode, bool> descendIntoChildren)
|
||||||
|
{
|
||||||
|
if (descendIntoChildren == null || descendIntoChildren(node))
|
||||||
|
{
|
||||||
|
PushChildren(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
// Return only reasonably-sized stacks to the pool.
|
||||||
|
if (_stack?.Length < 256)
|
||||||
|
{
|
||||||
|
Array.Clear(_stack, 0, _stack.Length);
|
||||||
|
StackPool.Free(_stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,16 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// 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.Diagnostics;
|
||||||
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
||||||
{
|
{
|
||||||
internal abstract class SyntaxNode
|
[DebuggerDisplay("{GetDebuggerDisplay(), nq}")]
|
||||||
|
internal abstract partial class SyntaxNode
|
||||||
{
|
{
|
||||||
public SyntaxNode(GreenNode green, SyntaxNode parent, int position)
|
public SyntaxNode(GreenNode green, SyntaxNode parent, int position)
|
||||||
{
|
{
|
||||||
|
|
@ -60,6 +64,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
||||||
|
|
||||||
public bool IsMissing => Green.IsMissing;
|
public bool IsMissing => Green.IsMissing;
|
||||||
|
|
||||||
|
public bool IsToken => Green.IsToken;
|
||||||
|
|
||||||
|
public bool IsTrivia => Green.IsTrivia;
|
||||||
|
|
||||||
public bool HasLeadingTrivia
|
public bool HasLeadingTrivia
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
@ -80,6 +88,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
||||||
|
|
||||||
public bool ContainsAnnotations => Green.ContainsAnnotations;
|
public bool ContainsAnnotations => Green.ContainsAnnotations;
|
||||||
|
|
||||||
|
internal string SerializedValue => SyntaxSerializer.Serialize(this);
|
||||||
|
|
||||||
public abstract TResult Accept<TResult>(SyntaxVisitor<TResult> visitor);
|
public abstract TResult Accept<TResult>(SyntaxVisitor<TResult> visitor);
|
||||||
|
|
||||||
public abstract void Accept(SyntaxVisitor visitor);
|
public abstract void Accept(SyntaxVisitor visitor);
|
||||||
|
|
@ -253,18 +263,102 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
|
SyntaxNode lastChild = null;
|
||||||
for (var i = node.SlotCount - 1; i >= 0; i--)
|
for (var i = node.SlotCount - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
var child = node.GetNodeSlot(i);
|
var child = node.GetNodeSlot(i);
|
||||||
if (child != null)
|
if (child != null && child.FullWidth > 0)
|
||||||
{
|
{
|
||||||
node = child;
|
lastChild = child;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} while (node.SlotCount != 0);
|
node = lastChild;
|
||||||
|
} while (node?.SlotCount > 0);
|
||||||
|
|
||||||
return node == this ? this : node;
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The list of child nodes of this node, where each element is a SyntaxNode instance.
|
||||||
|
/// </summary>
|
||||||
|
public ChildSyntaxList ChildNodes()
|
||||||
|
{
|
||||||
|
return new ChildSyntaxList(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a list of ancestor nodes
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<SyntaxNode> Ancestors()
|
||||||
|
{
|
||||||
|
return Parent?
|
||||||
|
.AncestorsAndSelf() ??
|
||||||
|
Array.Empty<SyntaxNode>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a list of ancestor nodes (including this node)
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<SyntaxNode> AncestorsAndSelf()
|
||||||
|
{
|
||||||
|
for (var node = this; node != null; node = node.Parent)
|
||||||
|
{
|
||||||
|
yield return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the first node of type TNode that matches the predicate.
|
||||||
|
/// </summary>
|
||||||
|
public TNode FirstAncestorOrSelf<TNode>(Func<TNode, bool> predicate = null)
|
||||||
|
where TNode : SyntaxNode
|
||||||
|
{
|
||||||
|
for (var node = this; node != null; node = node.Parent)
|
||||||
|
{
|
||||||
|
if (node is TNode tnode && (predicate == null || predicate(tnode)))
|
||||||
|
{
|
||||||
|
return tnode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a list of descendant nodes in prefix document order.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="descendIntoChildren">An optional function that determines if the search descends into the argument node's children.</param>
|
||||||
|
public IEnumerable<SyntaxNode> DescendantNodes(Func<SyntaxNode, bool> descendIntoChildren = null)
|
||||||
|
{
|
||||||
|
return DescendantNodesImpl(FullSpan, descendIntoChildren, includeSelf: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a list of descendant nodes (including this node) in prefix document order.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="descendIntoChildren">An optional function that determines if the search descends into the argument node's children.</param>
|
||||||
|
public IEnumerable<SyntaxNode> DescendantNodesAndSelf(Func<SyntaxNode, bool> descendIntoChildren = null)
|
||||||
|
{
|
||||||
|
return DescendantNodesImpl(FullSpan, descendIntoChildren, includeSelf: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected internal SyntaxNode ReplaceCore<TNode>(
|
||||||
|
IEnumerable<TNode> nodes = null,
|
||||||
|
Func<TNode, TNode, SyntaxNode> computeReplacementNode = null)
|
||||||
|
where TNode : SyntaxNode
|
||||||
|
{
|
||||||
|
return SyntaxReplacer.Replace(this, nodes, computeReplacementNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected internal SyntaxNode ReplaceNodeInListCore(SyntaxNode originalNode, IEnumerable<SyntaxNode> replacementNodes)
|
||||||
|
{
|
||||||
|
return SyntaxReplacer.ReplaceNodeInList(this, originalNode, replacementNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected internal SyntaxNode InsertNodesInListCore(SyntaxNode nodeInList, IEnumerable<SyntaxNode> nodesToInsert, bool insertBefore)
|
||||||
|
{
|
||||||
|
return SyntaxReplacer.InsertNodeInList(this, nodeInList, nodesToInsert, insertBefore);
|
||||||
}
|
}
|
||||||
|
|
||||||
public RazorDiagnostic[] GetDiagnostics()
|
public RazorDiagnostic[] GetDiagnostics()
|
||||||
|
|
@ -294,12 +388,26 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return Green.ToString();
|
var builder = new StringBuilder();
|
||||||
|
builder.Append(Green.ToString());
|
||||||
|
builder.AppendFormat(" at {0}::{1}", Position, FullWidth);
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual string ToFullString()
|
public virtual string ToFullString()
|
||||||
{
|
{
|
||||||
return Green.ToFullString();
|
return Green.ToFullString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual string GetDebuggerDisplay()
|
||||||
|
{
|
||||||
|
if (IsToken)
|
||||||
|
{
|
||||||
|
return string.Format("{0};[{1}]", Kind, ToFullString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Format("{0} [{1}..{2})", Kind, Position, EndPosition);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,339 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// 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.Legacy;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
||||||
{
|
{
|
||||||
internal static class SyntaxNodeExtensions
|
internal static class SyntaxNodeExtensions
|
||||||
{
|
{
|
||||||
|
// From http://dev.w3.org/html5/spec/Overview.html#elements-0
|
||||||
|
private static readonly HashSet<string> VoidElements = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
"area",
|
||||||
|
"base",
|
||||||
|
"br",
|
||||||
|
"col",
|
||||||
|
"command",
|
||||||
|
"embed",
|
||||||
|
"hr",
|
||||||
|
"img",
|
||||||
|
"input",
|
||||||
|
"keygen",
|
||||||
|
"link",
|
||||||
|
"meta",
|
||||||
|
"param",
|
||||||
|
"source",
|
||||||
|
"track",
|
||||||
|
"wbr"
|
||||||
|
};
|
||||||
|
|
||||||
public static TNode WithAnnotations<TNode>(this TNode node, params SyntaxAnnotation[] annotations) where TNode : SyntaxNode
|
public static TNode WithAnnotations<TNode>(this TNode node, params SyntaxAnnotation[] annotations) where TNode : SyntaxNode
|
||||||
{
|
{
|
||||||
return (TNode)node.Green.SetAnnotations(annotations).CreateRed();
|
if (node == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (TNode)node.Green.SetAnnotations(annotations).CreateRed(node.Parent, node.Position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static object GetAnnotationValue<TNode>(this TNode node, string key) where TNode : SyntaxNode
|
||||||
|
{
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
var annotation = node.GetAnnotations().FirstOrDefault(n => n.Kind == key);
|
||||||
|
return annotation?.Data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TNode WithDiagnostics<TNode>(this TNode node, params RazorDiagnostic[] diagnostics) where TNode : SyntaxNode
|
||||||
|
{
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (TNode)node.Green.SetDiagnostics(diagnostics).CreateRed(node.Parent, node.Position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TNode AppendDiagnostic<TNode>(this TNode node, params RazorDiagnostic[] diagnostics) where TNode : SyntaxNode
|
||||||
|
{
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
var existingDiagnostics = node.GetDiagnostics();
|
||||||
|
var allDiagnostics = existingDiagnostics.Concat(diagnostics).ToArray();
|
||||||
|
|
||||||
|
return (TNode)node.WithDiagnostics(allDiagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SourceLocation GetSourceLocation(this SyntaxNode node, RazorSourceDocument source)
|
||||||
|
{
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(node));
|
||||||
|
}
|
||||||
|
if (source == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(source));
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (source.Length == 0)
|
||||||
|
{
|
||||||
|
// Just a marker symbol
|
||||||
|
return new SourceLocation(source.FilePath, 0, 0, 0);
|
||||||
|
}
|
||||||
|
if (node.Position == source.Length)
|
||||||
|
{
|
||||||
|
// E.g. Marker symbol at the end of the document
|
||||||
|
var lastPosition = source.Length - 1;
|
||||||
|
var endsWithLineBreak = ParserHelpers.IsNewLine(source[lastPosition]);
|
||||||
|
var lastLocation = source.Lines.GetLocation(lastPosition);
|
||||||
|
return new SourceLocation(
|
||||||
|
source.FilePath, // GetLocation prefers RelativePath but we want FilePath.
|
||||||
|
lastLocation.AbsoluteIndex + 1,
|
||||||
|
lastLocation.LineIndex + (endsWithLineBreak ? 1 : 0),
|
||||||
|
endsWithLineBreak ? 0 : lastLocation.CharacterIndex + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
var location = source.Lines.GetLocation(node.Position);
|
||||||
|
return new SourceLocation(
|
||||||
|
source.FilePath, // GetLocation prefers RelativePath but we want FilePath.
|
||||||
|
location.AbsoluteIndex,
|
||||||
|
location.LineIndex,
|
||||||
|
location.CharacterIndex);
|
||||||
|
}
|
||||||
|
catch (IndexOutOfRangeException)
|
||||||
|
{
|
||||||
|
Debug.Assert(false, "Node position should stay within document length.");
|
||||||
|
return new SourceLocation(source.FilePath, node.Position, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SourceSpan GetSourceSpan(this SyntaxNode node, RazorSourceDocument source)
|
||||||
|
{
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(node));
|
||||||
|
}
|
||||||
|
if (source == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(source));
|
||||||
|
}
|
||||||
|
|
||||||
|
var location = node.GetSourceLocation(source);
|
||||||
|
|
||||||
|
return new SourceSpan(location, node.FullWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new tree of nodes with the specified nodes, tokens and trivia replaced.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TRoot">The type of the root node.</typeparam>
|
||||||
|
/// <param name="root">The root node of the tree of nodes.</param>
|
||||||
|
/// <param name="nodes">The nodes to be replaced.</param>
|
||||||
|
/// <param name="computeReplacementNode">A function that computes a replacement node for the
|
||||||
|
/// argument nodes. The first argument is the original node. The second argument is the same
|
||||||
|
/// node potentially rewritten with replaced descendants.</param>
|
||||||
|
public static TRoot ReplaceSyntax<TRoot>(
|
||||||
|
this TRoot root,
|
||||||
|
IEnumerable<SyntaxNode> nodes,
|
||||||
|
Func<SyntaxNode, SyntaxNode, SyntaxNode> computeReplacementNode)
|
||||||
|
where TRoot : SyntaxNode
|
||||||
|
{
|
||||||
|
return (TRoot)root.ReplaceCore(
|
||||||
|
nodes: nodes, computeReplacementNode: computeReplacementNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new tree of nodes with the specified old node replaced with a new node.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TRoot">The type of the root node.</typeparam>
|
||||||
|
/// <typeparam name="TNode">The type of the nodes being replaced.</typeparam>
|
||||||
|
/// <param name="root">The root node of the tree of nodes.</param>
|
||||||
|
/// <param name="nodes">The nodes to be replaced; descendants of the root node.</param>
|
||||||
|
/// <param name="computeReplacementNode">A function that computes a replacement node for the
|
||||||
|
/// argument nodes. The first argument is the original node. The second argument is the same
|
||||||
|
/// node potentially rewritten with replaced descendants.</param>
|
||||||
|
public static TRoot ReplaceNodes<TRoot, TNode>(this TRoot root, IEnumerable<TNode> nodes, Func<TNode, TNode, SyntaxNode> computeReplacementNode)
|
||||||
|
where TRoot : SyntaxNode
|
||||||
|
where TNode : SyntaxNode
|
||||||
|
{
|
||||||
|
return (TRoot)root.ReplaceCore(nodes: nodes, computeReplacementNode: computeReplacementNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new tree of nodes with the specified old node replaced with a new node.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TRoot">The type of the root node.</typeparam>
|
||||||
|
/// <param name="root">The root node of the tree of nodes.</param>
|
||||||
|
/// <param name="oldNode">The node to be replaced; a descendant of the root node.</param>
|
||||||
|
/// <param name="newNode">The new node to use in the new tree in place of the old node.</param>
|
||||||
|
public static TRoot ReplaceNode<TRoot>(this TRoot root, SyntaxNode oldNode, SyntaxNode newNode)
|
||||||
|
where TRoot : SyntaxNode
|
||||||
|
{
|
||||||
|
if (oldNode == newNode)
|
||||||
|
{
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (TRoot)root.ReplaceCore(nodes: new[] { oldNode }, computeReplacementNode: (o, r) => newNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new tree of nodes with specified old node replaced with a new nodes.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TRoot">The type of the root node.</typeparam>
|
||||||
|
/// <param name="root">The root of the tree of nodes.</param>
|
||||||
|
/// <param name="oldNode">The node to be replaced; a descendant of the root node and an element of a list member.</param>
|
||||||
|
/// <param name="newNodes">A sequence of nodes to use in the tree in place of the old node.</param>
|
||||||
|
public static TRoot ReplaceNode<TRoot>(this TRoot root, SyntaxNode oldNode, IEnumerable<SyntaxNode> newNodes)
|
||||||
|
where TRoot : SyntaxNode
|
||||||
|
{
|
||||||
|
return (TRoot)root.ReplaceNodeInListCore(oldNode, newNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new tree of nodes with new nodes inserted before the specified node.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TRoot">The type of the root node.</typeparam>
|
||||||
|
/// <param name="root">The root of the tree of nodes.</param>
|
||||||
|
/// <param name="nodeInList">The node to insert before; a descendant of the root node an element of a list member.</param>
|
||||||
|
/// <param name="newNodes">A sequence of nodes to insert into the tree immediately before the specified node.</param>
|
||||||
|
public static TRoot InsertNodesBefore<TRoot>(this TRoot root, SyntaxNode nodeInList, IEnumerable<SyntaxNode> newNodes)
|
||||||
|
where TRoot : SyntaxNode
|
||||||
|
{
|
||||||
|
return (TRoot)root.InsertNodesInListCore(nodeInList, newNodes, insertBefore: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new tree of nodes with new nodes inserted after the specified node.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TRoot">The type of the root node.</typeparam>
|
||||||
|
/// <param name="root">The root of the tree of nodes.</param>
|
||||||
|
/// <param name="nodeInList">The node to insert after; a descendant of the root node an element of a list member.</param>
|
||||||
|
/// <param name="newNodes">A sequence of nodes to insert into the tree immediately after the specified node.</param>
|
||||||
|
public static TRoot InsertNodesAfter<TRoot>(this TRoot root, SyntaxNode nodeInList, IEnumerable<SyntaxNode> newNodes)
|
||||||
|
where TRoot : SyntaxNode
|
||||||
|
{
|
||||||
|
return (TRoot)root.InsertNodesInListCore(nodeInList, newNodes, insertBefore: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetContent<TNode>(this TNode node) where TNode : SyntaxNode
|
||||||
|
{
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
var tokens = node.DescendantNodes().Where(n => n.IsToken).Cast<SyntaxToken>();
|
||||||
|
var content = string.Concat(tokens.Select(t => t.Content));
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetTagName(this MarkupTagBlockSyntax tagBlock)
|
||||||
|
{
|
||||||
|
if (tagBlock == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(tagBlock));
|
||||||
|
}
|
||||||
|
|
||||||
|
var child = tagBlock.Children[0];
|
||||||
|
|
||||||
|
if (tagBlock.Children.Count == 0 || !(child is MarkupTextLiteralSyntax))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var childLiteral = (MarkupTextLiteralSyntax)child;
|
||||||
|
SyntaxToken textToken = null;
|
||||||
|
for (var i = 0; i < childLiteral.LiteralTokens.Count; i++)
|
||||||
|
{
|
||||||
|
var token = childLiteral.LiteralTokens[i];
|
||||||
|
|
||||||
|
if (token != null &&
|
||||||
|
(token.Kind == SyntaxKind.Whitespace || token.Kind == SyntaxKind.Text))
|
||||||
|
{
|
||||||
|
textToken = token;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (textToken == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return textToken.Kind == SyntaxKind.Whitespace ? null : textToken.Content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetTagName(this MarkupTagHelperStartTagSyntax tagBlock)
|
||||||
|
{
|
||||||
|
if (tagBlock == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(tagBlock));
|
||||||
|
}
|
||||||
|
|
||||||
|
var child = tagBlock.Children[0];
|
||||||
|
|
||||||
|
if (tagBlock.Children.Count == 0 || !(child is MarkupTextLiteralSyntax))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var childLiteral = (MarkupTextLiteralSyntax)child;
|
||||||
|
SyntaxToken textToken = null;
|
||||||
|
for (var i = 0; i < childLiteral.LiteralTokens.Count; i++)
|
||||||
|
{
|
||||||
|
var token = childLiteral.LiteralTokens[i];
|
||||||
|
|
||||||
|
if (token != null &&
|
||||||
|
(token.Kind == SyntaxKind.Whitespace || token.Kind == SyntaxKind.Text))
|
||||||
|
{
|
||||||
|
textToken = token;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (textToken == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return textToken.Kind == SyntaxKind.Whitespace ? null : textToken.Content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsSelfClosing(this MarkupTagBlockSyntax 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 MarkupTagBlockSyntax tagBlock)
|
||||||
|
{
|
||||||
|
if (tagBlock == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(tagBlock));
|
||||||
|
}
|
||||||
|
|
||||||
|
return VoidElements.Contains(tagBlock.GetTagName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,207 @@
|
||||||
|
// 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.Linq;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
||||||
|
{
|
||||||
|
internal static class SyntaxReplacer
|
||||||
|
{
|
||||||
|
internal static SyntaxNode Replace<TNode>(
|
||||||
|
SyntaxNode root,
|
||||||
|
IEnumerable<TNode> nodes = null,
|
||||||
|
Func<TNode, TNode, SyntaxNode> computeReplacementNode = null)
|
||||||
|
where TNode : SyntaxNode
|
||||||
|
{
|
||||||
|
var replacer = new Replacer<TNode>(nodes, computeReplacementNode);
|
||||||
|
|
||||||
|
if (replacer.HasWork)
|
||||||
|
{
|
||||||
|
return replacer.Visit(root);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static SyntaxNode ReplaceNodeInList(SyntaxNode root, SyntaxNode originalNode, IEnumerable<SyntaxNode> newNodes)
|
||||||
|
{
|
||||||
|
return new NodeListEditor(originalNode, newNodes, ListEditKind.Replace).Visit(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static SyntaxNode InsertNodeInList(SyntaxNode root, SyntaxNode nodeInList, IEnumerable<SyntaxNode> nodesToInsert, bool insertBefore)
|
||||||
|
{
|
||||||
|
return new NodeListEditor(nodeInList, nodesToInsert, insertBefore ? ListEditKind.InsertBefore : ListEditKind.InsertAfter).Visit(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Replacer<TNode> : SyntaxRewriter where TNode : SyntaxNode
|
||||||
|
{
|
||||||
|
private readonly Func<TNode, TNode, SyntaxNode> _computeReplacementNode;
|
||||||
|
private readonly HashSet<SyntaxNode> _nodeSet;
|
||||||
|
private readonly HashSet<TextSpan> _spanSet;
|
||||||
|
private readonly TextSpan _totalSpan;
|
||||||
|
|
||||||
|
public Replacer(IEnumerable<TNode> nodes, Func<TNode, TNode, SyntaxNode> computeReplacementNode)
|
||||||
|
{
|
||||||
|
_computeReplacementNode = computeReplacementNode;
|
||||||
|
_nodeSet = nodes != null ? new HashSet<SyntaxNode>(nodes) : new HashSet<SyntaxNode>();
|
||||||
|
_spanSet = new HashSet<TextSpan>(_nodeSet.Select(n => n.FullSpan));
|
||||||
|
_totalSpan = ComputeTotalSpan(_spanSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasWork => _nodeSet.Count > 0;
|
||||||
|
|
||||||
|
public override SyntaxNode Visit(SyntaxNode node)
|
||||||
|
{
|
||||||
|
var rewritten = node;
|
||||||
|
|
||||||
|
if (node != null)
|
||||||
|
{
|
||||||
|
if (ShouldVisit(node.FullSpan))
|
||||||
|
{
|
||||||
|
rewritten = base.Visit(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_nodeSet.Contains(node) && _computeReplacementNode != null)
|
||||||
|
{
|
||||||
|
rewritten = _computeReplacementNode((TNode)node, (TNode)rewritten);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rewritten;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TextSpan ComputeTotalSpan(IEnumerable<TextSpan> spans)
|
||||||
|
{
|
||||||
|
var first = true;
|
||||||
|
var start = 0;
|
||||||
|
var end = 0;
|
||||||
|
|
||||||
|
foreach (var span in spans)
|
||||||
|
{
|
||||||
|
if (first)
|
||||||
|
{
|
||||||
|
start = span.Start;
|
||||||
|
end = span.End;
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
start = Math.Min(start, span.Start);
|
||||||
|
end = Math.Max(end, span.End);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TextSpan(start, end - start);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ShouldVisit(TextSpan span)
|
||||||
|
{
|
||||||
|
// first do quick check against total span
|
||||||
|
if (!span.IntersectsWith(_totalSpan))
|
||||||
|
{
|
||||||
|
// if the node is outside the total span of the nodes to be replaced
|
||||||
|
// then we won't find any nodes to replace below it.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var s in _spanSet)
|
||||||
|
{
|
||||||
|
if (span.IntersectsWith(s))
|
||||||
|
{
|
||||||
|
// node's full span intersects with at least one node to be replaced
|
||||||
|
// so we need to visit node's children to find it.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NodeListEditor : SyntaxRewriter
|
||||||
|
{
|
||||||
|
private readonly TextSpan _elementSpan;
|
||||||
|
private readonly SyntaxNode _originalNode;
|
||||||
|
private readonly IEnumerable<SyntaxNode> _newNodes;
|
||||||
|
private readonly ListEditKind _editKind;
|
||||||
|
|
||||||
|
public NodeListEditor(
|
||||||
|
SyntaxNode originalNode,
|
||||||
|
IEnumerable<SyntaxNode> replacementNodes,
|
||||||
|
ListEditKind editKind)
|
||||||
|
{
|
||||||
|
_elementSpan = originalNode.Span;
|
||||||
|
_originalNode = originalNode;
|
||||||
|
_newNodes = replacementNodes;
|
||||||
|
_editKind = editKind;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ShouldVisit(TextSpan span)
|
||||||
|
{
|
||||||
|
if (span.IntersectsWith(_elementSpan))
|
||||||
|
{
|
||||||
|
// node's full span intersects with at least one node to be replaced
|
||||||
|
// so we need to visit node's children to find it.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override SyntaxNode Visit(SyntaxNode node)
|
||||||
|
{
|
||||||
|
if (node == _originalNode)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Expecting a list");
|
||||||
|
}
|
||||||
|
|
||||||
|
var rewritten = node;
|
||||||
|
|
||||||
|
if (node != null)
|
||||||
|
{
|
||||||
|
if (ShouldVisit(node.FullSpan))
|
||||||
|
{
|
||||||
|
rewritten = base.Visit(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rewritten;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override SyntaxList<TNode> VisitList<TNode>(SyntaxList<TNode> list)
|
||||||
|
{
|
||||||
|
if (_originalNode is TNode)
|
||||||
|
{
|
||||||
|
var index = list.IndexOf((TNode)_originalNode);
|
||||||
|
if (index >= 0 && index < list.Count)
|
||||||
|
{
|
||||||
|
switch (_editKind)
|
||||||
|
{
|
||||||
|
case ListEditKind.Replace:
|
||||||
|
return list.ReplaceRange((TNode)_originalNode, _newNodes.Cast<TNode>());
|
||||||
|
|
||||||
|
case ListEditKind.InsertAfter:
|
||||||
|
return list.InsertRange(index + 1, _newNodes.Cast<TNode>());
|
||||||
|
|
||||||
|
case ListEditKind.InsertBefore:
|
||||||
|
return list.InsertRange(index, _newNodes.Cast<TNode>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.VisitList<TNode>(list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum ListEditKind
|
||||||
|
{
|
||||||
|
InsertBefore,
|
||||||
|
InsertAfter,
|
||||||
|
Replace
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,148 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
||||||
|
{
|
||||||
|
internal abstract partial class SyntaxRewriter : SyntaxVisitor<SyntaxNode>
|
||||||
|
{
|
||||||
|
public override SyntaxNode VisitToken(SyntaxToken token)
|
||||||
|
{
|
||||||
|
// PERF: This is a hot method, so it has been written to minimize the following:
|
||||||
|
// 1. Virtual method calls
|
||||||
|
// 2. Copying of structs
|
||||||
|
// 3. Repeated null checks
|
||||||
|
|
||||||
|
// PERF: Avoid testing node for null more than once
|
||||||
|
var node = token?.Green;
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
// PERF: Make one virtual method call each to get the leading and trailing trivia
|
||||||
|
var leadingTrivia = node.GetLeadingTrivia();
|
||||||
|
var trailingTrivia = node.GetTrailingTrivia();
|
||||||
|
|
||||||
|
// Trivia is either null or a non-empty list (there's no such thing as an empty green list)
|
||||||
|
Debug.Assert(leadingTrivia == null || !leadingTrivia.IsList || leadingTrivia.SlotCount > 0);
|
||||||
|
Debug.Assert(trailingTrivia == null || !trailingTrivia.IsList || trailingTrivia.SlotCount > 0);
|
||||||
|
|
||||||
|
if (leadingTrivia != null)
|
||||||
|
{
|
||||||
|
// PERF: Expand token.LeadingTrivia when node is not null.
|
||||||
|
var leading = VisitList(new SyntaxTriviaList(leadingTrivia.CreateRed(token, token.Position)));
|
||||||
|
|
||||||
|
if (trailingTrivia != null)
|
||||||
|
{
|
||||||
|
// Both leading and trailing trivia
|
||||||
|
|
||||||
|
// PERF: Expand token.TrailingTrivia when node is not null and leadingTrivia is not null.
|
||||||
|
// Also avoid node.Width because it makes a virtual call to GetText. Instead use node.FullWidth - trailingTrivia.FullWidth.
|
||||||
|
var index = leadingTrivia.IsList ? leadingTrivia.SlotCount : 1;
|
||||||
|
var position = token.Position + node.FullWidth - trailingTrivia.FullWidth;
|
||||||
|
var trailing = VisitList(new SyntaxTriviaList(trailingTrivia.CreateRed(token, position), position, index));
|
||||||
|
|
||||||
|
if (leading.Node.Green != leadingTrivia)
|
||||||
|
{
|
||||||
|
token = token.WithLeadingTrivia(leading);
|
||||||
|
}
|
||||||
|
|
||||||
|
return trailing.Node.Green != trailingTrivia ? token.WithTrailingTrivia(trailing) : token;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Leading trivia only
|
||||||
|
return leading.Node.Green != leadingTrivia ? token.WithLeadingTrivia(leading) : token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (trailingTrivia != null)
|
||||||
|
{
|
||||||
|
// Trailing trivia only
|
||||||
|
// PERF: Expand token.TrailingTrivia when node is not null and leading is null.
|
||||||
|
// Also avoid node.Width because it makes a virtual call to GetText. Instead use node.FullWidth - trailingTrivia.FullWidth.
|
||||||
|
var position = token.Position + node.FullWidth - trailingTrivia.FullWidth;
|
||||||
|
var trailing = VisitList(new SyntaxTriviaList(trailingTrivia.CreateRed(token, position), position, index: 0));
|
||||||
|
return trailing.Node.Green != trailingTrivia ? token.WithTrailingTrivia(trailing) : token;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// No trivia
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual SyntaxList<TNode> VisitList<TNode>(SyntaxList<TNode> list) where TNode : SyntaxNode
|
||||||
|
{
|
||||||
|
SyntaxListBuilder alternate = null;
|
||||||
|
for (int i = 0, n = list.Count; i < n; i++)
|
||||||
|
{
|
||||||
|
var item = list[i];
|
||||||
|
var visited = VisitListElement(item);
|
||||||
|
if (item != visited && alternate == null)
|
||||||
|
{
|
||||||
|
alternate = new SyntaxListBuilder(n);
|
||||||
|
alternate.AddRange(list, 0, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alternate != null && visited != null)
|
||||||
|
{
|
||||||
|
alternate.Add(visited);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alternate != null)
|
||||||
|
{
|
||||||
|
return alternate.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override SyntaxNode VisitTrivia(SyntaxTrivia trivia)
|
||||||
|
{
|
||||||
|
return trivia;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual SyntaxTriviaList VisitList(SyntaxTriviaList list)
|
||||||
|
{
|
||||||
|
var count = list.Count;
|
||||||
|
if (count != 0)
|
||||||
|
{
|
||||||
|
SyntaxTriviaListBuilder alternate = null;
|
||||||
|
var index = -1;
|
||||||
|
|
||||||
|
foreach (var item in list)
|
||||||
|
{
|
||||||
|
index++;
|
||||||
|
var visited = VisitListElement(item);
|
||||||
|
|
||||||
|
//skip the null check since SyntaxTrivia is a value type
|
||||||
|
if (visited != item && alternate == null)
|
||||||
|
{
|
||||||
|
alternate = new SyntaxTriviaListBuilder(count);
|
||||||
|
alternate.Add(list, 0, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alternate != null && visited != null)
|
||||||
|
{
|
||||||
|
alternate.Add(visited);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alternate != null)
|
||||||
|
{
|
||||||
|
return alternate.ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual TNode VisitListElement<TNode>(TNode node) where TNode : SyntaxNode
|
||||||
|
{
|
||||||
|
return (TNode)(SyntaxNode)Visit(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,304 @@
|
||||||
|
// 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.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
||||||
|
{
|
||||||
|
internal class SyntaxSerializer
|
||||||
|
{
|
||||||
|
internal static string Serialize(SyntaxNode node)
|
||||||
|
{
|
||||||
|
using (var writer = new StringWriter())
|
||||||
|
{
|
||||||
|
var walker = new Walker(writer);
|
||||||
|
walker.Visit(node);
|
||||||
|
|
||||||
|
return writer.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Walker : SyntaxWalker
|
||||||
|
{
|
||||||
|
private readonly SyntaxWriter _visitor;
|
||||||
|
private readonly TextWriter _writer;
|
||||||
|
|
||||||
|
public Walker(TextWriter writer)
|
||||||
|
{
|
||||||
|
_visitor = new SyntaxWriter(writer);
|
||||||
|
_writer = writer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextWriter Writer { get; }
|
||||||
|
|
||||||
|
public override SyntaxNode Visit(SyntaxNode node)
|
||||||
|
{
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.IsList)
|
||||||
|
{
|
||||||
|
return base.DefaultVisit(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
_visitor.Visit(node);
|
||||||
|
_writer.WriteLine();
|
||||||
|
|
||||||
|
if (!node.IsToken && !node.IsTrivia)
|
||||||
|
{
|
||||||
|
_visitor.Depth++;
|
||||||
|
node = base.DefaultVisit(node);
|
||||||
|
_visitor.Depth--;
|
||||||
|
}
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SyntaxWalker : SyntaxRewriter
|
||||||
|
{
|
||||||
|
private readonly List<SyntaxNode> _ancestors = new List<SyntaxNode>();
|
||||||
|
|
||||||
|
protected IReadOnlyList<SyntaxNode> Ancestors => _ancestors;
|
||||||
|
|
||||||
|
protected SyntaxNode Parent => _ancestors.Count > 0 ? _ancestors[0] : null;
|
||||||
|
|
||||||
|
protected override SyntaxNode DefaultVisit(SyntaxNode node)
|
||||||
|
{
|
||||||
|
_ancestors.Insert(0, node);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
for (var i = 0; i < node.SlotCount; i++)
|
||||||
|
{
|
||||||
|
var child = node.GetNodeSlot(i);
|
||||||
|
Visit(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_ancestors.RemoveAt(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SyntaxWriter : SyntaxRewriter
|
||||||
|
{
|
||||||
|
private readonly TextWriter _writer;
|
||||||
|
private bool _visitedRoot;
|
||||||
|
|
||||||
|
public SyntaxWriter(TextWriter writer)
|
||||||
|
{
|
||||||
|
_writer = writer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Depth { get; set; }
|
||||||
|
|
||||||
|
public override SyntaxNode Visit(SyntaxNode node)
|
||||||
|
{
|
||||||
|
if (node is SyntaxToken token)
|
||||||
|
{
|
||||||
|
return VisitToken(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteNode(node);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override SyntaxNode VisitToken(SyntaxToken token)
|
||||||
|
{
|
||||||
|
WriteToken(token);
|
||||||
|
return base.VisitToken(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override SyntaxNode VisitTrivia(SyntaxTrivia trivia)
|
||||||
|
{
|
||||||
|
WriteTrivia(trivia);
|
||||||
|
return base.VisitTrivia(trivia);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteNode(SyntaxNode node)
|
||||||
|
{
|
||||||
|
WriteIndent();
|
||||||
|
Write(node.Kind);
|
||||||
|
WriteSeparator();
|
||||||
|
Write($"[{node.Position}..{node.EndPosition})");
|
||||||
|
WriteSeparator();
|
||||||
|
Write($"FullWidth: {node.FullWidth}");
|
||||||
|
|
||||||
|
if (node is RazorDirectiveSyntax razorDirective)
|
||||||
|
{
|
||||||
|
WriteRazorDirective(razorDirective);
|
||||||
|
}
|
||||||
|
else if (node is MarkupTagHelperElementSyntax tagHelperElement)
|
||||||
|
{
|
||||||
|
WriteTagHelperElement(tagHelperElement);
|
||||||
|
}
|
||||||
|
else if (node is MarkupTagHelperAttributeSyntax tagHelperAttribute)
|
||||||
|
{
|
||||||
|
WriteTagHelperAttributeInfo(tagHelperAttribute.TagHelperAttributeInfo);
|
||||||
|
}
|
||||||
|
else if (node is MarkupMinimizedTagHelperAttributeSyntax minimizedTagHelperAttribute)
|
||||||
|
{
|
||||||
|
WriteTagHelperAttributeInfo(minimizedTagHelperAttribute.TagHelperAttributeInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ShouldDisplayNodeContent(node))
|
||||||
|
{
|
||||||
|
WriteSeparator();
|
||||||
|
Write($"[{node.GetContent()}]");
|
||||||
|
}
|
||||||
|
|
||||||
|
var annotation = node.GetAnnotations().FirstOrDefault(a => a.Kind == SyntaxConstants.SpanContextKind);
|
||||||
|
if (annotation != null && annotation.Data is SpanContext context)
|
||||||
|
{
|
||||||
|
WriteSpanContext(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_visitedRoot)
|
||||||
|
{
|
||||||
|
WriteSeparator();
|
||||||
|
Write($"[{node.ToFullString()}]");
|
||||||
|
_visitedRoot = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteRazorDirective(RazorDirectiveSyntax node)
|
||||||
|
{
|
||||||
|
if (node.DirectiveDescriptor == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var builder = new StringBuilder("Directive:{");
|
||||||
|
builder.Append(node.DirectiveDescriptor.Directive);
|
||||||
|
builder.Append(";");
|
||||||
|
builder.Append(node.DirectiveDescriptor.Kind);
|
||||||
|
builder.Append(";");
|
||||||
|
builder.Append(node.DirectiveDescriptor.Usage);
|
||||||
|
builder.Append("}");
|
||||||
|
|
||||||
|
var diagnostics = node.GetDiagnostics();
|
||||||
|
if (diagnostics.Length > 0)
|
||||||
|
{
|
||||||
|
builder.Append(" [");
|
||||||
|
var ids = string.Join(", ", diagnostics.Select(diagnostic => $"{diagnostic.Id}{diagnostic.Span}"));
|
||||||
|
builder.Append(ids);
|
||||||
|
builder.Append("]");
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteSeparator();
|
||||||
|
Write(builder.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteTagHelperElement(MarkupTagHelperElementSyntax node)
|
||||||
|
{
|
||||||
|
// Write tag name
|
||||||
|
WriteSeparator();
|
||||||
|
Write($"{node.TagHelperInfo.TagName}[{node.TagHelperInfo.TagMode}]");
|
||||||
|
|
||||||
|
// Write descriptors
|
||||||
|
foreach (var descriptor in node.TagHelperInfo.BindingResult.Descriptors)
|
||||||
|
{
|
||||||
|
WriteSeparator();
|
||||||
|
|
||||||
|
// Get the type name without the namespace.
|
||||||
|
var typeName = descriptor.Name.Substring(descriptor.Name.LastIndexOf('.') + 1);
|
||||||
|
Write(typeName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteTagHelperAttributeInfo(TagHelperAttributeInfo info)
|
||||||
|
{
|
||||||
|
// Write attributes
|
||||||
|
WriteSeparator();
|
||||||
|
Write(info.Name);
|
||||||
|
WriteSeparator();
|
||||||
|
Write(info.AttributeStructure);
|
||||||
|
WriteSeparator();
|
||||||
|
Write(info.Bound ? "Bound" : "Unbound");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteToken(SyntaxToken token)
|
||||||
|
{
|
||||||
|
WriteIndent();
|
||||||
|
var content = token.IsMissing ? "<Missing>" : token.Content;
|
||||||
|
var diagnostics = token.GetDiagnostics();
|
||||||
|
var tokenString = $"{token.Kind};[{content}];{string.Join(", ", diagnostics.Select(diagnostic => diagnostic.Id + diagnostic.Span))}";
|
||||||
|
Write(tokenString);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteTrivia(SyntaxTrivia trivia)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteSpanContext(SpanContext context)
|
||||||
|
{
|
||||||
|
WriteSeparator();
|
||||||
|
Write($"Gen<{context.ChunkGenerator}>");
|
||||||
|
WriteSeparator();
|
||||||
|
Write(context.EditHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void WriteIndent()
|
||||||
|
{
|
||||||
|
for (var i = 0; i < Depth; i++)
|
||||||
|
{
|
||||||
|
for (var j = 0; j < 4; j++)
|
||||||
|
{
|
||||||
|
Write(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void WriteSeparator()
|
||||||
|
{
|
||||||
|
Write(" - ");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void WriteNewLine()
|
||||||
|
{
|
||||||
|
_writer.WriteLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void Write(object value)
|
||||||
|
{
|
||||||
|
if (value is string stringValue)
|
||||||
|
{
|
||||||
|
stringValue = stringValue.Replace(Environment.NewLine, "LF");
|
||||||
|
_writer.Write(stringValue);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_writer.Write(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ShouldDisplayNodeContent(SyntaxNode node)
|
||||||
|
{
|
||||||
|
return node.Kind == SyntaxKind.MarkupTextLiteral ||
|
||||||
|
node.Kind == SyntaxKind.MarkupEphemeralTextLiteral ||
|
||||||
|
node.Kind == SyntaxKind.MarkupTagBlock ||
|
||||||
|
node.Kind == SyntaxKind.MarkupAttributeBlock ||
|
||||||
|
node.Kind == SyntaxKind.MarkupMinimizedAttributeBlock ||
|
||||||
|
node.Kind == SyntaxKind.MarkupTagHelperAttribute ||
|
||||||
|
node.Kind == SyntaxKind.MarkupMinimizedTagHelperAttribute ||
|
||||||
|
node.Kind == SyntaxKind.MarkupLiteralAttributeValue ||
|
||||||
|
node.Kind == SyntaxKind.MarkupDynamicAttributeValue ||
|
||||||
|
node.Kind == SyntaxKind.CSharpStatementLiteral ||
|
||||||
|
node.Kind == SyntaxKind.CSharpExpressionLiteral ||
|
||||||
|
node.Kind == SyntaxKind.CSharpEphemeralTextLiteral ||
|
||||||
|
node.Kind == SyntaxKind.UnclassifiedTextLiteral;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,50 +9,11 @@ using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
||||||
{
|
{
|
||||||
internal class SyntaxToken : SyntaxNode
|
internal class SyntaxToken : RazorSyntaxNode
|
||||||
{
|
{
|
||||||
internal SyntaxToken(GreenNode green, SyntaxNode parent, int position)
|
internal SyntaxToken(GreenNode green, SyntaxNode parent, int position)
|
||||||
: this(green, parent, null, position)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// Temporary plumbing
|
|
||||||
internal SyntaxToken(GreenNode green, SyntaxNode parent, Span parentSpan, int position)
|
|
||||||
: base(green, parent, position)
|
: base(green, parent, position)
|
||||||
{
|
{
|
||||||
Debug.Assert(parent == null || !parent.Green.IsList, "list cannot be a parent");
|
|
||||||
Debug.Assert(green == null || green.IsToken, "green must be a token");
|
|
||||||
|
|
||||||
ParentSpan = parentSpan;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Temporary plumbing
|
|
||||||
internal Span ParentSpan { get; }
|
|
||||||
|
|
||||||
// Temporary plumbing
|
|
||||||
internal SourceLocation Start
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (ParentSpan == null)
|
|
||||||
{
|
|
||||||
return SourceLocation.Undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
var tracker = new SourceLocationTracker(ParentSpan.Start);
|
|
||||||
for (var i = 0; i < ParentSpan.Tokens.Count; i++)
|
|
||||||
{
|
|
||||||
var token = ParentSpan.Tokens[i];
|
|
||||||
if (object.ReferenceEquals(this, token))
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
tracker.UpdateLocation(token.Content);
|
|
||||||
}
|
|
||||||
|
|
||||||
return tracker.CurrentLocation;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal new InternalSyntax.SyntaxToken Green => (InternalSyntax.SyntaxToken)base.Green;
|
internal new InternalSyntax.SyntaxToken Green => (InternalSyntax.SyntaxToken)base.Green;
|
||||||
|
|
@ -107,32 +68,33 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
||||||
|
|
||||||
public override SyntaxTriviaList GetLeadingTrivia()
|
public override SyntaxTriviaList GetLeadingTrivia()
|
||||||
{
|
{
|
||||||
if (Green.LeadingTrivia == null)
|
var leading = Green.GetLeadingTrivia();
|
||||||
|
if (leading == null)
|
||||||
{
|
{
|
||||||
return default(SyntaxTriviaList);
|
return default(SyntaxTriviaList);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new SyntaxTriviaList(Green.LeadingTrivia.CreateRed(this, Position), Position);
|
return new SyntaxTriviaList(leading.CreateRed(this, Position), Position);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override SyntaxTriviaList GetTrailingTrivia()
|
public override SyntaxTriviaList GetTrailingTrivia()
|
||||||
{
|
{
|
||||||
var trailingGreen = Green.TrailingTrivia;
|
var trailing = Green.GetTrailingTrivia();
|
||||||
if (trailingGreen == null)
|
if (trailing == null)
|
||||||
{
|
{
|
||||||
return default(SyntaxTriviaList);
|
return default(SyntaxTriviaList);
|
||||||
}
|
}
|
||||||
|
|
||||||
var leading = Green.LeadingTrivia;
|
var leading = Green.GetLeadingTrivia();
|
||||||
int index = 0;
|
var index = 0;
|
||||||
if (leading != null)
|
if (leading != null)
|
||||||
{
|
{
|
||||||
index = leading.IsList ? leading.SlotCount : 1;
|
index = leading.IsList ? leading.SlotCount : 1;
|
||||||
}
|
}
|
||||||
int trailingPosition = Position + FullWidth;
|
int trailingPosition = Position + FullWidth;
|
||||||
trailingPosition -= trailingGreen.FullWidth;
|
trailingPosition -= trailing.FullWidth;
|
||||||
|
|
||||||
return new SyntaxTriviaList(trailingGreen.CreateRed(this, trailingPosition), trailingPosition, index);
|
return new SyntaxTriviaList(trailing.CreateRed(this, trailingPosition), trailingPosition, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,14 @@
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a <see cref="SyntaxNode"/> visitor that visits only the single SyntaxNode
|
||||||
|
/// passed into its Visit method and produces
|
||||||
|
/// a value of the type specified by the <typeparamref name="TResult"/> parameter.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TResult">
|
||||||
|
/// The type of the return value this visitor's Visit method.
|
||||||
|
/// </typeparam>
|
||||||
internal abstract partial class SyntaxVisitor<TResult>
|
internal abstract partial class SyntaxVisitor<TResult>
|
||||||
{
|
{
|
||||||
public virtual TResult Visit(SyntaxNode node)
|
public virtual TResult Visit(SyntaxNode node)
|
||||||
|
|
@ -31,6 +39,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a <see cref="SyntaxNode"/> visitor that visits only the single SyntaxNode
|
||||||
|
/// passed into its Visit method.
|
||||||
|
/// </summary>
|
||||||
internal abstract partial class SyntaxVisitor
|
internal abstract partial class SyntaxVisitor
|
||||||
{
|
{
|
||||||
public virtual void Visit(SyntaxNode node)
|
public virtual void Visit(SyntaxNode node)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a <see cref="SyntaxVisitor"/> that descends an entire <see cref="SyntaxNode"/> graph
|
||||||
|
/// visiting each SyntaxNode and its child SyntaxNodes and <see cref="SyntaxToken"/>s in depth-first order.
|
||||||
|
/// </summary>
|
||||||
|
internal abstract class SyntaxWalker : SyntaxVisitor
|
||||||
|
{
|
||||||
|
public override void Visit(SyntaxNode node)
|
||||||
|
{
|
||||||
|
node?.Accept(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void DefaultVisit(SyntaxNode node)
|
||||||
|
{
|
||||||
|
var children = node.ChildNodes();
|
||||||
|
for (var i = 0; i < children.Count; i++)
|
||||||
|
{
|
||||||
|
var child = children[i];
|
||||||
|
Visit(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitToken(SyntaxToken token)
|
||||||
|
{
|
||||||
|
VisitLeadingTrivia(token);
|
||||||
|
VisitTrailingTrivia(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void VisitLeadingTrivia(SyntaxToken token)
|
||||||
|
{
|
||||||
|
if (token.HasLeadingTrivia)
|
||||||
|
{
|
||||||
|
foreach (var trivia in token.GetLeadingTrivia())
|
||||||
|
{
|
||||||
|
VisitTrivia(trivia);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void VisitTrailingTrivia(SyntaxToken token)
|
||||||
|
{
|
||||||
|
if (token.HasTrailingTrivia)
|
||||||
|
{
|
||||||
|
foreach (var trivia in token.GetTrailingTrivia())
|
||||||
|
{
|
||||||
|
VisitTrivia(trivia);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue