File: Language\Legacy\WhiteSpaceRewriter.cs
Web Access
Project: src\src\Razor\src\Compiler\Microsoft.CodeAnalysis.Razor.Compiler\src\Microsoft.CodeAnalysis.Razor.Compiler.csproj (Microsoft.CodeAnalysis.Razor.Compiler)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using Microsoft.AspNetCore.Razor.Language.Syntax;
 
namespace Microsoft.AspNetCore.Razor.Language.Legacy;
 
internal sealed class WhitespaceRewriter(CancellationToken cancellationToken = default) : SyntaxRewriter
{
    private readonly CancellationToken _cancellationToken = cancellationToken;
 
    [return: NotNullIfNotNull(nameof(node))]
    public override SyntaxNode? Visit(SyntaxNode? node)
    {
        _cancellationToken.ThrowIfCancellationRequested();
 
        if (node == null)
        {
            return base.Visit(node);
        }
 
        var children = node.ChildNodesAndTokens();
 
        for (var i = 0; i < children.Count; i++)
        {
            var child = children[i];
 
            if (child.AsNode() is CSharpCodeBlockSyntax codeBlock &&
                TryRewriteWhitespace(codeBlock, out var rewritten, out var whitespaceLiteral))
            {
                // Replace the existing code block with the whitespace literal
                // followed by the rewritten code block (with the code whitespace removed).
                node = node.ReplaceNode(codeBlock, [whitespaceLiteral, rewritten]);
 
                // Since we replaced node, its children are different. Update our collection.
                children = node.ChildNodesAndTokens();
            }
        }
 
        return base.Visit(node);
    }
 
    private static bool TryRewriteWhitespace(
        CSharpCodeBlockSyntax codeBlock,
        [NotNullWhen(true)] out CSharpCodeBlockSyntax? rewritten,
        [NotNullWhen(true)] out SyntaxNode? whitespaceLiteral)
    {
        // Rewrite any whitespace represented as code at the start of a line preceding an expression block.
        // We want it to be rendered as Markup.
 
        if (codeBlock.Children is [CSharpStatementLiteralSyntax literal, CSharpExplicitExpressionSyntax or CSharpImplicitExpressionSyntax, ..])
        {
            var containsNonWhitespace = literal.DescendantTokens().Any(static t => !string.IsNullOrWhiteSpace(t.Content));
 
            if (!containsNonWhitespace)
            {
                // Literal node is all whitespace. Can rewrite.
                whitespaceLiteral = SyntaxFactory.MarkupTextLiteral(literal.LiteralTokens);
                rewritten = codeBlock.ReplaceNode(literal, newNode: null);
                return true;
            }
        }
 
        rewritten = null;
        whitespaceLiteral = null;
 
        return false;
    }
}