Correctly handle whitespace that precedes a Razor directive (dotnet/aspnetcore-tooling#117)

\n\nCommit migrated from 15556c8ff3
This commit is contained in:
Ajay Bhargav Baaskaran 2019-01-14 11:59:37 -08:00 committed by GitHub
parent 9541938327
commit d5e9a153c7
2 changed files with 42 additions and 27 deletions

View File

@ -77,11 +77,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
break;
case CSharpCodeIntermediateNode codeIntermediateNode:
// A C# code node could be empty. We can't remove them, but we can skip them.
shouldRemoveNode = false;
shouldContinueIteration =
IsEmpty(codeIntermediateNode) ||
ComponentDocumentClassifierPass.IsBuildRenderTreeBaseCall(codeIntermediateNode);
shouldContinueIteration = ComponentDocumentClassifierPass.IsBuildRenderTreeBaseCall(codeIntermediateNode);
break;
default:
@ -113,18 +110,5 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
Forwards,
Backwards
}
private static bool IsEmpty(CSharpCodeIntermediateNode node)
{
for (var i = 0; i < node.Children.Count; i++)
{
if (!(node.Children[i] is IntermediateToken token && string.IsNullOrWhiteSpace(token.Content)))
{
return false;
}
}
return true;
}
}
}

View File

@ -124,9 +124,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
NextToken();
// Unless changed, the block is a statement block
AcceptWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
builder.Add(OutputTokensAsStatementLiteral());
var precedingWhitespace = ReadWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
// We are usually called when the other parser sees a transition '@'. Look for it.
SyntaxToken transitionToken = null;
@ -157,20 +155,33 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
if (At(SyntaxKind.LeftBrace))
{
// This is a statement. We want to preserve preceding whitespace in the output.
Accept(precedingWhitespace);
builder.Add(OutputTokensAsStatementLiteral());
var statementBody = ParseStatementBody();
var statement = SyntaxFactory.CSharpStatement(transition, statementBody);
builder.Add(statement);
}
else if (At(SyntaxKind.LeftParenthesis))
{
// This is an explicit expression. We want to preserve preceding whitespace in the output.
Accept(precedingWhitespace);
builder.Add(OutputTokensAsStatementLiteral());
var expressionBody = ParseExplicitExpressionBody();
var expression = SyntaxFactory.CSharpExplicitExpression(transition, expressionBody);
builder.Add(expression);
}
else if (At(SyntaxKind.Identifier))
{
if (!TryParseDirective(builder, transition, CurrentToken.Content))
if (!TryParseDirective(builder, precedingWhitespace, transition, CurrentToken.Content))
{
// Not a directive.
// This is an implicit expression. We want to preserve preceding whitespace in the output.
Accept(precedingWhitespace);
builder.Add(OutputTokensAsStatementLiteral());
if (string.Equals(
CurrentToken.Content,
SyntaxConstants.CSharp.HelperKeyword,
@ -189,9 +200,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
}
else if (At(SyntaxKind.Keyword))
{
if (!TryParseDirective(builder, transition, CurrentToken.Content) &&
!TryParseKeyword(builder, transition))
if (!TryParseDirective(builder, precedingWhitespace, transition, CurrentToken.Content) &&
!TryParseKeyword(builder, precedingWhitespace, transition))
{
// Not a directive or keyword.
// This is an implicit expression. We want to preserve preceding whitespace in the output.
Accept(precedingWhitespace);
builder.Add(OutputTokensAsStatementLiteral());
// Not a directive or a special keyword. Just parse as an implicit expression.
var implicitExpressionBody = ParseImplicitExpressionBody();
var implicitExpression = SyntaxFactory.CSharpImplicitExpression(transition, implicitExpressionBody);
@ -202,7 +218,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
}
else
{
// Invalid character
// Invalid character after transition.
// Preserve the preceding whitespace in the output
Accept(precedingWhitespace);
builder.Add(OutputTokensAsStatementLiteral());
SpanContext.ChunkGenerator = new ExpressionChunkGenerator();
SpanContext.EditHandler = new ImplicitExpressionEditHandler(
Language.TokenizeString,
@ -616,7 +636,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
ParseCodeBlock(builder, block);
break;
case SyntaxKind.Keyword:
if (!TryParseKeyword(builder, transition: null))
if (!TryParseKeyword(builder, whitespace: null, transition: null))
{
ParseStandardStatement(builder);
}
@ -797,10 +817,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
}
}
protected bool TryParseDirective(in SyntaxListBuilder<RazorSyntaxNode> builder, CSharpTransitionSyntax transition, string directive)
protected bool TryParseDirective(in SyntaxListBuilder<RazorSyntaxNode> builder, IEnumerable<SyntaxToken> whitespace, CSharpTransitionSyntax transition, string directive)
{
if (_directiveParserMap.TryGetValue(directive, out var handler))
{
// This is a directive. We don't want to generate the preceding whitespace in the output.
Accept(whitespace);
builder.Add(OutputTokensAsEphemeralLiteral());
SpanContext.ChunkGenerator = SpanChunkGenerator.Null;
handler(builder, transition);
return true;
@ -1544,12 +1568,19 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
}
}
private bool TryParseKeyword(in SyntaxListBuilder<RazorSyntaxNode> builder, CSharpTransitionSyntax transition)
private bool TryParseKeyword(in SyntaxListBuilder<RazorSyntaxNode> builder, IEnumerable<SyntaxToken> whitespace, CSharpTransitionSyntax transition)
{
var result = CSharpTokenizer.GetTokenKeyword(CurrentToken);
Debug.Assert(CurrentToken.Kind == SyntaxKind.Keyword && result.HasValue);
if (_keywordParserMap.TryGetValue(result.Value, out var handler))
{
if (whitespace != null)
{
// This is a keyword. We want to preserve preceding whitespace in the output.
Accept(whitespace);
builder.Add(OutputTokensAsStatementLiteral());
}
handler(builder, transition);
return true;
}