File: Language\RazorCodeDocument.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.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.InteropServices;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Microsoft.CodeAnalysis;
 
namespace Microsoft.AspNetCore.Razor.Language;
 
public sealed partial class RazorCodeDocument
{
    public RazorSourceDocument Source { get; }
    public ImmutableArray<RazorSourceDocument> Imports { get; }
    public RazorParserOptions ParserOptions { get; }
    public RazorCodeGenerationOptions CodeGenerationOptions { get; }
 
    public RazorFileKind FileKind => ParserOptions.FileKind;
 
    private readonly TagHelperCollection? _tagHelpers;
    private readonly TagHelperCollection? _referencedTagHelpers;
    // The canonical syntax tree produced by parsing and syntax-tree passes, before tag helper rewriting.
    // Established by DefaultRazorTagHelperContextDiscoveryPhase (which reads it from _tagHelperRewrittenSyntaxTree
    // on the first run). Once set, this field is stable throughout the rest of the pipeline.
    private readonly RazorSyntaxTree? _syntaxTree;
    // The working syntax tree: initially set by DefaultRazorParsingPhase (same value as _syntaxTree),
    // updated by DefaultRazorSyntaxTreePhase, and finally replaced with the tag-helper-rewritten tree
    // by DefaultRazorTagHelperRewritePhase.
    private readonly RazorSyntaxTree? _tagHelperRewrittenSyntaxTree;
    private readonly ImmutableArray<RazorSyntaxTree> _importSyntaxTrees;
    private readonly TagHelperDocumentContext? _tagHelperContext;
    private readonly DocumentIntermediateNode? _documentNode;
    private readonly RazorCSharpDocument? _csharpDocument;
    private readonly ImmutableArray<DirectiveTagHelperContribution> _directiveTagHelperContributions;
 
    private RazorCodeDocument(
        RazorSourceDocument source,
        ImmutableArray<RazorSourceDocument> imports,
        RazorParserOptions parserOptions,
        RazorCodeGenerationOptions codeGenerationOptions,
        TagHelperCollection? tagHelpers,
        TagHelperCollection? referencedTagHelpers,
        RazorSyntaxTree? syntaxTree,
        RazorSyntaxTree? tagHelperRewrittenSyntaxTree,
        ImmutableArray<RazorSyntaxTree> importSyntaxTrees,
        TagHelperDocumentContext? tagHelperContext,
        DocumentIntermediateNode? documentNode,
        RazorCSharpDocument? csharpDocument,
        ImmutableArray<DirectiveTagHelperContribution> directiveTagHelperContributions)
    {
        Source = source;
        Imports = imports.NullToEmpty();
 
        ParserOptions = parserOptions;
        CodeGenerationOptions = codeGenerationOptions;
 
        _tagHelpers = tagHelpers;
        _referencedTagHelpers = referencedTagHelpers;
        _syntaxTree = syntaxTree;
        _tagHelperRewrittenSyntaxTree = tagHelperRewrittenSyntaxTree;
        _importSyntaxTrees = importSyntaxTrees;
        _tagHelperContext = tagHelperContext;
        _documentNode = documentNode;
        _csharpDocument = csharpDocument;
        _directiveTagHelperContributions = directiveTagHelperContributions.NullToEmpty();
    }
 
    public static RazorCodeDocument Create(
        RazorSourceDocument source,
        RazorParserOptions? parserOptions = null,
        RazorCodeGenerationOptions? codeGenerationOptions = null)
        => Create(source, imports: [], parserOptions, codeGenerationOptions);
 
    public static RazorCodeDocument Create(
        RazorSourceDocument source,
        ImmutableArray<RazorSourceDocument> imports,
        RazorParserOptions? parserOptions = null,
        RazorCodeGenerationOptions? codeGenerationOptions = null)
    {
        ArgHelper.ThrowIfNull(source);
 
        return new RazorCodeDocument(
            source,
            imports,
            parserOptions ?? RazorParserOptions.Default,
            codeGenerationOptions ?? RazorCodeGenerationOptions.Default,
            tagHelpers: null,
            referencedTagHelpers: null,
            syntaxTree: null,
            tagHelperRewrittenSyntaxTree: null,
            importSyntaxTrees: default,
            tagHelperContext: null,
            documentNode: null,
            csharpDocument: null,
            directiveTagHelperContributions: default);
    }
 
    internal bool TryGetTagHelpers([NotNullWhen(true)] out TagHelperCollection? result)
    {
        result = _tagHelpers;
        return result is not null;
    }
 
    internal TagHelperCollection? GetTagHelpers()
        => _tagHelpers;
 
    internal TagHelperCollection GetRequiredTagHelpers()
        => _tagHelpers.AssumeNotNull();
 
