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 Microsoft.AspNetCore.Razor.Language.Extensions;
|
||||
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||
using Microsoft.AspNetCore.Razor.Language.Syntax;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language
|
||||
{
|
||||
|
|
@ -23,37 +24,42 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
{
|
||||
throw new ArgumentNullException(nameof(syntaxTree));
|
||||
}
|
||||
|
||||
var sectionVerifier = new NestedSectionVerifier();
|
||||
sectionVerifier.Verify(syntaxTree);
|
||||
|
||||
return syntaxTree;
|
||||
|
||||
var sectionVerifier = new NestedSectionVerifier(syntaxTree);
|
||||
return sectionVerifier.Verify();
|
||||
}
|
||||
|
||||
private class NestedSectionVerifier : ParserVisitor
|
||||
private class NestedSectionVerifier : SyntaxRewriter
|
||||
{
|
||||
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)
|
||||
{
|
||||
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 error = RazorDiagnosticFactory.CreateParsing_SectionsCannotBeNested(new SourceSpan(directiveStart, errorLength));
|
||||
chunkGenerator.Diagnostics.Add(error);
|
||||
node = node.AppendDiagnostic(error);
|
||||
}
|
||||
|
||||
_nestedLevel++;
|
||||
|
||||
VisitDefault(block);
|
||||
|
||||
var result = base.VisitRazorDirective(node);
|
||||
_nestedLevel--;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,15 +1,17 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||
using Microsoft.AspNetCore.Razor.Language.Syntax;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language
|
||||
{
|
||||
internal class DefaultRazorSyntaxTree : RazorSyntaxTree
|
||||
{
|
||||
public DefaultRazorSyntaxTree(
|
||||
Block root,
|
||||
SyntaxNode root,
|
||||
RazorSourceDocument source,
|
||||
IReadOnlyList<RazorDiagnostic> diagnostics,
|
||||
RazorParserOptions options)
|
||||
|
|
@ -24,7 +26,7 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
|
||||
public override RazorParserOptions Options { get; }
|
||||
|
||||
internal override Block Root { get; }
|
||||
internal override SyntaxNode Root { get; }
|
||||
|
||||
public override RazorSourceDocument Source { get; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||
using Microsoft.AspNetCore.Razor.Language.Syntax;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language
|
||||
{
|
||||
|
|
@ -39,11 +40,11 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
for (var i = 0; i < imports.Count; 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;
|
||||
descriptors = visitor.Matches.ToArray();
|
||||
|
|
@ -57,21 +58,9 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
return;
|
||||
}
|
||||
|
||||
var errorSink = new ErrorSink();
|
||||
var rewriter = new TagHelperParseTreeRewriter(tagHelperPrefix, descriptors, syntaxTree.Options.FeatureFlags);
|
||||
|
||||
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);
|
||||
var rewrittenSyntaxTree = TagHelperParseTreeRewriter.Rewrite(syntaxTree, tagHelperPrefix, descriptors);
|
||||
|
||||
codeDocument.SetSyntaxTree(rewrittenSyntaxTree);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private static int GetErrorLength(string directiveText)
|
||||
{
|
||||
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
|
||||
internal class DirectiveVisitor : SyntaxRewriter
|
||||
{
|
||||
private IReadOnlyList<TagHelperDescriptor> _tagHelpers;
|
||||
|
||||
|
|
@ -128,62 +99,80 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
|
||||
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
|
||||
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))
|
||||
if (!(child is CSharpStatementLiteralSyntax literal))
|
||||
{
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
return base.VisitRazorDirective(node);
|
||||
}
|
||||
|
||||
private bool AssemblyContainsTagHelpers(string assemblyName, IReadOnlyList<TagHelperDescriptor> tagHelpers)
|
||||
|
|
|
|||
|
|
@ -4,17 +4,17 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||
using Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax;
|
||||
using Microsoft.AspNetCore.Razor.Language.Syntax;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
|
@ -31,7 +31,6 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
}
|
||||
|
||||
return PartialParseResultInternal.Rejected;
|
||||
|
||||
}
|
||||
|
||||
private static bool ContainsWhitespace(string content)
|
||||
|
|
|
|||
|
|
@ -22,14 +22,11 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
throw new ArgumentNullException(nameof(syntaxTree));
|
||||
}
|
||||
|
||||
var conditionalAttributeCollapser = new ConditionalAttributeCollapser();
|
||||
var rewritten = conditionalAttributeCollapser.Rewrite(syntaxTree.Root);
|
||||
|
||||
var whitespaceRewriter = new WhiteSpaceRewriter();
|
||||
rewritten = whitespaceRewriter.Rewrite(rewritten);
|
||||
var whitespaceRewriter = new WhitespaceRewriter();
|
||||
var rewritten = whitespaceRewriter.Visit(syntaxTree.Root);
|
||||
|
||||
var rewrittenSyntaxTree = RazorSyntaxTree.Create(rewritten, syntaxTree.Source, syntaxTree.Diagnostics, syntaxTree.Options);
|
||||
return rewrittenSyntaxTree;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,23 +14,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
|
||||
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()
|
||||
{
|
||||
return "Import:" + Namespace + ";";
|
||||
|
|
|
|||
|
|
@ -35,11 +35,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
|
||||
public List<RazorDiagnostic> Diagnostics { get; }
|
||||
|
||||
public override void Accept(ParserVisitor visitor, Span span)
|
||||
{
|
||||
visitor.VisitAddTagHelperSpan(this, span);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax;
|
||||
using Microsoft.AspNetCore.Razor.Language.Syntax;
|
||||
using Microsoft.Extensions.Internal;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||
|
|
@ -12,18 +12,18 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
{
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
public AutoCompleteEditHandler(Func<string, IEnumerable<SyntaxToken>> tokenizer, bool autoCompleteAtEndOfSpan)
|
||||
public AutoCompleteEditHandler(Func<string, IEnumerable<Syntax.InternalSyntax.SyntaxToken>> tokenizer, bool autoCompleteAtEndOfSpan)
|
||||
: this(tokenizer)
|
||||
{
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
|
@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
|
||||
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)) &&
|
||||
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()
|
||||
{
|
||||
return SyntaxFactory.Token(SyntaxKind.Unknown, string.Empty);
|
||||
return SyntaxFactory.Token(SyntaxKind.Marker, string.Empty);
|
||||
}
|
||||
|
||||
public override SyntaxKind GetKnownTokenType(KnownTokenType type)
|
||||
|
|
@ -127,7 +127,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
return SyntaxKind.Keyword;
|
||||
case KnownTokenType.NewLine:
|
||||
return SyntaxKind.NewLine;
|
||||
case KnownTokenType.WhiteSpace:
|
||||
case KnownTokenType.Whitespace:
|
||||
return SyntaxKind.Whitespace;
|
||||
case KnownTokenType.Transition:
|
||||
return SyntaxKind.Transition;
|
||||
|
|
@ -138,7 +138,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
case KnownTokenType.CommentBody:
|
||||
return SyntaxKind.RazorCommentLiteral;
|
||||
default:
|
||||
return SyntaxKind.Unknown;
|
||||
return SyntaxKind.Marker;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -164,7 +164,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
return SyntaxKind.LessThan;
|
||||
default:
|
||||
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 SyntaxKind.Unknown;
|
||||
return SyntaxKind.Marker;
|
||||
}
|
||||
|
||||
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.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax;
|
||||
using Microsoft.AspNetCore.Razor.Language.Syntax;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||
{
|
||||
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))
|
||||
{
|
||||
|
|
@ -35,7 +35,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
}
|
||||
|
||||
// Internal for testing
|
||||
internal static bool IsAcceptableReplacement(Span target, SourceChange change)
|
||||
internal static bool IsAcceptableReplacement(SyntaxNode target, SourceChange change)
|
||||
{
|
||||
if (!change.IsReplace)
|
||||
{
|
||||
|
|
@ -56,7 +56,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
}
|
||||
|
||||
// Internal for testing
|
||||
internal static bool IsAcceptableDeletion(Span target, SourceChange change)
|
||||
internal static bool IsAcceptableDeletion(SyntaxNode target, SourceChange change)
|
||||
{
|
||||
if (!change.IsDelete)
|
||||
{
|
||||
|
|
@ -72,11 +72,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 override void Accept(ParserVisitor visitor, Span span)
|
||||
{
|
||||
visitor.VisitDirectiveToken(this, span);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
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.
|
||||
// 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
|
||||
{
|
||||
internal class EditResult
|
||||
{
|
||||
public EditResult(PartialParseResultInternal result, SpanBuilder editedSpan)
|
||||
public EditResult(PartialParseResultInternal result, SyntaxNode editedNode)
|
||||
{
|
||||
Result = result;
|
||||
EditedSpan = editedSpan;
|
||||
EditedNode = editedNode;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
internal class ExpressionChunkGenerator : ISpanChunkGenerator, IParentChunkGenerator
|
||||
internal class ExpressionChunkGenerator : ISpanChunkGenerator
|
||||
{
|
||||
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()
|
||||
{
|
||||
return "Expr";
|
||||
|
|
|
|||
|
|
@ -86,13 +86,13 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
return SyntaxKind.OpenAngle;
|
||||
default:
|
||||
Debug.Fail("FlipBracket must be called with a bracket character");
|
||||
return SyntaxKind.Unknown;
|
||||
return SyntaxKind.Marker;
|
||||
}
|
||||
}
|
||||
|
||||
public override SyntaxToken CreateMarkerToken()
|
||||
{
|
||||
return SyntaxFactory.Token(SyntaxKind.Unknown, string.Empty);
|
||||
return SyntaxFactory.Token(SyntaxKind.Marker, string.Empty);
|
||||
}
|
||||
|
||||
public override SyntaxKind GetKnownTokenType(KnownTokenType type)
|
||||
|
|
@ -113,10 +113,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
return SyntaxKind.NewLine;
|
||||
case KnownTokenType.Transition:
|
||||
return SyntaxKind.Transition;
|
||||
case KnownTokenType.WhiteSpace:
|
||||
case KnownTokenType.Whitespace:
|
||||
return SyntaxKind.Whitespace;
|
||||
default:
|
||||
return SyntaxKind.Unknown;
|
||||
return SyntaxKind.Marker;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -227,7 +227,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
return EndToken(SyntaxKind.DoubleHyphen);
|
||||
default:
|
||||
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
|
||||
{
|
||||
void GenerateChunk(Span target, ChunkGeneratorContext context);
|
||||
|
||||
void Accept(ParserVisitor visitor, Span span);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
return hashCodeCombiner;
|
||||
}
|
||||
|
||||
protected override PartialParseResultInternal CanAcceptChange(Span target, SourceChange change)
|
||||
protected override PartialParseResultInternal CanAcceptChange(SyntaxNode target, SourceChange change)
|
||||
{
|
||||
if (AcceptedCharacters == AcceptedCharactersInternal.Any)
|
||||
{
|
||||
|
|
@ -88,13 +88,13 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
{
|
||||
return HandleReplacement(target, change);
|
||||
}
|
||||
var changeRelativePosition = change.Span.AbsoluteIndex - target.Start.AbsoluteIndex;
|
||||
var changeRelativePosition = change.Span.AbsoluteIndex - target.Position;
|
||||
|
||||
// Get the edit context
|
||||
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
|
||||
|
|
@ -129,18 +129,18 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// 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) &&
|
||||
change.Span.AbsoluteIndex > 0 &&
|
||||
change.NewText.Length > 0 &&
|
||||
target.Content.Last() == '.' &&
|
||||
target.GetContent().Last() == '.' &&
|
||||
ParserHelpers.IsIdentifier(change.NewText, 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
|
||||
// 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.
|
||||
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)
|
||||
return change.NewText.Length == 1 &&
|
||||
change.NewText == "." &&
|
||||
!string.IsNullOrEmpty(target.Content) &&
|
||||
target.Content.Last() == '.' &&
|
||||
!string.IsNullOrEmpty(target.GetContent()) &&
|
||||
target.GetContent().Last() == '.' &&
|
||||
change.Span.Length == 0;
|
||||
}
|
||||
|
||||
private static bool IsAcceptableReplace(Span target, SourceChange change)
|
||||
private static bool IsAcceptableReplace(SyntaxNode target, SourceChange change)
|
||||
{
|
||||
return IsEndReplace(target, change) ||
|
||||
(change.IsReplace && RemainingIsWhitespace(target, change));
|
||||
}
|
||||
|
||||
private bool IsAcceptableIdentifierReplacement(Span target, SourceChange change)
|
||||
private bool IsAcceptableIdentifierReplacement(SyntaxNode target, SourceChange change)
|
||||
{
|
||||
if (!change.IsReplace)
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
|
@ -217,14 +218,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
return false;
|
||||
}
|
||||
|
||||
private static bool IsAcceptableDeletion(Span target, SourceChange change)
|
||||
private static bool IsAcceptableDeletion(SyntaxNode target, SourceChange change)
|
||||
{
|
||||
return IsEndDeletion(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.
|
||||
private static bool IsAcceptableInsertion(Span target, SourceChange change)
|
||||
private static bool IsAcceptableInsertion(SyntaxNode target, SourceChange change)
|
||||
{
|
||||
return change.IsInsert &&
|
||||
(IsAcceptableEndInsertion(target, change) ||
|
||||
|
|
@ -232,7 +233,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
}
|
||||
|
||||
// Internal for testing
|
||||
internal static bool IsAcceptableDeletionInBalancedParenthesis(Span target, SourceChange change)
|
||||
internal static bool IsAcceptableDeletionInBalancedParenthesis(SyntaxNode target, SourceChange change)
|
||||
{
|
||||
if (!change.IsDelete)
|
||||
{
|
||||
|
|
@ -242,14 +243,15 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
var changeStart = change.Span.AbsoluteIndex;
|
||||
var changeLength = change.Span.Length;
|
||||
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.
|
||||
return false;
|
||||
}
|
||||
|
||||
var relativePosition = changeStart - target.Start.AbsoluteIndex;
|
||||
var deletionContent = target.Content.Substring(relativePosition, changeLength);
|
||||
var relativePosition = changeStart - target.Position;
|
||||
var deletionContent = target.GetContent().Substring(relativePosition, changeLength);
|
||||
|
||||
if (deletionContent.IndexOfAny(new[] { '(', ')' }) >= 0)
|
||||
{
|
||||
|
|
@ -261,7 +263,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
}
|
||||
|
||||
// Internal for testing
|
||||
internal static bool IsAcceptableInsertionInBalancedParenthesis(Span target, SourceChange change)
|
||||
internal static bool IsAcceptableInsertionInBalancedParenthesis(SyntaxNode target, SourceChange change)
|
||||
{
|
||||
if (!change.IsInsert)
|
||||
{
|
||||
|
|
@ -274,7 +276,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
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;
|
||||
}
|
||||
|
|
@ -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.
|
||||
private static bool IsAcceptableEndInsertion(Span target, SourceChange change)
|
||||
private static bool IsAcceptableEndInsertion(SyntaxNode target, SourceChange change)
|
||||
{
|
||||
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'
|
||||
// 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);
|
||||
|
||||
|
|
@ -440,23 +443,23 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
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;
|
||||
return string.IsNullOrWhiteSpace(target.Content.Substring(offset));
|
||||
var offset = (change.Span.AbsoluteIndex - target.Position) + change.Span.Length;
|
||||
return string.IsNullOrWhiteSpace(target.GetContent().Substring(offset));
|
||||
}
|
||||
|
||||
private PartialParseResultInternal HandleDotlessCommitInsertion(Span target)
|
||||
private PartialParseResultInternal HandleDotlessCommitInsertion(SyntaxNode target)
|
||||
{
|
||||
var result = PartialParseResultInternal.Accepted;
|
||||
if (!AcceptTrailingDot && target.Content.LastOrDefault() == '.')
|
||||
if (!AcceptTrailingDot && target.GetContent().LastOrDefault() == '.')
|
||||
{
|
||||
result |= PartialParseResultInternal.Provisional;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private PartialParseResultInternal HandleReplacement(Span target, SourceChange change)
|
||||
private PartialParseResultInternal HandleReplacement(SyntaxNode target, SourceChange change)
|
||||
{
|
||||
// Special Case for IntelliSense commits.
|
||||
// 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;
|
||||
}
|
||||
|
||||
private PartialParseResultInternal HandleDeletion(Span target, char previousChar, SourceChange change)
|
||||
private PartialParseResultInternal HandleDeletion(SyntaxNode target, char previousChar, SourceChange change)
|
||||
{
|
||||
// What's left after deleting?
|
||||
if (previousChar == '.')
|
||||
|
|
@ -490,8 +493,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
}
|
||||
else if (previousChar == '(')
|
||||
{
|
||||
var changeRelativePosition = change.Span.AbsoluteIndex - target.Start.AbsoluteIndex;
|
||||
if (target.Content[changeRelativePosition] == ')')
|
||||
var changeRelativePosition = change.Span.AbsoluteIndex - target.Position;
|
||||
if (target.GetContent()[changeRelativePosition] == ')')
|
||||
{
|
||||
return PartialParseResultInternal.Accepted | PartialParseResultInternal.Provisional;
|
||||
}
|
||||
|
|
@ -500,7 +503,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
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?
|
||||
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 (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))
|
||||
{
|
||||
|
|
@ -560,6 +563,27 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
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)
|
||||
{
|
||||
return
|
||||
|
|
@ -591,27 +615,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
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)
|
||||
{
|
||||
using (var reader = new StringReader(newContent))
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
{
|
||||
internal enum KnownTokenType
|
||||
{
|
||||
WhiteSpace,
|
||||
Whitespace,
|
||||
NewLine,
|
||||
Identifier,
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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 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()
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, "LitAttr:{0:F}", Prefix);
|
||||
|
|
|
|||
|
|
@ -5,16 +5,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
{
|
||||
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()
|
||||
{
|
||||
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 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;
|
||||
FeatureFlags = options.FeatureFlags;
|
||||
ParseLeadingDirectives = options.ParseLeadingDirectives;
|
||||
Builder = new SyntaxTreeBuilder();
|
||||
ErrorSink = new ErrorSink();
|
||||
SeenDirectives = new HashSet<string>(StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
public SyntaxTreeBuilder Builder { get; }
|
||||
|
||||
public ErrorSink ErrorSink { get; set; }
|
||||
|
||||
public RazorParserFeatureFlags FeatureFlags { get; }
|
||||
|
|
@ -50,6 +47,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
|
||||
public bool NullGenerateWhitespaceAndNewLine { get; set; }
|
||||
|
||||
public bool InTemplateContext { get; set; }
|
||||
|
||||
public AcceptedCharactersInternal LastAcceptedCharacters { get; set; } = AcceptedCharactersInternal.None;
|
||||
|
||||
public bool EndOfFile
|
||||
{
|
||||
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.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||
{
|
||||
|
|
@ -39,12 +38,9 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
codeParser.HtmlParser = markupParser;
|
||||
markupParser.CodeParser = codeParser;
|
||||
|
||||
markupParser.ParseDocument();
|
||||
|
||||
var root = context.Builder.Build();
|
||||
|
||||
var diagnostics = context.ErrorSink.Errors;
|
||||
|
||||
var root = markupParser.ParseDocument().CreateRed();
|
||||
return RazorSyntaxTree.Create(root, source, diagnostics, Options);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,31 +16,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
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];
|
||||
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;
|
||||
return visitor.ClassifiedSpans;
|
||||
}
|
||||
|
||||
public static IReadOnlyList<TagHelperSpanInternal> GetTagHelperSpans(this RazorSyntaxTree syntaxTree)
|
||||
|
|
@ -50,81 +29,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
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>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return visitor.TagHelperSpans;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,11 +35,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
|
||||
public List<RazorDiagnostic> Diagnostics { get; }
|
||||
|
||||
public override void Accept(ParserVisitor visitor, Span span)
|
||||
{
|
||||
visitor.VisitRemoveTagHelperSpan(this, span);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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 abstract void Accept(ParserVisitor visitor, Span span);
|
||||
|
||||
public virtual void GenerateChunk(Span target, ChunkGeneratorContext context)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj != null &&
|
||||
|
|
@ -30,15 +24,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
|
||||
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()
|
||||
{
|
||||
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.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
|
||||
{
|
||||
|
|
@ -11,12 +13,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
{
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
public SpanEditHandler(Func<string, IEnumerable<SyntaxToken>> tokenizer, AcceptedCharactersInternal accepted)
|
||||
public SpanEditHandler(Func<string, IEnumerable<Syntax.InternalSyntax.SyntaxToken>> tokenizer, AcceptedCharactersInternal accepted)
|
||||
{
|
||||
AcceptedCharacters = accepted;
|
||||
Tokenizer = tokenizer;
|
||||
|
|
@ -24,19 +26,24 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
|
||||
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);
|
||||
}
|
||||
|
||||
public virtual EditResult ApplyChange(Span target, SourceChange change)
|
||||
public virtual EditResult ApplyChange(SyntaxNode target, SourceChange change)
|
||||
{
|
||||
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;
|
||||
if (!force)
|
||||
|
|
@ -49,49 +56,81 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
{
|
||||
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;
|
||||
return change.Span.AbsoluteIndex >= target.Start.AbsoluteIndex &&
|
||||
return change.Span.AbsoluteIndex >= target.Position &&
|
||||
(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;
|
||||
}
|
||||
|
||||
protected virtual SpanBuilder UpdateSpan(Span target, SourceChange change)
|
||||
protected virtual SyntaxNode UpdateSpan(SyntaxNode target, SourceChange change)
|
||||
{
|
||||
var newContent = change.GetEditedContent(target);
|
||||
var newSpan = new SpanBuilder(target);
|
||||
newSpan.ClearTokens();
|
||||
var builder = Syntax.InternalSyntax.SyntaxListBuilder<Syntax.InternalSyntax.SyntaxToken>.Create();
|
||||
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);
|
||||
target.Next.ChangeStart(newEnd);
|
||||
newTarget = Syntax.InternalSyntax.SyntaxFactory.RazorMetaCode(builder.ToList()).CreateRed(target.Parent, target.Position);
|
||||
}
|
||||
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 });
|
||||
return (endOfFirstLine == -1 || (change.Span.AbsoluteIndex - target.Start.AbsoluteIndex) <= endOfFirstLine);
|
||||
var endOfFirstLine = target.GetContent().IndexOfAny(new char[] { (char)0x000d, (char)0x000a, (char)0x2028, (char)0x2029 });
|
||||
return (endOfFirstLine == -1 || (change.Span.AbsoluteIndex - target.Position) <= endOfFirstLine);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the specified change is an insertion of text at the end of this span.
|
||||
/// </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);
|
||||
}
|
||||
|
|
@ -99,14 +138,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
/// <summary>
|
||||
/// Returns true if the specified change is a replacement of text at the end of this span.
|
||||
/// </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);
|
||||
}
|
||||
|
||||
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()
|
||||
|
|
|
|||
|
|
@ -5,16 +5,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
{
|
||||
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()
|
||||
{
|
||||
return "Stmt";
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
public static readonly string TransitionString = "@";
|
||||
public static readonly string StartCommentSequence = "@*";
|
||||
public static readonly string EndCommentSequence = "*@";
|
||||
public static readonly string SpanContextKind = "SpanData";
|
||||
|
||||
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.
|
||||
// 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
|
||||
{
|
||||
internal class TagHelperAttributeNode
|
||||
{
|
||||
public TagHelperAttributeNode(string name, SyntaxTreeNode value, AttributeStructure attributeStructure)
|
||||
public TagHelperAttributeNode(string name, SyntaxNode value, AttributeStructure attributeStructure)
|
||||
{
|
||||
Name = name;
|
||||
Value = value;
|
||||
|
|
@ -13,14 +15,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
}
|
||||
|
||||
// Internal for testing
|
||||
internal TagHelperAttributeNode(string name, SyntaxTreeNode value)
|
||||
internal TagHelperAttributeNode(string name, SyntaxNode value)
|
||||
: this(name, value, AttributeStructure.DoubleQuotes)
|
||||
{
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public SyntaxTreeNode Value { get; }
|
||||
public SyntaxNode Value { 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 override void Accept(ParserVisitor visitor, Span span)
|
||||
{
|
||||
visitor.VisitTagHelperPrefixDirectiveSpan(this, span);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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
|
||||
where TTokenizer : Tokenizer
|
||||
{
|
||||
private readonly SyntaxListPool _pool = new SyntaxListPool();
|
||||
private readonly TokenizerView<TTokenizer> _tokenizer;
|
||||
private SyntaxListBuilder<SyntaxToken>? _tokenBuilder;
|
||||
|
||||
protected TokenizerBackedParser(LanguageCharacteristics<TTokenizer> language, ParserContext context)
|
||||
: base(context)
|
||||
|
|
@ -21,14 +23,28 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
|
||||
var languageTokenizer = Language.CreateTokenizer(Context.Source);
|
||||
_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
|
||||
{
|
||||
|
|
@ -37,8 +53,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
|
||||
protected SyntaxToken PreviousToken { get; private set; }
|
||||
|
||||
protected SourceLocation CurrentLocation => _tokenizer.Tokenizer.CurrentLocation;
|
||||
|
||||
protected SourceLocation CurrentStart => _tokenizer.Tokenizer.CurrentStart;
|
||||
|
||||
protected bool EndOfFile
|
||||
|
|
@ -48,28 +62,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
|
||||
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)
|
||||
{
|
||||
if (count < 0)
|
||||
|
|
@ -163,11 +155,9 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
[Conditional("DEBUG")]
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
return NextIs(token => token != null && TokenKindEquals(type, token.Kind));
|
||||
return NextIs(token => token != null && type == token.Kind);
|
||||
}
|
||||
|
||||
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)
|
||||
|
|
@ -317,173 +225,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
|
||||
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)
|
||||
{
|
||||
return !EndOfFile && CurrentToken != null && TokenKindEquals(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;
|
||||
return !EndOfFile && CurrentToken != null && CurrentToken.Kind == type;
|
||||
}
|
||||
|
||||
protected bool EnsureCurrent()
|
||||
|
|
@ -496,85 +243,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
return CurrentToken != null &&
|
||||
|
|
@ -593,30 +266,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
}
|
||||
}
|
||||
|
||||
private void Configure(SpanKindInternal? kind, AcceptedCharactersInternal? accepts)
|
||||
{
|
||||
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()
|
||||
protected RazorCommentBlockSyntax ParseRazorComment()
|
||||
{
|
||||
if (!Language.KnowsTokenType(KnownTokenType.CommentStart) ||
|
||||
!Language.KnowsTokenType(KnownTokenType.CommentStar) ||
|
||||
|
|
@ -624,54 +274,321 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
{
|
||||
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();
|
||||
var start = CurrentStart;
|
||||
|
||||
Expected(KnownTokenType.CommentStart);
|
||||
Output(SpanKindInternal.Transition, AcceptedCharactersInternal.None);
|
||||
|
||||
Expected(KnownTokenType.CommentStar);
|
||||
Output(SpanKindInternal.MetaCode, AcceptedCharactersInternal.None);
|
||||
|
||||
Optional(KnownTokenType.CommentBody);
|
||||
AddMarkerTokenIfNecessary();
|
||||
Output(SpanKindInternal.Comment);
|
||||
|
||||
var errorReported = false;
|
||||
if (!Optional(KnownTokenType.CommentStar))
|
||||
comment = SyntaxFactory.MissingToken(SyntaxKind.RazorCommentLiteral);
|
||||
}
|
||||
var endStar = GetOptionalToken(SyntaxKind.RazorCommentStar);
|
||||
if (endStar == null)
|
||||
{
|
||||
var diagnostic = RazorDiagnosticFactory.CreateParsing_RazorCommentNotTerminated(
|
||||
new SourceSpan(start, contentLength: 2 /* @* */));
|
||||
endStar = SyntaxFactory.MissingToken(SyntaxKind.RazorCommentStar, diagnostic);
|
||||
Context.ErrorSink.OnError(diagnostic);
|
||||
}
|
||||
var endTransition = GetOptionalToken(SyntaxKind.RazorCommentTransition);
|
||||
if (endTransition == null)
|
||||
{
|
||||
if (!endStar.IsMissing)
|
||||
{
|
||||
errorReported = true;
|
||||
Context.ErrorSink.OnError(
|
||||
RazorDiagnosticFactory.CreateParsing_RazorCommentNotTerminated(
|
||||
new SourceSpan(start, contentLength: 2 /* @* */)));
|
||||
}
|
||||
else
|
||||
{
|
||||
Output(SpanKindInternal.MetaCode, AcceptedCharactersInternal.None);
|
||||
var diagnostic = RazorDiagnosticFactory.CreateParsing_RazorCommentNotTerminated(
|
||||
new SourceSpan(start, contentLength: 2 /* @* */));
|
||||
Context.ErrorSink.OnError(diagnostic);
|
||||
endTransition = SyntaxFactory.MissingToken(SyntaxKind.RazorCommentTransition, diagnostic);
|
||||
}
|
||||
|
||||
if (!Optional(KnownTokenType.CommentStart))
|
||||
{
|
||||
if (!errorReported)
|
||||
{
|
||||
errorReported = true;
|
||||
Context.ErrorSink.OnError(
|
||||
RazorDiagnosticFactory.CreateParsing_RazorCommentNotTerminated(
|
||||
new SourceSpan(start, contentLength: 2 /* @* */)));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Output(SpanKindInternal.Transition, AcceptedCharactersInternal.None);
|
||||
}
|
||||
endTransition = SyntaxFactory.MissingToken(SyntaxKind.RazorCommentTransition);
|
||||
}
|
||||
|
||||
commentBlock = SyntaxFactory.RazorCommentBlock(startTransition, startStar, comment, endStar, endTransition);
|
||||
|
||||
// Make sure we generate a marker symbol after a comment if necessary.
|
||||
if (!comment.IsMissing || !endStar.IsMissing || !endTransition.IsMissing)
|
||||
{
|
||||
Context.LastAcceptedCharacters = 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.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Razor.Language.Syntax;
|
||||
|
||||
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);
|
||||
newBlock.Children.Clear();
|
||||
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());
|
||||
// Rewrite any whitespace represented as code at the start of a line preceding an expression block.
|
||||
// We want it to be rendered as Markup.
|
||||
|
||||
// Remove the old whitespace node
|
||||
newNodes = block.Children.Skip(1);
|
||||
rewritten = null;
|
||||
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.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||
using Microsoft.AspNetCore.Razor.Language.Syntax;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language
|
||||
{
|
||||
public abstract class RazorSyntaxTree
|
||||
{
|
||||
internal static RazorSyntaxTree Create(
|
||||
Block root,
|
||||
SyntaxNode root,
|
||||
RazorSourceDocument source,
|
||||
IEnumerable<RazorDiagnostic> diagnostics,
|
||||
RazorParserOptions options)
|
||||
|
|
@ -63,7 +64,7 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
|
||||
public abstract RazorParserOptions Options { get; }
|
||||
|
||||
internal abstract Block Root { get; }
|
||||
internal abstract SyntaxNode Root { get; }
|
||||
|
||||
public abstract RazorSourceDocument Source { get; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||
using Microsoft.AspNetCore.Razor.Language.Syntax;
|
||||
using Microsoft.Extensions.Internal;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language
|
||||
|
|
@ -51,15 +52,15 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
|
||||
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);
|
||||
return GetEditedContent(span.Content, offset);
|
||||
var offset = GetOffset(node);
|
||||
return GetEditedContent(node.GetContent(), 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);
|
||||
}
|
||||
|
||||
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 end = Span.AbsoluteIndex + Span.Length;
|
||||
|
||||
if (start < span.Start.AbsoluteIndex ||
|
||||
start > span.Start.AbsoluteIndex + span.Length ||
|
||||
end < span.Start.AbsoluteIndex ||
|
||||
end > span.Start.AbsoluteIndex + span.Length)
|
||||
if (start < node.Position ||
|
||||
start > node.EndPosition ||
|
||||
end < node.Position ||
|
||||
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;
|
||||
}
|
||||
|
||||
var offset = GetOffset(span);
|
||||
return span.Content.Substring(offset, Span.Length);
|
||||
var offset = GetOffset(node);
|
||||
return node.GetContent().Substring(offset, Span.Length);
|
||||
}
|
||||
|
||||
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[]>();
|
||||
private static readonly ConditionalWeakTable<GreenNode, SyntaxAnnotation[]> AnnotationsTable =
|
||||
new ConditionalWeakTable<GreenNode, SyntaxAnnotation[]>();
|
||||
|
||||
private NodeFlags _flags;
|
||||
private byte _slotCount;
|
||||
|
||||
protected GreenNode(SyntaxKind kind)
|
||||
|
|
@ -44,7 +42,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
|||
{
|
||||
if (diagnostics?.Length > 0)
|
||||
{
|
||||
_flags |= NodeFlags.ContainsDiagnostics;
|
||||
Flags |= NodeFlags.ContainsDiagnostics;
|
||||
DiagnosticsTable.Add(this, diagnostics);
|
||||
}
|
||||
|
||||
|
|
@ -58,7 +56,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
|||
}
|
||||
}
|
||||
|
||||
_flags |= NodeFlags.ContainsAnnotations;
|
||||
Flags |= NodeFlags.ContainsAnnotations;
|
||||
AnnotationsTable.Add(this, annotations);
|
||||
}
|
||||
}
|
||||
|
|
@ -70,7 +68,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
|||
return;
|
||||
}
|
||||
|
||||
_flags |= (node.Flags & NodeFlags.InheritMask);
|
||||
Flags |= (node.Flags & NodeFlags.InheritMask);
|
||||
FullWidth += node.FullWidth;
|
||||
}
|
||||
|
||||
|
|
@ -150,25 +148,25 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
|||
#endregion
|
||||
|
||||
#region Flags
|
||||
internal NodeFlags Flags => _flags;
|
||||
public NodeFlags Flags { get; protected set; }
|
||||
|
||||
internal void SetFlags(NodeFlags flags)
|
||||
{
|
||||
_flags |= flags;
|
||||
Flags |= 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
|
||||
{
|
||||
get
|
||||
{
|
||||
return (_flags & NodeFlags.ContainsDiagnostics) != 0;
|
||||
return (Flags & NodeFlags.ContainsDiagnostics) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -176,7 +174,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
|||
{
|
||||
get
|
||||
{
|
||||
return (_flags & NodeFlags.ContainsAnnotations) != 0;
|
||||
return (Flags & NodeFlags.ContainsAnnotations) != 0;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
|
@ -255,6 +253,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
|||
#endregion
|
||||
|
||||
#region Text
|
||||
public override string ToString()
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.AppendFormat("{0}<{1}>", GetType().Name, Kind);
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
public virtual string ToFullString()
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
|
|
@ -342,7 +348,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
|||
for (int i = 0, n = node.SlotCount; i < n; i++)
|
||||
{
|
||||
var child = node.GetSlot(i);
|
||||
if (child != null)
|
||||
if (child != null && child.FullWidth > 0)
|
||||
{
|
||||
firstChild = child;
|
||||
break;
|
||||
|
|
@ -364,7 +370,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
|||
for (var i = node.SlotCount - 1; i >= 0; i--)
|
||||
{
|
||||
var child = node.GetSlot(i);
|
||||
if (child != null)
|
||||
if (child != null && child.FullWidth > 0)
|
||||
{
|
||||
lastChild = child;
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
// 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
|
||||
{
|
||||
|
|
@ -19,7 +21,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
|||
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>();
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
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.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax
|
||||
{
|
||||
internal readonly struct SyntaxList<TNode>
|
||||
internal struct SyntaxList<TNode> : IEquatable<SyntaxList<TNode>>
|
||||
where TNode : GreenNode
|
||||
{
|
||||
private readonly GreenNode _node;
|
||||
|
|
@ -15,13 +16,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax
|
|||
_node = node;
|
||||
}
|
||||
|
||||
public GreenNode Node
|
||||
{
|
||||
get
|
||||
{
|
||||
return ((GreenNode)_node);
|
||||
}
|
||||
}
|
||||
internal GreenNode Node => _node;
|
||||
|
||||
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]
|
||||
{
|
||||
get
|
||||
{
|
||||
var node = _node;
|
||||
if (node.IsList)
|
||||
if (_node == null)
|
||||
{
|
||||
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);
|
||||
return ((TNode)_node.GetSlot(index));
|
||||
}
|
||||
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)
|
||||
{
|
||||
return (left._node == right._node);
|
||||
|
|
@ -116,6 +127,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax
|
|||
return !(left._node == right._node);
|
||||
}
|
||||
|
||||
public bool Equals(SyntaxList<TNode> other)
|
||||
{
|
||||
return _node == other._node;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||
|
||||
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)
|
||||
: base(kind, content.Length, diagnostics, annotations: null)
|
||||
{
|
||||
|
|
@ -20,9 +21,9 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax
|
|||
: base(kind, content.Length)
|
||||
{
|
||||
Content = content;
|
||||
LeadingTrivia = leadingTrivia;
|
||||
_leadingTrivia = leadingTrivia;
|
||||
AdjustFlagsAndWidth(leadingTrivia);
|
||||
TrailingTrivia = trailingTrivia;
|
||||
_trailingTrivia = trailingTrivia;
|
||||
AdjustFlagsAndWidth(trailingTrivia);
|
||||
}
|
||||
|
||||
|
|
@ -30,17 +31,23 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax
|
|||
: base(kind, content.Length, diagnostics, annotations)
|
||||
{
|
||||
Content = content;
|
||||
LeadingTrivia = leadingTrivia;
|
||||
_leadingTrivia = leadingTrivia;
|
||||
AdjustFlagsAndWidth(leadingTrivia);
|
||||
TrailingTrivia = trailingTrivia;
|
||||
_trailingTrivia = trailingTrivia;
|
||||
AdjustFlagsAndWidth(trailingTrivia);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
|
|
@ -76,22 +83,22 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax
|
|||
|
||||
public override sealed GreenNode GetLeadingTrivia()
|
||||
{
|
||||
return LeadingTrivia;
|
||||
return _leadingTrivia;
|
||||
}
|
||||
|
||||
public override int GetLeadingTriviaWidth()
|
||||
{
|
||||
return LeadingTrivia == null ? 0 : LeadingTrivia.FullWidth;
|
||||
return _leadingTrivia == null ? 0 : _leadingTrivia.FullWidth;
|
||||
}
|
||||
|
||||
public override sealed GreenNode GetTrailingTrivia()
|
||||
{
|
||||
return TrailingTrivia;
|
||||
return _trailingTrivia;
|
||||
}
|
||||
|
||||
public override int GetTrailingTriviaWidth()
|
||||
{
|
||||
return TrailingTrivia == null ? 0 : TrailingTrivia.FullWidth;
|
||||
return _trailingTrivia == null ? 0 : _trailingTrivia.FullWidth;
|
||||
}
|
||||
|
||||
public sealed override GreenNode WithLeadingTrivia(GreenNode trivia)
|
||||
|
|
@ -101,7 +108,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax
|
|||
|
||||
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)
|
||||
|
|
@ -111,17 +118,17 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax
|
|||
|
||||
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)
|
||||
{
|
||||
return new SyntaxToken(Kind, Content, LeadingTrivia, TrailingTrivia, diagnostics, GetAnnotations());
|
||||
return new SyntaxToken(Kind, Content, _leadingTrivia, _trailingTrivia, diagnostics, GetAnnotations());
|
||||
}
|
||||
|
||||
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()
|
||||
|
|
@ -195,5 +202,25 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax
|
|||
{
|
||||
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" ?>
|
||||
|
||||
<Tree Root="SyntaxNode">
|
||||
<PredefinedNode Name="SyntaxToken" Base="SyntaxNode" />
|
||||
<PredefinedNode Name="RazorSyntaxNode" Base="SyntaxNode" />
|
||||
<PredefinedNode Name="SyntaxToken" Base="RazorSyntaxNode" />
|
||||
|
||||
<!-- 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">
|
||||
<Kind Name="RazorComment" />
|
||||
<Field Name="StartCommentTransition" Type="SyntaxToken">
|
||||
|
|
@ -13,8 +20,8 @@
|
|||
<Field Name="StartCommentStar" Type="SyntaxToken">
|
||||
<Kind Name="RazorCommentStar" />
|
||||
</Field>
|
||||
<Field Name="Comment" Type="SyntaxToken" Optional="true">
|
||||
<Kind Name="RazorComment" />
|
||||
<Field Name="Comment" Type="SyntaxToken">
|
||||
<Kind Name="RazorCommentLiteral" />
|
||||
</Field>
|
||||
<Field Name="EndCommentStar" Type="SyntaxToken">
|
||||
<Kind Name="RazorCommentStar" />
|
||||
|
|
@ -23,39 +30,147 @@
|
|||
<Kind Name="RazorCommentTransition" />
|
||||
</Field>
|
||||
</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 -->
|
||||
<AbstractNode Name="HtmlSyntaxNode" Base="RazorSyntaxNode" />
|
||||
<Node Name="HtmlTextLiteralSyntax" Base="HtmlSyntaxNode">
|
||||
<Kind Name="HtmlTextLiteral" />
|
||||
<Field Name="TextTokens" Type="SyntaxList<SyntaxToken>" />
|
||||
<!-- Markup -->
|
||||
<AbstractNode Name="MarkupSyntaxNode" Base="RazorSyntaxNode" />
|
||||
<Node Name="MarkupBlockSyntax" Base="RazorBlockSyntax">
|
||||
<Kind Name="MarkupBlock" />
|
||||
<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>
|
||||
|
||||
<!-- CSharp -->
|
||||
<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">
|
||||
<Kind Name="CSharpTransition" />
|
||||
<Field Name="Transition" Type="SyntaxToken">
|
||||
<Kind Name="Transition" />
|
||||
</Field>
|
||||
</Node>
|
||||
<Node Name="CSharpMetaCodeSyntax" Base="CSharpSyntaxNode">
|
||||
<Kind Name="CSharpMetaCode" />
|
||||
<Field Name="MetaCode" Type="SyntaxList<SyntaxToken>" />
|
||||
<Node Name="CSharpStatementLiteralSyntax" Base="CSharpSyntaxNode">
|
||||
<Kind Name="CSharpStatementLiteral" />
|
||||
<Field Name="LiteralTokens" Type="SyntaxList<SyntaxToken>" />
|
||||
</Node>
|
||||
<Node Name="CSharpCodeLiteralSyntax" Base="CSharpSyntaxNode">
|
||||
<Kind Name="CSharpCodeLiteral" />
|
||||
<Field Name="CSharpTokens" Type="SyntaxList<SyntaxToken>" />
|
||||
<Node Name="CSharpExpressionLiteralSyntax" Base="CSharpSyntaxNode">
|
||||
<Kind Name="CSharpExpressionLiteral" />
|
||||
<Field Name="LiteralTokens" Type="SyntaxList<SyntaxToken>" />
|
||||
</Node>
|
||||
<Node Name="CSharpCodeBlockSyntax" Base="CSharpSyntaxNode">
|
||||
<Kind Name="CSharpCodeBlock" />
|
||||
<Field Name="Children" Type="SyntaxList<RazorSyntaxNode>" />
|
||||
<Node Name="CSharpEphemeralTextLiteralSyntax" Base="CSharpSyntaxNode">
|
||||
<Kind Name="CSharpEphemeralTextLiteral" />
|
||||
<Field Name="LiteralTokens" Type="SyntaxList<SyntaxToken>" />
|
||||
</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="Body" Type="CSharpSyntaxNode" />
|
||||
</AbstractNode>
|
||||
<Node Name="CSharpStatement" Base="CSharpBlockSyntax">
|
||||
<Node Name="CSharpStatementSyntax" Base="CSharpRazorBlockSyntax">
|
||||
<Kind Name="CSharpStatement" />
|
||||
<Field Name="Transition" Type="CSharpTransitionSyntax" Override="true">
|
||||
<Kind Name="CSharpTransition" />
|
||||
|
|
@ -66,37 +181,50 @@
|
|||
</Node>
|
||||
<Node Name="CSharpStatementBodySyntax" Base="CSharpSyntaxNode">
|
||||
<Kind Name="CSharpStatementBody" />
|
||||
<Field Name="OpenBrace" Type="CSharpMetaCodeSyntax" />
|
||||
<Field Name="OpenBrace" Type="RazorMetaCodeSyntax" />
|
||||
<Field Name="CSharpCode" Type="CSharpCodeBlockSyntax" />
|
||||
<Field Name="CloseBrace" Type="CSharpMetaCodeSyntax" />
|
||||
<Field Name="CloseBrace" Type="RazorMetaCodeSyntax" />
|
||||
</Node>
|
||||
<Node Name="CSharpExpression" Base="CSharpBlockSyntax">
|
||||
<Kind Name="CSharpExpression" />
|
||||
<Node Name="CSharpExplicitExpressionSyntax" Base="CSharpRazorBlockSyntax">
|
||||
<Kind Name="CSharpExplicitExpression" />
|
||||
<Field Name="Transition" Type="CSharpTransitionSyntax" Override="true">
|
||||
<Kind Name="CSharpTransition" />
|
||||
</Field>
|
||||
<Field Name="Body" Type="CSharpSyntaxNode" Override="true">
|
||||
<Kind Name="CSharpExpressionBody" />
|
||||
<Kind Name="CSharpExplicitExpressionBody" />
|
||||
</Field>
|
||||
</Node>
|
||||
<Node Name="CSharpExpressionBodySyntax" Base="CSharpSyntaxNode">
|
||||
<Kind Name="CSharpExpressionBody" />
|
||||
<Field Name="OpenParen" Type="CSharpMetaCodeSyntax" Optional="true" />
|
||||
<Node Name="CSharpExplicitExpressionBodySyntax" Base="CSharpSyntaxNode">
|
||||
<Kind Name="CSharpExplicitExpressionBody" />
|
||||
<Field Name="OpenParen" Type="RazorMetaCodeSyntax" />
|
||||
<Field Name="CSharpCode" Type="CSharpCodeBlockSyntax" />
|
||||
<Field Name="CloseParen" Type="CSharpMetaCodeSyntax" Optional="true" />
|
||||
<Field Name="CloseParen" Type="RazorMetaCodeSyntax" />
|
||||
</Node>
|
||||
<Node Name="CSharpDirectiveSyntax" Base="CSharpBlockSyntax">
|
||||
<Kind Name="CSharpDirective" />
|
||||
<Node Name="CSharpImplicitExpressionSyntax" Base="CSharpRazorBlockSyntax">
|
||||
<Kind Name="CSharpImplicitExpression" />
|
||||
<Field Name="Transition" Type="CSharpTransitionSyntax" Override="true">
|
||||
<Kind Name="CSharpTransition" />
|
||||
</Field>
|
||||
<Field Name="Body" Type="CSharpSyntaxNode" Override="true">
|
||||
<Kind Name="CSharpDirectiveBody" />
|
||||
<Kind Name="CSharpImplicitExpressionBody" />
|
||||
</Field>
|
||||
</Node>
|
||||
<Node Name="CSharpDirectiveBodySyntax" Base="CSharpSyntaxNode">
|
||||
<Kind Name="CSharpDirectiveBody" />
|
||||
<Field Name="Keyword" Type="CSharpMetaCodeSyntax" />
|
||||
<Node Name="CSharpImplicitExpressionBodySyntax" Base="CSharpSyntaxNode">
|
||||
<Kind Name="CSharpImplicitExpressionBody" />
|
||||
<Field Name="CSharpCode" Type="CSharpCodeBlockSyntax" />
|
||||
</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>
|
||||
|
|
@ -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.
|
||||
public string Kind { get; }
|
||||
public string Data { get; }
|
||||
public object Data { get; }
|
||||
|
||||
public SyntaxAnnotation()
|
||||
{
|
||||
|
|
@ -34,7 +34,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
|||
Kind = kind;
|
||||
}
|
||||
|
||||
public SyntaxAnnotation(string kind, string data)
|
||||
public SyntaxAnnotation(string kind, object data)
|
||||
: this(kind)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
#region Nodes
|
||||
// HTML
|
||||
HtmlTextLiteral,
|
||||
HtmlDocument,
|
||||
HtmlDeclaration,
|
||||
// Common
|
||||
RazorDocument,
|
||||
GenericBlock,
|
||||
RazorComment,
|
||||
RazorMetaCode,
|
||||
RazorDirective,
|
||||
RazorDirectiveBody,
|
||||
UnclassifiedTextLiteral,
|
||||
|
||||
// Markup
|
||||
MarkupBlock,
|
||||
MarkupTransition,
|
||||
MarkupElement,
|
||||
MarkupTagBlock,
|
||||
MarkupTextLiteral,
|
||||
MarkupEphemeralTextLiteral,
|
||||
MarkupCommentBlock,
|
||||
MarkupAttributeBlock,
|
||||
MarkupMinimizedAttributeBlock,
|
||||
MarkupLiteralAttributeValue,
|
||||
MarkupDynamicAttributeValue,
|
||||
MarkupTagHelperElement,
|
||||
MarkupTagHelperStartTag,
|
||||
MarkupTagHelperEndTag,
|
||||
MarkupTagHelperAttribute,
|
||||
MarkupMinimizedTagHelperAttribute,
|
||||
MarkupTagHelperAttributeValue,
|
||||
|
||||
// CSharp
|
||||
CSharpBlock,
|
||||
CSharpStatement,
|
||||
CSharpStatementBody,
|
||||
CSharpExpression,
|
||||
CSharpExpressionBody,
|
||||
CSharpDirective,
|
||||
CSharpDirectiveBody,
|
||||
CSharpExplicitExpression,
|
||||
CSharpExplicitExpressionBody,
|
||||
CSharpImplicitExpression,
|
||||
CSharpImplicitExpressionBody,
|
||||
CSharpCodeBlock,
|
||||
CSharpCodeLiteral,
|
||||
CSharpMetaCode,
|
||||
CSharpTemplateBlock,
|
||||
CSharpStatementLiteral,
|
||||
CSharpExpressionLiteral,
|
||||
CSharpEphemeralTextLiteral,
|
||||
CSharpTransition,
|
||||
|
||||
// Common
|
||||
RazorComment,
|
||||
#endregion
|
||||
|
||||
#region Tokens
|
||||
// Common
|
||||
Unknown,
|
||||
None,
|
||||
Marker,
|
||||
List,
|
||||
Whitespace,
|
||||
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.
|
||||
// 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.Text;
|
||||
using System.Threading;
|
||||
|
||||
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)
|
||||
{
|
||||
|
|
@ -60,6 +64,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
|||
|
||||
public bool IsMissing => Green.IsMissing;
|
||||
|
||||
public bool IsToken => Green.IsToken;
|
||||
|
||||
public bool IsTrivia => Green.IsTrivia;
|
||||
|
||||
public bool HasLeadingTrivia
|
||||
{
|
||||
get
|
||||
|
|
@ -80,6 +88,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
|||
|
||||
public bool ContainsAnnotations => Green.ContainsAnnotations;
|
||||
|
||||
internal string SerializedValue => SyntaxSerializer.Serialize(this);
|
||||
|
||||
public abstract TResult Accept<TResult>(SyntaxVisitor<TResult> visitor);
|
||||
|
||||
public abstract void Accept(SyntaxVisitor visitor);
|
||||
|
|
@ -253,18 +263,102 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
|||
|
||||
do
|
||||
{
|
||||
SyntaxNode lastChild = null;
|
||||
for (var i = node.SlotCount - 1; i >= 0; i--)
|
||||
{
|
||||
var child = node.GetNodeSlot(i);
|
||||
if (child != null)
|
||||
if (child != null && child.FullWidth > 0)
|
||||
{
|
||||
node = child;
|
||||
lastChild = child;
|
||||
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()
|
||||
|
|
@ -294,12 +388,26 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
|||
|
||||
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()
|
||||
{
|
||||
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.
|
||||
// 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
|
||||
{
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
internal class SyntaxToken : SyntaxNode
|
||||
internal class SyntaxToken : RazorSyntaxNode
|
||||
{
|
||||
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)
|
||||
{
|
||||
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;
|
||||
|
|
@ -107,32 +68,33 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
|||
|
||||
public override SyntaxTriviaList GetLeadingTrivia()
|
||||
{
|
||||
if (Green.LeadingTrivia == null)
|
||||
var leading = Green.GetLeadingTrivia();
|
||||
if (leading == null)
|
||||
{
|
||||
return default(SyntaxTriviaList);
|
||||
}
|
||||
|
||||
return new SyntaxTriviaList(Green.LeadingTrivia.CreateRed(this, Position), Position);
|
||||
return new SyntaxTriviaList(leading.CreateRed(this, Position), Position);
|
||||
}
|
||||
|
||||
public override SyntaxTriviaList GetTrailingTrivia()
|
||||
{
|
||||
var trailingGreen = Green.TrailingTrivia;
|
||||
if (trailingGreen == null)
|
||||
var trailing = Green.GetTrailingTrivia();
|
||||
if (trailing == null)
|
||||
{
|
||||
return default(SyntaxTriviaList);
|
||||
}
|
||||
|
||||
var leading = Green.LeadingTrivia;
|
||||
int index = 0;
|
||||
var leading = Green.GetLeadingTrivia();
|
||||
var index = 0;
|
||||
if (leading != null)
|
||||
{
|
||||
index = leading.IsList ? leading.SlotCount : 1;
|
||||
}
|
||||
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()
|
||||
|
|
|
|||
|
|
@ -3,6 +3,14 @@
|
|||
|
||||
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>
|
||||
{
|
||||
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
|
||||
{
|
||||
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