File: Language\RazorProjectEngineTestBase.cs
Web Access
Project: src\src\Razor\src\Shared\Microsoft.AspNetCore.Razor.Test.Common\Microsoft.AspNetCore.Razor.Test.Common.csproj (Microsoft.AspNetCore.Razor.Test.Common)
// 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 Microsoft.AspNetCore.Razor.Language.Intermediate;
 
namespace Microsoft.AspNetCore.Razor.Language;
 
public abstract class RazorProjectEngineTestBase
{
    private RazorProjectEngine? _projectEngine;
 
    /// <summary>
    ///  A default <see cref="RazorProjectEngine"/> instance that is configured with
    ///  <see cref="ConfigureProjectEngine(RazorProjectEngineBuilder)"/>.
    /// </summary>
    protected RazorProjectEngine ProjectEngine
    {
        get
        {
            return _projectEngine ?? InterlockedOperations.Initialize(ref _projectEngine, CreateProjectEngine());
 
            RazorProjectEngine CreateProjectEngine()
            {
                return RazorProjectEngine.Create(Configuration, RazorProjectFileSystem.Empty, ConfigureProjectEngine);
            }
        }
    }
 
    protected RazorConfiguration Configuration { get; }
 
    protected abstract RazorLanguageVersion Version { get; }
 
    protected RazorProjectEngineTestBase()
    {
        Configuration = new RazorConfiguration(Version, "test", Extensions: []);
    }
 
    /// <summary>
    ///  Override to configure the <see cref="RazorProjectEngine"/>s produced by this test class.
    /// </summary>
    ///
    /// <remarks>
    ///  This is called to configure the default <see cref="RazorProjectEngine"/> returned by <see cref="ProjectEngine"/>
    ///  and any <see cref="RazorProjectEngine"/> created by <see cref="CreateProjectEngine(Action{RazorProjectEngineBuilder})"/>.
    /// </remarks>
    protected virtual void ConfigureProjectEngine(RazorProjectEngineBuilder builder)
    {
    }
 
    /// <summary>
    ///  Override to configure the <see cref="RazorCodeDocumentProcessor"/>s produced by this test class.
    /// </summary>
    ///
    /// <remarks>
    ///  This can be used to ensure that the <see cref="RazorCodeDocument"/> is initially processed in particular way
    ///  by executing compiler phases or passes.
    /// </remarks>
    protected virtual void ConfigureCodeDocumentProcessor(RazorCodeDocumentProcessor processor)
    {
    }
 
    /// <summary>
    ///  Creates a new <see cref="RazorCodeDocumentProcessor"/> for the given <see cref="RazorCodeDocument"/> targeting
    ///  the default <see cref="RazorProjectEngine"/>.
    /// </summary>
    ///
    /// <remarks>
    ///  Override <see cref="ConfigureCodeDocumentProcessor(RazorCodeDocumentProcessor)"/> to configure the
    ///  <see cref="RazorCodeDocumentProcessor"/> that is returned. This can be used to ensure that
    ///  the <see cref="RazorCodeDocument"/> is initially processed in a particular way by executing compiler phases
    ///  or passes.
    /// </remarks>
    protected RazorCodeDocumentProcessor CreateCodeDocumentProcessor(RazorCodeDocument codeDocument)
        => CreateCodeDocumentProcessor(ProjectEngine, codeDocument);
 
    /// <summary>
    ///  Creates a new <see cref="RazorCodeDocumentProcessor"/> for the given <see cref="RazorCodeDocument"/> and
    ///  <see cref="RazorProjectEngine"/>.
    /// </summary>
    ///
    /// <remarks>
    ///  Override <see cref="ConfigureCodeDocumentProcessor(RazorCodeDocumentProcessor)"/> to configure the
    ///  <see cref="RazorCodeDocumentProcessor"/> that is returned. This can be used to ensure that
    ///  the <see cref="RazorCodeDocument"/> is processed in a particular way, such as executing compiler phases
    ///  or passes.
    /// </remarks>
    protected RazorCodeDocumentProcessor CreateCodeDocumentProcessor(RazorProjectEngine projectEngine, RazorCodeDocument codeDocument)
    {
        var processor = RazorCodeDocumentProcessor.From(projectEngine, codeDocument);
        ConfigureCodeDocumentProcessor(processor);
 
        return processor;
    }
 
    /// <summary>
    ///  Creates a new <see cref="RazorProjectEngine"/> configured with <see cref="ConfigureProjectEngine(RazorProjectEngineBuilder)"/>
    ///  and the delegate provided by <paramref name="configure"/>.
    /// </summary>
    /// 
    /// <param name="configure">
    ///  A delegate that provides additional configuration for the <see cref="RazorProjectEngine"/>. 
    /// </param>
    protected RazorProjectEngine CreateProjectEngine(Action<RazorProjectEngineBuilder> configure)
        => RazorProjectEngine.Create(Configuration, RazorProjectFileSystem.Empty, builder =>
        {
            // Ensure that tests are using the Roslyn tokenizer by default.
            builder.ConfigureParserOptions(builder =>
            {
                builder.UseRoslynTokenizer = true;
            });
 
            ConfigureProjectEngine(builder);
            configure.Invoke(builder);
        });
 
    /// <summary>
    ///  Finds the first descendant node of the specified type using a depth-first search.
    /// </summary>
    protected static T FindDescendant<T>(IntermediateNode root) where T : IntermediateNode
    {
        var stack = new System.Collections.Generic.Stack<IntermediateNode>();
        stack.Push(root);
        while (stack.Count > 0)
        {
            var current = stack.Pop();
            if (current is T match)
            {
                return match;
            }
 
            for (var i = current.Children.Count - 1; i >= 0; i--)
            {
                stack.Push(current.Children[i]);
            }
        }
 
        throw new InvalidOperationException("Not found: " + typeof(T).Name);
    }
}