File: Language\Intermediate\DocumentIntermediateNodeExtensions.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;
using System.Collections.Immutable;
using Microsoft.AspNetCore.Razor.PooledObjects;
 
namespace Microsoft.AspNetCore.Razor.Language.Intermediate;
 
public static class DocumentIntermediateNodeExtensions
{
    public static ClassDeclarationIntermediateNode? FindPrimaryClass(this DocumentIntermediateNode node)
    {
        ArgHelper.ThrowIfNull(node);
 
        return FindNode<ClassDeclarationIntermediateNode>(node, static n => n.IsPrimaryClass);
    }
 
    public static MethodDeclarationIntermediateNode? FindPrimaryMethod(this DocumentIntermediateNode node)
    {
        ArgHelper.ThrowIfNull(node);
 
        return FindNode<MethodDeclarationIntermediateNode>(node, static n => n.IsPrimaryMethod);
    }
 
    public static NamespaceDeclarationIntermediateNode? FindPrimaryNamespace(this DocumentIntermediateNode node)
    {
        ArgHelper.ThrowIfNull(node);
 
        return FindNode<NamespaceDeclarationIntermediateNode>(node, static n => n.IsPrimaryNamespace);
    }
 
    private static T? FindNode<T>(IntermediateNode node, Func<T, bool> predicate)
        where T : IntermediateNode
    {
        using var stack = new PooledArrayBuilder<IntermediateNode>();
        stack.Push(node);
 
        while (stack.Count > 0)
        {
            node = stack.Pop();
 
            if (node is T target && predicate(target))
            {
                return target;
            }
 
            // Push in reverse order so we process in original order.
            var children = node.Children;
            for (var i = children.Count - 1; i >= 0; i--)
            {
                stack.Push(children[i]);
            }
        }
 
        return null;
    }
 
    public static ImmutableArray<IntermediateNodeReference<DirectiveIntermediateNode>> FindDirectiveReferences(
        this DocumentIntermediateNode node, DirectiveDescriptor directive)
    {
        ArgHelper.ThrowIfNull(node);
        ArgHelper.ThrowIfNull(directive);
 
        using var results = new PooledArrayBuilder<IntermediateNodeReference<DirectiveIntermediateNode>>();
        node.CollectDirectiveReferences(directive, ref results.AsRef());
 
        return results.ToImmutableAndClear();
    }
 
    internal static void CollectDirectiveReferences(
        this DocumentIntermediateNode document,
        DirectiveDescriptor directive,
        ref PooledArrayBuilder<IntermediateNodeReference<DirectiveIntermediateNode>> references)
    {
        using var stack = new PooledArrayBuilder<(IntermediateNode node, IntermediateNode parent)>();
 
        stack.Push((document, null!));
 
        while (stack.Count > 0)
        {
            var (node, parent) = stack.Pop();
 
            if (node is DirectiveIntermediateNode directiveNode &&
                directiveNode.Directive == directive)
            {
                references.Add(new(directiveNode, parent));
            }
 
            var children = node.Children;
 
            // Push children on the stack in reverse order so they are processed in the original order.
            for (var i = children.Count - 1; i >= 0; i--)
            {
                stack.Push((children[i], node));
            }
        }
    }
 
    public static ImmutableArray<IntermediateNodeReference<TNode>> FindDescendantReferences<TNode>(this DocumentIntermediateNode document)
        where TNode : IntermediateNode
    {
        ArgHelper.ThrowIfNull(document);
 
        using var results = new PooledArrayBuilder<IntermediateNodeReference<TNode>>();
        document.CollectDescendantReferences(ref results.AsRef());
 
        return results.ToImmutableAndClear();
    }
 
    internal static void CollectDescendantReferences<TNode>(
        this DocumentIntermediateNode document,
        ref PooledArrayBuilder<IntermediateNodeReference<TNode>> references)
        where TNode : IntermediateNode
    {
        // Use a post-order traversal because references are used to replace nodes, and thus
        // change the parent nodes.
        //
        // This ensures that we always operate on the leaf nodes first.
 
        using var stack = new PooledArrayBuilder<(IntermediateNode node, IntermediateNode parent, bool visited)>();
 
        stack.Push((document, null!, false));
 
        while (stack.Count > 0)
        {
            // Pop the top of the stack and see if this node has been visited.
            var (node, parent, visited) = stack.Pop();
 
            if (visited)
            {
                // We've already visited the children, so process this node.
                if (node is TNode typedNode && parent != null)
                {
                    references.Add(new(typedNode, parent));
                }
            }
            else
            {
                // Push back on the stack and mark as visited.
                stack.Push((node, parent, true));
 
                var children = node.Children;
 
                // Push the children in reverse order so they are processed in the original order.
                for (var i = children.Count - 1; i >= 0; i--)
                {
                    stack.Push((children[i], node, false));
                }
            }
        }
    }
}