File: Workspace\Solution\DocumentInfo.cs
Web Access
Project: src\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis;
 
/// <summary>
/// A class that represents all the arguments necessary to create a new document instance.
/// </summary>
[DebuggerDisplay("{GetDebuggerDisplay() , nq}")]
public sealed class DocumentInfo
{
    internal DocumentAttributes Attributes { get; }
 
    /// <summary>
    /// The Id of the document.
    /// </summary>
    public DocumentId Id => Attributes.Id;
 
    /// <summary>
    /// The name of the document.
    /// </summary>
    public string Name => Attributes.Name;
 
    /// <summary>
    /// The names of the logical nested folders the document is contained in.
    /// </summary>
    public IReadOnlyList<string> Folders => Attributes.Folders;
 
    /// <summary>
    /// The kind of the source code.
    /// </summary>
    public SourceCodeKind SourceCodeKind => Attributes.SourceCodeKind;
 
    /// <summary>
    /// The file path of the document.
    /// </summary>
    public string? FilePath => Attributes.FilePath;
 
    /// <summary>
    /// True if the document is a side effect of the build.
    /// </summary>
    public bool IsGenerated => Attributes.IsGenerated;
 
    /// <summary>
    /// A loader that can retrieve the document text.
    /// </summary>
    public TextLoader? TextLoader { get; }
 
    /// <summary>
    /// A <see cref="IDocumentServiceProvider"/> associated with this document
    /// </summary>
    internal IDocumentServiceProvider? DocumentServiceProvider { get; }
 
    /// <summary>
    /// Create a new instance of a <see cref="DocumentInfo"/>.
    /// </summary>
    internal DocumentInfo(DocumentAttributes attributes, TextLoader? loader, IDocumentServiceProvider? documentServiceProvider)
    {
        Attributes = attributes;
        TextLoader = loader;
        DocumentServiceProvider = documentServiceProvider;
    }
 
    /// <summary>
    /// Creates info.
    /// </summary>
    public static DocumentInfo Create(
        DocumentId id,
        string name,
        IEnumerable<string>? folders = null,
        SourceCodeKind sourceCodeKind = SourceCodeKind.Regular,
        TextLoader? loader = null,
        string? filePath = null,
        bool isGenerated = false)
    {
        return new DocumentInfo(
            new DocumentAttributes(
                id ?? throw new ArgumentNullException(nameof(id)),
                name ?? throw new ArgumentNullException(nameof(name)),
                PublicContract.ToBoxedImmutableArrayWithNonNullItems(folders, nameof(folders)),
                sourceCodeKind,
                filePath,
                isGenerated,
                designTimeOnly: false),
            loader,
            documentServiceProvider: null);
    }
 
    private DocumentInfo With(
        DocumentAttributes? attributes = null,
        Optional<TextLoader?> loader = default,
        Optional<IDocumentServiceProvider?> documentServiceProvider = default)
    {
        var newAttributes = attributes ?? Attributes;
        var newLoader = loader.HasValue ? loader.Value : TextLoader;
        var newDocumentServiceProvider = documentServiceProvider.HasValue ? documentServiceProvider.Value : DocumentServiceProvider;
 
        if (newAttributes == Attributes &&
            newLoader == TextLoader &&
            newDocumentServiceProvider == DocumentServiceProvider)
        {
            return this;
        }
 
        return new DocumentInfo(newAttributes, newLoader, newDocumentServiceProvider);
    }
 
    public DocumentInfo WithId(DocumentId id)
        => With(attributes: Attributes.With(id: id ?? throw new ArgumentNullException(nameof(id))));
 
    public DocumentInfo WithName(string name)
        => With(attributes: Attributes.With(name: name ?? throw new ArgumentNullException(nameof(name))));
 
    public DocumentInfo WithFolders(IEnumerable<string>? folders)
        => With(attributes: Attributes.With(folders: PublicContract.ToBoxedImmutableArrayWithNonNullItems(folders, nameof(folders))));
 
    public DocumentInfo WithSourceCodeKind(SourceCodeKind kind)
        => With(attributes: Attributes.With(sourceCodeKind: kind));
 
    public DocumentInfo WithFilePath(string? filePath)
        => With(attributes: Attributes.With(filePath: filePath));
 
    public DocumentInfo WithTextLoader(TextLoader? loader)
        => With(loader: loader);
 
    internal DocumentInfo WithDesignTimeOnly(bool designTimeOnly)
        => With(attributes: Attributes.With(designTimeOnly: designTimeOnly));
 
    internal DocumentInfo WithDocumentServiceProvider(IDocumentServiceProvider? provider)
        => With(documentServiceProvider: new(provider));
 
