Adds stronger verification for spans
This change ensures that spans are contiguous and that all source is part of a span. This means that a character can't be 'lost' and not a member of any span. And guess what? We have a bug like that. So now a few tests are skipped due to that bug. Also made some changes to tests that construct invalid spans or spans without correct locations as their expected input. This allows us to add the above verification to all parser tests.
This commit is contained in:
parent
f191cc62a2
commit
0835de17ec
|
|
@ -611,6 +611,7 @@ catch(bar) { baz(); }", BlockType.Statement, SpanKind.Code);
|
|||
prefix: "try {",
|
||||
markup: " <p>Foo</p> ",
|
||||
suffix: "}",
|
||||
expectedStart: new SourceLocation(5, 0, 5),
|
||||
expectedMarkup: new MarkupBlock(
|
||||
Factory.Markup(" "),
|
||||
BlockFactory.MarkupTagBlock("<p>", AcceptedCharacters.None),
|
||||
|
|
@ -632,6 +633,7 @@ catch(bar) { baz(); }", BlockType.Statement, SpanKind.Code);
|
|||
prefix: "try { var foo = new { } } catch(Foo Bar Baz) {",
|
||||
markup: " <p>Foo</p> ",
|
||||
suffix: "}",
|
||||
expectedStart: new SourceLocation(46, 0, 46),
|
||||
expectedMarkup: new MarkupBlock(
|
||||
Factory.Markup(" "),
|
||||
BlockFactory.MarkupTagBlock("<p>", AcceptedCharacters.None),
|
||||
|
|
@ -664,6 +666,7 @@ catch(bar) { baz(); }", BlockType.Statement, SpanKind.Code);
|
|||
"{ var foo = new { } } catch(Foo Bar Baz) {",
|
||||
markup: " <p>Foo</p> ",
|
||||
suffix: "}",
|
||||
expectedStart: new SourceLocation(128, 0, 128),
|
||||
expectedMarkup: new MarkupBlock(
|
||||
Factory.Markup(" "),
|
||||
BlockFactory.MarkupTagBlock("<p>", AcceptedCharacters.None),
|
||||
|
|
@ -685,6 +688,7 @@ catch(bar) { baz(); }", BlockType.Statement, SpanKind.Code);
|
|||
prefix: "try { var foo = new { } } finally {",
|
||||
markup: " <p>Foo</p> ",
|
||||
suffix: "}",
|
||||
expectedStart: new SourceLocation(35, 0, 35),
|
||||
expectedMarkup: new MarkupBlock(
|
||||
Factory.Markup(" "),
|
||||
BlockFactory.MarkupTagBlock("<p>", AcceptedCharacters.None),
|
||||
|
|
@ -1115,6 +1119,8 @@ catch(bar) { baz(); }", BlockType.Statement, SpanKind.Code);
|
|||
[MemberData(nameof(BlockWithEscapedTransitionData))]
|
||||
public void ParseBlock_WithDoubleTransition_DoesNotThrow(string input, object expected)
|
||||
{
|
||||
FixupSpans = true;
|
||||
|
||||
// Act & Assert
|
||||
ParseBlockTest(input, (Block)expected);
|
||||
}
|
||||
|
|
@ -1217,14 +1223,22 @@ catch(bar) { baz(); }", BlockType.Statement, SpanKind.Code);
|
|||
Factory.Code(postComment).AsStatement().Accepts(acceptedCharacters)));
|
||||
}
|
||||
|
||||
private void RunSimpleWrappedMarkupTest(string prefix, string markup, string suffix, MarkupBlock expectedMarkup, AcceptedCharacters acceptedCharacters = AcceptedCharacters.Any)
|
||||
private void RunSimpleWrappedMarkupTest(string prefix, string markup, string suffix, MarkupBlock expectedMarkup, SourceLocation expectedStart, AcceptedCharacters acceptedCharacters = AcceptedCharacters.Any)
|
||||
{
|
||||
ParseBlockTest(prefix + markup + suffix,
|
||||
new StatementBlock(
|
||||
Factory.Code(prefix).AsStatement(),
|
||||
expectedMarkup,
|
||||
Factory.Code(suffix).AsStatement().Accepts(acceptedCharacters)
|
||||
));
|
||||
var expected = new StatementBlock(
|
||||
Factory.Code(prefix).AsStatement(),
|
||||
expectedMarkup,
|
||||
Factory.Code(suffix).AsStatement().Accepts(acceptedCharacters));
|
||||
|
||||
// Since we're building the 'expected' input out of order we need to do some trickery
|
||||
// to get the locations right.
|
||||
SpancestryCorrector.Correct(expected);
|
||||
expected.FindFirstDescendentSpan().ChangeStart(SourceLocation.Zero);
|
||||
|
||||
// We make the caller pass a start location so we can verify that nothing has gone awry.
|
||||
Assert.Equal(expectedStart, expectedMarkup.Start);
|
||||
|
||||
ParseBlockTest(prefix + markup + suffix, expected);
|
||||
}
|
||||
|
||||
private void NamespaceImportTest(string content, string expectedNS, AcceptedCharacters acceptedCharacters = AcceptedCharacters.None, string errorMessage = null, SourceLocation? location = null)
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
|
|||
length: 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Fact(Skip = "Fails due to https://github.com/aspnet/Razor/issues/897")]
|
||||
public void RazorCommentInVerbatimBlock()
|
||||
{
|
||||
ParseDocumentTest("@{" + Environment.NewLine
|
||||
|
|
|
|||
|
|
@ -696,6 +696,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
|
|||
[MemberData(nameof(SectionWithEscapedTransitionData))]
|
||||
public void ParseSectionBlock_WithDoubleTransition_DoesNotThrow(string input, object expected)
|
||||
{
|
||||
FixupSpans = true;
|
||||
|
||||
ParseDocumentTest(input, (Block)expected);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -237,6 +237,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
|
|||
[MemberData(nameof(ExceptionFilterData))]
|
||||
public void ExceptionFilters(string document, object expectedStatement)
|
||||
{
|
||||
FixupSpans = true;
|
||||
|
||||
// Act & Assert
|
||||
ParseBlockTest(document, (StatementBlock)expectedStatement);
|
||||
}
|
||||
|
|
@ -292,6 +294,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
|
|||
object expectedStatement,
|
||||
object expectedErrors)
|
||||
{
|
||||
FixupSpans = true;
|
||||
|
||||
// Act & Assert
|
||||
ParseBlockTest(document, (StatementBlock)expectedStatement, (RazorError[])expectedErrors);
|
||||
}
|
||||
|
|
@ -351,6 +355,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
|
|||
[MemberData(nameof(StaticUsingData))]
|
||||
public void StaticUsingImport(string document, object expectedResult)
|
||||
{
|
||||
FixupSpans = true;
|
||||
|
||||
// Act & Assert
|
||||
ParseBlockTest(document, (DirectiveBlock)expectedResult);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -270,6 +270,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
|
|||
[Fact]
|
||||
public void ParseBlock_WithDoubleTransition_DoesNotThrow()
|
||||
{
|
||||
FixupSpans = true;
|
||||
|
||||
// Arrange
|
||||
var testTemplateWithDoubleTransitionCode = " @<p foo='@@'>Foo #@item</p>";
|
||||
var testTemplateWithDoubleTransition = new TemplateBlock(
|
||||
|
|
|
|||
|
|
@ -605,7 +605,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
|
|||
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Fact(Skip = "Fails due to https://github.com/aspnet/Razor/issues/897")]
|
||||
public void ParseBlockCorrectlyReturnsFromMarkupBlockWithPseudoTag()
|
||||
{
|
||||
ParseBlockTest("if (i > 0) { <text>;</text> }",
|
||||
|
|
@ -620,7 +620,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
|
|||
Factory.Code(" }").AsStatement()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Fact(Skip = "Fails due to https://github.com/aspnet/Razor/issues/897")]
|
||||
public void ParseBlockCorrectlyReturnsFromMarkupBlockWithPseudoTagInCodeBlock()
|
||||
{
|
||||
ParseBlockTest("{ if (i > 0) { <text>;</text> } }",
|
||||
|
|
@ -639,7 +639,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
|
|||
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Fact(Skip = "Fails due to https://github.com/aspnet/Razor/issues/897")]
|
||||
public void ParseBlockSupportsAllKindsOfImplicitMarkupInCodeBlock()
|
||||
{
|
||||
ParseBlockTest("{" + Environment.NewLine
|
||||
|
|
|
|||
|
|
@ -390,6 +390,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
|
|||
[MemberData(nameof(HtmlCommentSupportsMultipleDashesData))]
|
||||
public void HtmlCommentSupportsMultipleDashes(string documentContent, object expectedOutput)
|
||||
{
|
||||
FixupSpans = true;
|
||||
|
||||
ParseBlockTest(documentContent, (MarkupBlock)expectedOutput);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -757,6 +757,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
|
|||
[MemberData(nameof(BlockWithEscapedTransitionData))]
|
||||
public void ParseBlock_WithDoubleTransition_DoesNotThrow(string input, object expected)
|
||||
{
|
||||
FixupSpans = true;
|
||||
|
||||
// Act & Assert
|
||||
ParseDocumentTest(input, (Block)expected);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,15 +14,22 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
|
|||
{
|
||||
internal static Block IgnoreOutput = new IgnoreOutputBlock();
|
||||
|
||||
internal SpanFactory Factory { get; private set; }
|
||||
internal BlockFactory BlockFactory { get; private set; }
|
||||
|
||||
internal ParserTestBase()
|
||||
{
|
||||
Factory = CreateSpanFactory();
|
||||
BlockFactory = CreateBlockFactory();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set to true to autocorrect the locations of spans to appear in document order with no gaps.
|
||||
/// Use this when spans were not created in document order.
|
||||
/// </summary>
|
||||
protected bool FixupSpans { get; set; }
|
||||
|
||||
internal SpanFactory Factory { get; private set; }
|
||||
|
||||
internal BlockFactory BlockFactory { get; private set; }
|
||||
|
||||
internal abstract RazorSyntaxTree ParseBlock(string document, bool designTime);
|
||||
|
||||
internal virtual RazorSyntaxTree ParseDocument(string document, bool designTime = false)
|
||||
|
|
@ -149,6 +156,17 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
|
|||
{
|
||||
var result = ParseBlock(document, designTime);
|
||||
|
||||
if (FixupSpans)
|
||||
{
|
||||
SpancestryCorrector.Correct(expected);
|
||||
|
||||
var span = expected.FindFirstDescendentSpan();
|
||||
span.ChangeStart(SourceLocation.Zero);
|
||||
}
|
||||
|
||||
SyntaxTreeVerifier.Verify(result);
|
||||
SyntaxTreeVerifier.Verify(expected);
|
||||
|
||||
if (!ReferenceEquals(expected, IgnoreOutput))
|
||||
{
|
||||
EvaluateResults(result, expected, expectedErrors);
|
||||
|
|
@ -188,6 +206,17 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
|
|||
builder.Type = blockType;
|
||||
var expected = ConfigureAndAddSpanToBlock(builder, Factory.Span(spanType, spanContent, spanType == SpanKind.Markup).Accepts(acceptedCharacters));
|
||||
|
||||
if (FixupSpans)
|
||||
{
|
||||
SpancestryCorrector.Correct(expected);
|
||||
|
||||
var span = expected.FindFirstDescendentSpan();
|
||||
span.ChangeStart(SourceLocation.Zero);
|
||||
}
|
||||
|
||||
SyntaxTreeVerifier.Verify(result);
|
||||
SyntaxTreeVerifier.Verify(expected);
|
||||
|
||||
if (!ReferenceEquals(expected, IgnoreOutput))
|
||||
{
|
||||
EvaluateResults(result, expected, expectedErrors);
|
||||
|
|
@ -219,33 +248,25 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
|
|||
ParseDocumentTest(document, expectedRoot, designTime, null);
|
||||
}
|
||||
|
||||
internal virtual void ParseDocumentTest(string document, Block expectedRoot, bool designTime, params RazorError[] expectedErrors)
|
||||
internal virtual void ParseDocumentTest(string document, Block expected, bool designTime, params RazorError[] expectedErrors)
|
||||
{
|
||||
var result = ParseDocument(document, designTime);
|
||||
|
||||
if (!ReferenceEquals(expectedRoot, IgnoreOutput))
|
||||
if (FixupSpans)
|
||||
{
|
||||
EvaluateResults(result, expectedRoot, expectedErrors);
|
||||
SpancestryCorrector.Correct(expected);
|
||||
|
||||
var span = expected.FindFirstDescendentSpan();
|
||||
span.ChangeStart(SourceLocation.Zero);
|
||||
}
|
||||
}
|
||||
|
||||
internal virtual RazorSyntaxTree RunParse(
|
||||
string document,
|
||||
Func<ParserBase, Action> parserActionSelector,
|
||||
bool designTimeParser,
|
||||
Func<ParserContext, ParserBase> parserSelector = null,
|
||||
ErrorSink errorSink = null)
|
||||
{
|
||||
throw null;
|
||||
}
|
||||
SyntaxTreeVerifier.Verify(result);
|
||||
SyntaxTreeVerifier.Verify(expected);
|
||||
|
||||
internal virtual void RunParseTest(
|
||||
string document,
|
||||
Func<ParserBase, Action> parserActionSelector,
|
||||
Block expectedRoot, IList<RazorError> expectedErrors,
|
||||
bool designTimeParser, Func<ParserContext, ParserBase> parserSelector = null)
|
||||
{
|
||||
throw null;
|
||||
if (!ReferenceEquals(expected, IgnoreOutput))
|
||||
{
|
||||
EvaluateResults(result, expected, expectedErrors);
|
||||
}
|
||||
}
|
||||
|
||||
[Conditional("PARSER_TRACE")]
|
||||
|
|
@ -547,5 +568,41 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
|
|||
{
|
||||
public IgnoreOutputBlock() : base(BlockType.Template, new SyntaxTreeNode[0], null) { }
|
||||
}
|
||||
|
||||
// Corrects the parents and previous/next information for spans
|
||||
internal class SpancestryCorrector : ParserVisitor
|
||||
{
|
||||
private SpancestryCorrector()
|
||||
{
|
||||
}
|
||||
|
||||
protected Block CurrentBlock { get; set; }
|
||||
|
||||
protected Span LastSpan { get; set; }
|
||||
|
||||
public static void Correct(Block block)
|
||||
{
|
||||
new SpancestryCorrector().VisitBlock(block);
|
||||
}
|
||||
|
||||
public override void VisitBlock(Block block)
|
||||
{
|
||||
CurrentBlock = block;
|
||||
base.VisitBlock(block);
|
||||
}
|
||||
|
||||
public override void VisitSpan(Span span)
|
||||
{
|
||||
span.Parent = CurrentBlock;
|
||||
|
||||
span.Previous = LastSpan;
|
||||
if (LastSpan != null)
|
||||
{
|
||||
LastSpan.Next = span;
|
||||
}
|
||||
|
||||
LastSpan = span;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
// 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 Microsoft.AspNetCore.Razor.Evolution.Legacy;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Evolution
|
||||
{
|
||||
// Verifies recursively that a syntax tree has no gaps in terms of position/location.
|
||||
internal class SyntaxTreeVerifier : ParserVisitor
|
||||
{
|
||||
private readonly SourceLocationTracker _tracker = new SourceLocationTracker(SourceLocation.Zero);
|
||||
|
||||
private SyntaxTreeVerifier()
|
||||
{
|
||||
}
|
||||
|
||||
public static void Verify(RazorSyntaxTree syntaxTree)
|
||||
{
|
||||
Verify(syntaxTree.Root);
|
||||
}
|
||||
|
||||
public static void Verify(Block block)
|
||||
{
|
||||
new SyntaxTreeVerifier().VisitBlock(block);
|
||||
}
|
||||
|
||||
public override void VisitSpan(Span span)
|
||||
{
|
||||
var start = span.Start;
|
||||
if (!start.Equals(_tracker.CurrentLocation))
|
||||
{
|
||||
throw new InvalidOperationException($"Span starting at {span.Start} should start at {_tracker.CurrentLocation} - {span} ");
|
||||
}
|
||||
|
||||
for (var i = 0; i < span.Symbols.Count; i++)
|
||||
{
|
||||
_tracker.UpdateLocation(span.Symbols[i].Content);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue