Skip to content
This repository was archived by the owner on Dec 19, 2018. It is now read-only.

Commit 8372786

Browse files
committed
Rewrite WhitespaceRewriter (#2604)
* Rewrite WhitespaceRewriter * Rename CSharpExpression to CSharpExplicitExpression
1 parent 46df7f0 commit 8372786

15 files changed

+370
-196
lines changed

src/Microsoft.AspNetCore.Razor.Language/ClassifiedSpanVisitor.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public override SyntaxNode VisitRazorCommentBlock(RazorCommentBlockSyntax node)
3333
public override SyntaxNode VisitCSharpCodeBlock(CSharpCodeBlockSyntax node)
3434
{
3535
if (node.Parent is CSharpStatementBodySyntax ||
36-
node.Parent is CSharpExpressionBodySyntax ||
36+
node.Parent is CSharpExplicitExpressionBodySyntax ||
3737
node.Parent is CSharpImplicitExpressionBodySyntax ||
3838
node.Parent is RazorDirectiveBodySyntax)
3939
{
@@ -43,17 +43,17 @@ node.Parent is CSharpImplicitExpressionBodySyntax ||
4343
return WriteBlock(node, BlockKindInternal.Statement, base.VisitCSharpCodeBlock);
4444
}
4545

46-
public override SyntaxNode VisitCSharpStatement(CSharpStatement node)
46+
public override SyntaxNode VisitCSharpStatement(CSharpStatementSyntax node)
4747
{
4848
return WriteBlock(node, BlockKindInternal.Statement, base.VisitCSharpStatement);
4949
}
5050

51-
public override SyntaxNode VisitCSharpExpression(CSharpExpression node)
51+
public override SyntaxNode VisitCSharpExplicitExpression(CSharpExplicitExpressionSyntax node)
5252
{
53-
return WriteBlock(node, BlockKindInternal.Expression, base.VisitCSharpExpression);
53+
return WriteBlock(node, BlockKindInternal.Expression, base.VisitCSharpExplicitExpression);
5454
}
5555

56-
public override SyntaxNode VisitCSharpImplicitExpression(CSharpImplicitExpression node)
56+
public override SyntaxNode VisitCSharpImplicitExpression(CSharpImplicitExpressionSyntax node)
5757
{
5858
return WriteBlock(node, BlockKindInternal.Expression, base.VisitCSharpImplicitExpression);
5959
}

src/Microsoft.AspNetCore.Razor.Language/HtmlNodeOptimizationPass.cs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,28 @@ public RazorSyntaxTree Execute(RazorCodeDocument codeDocument, RazorSyntaxTree s
2222
throw new ArgumentNullException(nameof(syntaxTree));
2323
}
2424

25-
var conditionalAttributeCollapser = new ConditionalAttributeCollapser();
25+
if (syntaxTree is LegacyRazorSyntaxTree)
26+
{
27+
return LegacyExecute(codeDocument, syntaxTree);
28+
}
29+
30+
var whitespaceRewriter = new WhitespaceRewriter();
31+
var rewritten = whitespaceRewriter.Visit(syntaxTree.Root);
32+
33+
var rewrittenSyntaxTree = RazorSyntaxTree.Create(rewritten, syntaxTree.Source, syntaxTree.Diagnostics, syntaxTree.Options);
34+
return rewrittenSyntaxTree;
35+
}
36+
37+
private RazorSyntaxTree LegacyExecute(RazorCodeDocument codeDocument, RazorSyntaxTree syntaxTree)
38+
{
39+
var conditionalAttributeCollapser = new LegacyConditionalAttributeCollapser();
2640
var rewritten = conditionalAttributeCollapser.Rewrite(syntaxTree.LegacyRoot);
2741

28-
var whitespaceRewriter = new WhiteSpaceRewriter();
42+
var whitespaceRewriter = new LegacyWhitespaceRewriter();
2943
rewritten = whitespaceRewriter.Rewrite(rewritten);
3044

3145
var rewrittenSyntaxTree = RazorSyntaxTree.Create(rewritten, syntaxTree.Source, syntaxTree.Diagnostics, syntaxTree.Options);
3246
return rewrittenSyntaxTree;
3347
}
3448
}
35-
}
49+
}

