File: Language\DefaultRazorParsingPhase.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.Runtime.CompilerServices;
using System.Threading;
using Microsoft.AspNetCore.Razor.PooledObjects;
 
namespace Microsoft.AspNetCore.Razor.Language;
 
internal class DefaultRazorParsingPhase : RazorEnginePhaseBase, IRazorParsingPhase
{
    private static readonly ConditionalWeakTable<RazorSourceDocument, RazorSyntaxTree> s_importTrees = new();
 
#if !NET
    private static readonly object s_importTreesLock = new();
#endif
 
    protected override RazorCodeDocument ExecuteCore(RazorCodeDocument codeDocument, CancellationToken cancellationToken)
    {
        var options = codeDocument.ParserOptions;
        var syntaxTree = RazorSyntaxTree.Parse(codeDocument.Source, options, cancellationToken);
 
        using var importSyntaxTrees = new PooledArrayBuilder<RazorSyntaxTree>(codeDocument.Imports.Length);
 
        foreach (var import in codeDocument.Imports)
        {
            // Attempt to pull the parsed import tree from the CWT
            if (!TryGetCachedImportTree(import, options, out var tree))
            {
                // We don't have a cached version, parse the import and add it to the CWT
                tree = RazorSyntaxTree.Parse(import, options, cancellationToken);
 
#if NET
                s_importTrees.AddOrUpdate(import, tree);
#else
                // NetStandard2.0 doesn't have a nice AddOrUpdate method, so we'll use our own locking to
                // ensure the CWT is updated correctly.
                lock (s_importTreesLock)
                {
                    if (TryGetCachedImportTree(import, options, out var cachedTree))
                    {
                        // Someone else added it while we were parsing, use theirs.
                        tree = cachedTree;
                    }
                    else
                    {
                        if (cachedTree is not null)
                        {
                            // If there is a cachedTree, it must have different options. Remove it from the cache
                            s_importTrees.Remove(import);
                        }
 
                        // Add the tree we created to the cache
                        s_importTrees.Add(import, tree);
                    }
                }
#endif
            }
 
            importSyntaxTrees.Add(tree);
        }
 
        return codeDocument
            .WithSyntaxTree(syntaxTree)
            .WithImportSyntaxTrees(importSyntaxTrees.ToImmutableAndClear());
 
        static bool TryGetCachedImportTree(RazorSourceDocument import, RazorParserOptions options, [NotNullWhen(true)] out RazorSyntaxTree? tree)
            => s_importTrees.TryGetValue(import, out tree) && tree.Options.Equals(options);
    }
}