First step in converting parser tests to use baselines
- Added the necessary infrastructure to serialize and verify the syntax tree - Updated ParserVisitor to be overrideable - Made CSharpVerbatimBlockTest to use baselines
This commit is contained in:
parent
620105cc5e
commit
b821ce8b8e
|
|
@ -47,10 +47,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
|
|
||||||
public void Accept(ParserVisitor visitor, Block block)
|
public void Accept(ParserVisitor visitor, Block block)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < block.Children.Count; i++)
|
visitor.VisitDefault(block);
|
||||||
{
|
|
||||||
block.Children[i].Accept(visitor);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
{
|
{
|
||||||
internal abstract class ParserVisitor
|
internal abstract class ParserVisitor
|
||||||
{
|
{
|
||||||
protected virtual void VisitDefault(Block block)
|
public virtual void Visit(SyntaxTreeNode node)
|
||||||
|
{
|
||||||
|
node.Accept(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void VisitDefault(Block block)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < block.Children.Count; i++)
|
for (var i = 0; i < block.Children.Count; i++)
|
||||||
{
|
{
|
||||||
|
|
@ -13,6 +18,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public virtual void VisitDefault(Span span)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public virtual void VisitBlock(Block block)
|
public virtual void VisitBlock(Block block)
|
||||||
{
|
{
|
||||||
if (block.ChunkGenerator != null)
|
if (block.ChunkGenerator != null)
|
||||||
|
|
@ -44,35 +53,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
VisitDefault(block);
|
VisitDefault(block);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void VisitExpressionSpan(ExpressionChunkGenerator chunkGenerator, Span span)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void VisitMarkupSpan(MarkupChunkGenerator chunkGenerator, Span span)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void VisitImportSpan(AddImportChunkGenerator chunkGenerator, Span span)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void VisitStatementSpan(StatementChunkGenerator chunkGenerator, Span span)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void VisitLiteralAttributeSpan(LiteralAttributeChunkGenerator chunkGenerator, Span span)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void VisitDirectiveToken(DirectiveTokenChunkGenerator chunkGenerator, Span block)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void VisitDirectiveBlock(DirectiveChunkGenerator chunkGenerator, Block block)
|
|
||||||
{
|
|
||||||
VisitDefault(block);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void VisitTemplateBlock(TemplateBlockChunkGenerator chunkGenerator, Block block)
|
public virtual void VisitTemplateBlock(TemplateBlockChunkGenerator chunkGenerator, Block block)
|
||||||
{
|
{
|
||||||
VisitDefault(block);
|
VisitDefault(block);
|
||||||
|
|
@ -88,16 +68,54 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
VisitDefault(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)
|
public virtual void VisitAddTagHelperSpan(AddTagHelperChunkGenerator chunkGenerator, Span span)
|
||||||
{
|
{
|
||||||
|
VisitDefault(span);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void VisitRemoveTagHelperSpan(RemoveTagHelperChunkGenerator chunkGenerator, Span span)
|
public virtual void VisitRemoveTagHelperSpan(RemoveTagHelperChunkGenerator chunkGenerator, Span span)
|
||||||
{
|
{
|
||||||
|
VisitDefault(span);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void VisitTagHelperPrefixDirectiveSpan(TagHelperPrefixDirectiveChunkGenerator chunkGenerator, Span span)
|
public virtual void VisitTagHelperPrefixDirectiveSpan(TagHelperPrefixDirectiveChunkGenerator chunkGenerator, Span span)
|
||||||
{
|
{
|
||||||
|
VisitDefault(span);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
{
|
{
|
||||||
public void Accept(ParserVisitor visitor, Span span)
|
public void Accept(ParserVisitor visitor, Span span)
|
||||||
{
|
{
|
||||||
|
visitor.VisitDefault(span);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void GenerateChunk(Span target, ChunkGeneratorContext context)
|
public void GenerateChunk(Span target, ChunkGeneratorContext context)
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
{
|
{
|
||||||
public class CSharpReservedWordsTest : CsHtmlCodeParserTestBase
|
public class CSharpReservedWordsTest : CsHtmlCodeParserTestBase
|
||||||
{
|
{
|
||||||
|
public CSharpReservedWordsTest()
|
||||||
|
{
|
||||||
|
UseBaselineTests = true;
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("namespace")]
|
[InlineData("namespace")]
|
||||||
[InlineData("class")]
|
[InlineData("class")]
|
||||||
|
|
|
||||||
|
|
@ -10,125 +10,45 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
{
|
{
|
||||||
private const string TestExtraKeyword = "model";
|
private const string TestExtraKeyword = "model";
|
||||||
|
|
||||||
|
public CSharpVerbatimBlockTest()
|
||||||
|
{
|
||||||
|
UseBaselineTests = true;
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void VerbatimBlock()
|
public void VerbatimBlock()
|
||||||
{
|
{
|
||||||
ParseBlockTest("@{ foo(); }",
|
ParseBlockTest("@{ foo(); }");
|
||||||
new StatementBlock(
|
|
||||||
Factory.CodeTransition(),
|
|
||||||
Factory.MetaCode("{")
|
|
||||||
.Accepts(AcceptedCharactersInternal.None),
|
|
||||||
Factory.Code(" foo(); ")
|
|
||||||
.AsStatement()
|
|
||||||
.AutoCompleteWith(autoCompleteString: null),
|
|
||||||
Factory.MetaCode("}")
|
|
||||||
.Accepts(AcceptedCharactersInternal.None)
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void InnerImplicitExpressionWithOnlySingleAtOutputsZeroLengthCodeSpan()
|
public void InnerImplicitExpressionWithOnlySingleAtOutputsZeroLengthCodeSpan()
|
||||||
{
|
{
|
||||||
ParseBlockTest("{@}",
|
ParseBlockTest("{@}");
|
||||||
new StatementBlock(
|
|
||||||
Factory.MetaCode("{").Accepts(AcceptedCharactersInternal.None),
|
|
||||||
Factory.EmptyCSharp()
|
|
||||||
.AsStatement()
|
|
||||||
.AutoCompleteWith(autoCompleteString: null),
|
|
||||||
new ExpressionBlock(
|
|
||||||
Factory.CodeTransition(),
|
|
||||||
Factory.EmptyCSharp().AsImplicitExpression(KeywordSet, acceptTrailingDot: true).Accepts(AcceptedCharactersInternal.NonWhiteSpace)
|
|
||||||
),
|
|
||||||
Factory.EmptyCSharp().AsStatement(),
|
|
||||||
Factory.MetaCode("}").Accepts(AcceptedCharactersInternal.None)),
|
|
||||||
designTime: true,
|
|
||||||
expectedErrors: new[]
|
|
||||||
{
|
|
||||||
RazorDiagnosticFactory.CreateParsing_UnexpectedCharacterAtStartOfCodeBlock(
|
|
||||||
new SourceSpan(new SourceLocation(2, 0, 2), contentLength: 1),
|
|
||||||
"}")
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void InnerImplicitExpressionDoesNotAcceptDotAfterAt()
|
public void InnerImplicitExpressionDoesNotAcceptDotAfterAt()
|
||||||
{
|
{
|
||||||
ParseBlockTest("{@.}",
|
ParseBlockTest("{@.}");
|
||||||
new StatementBlock(
|
|
||||||
Factory.MetaCode("{").Accepts(AcceptedCharactersInternal.None),
|
|
||||||
Factory.EmptyCSharp()
|
|
||||||
.AsStatement()
|
|
||||||
.AutoCompleteWith(autoCompleteString: null),
|
|
||||||
new ExpressionBlock(
|
|
||||||
Factory.CodeTransition(),
|
|
||||||
Factory.EmptyCSharp().AsImplicitExpression(KeywordSet, acceptTrailingDot: true).Accepts(AcceptedCharactersInternal.NonWhiteSpace)
|
|
||||||
),
|
|
||||||
Factory.Code(".").AsStatement(),
|
|
||||||
Factory.MetaCode("}").Accepts(AcceptedCharactersInternal.None)),
|
|
||||||
designTime: true,
|
|
||||||
expectedErrors: new[]
|
|
||||||
{
|
|
||||||
RazorDiagnosticFactory.CreateParsing_UnexpectedCharacterAtStartOfCodeBlock(
|
|
||||||
new SourceSpan(new SourceLocation(2, 0, 2), contentLength: 1),
|
|
||||||
".")
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void InnerImplicitExpressionWithOnlySingleAtAcceptsSingleSpaceOrNewlineAtDesignTime()
|
public void InnerImplicitExpressionWithOnlySingleAtAcceptsSingleSpaceOrNewlineAtDesignTime()
|
||||||
{
|
{
|
||||||
ParseBlockTest("{" + Environment.NewLine
|
ParseBlockTest("{" + Environment.NewLine + " @" + Environment.NewLine + "}", designTime: true);
|
||||||
+ " @" + Environment.NewLine
|
|
||||||
+ "}",
|
|
||||||
new StatementBlock(
|
|
||||||
Factory.MetaCode("{").Accepts(AcceptedCharactersInternal.None),
|
|
||||||
Factory.Code(Environment.NewLine + " ")
|
|
||||||
.AsStatement()
|
|
||||||
.AutoCompleteWith(autoCompleteString: null),
|
|
||||||
new ExpressionBlock(
|
|
||||||
Factory.CodeTransition(),
|
|
||||||
Factory.EmptyCSharp().AsImplicitExpression(KeywordSet, acceptTrailingDot: true).Accepts(AcceptedCharactersInternal.NonWhiteSpace)
|
|
||||||
),
|
|
||||||
Factory.Code(Environment.NewLine).AsStatement(),
|
|
||||||
Factory.MetaCode("}").Accepts(AcceptedCharactersInternal.None)),
|
|
||||||
/* designTimeParser */ true,
|
|
||||||
RazorDiagnosticFactory.CreateParsing_UnexpectedWhiteSpaceAtStartOfCodeBlock(
|
|
||||||
new SourceSpan(new SourceLocation(6 + Environment.NewLine.Length, 1, 5), Environment.NewLine.Length)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void InnerImplicitExpressionDoesNotAcceptTrailingNewlineInRunTimeMode()
|
public void InnerImplicitExpressionDoesNotAcceptTrailingNewlineInRunTimeMode()
|
||||||
{
|
{
|
||||||
ParseBlockTest("{@foo." + Environment.NewLine
|
ParseBlockTest("{@foo." + Environment.NewLine + "}");
|
||||||
+ "}",
|
|
||||||
new StatementBlock(
|
|
||||||
Factory.MetaCode("{").Accepts(AcceptedCharactersInternal.None),
|
|
||||||
Factory.EmptyCSharp()
|
|
||||||
.AsStatement()
|
|
||||||
.AutoCompleteWith(autoCompleteString: null),
|
|
||||||
new ExpressionBlock(
|
|
||||||
Factory.CodeTransition(),
|
|
||||||
Factory.Code("foo.").AsImplicitExpression(KeywordSet, acceptTrailingDot: true).Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
|
|
||||||
Factory.Code(Environment.NewLine).AsStatement(),
|
|
||||||
Factory.MetaCode("}").Accepts(AcceptedCharactersInternal.None)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void InnerImplicitExpressionAcceptsTrailingNewlineInDesignTimeMode()
|
public void InnerImplicitExpressionAcceptsTrailingNewlineInDesignTimeMode()
|
||||||
{
|
{
|
||||||
ParseBlockTest("{@foo." + Environment.NewLine
|
ParseBlockTest("{@foo." + Environment.NewLine + "}", designTime: true);
|
||||||
+ "}",
|
|
||||||
new StatementBlock(
|
|
||||||
Factory.MetaCode("{").Accepts(AcceptedCharactersInternal.None),
|
|
||||||
Factory.EmptyCSharp()
|
|
||||||
.AsStatement()
|
|
||||||
.AutoCompleteWith(autoCompleteString: null),
|
|
||||||
new ExpressionBlock(
|
|
||||||
Factory.CodeTransition(),
|
|
||||||
Factory.Code("foo.").AsImplicitExpression(KeywordSet, acceptTrailingDot: true).Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
|
|
||||||
Factory.Code(Environment.NewLine).AsStatement(),
|
|
||||||
Factory.MetaCode("}").Accepts(AcceptedCharactersInternal.None)),
|
|
||||||
designTime: true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
Statement block - NullParentChunkGenerator - 9 - (0:0,0)
|
||||||
|
MetaCode span - NullSpanChunkGenerator - SpanEditHandler;Accepts:None - { - (0:0,0)
|
||||||
|
Code span - StatementChunkGenerator - AutoCompleteEditHandler;Accepts:Any,AutoComplete:[<null>];AtEOL - - (1:0,1)
|
||||||
|
Expression block - ExpressionChunkGenerator - 5 - (1:0,1)
|
||||||
|
Transition span - NullSpanChunkGenerator - SpanEditHandler;Accepts:None - @ - (1:0,1)
|
||||||
|
Code span - ExpressionChunkGenerator - ImplicitExpressionEditHandler;Accepts:NonWhiteSpace;ImplicitExpression[ATD];K14 - foo. - (2:0,2)
|
||||||
|
Code span - StatementChunkGenerator - SpanEditHandler;Accepts:Any - LF - (6:0,6)
|
||||||
|
MetaCode span - NullSpanChunkGenerator - SpanEditHandler;Accepts:None - } - (8:1,0)
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
(1,3): Error RZ1005: "." is not valid at the start of a code block. Only identifiers, keywords, comments, "(" and "{" are valid.
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
Statement block - NullParentChunkGenerator - 4 - (0:0,0)
|
||||||
|
MetaCode span - NullSpanChunkGenerator - SpanEditHandler;Accepts:None - { - (0:0,0)
|
||||||
|
Code span - StatementChunkGenerator - AutoCompleteEditHandler;Accepts:Any,AutoComplete:[<null>];AtEOL - - (1:0,1)
|
||||||
|
Expression block - ExpressionChunkGenerator - 1 - (1:0,1)
|
||||||
|
Transition span - NullSpanChunkGenerator - SpanEditHandler;Accepts:None - @ - (1:0,1)
|
||||||
|
Code span - ExpressionChunkGenerator - ImplicitExpressionEditHandler;Accepts:NonWhiteSpace;ImplicitExpression[ATD];K14 - - (2:0,2)
|
||||||
|
Code span - StatementChunkGenerator - SpanEditHandler;Accepts:Any - . - (2:0,2)
|
||||||
|
MetaCode span - NullSpanChunkGenerator - SpanEditHandler;Accepts:None - } - (3:0,3)
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
Statement block - NullParentChunkGenerator - 9 - (0:0,0)
|
||||||
|
MetaCode span - NullSpanChunkGenerator - SpanEditHandler;Accepts:None - { - (0:0,0)
|
||||||
|
Code span - StatementChunkGenerator - AutoCompleteEditHandler;Accepts:Any,AutoComplete:[<null>];AtEOL - - (1:0,1)
|
||||||
|
Expression block - ExpressionChunkGenerator - 5 - (1:0,1)
|
||||||
|
Transition span - NullSpanChunkGenerator - SpanEditHandler;Accepts:None - @ - (1:0,1)
|
||||||
|
Code span - ExpressionChunkGenerator - ImplicitExpressionEditHandler;Accepts:NonWhiteSpace;ImplicitExpression[ATD];K14 - foo. - (2:0,2)
|
||||||
|
Code span - StatementChunkGenerator - SpanEditHandler;Accepts:Any - LF - (6:0,6)
|
||||||
|
MetaCode span - NullSpanChunkGenerator - SpanEditHandler;Accepts:None - } - (8:1,0)
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
(2,6): Error RZ1003: A space or line break was encountered after the "@" character. Only valid identifiers, keywords, comments, "(" and "{" are valid at the start of a code block and they must occur immediately following "@" with no space in between.
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
Statement block - NullParentChunkGenerator - 11 - (0:0,0)
|
||||||
|
MetaCode span - NullSpanChunkGenerator - SpanEditHandler;Accepts:None - { - (0:0,0)
|
||||||
|
Code span - StatementChunkGenerator - AutoCompleteEditHandler;Accepts:Any,AutoComplete:[<null>];AtEOL - LF - (1:0,1)
|
||||||
|
Expression block - ExpressionChunkGenerator - 1 - (7:1,4)
|
||||||
|
Transition span - NullSpanChunkGenerator - SpanEditHandler;Accepts:None - @ - (7:1,4)
|
||||||
|
Code span - ExpressionChunkGenerator - ImplicitExpressionEditHandler;Accepts:NonWhiteSpace;ImplicitExpression[ATD];K14 - - (8:1,5)
|
||||||
|
Code span - StatementChunkGenerator - SpanEditHandler;Accepts:Any - LF - (8:1,5)
|
||||||
|
MetaCode span - NullSpanChunkGenerator - SpanEditHandler;Accepts:None - } - (10:2,0)
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
(1,3): Error RZ1005: "}" is not valid at the start of a code block. Only identifiers, keywords, comments, "(" and "{" are valid.
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
Statement block - NullParentChunkGenerator - 3 - (0:0,0)
|
||||||
|
MetaCode span - NullSpanChunkGenerator - SpanEditHandler;Accepts:None - { - (0:0,0)
|
||||||
|
Code span - StatementChunkGenerator - AutoCompleteEditHandler;Accepts:Any,AutoComplete:[<null>];AtEOL - - (1:0,1)
|
||||||
|
Expression block - ExpressionChunkGenerator - 1 - (1:0,1)
|
||||||
|
Transition span - NullSpanChunkGenerator - SpanEditHandler;Accepts:None - @ - (1:0,1)
|
||||||
|
Code span - ExpressionChunkGenerator - ImplicitExpressionEditHandler;Accepts:NonWhiteSpace;ImplicitExpression[ATD];K14 - - (2:0,2)
|
||||||
|
Code span - StatementChunkGenerator - SpanEditHandler;Accepts:Any - - (2:0,2)
|
||||||
|
MetaCode span - NullSpanChunkGenerator - SpanEditHandler;Accepts:None - } - (2:0,2)
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
Statement block - NullParentChunkGenerator - 11 - (0:0,0)
|
||||||
|
Transition span - NullSpanChunkGenerator - SpanEditHandler;Accepts:None - @ - (0:0,0)
|
||||||
|
MetaCode span - NullSpanChunkGenerator - SpanEditHandler;Accepts:None - { - (1:0,1)
|
||||||
|
Code span - StatementChunkGenerator - AutoCompleteEditHandler;Accepts:Any,AutoComplete:[<null>];AtEOL - foo(); - (2:0,2)
|
||||||
|
MetaCode span - NullSpanChunkGenerator - SpanEditHandler;Accepts:None - } - (10:0,10)
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
// 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.Reflection;
|
||||||
|
using Xunit;
|
||||||
|
using Xunit.Sdk;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
|
{
|
||||||
|
public class IntializeTestFileAttribute : BeforeAfterTestAttribute
|
||||||
|
{
|
||||||
|
public override void Before(MethodInfo methodUnderTest)
|
||||||
|
{
|
||||||
|
if (typeof(ParserTestBase).GetTypeInfo().IsAssignableFrom(methodUnderTest.DeclaringType.GetTypeInfo()))
|
||||||
|
{
|
||||||
|
var typeName = methodUnderTest.DeclaringType.Name;
|
||||||
|
ParserTestBase.FileName = $"TestFiles/ParserTests/{typeName}/{methodUnderTest.Name}";
|
||||||
|
ParserTestBase.IsTheory = false;
|
||||||
|
|
||||||
|
if (methodUnderTest.GetCustomAttributes(typeof(TheoryAttribute), inherit: false).Length > 0)
|
||||||
|
{
|
||||||
|
ParserTestBase.IsTheory = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void After(MethodInfo methodUnderTest)
|
||||||
|
{
|
||||||
|
if (typeof(ParserTestBase).GetTypeInfo().IsAssignableFrom(methodUnderTest.DeclaringType.GetTypeInfo()))
|
||||||
|
{
|
||||||
|
ParserTestBase.FileName = null;
|
||||||
|
ParserTestBase.IsTheory = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,20 +4,36 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
#if NET46
|
||||||
|
using System.Runtime.Remoting;
|
||||||
|
using System.Runtime.Remoting.Messaging;
|
||||||
|
#else
|
||||||
|
using System.Threading;
|
||||||
|
#endif
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
using Xunit.Sdk;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
{
|
{
|
||||||
|
[IntializeTestFile]
|
||||||
public abstract class ParserTestBase
|
public abstract class ParserTestBase
|
||||||
{
|
{
|
||||||
|
#if !NET46
|
||||||
|
private static readonly AsyncLocal<string> _fileName = new AsyncLocal<string>();
|
||||||
|
private static readonly AsyncLocal<bool> _isTheory = new AsyncLocal<bool>();
|
||||||
|
#endif
|
||||||
|
|
||||||
internal static Block IgnoreOutput = new IgnoreOutputBlock();
|
internal static Block IgnoreOutput = new IgnoreOutputBlock();
|
||||||
|
|
||||||
internal ParserTestBase()
|
internal ParserTestBase()
|
||||||
{
|
{
|
||||||
Factory = CreateSpanFactory();
|
Factory = CreateSpanFactory();
|
||||||
BlockFactory = CreateBlockFactory();
|
BlockFactory = CreateBlockFactory();
|
||||||
|
TestProjectRoot = TestProject.GetProjectDirectory(GetType());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -30,6 +46,105 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
|
|
||||||
internal BlockFactory BlockFactory { get; private set; }
|
internal BlockFactory BlockFactory { get; private set; }
|
||||||
|
|
||||||
|
#if GENERATE_BASELINES
|
||||||
|
protected bool GenerateBaselines { get; set; } = true;
|
||||||
|
#else
|
||||||
|
protected bool GenerateBaselines { get; set; } = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
protected string TestProjectRoot { get; }
|
||||||
|
|
||||||
|
protected bool UseBaselineTests { get; set; }
|
||||||
|
|
||||||
|
// Used by the test framework to set the 'base' name for test files.
|
||||||
|
public static string FileName
|
||||||
|
{
|
||||||
|
#if NET46
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var handle = (ObjectHandle)CallContext.LogicalGetData("ParserTestBase_FileName");
|
||||||
|
return (string)handle.Unwrap();
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
CallContext.LogicalSetData("ParserTestBase_FileName", new ObjectHandle(value));
|
||||||
|
}
|
||||||
|
#elif NETCOREAPP2_2
|
||||||
|
get { return _fileName.Value; }
|
||||||
|
set { _fileName.Value = value; }
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsTheory
|
||||||
|
{
|
||||||
|
#if NET46
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var handle = (ObjectHandle)CallContext.LogicalGetData("ParserTestBase_IsTheory");
|
||||||
|
return (bool)handle.Unwrap();
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
CallContext.LogicalSetData("ParserTestBase_IsTheory", new ObjectHandle(value));
|
||||||
|
}
|
||||||
|
#elif NETCOREAPP2_2
|
||||||
|
get { return _isTheory.Value; }
|
||||||
|
set { _isTheory.Value = value; }
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void AssertSyntaxTreeNodeMatchesBaseline(RazorSyntaxTree syntaxTree)
|
||||||
|
{
|
||||||
|
if (FileName == null)
|
||||||
|
{
|
||||||
|
var message = $"{nameof(AssertSyntaxTreeNodeMatchesBaseline)} should only be called from a parser test ({nameof(FileName)} is null).";
|
||||||
|
throw new InvalidOperationException(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
var baselineFileName = Path.ChangeExtension(FileName, ".syntaxtree.txt");
|
||||||
|
var baselineDiagnosticsFileName = Path.ChangeExtension(FileName, ".diagnostics.txt");
|
||||||
|
|
||||||
|
var root = syntaxTree.Root;
|
||||||
|
var diagnostics = syntaxTree.Diagnostics;
|
||||||
|
if (GenerateBaselines)
|
||||||
|
{
|
||||||
|
var baselineFullPath = Path.Combine(TestProjectRoot, baselineFileName);
|
||||||
|
File.WriteAllText(baselineFullPath, SyntaxTreeNodeSerializer.Serialize(root));
|
||||||
|
|
||||||
|
var baselineDiagnosticsFullPath = Path.Combine(TestProjectRoot, baselineDiagnosticsFileName);
|
||||||
|
var lines = diagnostics.Select(RazorDiagnosticSerializer.Serialize).ToArray();
|
||||||
|
if (lines.Any())
|
||||||
|
{
|
||||||
|
File.WriteAllLines(baselineDiagnosticsFullPath, lines);
|
||||||
|
}
|
||||||
|
else if (File.Exists(baselineDiagnosticsFullPath))
|
||||||
|
{
|
||||||
|
File.Delete(baselineDiagnosticsFullPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var stFile = TestFile.Create(baselineFileName, GetType().GetTypeInfo().Assembly);
|
||||||
|
if (!stFile.Exists())
|
||||||
|
{
|
||||||
|
throw new XunitException($"The resource {baselineFileName} was not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var baseline = stFile.ReadAllText().Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
SyntaxTreeNodeVerifier.Verify(root, baseline);
|
||||||
|
|
||||||
|
var baselineDiagnostics = string.Empty;
|
||||||
|
var diagnosticsFile = TestFile.Create(baselineDiagnosticsFileName, GetType().GetTypeInfo().Assembly);
|
||||||
|
if (diagnosticsFile.Exists())
|
||||||
|
{
|
||||||
|
baselineDiagnostics = diagnosticsFile.ReadAllText();
|
||||||
|
}
|
||||||
|
|
||||||
|
var actualDiagnostics = string.Concat(diagnostics.Select(d => RazorDiagnosticSerializer.Serialize(d) + "\r\n"));
|
||||||
|
Assert.Equal(baselineDiagnostics, actualDiagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
internal RazorSyntaxTree ParseBlock(string document, bool designTime)
|
internal RazorSyntaxTree ParseBlock(string document, bool designTime)
|
||||||
{
|
{
|
||||||
return ParseBlock(RazorLanguageVersion.Latest, document, designTime);
|
return ParseBlock(RazorLanguageVersion.Latest, document, designTime);
|
||||||
|
|
@ -128,7 +243,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
{
|
{
|
||||||
directives = directives ?? Array.Empty<DirectiveDescriptor>();
|
directives = directives ?? Array.Empty<DirectiveDescriptor>();
|
||||||
|
|
||||||
var source = TestRazorSourceDocument.Create(document, filePath: null);
|
var source = TestRazorSourceDocument.Create(document, filePath: null, normalizeNewLines: UseBaselineTests);
|
||||||
var options = CreateParserOptions(version, directives, designTime);
|
var options = CreateParserOptions(version, directives, designTime);
|
||||||
var context = new ParserContext(source, options);
|
var context = new ParserContext(source, options);
|
||||||
|
|
||||||
|
|
@ -222,6 +337,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
{
|
{
|
||||||
var result = ParseBlock(version, document, directives, designTime);
|
var result = ParseBlock(version, document, directives, designTime);
|
||||||
|
|
||||||
|
if (UseBaselineTests && !IsTheory)
|
||||||
|
{
|
||||||
|
AssertSyntaxTreeNodeMatchesBaseline(result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (FixupSpans)
|
if (FixupSpans)
|
||||||
{
|
{
|
||||||
SpancestryCorrector.Correct(expected);
|
SpancestryCorrector.Correct(expected);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
// 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.IO;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
|
{
|
||||||
|
public static class SyntaxTreeNodeSerializer
|
||||||
|
{
|
||||||
|
internal static string Serialize(SyntaxTreeNode node)
|
||||||
|
{
|
||||||
|
using (var writer = new StringWriter())
|
||||||
|
{
|
||||||
|
var walker = new Walker(writer);
|
||||||
|
walker.Visit(node);
|
||||||
|
|
||||||
|
return writer.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Walker : SyntaxTreeNodeWalker
|
||||||
|
{
|
||||||
|
private readonly SyntaxTreeNodeWriter _visitor;
|
||||||
|
private readonly TextWriter _writer;
|
||||||
|
|
||||||
|
public Walker(TextWriter writer)
|
||||||
|
{
|
||||||
|
_visitor = new SyntaxTreeNodeWriter(writer);
|
||||||
|
_writer = writer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextWriter Writer { get; }
|
||||||
|
|
||||||
|
public override void Visit(SyntaxTreeNode node)
|
||||||
|
{
|
||||||
|
_visitor.Visit(node);
|
||||||
|
_writer.WriteLine();
|
||||||
|
|
||||||
|
if (node is Block block)
|
||||||
|
{
|
||||||
|
_visitor.Depth++;
|
||||||
|
base.VisitDefault(block);
|
||||||
|
_visitor.Depth--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,276 @@
|
||||||
|
// 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 Xunit;
|
||||||
|
using Xunit.Sdk;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
|
{
|
||||||
|
public static class SyntaxTreeNodeVerifier
|
||||||
|
{
|
||||||
|
internal static void Verify(SyntaxTreeNode node, string[] baseline)
|
||||||
|
{
|
||||||
|
var walker = new Walker(baseline);
|
||||||
|
walker.Visit(node);
|
||||||
|
walker.AssertReachedEndOfBaseline();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Walker : SyntaxTreeNodeWalker
|
||||||
|
{
|
||||||
|
private readonly string[] _baseline;
|
||||||
|
private readonly SyntaxTreeNodeWriter _visitor;
|
||||||
|
private readonly StringWriter _writer;
|
||||||
|
|
||||||
|
private int _index;
|
||||||
|
|
||||||
|
public Walker(string[] baseline)
|
||||||
|
{
|
||||||
|
_writer = new StringWriter();
|
||||||
|
|
||||||
|
_visitor = new SyntaxTreeNodeWriter(_writer);
|
||||||
|
_baseline = baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextWriter Writer { get; }
|
||||||
|
|
||||||
|
public override void Visit(SyntaxTreeNode node)
|
||||||
|
{
|
||||||
|
var expected = _index < _baseline.Length ? _baseline[_index++] : null;
|
||||||
|
|
||||||
|
// Write the node as text for comparison
|
||||||
|
_writer.GetStringBuilder().Clear();
|
||||||
|
_visitor.Visit(node);
|
||||||
|
var actual = _writer.GetStringBuilder().ToString();
|
||||||
|
|
||||||
|
AssertNodeEquals(node, Ancestors, expected, actual);
|
||||||
|
|
||||||
|
if (node is Block block)
|
||||||
|
{
|
||||||
|
_visitor.Depth++;
|
||||||
|
base.VisitDefault(block);
|
||||||
|
_visitor.Depth--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AssertReachedEndOfBaseline()
|
||||||
|
{
|
||||||
|
// Since we're walking the nodes of our generated code there's the chance that our baseline is longer.
|
||||||
|
Assert.True(_baseline.Length == _index, "Not all lines of the baseline were visited!");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AssertNodeEquals(SyntaxTreeNode node, IEnumerable<SyntaxTreeNode> ancestors, string expected, string actual)
|
||||||
|
{
|
||||||
|
if (string.Equals(expected, actual))
|
||||||
|
{
|
||||||
|
// YAY!!! everything is great.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expected == null)
|
||||||
|
{
|
||||||
|
var message = "The node is missing from baseline.";
|
||||||
|
throw new SyntaxTreeNodeBaselineException(node, Ancestors.ToArray(), expected, actual, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
int charsVerified = 0;
|
||||||
|
AssertNestingEqual(node, ancestors, expected, actual, ref charsVerified);
|
||||||
|
AssertNameEqual(node, ancestors, expected, actual, ref charsVerified);
|
||||||
|
AssertDelimiter(node, expected, actual, true, ref charsVerified);
|
||||||
|
AssertLocationEqual(node, ancestors, expected, actual, ref charsVerified);
|
||||||
|
AssertDelimiter(node, expected, actual, false, ref charsVerified);
|
||||||
|
AssertContentEqual(node, ancestors, expected, actual, ref charsVerified);
|
||||||
|
|
||||||
|
throw new InvalidOperationException("We can't figure out HOW these two things are different. This is a bug.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AssertNestingEqual(SyntaxTreeNode node, IEnumerable<SyntaxTreeNode> ancestors, string expected, string actual, ref int charsVerified)
|
||||||
|
{
|
||||||
|
var i = 0;
|
||||||
|
for (; i < expected.Length; i++)
|
||||||
|
{
|
||||||
|
if (expected[i] != ' ')
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var failed = false;
|
||||||
|
var j = 0;
|
||||||
|
for (; j < i; j++)
|
||||||
|
{
|
||||||
|
if (actual.Length <= j || actual[j] != ' ')
|
||||||
|
{
|
||||||
|
failed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actual.Length <= j + 1 || actual[j] == ' ')
|
||||||
|
{
|
||||||
|
failed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failed)
|
||||||
|
{
|
||||||
|
var message = "The node is at the wrong level of nesting. This usually means a child is missing.";
|
||||||
|
throw new SyntaxTreeNodeBaselineException(node, ancestors.ToArray(), expected, actual, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
charsVerified = j;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AssertNameEqual(SyntaxTreeNode node, IEnumerable<SyntaxTreeNode> ancestors, string expected, string actual, ref int charsVerified)
|
||||||
|
{
|
||||||
|
var expectedName = GetName(expected, charsVerified);
|
||||||
|
var actualName = GetName(actual, charsVerified);
|
||||||
|
|
||||||
|
if (!string.Equals(expectedName, actualName))
|
||||||
|
{
|
||||||
|
var message = $"Node names are not equal.";
|
||||||
|
throw new SyntaxTreeNodeBaselineException(node, ancestors.ToArray(), expected, actual, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
charsVerified += expectedName.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Either both strings need to have a delimiter next or neither should.
|
||||||
|
private void AssertDelimiter(SyntaxTreeNode node, string expected, string actual, bool required, ref int charsVerified)
|
||||||
|
{
|
||||||
|
if (charsVerified == expected.Length && required)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Baseline text is not well-formed: '{expected}'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (charsVerified == actual.Length && required)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Baseline text is not well-formed: '{actual}'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (charsVerified == expected.Length && charsVerified == actual.Length)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var expectedDelimiter = expected.IndexOf(" - ", charsVerified);
|
||||||
|
if (expectedDelimiter != charsVerified && expectedDelimiter != -1)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Baseline text is not well-formed: '{actual}'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var actualDelimiter = actual.IndexOf(" - ", charsVerified);
|
||||||
|
if (actualDelimiter != charsVerified && actualDelimiter != -1)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Baseline text is not well-formed: '{actual}'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.Equal(expectedDelimiter, actualDelimiter);
|
||||||
|
|
||||||
|
charsVerified += 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AssertLocationEqual(SyntaxTreeNode node, IEnumerable<SyntaxTreeNode> ancestors, string expected, string actual, ref int charsVerified)
|
||||||
|
{
|
||||||
|
var expectedLocation = GetLocation(expected, charsVerified);
|
||||||
|
var actualLocation = GetLocation(actual, charsVerified);
|
||||||
|
|
||||||
|
if (!string.Equals(expectedLocation, actualLocation))
|
||||||
|
{
|
||||||
|
var message = $"Locations are not equal.";
|
||||||
|
throw new SyntaxTreeNodeBaselineException(node, ancestors.ToArray(), expected, actual, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
charsVerified += expectedLocation.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AssertContentEqual(SyntaxTreeNode node, IEnumerable<SyntaxTreeNode> ancestors, string expected, string actual, ref int charsVerified)
|
||||||
|
{
|
||||||
|
var expectedContent = GetContent(expected, charsVerified);
|
||||||
|
var actualContent = GetContent(actual, charsVerified);
|
||||||
|
|
||||||
|
if (!string.Equals(expectedContent, actualContent))
|
||||||
|
{
|
||||||
|
var message = $"Contents are not equal.";
|
||||||
|
throw new SyntaxTreeNodeBaselineException(node, ancestors.ToArray(), expected, actual, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
charsVerified += expectedContent.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetName(string text, int start)
|
||||||
|
{
|
||||||
|
var delimiter = text.IndexOf(" - ", start);
|
||||||
|
if (delimiter == -1)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Baseline text is not well-formed: '{text}'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return text.Substring(start, delimiter - start);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetLocation(string text, int start)
|
||||||
|
{
|
||||||
|
var delimiter = text.IndexOf(" - ", start);
|
||||||
|
return delimiter == -1 ? text.Substring(start) : text.Substring(start, delimiter - start);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetContent(string text, int start)
|
||||||
|
{
|
||||||
|
return start == text.Length ? string.Empty : text.Substring(start);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SyntaxTreeNodeBaselineException : XunitException
|
||||||
|
{
|
||||||
|
public SyntaxTreeNodeBaselineException(SyntaxTreeNode node, SyntaxTreeNode[] ancestors, string expected, string actual, string userMessage)
|
||||||
|
: base(Format(node, ancestors, expected, actual, userMessage))
|
||||||
|
{
|
||||||
|
Node = node;
|
||||||
|
Expected = expected;
|
||||||
|
Actual = actual;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SyntaxTreeNode Node { get; }
|
||||||
|
|
||||||
|
public string Actual { get; }
|
||||||
|
|
||||||
|
public string Expected { get; }
|
||||||
|
|
||||||
|
private static string Format(SyntaxTreeNode node, SyntaxTreeNode[] ancestors, string expected, string actual, string userMessage)
|
||||||
|
{
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
builder.AppendLine(userMessage);
|
||||||
|
builder.AppendLine();
|
||||||
|
|
||||||
|
if (expected != null)
|
||||||
|
{
|
||||||
|
builder.Append("Expected: ");
|
||||||
|
builder.AppendLine(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actual != null)
|
||||||
|
{
|
||||||
|
builder.Append("Actual: ");
|
||||||
|
builder.AppendLine(actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ancestors != null)
|
||||||
|
{
|
||||||
|
builder.AppendLine();
|
||||||
|
builder.AppendLine("Path:");
|
||||||
|
|
||||||
|
foreach (var ancestor in ancestors)
|
||||||
|
{
|
||||||
|
builder.AppendLine(ancestor.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
// 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 SyntaxTreeNodeWalker : ParserVisitor
|
||||||
|
{
|
||||||
|
private readonly List<SyntaxTreeNode> _ancestors = new List<SyntaxTreeNode>();
|
||||||
|
|
||||||
|
protected IReadOnlyList<SyntaxTreeNode> Ancestors => _ancestors;
|
||||||
|
|
||||||
|
protected SyntaxTreeNode Parent => _ancestors.Count > 0 ? _ancestors[0] : null;
|
||||||
|
|
||||||
|
public override void VisitDefault(Block block)
|
||||||
|
{
|
||||||
|
var children = block.Children;
|
||||||
|
if (block.Children.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ancestors.Insert(0, block);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
for (var i = 0; i < block.Children.Count; i++)
|
||||||
|
{
|
||||||
|
var child = children[i];
|
||||||
|
Visit(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_ancestors.RemoveAt(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,244 @@
|
||||||
|
// 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.IO;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
|
{
|
||||||
|
internal class SyntaxTreeNodeWriter : ParserVisitor
|
||||||
|
{
|
||||||
|
private readonly TextWriter _writer;
|
||||||
|
|
||||||
|
public int Depth { get; set; }
|
||||||
|
|
||||||
|
public SyntaxTreeNodeWriter(TextWriter writer)
|
||||||
|
{
|
||||||
|
_writer = writer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitDefault(Block block)
|
||||||
|
{
|
||||||
|
WriteBlock(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitDefault(Span span)
|
||||||
|
{
|
||||||
|
WriteSpan(span);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitTagHelperBlock(TagHelperChunkGenerator chunkGenerator, Block block)
|
||||||
|
{
|
||||||
|
WriteBlock(block);
|
||||||
|
|
||||||
|
if (block is TagHelperBlock tagHelperBlock)
|
||||||
|
{
|
||||||
|
WriteSeparator();
|
||||||
|
Write(tagHelperBlock.TagName);
|
||||||
|
WriteSeparator();
|
||||||
|
Write(tagHelperBlock.TagMode);
|
||||||
|
|
||||||
|
foreach (var descriptor in tagHelperBlock.Binding.Descriptors)
|
||||||
|
{
|
||||||
|
WriteSeparator();
|
||||||
|
|
||||||
|
// Get the type name without the namespace.
|
||||||
|
var typeName = descriptor.Name.Substring(descriptor.Name.LastIndexOf('.') + 1);
|
||||||
|
Write(typeName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitAttributeBlock(AttributeBlockChunkGenerator chunkGenerator, Block block)
|
||||||
|
{
|
||||||
|
WriteBlock(block);
|
||||||
|
WriteSeparator();
|
||||||
|
Write(chunkGenerator.Name);
|
||||||
|
WriteSeparator();
|
||||||
|
WriteLocationTaggedString(chunkGenerator.Prefix);
|
||||||
|
WriteSeparator();
|
||||||
|
WriteLocationTaggedString(chunkGenerator.Suffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitCommentBlock(RazorCommentChunkGenerator chunkGenerator, Block block)
|
||||||
|
{
|
||||||
|
WriteBlock(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitDirectiveBlock(DirectiveChunkGenerator chunkGenerator, Block block)
|
||||||
|
{
|
||||||
|
WriteBlock(block);
|
||||||
|
WriteSeparator();
|
||||||
|
Write(chunkGenerator.Descriptor.Directive);
|
||||||
|
WriteSeparator();
|
||||||
|
Write(chunkGenerator.Descriptor.Kind);
|
||||||
|
WriteSeparator();
|
||||||
|
Write(chunkGenerator.Descriptor.Usage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitDynamicAttributeBlock(DynamicAttributeBlockChunkGenerator chunkGenerator, Block block)
|
||||||
|
{
|
||||||
|
WriteBlock(block);
|
||||||
|
WriteSeparator();
|
||||||
|
WriteLocationTaggedString(chunkGenerator.Prefix);
|
||||||
|
WriteSeparator();
|
||||||
|
WriteSourceLocation(chunkGenerator.ValueStart);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitExpressionBlock(ExpressionChunkGenerator chunkGenerator, Block block)
|
||||||
|
{
|
||||||
|
WriteBlock(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitTemplateBlock(TemplateBlockChunkGenerator chunkGenerator, Block block)
|
||||||
|
{
|
||||||
|
WriteBlock(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitMarkupSpan(MarkupChunkGenerator chunkGenerator, Span span)
|
||||||
|
{
|
||||||
|
WriteSpan(span);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitAddTagHelperSpan(AddTagHelperChunkGenerator chunkGenerator, Span span)
|
||||||
|
{
|
||||||
|
WriteSpan(span);
|
||||||
|
WriteSeparator();
|
||||||
|
Write(chunkGenerator.LookupText);
|
||||||
|
WriteSeparator();
|
||||||
|
Write(chunkGenerator.DirectiveText);
|
||||||
|
WriteSeparator();
|
||||||
|
Write(chunkGenerator.TypePattern);
|
||||||
|
WriteSeparator();
|
||||||
|
Write(chunkGenerator.AssemblyName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitExpressionSpan(ExpressionChunkGenerator chunkGenerator, Span span)
|
||||||
|
{
|
||||||
|
WriteSpan(span);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitImportSpan(AddImportChunkGenerator chunkGenerator, Span span)
|
||||||
|
{
|
||||||
|
WriteSpan(span);
|
||||||
|
WriteSeparator();
|
||||||
|
Write(chunkGenerator.Namespace);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitLiteralAttributeSpan(LiteralAttributeChunkGenerator chunkGenerator, Span span)
|
||||||
|
{
|
||||||
|
WriteSpan(span);
|
||||||
|
WriteSeparator();
|
||||||
|
WriteLocationTaggedString(chunkGenerator.Prefix);
|
||||||
|
WriteSeparator();
|
||||||
|
WriteLocationTaggedString(chunkGenerator.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitRemoveTagHelperSpan(RemoveTagHelperChunkGenerator chunkGenerator, Span span)
|
||||||
|
{
|
||||||
|
WriteSpan(span);
|
||||||
|
WriteSeparator();
|
||||||
|
Write(chunkGenerator.LookupText);
|
||||||
|
WriteSeparator();
|
||||||
|
Write(chunkGenerator.DirectiveText);
|
||||||
|
WriteSeparator();
|
||||||
|
Write(chunkGenerator.TypePattern);
|
||||||
|
WriteSeparator();
|
||||||
|
Write(chunkGenerator.AssemblyName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitTagHelperPrefixDirectiveSpan(TagHelperPrefixDirectiveChunkGenerator chunkGenerator, Span span)
|
||||||
|
{
|
||||||
|
WriteSpan(span);
|
||||||
|
WriteSeparator();
|
||||||
|
Write(chunkGenerator.Prefix);
|
||||||
|
WriteSeparator();
|
||||||
|
Write(chunkGenerator.DirectiveText);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitStatementSpan(StatementChunkGenerator chunkGenerator, Span span)
|
||||||
|
{
|
||||||
|
WriteSpan(span);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitDirectiveToken(DirectiveTokenChunkGenerator chunkGenerator, Span span)
|
||||||
|
{
|
||||||
|
WriteSpan(span);
|
||||||
|
WriteSeparator();
|
||||||
|
Write(chunkGenerator.Descriptor.Kind);
|
||||||
|
WriteSeparator();
|
||||||
|
Write(chunkGenerator.Descriptor.Name);
|
||||||
|
WriteSeparator();
|
||||||
|
Write($"Optional: {chunkGenerator.Descriptor.Optional}");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void WriteBlock(Block block)
|
||||||
|
{
|
||||||
|
WriteIndent();
|
||||||
|
Write($"{block.Type} block");
|
||||||
|
WriteSeparator();
|
||||||
|
Write(block.ChunkGenerator.GetType().Name);
|
||||||
|
WriteSeparator();
|
||||||
|
Write(block.Length);
|
||||||
|
WriteSeparator();
|
||||||
|
WriteSourceLocation(block.Start);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void WriteSpan(Span span)
|
||||||
|
{
|
||||||
|
WriteIndent();
|
||||||
|
Write($"{span.Kind} span");
|
||||||
|
WriteSeparator();
|
||||||
|
Write(span.ChunkGenerator.GetType().Name);
|
||||||
|
WriteSeparator();
|
||||||
|
Write(span.EditHandler);
|
||||||
|
WriteSeparator();
|
||||||
|
Write(span.Content);
|
||||||
|
WriteSeparator();
|
||||||
|
WriteSourceLocation(span.Start);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void WriteSourceLocation(SourceLocation location)
|
||||||
|
{
|
||||||
|
Write(location);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void WriteLocationTaggedString(LocationTagged<string> item)
|
||||||
|
{
|
||||||
|
Write(item.ToString("F", null));
|
||||||
|
}
|
||||||
|
|
||||||
|
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("\r\n", "LF");
|
||||||
|
_writer.Write(stringValue);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_writer.Write(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue