File: Extensions\RazorCodeDocumentExtensions.CachedData.cs
Web Access
Project: src\src\Razor\src\Razor\src\Microsoft.CodeAnalysis.Razor.Workspaces\Microsoft.CodeAnalysis.Razor.Workspaces.csproj (Microsoft.CodeAnalysis.Razor.Workspaces)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Immutable;
using System.Runtime.CompilerServices;
using System.Threading;
using Microsoft.AspNetCore.Razor.Language.Syntax;
using Microsoft.AspNetCore.Razor.PooledObjects;
using Microsoft.CodeAnalysis.Razor.Threading;
 
namespace Microsoft.AspNetCore.Razor.Language;
 
internal static partial class RazorCodeDocumentExtensions
{
    private static readonly ConditionalWeakTable<RazorCodeDocument, CachedData> s_codeDocumentCache = new();
 
    private static CachedData GetCachedData(RazorCodeDocument codeDocument)
        => s_codeDocumentCache.GetValue(codeDocument, static doc => new CachedData(doc));
 
    private sealed class CachedData(RazorCodeDocument codeDocument)
    {
        private readonly RazorCodeDocument _codeDocument = codeDocument;
 
        private readonly SemaphoreSlim _stateLock = new(initialCount: 1);
        private ImmutableArray<ClassifiedSpan>? _classifiedSpans;
        private ImmutableArray<SourceSpan>? _tagHelperSpans;
        private RazorHtmlDocument? _htmlDocument;
 
        public ImmutableArray<ClassifiedSpan> GetOrComputeClassifiedSpans(CancellationToken cancellationToken)
        {
            if (_classifiedSpans is { } classifiedSpans)
            {
                return classifiedSpans;
            }
 
            using (_stateLock.DisposableWait(cancellationToken))
            {
                return _classifiedSpans ??= ClassifiedSpanVisitor.VisitRoot(_codeDocument.GetRequiredTagHelperRewrittenSyntaxTree());
            }
        }
 
        public ImmutableArray<SourceSpan> GetOrComputeTagHelperSpans(CancellationToken cancellationToken)
        {
            if (_tagHelperSpans is { } tagHelperSpans)
            {
                return tagHelperSpans;
            }
 
            using (_stateLock.DisposableWait(cancellationToken))
            {
                return _tagHelperSpans ??= ComputeTagHelperSpans(_codeDocument.GetRequiredTagHelperRewrittenSyntaxTree());
            }
 
            static ImmutableArray<SourceSpan> ComputeTagHelperSpans(RazorSyntaxTree syntaxTree)
            {
                using var builder = new PooledArrayBuilder<SourceSpan>();
 
                foreach (var node in syntaxTree.Root.DescendantNodes())
                {
                    if (node is not MarkupTagHelperElementSyntax tagHelperElement ||
                        tagHelperElement.TagHelperInfo is null)
                    {
                        continue;
                    }
 
                    builder.Add(tagHelperElement.GetSourceSpan(syntaxTree.Source));
                }
 
                return builder.ToImmutableAndClear();
            }
        }
 
        public RazorHtmlDocument GetOrComputeHtmlDocument(CancellationToken cancellationToken)
        {
            if (_htmlDocument is not null)
            {
                return _htmlDocument;
            }
 
            using (_stateLock.DisposableWait(cancellationToken))
            {
                return _htmlDocument ??= RazorHtmlWriter.GetHtmlDocument(_codeDocument);
            }
        }
    }
}