    private string GetDebuggerDisplay()
        => (FilePath == null) ? (nameof(Name) + " = " + Name) : (nameof(FilePath) + " = " + FilePath);
 
    /// <summary>
    /// type that contains information regarding this document itself but
    /// no tree information such as document info
    /// </summary>
    internal sealed class DocumentAttributes(
        DocumentId id,
        string name,
        IReadOnlyList<string> folders,
        SourceCodeKind sourceCodeKind,
        string? filePath,
        bool isGenerated,
        bool designTimeOnly)
    {
        private SingleInitNullable<Checksum> _lazyChecksum;
 
        /// <summary>
        /// The Id of the document.
        /// </summary>
        public DocumentId Id { get; } = id;
 
        /// <summary>
        /// The name of the document.
        /// </summary>
        public string Name { get; } = name;
 
        /// <summary>
        /// The names of the logical nested folders the document is contained in.
        /// </summary>
        public IReadOnlyList<string> Folders { get; } = folders;
 
        /// <summary>
        /// The kind of the source code.
        /// </summary>
        public SourceCodeKind SourceCodeKind { get; } = sourceCodeKind;
 
        /// <summary>
        /// The file path of the document.
        /// </summary>
        public string? FilePath { get; } = filePath;
 
        /// <summary>
        /// True if the document is a side effect of the build.
        /// </summary>
        public bool IsGenerated { get; } = isGenerated;
 
        /// <summary>
        /// True if the source code contained in the document is only used in design-time (e.g. for completion),
        /// but is not passed to the compiler when the containing project is built, e.g. a Razor view
        /// </summary>
        public bool DesignTimeOnly { get; } = designTimeOnly;
 
        public DocumentAttributes With(
            DocumentId? id = null,
            string? name = null,
            IReadOnlyList<string>? folders = null,
            Optional<SourceCodeKind> sourceCodeKind = default,
            Optional<string?> filePath = default,
            Optional<bool> isGenerated = default,
            Optional<bool> designTimeOnly = default)
        {
            var newId = id ?? Id;
            var newName = name ?? Name;
            var newFolders = folders ?? Folders;
            var newSourceCodeKind = sourceCodeKind.HasValue ? sourceCodeKind.Value : SourceCodeKind;
            var newFilePath = filePath.HasValue ? filePath.Value : FilePath;
            var newIsGenerated = isGenerated.HasValue ? isGenerated.Value : IsGenerated;
            var newDesignTimeOnly = designTimeOnly.HasValue ? designTimeOnly.Value : DesignTimeOnly;
 
            if (newId == Id &&
                newName == Name &&
                newFolders.SequenceEqual(Folders) &&
                newSourceCodeKind == SourceCodeKind &&
                newFilePath == FilePath &&
                newIsGenerated == IsGenerated &&
                newDesignTimeOnly == DesignTimeOnly)
            {
                return this;
            }
 
            return new DocumentAttributes(newId, newName, newFolders, newSourceCodeKind, newFilePath, newIsGenerated, newDesignTimeOnly);
        }
 
        // This is the string used to represent the FilePath property on a SyntaxTree object.
        // if the document does not yet have a file path, use the document's name instead in regular code
        // or an empty string in script code.
        public string SyntaxTreeFilePath
            => FilePath ?? (SourceCodeKind == SourceCodeKind.Regular ? Name : "");
 
        public void WriteTo(ObjectWriter writer)
        {
            Id.WriteTo(writer);
 
            writer.WriteString(Name);
            writer.WriteArray(Folders.ToImmutableArrayOrEmpty(), static (w, f) => w.WriteString(f));
            writer.WriteByte(checked((byte)SourceCodeKind));
            writer.WriteString(FilePath);
            writer.WriteBoolean(IsGenerated);
            writer.WriteBoolean(DesignTimeOnly);
        }
 
        public static DocumentAttributes ReadFrom(ObjectReader reader)
        {
            var documentId = DocumentId.ReadFrom(reader);
 
            var name = reader.ReadRequiredString();
            var folders = reader.ReadArray(static r => r.ReadRequiredString());
            var sourceCodeKind = (SourceCodeKind)reader.ReadByte();
            var filePath = reader.ReadString();
            var isGenerated = reader.ReadBoolean();
            var designTimeOnly = reader.ReadBoolean();
 
            return new DocumentAttributes(documentId, name, folders, sourceCodeKind, filePath, isGenerated, designTimeOnly);
        }
 
        public Checksum Checksum
            => _lazyChecksum.Initialize(static @this => Checksum.Create(@this, static (@this, writer) => @this.WriteTo(writer)), this);
    }
}