File: Workspace\Solution\SolutionInfo.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.Collections.Immutable;
using System.ComponentModel;
using Microsoft.CodeAnalysis.Diagnostics;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis;
 
/// <summary>
/// A class that represents all the arguments necessary to create a new solution instance.
/// </summary>
public sealed class SolutionInfo
{
    internal SolutionAttributes Attributes { get; }
 
    /// <summary>
    /// The unique Id of the solution.
    /// </summary>
    public SolutionId Id => Attributes.Id;
 
    /// <summary>
    /// The version of the solution.
    /// </summary>
    public VersionStamp Version => Attributes.Version;
 
    /// <summary>
    /// The path to the solution file, or null if there is no solution file.
    /// </summary>
    public string? FilePath => Attributes.FilePath;
 
    /// <summary>
    /// A list of projects initially associated with the solution.
    /// </summary>
    public IReadOnlyList<ProjectInfo> Projects { get; }
 
    /// <summary>
    /// The analyzers initially associated with this solution.
    /// </summary>
    public IReadOnlyList<AnalyzerReference> AnalyzerReferences { get; }
 
    /// <summary>
    /// Per-language analyzer config options that are used as a fallback if the option is not present in <see cref="AnalyzerConfigOptionsResult"/> produced by the compiler.
    /// Implements a top-level (but not global) virtual editorconfig file that's in scope for all source files of the solution.
    /// </summary>
    internal ImmutableDictionary<string, StructuredAnalyzerConfigOptions> FallbackAnalyzerOptions { get; }
 
    private SolutionInfo(
        SolutionAttributes attributes,
        IReadOnlyList<ProjectInfo> projects,
        IReadOnlyList<AnalyzerReference> analyzerReferences,
        ImmutableDictionary<string, StructuredAnalyzerConfigOptions> fallbackAnalyzerOptions)
    {
        Attributes = attributes;
        Projects = projects;
        AnalyzerReferences = analyzerReferences;
        FallbackAnalyzerOptions = fallbackAnalyzerOptions;
    }
 
    // 3.5.0 BACKCOMPAT OVERLOAD -- DO NOT TOUCH
    /// <summary>
    /// Create a new instance of a SolutionInfo.
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public static SolutionInfo Create(
        SolutionId id,
        VersionStamp version,
        string? filePath,
        IEnumerable<ProjectInfo>? projects)
    {
        return Create(id, version, filePath, projects, analyzerReferences: null);
    }
 
    /// <summary>
    /// Create a new instance of a SolutionInfo.
    /// </summary>
    public static SolutionInfo Create(
        SolutionId id,
        VersionStamp version,
        string? filePath = null,
        IEnumerable<ProjectInfo>? projects = null,
        IEnumerable<AnalyzerReference>? analyzerReferences = null)
        => Create(id, version, filePath, projects, analyzerReferences, ImmutableDictionary<string, StructuredAnalyzerConfigOptions>.Empty);
 
    /// <summary>
    /// Create a new instance of a SolutionInfo.
    /// </summary>
    internal static SolutionInfo Create(
        SolutionId id,
        VersionStamp version,
        string? filePath,
        IEnumerable<ProjectInfo>? projects,
        IEnumerable<AnalyzerReference>? analyzerReferences,
        ImmutableDictionary<string, StructuredAnalyzerConfigOptions> fallbackAnalyzerOptions)
    {
        return new SolutionInfo(
            new SolutionAttributes(
                id ?? throw new ArgumentNullException(nameof(id)),
                version,
                filePath,
                telemetryId: default),
            PublicContract.ToBoxedImmutableArrayWithDistinctNonNullItems(projects, nameof(projects)),
            PublicContract.ToBoxedImmutableArrayWithDistinctNonNullItems(analyzerReferences, nameof(analyzerReferences)),
            fallbackAnalyzerOptions);
    }
 
    internal SolutionInfo WithTelemetryId(Guid telemetryId)
        => new(Attributes.With(telemetryId: telemetryId), Projects, AnalyzerReferences, FallbackAnalyzerOptions);
 
    /// <summary>
    /// type that contains information regarding this solution itself but
    /// no tree information such as project info
    /// </summary>
    internal sealed class SolutionAttributes(SolutionId id, VersionStamp version, string? filePath, Guid telemetryId)
    {
        private SingleInitNullable<Checksum> _lazyChecksum;
 
        /// <summary>
        /// The unique Id of the solution.
        /// </summary>
        public SolutionId Id { get; } = id;
 
        /// <summary>
        /// The version of the solution.
        /// </summary>
        public VersionStamp Version { get; } = version;
 
        /// <summary>
        /// The path to the solution file, or null if there is no solution file.
        /// </summary>
        public string? FilePath { get; } = filePath;
 
        /// <summary>
        /// The id report during telemetry events.
        /// </summary>
        public Guid TelemetryId { get; } = telemetryId;
 
        public SolutionAttributes With(
            VersionStamp? version = null,
            Optional<string?> filePath = default,
            Optional<Guid> telemetryId = default)
        {
            var newVersion = version ?? Version;
            var newFilePath = filePath.HasValue ? filePath.Value : FilePath;
            var newTelemetryId = telemetryId.HasValue ? telemetryId.Value : TelemetryId;
 
            if (newVersion == Version &&
                newFilePath == FilePath &&
                newTelemetryId == TelemetryId)
            {
                return this;
            }
 
            return new SolutionAttributes(Id, newVersion, newFilePath, newTelemetryId);
        }
 
        public void WriteTo(ObjectWriter writer)
        {
            Id.WriteTo(writer);
 
            // TODO: figure out a way to send version info over as well.
            //       right now, version get updated automatically, so 2 can't be exactly match
            // info.Version.WriteTo(writer);
 
            writer.WriteString(FilePath);
            writer.WriteGuid(TelemetryId);
        }
 
        public static SolutionAttributes ReadFrom(ObjectReader reader)
        {
            var solutionId = SolutionId.ReadFrom(reader);
            // var version = VersionStamp.ReadFrom(reader);
            var filePath = reader.ReadString();
            var telemetryId = reader.ReadGuid();
 
            return new SolutionAttributes(solutionId, VersionStamp.Create(), filePath, telemetryId);
        }
 
        public Checksum Checksum
            => _lazyChecksum.Initialize(static @this => Checksum.Create(@this, static (@this, writer) => @this.WriteTo(writer)), this);
    }
}