Make TagHelper & Razor comments locatable at design time.
- Added a step to full fidelity verification that does a `LocateOwner` at every source location. If we find a `null` owner we build a snippet with a detailed response to enable us to diagnose the problem.
Addresses aspnet/AspNetCoredotnet/aspnetcore-tooling#7718
\n\nCommit migrated from 08587a30fd
This commit is contained in:
parent
82d850d3a7
commit
1243e07e56
|
|
@ -23,6 +23,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
|
|
||||||
private static readonly SyntaxKind[] CommentSpanKinds = new SyntaxKind[]
|
private static readonly SyntaxKind[] CommentSpanKinds = new SyntaxKind[]
|
||||||
{
|
{
|
||||||
|
SyntaxKind.RazorCommentTransition,
|
||||||
|
SyntaxKind.RazorCommentStar,
|
||||||
SyntaxKind.RazorCommentLiteral,
|
SyntaxKind.RazorCommentLiteral,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -100,7 +102,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
}
|
}
|
||||||
|
|
||||||
SyntaxNode owner = null;
|
SyntaxNode owner = null;
|
||||||
IEnumerable<SyntaxNode> children = null;
|
IEnumerable<SyntaxNode> children;
|
||||||
if (node is MarkupStartTagSyntax startTag)
|
if (node is MarkupStartTagSyntax startTag)
|
||||||
{
|
{
|
||||||
children = startTag.Children;
|
children = startTag.Children;
|
||||||
|
|
@ -109,6 +111,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
{
|
{
|
||||||
children = endTag.Children;
|
children = endTag.Children;
|
||||||
}
|
}
|
||||||
|
else if (node is MarkupTagHelperStartTagSyntax startTagHelper)
|
||||||
|
{
|
||||||
|
children = startTagHelper.Children;
|
||||||
|
}
|
||||||
|
else if (node is MarkupTagHelperEndTagSyntax endTagHelper)
|
||||||
|
{
|
||||||
|
children = endTagHelper.Children;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
children = node.ChildNodes();
|
children = node.ChildNodes();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
||||||
|
{
|
||||||
|
internal partial class MarkupTagHelperEndTagSyntax
|
||||||
|
{
|
||||||
|
// Copied directly from MarkupEndTagSyntax Children & GetLegacyChildren.
|
||||||
|
public SyntaxList<RazorSyntaxNode> Children => GetLegacyChildren();
|
||||||
|
|
||||||
|
private SyntaxList<RazorSyntaxNode> GetLegacyChildren()
|
||||||
|
{
|
||||||
|
// This method returns the children of this end tag in legacy format.
|
||||||
|
// This is needed to generate the same classified spans as the legacy syntax tree.
|
||||||
|
var builder = new SyntaxListBuilder(3);
|
||||||
|
var tokens = SyntaxListBuilder<SyntaxToken>.Create();
|
||||||
|
var context = this.GetSpanContext();
|
||||||
|
if (!OpenAngle.IsMissing)
|
||||||
|
{
|
||||||
|
tokens.Add(OpenAngle);
|
||||||
|
}
|
||||||
|
if (!ForwardSlash.IsMissing)
|
||||||
|
{
|
||||||
|
tokens.Add(ForwardSlash);
|
||||||
|
}
|
||||||
|
if (Bang != null)
|
||||||
|
{
|
||||||
|
// The prefix of an end tag(E.g '|</|!foo>') will have 'Any' accepted characters if a bang exists.
|
||||||
|
var acceptsAnyContext = new SpanContext(context.ChunkGenerator, SpanEditHandler.CreateDefault());
|
||||||
|
acceptsAnyContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.Any;
|
||||||
|
builder.Add(SyntaxFactory.MarkupTextLiteral(tokens.Consume()).WithSpanContext(acceptsAnyContext));
|
||||||
|
|
||||||
|
tokens.Add(Bang);
|
||||||
|
var acceptsNoneContext = new SpanContext(context.ChunkGenerator, SpanEditHandler.CreateDefault());
|
||||||
|
acceptsNoneContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.None;
|
||||||
|
builder.Add(SyntaxFactory.RazorMetaCode(tokens.Consume()).WithSpanContext(acceptsNoneContext));
|
||||||
|
}
|
||||||
|
if (!Name.IsMissing)
|
||||||
|
{
|
||||||
|
tokens.Add(Name);
|
||||||
|
}
|
||||||
|
if (MiscAttributeContent?.Children != null && MiscAttributeContent.Children.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var content in MiscAttributeContent.Children)
|
||||||
|
{
|
||||||
|
tokens.AddRange(((MarkupTextLiteralSyntax)content).LiteralTokens);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!CloseAngle.IsMissing)
|
||||||
|
{
|
||||||
|
tokens.Add(CloseAngle);
|
||||||
|
}
|
||||||
|
builder.Add(SyntaxFactory.MarkupTextLiteral(tokens.Consume()).WithSpanContext(context));
|
||||||
|
|
||||||
|
return new SyntaxList<RazorSyntaxNode>(builder.ToListNode().CreateRed(this, Position));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
||||||
|
{
|
||||||
|
internal partial class MarkupTagHelperStartTagSyntax
|
||||||
|
{
|
||||||
|
// Copied directly from MarkupStartTagSyntax Children & GetLegacyChildren.
|
||||||
|
|
||||||
|
public SyntaxList<RazorSyntaxNode> Children => GetLegacyChildren();
|
||||||
|
|
||||||
|
private SyntaxList<RazorSyntaxNode> GetLegacyChildren()
|
||||||
|
{
|
||||||
|
// This method returns the children of this start tag in legacy format.
|
||||||
|
// This is needed to generate the same classified spans as the legacy syntax tree.
|
||||||
|
var builder = new SyntaxListBuilder(5);
|
||||||
|
var tokens = SyntaxListBuilder<SyntaxToken>.Create();
|
||||||
|
var context = this.GetSpanContext();
|
||||||
|
|
||||||
|
// We want to know if this tag contains non-whitespace attribute content to set the appropriate AcceptedCharacters.
|
||||||
|
// The prefix of a start tag(E.g '|<foo| attr>') will have 'Any' accepted characters if non-whitespace attribute content exists.
|
||||||
|
var acceptsAnyContext = new SpanContext(context.ChunkGenerator, SpanEditHandler.CreateDefault());
|
||||||
|
acceptsAnyContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.Any;
|
||||||
|
var containsAttributesContent = false;
|
||||||
|
foreach (var attribute in Attributes)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(attribute.GetContent()))
|
||||||
|
{
|
||||||
|
containsAttributesContent = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!OpenAngle.IsMissing)
|
||||||
|
{
|
||||||
|
tokens.Add(OpenAngle);
|
||||||
|
}
|
||||||
|
if (Bang != null)
|
||||||
|
{
|
||||||
|
builder.Add(SyntaxFactory.MarkupTextLiteral(tokens.Consume()).WithSpanContext(acceptsAnyContext));
|
||||||
|
|
||||||
|
tokens.Add(Bang);
|
||||||
|
var acceptsNoneContext = new SpanContext(context.ChunkGenerator, SpanEditHandler.CreateDefault());
|
||||||
|
acceptsNoneContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.None;
|
||||||
|
builder.Add(SyntaxFactory.RazorMetaCode(tokens.Consume()).WithSpanContext(acceptsNoneContext));
|
||||||
|
}
|
||||||
|
if (!Name.IsMissing)
|
||||||
|
{
|
||||||
|
tokens.Add(Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.Add(SyntaxFactory.MarkupTextLiteral(tokens.Consume()).WithSpanContext(containsAttributesContent ? acceptsAnyContext : context));
|
||||||
|
|
||||||
|
builder.AddRange(Attributes);
|
||||||
|
|
||||||
|
if (ForwardSlash != null)
|
||||||
|
{
|
||||||
|
tokens.Add(ForwardSlash);
|
||||||
|
}
|
||||||
|
if (!CloseAngle.IsMissing)
|
||||||
|
{
|
||||||
|
tokens.Add(CloseAngle);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tokens.Count > 0)
|
||||||
|
{
|
||||||
|
builder.Add(SyntaxFactory.MarkupTextLiteral(tokens.Consume()).WithSpanContext(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SyntaxList<RazorSyntaxNode>(builder.ToListNode().CreateRed(this, Position));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,7 @@ using System.Text;
|
||||||
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||||
using Microsoft.AspNetCore.Razor.Language.Syntax;
|
using Microsoft.AspNetCore.Razor.Language.Syntax;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
using Xunit.Sdk;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language
|
namespace Microsoft.AspNetCore.Razor.Language
|
||||||
{
|
{
|
||||||
|
|
@ -29,6 +30,38 @@ namespace Microsoft.AspNetCore.Razor.Language
|
||||||
|
|
||||||
// Make sure the syntax tree contains all of the text in the document.
|
// Make sure the syntax tree contains all of the text in the document.
|
||||||
Assert.Equal(sourceString, syntaxTreeString);
|
Assert.Equal(sourceString, syntaxTreeString);
|
||||||
|
|
||||||
|
// Ensure all source is locatable
|
||||||
|
for (var i = 0; i < syntaxTree.Source.Length; i++)
|
||||||
|
{
|
||||||
|
var span = new SourceSpan(i, 0);
|
||||||
|
var location = new SourceChange(span, string.Empty);
|
||||||
|
var owner = syntaxTree.Root.LocateOwner(location);
|
||||||
|
|
||||||
|
if (owner == null)
|
||||||
|
{
|
||||||
|
var snippetStartIndex = Math.Max(0, i - 10);
|
||||||
|
var snippetStartLength = i - snippetStartIndex;
|
||||||
|
var snippetStart = new char[snippetStartLength];
|
||||||
|
syntaxTree.Source.CopyTo(snippetStartIndex, snippetStart, 0, snippetStartLength);
|
||||||
|
|
||||||
|
var snippetEndIndex = Math.Min(syntaxTree.Source.Length - 1, i + 10);
|
||||||
|
var snippetEndLength = snippetEndIndex - i;
|
||||||
|
var snippetEnd = new char[snippetEndLength];
|
||||||
|
syntaxTree.Source.CopyTo(i, snippetEnd, 0, snippetEndLength);
|
||||||
|
|
||||||
|
var snippet = new char[snippetStart.Length + snippetEnd.Length + 1];
|
||||||
|
snippetStart.CopyTo(snippet, 0);
|
||||||
|
snippet[snippetStart.Length] = '|';
|
||||||
|
snippetEnd.CopyTo(snippet, snippetStart.Length + 1);
|
||||||
|
|
||||||
|
var snippetString = new string(snippet);
|
||||||
|
|
||||||
|
throw new XunitException(
|
||||||
|
$@"Could not locate Syntax Node owner at position '{i}':
|
||||||
|
{snippetString}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue