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:
Ajay Bhargav Baaskaran 2018-11-16 17:22:37 -08:00 committed by GitHub
parent 61565f61f9
commit 6c8e900d11
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1453 changed files with 53639 additions and 36455 deletions

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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; }
}

View File

@ -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)

View File

@ -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)

View File

@ -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;
}
}
}
}

View File

@ -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 + ";";

View File

@ -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)
{

View File

@ -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;
}
}
}

View File

@ -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 &&

View File

@ -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();
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -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;
}
}

View File

@ -458,7 +458,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
return handler();
}
return SyntaxKind.Unknown;
return SyntaxKind.Marker;
}
private SyntaxKind LessThanOperator()

View File

@ -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; }
}
}

View File

@ -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;
}

View File

@ -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);
}
}
}

View File

@ -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();
}
}
}

View File

@ -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;

View File

@ -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();
}
}
}

View File

@ -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; }
}
}

View File

@ -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";

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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))

View File

@ -5,7 +5,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
internal enum KnownTokenType
{
WhiteSpace,
Whitespace,
NewLine,
Identifier,
Keyword,

View File

@ -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)

View File

@ -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;
}
}
}

View File

@ -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);

View File

@ -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";

View File

@ -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);
}
}
}
}

View File

@ -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);
}
}
}
}

View File

@ -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();
}
}

View File

@ -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; }

View File

@ -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);
}
}
}

View File

@ -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";
}
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -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)
{

View File

@ -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;
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -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";

View File

@ -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;
}
}
}

View File

@ -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()

View File

@ -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";

View File

@ -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
{

View File

@ -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;
}
}
}

View File

@ -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();
}
}

View File

@ -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; }
}

View File

@ -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;
}
}
}

View File

@ -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; }
}
}

View File

@ -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";
}
}
}

View File

@ -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)
{

View File

@ -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";
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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; }
}

View File

@ -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)

View File

@ -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();
}
}
}
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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;
}
}
}

View File

@ -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)
{
}
}
}

View File

@ -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);
}
}
}

View File

@ -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];
}
}
}
}
}

View File

@ -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);
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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,
}
}

View File

@ -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);
}
}
}

View File

@ -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)
{
}
}
}

View File

@ -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&lt;RazorSyntaxNode&gt;" />
</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&lt;SyntaxToken&gt;" />
</Node>
<Node Name="GenericBlockSyntax" Base="RazorBlockSyntax">
<Kind Name="GenericBlock" />
<Field Name="Children" Type="SyntaxList&lt;RazorSyntaxNode&gt;" Override="true" />
</Node>
<Node Name="UnclassifiedTextLiteralSyntax" Base="RazorSyntaxNode">
<Kind Name="UnclassifiedTextLiteral" />
<Field Name="LiteralTokens" Type="SyntaxList&lt;SyntaxToken&gt;" />
</Node>
<!-- HTML -->
<AbstractNode Name="HtmlSyntaxNode" Base="RazorSyntaxNode" />
<Node Name="HtmlTextLiteralSyntax" Base="HtmlSyntaxNode">
<Kind Name="HtmlTextLiteral" />
<Field Name="TextTokens" Type="SyntaxList&lt;SyntaxToken&gt;" />
<!-- Markup -->
<AbstractNode Name="MarkupSyntaxNode" Base="RazorSyntaxNode" />
<Node Name="MarkupBlockSyntax" Base="RazorBlockSyntax">
<Kind Name="MarkupBlock" />
<Field Name="Children" Type="SyntaxList&lt;RazorSyntaxNode&gt;" Override="true" />
</Node>
<Node Name="MarkupTransitionSyntax" Base="MarkupSyntaxNode">
<Kind Name="MarkupTransition" />
<Field Name="TransitionTokens" Type="SyntaxList&lt;SyntaxToken&gt;" />
</Node>
<Node Name="MarkupTextLiteralSyntax" Base="MarkupSyntaxNode">
<Kind Name="MarkupTextLiteral" />
<Field Name="LiteralTokens" Type="SyntaxList&lt;SyntaxToken&gt;" />
</Node>
<Node Name="MarkupEphemeralTextLiteralSyntax" Base="MarkupSyntaxNode">
<Kind Name="MarkupEphemeralTextLiteral" />
<Field Name="LiteralTokens" Type="SyntaxList&lt;SyntaxToken&gt;" />
</Node>
<Node Name="MarkupCommentBlockSyntax" Base="RazorBlockSyntax">
<Kind Name="MarkupCommentBlock" />
<Field Name="Children" Type="SyntaxList&lt;RazorSyntaxNode&gt;" Override="true" />
</Node>
<Node Name="MarkupTagBlockSyntax" Base="RazorBlockSyntax">
<Kind Name="MarkupTagBlock" />
<Field Name="Children" Type="SyntaxList&lt;RazorSyntaxNode&gt;" 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&lt;RazorSyntaxNode&gt;" 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&lt;RazorSyntaxNode&gt;" Optional="true" />
<Field Name="EndTag" Type="MarkupTagHelperEndTagSyntax" Optional="true" />
</Node>
<Node Name="MarkupTagHelperStartTagSyntax" Base="RazorBlockSyntax">
<Kind Name="MarkupTagHelperStartTag" />
<Field Name="Children" Type="SyntaxList&lt;RazorSyntaxNode&gt;" Override="true" />
</Node>
<Node Name="MarkupTagHelperEndTagSyntax" Base="RazorBlockSyntax">
<Kind Name="MarkupTagHelperEndTag" />
<Field Name="Children" Type="SyntaxList&lt;RazorSyntaxNode&gt;" 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&lt;RazorSyntaxNode&gt;" Override="true" />
</Node>
<!-- CSharp -->
<AbstractNode Name="CSharpSyntaxNode" Base="RazorSyntaxNode" />
<Node Name="CSharpCodeBlockSyntax" Base="RazorBlockSyntax">
<Kind Name="CSharpCodeBlock" />
<Field Name="Children" Type="SyntaxList&lt;RazorSyntaxNode&gt;" 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&lt;SyntaxToken&gt;" />
<Node Name="CSharpStatementLiteralSyntax" Base="CSharpSyntaxNode">
<Kind Name="CSharpStatementLiteral" />
<Field Name="LiteralTokens" Type="SyntaxList&lt;SyntaxToken&gt;" />
</Node>
<Node Name="CSharpCodeLiteralSyntax" Base="CSharpSyntaxNode">
<Kind Name="CSharpCodeLiteral" />
<Field Name="CSharpTokens" Type="SyntaxList&lt;SyntaxToken&gt;" />
<Node Name="CSharpExpressionLiteralSyntax" Base="CSharpSyntaxNode">
<Kind Name="CSharpExpressionLiteral" />
<Field Name="LiteralTokens" Type="SyntaxList&lt;SyntaxToken&gt;" />
</Node>
<Node Name="CSharpCodeBlockSyntax" Base="CSharpSyntaxNode">
<Kind Name="CSharpCodeBlock" />
<Field Name="Children" Type="SyntaxList&lt;RazorSyntaxNode&gt;" />
<Node Name="CSharpEphemeralTextLiteralSyntax" Base="CSharpSyntaxNode">
<Kind Name="CSharpEphemeralTextLiteral" />
<Field Name="LiteralTokens" Type="SyntaxList&lt;SyntaxToken&gt;" />
</Node>
<AbstractNode Name="CSharpBlockSyntax" Base="CSharpSyntaxNode">
<Node Name="CSharpTemplateBlockSyntax" Base="RazorBlockSyntax">
<Kind Name="CSharpTemplateBlock" />
<Field Name="Children" Type="SyntaxList&lt;RazorSyntaxNode&gt;" 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>

View File

@ -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;

View File

@ -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());
}
}
}

View File

@ -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);
}
}
}

View File

@ -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,

View File

@ -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);
}
}
}
}
}

View File

@ -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);
}
}
}

View File

@ -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());
}
}
}

View File

@ -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
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -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()

View File

@ -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)

View File

@ -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