src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpParser.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public CSharpCodeBlockSyntax ParseBlock()
7575
else if (At(SyntaxKind.LeftParenthesis))
7676
{
7777
var expressionBody = ParseExplicitExpressionBody();
78-
var expression = SyntaxFactory.CSharpExpression(transition, expressionBody);
78+
var expression = SyntaxFactory.CSharpExplicitExpression(transition, expressionBody);
7979
builder.Add(expression);
8080
}
8181
else if (At(SyntaxKind.Identifier))
@@ -163,7 +163,7 @@ public CSharpCodeBlockSyntax ParseBlock()
163163
}
164164
}
165165

166-
private CSharpExpressionBodySyntax ParseExplicitExpressionBody()
166+
private CSharpExplicitExpressionBodySyntax ParseExplicitExpressionBody()
167167
{
168168
var block = new Block(Resources.BlockName_ExplicitExpression, CurrentStart);
169169
Assert(SyntaxKind.LeftParenthesis);
@@ -214,7 +214,7 @@ private CSharpExpressionBodySyntax ParseExplicitExpressionBody()
214214
PutCurrentBack();
215215
}
216216

217-
return SyntaxFactory.CSharpExpressionBody(leftParen, expressionBlock, rightParen);
217+
return SyntaxFactory.CSharpExplicitExpressionBody(leftParen, expressionBlock, rightParen);
218218
}
219219
}
220220

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Diagnostics;
5+
using System.Text;
6+
7+
namespace Microsoft.AspNetCore.Razor.Language.Legacy
8+
{
9+
internal class LegacyConditionalAttributeCollapser : MarkupRewriter
10+
{
11+
protected override bool CanRewrite(Block block)
12+
{
13+
var generator = block.ChunkGenerator as AttributeBlockChunkGenerator;
14+
if (generator != null && block.Children.Count > 0)
15+
{
16+
// Perf: Avoid allocating an enumerator.
17+
for (var i = 0; i < block.Children.Count; i++)
18+
{
19+
if (!IsLiteralAttributeValue(block.Children[i]))
20+
{
21+
return false;
22+
}
23+
}
24+
25+
return true;
26+
}
27+
28+
return false;
29+
}
30+
31+
protected override SyntaxTreeNode RewriteBlock(BlockBuilder parent, Block block)
32+
{
33+
// Collect the content of this node
34+
var builder = new StringBuilder();
35+
for (var i = 0; i < block.Children.Count; i++)
36+
{
37+
var childSpan = (Span)block.Children[i];
38+
builder.Append(childSpan.Content);
39+
}
40+
41+
// Create a new span containing this content
42+
var span = new SpanBuilder(block.Children[0].Start);
43+
44+
span.EditHandler = SpanEditHandler.CreateDefault(HtmlLanguageCharacteristics.Instance.TokenizeString);
45+
Debug.Assert(block.Children.Count > 0);
46+
var start = ((Span)block.Children[0]).Start;
47+
FillSpan(span, start, builder.ToString());
48+
return span.Build();
49+
}
50+
51+
private bool IsLiteralAttributeValue(SyntaxTreeNode node)
52+
{
53+
if (node.IsBlock)
54+
{
55+
return false;
56+
}
57+
58+
var span = node as Span;
59+
Debug.Assert(span != null);
60+
61+
return span != null &&
62+
(span.ChunkGenerator is LiteralAttributeChunkGenerator ||
63+
span.ChunkGenerator is MarkupChunkGenerator ||
64+
span.ChunkGenerator == SpanChunkGenerator.Null);
65+
}
66+
}
67+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
7+
namespace Microsoft.AspNetCore.Razor.Language.Legacy
8+
{
9+
internal class LegacyWhitespaceRewriter : MarkupRewriter
10+
{
11+
protected override bool CanRewrite(Block block)
12+
{
13+
return block.Type == BlockKindInternal.Expression && Parent != null;
14+
}
15+
16+
protected override SyntaxTreeNode RewriteBlock(BlockBuilder parent, Block block)
17+
{
18+
var newBlock = new BlockBuilder(block);
19+
newBlock.Children.Clear();
20+
var ws = block.Children.FirstOrDefault() as Span;
21+
IEnumerable<SyntaxTreeNode> newNodes = block.Children;
22+
if (ws.Content.All(char.IsWhiteSpace))
23+
{
24+
// Add this node to the parent
25+
var builder = new SpanBuilder(ws);
26+
builder.ClearTokens();
27+
FillSpan(builder, ws.Start, ws.Content);
28+
parent.Children.Add(builder.Build());
29+
30+
// Remove the old whitespace node
31+
newNodes = block.Children.Skip(1);
32+
}
33+
34+
foreach (SyntaxTreeNode node in newNodes)
35+
{
36+
newBlock.Children.Add(node);
37+
}
38+
return newBlock.Build();
39+
}
40+
}
41+
}
Lines changed: 43 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,62 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4-
using System.Collections.Generic;
54
using System.Linq;
5+
using Microsoft.AspNetCore.Razor.Language.Syntax;
66

77
namespace Microsoft.AspNetCore.Razor.Language.Legacy
88
{
9-
internal class WhiteSpaceRewriter : MarkupRewriter
9+
internal class WhitespaceRewriter : SyntaxRewriter
1010
{
11-
protected override bool CanRewrite(Block block)
11+
public override SyntaxNode Visit(SyntaxNode node)
1212
{
13-
return block.Type == BlockKindInternal.Expression && Parent != null;
13+
var children = node.ChildNodes();
14+
for (var i = 0; i < children.Count; i++)
15+
{
16+
var child = children[i];
17+
if (child is CSharpCodeBlockSyntax codeBlock &&
18+
TryRewriteWhitespace(codeBlock, out var rewritten, out var whitespaceLiteral))
19+
{
20+
// Replace the existing code block with the whitespace literal
21+
// followed by the rewritten code block (with the code whitespace removed).
22+
node = node.ReplaceNode(codeBlock, new SyntaxNode[] { whitespaceLiteral, rewritten });
23+
}
24+
}
25+
26+
return base.Visit(node);
1427
}
1528

16-
protected override SyntaxTreeNode RewriteBlock(BlockBuilder parent, Block block)
29+
private bool TryRewriteWhitespace(CSharpCodeBlockSyntax codeBlock, out CSharpCodeBlockSyntax rewritten, out SyntaxNode whitespaceLiteral)
1730
{
18-
var newBlock = new BlockBuilder(block);
19-
newBlock.Children.Clear();
20-
var ws = block.Children.FirstOrDefault() as Span;
21-
IEnumerable<SyntaxTreeNode> newNodes = block.Children;
22-
if (ws.Content.All(char.IsWhiteSpace))
31+
// Rewrite any whitespace represented as code at the start of a line preceding an expression block.
32+
// We want it to be rendered as Markup.
33+
34+
rewritten = null;
35+
whitespaceLiteral = null;
36+
var children = codeBlock.ChildNodes();
37+
if (children.Count < 2)
2338
{
24-
// Add this node to the parent
25-
var builder = new SpanBuilder(ws);
26-
builder.ClearTokens();
27-
FillSpan(builder, ws.Start, ws.Content);
28-
parent.Children.Add(builder.Build());
29-
30-
// Remove the old whitespace node
31-
newNodes = block.Children.Skip(1);
39+
return false;
3240
}
3341

34-
foreach (SyntaxTreeNode node in newNodes)
42+
if (children[0] is CSharpStatementLiteralSyntax literal &&
43+
(children[1] is CSharpExplicitExpressionSyntax || children[1] is CSharpImplicitExpressionSyntax))
3544
{
36-
newBlock.Children.Add(node);
45+
var containsNonWhitespace = literal.DescendantNodes()
46+
.Where(n => n.IsToken)
47+
.Cast<SyntaxToken>()
48+
.Any(t => !string.IsNullOrWhiteSpace(t.Content));
49+
50+
if (!containsNonWhitespace)
51+
{
52+
// Literal node is all whitespace. Can rewrite.
53+
whitespaceLiteral = SyntaxFactory.MarkupTextLiteral(literal.LiteralTokens);
54+
rewritten = codeBlock.ReplaceNode(literal, newNode: null);
55+
return true;
56+
}
3757
}
38-
return newBlock.Build();
58+
59+
return false;
3960
}
4061
}
4162
}

0 commit comments

Comments
 (0)