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)
|
||||
{
|
||||
for (var i = 0; i < block.Children.Count; i++)
|
||||
{
|
||||
block.Children[i].Accept(visitor);
|
||||
}
|
||||
visitor.VisitDefault(block);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
{
|
||||
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++)
|
||||
{
|
||||
|
|
@ -13,6 +18,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
}
|
||||
}
|
||||
|
||||
public virtual void VisitDefault(Span span)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void VisitBlock(Block block)
|
||||
{
|
||||
if (block.ChunkGenerator != null)
|
||||
|
|
@ -44,35 +53,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
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)
|
||||
{
|
||||
VisitDefault(block);
|
||||
|
|
@ -88,16 +68,54 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
{
|
||||
public void Accept(ParserVisitor visitor, Span span)
|
||||
{
|
||||
visitor.VisitDefault(span);
|
||||
}
|
||||
|
||||
public void GenerateChunk(Span target, ChunkGeneratorContext context)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
{
|
||||
public class CSharpReservedWordsTest : CsHtmlCodeParserTestBase
|
||||
{
|
||||
public CSharpReservedWordsTest()
|
||||
{
|
||||
UseBaselineTests = true;
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("namespace")]
|
||||
[InlineData("class")]
|
||||
|
|
|
|||
|
|
@ -10,125 +10,45 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
{
|
||||
private const string TestExtraKeyword = "model";
|
||||
|
||||
public CSharpVerbatimBlockTest()
|
||||
{
|
||||
UseBaselineTests = true;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VerbatimBlock()
|
||||
{
|
||||
ParseBlockTest("@{ foo(); }",
|
||||
new StatementBlock(
|
||||
Factory.CodeTransition(),
|
||||
Factory.MetaCode("{")
|
||||
.Accepts(AcceptedCharactersInternal.None),
|
||||
Factory.Code(" foo(); ")
|
||||
.AsStatement()
|
||||
.AutoCompleteWith(autoCompleteString: null),
|
||||
Factory.MetaCode("}")
|
||||
.Accepts(AcceptedCharactersInternal.None)
|
||||
));
|
||||
ParseBlockTest("@{ foo(); }");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InnerImplicitExpressionWithOnlySingleAtOutputsZeroLengthCodeSpan()
|
||||
{
|
||||
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),
|
||||
"}")
|
||||
});
|
||||
ParseBlockTest("{@}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InnerImplicitExpressionDoesNotAcceptDotAfterAt()
|
||||
{
|
||||
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),
|
||||
".")
|
||||
});
|
||||
ParseBlockTest("{@.}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InnerImplicitExpressionWithOnlySingleAtAcceptsSingleSpaceOrNewlineAtDesignTime()
|
||||
{
|
||||
ParseBlockTest("{" + Environment.NewLine
|
||||
+ " @" + 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)));
|
||||
ParseBlockTest("{" + Environment.NewLine + " @" + Environment.NewLine + "}", designTime: true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InnerImplicitExpressionDoesNotAcceptTrailingNewlineInRunTimeMode()
|
||||
{
|
||||
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)));
|
||||
ParseBlockTest("{@foo." + Environment.NewLine + "}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InnerImplicitExpressionAcceptsTrailingNewlineInDesignTimeMode()
|
||||
{
|
||||
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)),
|
||||
designTime: true);
|
||||
ParseBlockTest("{@foo." + Environment.NewLine + "}", 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.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
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 Xunit;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||
{
|
||||
[IntializeTestFile]
|
||||
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 ParserTestBase()
|
||||
{
|
||||
Factory = CreateSpanFactory();
|
||||
BlockFactory = CreateBlockFactory();
|
||||
TestProjectRoot = TestProject.GetProjectDirectory(GetType());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -30,6 +46,105 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
|
||||
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)
|
||||
{
|
||||
return ParseBlock(RazorLanguageVersion.Latest, document, designTime);
|
||||
|
|
@ -128,7 +243,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
{
|
||||
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 context = new ParserContext(source, options);
|
||||
|
||||
|
|
@ -222,6 +337,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
{
|
||||
var result = ParseBlock(version, document, directives, designTime);
|
||||
|
||||
if (UseBaselineTests && !IsTheory)
|
||||
{
|
||||
AssertSyntaxTreeNodeMatchesBaseline(result);
|
||||
return;
|
||||
}
|
||||
|
||||
if (FixupSpans)
|
||||
{
|
||||
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