    internal RazorCodeDocument WithTagHelpers(TagHelperCollection? value)
    {
        if (Equals(value, _tagHelpers))
        {
            return this;
        }
        return new RazorCodeDocument(Source, Imports, ParserOptions, CodeGenerationOptions, value, _referencedTagHelpers, _syntaxTree, _tagHelperRewrittenSyntaxTree, _importSyntaxTrees, _tagHelperContext, _documentNode, _csharpDocument, _directiveTagHelperContributions);
    }
 
    internal bool TryGetReferencedTagHelpers([NotNullWhen(true)] out TagHelperCollection? result)
    {
        result = _referencedTagHelpers;
        return result is not null;
    }
 
    internal TagHelperCollection? GetReferencedTagHelpers()
        => _referencedTagHelpers;
 
    internal TagHelperCollection GetRequiredReferencedTagHelpers()
        => _referencedTagHelpers.AssumeNotNull();
 
    internal RazorCodeDocument WithReferencedTagHelpers(TagHelperCollection value)
    {
        if (Equals(value, _referencedTagHelpers))
        {
            return this;
        }
        return new RazorCodeDocument(Source, Imports, ParserOptions, CodeGenerationOptions, _tagHelpers, value, _syntaxTree, _tagHelperRewrittenSyntaxTree, _importSyntaxTrees, _tagHelperContext, _documentNode, _csharpDocument, _directiveTagHelperContributions);
    }
 
    internal bool TryGetSyntaxTree([NotNullWhen(true)] out RazorSyntaxTree? result)
    {
        result = _syntaxTree;
        return result is not null;
    }
 
    internal RazorSyntaxTree? GetSyntaxTree()
        => _syntaxTree;
 
    internal RazorSyntaxTree GetRequiredSyntaxTree()
        => _syntaxTree.AssumeNotNull();
 
    internal RazorCodeDocument WithSyntaxTree(RazorSyntaxTree value)
    {
        Debug.Assert(value is not null);
        if (ReferenceEquals(value, _syntaxTree))
        {
            return this;
        }
        return new RazorCodeDocument(Source, Imports, ParserOptions, CodeGenerationOptions, _tagHelpers, _referencedTagHelpers, value, _tagHelperRewrittenSyntaxTree, _importSyntaxTrees, _tagHelperContext, _documentNode, _csharpDocument, _directiveTagHelperContributions);
    }
 
    internal bool TryGetTagHelperRewrittenSyntaxTree([NotNullWhen(true)] out RazorSyntaxTree? result)
    {
        result = _tagHelperRewrittenSyntaxTree;
        return result is not null;
    }
 
    internal RazorSyntaxTree? GetTagHelperRewrittenSyntaxTree()
        => _tagHelperRewrittenSyntaxTree;
 
    internal RazorSyntaxTree GetRequiredTagHelperRewrittenSyntaxTree()
        => _tagHelperRewrittenSyntaxTree.AssumeNotNull();
 
    internal RazorCodeDocument WithTagHelperRewrittenSyntaxTree(RazorSyntaxTree value)
    {
        Debug.Assert(value is not null);
        if (ReferenceEquals(value, _tagHelperRewrittenSyntaxTree))
        {
            return this;
        }
        return new RazorCodeDocument(Source, Imports, ParserOptions, CodeGenerationOptions, _tagHelpers, _referencedTagHelpers, _syntaxTree, value, _importSyntaxTrees, _tagHelperContext, _documentNode, _csharpDocument, _directiveTagHelperContributions);
    }
 
    internal bool TryGetImportSyntaxTrees(out ImmutableArray<RazorSyntaxTree> result)
    {
        if (!_importSyntaxTrees.IsDefault)
        {
            result = _importSyntaxTrees;
            return true;
        }
 
        result = default;
        return false;
    }
 
    internal ImmutableArray<RazorSyntaxTree> GetImportSyntaxTrees()
        => _importSyntaxTrees.IsDefault ? [] : _importSyntaxTrees;
 
    internal RazorCodeDocument WithImportSyntaxTrees(ImmutableArray<RazorSyntaxTree> value)
    {
        Debug.Assert(!value.IsDefault);
        Debug.Assert(value.IsEmpty || value.All(static t => t is not null));
 
        if (ReferenceEquals(ImmutableCollectionsMarshal.AsArray(value), ImmutableCollectionsMarshal.AsArray(_importSyntaxTrees)))
        {
            return this;
        }
        return new RazorCodeDocument(Source, Imports, ParserOptions, CodeGenerationOptions, _tagHelpers, _referencedTagHelpers, _syntaxTree, _tagHelperRewrittenSyntaxTree, value, _tagHelperContext, _documentNode, _csharpDocument, _directiveTagHelperContributions);
    }
 
    internal bool TryGetTagHelperContext([NotNullWhen(true)] out TagHelperDocumentContext? result)
    {
        result = _tagHelperContext;
        return result is not null;
    }
 
    internal TagHelperDocumentContext? GetTagHelperContext()
        => _tagHelperContext;
 
    internal TagHelperDocumentContext GetRequiredTagHelperContext()
        => _tagHelperContext.AssumeNotNull();
 
    internal RazorCodeDocument WithTagHelperContext(TagHelperDocumentContext value)
    {
        Debug.Assert(value is not null);
 
        if (ReferenceEquals(value, _tagHelperContext))
        {
            return this;
        }
        return new RazorCodeDocument(Source, Imports, ParserOptions, CodeGenerationOptions, _tagHelpers, _referencedTagHelpers, _syntaxTree, _tagHelperRewrittenSyntaxTree, _importSyntaxTrees, value, _documentNode, _csharpDocument, _directiveTagHelperContributions);
    }
 
    internal bool TryGetDocumentNode([NotNullWhen(true)] out DocumentIntermediateNode? result)
    {
        result = _documentNode;
        return result is not null;
    }
 
    internal DocumentIntermediateNode? GetDocumentNode()
        => _documentNode;
 
    internal DocumentIntermediateNode GetRequiredDocumentNode()
        => _documentNode.AssumeNotNull();
 
    internal RazorCodeDocument WithDocumentNode(DocumentIntermediateNode value)
    {
        Debug.Assert(value is not null);
        if (ReferenceEquals(value, _documentNode))
        {
            return this;
        }
        return new RazorCodeDocument(Source, Imports, ParserOptions, CodeGenerationOptions, _tagHelpers, _referencedTagHelpers, _syntaxTree, _tagHelperRewrittenSyntaxTree, _importSyntaxTrees, _tagHelperContext, value, _csharpDocument, _directiveTagHelperContributions);
    }
 
    internal bool TryGetCSharpDocument([NotNullWhen(true)] out RazorCSharpDocument? result)
    {
        result = _csharpDocument;
        return result is not null;
    }
 
    internal RazorCSharpDocument? GetCSharpDocument()
        => _csharpDocument;
 
    internal RazorCSharpDocument GetRequiredCSharpDocument()
        => _csharpDocument.AssumeNotNull();
 
    internal RazorCodeDocument WithCSharpDocument(RazorCSharpDocument value)
    {
        Debug.Assert(value is not null);
        if (ReferenceEquals(value, _csharpDocument))
        {
            return this;
        }
        return new RazorCodeDocument(Source, Imports, ParserOptions, CodeGenerationOptions, _tagHelpers, _referencedTagHelpers, _syntaxTree, _tagHelperRewrittenSyntaxTree, _importSyntaxTrees, _tagHelperContext, _documentNode, value, _directiveTagHelperContributions);
    }
 
    internal ImmutableArray<DirectiveTagHelperContribution> GetDirectiveTagHelperContributions()
        => _directiveTagHelperContributions;
 
    internal RazorCodeDocument WithDirectiveTagHelperContributions(ImmutableArray<DirectiveTagHelperContribution> value)
    {
        if (value == _directiveTagHelperContributions)
        {
            return this;
        }
 
        return new RazorCodeDocument(Source, Imports, ParserOptions, CodeGenerationOptions, _tagHelpers, _referencedTagHelpers, _syntaxTree, _tagHelperRewrittenSyntaxTree, _importSyntaxTrees, _tagHelperContext, _documentNode, _csharpDocument, value);
    }
 
    // In general documents will have a relative path (relative to the project root).
    // We can only really compute a nice namespace when we know a relative path.
    //
    // However all kinds of thing are possible in tools. We shouldn't barf here if the document isn't set up correctly.
    internal bool TryGetNamespace(
        bool fallbackToRootNamespace,
        bool considerImports,
        [NotNullWhen(true)] out string? @namespace,
        out SourceSpan? namespaceSpan)
    {
        if (NamespaceComputer.TryComputeNamespace(this, fallbackToRootNamespace, considerImports, out @namespace, out namespaceSpan))
        {
            VerifyNamespace(this, fallbackToRootNamespace, considerImports, @namespace);
            return true;
        }
 
        return false;
 
        [Conditional("DEBUG")]
        static void VerifyNamespace(RazorCodeDocument codeDocument, bool fallbackToRootNamespace, bool considerImports, string? @namespace)
        {
            // In debug mode, even if we're cached, lets take the hit to run this again and make sure the cached value is correct.
            // This is to help us find issues with caching logic during development.
            var validateResult = NamespaceComputer.TryComputeNamespace(
                codeDocument, fallbackToRootNamespace, considerImports, out var validateNamespace, out _);
 
            Debug.Assert(validateResult, "We couldn't compute the namespace, but have a cached value, so something has gone wrong");
            Debug.Assert(validateNamespace == @namespace, $"We cached a namespace of {@namespace} but calculated that it should be {validateNamespace}");
        }
    }
}