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; break;
case CSharpCodeIntermediateNode codeIntermediateNode: case CSharpCodeIntermediateNode codeIntermediateNode:
// A C# code node could be empty. We can't remove them, but we can skip them.
shouldRemoveNode = false; shouldRemoveNode = false;
shouldContinueIteration = shouldContinueIteration = ComponentDocumentClassifierPass.IsBuildRenderTreeBaseCall(codeIntermediateNode);
IsEmpty(codeIntermediateNode) ||
ComponentDocumentClassifierPass.IsBuildRenderTreeBaseCall(codeIntermediateNode);
break; break;
default: default:
@ -113,18 +110,5 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
Forwards, Forwards,
Backwards 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(); NextToken();
// Unless changed, the block is a statement block var precedingWhitespace = ReadWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
AcceptWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
builder.Add(OutputTokensAsStatementLiteral());
// We are usually called when the other parser sees a transition '@'. Look for it. // We are usually called when the other parser sees a transition '@'. Look for it.
SyntaxToken transitionToken = null; SyntaxToken transitionToken = null;
@ -157,20 +155,33 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
if (At(SyntaxKind.LeftBrace)) 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 statementBody = ParseStatementBody();
var statement = SyntaxFactory.CSharpStatement(transition, statementBody); var statement = SyntaxFactory.CSharpStatement(transition, statementBody);
builder.Add(statement); builder.Add(statement);
} }
else if (At(SyntaxKind.LeftParenthesis)) 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 expressionBody = ParseExplicitExpressionBody();
var expression = SyntaxFactory.CSharpExplicitExpression(transition, expressionBody); var expression = SyntaxFactory.CSharpExplicitExpression(transition, expressionBody);
builder.Add(expression); builder.Add(expression);
} }
else if (At(SyntaxKind.Identifier)) 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( if (string.Equals(
CurrentToken.Content, CurrentToken.Content,
SyntaxConstants.CSharp.HelperKeyword, SyntaxConstants.CSharp.HelperKeyword,
@ -189,9 +200,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
} }
else if (At(SyntaxKind.Keyword)) else if (At(SyntaxKind.Keyword))
{ {
if (!TryParseDirective(builder, transition, CurrentToken.Content) && if (!TryParseDirective(builder, precedingWhitespace, transition, CurrentToken.Content) &&
!TryParseKeyword(builder, transition)) !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. // Not a directive or a special keyword. Just parse as an implicit expression.
var implicitExpressionBody = ParseImplicitExpressionBody(); var implicitExpressionBody = ParseImplicitExpressionBody();
var implicitExpression = SyntaxFactory.CSharpImplicitExpression(transition, implicitExpressionBody); var implicitExpression = SyntaxFactory.CSharpImplicitExpression(transition, implicitExpressionBody);
@ -202,7 +218,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
} }
else else
{ {
// Invalid character // Invalid character after transition.
// Preserve the preceding whitespace in the output
Accept(precedingWhitespace);
builder.Add(OutputTokensAsStatementLiteral());
SpanContext.ChunkGenerator = new ExpressionChunkGenerator(); SpanContext.ChunkGenerator = new ExpressionChunkGenerator();
SpanContext.EditHandler = new ImplicitExpressionEditHandler( SpanContext.EditHandler = new ImplicitExpressionEditHandler(
Language.TokenizeString, Language.TokenizeString,
@ -616,7 +636,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
ParseCodeBlock(builder, block); ParseCodeBlock(builder, block);
break; break;
case SyntaxKind.Keyword: case SyntaxKind.Keyword:
if (!TryParseKeyword(builder, transition: null)) if (!TryParseKeyword(builder, whitespace: null, transition: null))
{ {
ParseStandardStatement(builder); 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)) 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; SpanContext.ChunkGenerator = SpanChunkGenerator.Null;
handler(builder, transition); handler(builder, transition);
return true; 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); var result = CSharpTokenizer.GetTokenKeyword(CurrentToken);
Debug.Assert(CurrentToken.Kind == SyntaxKind.Keyword && result.HasValue); Debug.Assert(CurrentToken.Kind == SyntaxKind.Keyword && result.HasValue);
if (_keywordParserMap.TryGetValue(result.Value, out var handler)) 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); handler(builder, transition);
return true; return true;
} }