File: SolutionTests\SolutionTests.cs
Web Access
Project: src\src\Workspaces\CoreTest\Microsoft.CodeAnalysis.Workspaces.UnitTests.csproj (Microsoft.CodeAnalysis.Workspaces.UnitTests)
// 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.Composition;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Formatting;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.VisualBasic;
using Microsoft.VisualStudio.Threading;
using Roslyn.Test.Utilities;
using Roslyn.Test.Utilities.TestGenerators;
using Roslyn.Utilities;
using Xunit;
using static Microsoft.CodeAnalysis.UnitTests.SolutionTestHelpers;
using CS = Microsoft.CodeAnalysis.CSharp;
using VB = Microsoft.CodeAnalysis.VisualBasic;
 
namespace Microsoft.CodeAnalysis.UnitTests;
 
[UseExportProvider]
[Trait(Traits.Feature, Traits.Features.Workspace)]
public sealed class SolutionTests : TestBase
{
#nullable enable
    private static readonly string s_projectDir = Path.GetDirectoryName(typeof(SolutionTests).Assembly.Location)!;
    private static readonly MetadataReference s_mscorlib = NetFramework.mscorlib;
    private static readonly DocumentId s_unrelatedDocumentId = DocumentId.CreateNewId(ProjectId.CreateNewId());
 
    private static Workspace CreateWorkspaceWithProjectAndDocuments(string? editorConfig = null)
    {
        var projectId = ProjectId.CreateNewId();
 
        var workspace = CreateWorkspace();
 
        Assert.True(workspace.TryApplyChanges(workspace.CurrentSolution
            .AddProject(ProjectInfo.Create(projectId, VersionStamp.Default, "proj1", "proj1", LanguageNames.CSharp, Path.Combine(s_projectDir, "proj1.dll")))
            .AddDocument(DocumentId.CreateNewId(projectId), "goo.cs", SourceText.From("public class Goo { }", Encoding.UTF8, SourceHashAlgorithms.Default), filePath: Path.Combine(s_projectDir, "goo.cs"))
            .AddAdditionalDocument(DocumentId.CreateNewId(projectId), "add.txt", SourceText.From("text", Encoding.UTF8, SourceHashAlgorithms.Default))
            .AddAnalyzerConfigDocument(DocumentId.CreateNewId(projectId), "editorcfg", SourceText.From(editorConfig ?? "#empty", Encoding.UTF8, SourceHashAlgorithms.Default), filePath: Path.Combine(s_projectDir, "editorcfg"))));
 
        return workspace;
    }
 
    private static Workspace CreateWorkspaceWithProjectAndLinkedDocuments(
        string docContents, ParseOptions? parseOptions1 = null, ParseOptions? parseOptions2 = null)
    {
        parseOptions1 ??= CSharpParseOptions.Default;
        parseOptions2 ??= CSharpParseOptions.Default;
        var projectId1 = ProjectId.CreateNewId();
        var projectId2 = ProjectId.CreateNewId();
 
        var workspace = CreateWorkspace();
 
        // note: despite the additional-doc and analyzer-config doc being at the same path in multiple projects,
        // they will still be treated as unique as the workspace only has the concept of linked docs for normal
        // docs.
        Assert.True(workspace.TryApplyChanges(workspace.CurrentSolution
            .AddProject(projectId1, "proj1", "proj1.dll", LanguageNames.CSharp).WithProjectParseOptions(projectId1, parseOptions1)
            .AddDocument(DocumentId.CreateNewId(projectId1), "goo.cs", SourceText.From(docContents, Encoding.UTF8, SourceHashAlgorithms.Default), filePath: "goo.cs")
            .AddAdditionalDocument(DocumentId.CreateNewId(projectId1), "add.txt", SourceText.From("text", Encoding.UTF8, SourceHashAlgorithms.Default), filePath: "add.txt")
            .AddAnalyzerConfigDocument(DocumentId.CreateNewId(projectId1), "editorcfg", SourceText.From("config", Encoding.UTF8, SourceHashAlgorithms.Default), filePath: "/a/b")
            .AddProject(projectId2, "proj2", "proj2.dll", LanguageNames.CSharp).WithProjectParseOptions(projectId2, parseOptions2)
            .AddDocument(DocumentId.CreateNewId(projectId2), "goo.cs", SourceText.From(docContents, Encoding.UTF8, SourceHashAlgorithms.Default), filePath: "goo.cs")
            .AddAdditionalDocument(DocumentId.CreateNewId(projectId2), "add.txt", SourceText.From("text", Encoding.UTF8, SourceHashAlgorithms.Default), filePath: "add.txt")
            .AddAnalyzerConfigDocument(DocumentId.CreateNewId(projectId2), "editorcfg", SourceText.From("config", Encoding.UTF8, SourceHashAlgorithms.Default), filePath: "/a/b")));
 
        return workspace;
    }
 
    private static IEnumerable<T> EmptyEnumerable<T>()
    {
        yield break;
    }
 
    // Returns an enumerable that can only be enumerated once.
    private static IEnumerable<T> OnceEnumerable<T>(params T[] items)
        => OnceEnumerableImpl(new StrongBox<int>(), items);
 
    private static IEnumerable<T> OnceEnumerableImpl<T>(StrongBox<int> counter, T[] items)
    {
        Assert.Equal(0, counter.Value);
        counter.Value++;
 
        foreach (var item in items)
        {
            yield return item;
        }
    }
 
    [Fact]
    public void RemoveDocument_Errors()
    {
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution;
        Assert.Throws<ArgumentNullException>(() => solution.RemoveDocument(null!));
        Assert.Throws<InvalidOperationException>(() => solution.RemoveDocument(s_unrelatedDocumentId));
    }
 
    [Fact]
    public void RemoveDocuments_Errors()
    {
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution;
        Assert.Throws<ArgumentNullException>(() => solution.RemoveDocuments(default));
        Assert.Throws<InvalidOperationException>(() => solution.RemoveDocuments(ImmutableArray.Create(s_unrelatedDocumentId)));
        Assert.Throws<ArgumentNullException>(() => solution.RemoveDocuments(ImmutableArray.Create((DocumentId)null!)));
    }
 
    [Fact]
    public void RemoveAdditionalDocument_Errors()
    {
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution;
        Assert.Throws<ArgumentNullException>(() => solution.RemoveAdditionalDocument(null!));
        Assert.Throws<InvalidOperationException>(() => solution.RemoveAdditionalDocument(s_unrelatedDocumentId));
    }
 
    [Fact]
    public void RemoveAdditionalDocuments_Errors()
    {
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution;
        Assert.Throws<ArgumentNullException>(() => solution.RemoveAdditionalDocuments(default));
        Assert.Throws<InvalidOperationException>(() => solution.RemoveAdditionalDocuments(ImmutableArray.Create(s_unrelatedDocumentId)));
        Assert.Throws<ArgumentNullException>(() => solution.RemoveAdditionalDocuments(ImmutableArray.Create((DocumentId)null!)));
    }
 
    [Fact]
    public void RemoveAnalyzerConfigDocument_Errors()
    {
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution;
        Assert.Throws<ArgumentNullException>(() => solution.RemoveAnalyzerConfigDocument(null!));
        Assert.Throws<InvalidOperationException>(() => solution.RemoveAnalyzerConfigDocument(s_unrelatedDocumentId));
    }
 
    [Fact]
    public void RemoveAnalyzerConfigDocuments_Errors()
    {
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution;
        Assert.Throws<ArgumentNullException>(() => solution.RemoveAnalyzerConfigDocuments(default));
        Assert.Throws<InvalidOperationException>(() => solution.RemoveAnalyzerConfigDocuments(ImmutableArray.Create(s_unrelatedDocumentId)));
        Assert.Throws<ArgumentNullException>(() => solution.RemoveAnalyzerConfigDocuments(ImmutableArray.Create((DocumentId)null!)));
    }
 
    [Fact]
    public void WithDocumentName()
    {
        using var workspace = CreateWorkspaceWithProjectAndDocuments();
        var solution = workspace.CurrentSolution;
        var documentId = solution.Projects.Single().DocumentIds.Single();
        var name = "new name";
 
        var newSolution1 = solution.WithDocumentName(documentId, name);
        Assert.Equal(name, newSolution1.GetDocument(documentId)!.Name);
 
        var newSolution2 = newSolution1.WithDocumentName(documentId, name);
        Assert.Same(newSolution1, newSolution2);
 
        Assert.Throws<ArgumentNullException>(() => solution.WithDocumentName(documentId, name: null!));
 
        Assert.Throws<ArgumentNullException>(() => solution.WithDocumentName(null!, name));
        Assert.Throws<InvalidOperationException>(() => solution.WithDocumentName(s_unrelatedDocumentId, name));
    }
 
    [Fact]
    public void WithDocumentFolders()
    {
        using var workspace = CreateWorkspaceWithProjectAndDocuments();
        var solution = workspace.CurrentSolution;
        var documentId = solution.Projects.Single().DocumentIds.Single();
        var folders = new[] { "folder1", "folder2" };
 
        var newSolution1 = solution.WithDocumentFolders(documentId, folders);
        Assert.Equal(folders, newSolution1.GetDocument(documentId)!.Folders);
 
        var newSolution2 = newSolution1.WithDocumentFolders(documentId, folders);
        Assert.Same(newSolution2, newSolution1);
 
        // empty:
        var newSolution3 = solution.WithDocumentFolders(documentId, []);
        Assert.Equal([], newSolution3.GetDocument(documentId)!.Folders);
 
        var newSolution4 = solution.WithDocumentFolders(documentId, []);
        Assert.Same(newSolution3, newSolution4);
 
        var newSolution5 = solution.WithDocumentFolders(documentId, null);
        Assert.Same(newSolution3, newSolution5);
 
        Assert.Throws<ArgumentNullException>(() => solution.WithDocumentFolders(documentId, folders: [null!]));
 
        Assert.Throws<ArgumentNullException>(() => solution.WithDocumentFolders(null!, folders));
        Assert.Throws<InvalidOperationException>(() => solution.WithDocumentFolders(s_unrelatedDocumentId, folders));
    }
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/34837")]
    [WorkItem("https://github.com/dotnet/roslyn/issues/37125")]
    public void WithDocumentFilePath()
    {
        using var workspace = CreateWorkspaceWithProjectAndDocuments();
        var solution = workspace.CurrentSolution;
        var documentId = solution.Projects.Single().DocumentIds.Single();
        var path = "new path";
 
        var newSolution1 = solution.WithDocumentFilePath(documentId, path);
        Assert.Equal(path, newSolution1.GetRequiredDocument(documentId).FilePath);
        AssertEx.Equal([documentId], newSolution1.GetDocumentIdsWithFilePath(path));
 
        var newSolution2 = newSolution1.WithDocumentFilePath(documentId, path);
        Assert.Same(newSolution1, newSolution2);
 
        var newSolution3 = solution.WithDocumentFilePath(documentId, "");
        Assert.Equal("", newSolution3.GetRequiredDocument(documentId).FilePath);
        Assert.Empty(newSolution3.GetDocumentIdsWithFilePath(""));
 
        var newSolution4 = solution.WithDocumentFilePath(documentId, null);
        Assert.Null(newSolution4.GetRequiredDocument(documentId).FilePath);
        Assert.Empty(newSolution4.GetDocumentIdsWithFilePath(null));
 
        Assert.Throws<ArgumentNullException>(() => solution.WithDocumentFilePath(null!, path));
        Assert.Throws<InvalidOperationException>(() => solution.WithDocumentFilePath(s_unrelatedDocumentId, path));
    }
 
    [Fact]
    public void WithSourceCodeKind()
    {
        using var workspace = CreateWorkspaceWithProjectAndDocuments();
        var solution = workspace.CurrentSolution;
        var documentId = solution.Projects.Single().DocumentIds.Single();
 
        Assert.Same(solution, solution.WithDocumentSourceCodeKind(documentId, SourceCodeKind.Regular));
 
        var newSolution1 = solution.WithDocumentSourceCodeKind(documentId, SourceCodeKind.Script);
        Assert.Equal(SourceCodeKind.Script, newSolution1.GetRequiredDocument(documentId).SourceCodeKind);
 
        Assert.Throws<ArgumentOutOfRangeException>(() => solution.WithDocumentSourceCodeKind(documentId, (SourceCodeKind)(-1)));
 
        Assert.Throws<ArgumentNullException>(() => solution.WithDocumentSourceCodeKind(null!, SourceCodeKind.Script));
        Assert.Throws<InvalidOperationException>(() => solution.WithDocumentSourceCodeKind(s_unrelatedDocumentId, SourceCodeKind.Script));
    }
 
    [Fact]
    public void WithSourceCodeKind_ParseOptions()
    {
        using var workspace = CreateWorkspaceWithProjectAndDocuments();
 
        var solution = workspace.CurrentSolution;
        var documentId = solution.Projects.Single().DocumentIds.Single();
        var projectId = documentId.ProjectId;
 
        solution = solution.WithProjectParseOptions(projectId, CSharpParseOptions.Default.WithKind(SourceCodeKind.Script));
 
        var document1 = solution.GetRequiredDocument(documentId);
        Assert.Equal(SourceCodeKind.Script, document1.DocumentState.ParseOptions?.Kind);
        Assert.Equal(SourceCodeKind.Script, document1.SourceCodeKind);
 
        var document2 = document1.WithSourceCodeKind(SourceCodeKind.Regular);
        Assert.Equal(SourceCodeKind.Regular, document2.DocumentState.ParseOptions?.Kind);
        Assert.Equal(SourceCodeKind.Regular, document2.SourceCodeKind);
    }
 
    [Fact, Obsolete("Testing obsolete API")]
    public void WithSourceCodeKind_Obsolete()
    {
        using var workspace = CreateWorkspaceWithProjectAndDocuments();
        var solution = workspace.CurrentSolution;
        var documentId = solution.Projects.Single().DocumentIds.Single();
 
        var newSolution = solution.WithDocumentSourceCodeKind(documentId, SourceCodeKind.Interactive);
        Assert.Equal(SourceCodeKind.Script, newSolution.GetDocument(documentId)!.SourceCodeKind);
    }
 
    [Fact]
    public void WithDocumentSyntaxRoot()
    {
        using var workspace = CreateWorkspaceWithProjectAndDocuments();
        var solution = workspace.CurrentSolution;
        var documentId = solution.Projects.Single().DocumentIds.Single();
 
        var tree = CS.SyntaxFactory.ParseSyntaxTree("class NewClass {}");
        Assert.Equal(SourceHashAlgorithm.Sha1, tree.GetText().ChecksumAlgorithm);
 
        var root = tree.GetRoot();
 
        var newSolution1 = solution.WithDocumentSyntaxRoot(documentId, root, PreservationMode.PreserveIdentity);
        Assert.True(newSolution1.GetDocument(documentId)!.TryGetSyntaxRoot(out var actualRoot));
        Assert.Equal(root.ToString(), actualRoot!.ToString());
 
        // the actual root has a new parent SyntaxTree:
        Assert.NotSame(root, actualRoot);
 
        var newSolution2 = newSolution1.WithDocumentSyntaxRoot(documentId, actualRoot);
        Assert.Same(newSolution1, newSolution2);
 
        Assert.Throws<ArgumentOutOfRangeException>(() => solution.WithDocumentSyntaxRoot(documentId, root, (PreservationMode)(-1)));
 
        Assert.Throws<ArgumentNullException>(() => solution.WithDocumentSyntaxRoot(null!, root));
        Assert.Throws<InvalidOperationException>(() => solution.WithDocumentSyntaxRoot(s_unrelatedDocumentId, root));
    }
 
    [Fact, WorkItem(37125, "https://github.com/dotnet/roslyn/issues/41940")]
    public async Task WithDocumentSyntaxRoot_AnalyzerConfigWithoutFilePath()
    {
        var projectId = ProjectId.CreateNewId();
 
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution
                        .AddProject(projectId, "goo", "goo.dll", LanguageNames.CSharp)
                        .AddDocument(DocumentId.CreateNewId(projectId), "goo.cs", "public class Goo { }")
                        .AddAnalyzerConfigDocument(DocumentId.CreateNewId(projectId), "editorcfg", SourceText.From("config"));
 
        var project = solution.GetProject(projectId)!;
        var compilation = (await project.GetCompilationAsync())!;
        var tree = compilation.SyntaxTrees.Single();
        var provider = compilation.Options.SyntaxTreeOptionsProvider!;
        Assert.Throws<ArgumentException>(() => provider.TryGetDiagnosticValue(tree, "CA1234", CancellationToken.None, out _));
    }
 
    [Fact]
    public void WithDocumentText_SourceText()
    {
        using var workspace = CreateWorkspaceWithProjectAndDocuments();
        var solution = workspace.CurrentSolution;
        var documentId = solution.Projects.Single().DocumentIds.Single();
 
        var text = SourceText.From("new text", encoding: null, SourceHashAlgorithm.Sha1);
 
        var newSolution1 = solution.WithDocumentText(documentId, text, PreservationMode.PreserveIdentity);
        var newDocument1 = newSolution1.GetRequiredDocument(documentId);
 
        Assert.True(newDocument1.TryGetText(out var actualText));
        Assert.Same(text, actualText);
 
        var newSolution2 = newSolution1.WithDocumentText(documentId, text, PreservationMode.PreserveIdentity);
        Assert.Same(newSolution1, newSolution2);
 
        Assert.Throws<ArgumentNullException>(() => solution.WithDocumentText(documentId, (SourceText)null!, PreservationMode.PreserveIdentity));
        Assert.Throws<ArgumentOutOfRangeException>(() => solution.WithDocumentText(documentId, text, (PreservationMode)(-1)));
 
        Assert.Throws<ArgumentNullException>(() => solution.WithDocumentText((DocumentId)null!, text, PreservationMode.PreserveIdentity));
        Assert.Throws<InvalidOperationException>(() => solution.WithDocumentText(s_unrelatedDocumentId, text, PreservationMode.PreserveIdentity));
    }
 
    [Fact]
    public void WithDocumentText_TextAndVersion()
    {
        using var workspace = CreateWorkspaceWithProjectAndDocuments();
        var solution = workspace.CurrentSolution;
        var documentId = solution.Projects.Single().DocumentIds.Single();
        var textAndVersion = TextAndVersion.Create(SourceText.From("new text"), VersionStamp.Default);
 
        var newSolution1 = solution.WithDocumentText(documentId, textAndVersion, PreservationMode.PreserveIdentity);
        Assert.True(newSolution1.GetDocument(documentId)!.TryGetText(out var actualText));
        Assert.True(newSolution1.GetDocument(documentId)!.TryGetTextVersion(out var actualVersion));
        Assert.Same(textAndVersion.Text, actualText);
        Assert.Equal(textAndVersion.Version, actualVersion);
 
        var newSolution2 = newSolution1.WithDocumentText(documentId, textAndVersion, PreservationMode.PreserveIdentity);
        Assert.Same(newSolution1, newSolution2);
 
        Assert.Throws<ArgumentNullException>(() => solution.WithDocumentText(documentId, (SourceText)null!, PreservationMode.PreserveIdentity));
        Assert.Throws<ArgumentOutOfRangeException>(() => solution.WithDocumentText(documentId, textAndVersion, (PreservationMode)(-1)));
 
        Assert.Throws<ArgumentNullException>(() => solution.WithDocumentText((DocumentId)null!, textAndVersion, PreservationMode.PreserveIdentity));
        Assert.Throws<InvalidOperationException>(() => solution.WithDocumentText(s_unrelatedDocumentId, textAndVersion, PreservationMode.PreserveIdentity));
    }
 
    [Fact]
    public void WithDocumentText_MultipleDocuments()
    {
        using var workspace = CreateWorkspaceWithProjectAndDocuments();
        var solution = workspace.CurrentSolution;
        var documentId = solution.Projects.Single().DocumentIds.Single();
        var text = SourceText.From("new text");
 
        var newSolution1 = solution.WithDocumentText([documentId], text, PreservationMode.PreserveIdentity);
        Assert.True(newSolution1.GetDocument(documentId)!.TryGetText(out var actualText));
        Assert.Same(text, actualText);
 
        var newSolution2 = newSolution1.WithDocumentText([documentId], text, PreservationMode.PreserveIdentity);
        Assert.Same(newSolution1, newSolution2);
 
        // documents not in solution are skipped: https://github.com/dotnet/roslyn/issues/42029
        Assert.Same(solution, solution.WithDocumentText([null!], text));
        Assert.Same(solution, solution.WithDocumentText([s_unrelatedDocumentId], text));
 
        Assert.Throws<ArgumentNullException>(() => solution.WithDocumentText((DocumentId[])null!, text, PreservationMode.PreserveIdentity));
        Assert.Throws<ArgumentNullException>(() => solution.WithDocumentText([documentId], null!, PreservationMode.PreserveIdentity));
        Assert.Throws<ArgumentOutOfRangeException>(() => solution.WithDocumentText([documentId], text, (PreservationMode)(-1)));
    }
 
    public enum TextUpdateType
    {
        SourceText,
        TextLoader,
        TextAndVersion,
    }
 
    [Theory, CombinatorialData]
    public async Task WithDocumentText_LinkedFiles(
        PreservationMode mode,
        TextUpdateType updateType)
    {
        using var workspace = CreateWorkspaceWithProjectAndLinkedDocuments("public class Goo { }");
        var solution = workspace.CurrentSolution;
 
        var documentId1 = solution.Projects.First().DocumentIds.Single();
        var documentId2 = solution.Projects.Last().DocumentIds.Single();
 
        var document1 = solution.GetRequiredDocument(documentId1);
        var document2 = solution.GetRequiredDocument(documentId2);
 
        var text1 = await document1.GetTextAsync();
        var text2 = await document2.GetTextAsync();
        var version1 = await document1.GetTextVersionAsync();
        var version2 = await document2.GetTextVersionAsync();
        var root1 = await document1.GetRequiredSyntaxRootAsync(CancellationToken.None);
        var root2 = await document2.GetRequiredSyntaxRootAsync(CancellationToken.None);
 
        Assert.Equal(text1.ToString(), text2.ToString());
        Assert.Equal(version1, version2);
 
        // We get different red nodes, but they should be backed by the same green nodes.
        Assert.NotEqual(root1, root2);
        Assert.True(root1.IsIncrementallyIdenticalTo(root2));
 
        var text = SourceText.From("new text", encoding: null, SourceHashAlgorithm.Sha1);
        var textAndVersion = TextAndVersion.Create(text, VersionStamp.Create());
        solution = UpdateSolution(mode, updateType, solution, documentId1, text, textAndVersion);
 
        // because we only forked one doc, the text/versions should be different in this interim solution.
 
        document1 = solution.GetRequiredDocument(documentId1);
        document2 = solution.GetRequiredDocument(documentId2);
 
        text1 = await document1.GetTextAsync();
        text2 = await document2.GetTextAsync();
        version1 = await document1.GetTextVersionAsync();
        version2 = await document2.GetTextVersionAsync();
        root1 = await document1.GetRequiredSyntaxRootAsync(CancellationToken.None);
        root2 = await document2.GetRequiredSyntaxRootAsync(CancellationToken.None);
 
        Assert.NotEqual(text1.ToString(), text2.ToString());
        Assert.NotEqual(version1, version2);
 
        // We get different red and green nodes.
        Assert.NotEqual(root1, root2);
        Assert.False(root1.IsIncrementallyIdenticalTo(root2));
 
        // Now apply the change to the workspace.  This should bring the linked document in sync with the one we changed.
        workspace.TryApplyChanges(solution);
        solution = workspace.CurrentSolution;
 
        document1 = solution.GetRequiredDocument(documentId1);
        document2 = solution.GetRequiredDocument(documentId2);
 
        text1 = await document1.GetTextAsync();
        text2 = await document2.GetTextAsync();
        version1 = await document1.GetTextVersionAsync();
        version2 = await document2.GetTextVersionAsync();
        root1 = await document1.GetRequiredSyntaxRootAsync(CancellationToken.None);
        root2 = await document2.GetRequiredSyntaxRootAsync(CancellationToken.None);
 
        Assert.Equal(text1.ToString(), text2.ToString());
        Assert.Equal(version1, version2);
 
        // We get different red nodes, but they should be backed by the same green nodes.
        Assert.NotEqual(root1, root2);
        Assert.True(root1.IsIncrementallyIdenticalTo(root2));
    }
 
    private static Solution UpdateSolution(PreservationMode mode, TextUpdateType updateType, Solution solution, DocumentId documentId1, SourceText text, TextAndVersion textAndVersion)
    {
        solution = updateType switch
        {
            TextUpdateType.SourceText => solution.WithDocumentText(documentId1, text, mode),
            TextUpdateType.TextAndVersion => solution.WithDocumentText(documentId1, textAndVersion, mode),
            TextUpdateType.TextLoader => solution.WithDocumentTextLoader(documentId1, TextLoader.From(textAndVersion), mode),
            _ => throw ExceptionUtilities.UnexpectedValue(updateType)
        };
        return solution;
    }
 
    [Theory, CombinatorialData]
    public async Task WithDocumentText_LinkedFiles_PPConditionalDirective_SameParseOptions(
        PreservationMode mode,
        TextUpdateType updateType)
    {
        using var workspace = CreateWorkspaceWithProjectAndLinkedDocuments("""
            #if NETSTANDARD
            public class Goo { }
            """);
        var solution = workspace.CurrentSolution;
 
        var documentId1 = solution.Projects.First().DocumentIds.Single();
        var documentId2 = solution.Projects.Last().DocumentIds.Single();
 
        var document1 = solution.GetRequiredDocument(documentId1);
        var document2 = solution.GetRequiredDocument(documentId2);
 
        var text1 = await document1.GetTextAsync();
        var text2 = await document2.GetTextAsync();
        var version1 = await document1.GetTextVersionAsync();
        var version2 = await document2.GetTextVersionAsync();
        var root1 = await document1.GetRequiredSyntaxRootAsync(CancellationToken.None);
        var root2 = await document2.GetRequiredSyntaxRootAsync(CancellationToken.None);
 
        Assert.Equal(text1.ToString(), text2.ToString());
        Assert.Equal(version1, version2);
 
        // We can reuse trees with conditional directives if the parse options are the same.
        Assert.NotEqual(root1, root2);
        Assert.True(root1.IsIncrementallyIdenticalTo(root2));
 
        var text = SourceText.From("new text", encoding: null, SourceHashAlgorithm.Sha1);
        var textAndVersion = TextAndVersion.Create(text, VersionStamp.Create());
        solution = UpdateSolution(mode, updateType, solution, documentId1, text, textAndVersion);
 
        // because we only forked one doc, the text/versions should be different in this interim solution.
 
        document1 = solution.GetRequiredDocument(documentId1);
        document2 = solution.GetRequiredDocument(documentId2);
 
        text1 = await document1.GetTextAsync();
        text2 = await document2.GetTextAsync();
        version1 = await document1.GetTextVersionAsync();
        version2 = await document2.GetTextVersionAsync();
        root1 = await document1.GetRequiredSyntaxRootAsync(CancellationToken.None);
        root2 = await document2.GetRequiredSyntaxRootAsync(CancellationToken.None);
 
        Assert.NotEqual(text1.ToString(), text2.ToString());
        Assert.NotEqual(version1, version2);
 
        // We get different red and green nodes entirely
        Assert.NotEqual(root1, root2);
        Assert.False(root1.IsIncrementallyIdenticalTo(root2));
 
        // Now apply the change to the workspace.  This should bring the linked document in sync with the one we changed.
        workspace.TryApplyChanges(solution);
        solution = workspace.CurrentSolution;
 
        document1 = solution.GetRequiredDocument(documentId1);
        document2 = solution.GetRequiredDocument(documentId2);
 
        text1 = await document1.GetTextAsync();
        text2 = await document2.GetTextAsync();
        version1 = await document1.GetTextVersionAsync();
        version2 = await document2.GetTextVersionAsync();
        root1 = await document1.GetRequiredSyntaxRootAsync(CancellationToken.None);
        root2 = await document2.GetRequiredSyntaxRootAsync(CancellationToken.None);
 
        Assert.Equal(text1.ToString(), text2.ToString());
        Assert.Equal(version1, version2);
 
        // We can reuse trees with conditional directives if the parse options are the same.
        Assert.NotEqual(root1, root2);
        Assert.True(root1.IsIncrementallyIdenticalTo(root2));
    }
 
    [Theory, CombinatorialData]
    public async Task WithDocumentText_LinkedFiles_PPConditionalDirective_DifferentParseOptions1(
        PreservationMode mode,
        TextUpdateType updateType)
    {
        var parseOptions1 = CSharpParseOptions.Default.WithPreprocessorSymbols("UNIQUE_NAME");
        var parseOptions2 = CSharpParseOptions.Default;
 
        using var workspace = CreateWorkspaceWithProjectAndLinkedDocuments("""
            #if NETSTANDARD
            public class Goo { }
            """, parseOptions1, parseOptions2);
        var solution = workspace.CurrentSolution;
 
        var documentId1 = solution.Projects.First().DocumentIds.Single();
        var documentId2 = solution.Projects.Last().DocumentIds.Single();
 
        var document1 = solution.GetRequiredDocument(documentId1);
        var document2 = solution.GetRequiredDocument(documentId2);
 
        var text1 = await document1.GetTextAsync();
        var text2 = await document2.GetTextAsync();
        var version1 = await document1.GetTextVersionAsync();
        var version2 = await document2.GetTextVersionAsync();
        var root1 = await document1.GetRequiredSyntaxRootAsync(CancellationToken.None);
        var root2 = await document2.GetRequiredSyntaxRootAsync(CancellationToken.None);
 
        Assert.Equal(text1.ToString(), text2.ToString());
        Assert.Equal(version1, version2);
 
        // We can never reuse trees with conditional directives.
        Assert.NotEqual(root1, root2);
        Assert.False(root1.IsIncrementallyIdenticalTo(root2));
 
        Assert.Equal(parseOptions1, root1.SyntaxTree.Options);
        Assert.Equal(parseOptions2, root2.SyntaxTree.Options);
 
        // Because we removed pp directives, we'll be able to reuse after this.
        var text = SourceText.From("new text without pp directives", encoding: null, SourceHashAlgorithm.Sha1);
        var textAndVersion = TextAndVersion.Create(text, VersionStamp.Create());
        solution = UpdateSolution(mode, updateType, solution, documentId1, text, textAndVersion);
 
        // because we only forked one doc, the text/versions should be different in this interim solution.
 
        document1 = solution.GetRequiredDocument(documentId1);
        document2 = solution.GetRequiredDocument(documentId2);
 
        text1 = await document1.GetTextAsync();
        text2 = await document2.GetTextAsync();
        version1 = await document1.GetTextVersionAsync();
        version2 = await document2.GetTextVersionAsync();
        root1 = await document1.GetRequiredSyntaxRootAsync(CancellationToken.None);
        root2 = await document2.GetRequiredSyntaxRootAsync(CancellationToken.None);
 
        Assert.NotEqual(text1.ToString(), text2.ToString());
        Assert.NotEqual(version1, version2);
 
        // We get different red and green nodes entirely
        Assert.NotEqual(root1, root2);
        Assert.False(root1.IsIncrementallyIdenticalTo(root2));
 
        Assert.Equal(parseOptions1, root1.SyntaxTree.Options);
        Assert.Equal(parseOptions2, root2.SyntaxTree.Options);
 
        // Now apply the change to the workspace.  This should bring the linked document in sync with the one we changed.
        workspace.TryApplyChanges(solution);
        solution = workspace.CurrentSolution;
 
        document1 = solution.GetRequiredDocument(documentId1);
        document2 = solution.GetRequiredDocument(documentId2);
 
        text1 = await document1.GetTextAsync();
        text2 = await document2.GetTextAsync();
        version1 = await document1.GetTextVersionAsync();
        version2 = await document2.GetTextVersionAsync();
        root1 = await document1.GetRequiredSyntaxRootAsync(CancellationToken.None);
        root2 = await document2.GetRequiredSyntaxRootAsync(CancellationToken.None);
 
        Assert.Equal(text1.ToString(), text2.ToString());
        Assert.Equal(version1, version2);
 
        // We can reuse trees once they don't have conditional directives.
        Assert.NotEqual(root1, root2);
        Assert.True(root1.IsIncrementallyIdenticalTo(root2));
 
        Assert.Equal(parseOptions1, root1.SyntaxTree.Options);
        Assert.Equal(parseOptions2, root2.SyntaxTree.Options);
    }
 
    [Theory, CombinatorialData]
    public async Task WithDocumentText_LinkedFiles_PPConditionalDirective_DifferentParseOptions2(
        PreservationMode mode,
        TextUpdateType updateType)
    {
        using var workspace = CreateWorkspaceWithProjectAndLinkedDocuments("""
            #if NETSTANDARD
            public class Goo { }
            """, CSharpParseOptions.Default.WithPreprocessorSymbols("UNIQUE_NAME"), CSharpParseOptions.Default);
        var solution = workspace.CurrentSolution;
 
        var documentId1 = solution.Projects.First().DocumentIds.Single();
        var documentId2 = solution.Projects.Last().DocumentIds.Single();
 
        var document1 = solution.GetRequiredDocument(documentId1);
        var document2 = solution.GetRequiredDocument(documentId2);
 
        var text1 = await document1.GetTextAsync();
        var text2 = await document2.GetTextAsync();
        var version1 = await document1.GetTextVersionAsync();
        var version2 = await document2.GetTextVersionAsync();
        var root1 = await document1.GetRequiredSyntaxRootAsync(CancellationToken.None);
        var root2 = await document2.GetRequiredSyntaxRootAsync(CancellationToken.None);
 
        Assert.Equal(text1.ToString(), text2.ToString());
        Assert.Equal(version1, version2);
 
        // We can never reuse trees with conditional directives.
        Assert.NotEqual(root1, root2);
        Assert.False(root1.IsIncrementallyIdenticalTo(root2));
 
        // Because we still have pp directives, we'll still not be able to reuse the file.
        var text = SourceText.From("#if true", encoding: null, SourceHashAlgorithm.Sha1);
        var textAndVersion = TextAndVersion.Create(text, VersionStamp.Create());
        solution = UpdateSolution(mode, updateType, solution, documentId1, text, textAndVersion);
 
        // because we only forked one doc, the text/versions should be different in this interim solution.
 
        document1 = solution.GetRequiredDocument(documentId1);
        document2 = solution.GetRequiredDocument(documentId2);
 
        text1 = await document1.GetTextAsync();
        text2 = await document2.GetTextAsync();
        version1 = await document1.GetTextVersionAsync();
        version2 = await document2.GetTextVersionAsync();
        root1 = await document1.GetRequiredSyntaxRootAsync(CancellationToken.None);
        root2 = await document2.GetRequiredSyntaxRootAsync(CancellationToken.None);
 
        Assert.NotEqual(text1.ToString(), text2.ToString());
        Assert.NotEqual(version1, version2);
 
        // We get different red and green nodes entirely
        Assert.NotEqual(root1, root2);
        Assert.False(root1.IsIncrementallyIdenticalTo(root2));
 
        // Now apply the change to the workspace.  This should bring the linked document in sync with the one we changed.
        workspace.TryApplyChanges(solution);
        solution = workspace.CurrentSolution;
 
        document1 = solution.GetRequiredDocument(documentId1);
        document2 = solution.GetRequiredDocument(documentId2);
 
        text1 = await document1.GetTextAsync();
        text2 = await document2.GetTextAsync();
        version1 = await document1.GetTextVersionAsync();
        version2 = await document2.GetTextVersionAsync();
        root1 = await document1.GetRequiredSyntaxRootAsync(CancellationToken.None);
        root2 = await document2.GetRequiredSyntaxRootAsync(CancellationToken.None);
 
        Assert.Equal(text1.ToString(), text2.ToString());
        Assert.Equal(version1, version2);
 
        // We can never reuse trees with conditional directives.
        Assert.NotEqual(root1, root2);
        Assert.False(root1.IsIncrementallyIdenticalTo(root2));
    }
 
    [Theory, CombinatorialData]
    public async Task WithDocumentText_LinkedFiles_NonConditionalDirective(
        PreservationMode mode,
        TextUpdateType updateType)
    {
        using var workspace = CreateWorkspaceWithProjectAndLinkedDocuments("""
            #nullable enable // should not impact being able to reuse.
            public class Goo { }
            """);
        var solution = workspace.CurrentSolution;
 
        var documentId1 = solution.Projects.First().DocumentIds.Single();
        var documentId2 = solution.Projects.Last().DocumentIds.Single();
 
        var document1 = solution.GetRequiredDocument(documentId1);
        var document2 = solution.GetRequiredDocument(documentId2);
 
        var text1 = await document1.GetTextAsync();
        var text2 = await document2.GetTextAsync();
        var version1 = await document1.GetTextVersionAsync();
        var version2 = await document2.GetTextVersionAsync();
        var root1 = await document1.GetRequiredSyntaxRootAsync(CancellationToken.None);
        var root2 = await document2.GetRequiredSyntaxRootAsync(CancellationToken.None);
 
        Assert.Equal(text1.ToString(), text2.ToString());
        Assert.Equal(version1, version2);
 
        // We get different red nodes, but they should be backed by the same green nodes.
        Assert.NotEqual(root1, root2);
        Assert.True(root1.IsIncrementallyIdenticalTo(root2));
 
        var text = SourceText.From("new text", encoding: null, SourceHashAlgorithm.Sha1);
        var textAndVersion = TextAndVersion.Create(text, VersionStamp.Create());
        solution = UpdateSolution(mode, updateType, solution, documentId1, text, textAndVersion);
 
        // because we only forked one doc, the text/versions should be different in this interim solution.
 
        document1 = solution.GetRequiredDocument(documentId1);
        document2 = solution.GetRequiredDocument(documentId2);
 
        text1 = await document1.GetTextAsync();
        text2 = await document2.GetTextAsync();
        version1 = await document1.GetTextVersionAsync();
        version2 = await document2.GetTextVersionAsync();
        root1 = await document1.GetRequiredSyntaxRootAsync(CancellationToken.None);
        root2 = await document2.GetRequiredSyntaxRootAsync(CancellationToken.None);
 
        Assert.NotEqual(text1.ToString(), text2.ToString());
        Assert.NotEqual(version1, version2);
 
        // We get different red and green nodes.
        Assert.NotEqual(root1, root2);
        Assert.False(root1.IsIncrementallyIdenticalTo(root2));
 
        // Now apply the change to the workspace.  This should bring the linked document in sync with the one we changed.
        workspace.TryApplyChanges(solution);
        solution = workspace.CurrentSolution;
 
        document1 = solution.GetRequiredDocument(documentId1);
        document2 = solution.GetRequiredDocument(documentId2);
 
        text1 = await document1.GetTextAsync();
        text2 = await document2.GetTextAsync();
        version1 = await document1.GetTextVersionAsync();
        version2 = await document2.GetTextVersionAsync();
        root1 = await document1.GetRequiredSyntaxRootAsync(CancellationToken.None);
        root2 = await document2.GetRequiredSyntaxRootAsync(CancellationToken.None);
 
        Assert.Equal(text1.ToString(), text2.ToString());
        Assert.Equal(version1, version2);
 
        // We get different red nodes, but they should be backed by the same green nodes.
        Assert.NotEqual(root1, root2);
        Assert.True(root1.IsIncrementallyIdenticalTo(root2));
    }
 
    [Theory, CombinatorialData]
    public async Task WithDocumentText_LinkedFiles_DifferentLanguage(
        PreservationMode mode,
        TextUpdateType updateType)
    {
        var parseOptions1 = CSharpParseOptions.Default;
        var parseOptions2 = VisualBasicParseOptions.Default;
        var projectId1 = ProjectId.CreateNewId();
        var projectId2 = ProjectId.CreateNewId();
 
        using var workspace = CreateWorkspace();
 
        var docContents = "";
 
        // Validate strange case were we have linked files to the same file, but with different languages.
        Assert.True(workspace.TryApplyChanges(workspace.CurrentSolution
            .AddProject(projectId1, "proj1", "proj1.dll", LanguageNames.CSharp).WithProjectParseOptions(projectId1, parseOptions1)
            .AddDocument(DocumentId.CreateNewId(projectId1), "goo.cs", SourceText.From(docContents, Encoding.UTF8, SourceHashAlgorithms.Default), filePath: "goo.cs")
            .AddProject(projectId2, "proj2", "proj2.dll", LanguageNames.VisualBasic).WithProjectParseOptions(projectId2, parseOptions2)
            .AddDocument(DocumentId.CreateNewId(projectId2), "goo.cs", SourceText.From(docContents, Encoding.UTF8, SourceHashAlgorithms.Default), filePath: "goo.cs")));
 
        var solution = workspace.CurrentSolution;
 
        var documentId1 = solution.Projects.First().DocumentIds.Single();
        var documentId2 = solution.Projects.Last().DocumentIds.Single();
 
        var document1 = solution.GetRequiredDocument(documentId1);
        var document2 = solution.GetRequiredDocument(documentId2);
 
        var text1 = await document1.GetTextAsync();
        var text2 = await document2.GetTextAsync();
        var version1 = await document1.GetTextVersionAsync();
        var version2 = await document2.GetTextVersionAsync();
        var root1 = await document1.GetRequiredSyntaxRootAsync(CancellationToken.None);
        var root2 = await document2.GetRequiredSyntaxRootAsync(CancellationToken.None);
 
        Assert.Equal(text1.ToString(), text2.ToString());
        Assert.Equal(version1, version2);
 
        // These are different languages, so we should get entirely different tree structures.
        Assert.NotEqual(root1.GetType(), root2.GetType());
 
        var text = SourceText.From(" ", encoding: null, SourceHashAlgorithm.Sha1);
        var textAndVersion = TextAndVersion.Create(text, VersionStamp.Create());
        solution = UpdateSolution(mode, updateType, solution, documentId1, text, textAndVersion);
 
        // because we only forked one doc, the text/versions should be different in this interim solution.
 
        document1 = solution.GetRequiredDocument(documentId1);
        document2 = solution.GetRequiredDocument(documentId2);
 
        text1 = await document1.GetTextAsync();
        text2 = await document2.GetTextAsync();
        version1 = await document1.GetTextVersionAsync();
        version2 = await document2.GetTextVersionAsync();
        root1 = await document1.GetRequiredSyntaxRootAsync(CancellationToken.None);
        root2 = await document2.GetRequiredSyntaxRootAsync(CancellationToken.None);
 
        Assert.NotEqual(text1.ToString(), text2.ToString());
 
        // The versions will not match as we won't share the underlying text-and-tree instances between languages.
        Assert.NotEqual(version1, version2);
 
        // These are different languages, so we should get entirely different tree structures.
        Assert.NotEqual(root1.GetType(), root2.GetType());
 
        // Now apply the change to the workspace.  This should bring the linked document in sync with the one we changed.
        // But not cause them to share trees.
        workspace.TryApplyChanges(solution);
        solution = workspace.CurrentSolution;
 
        document1 = solution.GetRequiredDocument(documentId1);
        document2 = solution.GetRequiredDocument(documentId2);
 
        text1 = await document1.GetTextAsync();
        text2 = await document2.GetTextAsync();
        version1 = await document1.GetTextVersionAsync();
        version2 = await document2.GetTextVersionAsync();
        root1 = await document1.GetRequiredSyntaxRootAsync(CancellationToken.None);
        root2 = await document2.GetRequiredSyntaxRootAsync(CancellationToken.None);
 
        Assert.Equal(text1.ToString(), text2.ToString());
        Assert.Equal(version1, version2);
 
        // These are different languages, so we should get entirely different tree structures.
        Assert.NotEqual(root1.GetType(), root2.GetType());
    }
 
    [Fact]
    public void WithAdditionalDocumentText_SourceText()
    {
        using var workspace = CreateWorkspaceWithProjectAndDocuments();
        var solution = workspace.CurrentSolution;
        var documentId = solution.Projects.Single().AdditionalDocumentIds.Single();
        var text = SourceText.From("new text");
 
        var newSolution1 = solution.WithAdditionalDocumentText(documentId, text, PreservationMode.PreserveIdentity);
        Assert.True(newSolution1.GetAdditionalDocument(documentId)!.TryGetText(out var actualText));
        Assert.Same(text, actualText);
 
        var newSolution2 = newSolution1.WithAdditionalDocumentText(documentId, text, PreservationMode.PreserveIdentity);
        Assert.Same(newSolution1, newSolution2);
 
        Assert.Throws<ArgumentNullException>(() => solution.WithAdditionalDocumentText(documentId, (SourceText)null!, PreservationMode.PreserveIdentity));
        Assert.Throws<ArgumentOutOfRangeException>(() => solution.WithAdditionalDocumentText(documentId, text, (PreservationMode)(-1)));
 
        Assert.Throws<ArgumentNullException>(() => solution.WithAdditionalDocumentText((DocumentId)null!, text, PreservationMode.PreserveIdentity));
        Assert.Throws<InvalidOperationException>(() => solution.WithAdditionalDocumentText(s_unrelatedDocumentId, text, PreservationMode.PreserveIdentity));
    }
 
    [Fact]
    public void WithAdditionalDocumentText_TextAndVersion()
    {
        using var workspace = CreateWorkspaceWithProjectAndDocuments();
        var solution = workspace.CurrentSolution;
        var documentId = solution.Projects.Single().AdditionalDocumentIds.Single();
        var textAndVersion = TextAndVersion.Create(SourceText.From("new text"), VersionStamp.Default);
 
        var newSolution1 = solution.WithAdditionalDocumentText(documentId, textAndVersion, PreservationMode.PreserveIdentity);
        Assert.True(newSolution1.GetAdditionalDocument(documentId)!.TryGetText(out var actualText));
        Assert.True(newSolution1.GetAdditionalDocument(documentId)!.TryGetTextVersion(out var actualVersion));
        Assert.Same(textAndVersion.Text, actualText);
        Assert.Equal(textAndVersion.Version, actualVersion);
 
        var newSolution2 = newSolution1.WithAdditionalDocumentText(documentId, textAndVersion, PreservationMode.PreserveIdentity);
        Assert.Same(newSolution1, newSolution2);
 
        Assert.Throws<ArgumentNullException>(() => solution.WithAdditionalDocumentText(documentId, (SourceText)null!, PreservationMode.PreserveIdentity));
        Assert.Throws<ArgumentOutOfRangeException>(() => solution.WithAdditionalDocumentText(documentId, textAndVersion, (PreservationMode)(-1)));
 
        Assert.Throws<ArgumentNullException>(() => solution.WithAdditionalDocumentText((DocumentId)null!, textAndVersion, PreservationMode.PreserveIdentity));
        Assert.Throws<InvalidOperationException>(() => solution.WithAdditionalDocumentText(s_unrelatedDocumentId, textAndVersion, PreservationMode.PreserveIdentity));
    }
 
    [Fact]
    public void WithAnalyzerConfigDocumentText_SourceText()
    {
        using var workspace = CreateWorkspaceWithProjectAndDocuments();
        var solution = workspace.CurrentSolution;
        var documentId = solution.Projects.Single().AnalyzerConfigDocumentIds.Single();
        var text = SourceText.From("new text");
 
        var newSolution1 = solution.WithAnalyzerConfigDocumentText(documentId, text, PreservationMode.PreserveIdentity);
        Assert.True(newSolution1.GetAnalyzerConfigDocument(documentId)!.TryGetText(out var actualText));
        Assert.Same(text, actualText);
 
        var newSolution2 = newSolution1.WithAnalyzerConfigDocumentText(documentId, text, PreservationMode.PreserveIdentity);
        Assert.Same(newSolution1, newSolution2);
 
        Assert.Throws<ArgumentNullException>(() => solution.WithAnalyzerConfigDocumentText(documentId, (SourceText)null!, PreservationMode.PreserveIdentity));
        Assert.Throws<ArgumentOutOfRangeException>(() => solution.WithAnalyzerConfigDocumentText(documentId, text, (PreservationMode)(-1)));
 
        Assert.Throws<ArgumentNullException>(() => solution.WithAnalyzerConfigDocumentText((DocumentId)null!, text, PreservationMode.PreserveIdentity));
        Assert.Throws<InvalidOperationException>(() => solution.WithAnalyzerConfigDocumentText(s_unrelatedDocumentId, text, PreservationMode.PreserveIdentity));
    }
 
    [Fact]
    public void WithAnalyzerConfigDocumentText_TextAndVersion()
    {
        using var workspace = CreateWorkspaceWithProjectAndDocuments();
        var solution = workspace.CurrentSolution;
        var documentId = solution.Projects.Single().AnalyzerConfigDocumentIds.Single();
        var textAndVersion = TextAndVersion.Create(SourceText.From("new text"), VersionStamp.Default);
 
        var newSolution1 = solution.WithAnalyzerConfigDocumentText(documentId, textAndVersion, PreservationMode.PreserveIdentity);
        Assert.True(newSolution1.GetAnalyzerConfigDocument(documentId)!.TryGetText(out var actualText));
        Assert.True(newSolution1.GetAnalyzerConfigDocument(documentId)!.TryGetTextVersion(out var actualVersion));
        Assert.Same(textAndVersion.Text, actualText);
        Assert.Equal(textAndVersion.Version, actualVersion);
 
        var newSolution2 = newSolution1.WithAnalyzerConfigDocumentText(documentId, textAndVersion, PreservationMode.PreserveIdentity);
        Assert.Same(newSolution1, newSolution2);
 
        Assert.Throws<ArgumentNullException>(() => solution.WithAnalyzerConfigDocumentText(documentId, (SourceText)null!, PreservationMode.PreserveIdentity));
        Assert.Throws<ArgumentOutOfRangeException>(() => solution.WithAnalyzerConfigDocumentText(documentId, textAndVersion, (PreservationMode)(-1)));
 
        Assert.Throws<ArgumentNullException>(() => solution.WithAnalyzerConfigDocumentText((DocumentId)null!, textAndVersion, PreservationMode.PreserveIdentity));
        Assert.Throws<InvalidOperationException>(() => solution.WithAnalyzerConfigDocumentText(s_unrelatedDocumentId, textAndVersion, PreservationMode.PreserveIdentity));
    }
 
    [Fact]
    public void WithDocumentTextLoader()
    {
        using var workspace = CreateWorkspaceWithProjectAndDocuments();
        var solution = workspace.CurrentSolution;
        var documentId = solution.Projects.Single().DocumentIds.Single();
        var loader = new TestTextLoader("new text");
 
        var newSolution1 = solution.WithDocumentTextLoader(documentId, loader, PreservationMode.PreserveIdentity);
        Assert.Equal("new text", newSolution1.GetDocument(documentId)!.GetTextSynchronously(CancellationToken.None).ToString());
 
        // Reusal is not currently implemented: https://github.com/dotnet/roslyn/issues/42028
        var newSolution2 = solution.WithDocumentTextLoader(documentId, loader, PreservationMode.PreserveIdentity);
        Assert.NotSame(newSolution1, newSolution2);
 
        Assert.Throws<ArgumentNullException>(() => solution.WithDocumentTextLoader(documentId, null!, PreservationMode.PreserveIdentity));
        Assert.Throws<ArgumentOutOfRangeException>(() => solution.WithDocumentTextLoader(documentId, loader, (PreservationMode)(-1)));
 
        Assert.Throws<ArgumentNullException>(() => solution.WithDocumentTextLoader(null!, loader, PreservationMode.PreserveIdentity));
        Assert.Throws<InvalidOperationException>(() => solution.WithDocumentTextLoader(s_unrelatedDocumentId, loader, PreservationMode.PreserveIdentity));
    }
 
    [Fact]
    public void WithAdditionalDocumentTextLoader()
    {
        using var workspace = CreateWorkspaceWithProjectAndDocuments();
        var solution = workspace.CurrentSolution;
        var documentId = solution.Projects.Single().AdditionalDocumentIds.Single();
        var loader = new TestTextLoader("new text");
 
        var newSolution1 = solution.WithAdditionalDocumentTextLoader(documentId, loader, PreservationMode.PreserveIdentity);
        Assert.Equal("new text", newSolution1.GetAdditionalDocument(documentId)!.GetTextSynchronously(CancellationToken.None).ToString());
 
        // Reusal is not currently implemented: https://github.com/dotnet/roslyn/issues/42028
        var newSolution2 = solution.WithAdditionalDocumentTextLoader(documentId, loader, PreservationMode.PreserveIdentity);
        Assert.NotSame(newSolution1, newSolution2);
 
        Assert.Throws<ArgumentNullException>(() => solution.WithAdditionalDocumentTextLoader(documentId, null!, PreservationMode.PreserveIdentity));
        Assert.Throws<ArgumentOutOfRangeException>(() => solution.WithAdditionalDocumentTextLoader(documentId, loader, (PreservationMode)(-1)));
 
        Assert.Throws<ArgumentNullException>(() => solution.WithAdditionalDocumentTextLoader(null!, loader, PreservationMode.PreserveIdentity));
        Assert.Throws<InvalidOperationException>(() => solution.WithAdditionalDocumentTextLoader(s_unrelatedDocumentId, loader, PreservationMode.PreserveIdentity));
    }
 
    [Fact]
    public void WithAnalyzerConfigDocumentTextLoader()
    {
        using var workspace = CreateWorkspaceWithProjectAndDocuments();
        var solution = workspace.CurrentSolution;
        var documentId = solution.Projects.Single().AnalyzerConfigDocumentIds.Single();
        var loader = new TestTextLoader("new text");
 
        var newSolution1 = solution.WithAnalyzerConfigDocumentTextLoader(documentId, loader, PreservationMode.PreserveIdentity);
        Assert.Equal("new text", newSolution1.GetAnalyzerConfigDocument(documentId)!.GetTextSynchronously(CancellationToken.None).ToString());
 
        // Reusal is not currently implemented: https://github.com/dotnet/roslyn/issues/42028
        var newSolution2 = solution.WithAnalyzerConfigDocumentTextLoader(documentId, loader, PreservationMode.PreserveIdentity);
        Assert.NotSame(newSolution1, newSolution2);
 
        Assert.Throws<ArgumentNullException>(() => solution.WithAnalyzerConfigDocumentTextLoader(documentId, null!, PreservationMode.PreserveIdentity));
        Assert.Throws<ArgumentOutOfRangeException>(() => solution.WithAnalyzerConfigDocumentTextLoader(documentId, loader, (PreservationMode)(-1)));
 
        Assert.Throws<ArgumentNullException>(() => solution.WithAnalyzerConfigDocumentTextLoader(null!, loader, PreservationMode.PreserveIdentity));
        Assert.Throws<InvalidOperationException>(() => solution.WithAnalyzerConfigDocumentTextLoader(s_unrelatedDocumentId, loader, PreservationMode.PreserveIdentity));
    }
 
    [Fact]
    public void WithProjectInfo()
    {
        var projectId = ProjectId.CreateNewId();
        var projectId2 = ProjectId.CreateNewId();
 
        using var workspace = CreateWorkspace();
 
        var d1 = DocumentId.CreateNewId(projectId);
        var d2 = DocumentId.CreateNewId(projectId);
        var d3 = DocumentId.CreateNewId(projectId);
        var a1 = DocumentId.CreateNewId(projectId);
        var a2 = DocumentId.CreateNewId(projectId);
        var a3 = DocumentId.CreateNewId(projectId);
        var c1 = DocumentId.CreateNewId(projectId);
        var c2 = DocumentId.CreateNewId(projectId);
        var c3 = DocumentId.CreateNewId(projectId);
 
        var solution = workspace.CurrentSolution
            .AddProject(ProjectInfo.Create(projectId, VersionStamp.Default, "proj1", "proj1", LanguageNames.CSharp, Path.Combine(s_projectDir, "proj1.dll")))
            .AddProject(ProjectInfo.Create(projectId2, VersionStamp.Default, "proj2", "proj2", LanguageNames.CSharp, Path.Combine(s_projectDir, "proj2.dll")))
            .AddDocument(d1, "d1.cs", SourceText.From("class D1;", Encoding.UTF8, SourceHashAlgorithms.Default), filePath: Path.Combine(s_projectDir, "d1.cs"))
            .AddDocument(d2, "d2.cs", SourceText.From("class D2;", Encoding.UTF8, SourceHashAlgorithms.Default), filePath: Path.Combine(s_projectDir, "d2.cs"))
            .AddAdditionalDocument(a1, "a1.txt", SourceText.From("text1", Encoding.UTF8, SourceHashAlgorithms.Default))
            .AddAdditionalDocument(a2, "a2.txt", SourceText.From("text2", Encoding.UTF8, SourceHashAlgorithms.Default))
            .AddAnalyzerConfigDocument(c1, "c1", SourceText.From("#empty1", Encoding.UTF8, SourceHashAlgorithms.Default), filePath: Path.Combine(s_projectDir, "editorcfg"))
            .AddAnalyzerConfigDocument(c2, "c2", SourceText.From("#empty2", Encoding.UTF8, SourceHashAlgorithms.Default), filePath: Path.Combine(s_projectDir, "editorcfg"));
 
        var oldProject = solution.GetRequiredProject(projectId);
        var documentIds = oldProject.DocumentIds;
 
        var newDocumentInfo1 = DocumentInfo.Create(
            d1,
            name: "newD1",
            folders: ["f", "g"],
            sourceCodeKind: SourceCodeKind.Script,
            loader: TextLoader.From(TextAndVersion.Create(SourceText.From("class NewD1;", Encoding.UTF32, SourceHashAlgorithm.Sha256), VersionStamp.Create(), filePath: Path.Combine(s_projectDir, "newD1.cs"))),
            filePath: Path.Combine(s_projectDir, "newD1.cs"),
            isGenerated: true);
 
        var newDocumentInfo3 = DocumentInfo.Create(
            d3,
            name: "newD3",
            folders: null,
            sourceCodeKind: SourceCodeKind.Regular,
            loader: TextLoader.From(TextAndVersion.Create(SourceText.From("class NewD3;", Encoding.UTF8, SourceHashAlgorithms.Default), VersionStamp.Create(), filePath: Path.Combine(s_projectDir, "newD3.cs"))),
            filePath: Path.Combine(s_projectDir, "newD3.cs"),
            isGenerated: false)
            .WithDocumentServiceProvider(new TestDocumentServiceProvider());
 
        var newAddDocumentInfo1 = DocumentInfo.Create(
            a1,
            name: "newA1",
            folders: ["af", "ag"],
            sourceCodeKind: SourceCodeKind.Script,
            loader: TextLoader.From(TextAndVersion.Create(SourceText.From("new text1", Encoding.UTF32, SourceHashAlgorithm.Sha256), VersionStamp.Create(), filePath: Path.Combine(s_projectDir, "newD1.cs"))),
            filePath: Path.Combine(s_projectDir, "newA1.txt"),
            isGenerated: true);
 
        var newAddDocumentInfo3 = DocumentInfo.Create(
            a3,
            name: "newA3",
            folders: null,
            sourceCodeKind: SourceCodeKind.Regular,
            loader: TextLoader.From(TextAndVersion.Create(SourceText.From("new text3", Encoding.UTF8, SourceHashAlgorithms.Default), VersionStamp.Create(), filePath: Path.Combine(s_projectDir, "newD3.cs"))),
            filePath: Path.Combine(s_projectDir, "newA3.txt"),
            isGenerated: false)
            .WithDocumentServiceProvider(new TestDocumentServiceProvider());
 
        var newConfigDocumentInfo1 = DocumentInfo.Create(
            c1,
            name: "newC1",
            folders: ["cf", "cg"],
            sourceCodeKind: SourceCodeKind.Script,
            loader: TextLoader.From(TextAndVersion.Create(SourceText.From("#new empty1", Encoding.UTF32, SourceHashAlgorithm.Sha256), VersionStamp.Create(), filePath: Path.Combine(s_projectDir, "newD1.cs"))),
            filePath: Path.Combine(s_projectDir, "newC1"),
            isGenerated: true);
 
        var newConfigDocumentInfo3 = DocumentInfo.Create(
            c3,
            name: "newC3",
            folders: null,
            sourceCodeKind: SourceCodeKind.Regular,
            loader: TextLoader.From(TextAndVersion.Create(SourceText.From("#new empty3", Encoding.UTF8, SourceHashAlgorithms.Default), VersionStamp.Create(), filePath: Path.Combine(s_projectDir, "newD3.cs"))),
            filePath: Path.Combine(s_projectDir, "newC3"),
            isGenerated: false)
            .WithDocumentServiceProvider(new TestDocumentServiceProvider());
 
        var metadataReference = MetadataReference.CreateFromImage([], filePath: "meta");
        var projectReference = new ProjectReference(projectId2);
        var analyzerReference = new TestAnalyzerReference();
        var generatedOutputDir = Path.Combine(TempRoot.Root, "obj");
        var assemblyPath = Path.Combine(TempRoot.Root, "bin", "assemblyName.dll");
 
        var newInfo = ProjectInfo.Create(
            projectId,
            VersionStamp.Create(),
            name: "newProjectName",
            assemblyName: "newAssemblyName",
            language: LanguageNames.CSharp,
            filePath: "newFilePath.cs",
            outputFilePath: "newOutputFilePath",
            outputRefFilePath: "newOutputRef",
            compilationOptions: new CSharpCompilationOptions(OutputKind.WindowsApplication, moduleName: "newModuleName"),
            parseOptions: new CSharpParseOptions(CS.LanguageVersion.CSharp5),
            documents:
            [
                // update existing document:
                newDocumentInfo1,
                // add new document:
                newDocumentInfo3,
                // remove existing document (d2)
            ],
            additionalDocuments:
            [
                // update existing document:
                newAddDocumentInfo1,
                // add new document:
                newAddDocumentInfo3,
                // remove existing document (a2)
            ],
            projectReferences: [projectReference],
            metadataReferences: [metadataReference],
            analyzerReferences: [analyzerReference],
            isSubmission: false,
            hostObjectType: null)
            .WithAnalyzerConfigDocuments(
            [
                // update existing document:
                newConfigDocumentInfo1,
                // add new document:
                newConfigDocumentInfo3,
                // remove existing document (c2)
            ])
            .WithCompilationOutputInfo(new CompilationOutputInfo(assemblyPath, generatedOutputDir));
 
        var newSolution = solution.WithProjectInfo(newInfo);
        var newProject = newSolution.GetRequiredProject(projectId);
 
        // attributes:
        Assert.True(newProject.Version.GetTestAccessor().IsNewerThan(oldProject.Version));
        Assert.Equal(newInfo.Name, newProject.Name);
        Assert.Equal(newInfo.AssemblyName, newProject.AssemblyName);
        Assert.Equal(newInfo.Language, newProject.Language);
        Assert.Equal(newInfo.FilePath, newProject.FilePath);
        Assert.Equal(newInfo.OutputFilePath, newProject.OutputFilePath);
        Assert.Equal(newInfo.CompilationOutputInfo, newProject.CompilationOutputInfo);
        Assert.Equal(newInfo.CompilationOptions!.OutputKind, newProject.CompilationOptions!.OutputKind);
        Assert.Equal(newInfo.CompilationOptions!.ModuleName, newProject.CompilationOptions!.ModuleName);
        Assert.Equal(newInfo.ParseOptions!.LanguageVersion, newProject.ParseOptions!.LanguageVersion);
        Assert.Equal(newInfo.OutputRefFilePath, newProject.OutputRefFilePath);
 
        AssertEx.AreEqual([projectReference], newProject.ProjectReferences);
        AssertEx.AreEqual([metadataReference], newProject.MetadataReferences);
        AssertEx.AreEqual([analyzerReference], newProject.AnalyzerReferences);
 
        // documents:
        AssertEx.SetEqual([d1, d3], newProject.DocumentIds);
 
        var newDocument1 = newProject.GetRequiredDocument(d1);
        var newText1 = newDocument1.GetTextSynchronously(CancellationToken.None);
        Assert.Equal(newDocumentInfo1.Name, newDocument1.Name);
        Assert.Equal(newDocumentInfo1.FilePath, newDocument1.FilePath);
        Assert.Same(DefaultTextDocumentServiceProvider.Instance, newDocument1.DocumentServiceProvider);
        Assert.Equal("class NewD1;", newText1.ToString());
        Assert.Same(Encoding.UTF32, newText1.Encoding);
        Assert.Equal(SourceHashAlgorithm.Sha256, newText1.ChecksumAlgorithm);
 
        var newDocument3 = newProject.GetRequiredDocument(d3);
        var newText3 = newDocument3.GetTextSynchronously(CancellationToken.None);
        Assert.Equal(newDocumentInfo3.Name, newDocument3.Name);
        Assert.Equal(newDocumentInfo3.FilePath, newDocument3.FilePath);
        Assert.Same(newDocumentInfo3.DocumentServiceProvider, newDocument3.DocumentServiceProvider);
        Assert.Equal("class NewD3;", newDocument3.GetTextSynchronously(CancellationToken.None).ToString());
        Assert.Same(Encoding.UTF8, newText3.Encoding);
        Assert.Equal(SourceHashAlgorithms.Default, newText3.ChecksumAlgorithm);
 
        // additional documents:
        AssertEx.SetEqual([a1, a3], newProject.AdditionalDocumentIds);
 
        var newAddDocument1 = newProject.GetRequiredAdditionalDocument(a1);
        var newAddText1 = newAddDocument1.GetTextSynchronously(CancellationToken.None);
        Assert.Equal(newAddDocumentInfo1.Name, newAddDocument1.Name);
        Assert.Equal(newAddDocumentInfo1.FilePath, newAddDocument1.FilePath);
        Assert.Same(DefaultTextDocumentServiceProvider.Instance, newAddDocument1.DocumentServiceProvider);
        Assert.Equal("new text1", newAddText1.ToString());
        Assert.Same(Encoding.UTF32, newAddText1.Encoding);
        Assert.Equal(SourceHashAlgorithm.Sha256, newAddText1.ChecksumAlgorithm);
 
        var newAddDocument3 = newProject.GetRequiredAdditionalDocument(a3);
        var newAddText3 = newAddDocument3.GetTextSynchronously(CancellationToken.None);
        Assert.Equal(newAddDocumentInfo3.Name, newAddDocument3.Name);
        Assert.Equal(newAddDocumentInfo3.FilePath, newAddDocument3.FilePath);
        Assert.Same(newAddDocumentInfo3.DocumentServiceProvider, newAddDocument3.DocumentServiceProvider);
        Assert.Equal("new text3", newAddDocument3.GetTextSynchronously(CancellationToken.None).ToString());
        Assert.Same(Encoding.UTF8, newAddText3.Encoding);
        Assert.Equal(SourceHashAlgorithms.Default, newAddText3.ChecksumAlgorithm);
 
        // analyzer config documents:
        AssertEx.SetEqual([c1, c3], newProject.AnalyzerConfigDocumentIds);
 
        var newConfigDocument1 = newProject.GetRequiredAnalyzerConfigDocument(c1);
        var newConfigText1 = newConfigDocument1.GetTextSynchronously(CancellationToken.None);
        Assert.Equal(newConfigDocumentInfo1.Name, newConfigDocument1.Name);
        Assert.Equal(newConfigDocumentInfo1.FilePath, newConfigDocument1.FilePath);
        Assert.Same(DefaultTextDocumentServiceProvider.Instance, newConfigDocument1.DocumentServiceProvider);
        Assert.Equal("#new empty1", newConfigText1.ToString());
        Assert.Same(Encoding.UTF32, newConfigText1.Encoding);
        Assert.Equal(SourceHashAlgorithm.Sha256, newConfigText1.ChecksumAlgorithm);
 
        var newConfigDocument3 = newProject.GetRequiredAnalyzerConfigDocument(c3);
        var newConfigText3 = newConfigDocument3.GetTextSynchronously(CancellationToken.None);
        Assert.Equal(newConfigDocumentInfo3.Name, newConfigDocument3.Name);
        Assert.Equal(newConfigDocumentInfo3.FilePath, newConfigDocument3.FilePath);
        Assert.Same(newConfigDocumentInfo3.DocumentServiceProvider, newConfigDocument3.DocumentServiceProvider);
        Assert.Equal("#new empty3", newConfigDocument3.GetTextSynchronously(CancellationToken.None).ToString());
        Assert.Same(Encoding.UTF8, newConfigText3.Encoding);
        Assert.Equal(SourceHashAlgorithms.Default, newConfigText3.ChecksumAlgorithm);
    }
 
    [Fact]
    public void WithProjectInfo_Unsupported_RemovingCompilationOptions()
    {
        var projectId = ProjectId.CreateNewId();
 
        using var workspace = CreateWorkspace();
 
        var solution = workspace.CurrentSolution
            .AddProject(ProjectInfo.Create(projectId, VersionStamp.Default, "proj1", "proj1", LanguageNames.CSharp, Path.Combine(s_projectDir, "proj1.dll")));
 
        var oldProject = solution.GetRequiredProject(projectId);
        var documentIds = oldProject.DocumentIds;
 
        var newInfo = ProjectInfo.Create(
            projectId,
            VersionStamp.Create(),
            name: "newProjectName",
            assemblyName: "newAssemblyName",
            language: LanguageNames.CSharp,
            filePath: "newFilePath.cs",
            outputFilePath: "newOutputFilePath",
            outputRefFilePath: "newOutputRef",
            compilationOptions: new CSharpCompilationOptions(OutputKind.WindowsApplication, moduleName: "newModuleName"),
            parseOptions: null,
            documents: [],
            additionalDocuments: [],
            projectReferences: [],
            metadataReferences: [],
            analyzerReferences: [],
            isSubmission: false,
            hostObjectType: null);
 
        Assert.Throws<NotSupportedException>(() => solution.WithProjectInfo(newInfo));
    }
 
    [Fact]
    public void WithProjectInfo_Unsupported_RemovingParseOptions()
    {
        var projectId = ProjectId.CreateNewId();
 
        using var workspace = CreateWorkspace();
 
        var solution = workspace.CurrentSolution
            .AddProject(ProjectInfo.Create(projectId, VersionStamp.Default, "proj1", "proj1", LanguageNames.CSharp, Path.Combine(s_projectDir, "proj1.dll")));
 
        var oldProject = solution.GetRequiredProject(projectId);
        var documentIds = oldProject.DocumentIds;
 
        var newInfo = ProjectInfo.Create(
            projectId,
            VersionStamp.Create(),
            name: "newProjectName",
            assemblyName: "newAssemblyName",
            language: LanguageNames.CSharp,
            filePath: "newFilePath.cs",
            outputFilePath: "newOutputFilePath",
            outputRefFilePath: "newOutputRef",
            compilationOptions: null,
            parseOptions: new CSharpParseOptions(CS.LanguageVersion.CSharp5),
            documents: [],
            additionalDocuments: [],
            projectReferences: [],
            metadataReferences: [],
            analyzerReferences: [],
            isSubmission: false,
            hostObjectType: null);
 
        Assert.Throws<NotSupportedException>(() => solution.WithProjectInfo(newInfo));
    }
 
    [Fact]
    public void WithProjectInfo_Unsupported_ChangingLanguage()
    {
        var projectId = ProjectId.CreateNewId();
 
        using var workspace = CreateWorkspace();
 
        var solution = workspace.CurrentSolution
            .AddProject(ProjectInfo.Create(projectId, VersionStamp.Default, "proj1", "proj1", LanguageNames.CSharp, Path.Combine(s_projectDir, "proj1.dll")));
 
        var oldProject = solution.GetRequiredProject(projectId);
        var documentIds = oldProject.DocumentIds;
 
        var newInfo = ProjectInfo.Create(
            projectId,
            VersionStamp.Create(),
            name: "newProjectName",
            assemblyName: "newAssemblyName",
            language: LanguageNames.VisualBasic,
            filePath: "newFilePath.cs",
            outputFilePath: "newOutputFilePath",
            outputRefFilePath: "newOutputRef",
            compilationOptions: null,
            parseOptions: null,
            documents: [],
            additionalDocuments: [],
            projectReferences: [],
            metadataReferences: [],
            analyzerReferences: [],
            isSubmission: false,
            hostObjectType: null);
 
        Assert.Throws<NotSupportedException>(() => solution.WithProjectInfo(newInfo));
    }
 
    [Fact]
    public void WithProjectInfo_Unsupported_ChangingIsSubmission()
    {
        var projectId = ProjectId.CreateNewId();
 
        using var workspace = CreateWorkspace();
 
        var solution = workspace.CurrentSolution
            .AddProject(ProjectInfo.Create(projectId, VersionStamp.Default, "proj1", "proj1", LanguageNames.CSharp, Path.Combine(s_projectDir, "proj1.dll")));
 
        var oldProject = solution.GetRequiredProject(projectId);
        var documentIds = oldProject.DocumentIds;
 
        var newInfo = ProjectInfo.Create(
            projectId,
            VersionStamp.Create(),
            name: "newProjectName",
            assemblyName: "newAssemblyName",
            language: LanguageNames.CSharp,
            filePath: "newFilePath.cs",
            outputFilePath: "newOutputFilePath",
            outputRefFilePath: "newOutputRef",
            compilationOptions: new CSharpCompilationOptions(OutputKind.WindowsApplication, moduleName: "newModuleName"),
            parseOptions: CS.CSharpParseOptions.Default,
            documents: [],
            additionalDocuments: [],
            projectReferences: [],
            metadataReferences: [],
            analyzerReferences: [],
            isSubmission: true,
            hostObjectType: null);
 
        Assert.Throws<NotSupportedException>(() => solution.WithProjectInfo(newInfo));
    }
 
    [Fact]
    public void WithProjectAssemblyName()
    {
        var projectId = ProjectId.CreateNewId();
 
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution
            .AddProject(projectId, "proj1", "proj1.dll", LanguageNames.CSharp);
 
        // any character is allowed
        var assemblyName = "\0<>a/b/*.dll";
 
        var newSolution = solution.WithProjectAssemblyName(projectId, assemblyName);
        Assert.Equal(assemblyName, newSolution.GetProject(projectId)!.AssemblyName);
 
        Assert.Same(newSolution, newSolution.WithProjectAssemblyName(projectId, assemblyName));
 
        Assert.Throws<ArgumentNullException>("assemblyName", () => solution.WithProjectAssemblyName(projectId, null!));
        Assert.Throws<ArgumentNullException>("projectId", () => solution.WithProjectAssemblyName(null!, "x.dll"));
        Assert.Throws<InvalidOperationException>(() => solution.WithProjectAssemblyName(ProjectId.CreateNewId(), "x.dll"));
    }
 
    [Fact]
    public void WithProjectOutputFilePath()
    {
        var projectId = ProjectId.CreateNewId();
 
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution
            .AddProject(projectId, "proj1", "proj1.dll", LanguageNames.CSharp);
 
        // any character is allowed
        var path = "\0<>a/b/*.dll";
 
        SolutionTestHelpers.TestProperty(
            solution,
            (s, value) => s.WithProjectOutputFilePath(projectId, value),
            s => s.GetRequiredProject(projectId).OutputFilePath,
            (string?)path,
            defaultThrows: false);
 
        Assert.Throws<ArgumentNullException>("projectId", () => solution.WithProjectOutputFilePath(null!, "x.dll"));
        Assert.Throws<ArgumentNullException>("projectId", () => solution.WithProjectOutputFilePath(null!, "x.dll"));
        Assert.Throws<InvalidOperationException>(() => solution.WithProjectOutputFilePath(ProjectId.CreateNewId(), "x.dll"));
    }
 
    [Fact]
    public void GetEffectiveGeneratedFilesOutputDirectory()
    {
        var projectId = ProjectId.CreateNewId();
 
        var objDir = Path.Combine(TempRoot.Root, "obj");
        var binDir = Path.Combine(TempRoot.Root, "bin");
        var otherDir = Path.Combine(TempRoot.Root, "other");
 
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution.AddProject(projectId, "proj1", "proj1.dll", LanguageNames.CSharp);
        var project = solution.GetRequiredProject(projectId);
 
        Assert.False(project.CompilationOutputInfo.HasEffectiveGeneratedFilesOutputDirectory);
 
        project = project
            .WithOutputFilePath(Path.Combine(binDir, "output.dll"))
            .WithCompilationOutputInfo(new CompilationOutputInfo(
                assemblyPath: Path.Combine(objDir, "output.dll"),
                generatedFilesOutputDirectory: null));
 
        Assert.True(project.CompilationOutputInfo.HasEffectiveGeneratedFilesOutputDirectory);
        AssertEx.AreEqual(objDir, project.CompilationOutputInfo.GetEffectiveGeneratedFilesOutputDirectory());
 
        project = project.WithCompilationOutputInfo(
            project.CompilationOutputInfo.WithGeneratedFilesOutputDirectory(otherDir));
 
        Assert.True(project.CompilationOutputInfo.HasEffectiveGeneratedFilesOutputDirectory);
        AssertEx.AreEqual(otherDir, project.CompilationOutputInfo.GetEffectiveGeneratedFilesOutputDirectory());
    }
 
    [Fact]
    public void WithProjectOutputRefFilePath()
    {
        var projectId = ProjectId.CreateNewId();
 
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution
                        .AddProject(projectId, "proj1", "proj1.dll", LanguageNames.CSharp);
 
        // any character is allowed
        var path = "\0<>a/b/*.dll";
 
        SolutionTestHelpers.TestProperty(
            solution,
            (s, value) => s.WithProjectOutputRefFilePath(projectId, value),
            s => s.GetRequiredProject(projectId).OutputRefFilePath,
            (string?)path,
            defaultThrows: false);
 
        Assert.Throws<ArgumentNullException>("projectId", () => solution.WithProjectOutputRefFilePath(null!, "x.dll"));
        Assert.Throws<InvalidOperationException>(() => solution.WithProjectOutputRefFilePath(ProjectId.CreateNewId(), "x.dll"));
    }
 
    [Fact]
    public void WithProjectCompilationOutputInfo()
    {
        var projectId = ProjectId.CreateNewId();
 
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution
                        .AddProject(projectId, "proj1", "proj1.dll", LanguageNames.CSharp);
 
        // any character is allowed
        var info = new CompilationOutputInfo("\0<>a/b/*.dll", TempRoot.Root + "<>\0");
 
        SolutionTestHelpers.TestProperty(
            solution,
            (s, value) => s.WithProjectCompilationOutputInfo(projectId, value),
            s => s.GetRequiredProject(projectId).CompilationOutputInfo,
            info,
            defaultThrows: false);
 
        Assert.Throws<ArgumentNullException>("projectId", () => solution.WithProjectCompilationOutputInfo(null!, info));
        Assert.Throws<InvalidOperationException>(() => solution.WithProjectCompilationOutputInfo(ProjectId.CreateNewId(), info));
    }
 
    [Fact]
    public void WithProjectDefaultNamespace()
    {
        var projectId = ProjectId.CreateNewId();
 
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution.
            AddProject(projectId, "proj1", "proj1.dll", LanguageNames.CSharp);
 
        // any character is allowed
        var defaultNamespace = "\0<>a/b/*";
 
        SolutionTestHelpers.TestProperty(
            solution,
            (s, value) => s.WithProjectDefaultNamespace(projectId, value),
            s => s.GetRequiredProject(projectId).DefaultNamespace,
            (string?)defaultNamespace,
            defaultThrows: false);
 
        Assert.Throws<ArgumentNullException>("projectId", () => solution.WithProjectDefaultNamespace(null!, "x"));
        Assert.Throws<InvalidOperationException>(() => solution.WithProjectDefaultNamespace(ProjectId.CreateNewId(), "x"));
    }
 
    [Fact]
    public void WithProjectChecksumAlgorithm()
    {
        var projectId = ProjectId.CreateNewId();
 
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution.
            AddProject(projectId, "proj1", "proj1.dll", LanguageNames.CSharp);
 
        SolutionTestHelpers.TestProperty(
            solution,
            (s, value) => s.WithProjectChecksumAlgorithm(projectId, value),
            s => s.GetRequiredProject(projectId).State.ChecksumAlgorithm,
            SourceHashAlgorithms.Default,
            defaultThrows: false);
    }
 
    [Fact]
    public async Task WithProjectChecksumAlgorithm_DocumentUpdates()
    {
        var projectId = ProjectId.CreateNewId();
        var documentAId = DocumentId.CreateNewId(projectId);
        var documentBId = DocumentId.CreateNewId(projectId);
        var documentCId = DocumentId.CreateNewId(projectId);
        var fileDocumentId = DocumentId.CreateNewId(projectId);
 
        var fileD = Temp.CreateFile();
        var bytes = new UTF8Encoding(encoderShouldEmitUTF8Identifier: true).GetBytes("Text");
        fileD.WriteAllBytes(bytes);
 
        var sha256 = SHA256.Create();
        // CodeQL [SM02196] This is not enabled by default but exists as a compat option for existing builds.
        var sha1 = SHA1.Create();
        var checksumSHA1 = sha1.ComputeHash(bytes);
        var checksumSHA256 = sha256.ComputeHash(bytes);
        sha256.Dispose();
        sha1.Dispose();
 
        using var workspace = CreateWorkspace();
 
        var textLoaderA = new TestTextLoader("class A {}", SourceHashAlgorithm.Sha1);
        var textC = SourceText.From("class C {}", encoding: null, checksumAlgorithm: SourceHashAlgorithm.Sha1);
 
        var solution = workspace.CurrentSolution
            .AddProject(projectId, "proj1", "proj1.dll", LanguageNames.CSharp)
            .WithProjectChecksumAlgorithm(projectId, SourceHashAlgorithm.Sha1);
 
        solution = solution.AddDocument(DocumentInfo.Create(documentAId, "a.cs", loader: textLoaderA, filePath: "a.cs"));
        solution = solution.AddDocument(documentBId, "b.cs", "class B {}", filePath: "b.cs");
        solution = solution.AddDocument(documentCId, "c.cs", textC, filePath: "c.cs");
        solution = solution.AddDocument(DocumentInfo.Create(fileDocumentId, "d.cs", loader: new FileTextLoader(fileD.Path, defaultEncoding: null), filePath: fileD.Path));
 
        await Verify(solution.GetRequiredDocument(documentAId), SourceHashAlgorithm.Sha1);
        await Verify(solution.GetRequiredDocument(documentBId), SourceHashAlgorithm.Sha1);
        await Verify(solution.GetRequiredDocument(documentCId), SourceHashAlgorithm.Sha1);
        await Verify(solution.GetRequiredDocument(fileDocumentId), SourceHashAlgorithm.Sha1, checksumSHA1);
 
        // only file loader based documents support updating checksum alg:
        solution = solution.WithProjectChecksumAlgorithm(projectId, SourceHashAlgorithm.Sha256);
        await Verify(solution.GetRequiredDocument(documentAId), SourceHashAlgorithm.Sha1);
        await Verify(solution.GetRequiredDocument(documentBId), SourceHashAlgorithm.Sha1);
        await Verify(solution.GetRequiredDocument(documentCId), SourceHashAlgorithm.Sha1);
        await Verify(solution.GetRequiredDocument(fileDocumentId), SourceHashAlgorithm.Sha256, checksumSHA256);
 
        // only file loader based documents support updating checksum alg:
        solution = solution.WithProjectChecksumAlgorithm(projectId, SourceHashAlgorithm.Sha1);
        await Verify(solution.GetRequiredDocument(documentAId), SourceHashAlgorithm.Sha1);
        await Verify(solution.GetRequiredDocument(documentBId), SourceHashAlgorithm.Sha1);
        await Verify(solution.GetRequiredDocument(documentCId), SourceHashAlgorithm.Sha1);
        await Verify(solution.GetRequiredDocument(fileDocumentId), SourceHashAlgorithm.Sha1, checksumSHA1);
 
        static async Task Verify(Document document, SourceHashAlgorithm expectedAlgorithm, byte[]? expectedChecksum = null)
        {
            Assert.Equal(expectedAlgorithm, document.State.LoadTextOptions.ChecksumAlgorithm);
            Assert.Equal(expectedAlgorithm, (await document.GetTextAsync(default)).ChecksumAlgorithm);
            Assert.Equal(expectedAlgorithm, document.GetTextSynchronously(default).ChecksumAlgorithm);
            Assert.Equal(expectedAlgorithm, (await document.GetRequiredSyntaxTreeAsync(default)).GetText().ChecksumAlgorithm);
            Assert.Equal(expectedAlgorithm, document.GetRequiredSyntaxTreeSynchronously(default).GetText().ChecksumAlgorithm);
 
            if (expectedChecksum != null)
            {
                Assert.Equal(expectedChecksum, document.GetTextSynchronously(default).GetChecksum());
            }
 
            var compilation = await document.Project.GetRequiredCompilationAsync(default);
            var tree = compilation.SyntaxTrees.Single(t => t.FilePath == document.FilePath);
            Assert.Equal(expectedAlgorithm, (await tree.GetTextAsync(default)).ChecksumAlgorithm);
            Assert.Equal(expectedAlgorithm, tree.GetText(default).ChecksumAlgorithm);
        }
    }
 
    [Fact]
    public void WithProjectName()
    {
        var projectId = ProjectId.CreateNewId();
 
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution
                        .AddProject(projectId, "proj1", "proj1.dll", LanguageNames.CSharp);
 
        // any character is allowed
        var projectName = "\0<>a/b/*";
 
        SolutionTestHelpers.TestProperty(
            solution,
            (s, value) => s.WithProjectName(projectId, value),
            s => s.GetRequiredProject(projectId).Name,
            projectName,
            defaultThrows: true);
 
        Assert.Throws<ArgumentNullException>("projectId", () => solution.WithProjectName(null!, "x"));
        Assert.Throws<InvalidOperationException>(() => solution.WithProjectName(ProjectId.CreateNewId(), "x"));
    }
 
    [Fact]
    public void WithProjectFilePath()
    {
        var projectId = ProjectId.CreateNewId();
 
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution
                        .AddProject(projectId, "proj1", "proj1.dll", LanguageNames.CSharp);
 
        // any character is allowed
        var path = "\0<>a/b/*.csproj";
 
        SolutionTestHelpers.TestProperty(
            solution,
            (s, value) => s.WithProjectFilePath(projectId, value),
            s => s.GetRequiredProject(projectId).FilePath,
            (string?)path,
            defaultThrows: false);
 
        Assert.Throws<ArgumentNullException>("projectId", () => solution.WithProjectFilePath(null!, "x"));
        Assert.Throws<InvalidOperationException>(() => solution.WithProjectFilePath(ProjectId.CreateNewId(), "x"));
    }
 
    [Fact]
    public void WithProjectCompilationOptionsExceptionHandling()
    {
        var projectId = ProjectId.CreateNewId();
 
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution
                        .AddProject(projectId, "proj1", "proj1.dll", LanguageNames.CSharp);
 
        var options = new CSharpCompilationOptions(OutputKind.NetModule);
 
        Assert.Throws<ArgumentNullException>("projectId", () => solution.WithProjectCompilationOptions(null!, options));
        Assert.Throws<ArgumentNullException>("options", () => solution.WithProjectCompilationOptions(projectId, options: null!));
        Assert.Throws<InvalidOperationException>(() => solution.WithProjectCompilationOptions(ProjectId.CreateNewId(), options));
    }
 
    [Theory, CombinatorialData]
    public void WithProjectCompilationOptionsReplacesSyntaxTreeOptionProvider([CombinatorialValues(LanguageNames.CSharp, LanguageNames.VisualBasic)] string languageName)
    {
        var projectId = ProjectId.CreateNewId();
 
        using var workspace = CreateWorkspace();
 
        var solution = workspace.CurrentSolution
            .AddProject(projectId, "proj1", "proj1.dll", languageName);
 
        // We always have a non-null SyntaxTreeOptionsProvider for C# and VB projects
        var originalSyntaxTreeOptionsProvider = solution.Projects.Single().CompilationOptions!.SyntaxTreeOptionsProvider;
        Assert.NotNull(originalSyntaxTreeOptionsProvider);
 
        var defaultOptions = solution.Projects.Single().Services.GetRequiredService<ICompilationFactoryService>().GetDefaultCompilationOptions();
        Assert.Null(defaultOptions.SyntaxTreeOptionsProvider);
 
        solution = solution.WithProjectCompilationOptions(projectId, defaultOptions);
 
        // The CompilationOptions we replaced with didn't have a SyntaxTreeOptionsProvider, but we would have placed it
        // back. The SyntaxTreeOptionsProvider should behave the same as the prior one and thus should be equal.
        var newSyntaxTreeOptionsProvider = solution.Projects.Single().CompilationOptions!.SyntaxTreeOptionsProvider;
        Assert.NotNull(newSyntaxTreeOptionsProvider);
        Assert.Equal(originalSyntaxTreeOptionsProvider, newSyntaxTreeOptionsProvider);
    }
 
    [Fact]
    public void WithProjectParseOptions()
    {
        var projectId = ProjectId.CreateNewId();
 
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution
                        .AddProject(projectId, "proj1", "proj1.dll", LanguageNames.CSharp);
 
        var options = new CSharpParseOptions(CS.LanguageVersion.CSharp1);
 
        SolutionTestHelpers.TestProperty(
            solution,
            (s, value) => s.WithProjectParseOptions(projectId, value),
            s => s.GetRequiredProject(projectId).ParseOptions!,
            (ParseOptions)options,
            defaultThrows: true);
 
        Assert.Throws<ArgumentNullException>("projectId", () => solution.WithProjectParseOptions(null!, options));
        Assert.Throws<ArgumentNullException>("options", () => solution.WithProjectParseOptions(projectId, options: null!));
        Assert.Throws<InvalidOperationException>(() => solution.WithProjectParseOptions(ProjectId.CreateNewId(), options));
    }
 
    [Fact]
    public async Task ChangingLanguageVersionReparses()
    {
        var projectId = ProjectId.CreateNewId();
        var documentId = DocumentId.CreateNewId(projectId);
 
        using var workspace = CreateWorkspace();
        var document = workspace.CurrentSolution
                        .AddProject(projectId, "proj1", "proj1.dll", LanguageNames.CSharp)
                        .AddDocument(documentId, "Test.cs", "// File")
                        .GetRequiredDocument(documentId);
 
        var oldTree = await document.GetRequiredSyntaxTreeAsync(CancellationToken.None);
 
        Assert.Equal(document.Project.ParseOptions, oldTree.Options);
 
        document = document.Project.WithParseOptions(new CSharpParseOptions(languageVersion: CS.LanguageVersion.CSharp1)).GetRequiredDocument(documentId);
 
        var newTree = await document.GetRequiredSyntaxTreeAsync(CancellationToken.None);
 
        Assert.Equal(document.Project.ParseOptions, newTree.Options);
 
        Assert.False(oldTree.GetRoot().IsIncrementallyIdenticalTo(newTree.GetRoot()));
    }
 
    [Theory]
    [InlineData("#if DEBUG", false, LanguageNames.CSharp)]
    [InlineData(@"#region ""goo""", true, LanguageNames.CSharp)]
    [InlineData(@"#nullable enable", true, LanguageNames.CSharp)]
    [InlineData(@"#elif DEBUG", true, LanguageNames.CSharp)]
    [InlineData("// File", true, LanguageNames.CSharp)]
    [InlineData("#if DEBUG", false, LanguageNames.VisualBasic)]
    [InlineData(@"#region ""goo""", true, LanguageNames.VisualBasic)]
    [InlineData(@"#ElseIf DEBUG", true, LanguageNames.VisualBasic)]
    [InlineData("' File", true, LanguageNames.VisualBasic)]
    public async Task ChangingPreprocessorDirectivesMayReparse(string source, bool expectReuse, string languageName)
    {
        var projectId = ProjectId.CreateNewId();
        var documentId = DocumentId.CreateNewId(projectId);
 
        using var workspace = CreateWorkspace();
        var document = workspace.CurrentSolution
                        .AddProject(projectId, "proj1", "proj1.dll", languageName)
                        .AddDocument(documentId, "Test", source)
                        .GetRequiredDocument(documentId);
 
        var oldTree = await document.GetRequiredSyntaxTreeAsync(CancellationToken.None);
 
        // Hold onto the old root, so we don't actually release the root; if the root were to fall away
        // we're unable to use IsIncrementallyIdenticalTo to see if we didn't reparse, since asking for
        // the old root will recover the tree and produce a new green node.
        var oldRoot = oldTree.GetRoot();
 
        Assert.Equal(document.Project.ParseOptions, oldTree.Options);
 
        ParseOptions newOptions = languageName == LanguageNames.CSharp
            ? new CSharpParseOptions(preprocessorSymbols: ["DEBUG"])
            : new VisualBasicParseOptions(preprocessorSymbols: [new("DEBUG", null)]);
 
        document = document.Project.WithParseOptions(newOptions).GetRequiredDocument(documentId);
 
        var newTree = await document.GetRequiredSyntaxTreeAsync(CancellationToken.None);
 
        Assert.Equal(document.Project.ParseOptions, newTree.Options);
 
        Assert.Equal(expectReuse, oldRoot.IsIncrementallyIdenticalTo(newTree.GetRoot()));
    }
 
    [Theory]
    [InlineData(null, "test.cs")]
    [InlineData("test.cs", null)]
    [InlineData("", null)]
    [InlineData("test.cs", "")]
    public async Task ChangingFilePathReparses(string oldPath, string newPath)
    {
        var projectId = ProjectId.CreateNewId();
        var documentId = DocumentId.CreateNewId(projectId);
 
        using var workspace = CreateWorkspace();
        var document = workspace.CurrentSolution
                        .AddProject(projectId, "proj1", "proj1.dll", LanguageNames.CSharp)
                        .AddDocument(documentId, name: "Test.cs", text: "// File", filePath: oldPath)
                        .GetRequiredDocument(documentId);
 
        var oldTree = await document.GetRequiredSyntaxTreeAsync(CancellationToken.None);
        var newDocument = document.WithFilePath(newPath);
        var newTree = await newDocument.GetRequiredSyntaxTreeAsync(CancellationToken.None);
 
        Assert.False(oldTree.GetRoot().IsIncrementallyIdenticalTo(newTree.GetRoot()));
    }
 
    [Fact]
    public async Task ChangingName_ReparsesWhenPathIsNull()
    {
        var projectId = ProjectId.CreateNewId();
        var documentId = DocumentId.CreateNewId(projectId);
 
        using var workspace = CreateWorkspace();
        var document = workspace.CurrentSolution
                        .AddProject(projectId, "proj1", "proj1.dll", LanguageNames.CSharp)
                        .AddDocument(documentId, name: "name1", text: "// File", filePath: null)
                        .GetRequiredDocument(documentId);
 
        var oldTree = await document.GetRequiredSyntaxTreeAsync(CancellationToken.None);
        var newDocument = document.WithName("name2");
        var newTree = await newDocument.GetRequiredSyntaxTreeAsync(CancellationToken.None);
 
        Assert.False(oldTree.GetRoot().IsIncrementallyIdenticalTo(newTree.GetRoot()));
    }
 
    [Fact]
    public async Task ChangingName_NoReparse()
    {
        var projectId = ProjectId.CreateNewId();
        var documentId = DocumentId.CreateNewId(projectId);
 
        using var workspace = CreateWorkspace();
        var document = workspace.CurrentSolution
                        .AddProject(projectId, "proj1", "proj1.dll", LanguageNames.CSharp)
                        .AddDocument(documentId, name: "name1", text: "// File", filePath: "")
                        .GetRequiredDocument(documentId);
 
        var oldTree = await document.GetRequiredSyntaxTreeAsync(CancellationToken.None);
        var newDocument = document.WithName("name2");
        var newTree = await newDocument.GetRequiredSyntaxTreeAsync(CancellationToken.None);
 
        Assert.True(oldTree.GetRoot().IsIncrementallyIdenticalTo(newTree.GetRoot()));
    }
 
    [Fact]
    public void WithProjectReferences()
    {
        using var workspace = CreateWorkspaceWithProjectAndDocuments();
        var solution = workspace.CurrentSolution;
        var projectId = solution.Projects.Single().Id;
 
        var projectId2 = ProjectId.CreateNewId();
        solution = solution.AddProject(projectId2, "proj2", "proj2.dll", LanguageNames.CSharp);
        var projectRef = new ProjectReference(projectId2);
 
        SolutionTestHelpers.TestListProperty(solution,
            (old, value) => old.WithProjectReferences(projectId, value),
            opt => opt.GetProject(projectId)!.AllProjectReferences,
            projectRef,
            allowDuplicates: false);
 
        var projectRefs = (IEnumerable<ProjectReference>)ImmutableArray.Create(
            new ProjectReference(projectId2),
            new ProjectReference(projectId2, ImmutableArray.Create("alias")),
            new ProjectReference(projectId2, embedInteropTypes: true));
 
        var solution2 = solution.WithProjectReferences(projectId, projectRefs);
        Assert.Same(projectRefs, solution2.GetProject(projectId)!.AllProjectReferences);
 
        Assert.Throws<ArgumentNullException>("projectId", () => solution.WithProjectReferences(null!, [projectRef]));
        Assert.Throws<InvalidOperationException>(() => solution.WithProjectReferences(ProjectId.CreateNewId(), [projectRef]));
 
        // cycles:
        Assert.Throws<InvalidOperationException>(() => solution2.WithProjectReferences(projectId2, [new ProjectReference(projectId)]));
        Assert.Throws<InvalidOperationException>(() => solution.WithProjectReferences(projectId, [new ProjectReference(projectId)]));
    }
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/42406")]
    public void WithProjectReferences_ProjectNotInSolution()
    {
        using var workspace = CreateWorkspaceWithProjectAndDocuments();
        var solution = workspace.CurrentSolution;
        var projectId = solution.Projects.Single().Id;
        var externalProjectRef = new ProjectReference(ProjectId.CreateNewId());
 
        var projectRefs = (IEnumerable<ProjectReference>)ImmutableArray.Create(externalProjectRef);
        var newSolution1 = solution.WithProjectReferences(projectId, projectRefs);
        Assert.Same(projectRefs, newSolution1.GetProject(projectId)!.AllProjectReferences);
 
        // project reference is not included:
        Assert.Empty(newSolution1.GetProject(projectId)!.ProjectReferences);
    }
 
    [Fact]
    public void AddProjectReferences()
    {
        using var workspace = CreateWorkspaceWithProjectAndDocuments();
        var solution = workspace.CurrentSolution;
        var projectId = solution.Projects.Single().Id;
        var projectId2 = ProjectId.CreateNewId();
        var projectId3 = ProjectId.CreateNewId();
 
        solution = solution
            .AddProject(projectId2, "proj2", "proj2.dll", LanguageNames.CSharp)
            .AddProject(projectId3, "proj3", "proj3.dll", LanguageNames.CSharp);
 
        var projectRef2 = new ProjectReference(projectId2);
        var projectRef3 = new ProjectReference(projectId3);
        var externalProjectRef = new ProjectReference(ProjectId.CreateNewId());
 
        solution = solution.AddProjectReference(projectId3, projectRef2);
 
        var solution2 = solution.AddProjectReferences(projectId, EmptyEnumerable<ProjectReference>());
        Assert.Same(solution, solution2);
 
        var e = OnceEnumerable(projectRef2, externalProjectRef);
 
        var solution3 = solution.AddProjectReferences(projectId, e);
        AssertEx.Equal((ProjectReference[])[projectRef2], solution3.GetProject(projectId)!.ProjectReferences);
        AssertEx.Equal((ProjectReference[])[projectRef2, externalProjectRef], solution3.GetProject(projectId)!.AllProjectReferences);
 
        Assert.Throws<ArgumentNullException>("projectId", () => solution.AddProjectReferences(null!, [projectRef2]));
        Assert.Throws<ArgumentNullException>("projectReferences", () => solution.AddProjectReferences(projectId, null!));
        Assert.Throws<ArgumentNullException>("projectReferences[0]", () => solution.AddProjectReferences(projectId, [null!]));
        Assert.Throws<ArgumentException>("projectReferences[1]", () => solution.AddProjectReferences(projectId, [projectRef2, projectRef2]));
        Assert.Throws<ArgumentException>("projectReferences[1]", () => solution.AddProjectReferences(projectId, [new ProjectReference(projectId2), new ProjectReference(projectId2)]));
 
        // dup:
        Assert.Throws<InvalidOperationException>(() => solution.AddProjectReferences(projectId3, [projectRef2]));
 
        // cycles:
        Assert.Throws<InvalidOperationException>(() => solution3.AddProjectReferences(projectId2, [projectRef3]));
        Assert.Throws<InvalidOperationException>(() => solution3.AddProjectReferences(projectId, [new ProjectReference(projectId)]));
    }
 
    [Fact]
    public void RemoveProjectReference()
    {
        using var workspace = CreateWorkspaceWithProjectAndDocuments();
        var solution = workspace.CurrentSolution;
        var projectId = solution.Projects.Single().Id;
 
        var projectId2 = ProjectId.CreateNewId();
        solution = solution.AddProject(projectId2, "proj2", "proj2.dll", LanguageNames.CSharp);
        var projectRef2 = new ProjectReference(projectId2);
        var externalProjectRef = new ProjectReference(ProjectId.CreateNewId());
 
        solution = solution.WithProjectReferences(projectId, [projectRef2, externalProjectRef]);
 
        // remove reference to a project that's not part of the solution:
        var solution2 = solution.RemoveProjectReference(projectId, externalProjectRef);
        AssertEx.Equal((ProjectReference[])[projectRef2], solution2.GetProject(projectId)!.AllProjectReferences);
 
        // remove reference to a project that's part of the solution:
        var solution3 = solution.RemoveProjectReference(projectId, projectRef2);
        AssertEx.Equal((ProjectReference[])[externalProjectRef], solution3.GetProject(projectId)!.AllProjectReferences);
 
        var solution4 = solution3.RemoveProjectReference(projectId, externalProjectRef);
        Assert.Empty(solution4.GetProject(projectId)!.AllProjectReferences);
 
        Assert.Throws<ArgumentNullException>("projectId", () => solution.RemoveProjectReference(null!, projectRef2));
        Assert.Throws<ArgumentNullException>("projectReference", () => solution.RemoveProjectReference(projectId, null!));
 
        // removing a reference that's not in the list:
        Assert.Throws<ArgumentException>("projectReference", () => solution.RemoveProjectReference(projectId, new ProjectReference(ProjectId.CreateNewId())));
 
        // project not in solution:
        Assert.Throws<InvalidOperationException>(() => solution.RemoveProjectReference(ProjectId.CreateNewId(), projectRef2));
    }
 
    [Fact]
    public void ProjectReferences_Submissions()
    {
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution;
 
        var projectId0 = ProjectId.CreateNewId();
        var submissionId1 = ProjectId.CreateNewId();
        var submissionId2 = ProjectId.CreateNewId();
        var submissionId3 = ProjectId.CreateNewId();
 
        solution = solution
            .AddProject(projectId0, "non-submission", "non-submission.dll", LanguageNames.CSharp)
            .AddProject(ProjectInfo.Create(submissionId1, VersionStamp.Default, name: "submission1", assemblyName: "submission1.dll", LanguageNames.CSharp, isSubmission: true))
            .AddProject(ProjectInfo.Create(submissionId2, VersionStamp.Default, name: "submission2", assemblyName: "submission2.dll", LanguageNames.CSharp, isSubmission: true))
            .AddProject(ProjectInfo.Create(submissionId3, VersionStamp.Default, name: "submission3", assemblyName: "submission3.dll", LanguageNames.CSharp, isSubmission: true))
            .AddProjectReference(submissionId2, new ProjectReference(submissionId1))
            .WithProjectReferences(submissionId2, [new ProjectReference(submissionId1)]);
 
        // submission may be referenced from multiple submissions (forming a tree):
        _ = solution.AddProjectReferences(submissionId3, [new ProjectReference(submissionId1)]);
        _ = solution.WithProjectReferences(submissionId3, [new ProjectReference(submissionId1)]);
 
        // submission may reference a non-submission project:
        _ = solution.AddProjectReferences(submissionId3, [new ProjectReference(projectId0)]);
        _ = solution.WithProjectReferences(submissionId3, [new ProjectReference(projectId0)]);
 
        // submission can't reference multiple submissions:
        Assert.Throws<InvalidOperationException>(() => solution.AddProjectReferences(submissionId2, [new ProjectReference(submissionId3)]));
        Assert.Throws<InvalidOperationException>(() => solution.WithProjectReferences(submissionId1, [new ProjectReference(submissionId2), new ProjectReference(submissionId3)]));
 
        // non-submission project can't reference a submission:
        Assert.Throws<InvalidOperationException>(() => solution.AddProjectReferences(projectId0, [new ProjectReference(submissionId1)]));
        Assert.Throws<InvalidOperationException>(() => solution.WithProjectReferences(projectId0, [new ProjectReference(submissionId1)]));
    }
 
    [Fact]
    public void WithProjectMetadataReferences()
    {
        using var workspace = CreateWorkspaceWithProjectAndDocuments();
        var solution = workspace.CurrentSolution;
        var projectId = solution.Projects.Single().Id;
        var metadataRef = (MetadataReference)new TestMetadataReference();
 
        SolutionTestHelpers.TestListProperty(solution,
            (old, value) => old.WithProjectMetadataReferences(projectId, value),
            opt => opt.GetProject(projectId)!.MetadataReferences,
            metadataRef,
            allowDuplicates: false);
 
        Assert.Throws<ArgumentNullException>("projectId", () => solution.WithProjectMetadataReferences(null!, [metadataRef]));
        Assert.Throws<InvalidOperationException>(() => solution.WithProjectMetadataReferences(ProjectId.CreateNewId(), [metadataRef]));
    }
 
    [Fact]
    public void AddMetadataReferences()
    {
        using var workspace = CreateWorkspaceWithProjectAndDocuments();
        var solution = workspace.CurrentSolution;
        var projectId = solution.Projects.Single().Id;
 
        var solution2 = solution.AddMetadataReferences(projectId, EmptyEnumerable<MetadataReference>());
        Assert.Same(solution, solution2);
 
        var metadataRef1 = new TestMetadataReference();
        var metadataRef2 = new TestMetadataReference();
 
        var solution3 = solution.AddMetadataReferences(projectId, OnceEnumerable(metadataRef1, metadataRef2));
        AssertEx.Equal((MetadataReference[])[metadataRef1, metadataRef2], solution3.GetProject(projectId)!.MetadataReferences);
 
        Assert.Throws<ArgumentNullException>("projectId", () => solution.AddMetadataReferences(null!, [metadataRef1]));
        Assert.Throws<ArgumentNullException>("metadataReferences", () => solution.AddMetadataReferences(projectId, null!));
        Assert.Throws<ArgumentNullException>("metadataReferences[0]", () => solution.AddMetadataReferences(projectId, [null!]));
        Assert.Throws<ArgumentException>("metadataReferences[1]", () => solution.AddMetadataReferences(projectId, [metadataRef1, metadataRef1]));
 
        // dup:
        Assert.Throws<InvalidOperationException>(() => solution3.AddMetadataReferences(projectId, [metadataRef1]));
    }
 
    [Fact]
    public void RemoveMetadataReference()
    {
        using var workspace = CreateWorkspaceWithProjectAndDocuments();
        var solution = workspace.CurrentSolution;
        var projectId = solution.Projects.Single().Id;
        var metadataRef1 = new TestMetadataReference();
        var metadataRef2 = new TestMetadataReference();
 
        solution = solution.WithProjectMetadataReferences(projectId, [metadataRef1, metadataRef2]);
 
        var solution2 = solution.RemoveMetadataReference(projectId, metadataRef1);
        AssertEx.Equal((MetadataReference[])[metadataRef2], solution2.GetProject(projectId)!.MetadataReferences);
 
        var solution3 = solution2.RemoveMetadataReference(projectId, metadataRef2);
        Assert.Empty(solution3.GetProject(projectId)!.MetadataReferences);
 
        Assert.Throws<ArgumentNullException>("projectId", () => solution.RemoveMetadataReference(null!, metadataRef1));
        Assert.Throws<ArgumentNullException>("metadataReference", () => solution.RemoveMetadataReference(projectId, null!));
 
        // removing a reference that's not in the list:
        Assert.Throws<InvalidOperationException>(() => solution.RemoveMetadataReference(projectId, new TestMetadataReference()));
 
        // project not in solution:
        Assert.Throws<InvalidOperationException>(() => solution.RemoveMetadataReference(ProjectId.CreateNewId(), metadataRef1));
    }
 
    [Fact]
    public void WithProjectAnalyzerReferences()
    {
        using var workspace = CreateWorkspaceWithProjectAndDocuments();
        var solution = workspace.CurrentSolution;
        var projectId = solution.Projects.Single().Id;
        var analyzerRef = (AnalyzerReference)new TestAnalyzerReference();
 
        SolutionTestHelpers.TestListProperty(solution,
            (old, value) => old.WithProjectAnalyzerReferences(projectId, value),
            opt => opt.GetProject(projectId)!.AnalyzerReferences,
            analyzerRef,
            allowDuplicates: false);
 
        Assert.Throws<ArgumentNullException>("projectId", () => solution.WithProjectAnalyzerReferences(null!, [analyzerRef]));
        Assert.Throws<InvalidOperationException>(() => solution.WithProjectAnalyzerReferences(ProjectId.CreateNewId(), [analyzerRef]));
    }
 
    [Fact]
    public void AddAnalyzerReferences_Project()
    {
        using var workspace = CreateWorkspaceWithProjectAndDocuments();
        var solution = workspace.CurrentSolution;
        var projectId = solution.Projects.Single().Id;
 
        var solution2 = solution.AddAnalyzerReferences(projectId, EmptyEnumerable<AnalyzerReference>());
        Assert.Same(solution, solution2);
 
        var analyzerRef1 = new TestAnalyzerReference();
        var analyzerRef2 = new TestAnalyzerReference();
 
        var solution3 = solution.AddAnalyzerReferences(projectId, OnceEnumerable(analyzerRef1, analyzerRef2));
        AssertEx.Equal((AnalyzerReference[])[analyzerRef1, analyzerRef2], solution3.GetProject(projectId)!.AnalyzerReferences);
 
        var solution4 = solution3.AddAnalyzerReferences(projectId, []);
 
        Assert.Same(solution, solution2);
        Assert.Throws<ArgumentNullException>("projectId", () => solution.AddAnalyzerReferences(null!, [analyzerRef1]));
        Assert.Throws<ArgumentNullException>("analyzerReferences", () => solution.AddAnalyzerReferences(projectId, null!));
        Assert.Throws<ArgumentNullException>("analyzerReferences[0]", () => solution.AddAnalyzerReferences(projectId, [null!]));
        Assert.Throws<ArgumentException>("analyzerReferences[1]", () => solution.AddAnalyzerReferences(projectId, [analyzerRef1, analyzerRef1]));
 
        // dup:
        Assert.Throws<InvalidOperationException>(() => solution3.AddAnalyzerReferences(projectId, [analyzerRef1]));
    }
 
    [Fact]
    public void RemoveAnalyzerReference_Project()
    {
        using var workspace = CreateWorkspaceWithProjectAndDocuments();
        var solution = workspace.CurrentSolution;
        var projectId = solution.Projects.Single().Id;
        var analyzerRef1 = new TestAnalyzerReference();
        var analyzerRef2 = new TestAnalyzerReference();
 
        solution = solution.WithProjectAnalyzerReferences(projectId, [analyzerRef1, analyzerRef2]);
 
        var solution2 = solution.RemoveAnalyzerReference(projectId, analyzerRef1);
        AssertEx.Equal((AnalyzerReference[])[analyzerRef2], solution2.GetProject(projectId)!.AnalyzerReferences);
 
        var solution3 = solution2.RemoveAnalyzerReference(projectId, analyzerRef2);
        Assert.Empty(solution3.GetProject(projectId)!.AnalyzerReferences);
 
        Assert.Throws<ArgumentNullException>("projectId", () => solution.RemoveAnalyzerReference(null!, analyzerRef1));
        Assert.Throws<ArgumentNullException>("analyzerReference", () => solution.RemoveAnalyzerReference(projectId, null!));
 
        // removing a reference that's not in the list:
        Assert.Throws<InvalidOperationException>(() => solution.RemoveAnalyzerReference(projectId, new TestAnalyzerReference()));
 
        // project not in solution:
        Assert.Throws<InvalidOperationException>(() => solution.RemoveAnalyzerReference(ProjectId.CreateNewId(), analyzerRef1));
    }
 
    [Fact]
    public void WithAnalyzerReferences()
    {
        using var workspace = CreateWorkspaceWithProjectAndDocuments();
        var solution = workspace.CurrentSolution;
        var analyzerRef = (AnalyzerReference)new TestAnalyzerReference();
 
        SolutionTestHelpers.TestListProperty(solution,
            (old, value) => old.WithAnalyzerReferences(value),
            opt => opt.AnalyzerReferences,
            analyzerRef,
            allowDuplicates: false);
    }
 
    [Fact]
    public void AddAnalyzerReferences()
    {
        using var workspace = CreateWorkspaceWithProjectAndDocuments();
        var solution = workspace.CurrentSolution;
 
        var solution2 = solution.AddAnalyzerReferences(EmptyEnumerable<AnalyzerReference>());
        Assert.Same(solution, solution2);
 
        var analyzerRef1 = new TestAnalyzerReference();
        var analyzerRef2 = new TestAnalyzerReference();
 
        var solution3 = solution.AddAnalyzerReferences(OnceEnumerable(analyzerRef1, analyzerRef2));
        AssertEx.Equal((AnalyzerReference[])[analyzerRef1, analyzerRef2], solution3.AnalyzerReferences);
 
        var solution4 = solution3.AddAnalyzerReferences([]);
 
        Assert.Same(solution, solution2);
        Assert.Throws<ArgumentNullException>("analyzerReferences", () => solution.AddAnalyzerReferences(null!));
        Assert.Throws<ArgumentNullException>("analyzerReferences[0]", () => solution.AddAnalyzerReferences([null!]));
        Assert.Throws<ArgumentException>("analyzerReferences[1]", () => solution.AddAnalyzerReferences([analyzerRef1, analyzerRef1]));
 
        // dup:
        Assert.Throws<InvalidOperationException>(() => solution3.AddAnalyzerReferences([analyzerRef1]));
    }
 
    [Fact]
    public void RemoveAnalyzerReference()
    {
        using var workspace = CreateWorkspaceWithProjectAndDocuments();
        var solution = workspace.CurrentSolution;
        var analyzerRef1 = new TestAnalyzerReference();
        var analyzerRef2 = new TestAnalyzerReference();
 
        solution = solution.WithAnalyzerReferences([analyzerRef1, analyzerRef2]);
 
        var solution2 = solution.RemoveAnalyzerReference(analyzerRef1);
        AssertEx.Equal((AnalyzerReference[])[analyzerRef2], solution2.AnalyzerReferences);
 
        var solution3 = solution2.RemoveAnalyzerReference(analyzerRef2);
        Assert.Empty(solution3.AnalyzerReferences);
 
        Assert.Throws<ArgumentNullException>("analyzerReference", () => solution.RemoveAnalyzerReference(null!));
 
        // removing a reference that's not in the list:
        Assert.Throws<InvalidOperationException>(() => solution.RemoveAnalyzerReference(new TestAnalyzerReference()));
    }
 
    [Theory]
    [CombinatorialData]
    public void FallbackAnalyzerOptions(bool isRoot)
    {
        using var workspace = CreateWorkspaceWithProjectAndDocuments(editorConfig: $"""
            {(isRoot ? "root = true" : "")}
            [*]
            optionA = A
            """);
        var solution = workspace.CurrentSolution;
 
        Assert.True(solution.FallbackAnalyzerOptions.IsEmpty);
 
        var solution2 = solution.WithFallbackAnalyzerOptions(ImmutableDictionary<string, StructuredAnalyzerConfigOptions>.Empty
            .Add(LanguageNames.CSharp, StructuredAnalyzerConfigOptions.Create(new DictionaryAnalyzerConfigOptions(ImmutableDictionary<string, string>.Empty
                .Add("optionA", "fallbackA")
                .Add("optionB", "fallbackB")))));
 
        TestOptionValues(solution2, expectedA: "A", hasBWithoutFallback: false, expectedB: "fallbackB", expectedBFile: "fallbackB");
 
        var solution3 = solution2.WithFallbackAnalyzerOptions(ImmutableDictionary<string, StructuredAnalyzerConfigOptions>.Empty
           .Add(LanguageNames.CSharp, StructuredAnalyzerConfigOptions.Create(new DictionaryAnalyzerConfigOptions(ImmutableDictionary<string, string>.Empty
               .Add("optionA", "fallbackX")
               .Add("optionB", "fallbackY")))));
 
        TestOptionValues(solution3, expectedA: "A", hasBWithoutFallback: false, expectedB: "fallbackY", expectedBFile: "fallbackY");
 
        Assert.True(workspace.TryApplyChanges(solution3));
        TestOptionValues(workspace.CurrentSolution, expectedA: "A", hasBWithoutFallback: false, expectedB: "fallbackY", expectedBFile: "fallbackY");
 
        var editorConfigContent = """
            [*]
            optionB = ec2
            """;
        var editorConfigId = DocumentId.CreateNewId(solution3.Projects.Single().Id);
        var solution4 = solution3.AddAnalyzerConfigDocument(editorConfigId, ".editorconfig", SourceText.From(editorConfigContent), filePath: Path.Combine(s_projectDir, "subfolder", ".editorconfig"));
 
        TestOptionValues(solution4, expectedA: "A", hasBWithoutFallback: true, expectedB: "fallbackY", expectedBFile: "ec2");
 
        static void TestOptionValues(Solution solution, string expectedA, bool hasBWithoutFallback, string expectedB, string expectedBFile)
        {
            var project2 = solution.Projects.Single();
 
            var projectOptions = project2.GetAnalyzerConfigOptions();
            Assert.NotNull(projectOptions);
 
            Assert.True(projectOptions!.Value.ConfigOptionsWithoutFallback.TryGetValue("optionA", out var value1));
            Assert.Equal(expectedA, value1);
            Assert.True(projectOptions!.Value.ConfigOptionsWithFallback.TryGetValue("optionA", out value1));
            Assert.Equal(expectedA, value1);
 
            Assert.False(projectOptions!.Value.ConfigOptionsWithoutFallback.TryGetValue("optionB", out var value2));
            Assert.Null(value2);
            Assert.True(projectOptions!.Value.ConfigOptionsWithFallback.TryGetValue("optionB", out value2));
            Assert.Equal(expectedB, value2);
 
            var sourcePathOptions = project2.State.GetAnalyzerOptionsForPath(Path.Combine(s_projectDir, "x.cs"), CancellationToken.None);
            Assert.True(sourcePathOptions.ConfigOptionsWithoutFallback.TryGetValue("optionA", out var value3));
            Assert.Equal(expectedA, value3);
            Assert.True(sourcePathOptions.ConfigOptionsWithFallback.TryGetValue("optionA", out value3));
            Assert.Equal(expectedA, value3);
 
            Assert.False(sourcePathOptions.ConfigOptionsWithoutFallback.TryGetValue("optionB", out var value4));
            Assert.Null(value4);
            Assert.True(sourcePathOptions.ConfigOptionsWithFallback.TryGetValue("optionB", out value4));
            Assert.Equal(expectedB, value4);
 
            sourcePathOptions = project2.State.GetAnalyzerOptionsForPath(Path.Combine(s_projectDir, "subfolder", "x.cs"), CancellationToken.None);
            Assert.True(sourcePathOptions.ConfigOptionsWithoutFallback.TryGetValue("optionA", out var value5));
            Assert.Equal(expectedA, value5);
            Assert.True(sourcePathOptions.ConfigOptionsWithFallback.TryGetValue("optionA", out value5));
            Assert.Equal(expectedA, value5);
 
            Assert.Equal(hasBWithoutFallback, sourcePathOptions.ConfigOptionsWithoutFallback.TryGetValue("optionB", out var value6));
            Assert.Equal(hasBWithoutFallback ? expectedBFile : null, value6);
            Assert.True(sourcePathOptions.ConfigOptionsWithFallback.TryGetValue("optionB", out value6));
            Assert.Equal(expectedBFile, value6);
        }
    }
 
    [Fact]
    public void AddDocument_Loader()
    {
        var projectId = ProjectId.CreateNewId();
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution.AddProject(projectId, "proj1", "proj1.dll", LanguageNames.CSharp).
            WithProjectChecksumAlgorithm(projectId, SourceHashAlgorithms.Default).
            WithProjectParseOptions(projectId, new CSharpParseOptions(kind: SourceCodeKind.Script));
 
        var loader = new TestTextLoader(checksumAlgorithm: SourceHashAlgorithm.Sha1);
        var documentId = DocumentId.CreateNewId(projectId);
        var folders = new[] { "folder1", "folder2" };
 
        var solution2 = solution.AddDocument(documentId, "name", loader, folders);
        var document = solution2.GetRequiredDocument(documentId);
        AssertEx.Equal(folders, document.Folders);
        Assert.Equal(SourceCodeKind.Script, document.SourceCodeKind);
        Assert.Equal(SourceHashAlgorithm.Sha1, document.GetTextSynchronously(default).ChecksumAlgorithm);
 
        Assert.Throws<ArgumentNullException>("documentId", () => solution.AddDocument(documentId: null!, "name", loader));
        Assert.Throws<ArgumentNullException>("name", () => solution.AddDocument(documentId, name: null!, loader));
        Assert.Throws<ArgumentNullException>("loader", () => solution.AddDocument(documentId, "name", loader: null!));
        Assert.Throws<InvalidOperationException>(() => solution.AddDocument(documentId: DocumentId.CreateNewId(ProjectId.CreateNewId()), "name", loader));
    }
 
    [Fact]
    public void AddDocument_Text()
    {
        var projectId = ProjectId.CreateNewId();
        using var workspace = CreateWorkspace();
 
        var solution = workspace.CurrentSolution.AddProject(projectId, "proj1", "proj1.dll", LanguageNames.CSharp).
            WithProjectChecksumAlgorithm(projectId, SourceHashAlgorithms.Default).
            WithProjectParseOptions(projectId, new CSharpParseOptions(kind: SourceCodeKind.Script));
 
        var documentId = DocumentId.CreateNewId(projectId);
        var filePath = Path.Combine(TempRoot.Root, "x.cs");
        var folders = new[] { "folder1", "folder2" };
 
        var solution2 = solution.AddDocument(documentId, "name", "text", folders, filePath);
        var document = solution2.GetRequiredDocument(documentId);
        var sourceText = document.GetTextSynchronously(default);
 
        Assert.Equal("text", sourceText.ToString());
        Assert.Equal(SourceHashAlgorithms.Default, sourceText.ChecksumAlgorithm);
        AssertEx.Equal(folders, document.Folders);
        Assert.Equal(filePath, document.FilePath);
        Assert.False(document.State.Attributes.IsGenerated);
        Assert.Equal(SourceCodeKind.Script, document.SourceCodeKind);
 
        Assert.Throws<ArgumentNullException>("documentId", () => solution.AddDocument(documentId: null!, "name", "text"));
        Assert.Throws<ArgumentNullException>("name", () => solution.AddDocument(documentId, name: null!, "text"));
        Assert.Throws<ArgumentNullException>("text", () => solution.AddDocument(documentId, "name", text: (string)null!));
        Assert.Throws<InvalidOperationException>(() => solution.AddDocument(documentId: DocumentId.CreateNewId(ProjectId.CreateNewId()), "name", "text"));
    }
 
    [Fact]
    public void AddDocument_SourceText()
    {
        var projectId = ProjectId.CreateNewId();
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution.AddProject(projectId, "proj1", "proj1.dll", LanguageNames.CSharp).
            WithProjectChecksumAlgorithm(projectId, SourceHashAlgorithms.Default).
            WithProjectParseOptions(projectId, new CSharpParseOptions(kind: SourceCodeKind.Script));
 
        var documentId = DocumentId.CreateNewId(projectId);
        var sourceText = SourceText.From("text", checksumAlgorithm: SourceHashAlgorithms.Default);
        var filePath = Path.Combine(TempRoot.Root, "x.cs");
        var folders = new[] { "folder1", "folder2" };
 
        var solution2 = solution.AddDocument(documentId, "name", sourceText, folders, filePath, isGenerated: true);
        var document = solution2.GetRequiredDocument(documentId);
 
        AssertEx.Equal(folders, document.Folders);
        Assert.Equal(filePath, document.FilePath);
        Assert.True(document.State.Attributes.IsGenerated);
        Assert.Equal(SourceCodeKind.Script, document.SourceCodeKind);
 
        Assert.Throws<ArgumentNullException>("documentId", () => solution.AddDocument(documentId: null!, "name", sourceText));
        Assert.Throws<ArgumentNullException>("name", () => solution.AddDocument(documentId, name: null!, sourceText));
        Assert.Throws<ArgumentNullException>("text", () => solution.AddDocument(documentId, "name", text: (SourceText)null!));
        Assert.Throws<InvalidOperationException>(() => solution.AddDocument(documentId: DocumentId.CreateNewId(ProjectId.CreateNewId()), "name", sourceText));
    }
 
    [Fact]
    public void AddDocument_SyntaxRoot()
    {
        var projectId = ProjectId.CreateNewId();
        using var workspace = CreateWorkspace();
 
        var solution = workspace.CurrentSolution.AddProject(projectId, "proj1", "proj1.dll", LanguageNames.CSharp).
            WithProjectChecksumAlgorithm(projectId, SourceHashAlgorithms.Default).
            WithProjectParseOptions(projectId, new CSharpParseOptions(kind: SourceCodeKind.Script));
 
        var documentId = DocumentId.CreateNewId(projectId);
        var filePath = Path.Combine(TempRoot.Root, "x.cs");
        var folders = new[] { "folder1", "folder2" };
 
        var root = CSharp.SyntaxFactory.ParseCompilationUnit("class C {}");
        var solution2 = solution.AddDocument(documentId, "name", root, folders, filePath);
        var document2 = solution2.GetRequiredDocument(documentId);
 
        AssertEx.Equal(folders, document2.Folders);
        Assert.Equal(filePath, document2.FilePath);
        Assert.False(document2.State.Attributes.IsGenerated);
        Assert.Equal(SourceCodeKind.Script, document2.SourceCodeKind);
 
        Assert.Throws<ArgumentNullException>("documentId", () => solution.AddDocument(documentId: null!, "name", root));
        Assert.Throws<ArgumentNullException>("name", () => solution.AddDocument(documentId, name: null!, root));
        Assert.Throws<ArgumentNullException>("syntaxRoot", () => solution.AddDocument(documentId, "name", syntaxRoot: null!));
        Assert.Throws<InvalidOperationException>(() => solution.AddDocument(documentId: DocumentId.CreateNewId(ProjectId.CreateNewId()), "name", syntaxRoot: root));
    }
 
    [Fact]
    public void AddDocument_SyntaxRoot_ExplicitTree()
    {
        var projectId = ProjectId.CreateNewId();
        using var workspace = CreateWorkspace();
 
        var solution = workspace.CurrentSolution.AddProject(projectId, "proj1", "proj1.dll", LanguageNames.CSharp).
            WithProjectChecksumAlgorithm(projectId, SourceHashAlgorithms.Default).
            WithProjectParseOptions(projectId, new CSharpParseOptions(kind: SourceCodeKind.Script));
 
        var documentId = DocumentId.CreateNewId(projectId);
        var filePath = Path.Combine(TempRoot.Root, "x.cs");
        var folders = new[] { "folder1", "folder2" };
 
        var root = CSharp.SyntaxFactory.ParseSyntaxTree(SourceText.From("class C {}", encoding: null, SourceHashAlgorithm.Sha1)).GetRoot();
        Assert.Equal(SourceHashAlgorithm.Sha1, root.SyntaxTree.GetText().ChecksumAlgorithm);
 
        var solution2 = solution.AddDocument(documentId, "name", root, folders, filePath);
        var document2 = solution2.GetRequiredDocument(documentId);
        var sourceText = document2.GetTextSynchronously(default);
        Assert.Equal("class C {}", sourceText.ToString());
 
        // the checksum algorithm of the tree is ignored, instead the one set on the project is used:
        Assert.Equal(SourceHashAlgorithms.Default, sourceText.ChecksumAlgorithm);
 
        AssertEx.Equal(folders, document2.Folders);
        Assert.Equal(filePath, document2.FilePath);
        Assert.False(document2.State.Attributes.IsGenerated);
        Assert.Equal(SourceCodeKind.Script, document2.SourceCodeKind);
    }
 
    [Fact]
    public void AddDocument_SyntaxRoot_SynthesizedTree()
    {
        var projectId = ProjectId.CreateNewId();
        using var workspace = CreateWorkspace();
 
        var solution = workspace.CurrentSolution.AddProject(projectId, "proj1", "proj1.dll", LanguageNames.CSharp).
            WithProjectChecksumAlgorithm(projectId, SourceHashAlgorithms.Default).
            WithProjectParseOptions(projectId, new CSharpParseOptions(kind: SourceCodeKind.Script));
 
        var documentId = DocumentId.CreateNewId(projectId);
 
        var root = CSharp.SyntaxFactory.ParseCompilationUnit("class C {}");
        Assert.Equal(SourceHashAlgorithm.Sha1, root.SyntaxTree.GetText().ChecksumAlgorithm);
 
        var solution2 = solution.AddDocument(documentId, "name", root);
        var document2 = solution2.GetRequiredDocument(documentId);
        var sourceText = document2.GetTextSynchronously(default);
        Assert.Equal("class C {}", sourceText.ToString());
 
        // the checksum algorithm of the tree is ignored, instead the one set on the project is used:
        Assert.Equal(SourceHashAlgorithms.Default, sourceText.ChecksumAlgorithm);
    }
 
#nullable disable
    [Fact]
    public void TestAddProject()
    {
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution;
        var pid = ProjectId.CreateNewId();
        solution = solution.AddProject(pid, "goo", "goo.dll", LanguageNames.CSharp);
        Assert.True(solution.ProjectIds.Any(), "Solution was expected to have projects");
        Assert.NotNull(pid);
        var project = solution.GetProject(pid);
        Assert.False(project.HasDocuments);
    }
 
    [Fact]
    public void TestUpdateAssemblyName()
    {
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution;
        var project1 = ProjectId.CreateNewId();
        solution = solution.AddProject(project1, "goo", "goo.dll", LanguageNames.CSharp);
        solution = solution.WithProjectAssemblyName(project1, "bar");
        var project = solution.GetProject(project1);
        Assert.Equal("bar", project.AssemblyName);
    }
 
    [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/543964")]
    public void MultipleProjectsWithSameDisplayName()
    {
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution;
        var project1 = ProjectId.CreateNewId();
        var project2 = ProjectId.CreateNewId();
        solution = solution.AddProject(project1, "name", "assemblyName", LanguageNames.CSharp);
        solution = solution.AddProject(project2, "name", "assemblyName", LanguageNames.CSharp);
        Assert.Equal(2, solution.GetProjectsByName("name").Count());
    }
 
    [Fact]
    public async Task TestAddFirstDocumentAsync()
    {
        var pid = ProjectId.CreateNewId();
        var did = DocumentId.CreateNewId(pid);
 
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution
            .AddProject(pid, "goo", "goo.dll", LanguageNames.CSharp)
            .AddDocument(did, "goo.cs", "public class Goo { }");
 
        // verify project & document
        Assert.NotNull(pid);
        var project = solution.GetProject(pid);
        Assert.NotNull(project);
        Assert.True(solution.ContainsProject(pid), "Solution was expected to have project " + pid);
        Assert.True(project.HasDocuments, "Project was expected to have documents");
        Assert.Equal(project, solution.GetProject(pid));
        Assert.NotNull(did);
        var document = solution.GetDocument(did);
        Assert.True(project.ContainsDocument(did), "Project was expected to have document " + did);
        Assert.Equal(document, project.GetDocument(did));
        Assert.Equal(document, solution.GetDocument(did));
        var semantics = await document.GetSemanticModelAsync();
        Assert.NotNull(semantics);
 
        await ValidateSolutionAndCompilationsAsync(solution);
 
        var pid2 = solution.Projects.Single().Id;
        var did2 = DocumentId.CreateNewId(pid2);
        solution = solution.AddDocument(did2, "bar.cs", "public class Bar { }");
 
        // verify project & document
        var project2 = solution.GetProject(pid2);
        Assert.NotNull(project2);
        Assert.NotNull(did2);
        var document2 = solution.GetDocument(did2);
        Assert.True(project2.ContainsDocument(did2), "Project was expected to have document " + did2);
        Assert.Equal(document2, project2.GetDocument(did2));
        Assert.Equal(document2, solution.GetDocument(did2));
 
        await ValidateSolutionAndCompilationsAsync(solution);
    }
 
    [Fact]
    public async Task AddTwoDocumentsForSingleProject()
    {
        var projectId = ProjectId.CreateNewId();
 
        var documentInfo1 = DocumentInfo.Create(DocumentId.CreateNewId(projectId), "file1.cs");
        var documentInfo2 = DocumentInfo.Create(DocumentId.CreateNewId(projectId), "file2.cs");
 
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution
                        .AddProject(projectId, "goo", "goo.dll", LanguageNames.CSharp)
                        .AddDocuments(ImmutableArray.Create(documentInfo1, documentInfo2));
 
        var project = Assert.Single(solution.Projects);
 
        var document1 = project.GetDocument(documentInfo1.Id);
        var document2 = project.GetDocument(documentInfo2.Id);
 
        Assert.NotSame(document1, document2);
 
        await ValidateSolutionAndCompilationsAsync(solution);
    }
 
    [Fact]
    public async Task AddTwoDocumentsForTwoProjects()
    {
        var projectId1 = ProjectId.CreateNewId();
        var projectId2 = ProjectId.CreateNewId();
 
        var documentInfo1 = DocumentInfo.Create(DocumentId.CreateNewId(projectId1), "file1.cs");
        var documentInfo2 = DocumentInfo.Create(DocumentId.CreateNewId(projectId2), "file2.cs");
 
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution
                        .AddProject(projectId1, "project1", "project1.dll", LanguageNames.CSharp)
                        .AddProject(projectId2, "project2", "project2.dll", LanguageNames.CSharp)
                        .AddDocuments(ImmutableArray.Create(documentInfo1, documentInfo2));
 
        var project1 = solution.GetProject(projectId1);
        var project2 = solution.GetProject(projectId2);
 
        var document1 = project1.GetDocument(documentInfo1.Id);
        var document2 = project2.GetDocument(documentInfo2.Id);
 
        Assert.NotSame(document1, document2);
        Assert.NotSame(document1.Project, document2.Project);
 
        await ValidateSolutionAndCompilationsAsync(solution);
    }
 
    [Fact]
    public void AddTwoDocumentsWithMissingProject()
    {
        var projectId1 = ProjectId.CreateNewId();
        var projectId2 = ProjectId.CreateNewId();
 
        var documentInfo1 = DocumentInfo.Create(DocumentId.CreateNewId(projectId1), "file1.cs");
        var documentInfo2 = DocumentInfo.Create(DocumentId.CreateNewId(projectId2), "file2.cs");
 
        // We're only adding the first project, but not the second one
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution
                        .AddProject(projectId1, "project1", "project1.dll", LanguageNames.CSharp);
 
        Assert.ThrowsAny<InvalidOperationException>(() => solution.AddDocuments(ImmutableArray.Create(documentInfo1, documentInfo2)));
    }
 
    [Fact]
    public void RemoveZeroDocuments()
    {
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution;
 
        Assert.Same(solution, solution.RemoveDocuments([]));
    }
 
    [Fact]
    public async Task RemoveTwoDocuments()
    {
        var projectId = ProjectId.CreateNewId();
 
        var documentInfo1 = DocumentInfo.Create(DocumentId.CreateNewId(projectId), "file1.cs");
        var documentInfo2 = DocumentInfo.Create(DocumentId.CreateNewId(projectId), "file2.cs");
 
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution
                        .AddProject(projectId, "project1", "project1.dll", LanguageNames.CSharp)
                        .AddDocuments(ImmutableArray.Create(documentInfo1, documentInfo2));
 
        solution = solution.RemoveDocuments(ImmutableArray.Create(documentInfo1.Id, documentInfo2.Id));
 
        var finalProject = solution.Projects.Single();
        Assert.Empty(finalProject.Documents);
        Assert.Empty((await finalProject.GetCompilationAsync()).SyntaxTrees);
    }
 
    [Fact]
    public void RemoveTwoDocumentsFromDifferentProjects()
    {
        var projectId1 = ProjectId.CreateNewId();
        var projectId2 = ProjectId.CreateNewId();
 
        var documentInfo1 = DocumentInfo.Create(DocumentId.CreateNewId(projectId1), "file1.cs");
        var documentInfo2 = DocumentInfo.Create(DocumentId.CreateNewId(projectId2), "file2.cs");
 
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution
                        .AddProject(projectId1, "project1", "project1.dll", LanguageNames.CSharp)
                        .AddProject(projectId2, "project2", "project2.dll", LanguageNames.CSharp)
                        .AddDocuments(ImmutableArray.Create(documentInfo1, documentInfo2));
 
        Assert.All(solution.Projects, p => Assert.Single(p.Documents));
 
        solution = solution.RemoveDocuments(ImmutableArray.Create(documentInfo1.Id, documentInfo2.Id));
 
        Assert.All(solution.Projects, p => Assert.Empty(p.Documents));
    }
 
    [Fact]
    public void RemoveDocumentFromUnrelatedProject()
    {
        var projectId1 = ProjectId.CreateNewId();
        var projectId2 = ProjectId.CreateNewId();
 
        var documentInfo1 = DocumentInfo.Create(DocumentId.CreateNewId(projectId1), "file1.cs");
 
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution
                        .AddProject(projectId1, "project1", "project1.dll", LanguageNames.CSharp)
                        .AddProject(projectId2, "project2", "project2.dll", LanguageNames.CSharp)
                        .AddDocument(documentInfo1);
 
        // This should throw if we're removing one document from the wrong project. Right now we don't test the RemoveDocument
        // API due to https://github.com/dotnet/roslyn/issues/41211.
        Assert.Throws<ArgumentException>(() => solution.GetProject(projectId2).RemoveDocuments(ImmutableArray.Create(documentInfo1.Id)));
    }
 
    [Fact]
    public void RemoveAdditionalDocumentFromUnrelatedProject()
    {
        var projectId1 = ProjectId.CreateNewId();
        var projectId2 = ProjectId.CreateNewId();
 
        var documentInfo1 = DocumentInfo.Create(DocumentId.CreateNewId(projectId1), "file1.txt");
 
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution
                        .AddProject(projectId1, "project1", "project1.dll", LanguageNames.CSharp)
                        .AddProject(projectId2, "project2", "project2.dll", LanguageNames.CSharp)
                        .AddAdditionalDocument(documentInfo1);
 
        // This should throw if we're removing one document from the wrong project. Right now we don't test the RemoveAdditionalDocument
        // API due to https://github.com/dotnet/roslyn/issues/41211.
        Assert.Throws<ArgumentException>(() => solution.GetProject(projectId2).RemoveAdditionalDocuments(ImmutableArray.Create(documentInfo1.Id)));
    }
 
    [Fact]
    public void RemoveAnalyzerConfigDocumentFromUnrelatedProject()
    {
        var projectId1 = ProjectId.CreateNewId();
        var projectId2 = ProjectId.CreateNewId();
 
        var documentInfo1 = DocumentInfo.Create(DocumentId.CreateNewId(projectId1), ".editorconfig");
 
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution
                        .AddProject(projectId1, "project1", "project1.dll", LanguageNames.CSharp)
                        .AddProject(projectId2, "project2", "project2.dll", LanguageNames.CSharp)
                        .AddAnalyzerConfigDocuments(ImmutableArray.Create(documentInfo1));
 
        // This should throw if we're removing one document from the wrong project. Right now we don't test the RemoveAdditionalDocument
        // API due to https://github.com/dotnet/roslyn/issues/41211.
        Assert.Throws<ArgumentException>(() => solution.GetProject(projectId2).RemoveAnalyzerConfigDocuments(ImmutableArray.Create(documentInfo1.Id)));
    }
 
    [Fact]
    public async Task TestOneCSharpProjectAsync()
    {
        using var workspace = CreateWorkspace();
 
        var solution = workspace.CurrentSolution
            .AddProject("goo", "goo.dll", LanguageNames.CSharp)
            .AddMetadataReference(s_mscorlib)
            .AddDocument("goo.cs", "public class Goo { }")
            .Project.Solution;
 
        await ValidateSolutionAndCompilationsAsync(solution);
    }
 
    [Fact]
    public async Task TestTwoCSharpProjectsAsync()
    {
        using var workspace = CreateWorkspace();
 
        var pm1 = ProjectId.CreateNewId();
        var pm2 = ProjectId.CreateNewId();
        var doc1 = DocumentId.CreateNewId(pm1);
        var doc2 = DocumentId.CreateNewId(pm2);
 
        var solution = workspace.CurrentSolution
            .AddProject(pm1, "goo", "goo.dll", LanguageNames.CSharp)
            .AddProject(pm2, "bar", "bar.dll", LanguageNames.CSharp)
            .AddProjectReference(pm2, new ProjectReference(pm1))
            .AddDocument(doc1, "goo.cs", "public class Goo { }")
            .AddDocument(doc2, "bar.cs", "public class Bar : Goo { }");
 
        await ValidateSolutionAndCompilationsAsync(solution);
    }
 
    [Fact]
    public async Task TestCrossLanguageProjectsAsync()
    {
        var pm1 = ProjectId.CreateNewId();
        var pm2 = ProjectId.CreateNewId();
        using var workspace = CreateWorkspace();
 
        var solution = workspace.CurrentSolution
            .AddProject(ProjectInfo.Create(pm1, VersionStamp.Create(), "goo", "goo.dll", LanguageNames.CSharp, compilationOptions: workspace.Services
                .GetLanguageService<ICompilationFactoryService>(LanguageNames.CSharp)
                .GetDefaultCompilationOptions()
                .WithOutputKind(OutputKind.DynamicallyLinkedLibrary)))
            .AddMetadataReference(pm1, s_mscorlib)
            .AddProject(pm2, "bar", "bar.dll", LanguageNames.VisualBasic)
            .AddMetadataReference(pm2, s_mscorlib)
            .AddProjectReference(pm2, new ProjectReference(pm1))
            .AddDocument(DocumentId.CreateNewId(pm1), "goo.cs", "public class X { }")
            .AddDocument(DocumentId.CreateNewId(pm2), "bar.vb", "Public Class Y\r\nInherits X\r\nEnd Class");
 
        await ValidateSolutionAndCompilationsAsync(solution);
    }
 
    private static async Task ValidateSolutionAndCompilationsAsync(Solution solution)
    {
        foreach (var project in solution.Projects)
        {
            Assert.True(solution.ContainsProject(project.Id), "Solution was expected to have project " + project.Id);
            Assert.Equal(project, solution.GetProject(project.Id));
 
            // these won't always be unique in real-world but should be for these tests
            Assert.Equal(project, solution.GetProjectsByName(project.Name).FirstOrDefault());
 
            var compilation = await project.GetCompilationAsync();
            Assert.NotNull(compilation);
 
            // check that the options are the same
            Assert.Equal(project.CompilationOptions, compilation.Options);
 
            // check that all known metadata references are present in the compilation
            foreach (var meta in project.MetadataReferences)
            {
                Assert.True(compilation.References.Contains(meta), "Compilation references were expected to contain " + meta);
            }
 
            // check that all project-to-project reference metadata is present in the compilation
            foreach (var referenced in project.ProjectReferences)
            {
                if (solution.ContainsProject(referenced.ProjectId))
                {
                    var referencedMetadata = await solution.CompilationState.GetMetadataReferenceAsync(
                        referenced, solution.GetProjectState(project.Id), includeCrossLanguage: true, CancellationToken.None);
                    Assert.NotNull(referencedMetadata);
                    if (referencedMetadata is CompilationReference compilationReference)
                    {
                        compilation.References.Single(r =>
                        {
                            var cr = r as CompilationReference;
                            return cr != null && cr.Compilation == compilationReference.Compilation;
                        });
                    }
                }
            }
 
            // check that the syntax trees are the same
            var docs = project.Documents.ToList();
            var trees = compilation.SyntaxTrees.ToList();
            Assert.Equal(docs.Count, trees.Count);
 
            foreach (var doc in docs)
            {
                Assert.True(trees.Contains(await doc.GetSyntaxTreeAsync()), "trees list was expected to contain the syntax tree of doc");
            }
        }
    }
 
#if false
    [Fact(Skip = "641963")]
    public void TestDeepProjectReferenceTree()
    {
        int projectCount = 5;
        var solution = CreateSolutionWithProjectDependencyChain(projectCount);
        ProjectId[] projectIds = solution.ProjectIds.ToArray();
 
        Compilation compilation;
        for (int i = 0; i < projectCount; i++)
        {
            Assert.False(solution.GetProject(projectIds[i]).TryGetCompilation(out compilation));
        }
 
        var top = solution.GetCompilationAsync(projectIds.Last(), CancellationToken.None).Result;
        var partialSolution = solution.GetPartialSolution();
        for (int i = 0; i < projectCount; i++)
        {
            // While holding a compilation, we also hold its references, plus one further level
            // of references alive.  However, the references are only partial Declaration 
            // compilations
            var isPartialAvailable = i >= projectCount - 3;
            var isFinalAvailable = i == projectCount - 1;
 
            var projectId = projectIds[i];
            Assert.Equal(isFinalAvailable, solution.GetProject(projectId).TryGetCompilation(out compilation));
            Assert.Equal(isPartialAvailable, partialSolution.ProjectIds.Contains(projectId) && partialSolution.GetProject(projectId).TryGetCompilation(out compilation));
        }
    }
#endif
 
    [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/636431")]
    public async Task TestProjectDependencyLoadingAsync()
    {
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution;
 
        var projectIds = Enumerable.Range(0, 5).Select(i => ProjectId.CreateNewId()).ToArray();
        for (var i = 0; i < projectIds.Length; i++)
        {
            solution = solution.AddProject(projectIds[i], i.ToString(), i.ToString(), LanguageNames.CSharp);
            if (i >= 1)
            {
                solution = solution.AddProjectReference(projectIds[i], new ProjectReference(projectIds[i - 1]));
            }
        }
 
        await solution.GetProject(projectIds[0]).GetCompilationAsync(CancellationToken.None);
        await solution.GetProject(projectIds[2]).GetCompilationAsync(CancellationToken.None);
    }
 
    [Fact]
    public async Task TestAddMetadataReferencesAsync()
    {
        var mefReference = NetFramework.SystemCore;
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution;
        var project1 = ProjectId.CreateNewId();
        solution = solution.AddProject(project1, "goo", "goo.dll", LanguageNames.CSharp);
        solution = solution.AddMetadataReference(project1, s_mscorlib);
 
        solution = solution.AddMetadataReference(project1, mefReference);
        var assemblyReference = (IAssemblySymbol)solution.GetProject(project1).GetCompilationAsync().Result.GetAssemblyOrModuleSymbol(mefReference);
        var namespacesAndTypes = assemblyReference.GlobalNamespace.GetAllNamespacesAndTypes(CancellationToken.None);
        var foundSymbol = from symbol in namespacesAndTypes
                          where symbol.Name.Equals("Enumerable")
                          select symbol;
        Assert.Equal(1, foundSymbol.Count());
        solution = solution.RemoveMetadataReference(project1, mefReference);
        assemblyReference = (IAssemblySymbol)solution.GetProject(project1).GetCompilationAsync().Result.GetAssemblyOrModuleSymbol(mefReference);
        Assert.Null(assemblyReference);
 
        await ValidateSolutionAndCompilationsAsync(solution);
    }
 
    private sealed class MockDiagnosticAnalyzer : DiagnosticAnalyzer
    {
        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
        {
            get
            {
                throw new NotImplementedException();
            }
        }
 
        public override void Initialize(AnalysisContext analysisContext)
        {
        }
    }
 
    [Fact]
    public void TestProjectDiagnosticAnalyzers()
    {
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution;
        var project1 = ProjectId.CreateNewId();
        solution = solution.AddProject(project1, "goo", "goo.dll", LanguageNames.CSharp);
        Assert.Empty(solution.Projects.Single().AnalyzerReferences);
 
        DiagnosticAnalyzer analyzer = new MockDiagnosticAnalyzer();
        var analyzerReference = new AnalyzerImageReference(ImmutableArray.Create(analyzer));
 
        // Test AddAnalyzer
        var newSolution = solution.AddAnalyzerReference(project1, analyzerReference);
        var actualAnalyzerReferences = newSolution.Projects.Single().AnalyzerReferences;
        Assert.Equal(1, actualAnalyzerReferences.Count);
        Assert.Equal(analyzerReference, actualAnalyzerReferences[0]);
        var actualAnalyzers = actualAnalyzerReferences[0].GetAnalyzersForAllLanguages();
        Assert.Equal(1, actualAnalyzers.Length);
        Assert.Equal(analyzer, actualAnalyzers[0]);
 
        // Test ProjectChanges
        var changes = newSolution.GetChanges(solution).GetProjectChanges().Single();
        var addedAnalyzerReference = changes.GetAddedAnalyzerReferences().Single();
        Assert.Equal(analyzerReference, addedAnalyzerReference);
        var removedAnalyzerReferences = changes.GetRemovedAnalyzerReferences();
        Assert.Empty(removedAnalyzerReferences);
        solution = newSolution;
 
        // Test RemoveAnalyzer
        solution = solution.RemoveAnalyzerReference(project1, analyzerReference);
        actualAnalyzerReferences = solution.Projects.Single().AnalyzerReferences;
        Assert.Empty(actualAnalyzerReferences);
 
        // Test AddAnalyzers
        analyzerReference = new AnalyzerImageReference(ImmutableArray.Create(analyzer));
        DiagnosticAnalyzer secondAnalyzer = new MockDiagnosticAnalyzer();
        var secondAnalyzerReference = new AnalyzerImageReference(ImmutableArray.Create(secondAnalyzer));
        var analyzerReferences = new[] { analyzerReference, secondAnalyzerReference };
        solution = solution.AddAnalyzerReferences(project1, analyzerReferences);
        actualAnalyzerReferences = solution.Projects.Single().AnalyzerReferences;
        Assert.Equal(2, actualAnalyzerReferences.Count);
        Assert.Equal(analyzerReference, actualAnalyzerReferences[0]);
        Assert.Equal(secondAnalyzerReference, actualAnalyzerReferences[1]);
 
        solution = solution.RemoveAnalyzerReference(project1, analyzerReference);
        actualAnalyzerReferences = solution.Projects.Single().AnalyzerReferences;
        Assert.Equal(1, actualAnalyzerReferences.Count);
        Assert.Equal(secondAnalyzerReference, actualAnalyzerReferences[0]);
 
        // Test WithAnalyzers
        solution = solution.WithProjectAnalyzerReferences(project1, analyzerReferences);
        actualAnalyzerReferences = solution.Projects.Single().AnalyzerReferences;
        Assert.Equal(2, actualAnalyzerReferences.Count);
        Assert.Equal(analyzerReference, actualAnalyzerReferences[0]);
        Assert.Equal(secondAnalyzerReference, actualAnalyzerReferences[1]);
    }
 
    [Fact]
    public void TestProjectParseOptions()
    {
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution;
        var project1 = ProjectId.CreateNewId();
        solution = solution.AddProject(project1, "goo", "goo.dll", LanguageNames.CSharp);
        solution = solution.AddMetadataReference(project1, s_mscorlib);
 
        // Parse Options
        var oldParseOptions = solution.GetProject(project1).ParseOptions;
        var newParseOptions = new CSharpParseOptions(preprocessorSymbols: ["AFTER"]);
        solution = solution.WithProjectParseOptions(project1, newParseOptions);
        var newUpdatedParseOptions = solution.GetProject(project1).ParseOptions;
        Assert.NotEqual(oldParseOptions, newUpdatedParseOptions);
        Assert.Same(newParseOptions, newUpdatedParseOptions);
    }
 
    [Fact]
    public async Task TestRemoveProjectAsync()
    {
        using var workspace = CreateWorkspace();
        var sol = workspace.CurrentSolution;
 
        var pid = ProjectId.CreateNewId();
        sol = sol.AddProject(pid, "goo", "goo.dll", LanguageNames.CSharp);
        Assert.True(sol.ProjectIds.Any(), "Solution was expected to have projects");
        Assert.NotNull(pid);
        var project = sol.GetProject(pid);
        Assert.False(project.HasDocuments);
 
        var sol2 = sol.RemoveProject(pid);
        Assert.False(sol2.ProjectIds.Any());
 
        await ValidateSolutionAndCompilationsAsync(sol);
    }
 
    [Fact]
    public async Task TestRemoveProjectWithReferencesAsync()
    {
        using var workspace = CreateWorkspace();
        var sol = workspace.CurrentSolution;
 
        var pid = ProjectId.CreateNewId();
        var pid2 = ProjectId.CreateNewId();
        sol = sol.AddProject(pid, "goo", "goo.dll", LanguageNames.CSharp)
               .AddProject(pid2, "bar", "bar.dll", LanguageNames.CSharp)
               .AddProjectReference(pid2, new ProjectReference(pid));
 
        Assert.Equal(2, sol.Projects.Count());
 
        // remove the project that is being referenced
        // this should leave a dangling reference
        var sol2 = sol.RemoveProject(pid);
 
        Assert.False(sol2.ContainsProject(pid));
        Assert.True(sol2.ContainsProject(pid2), "sol2 was expected to contain project " + pid2);
        Assert.Equal(1, sol2.Projects.Count());
        Assert.True(sol2.GetProject(pid2).AllProjectReferences.Any(r => r.ProjectId == pid), "sol2 project pid2 was expected to contain project reference " + pid);
 
        await ValidateSolutionAndCompilationsAsync(sol2);
    }
 
    [Fact]
    public async Task TestRemoveProjectWithReferencesAndAddItBackAsync()
    {
        using var workspace = CreateWorkspace();
        var sol = workspace.CurrentSolution;
 
        var pid = ProjectId.CreateNewId();
        var pid2 = ProjectId.CreateNewId();
        sol = sol.AddProject(pid, "goo", "goo.dll", LanguageNames.CSharp)
               .AddProject(pid2, "bar", "bar.dll", LanguageNames.CSharp)
               .AddProjectReference(pid2, new ProjectReference(pid));
 
        Assert.Equal(2, sol.Projects.Count());
 
        // remove the project that is being referenced
        var sol2 = sol.RemoveProject(pid);
 
        Assert.False(sol2.ContainsProject(pid));
        Assert.True(sol2.ContainsProject(pid2), "sol2 was expected to contain project " + pid2);
        Assert.Equal(1, sol2.Projects.Count());
        Assert.True(sol2.GetProject(pid2).AllProjectReferences.Any(r => r.ProjectId == pid), "sol2 pid2 was expected to contain " + pid);
 
        var sol3 = sol2.AddProject(pid, "goo", "goo.dll", LanguageNames.CSharp);
 
        Assert.True(sol3.ContainsProject(pid), "sol3 was expected to contain " + pid);
        Assert.True(sol3.ContainsProject(pid2), "sol3 was expected to contain " + pid2);
        Assert.Equal(2, sol3.Projects.Count());
 
        await ValidateSolutionAndCompilationsAsync(sol3);
    }
 
    [Fact]
    public async Task TestGetSyntaxRootAsync()
    {
        var text = "public class Goo { }";
 
        var pid = ProjectId.CreateNewId();
        var did = DocumentId.CreateNewId(pid);
 
        using var workspace = CreateWorkspace();
        var sol = workspace.CurrentSolution
                        .AddProject(pid, "goo", "goo.dll", LanguageNames.CSharp)
                        .AddDocument(did, "goo.cs", text);
 
        var document = sol.GetDocument(did);
        Assert.False(document.TryGetSyntaxRoot(out _));
 
        var root = await document.GetSyntaxRootAsync();
        Assert.NotNull(root);
        Assert.Equal(text, root.ToString());
 
        Assert.True(document.TryGetSyntaxRoot(out root));
        Assert.NotNull(root);
    }
 
    [Fact]
    public async Task TestUpdateDocumentAsync()
    {
        var projectId = ProjectId.CreateNewId();
        var documentId = DocumentId.CreateNewId(projectId);
        using var workspace = CreateWorkspace();
        var solution1 = workspace.CurrentSolution
            .AddProject(projectId, "ProjectName", "AssemblyName", LanguageNames.CSharp)
            .AddDocument(documentId, "DocumentName", SourceText.From("class Class{}"));
 
        var document = solution1.GetDocument(documentId);
        var newRoot = await Formatter.FormatAsync(document, CSharpSyntaxFormattingOptions.Default, CancellationToken.None).Result.GetSyntaxRootAsync();
        var solution2 = solution1.WithDocumentSyntaxRoot(documentId, newRoot);
 
        Assert.NotEqual(solution1, solution2);
 
        var newText = solution2.GetDocument(documentId).GetTextAsync().Result.ToString();
        Assert.Equal("class Class { }", newText);
    }
 
    [Fact]
    public void TestUpdateSyntaxTreeWithAnnotations()
    {
        var text = "public class Goo { }";
 
        var pid = ProjectId.CreateNewId();
        var did = DocumentId.CreateNewId(pid);
 
        using var workspace = CreateWorkspace();
        var sol = workspace.CurrentSolution
                        .AddProject(pid, "goo", "goo.dll", LanguageNames.CSharp)
                        .AddDocument(did, "goo.cs", text);
 
        var document = sol.GetDocument(did);
        var tree = document.GetSyntaxTreeAsync().Result;
        var root = tree.GetRoot();
 
        var annotation = new SyntaxAnnotation();
        var annotatedRoot = root.WithAdditionalAnnotations(annotation);
 
        var sol2 = sol.WithDocumentSyntaxRoot(did, annotatedRoot);
        var doc2 = sol2.GetDocument(did);
        var tree2 = doc2.GetSyntaxTreeAsync().Result;
        var root2 = tree2.GetRoot();
        // text should not be available yet (it should be defer created from the node)
        // and getting the document or root should not cause it to be created.
        Assert.False(tree2.TryGetText(out _));
 
        var text2 = tree2.GetText();
        Assert.NotNull(text2);
 
        Assert.NotSame(tree, tree2);
        Assert.NotSame(annotatedRoot, root2);
 
        Assert.True(annotatedRoot.IsEquivalentTo(root2));
        Assert.True(root2.HasAnnotation(annotation));
    }
 
    [Theory]
    [InlineData(LanguageNames.CSharp)]
    [InlineData(LanguageNames.VisualBasic)]
    public async Task ParsedTreeRootOwnership(string language)
    {
        var pid = ProjectId.CreateNewId();
        var did = DocumentId.CreateNewId(pid);
 
        var source = (language == LanguageNames.CSharp) ? "class C {}" : "Class C : End Class";
 
        using var workspace = CreateWorkspace();
 
        var sol = workspace.CurrentSolution
            .AddProject(pid, "test", "test.dll", language)
            .AddDocument(did, "test", source);
 
        var document = sol.GetDocument(did);
 
        // update the document syntax root:
        var syntaxRoot = await document.GetSyntaxRootAsync(CancellationToken.None);
 
        SyntaxNode newSyntaxRoot;
        if (language == LanguageNames.CSharp)
        {
            var classNode = syntaxRoot.DescendantNodes().OfType<CS.Syntax.ClassDeclarationSyntax>().Single();
            newSyntaxRoot = syntaxRoot.ReplaceNode(classNode, classNode.WithModifiers([CS.SyntaxFactory.ParseToken("public")]));
        }
        else
        {
            var classNode = syntaxRoot.DescendantNodes().OfType<VB.Syntax.ClassStatementSyntax>().Single();
            newSyntaxRoot = syntaxRoot.ReplaceNode(classNode, classNode.WithModifiers(VB.SyntaxFactory.TokenList(VB.SyntaxFactory.ParseToken("Public"))));
        }
 
        var documentWithAttribute = document.WithSyntaxRoot(newSyntaxRoot);
 
        var tree = await documentWithAttribute.GetSyntaxTreeAsync(CancellationToken.None);
        var root = await documentWithAttribute.GetRequiredSyntaxRootAsync(CancellationToken.None);
 
        Assert.Same(tree, root.SyntaxTree);
        Assert.Same(tree, tree.WithRootAndOptions(root, tree.Options));
        Assert.Same(tree, tree.WithFilePath(tree.FilePath));
    }
 
    [Fact]
    public void TestUpdatingFilePathUpdatesSyntaxTree()
    {
        var projectId = ProjectId.CreateNewId();
        var documentId = DocumentId.CreateNewId(projectId);
 
        const string OldFilePath = @"Z:\OldFilePath.cs";
        const string NewFilePath = @"Z:\NewFilePath.cs";
 
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution
                        .AddProject(projectId, "goo", "goo.dll", LanguageNames.CSharp)
                        .AddDocument(documentId, "OldFilePath.cs", "public class Goo { }", filePath: OldFilePath);
 
        // scope so later asserts don't accidentally use oldDocument
        {
            var oldDocument = solution.GetDocument(documentId);
            Assert.Equal(OldFilePath, oldDocument.FilePath);
            Assert.Equal(OldFilePath, oldDocument.GetSyntaxTreeAsync().Result.FilePath);
        }
 
        solution = solution.WithDocumentFilePath(documentId, NewFilePath);
 
        {
            var newDocument = solution.GetDocument(documentId);
            Assert.Equal(NewFilePath, newDocument.FilePath);
            Assert.Equal(NewFilePath, newDocument.GetSyntaxTreeAsync().Result.FilePath);
        }
    }
 
    [MethodImpl(MethodImplOptions.NoInlining)]
    [ConditionalFact(typeof(WindowsOnly)), WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/542736")]
    public void TestDocumentChangedOnDiskIsNotObserved()
    {
        var text1 = "public class A {}";
        var text2 = "public class B {}";
 
        var file = Temp.CreateFile().WriteAllText(text1, Encoding.UTF8);
 
        // create a solution that evicts from the cache immediately.
        using var workspace = CreateWorkspace();
        var sol = workspace.CurrentSolution;
 
        var pid = ProjectId.CreateNewId();
        var did = DocumentId.CreateNewId(pid);
 
        sol = sol.AddProject(pid, "goo", "goo.dll", LanguageNames.CSharp)
                 .AddDocument(did, "x", new WorkspaceFileTextLoader(workspace.Services.SolutionServices, file.Path, Encoding.UTF8));
 
        var observedText = GetObservedText(sol, did, text1);
 
        // change text on disk & verify it is changed
        file.WriteAllText(text2);
        var textOnDisk = file.ReadAllText();
        Assert.Equal(text2, textOnDisk);
 
        // stop observing it and let GC reclaim it
        observedText.AssertReleased();
 
        // if we ask for the same text again we should get the original content
        var observedText2 = sol.GetDocument(did).GetTextAsync().Result;
        Assert.Equal(text1, observedText2.ToString());
    }
 
    [Fact]
    public void TestGetTextAsync()
    {
        var pid = ProjectId.CreateNewId();
        var did = DocumentId.CreateNewId(pid);
 
        var text = "public class C {}";
        using var workspace = CreateWorkspace();
        var sol = workspace.CurrentSolution
                                .AddProject(pid, "goo", "goo.dll", LanguageNames.CSharp)
                                .AddDocument(did, "goo.cs", text);
 
        var doc = sol.GetDocument(did);
 
        var docText = doc.GetTextAsync().Result;
 
        Assert.NotNull(docText);
        Assert.Equal(text, docText.ToString());
    }
 
    [Fact]
    public void TestGetLoadedTextAsync()
    {
        var pid = ProjectId.CreateNewId();
        var did = DocumentId.CreateNewId(pid);
 
        var text = "public class C {}";
        var file = Temp.CreateFile().WriteAllText(text, Encoding.UTF8);
 
        using var workspace = CreateWorkspace();
        var sol = workspace.CurrentSolution
                                .AddProject(pid, "goo", "goo.dll", LanguageNames.CSharp)
                                .AddDocument(did, "x", new WorkspaceFileTextLoader(workspace.Services.SolutionServices, file.Path, Encoding.UTF8));
 
        var doc = sol.GetDocument(did);
 
        var docText = doc.GetTextAsync().Result;
 
        Assert.NotNull(docText);
        Assert.Equal(text, docText.ToString());
    }
 
    [MethodImpl(MethodImplOptions.NoInlining)]
    [Fact(Skip = "https://github.com/dotnet/roslyn/issues/19427")]
    public void TestGetRecoveredTextAsync()
    {
        var pid = ProjectId.CreateNewId();
        var did = DocumentId.CreateNewId(pid);
 
        var text = "public class C {}";
        using var workspace = CreateWorkspace();
        var sol = workspace.CurrentSolution
                                .AddProject(pid, "goo", "goo.dll", LanguageNames.CSharp)
                                .AddDocument(did, "goo.cs", text);
 
        // observe the text and then wait for the references to be GC'd
        var observed = GetObservedText(sol, did, text);
        observed.AssertReleased();
 
        // get it async and force it to recover from temporary storage
        var doc = sol.GetDocument(did);
        var docText = doc.GetTextAsync().Result;
 
        Assert.NotNull(docText);
        Assert.Equal(text, docText.ToString());
    }
 
    [Fact]
    public void TestGetSyntaxTreeAsync()
    {
        var pid = ProjectId.CreateNewId();
        var did = DocumentId.CreateNewId(pid);
 
        var text = "public class C {}";
        using var workspace = CreateWorkspace();
        var sol = workspace.CurrentSolution
                                .AddProject(pid, "goo", "goo.dll", LanguageNames.CSharp)
                                .AddDocument(did, "goo.cs", text);
 
        var doc = sol.GetDocument(did);
 
        var docTree = doc.GetSyntaxTreeAsync().Result;
 
        Assert.NotNull(docTree);
        Assert.Equal(text, docTree.GetRoot().ToString());
    }
 
    [Fact]
    public void TestGetSyntaxTreeFromLoadedTextAsync()
    {
        var pid = ProjectId.CreateNewId();
        var did = DocumentId.CreateNewId(pid);
 
        var text = "public class C {}";
        var file = Temp.CreateFile().WriteAllText(text, Encoding.UTF8);
 
        using var workspace = CreateWorkspace();
        var sol = workspace.CurrentSolution
                                .AddProject(pid, "goo", "goo.dll", LanguageNames.CSharp)
                                .AddDocument(did, "x", new WorkspaceFileTextLoader(workspace.Services.SolutionServices, file.Path, Encoding.UTF8));
 
        var doc = sol.GetDocument(did);
        var docTree = doc.GetSyntaxTreeAsync().Result;
 
        Assert.NotNull(docTree);
        Assert.Equal(text, docTree.GetRoot().ToString());
    }
 
    [Fact]
    public void TestGetSyntaxTreeFromAddedTree()
    {
        var pid = ProjectId.CreateNewId();
        var did = DocumentId.CreateNewId(pid);
 
        var tree = CSharp.SyntaxFactory.ParseSyntaxTree("public class C {}").GetRoot(CancellationToken.None);
        tree = tree.WithAdditionalAnnotations(new SyntaxAnnotation("test"));
 
        using var workspace = CreateWorkspace();
        var sol = workspace.CurrentSolution
                                .AddProject(pid, "goo", "goo.dll", LanguageNames.CSharp)
                                .AddDocument(did, "x", tree);
 
        var doc = sol.GetDocument(did);
        var docTree = doc.GetSyntaxRootAsync().Result;
 
        Assert.NotNull(docTree);
        Assert.True(tree.IsEquivalentTo(docTree));
        Assert.NotNull(docTree.GetAnnotatedNodes("test").Single());
    }
 
    [Fact]
    public async Task TestGetSyntaxRootAsync2Async()
    {
        var pid = ProjectId.CreateNewId();
        var did = DocumentId.CreateNewId(pid);
 
        var text = "public class C {}";
        using var workspace = CreateWorkspace();
        var sol = workspace.CurrentSolution
                                .AddProject(pid, "goo", "goo.dll", LanguageNames.CSharp)
                                .AddDocument(did, "goo.cs", text);
 
        var doc = sol.GetDocument(did);
 
        var docRoot = await doc.GetSyntaxRootAsync();
 
        Assert.NotNull(docRoot);
        Assert.Equal(text, docRoot.ToString());
    }
 
    [Fact]
    public void TestGetCompilationAsync()
    {
        var pid = ProjectId.CreateNewId();
        var did = DocumentId.CreateNewId(pid);
 
        var text = "public class C {}";
        using var workspace = CreateWorkspace();
        var sol = workspace.CurrentSolution
                                .AddProject(pid, "goo", "goo.dll", LanguageNames.CSharp)
                                .AddDocument(did, "goo.cs", text);
 
        var proj = sol.GetProject(pid);
 
        var compilation = proj.GetCompilationAsync().Result;
 
        Assert.NotNull(compilation);
        Assert.Equal(1, compilation.SyntaxTrees.Count());
    }
 
    [Fact]
    public void TestGetSemanticModelAsync()
    {
        var pid = ProjectId.CreateNewId();
        var did = DocumentId.CreateNewId(pid);
 
        var text = "public class C {}";
        using var workspace = CreateWorkspace();
        var sol = workspace.CurrentSolution
                                .AddProject(pid, "goo", "goo.dll", LanguageNames.CSharp)
                                .AddDocument(did, "goo.cs", text);
 
        var doc = sol.GetDocument(did);
 
        var docModel = doc.GetSemanticModelAsync().Result;
        Assert.NotNull(docModel);
    }
 
    [MethodImpl(MethodImplOptions.NoInlining)]
    [Fact(Skip = "https://github.com/dotnet/roslyn/issues/13433")]
    public void TestGetTextDoesNotKeepTextAlive()
    {
        var pid = ProjectId.CreateNewId();
        var did = DocumentId.CreateNewId(pid);
 
        var text = "public class C {}";
        using var workspace = CreateWorkspace();
        var sol = workspace.CurrentSolution
                                .AddProject(pid, "goo", "goo.dll", LanguageNames.CSharp)
                                .AddDocument(did, "goo.cs", text);
 
        // observe the text and then wait for the references to be GC'd
        var observed = GetObservedText(sol, did, text);
        observed.AssertReleased();
    }
 
    [MethodImpl(MethodImplOptions.NoInlining)]
    private static ObjectReference<SourceText> GetObservedText(Solution solution, DocumentId documentId, string expectedText = null)
    {
        var observedText = solution.GetDocument(documentId).GetTextAsync().Result;
 
        if (expectedText != null)
        {
            Assert.Equal(expectedText, observedText.ToString());
        }
 
        return new ObjectReference<SourceText>(observedText);
    }
 
    [MethodImpl(MethodImplOptions.NoInlining)]
    [Fact(Skip = "https://github.com/dotnet/roslyn/issues/13433")]
    public void TestGetTextAsyncDoesNotKeepTextAlive()
    {
        var pid = ProjectId.CreateNewId();
        var did = DocumentId.CreateNewId(pid);
 
        var text = "public class C {}";
        using var workspace = CreateWorkspace();
        var sol = workspace.CurrentSolution
                                .AddProject(pid, "goo", "goo.dll", LanguageNames.CSharp)
                                .AddDocument(did, "goo.cs", text);
 
        // observe the text and then wait for the references to be GC'd
        var observed = GetObservedTextAsync(sol, did, text);
        observed.AssertReleased();
    }
 
    [MethodImpl(MethodImplOptions.NoInlining)]
    private static ObjectReference<SourceText> GetObservedTextAsync(Solution solution, DocumentId documentId, string expectedText = null)
    {
        var observedText = solution.GetDocument(documentId).GetTextAsync().Result;
 
        if (expectedText != null)
        {
            Assert.Equal(expectedText, observedText.ToString());
        }
 
        return new ObjectReference<SourceText>(observedText);
    }
 
    [MethodImpl(MethodImplOptions.NoInlining)]
    [Fact(Skip = "https://github.com/dotnet/roslyn/issues/13433")]
    public void TestGetSyntaxRootDoesNotKeepRootAlive()
    {
        var pid = ProjectId.CreateNewId();
        var did = DocumentId.CreateNewId(pid);
 
        var text = "public class C {}";
 
        using var workspace = CreateWorkspace();
        var sol = workspace.CurrentSolution
                                .AddProject(pid, "goo", "goo.dll", LanguageNames.CSharp)
                                .AddDocument(did, "goo.cs", text);
 
        // get it async and wait for it to get GC'd
        var observed = GetObservedSyntaxTreeRoot(sol, did);
        observed.AssertReleased();
    }
 
    [MethodImpl(MethodImplOptions.NoInlining)]
    private static ObjectReference<SyntaxNode> GetObservedSyntaxTreeRoot(Solution solution, DocumentId documentId)
    {
        var observedTree = solution.GetDocument(documentId).GetSyntaxRootAsync().Result;
        return new ObjectReference<SyntaxNode>(observedTree);
    }
 
    [MethodImpl(MethodImplOptions.NoInlining)]
    [Fact(Skip = "https://github.com/dotnet/roslyn/issues/13433")]
    public void TestGetSyntaxRootAsyncDoesNotKeepRootAlive()
    {
        var pid = ProjectId.CreateNewId();
        var did = DocumentId.CreateNewId(pid);
 
        var text = "public class C {}";
 
        using var workspace = CreateWorkspace();
        var sol = workspace.CurrentSolution
                                .AddProject(pid, "goo", "goo.dll", LanguageNames.CSharp)
                                .AddDocument(did, "goo.cs", text);
 
        // get it async and wait for it to get GC'd
        var observed = GetObservedSyntaxTreeRootAsync(sol, did);
        observed.AssertReleased();
    }
 
    [MethodImpl(MethodImplOptions.NoInlining)]
    private static ObjectReference<SyntaxNode> GetObservedSyntaxTreeRootAsync(Solution solution, DocumentId documentId)
    {
        var observedTree = solution.GetDocument(documentId).GetSyntaxRootAsync().Result;
        return new ObjectReference<SyntaxNode>(observedTree);
    }
 
    [MethodImpl(MethodImplOptions.NoInlining)]
    [Fact(Skip = "https://github.com/dotnet/roslyn/issues/13506")]
    [WorkItem("https://github.com/dotnet/roslyn/issues/13506")]
    public void TestRecoverableSyntaxTreeCSharp()
    {
        var pid = ProjectId.CreateNewId();
        var did = DocumentId.CreateNewId(pid);
 
        var text = @"public class C {
    public void Method1() {}
    public void Method2() {}
    public void Method3() {}
    public void Method4() {}
    public void Method5() {}
    public void Method6() {}
}";
 
        using var workspace = CreateWorkspace();
        var sol = workspace.CurrentSolution
                                .AddProject(pid, "goo", "goo.dll", LanguageNames.CSharp)
                                .AddDocument(did, "goo.cs", text);
 
        TestRecoverableSyntaxTree(sol, did);
    }
 
    [MethodImpl(MethodImplOptions.NoInlining)]
    [Fact(Skip = "https://github.com/dotnet/roslyn/issues/13433")]
    public void TestRecoverableSyntaxTreeVisualBasic()
    {
        var pid = ProjectId.CreateNewId();
        var did = DocumentId.CreateNewId(pid);
 
        var text = @"Public Class C
    Sub Method1()
    End Sub
    Sub Method2()
    End Sub
    Sub Method3()
    End Sub
    Sub Method4()
    End Sub
    Sub Method5()
    End Sub
    Sub Method6()
    End Sub
End Class";
 
        using var workspace = CreateWorkspace();
        var sol = workspace.CurrentSolution
                                .AddProject(pid, "goo", "goo.dll", LanguageNames.VisualBasic)
                                .AddDocument(did, "goo.vb", text);
 
        TestRecoverableSyntaxTree(sol, did);
    }
 
    private static void TestRecoverableSyntaxTree(Solution sol, DocumentId did)
    {
        // get it async and wait for it to get GC'd
        var observed = GetObservedSyntaxTreeRootAsync(sol, did);
        observed.AssertReleased();
 
        var doc = sol.GetDocument(did);
 
        // access the tree & root again (recover it)
        var tree = doc.GetSyntaxTreeAsync().Result;
 
        // this should cause reparsing
        var root = tree.GetRoot();
 
        // prove that the new root is correctly associated with the tree
        Assert.Equal(tree, root.SyntaxTree);
 
        // reset the syntax root, to make it 'refactored' by adding an attribute
        var newRoot = doc.GetSyntaxRootAsync().Result.WithAdditionalAnnotations(SyntaxAnnotation.ElasticAnnotation);
        var doc2 = doc.Project.Solution.WithDocumentSyntaxRoot(doc.Id, newRoot, PreservationMode.PreserveValue).GetDocument(doc.Id);
 
        // get it async and wait for it to get GC'd
        var observed2 = GetObservedSyntaxTreeRootAsync(doc2.Project.Solution, did);
        observed2.AssertReleased();
 
        // access the tree & root again (recover it)
        var tree2 = doc2.GetSyntaxTreeAsync().Result;
 
        // this should cause deserialization
        var root2 = tree2.GetRoot();
 
        // prove that the new root is correctly associated with the tree
        Assert.Equal(tree2, root2.SyntaxTree);
    }
 
    [MethodImpl(MethodImplOptions.NoInlining)]
    [Fact(Skip = "https://github.com/dotnet/roslyn/issues/13433")]
    public void TestGetCompilationAsyncDoesNotKeepCompilationAlive()
    {
        var pid = ProjectId.CreateNewId();
        var did = DocumentId.CreateNewId(pid);
 
        var text = "public class C {}";
        using var workspace = CreateWorkspace();
        var sol = workspace.CurrentSolution
                                .AddProject(pid, "goo", "goo.dll", LanguageNames.CSharp)
                                .AddDocument(did, "goo.cs", text);
 
        // get it async and wait for it to get GC'd
        var observed = GetObservedCompilationAsync(sol, pid);
        observed.AssertReleased();
    }
 
    [MethodImpl(MethodImplOptions.NoInlining)]
    private static ObjectReference<Compilation> GetObservedCompilationAsync(Solution solution, ProjectId projectId)
    {
        var observed = solution.GetProject(projectId).GetCompilationAsync().Result;
        return new ObjectReference<Compilation>(observed);
    }
 
    [MethodImpl(MethodImplOptions.NoInlining)]
    [Fact(Skip = "https://github.com/dotnet/roslyn/issues/13433")]
    public void TestGetCompilationDoesNotKeepCompilationAlive()
    {
        var pid = ProjectId.CreateNewId();
        var did = DocumentId.CreateNewId(pid);
 
        var text = "public class C {}";
        using var workspace = CreateWorkspace();
        var sol = workspace.CurrentSolution
                                .AddProject(pid, "goo", "goo.dll", LanguageNames.CSharp)
                                .AddDocument(did, "goo.cs", text);
 
        // get it async and wait for it to get GC'd
        var observed = GetObservedCompilation(sol, pid);
        observed.AssertReleased();
    }
 
    [MethodImpl(MethodImplOptions.NoInlining)]
    private static ObjectReference<Compilation> GetObservedCompilation(Solution solution, ProjectId projectId)
    {
        var observed = solution.GetProject(projectId).GetCompilationAsync().Result;
        return new ObjectReference<Compilation>(observed);
    }
 
    [Theory]
    [InlineData(LanguageNames.CSharp)]
    [InlineData(LanguageNames.VisualBasic)]
    [WorkItem("https://github.com/dotnet/roslyn/issues/63834")]
    public void RecoverableTree_With(string language)
    {
        using var workspace = CreateWorkspace();
 
        var pid = ProjectId.CreateNewId();
        var did = DocumentId.CreateNewId(pid);
 
        var sol = workspace.CurrentSolution
            .AddProject(pid, "test", "test.dll", language)
            .AddDocument(did, "test", SourceText.From(language == LanguageNames.CSharp ? "class C {}" : "Class C : End Class", Encoding.UTF8, SourceHashAlgorithm.Sha256), filePath: "old path");
 
        var document = sol.GetDocument(did);
        var tree = document.GetSyntaxTreeSynchronously(default);
        //var recoverableTree = Assert.IsAssignableFrom<IRecoverableSyntaxTree>(tree);
 
        var tree2 = tree.WithFilePath("new path");
        //var recoverableTree2 = Assert.IsAssignableFrom<IRecoverableSyntaxTree>(tree2);
 
        Assert.Equal("new path", tree2.FilePath);
        Assert.Same(tree2, tree2.GetRoot().SyntaxTree);
        Assert.Same(tree.Options, tree2.Options);
        Assert.Same(tree.Encoding, tree2.Encoding);
        Assert.Equal(tree.Length, tree2.Length);
        //Assert.Equal(recoverableTree.ContainsDirectives, recoverableTree2.ContainsDirectives);
 
        // unchanged:
        Assert.Same(tree, tree.WithFilePath("old path"));
 
        var newRoot = (language == LanguageNames.CSharp) ? CS.SyntaxFactory.ParseCompilationUnit("""
            #define X
            #if X
            class NewType {}
            #endif
            """) : (SyntaxNode)VB.SyntaxFactory.ParseCompilationUnit("""
            #Define X
            #If X
            Class C
            End Class
            #End If
            """);
 
        Assert.True(newRoot.ContainsDirectives);
 
        var tree3 = tree.WithRootAndOptions(newRoot, tree.Options);
        //var recoverableTree3 = Assert.IsAssignableFrom<IRecoverableSyntaxTree>(tree3);
 
        Assert.Equal("old path", tree3.FilePath);
        Assert.Same(tree3, tree3.GetRoot().SyntaxTree);
        Assert.Same(tree.Options, tree3.Options);
        Assert.Same(tree.Encoding, tree3.Encoding);
        Assert.Equal(newRoot.FullSpan.Length, tree3.Length);
        //Assert.True(recoverableTree3.ContainsDirectives);
 
        var newOptions = tree.Options.WithKind(SourceCodeKind.Script);
        var tree4 = tree.WithRootAndOptions(tree.GetRoot(), newOptions);
        //var recoverableTree4 = Assert.IsAssignableFrom<IRecoverableSyntaxTree>(tree4);
 
        Assert.Equal("old path", tree4.FilePath);
        Assert.Same(tree4, tree4.GetRoot().SyntaxTree);
        Assert.Same(newOptions, tree4.Options);
        Assert.Same(tree.Encoding, tree4.Encoding);
        Assert.Equal(tree.Length, tree4.Length);
        //Assert.Equal(recoverableTree.ContainsDirectives, recoverableTree4.ContainsDirectives);
 
        // unchanged:
        Assert.Same(tree, tree.WithRootAndOptions(tree.GetRoot(), tree.Options));
    }
 
    [Fact]
    public void TestWorkspaceLanguageServiceOverride()
    {
        var hostServices = FeaturesTestCompositions.Features.AddParts(
        [
            typeof(TestLanguageServiceA),
            typeof(TestLanguageServiceB),
        ]).GetHostServices();
 
        var ws = new AdhocWorkspace(hostServices, ServiceLayer.Host);
        var service = ws.Services.GetLanguageServices(LanguageNames.CSharp).GetService<ITestLanguageService>();
        Assert.NotNull(service as TestLanguageServiceA);
 
        var ws2 = new AdhocWorkspace(hostServices, "Quasimodo");
        var service2 = ws2.Services.GetLanguageServices(LanguageNames.CSharp).GetService<ITestLanguageService>();
        Assert.NotNull(service2 as TestLanguageServiceB);
    }
 
#if false
    [Fact]
    public void TestSolutionInfo()
    {
        var oldSolutionId = SolutionId.CreateNewId("oldId");
        var oldVersion = VersionStamp.Create();
        var solutionInfo = SolutionInfo.Create(oldSolutionId, oldVersion, null, null);
 
        var newSolutionId = SolutionId.CreateNewId("newId");
        solutionInfo = solutionInfo.WithId(newSolutionId);
        Assert.NotEqual(oldSolutionId, solutionInfo.Id);
        Assert.Equal(newSolutionId, solutionInfo.Id);
        
        var newVersion = oldVersion.GetNewerVersion();
        solutionInfo = solutionInfo.WithVersion(newVersion);
        Assert.NotEqual(oldVersion, solutionInfo.Version);
        Assert.Equal(newVersion, solutionInfo.Version);
 
        Assert.Null(solutionInfo.FilePath);
        var newFilePath = @"C:\test\fake.sln";
        solutionInfo = solutionInfo.WithFilePath(newFilePath);
        Assert.Equal(newFilePath, solutionInfo.FilePath);
 
        Assert.Equal(0, solutionInfo.Projects.Count());
    }
#endif
 
    private interface ITestLanguageService : ILanguageService
    {
    }
 
    [ExportLanguageService(typeof(ITestLanguageService), LanguageNames.CSharp, ServiceLayer.Default), Shared, PartNotDiscoverable]
    private sealed class TestLanguageServiceA : ITestLanguageService
    {
        [ImportingConstructor]
        [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
        public TestLanguageServiceA()
        {
        }
    }
 
    [ExportLanguageService(typeof(ITestLanguageService), LanguageNames.CSharp, "Quasimodo"), Shared, PartNotDiscoverable]
    private sealed class TestLanguageServiceB : ITestLanguageService
    {
        [ImportingConstructor]
        [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
        public TestLanguageServiceB()
        {
        }
    }
 
    [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/666263")]
    public async Task TestDocumentFileAccessFailureMissingFile()
    {
        var workspace = new AdhocWorkspace();
        var solution = workspace.CurrentSolution;
 
        var pid = ProjectId.CreateNewId();
        var did = DocumentId.CreateNewId(pid);
 
        solution = solution
            .AddProject(pid, "goo", "goo", LanguageNames.CSharp)
            .AddDocument(did, "x", new WorkspaceFileTextLoader(solution.Services, @"C:\doesnotexist.cs", Encoding.UTF8))
            .WithDocumentFilePath(did, "document path");
 
        var doc = solution.GetDocument(did);
        var text = await doc.GetTextAsync().ConfigureAwait(false);
 
        var exceptionMessage = await doc.State.GetFailedToLoadExceptionMessageAsync(CancellationToken.None).ConfigureAwait(false);
 
        Assert.NotNull(exceptionMessage);
        Assert.Equal("", text.ToString());
 
        // Verify invariant: The compilation is guaranteed to have a syntax tree for each document of the project (even if the contnet fails to load).
        var compilation = await doc.Project.GetCompilationAsync(CancellationToken.None).ConfigureAwait(false);
        var syntaxTree = compilation.SyntaxTrees.Single();
        Assert.Equal("", syntaxTree.ToString());
    }
 
    [Fact]
    public void TestGetProjectForAssemblySymbol()
    {
        var pid1 = ProjectId.CreateNewId("p1");
        var pid2 = ProjectId.CreateNewId("p2");
        var pid3 = ProjectId.CreateNewId("p3");
        var did1 = DocumentId.CreateNewId(pid1);
        var did2 = DocumentId.CreateNewId(pid2);
        var did3 = DocumentId.CreateNewId(pid3);
 
        var text1 = @"
Public Class A
End Class";
 
        var text2 = @"
Public Class B
End Class
";
 
        var text3 = @"
public class C : B {
}
";
 
        var text4 = @"
public class C : A {
}
";
 
        var solution = new AdhocWorkspace().CurrentSolution
            .AddProject(pid1, "GooA", "Goo.dll", LanguageNames.VisualBasic)
            .AddDocument(did1, "A.vb", text1)
            .AddMetadataReference(pid1, s_mscorlib)
            .AddProject(pid2, "GooB", "Goo2.dll", LanguageNames.VisualBasic)
            .AddDocument(did2, "B.vb", text2)
            .AddMetadataReference(pid2, s_mscorlib)
            .AddProject(pid3, "Bar", "Bar.dll", LanguageNames.CSharp)
            .AddDocument(did3, "C.cs", text3)
            .AddMetadataReference(pid3, s_mscorlib)
            .AddProjectReference(pid3, new ProjectReference(pid1))
            .AddProjectReference(pid3, new ProjectReference(pid2));
 
        var options = solution.Workspace.Services
            .GetLanguageService<ICompilationFactoryService>(LanguageNames.VisualBasic)
            .GetDefaultCompilationOptions()
            .WithOutputKind(OutputKind.DynamicallyLinkedLibrary);
        solution = solution.WithProjectCompilationOptions(pid1, options)
            .WithProjectCompilationOptions(pid2, options);
 
        var project3 = solution.GetProject(pid3);
        var comp3 = project3.GetCompilationAsync().Result;
        var classC = comp3.GetTypeByMetadataName("C");
        var projectForBaseType = solution.GetProject(classC.BaseType.ContainingAssembly);
        Assert.Equal(pid2, projectForBaseType.Id);
 
        // switch base type to A then try again
        var solution2 = solution.WithDocumentText(did3, SourceText.From(text4));
        project3 = solution2.GetProject(pid3);
        comp3 = project3.GetCompilationAsync().Result;
        classC = comp3.GetTypeByMetadataName("C");
        projectForBaseType = solution2.GetProject(classC.BaseType.ContainingAssembly);
        Assert.Equal(pid1, projectForBaseType.Id);
    }
 
    [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1088127")]
    public void TestEncodingRetainedAfterTreeChanged()
    {
        var ws = new AdhocWorkspace();
        var proj = ws.AddProject("proj", LanguageNames.CSharp);
        var doc = ws.AddDocument(proj.Id, "a.cs", SourceText.From("public class c { }", Encoding.UTF32));
 
        Assert.Equal(Encoding.UTF32, doc.GetTextAsync().Result.Encoding);
 
        // updating root doesn't change original encoding
        var root = doc.GetSyntaxRootAsync().Result;
        var newRoot = root.WithLeadingTrivia(root.GetLeadingTrivia().Add(CS.SyntaxFactory.Whitespace("    ")));
        var newDoc = doc.WithSyntaxRoot(newRoot);
 
        Assert.Equal(Encoding.UTF32, newDoc.GetTextAsync().Result.Encoding);
    }
 
    [Fact]
    public async Task TestProjectWithNoMetadataReferencesHasIncompleteReferences()
    {
        var workspace = new AdhocWorkspace();
        var project = workspace.AddProject("CSharpProject", LanguageNames.CSharp);
        Assert.False(await project.HasSuccessfullyLoadedAsync(CancellationToken.None));
        Assert.Empty(project.GetCompilationAsync().Result.ExternalReferences);
    }
 
    [Fact]
    public async Task TestProjectWithNoBrokenReferencesHasNoIncompleteReferences()
    {
        var workspace = new AdhocWorkspace();
        var project1 = workspace.AddProject(
            ProjectInfo.Create(
                ProjectId.CreateNewId(),
                VersionStamp.Create(),
                "CSharpProject",
                "CSharpProject",
                LanguageNames.CSharp,
                metadataReferences: [MscorlibRef],
                compilationOptions: workspace.Services
                    .GetLanguageService<ICompilationFactoryService>(LanguageNames.CSharp)
                    .GetDefaultCompilationOptions()
                    .WithOutputKind(OutputKind.DynamicallyLinkedLibrary)));
        var project2 = workspace.AddProject(
            ProjectInfo.Create(
                ProjectId.CreateNewId(),
                VersionStamp.Create(),
                "VisualBasicProject",
                "VisualBasicProject",
                LanguageNames.VisualBasic,
                metadataReferences: [MscorlibRef],
                projectReferences: [new ProjectReference(project1.Id)]));
 
        // Nothing should have incomplete references, and everything should build
        Assert.True(await project1.HasSuccessfullyLoadedAsync(CancellationToken.None));
        Assert.True(await project2.HasSuccessfullyLoadedAsync(CancellationToken.None));
        Assert.Equal(2, project2.GetCompilationAsync().Result.ExternalReferences.Length);
    }
 
    [Fact]
    public async Task TestProjectWithBrokenCrossLanguageReferenceHasIncompleteReferences()
    {
        var workspace = new AdhocWorkspace();
        var project1 = workspace.AddProject(
            ProjectInfo.Create(
                ProjectId.CreateNewId(),
                VersionStamp.Create(),
                "CSharpProject",
                "CSharpProject",
                LanguageNames.CSharp,
                metadataReferences: [MscorlibRef]));
        workspace.AddDocument(project1.Id, "Broken.cs", SourceText.From("class "));
 
        var project2 = workspace.AddProject(
            ProjectInfo.Create(
                ProjectId.CreateNewId(),
                VersionStamp.Create(),
                "VisualBasicProject",
                "VisualBasicProject",
                LanguageNames.VisualBasic,
                metadataReferences: [MscorlibRef],
                projectReferences: [new ProjectReference(project1.Id)]));
 
        Assert.True(await project1.HasSuccessfullyLoadedAsync(CancellationToken.None));
        Assert.False(await project2.HasSuccessfullyLoadedAsync(CancellationToken.None));
        Assert.Single(project2.GetCompilationAsync().Result.ExternalReferences);
    }
 
    [Fact]
    public async Task TestFrozenPartialProjectHasDifferentSemanticVersions_AddedDoc()
    {
        using var workspace = WorkspaceTestUtilities.CreateWorkspaceWithPartialSemantics();
        var project = workspace.CurrentSolution.AddProject("CSharpProject", "CSharpProject", LanguageNames.CSharp);
        project = project.AddDocument("Extra.cs", SourceText.From("class Extra { }")).Project;
 
        var documentToFreeze = project.AddDocument("DocumentToFreeze.cs", SourceText.From(""));
        var frozenDocument = documentToFreeze.WithFrozenPartialSemantics(CancellationToken.None);
 
        // Because we had no compilation produced yet, we expect that only the DocumentToFreeze is in the compilation
        Assert.NotSame(frozenDocument, documentToFreeze);
        var tree = Assert.Single((await frozenDocument.Project.GetCompilationAsync()).SyntaxTrees);
        Assert.Equal("DocumentToFreeze.cs", tree.FilePath);
 
        // Versions should be different
        Assert.NotEqual(
            await documentToFreeze.Project.GetDependentSemanticVersionAsync(),
            await frozenDocument.Project.GetDependentSemanticVersionAsync());
 
        Assert.NotEqual(
            await documentToFreeze.Project.GetSemanticVersionAsync(),
            await frozenDocument.Project.GetSemanticVersionAsync());
    }
 
    [Fact]
    public async Task TestFreezingTwiceGivesSameDocument()
    {
        using var workspace = WorkspaceTestUtilities.CreateWorkspaceWithPartialSemantics();
        var project = workspace.CurrentSolution.AddProject("CSharpProject", "CSharpProject", LanguageNames.CSharp);
        project = project.AddDocument("Extra.cs", SourceText.From("class Extra { }")).Project;
 
        var documentToFreeze = project.AddDocument("DocumentToFreeze.cs", SourceText.From(""));
        var frozenDocument = documentToFreeze.WithFrozenPartialSemantics(CancellationToken.None);
 
        // Because we had no compilation produced yet, we expect that only the DocumentToFreeze is in the compilation
        Assert.NotSame(frozenDocument, documentToFreeze);
        var tree = Assert.Single((await frozenDocument.Project.GetCompilationAsync()).SyntaxTrees);
        Assert.Equal("DocumentToFreeze.cs", tree.FilePath);
 
        // Versions should be different
        Assert.NotEqual(
            await documentToFreeze.Project.GetDependentSemanticVersionAsync(),
            await frozenDocument.Project.GetDependentSemanticVersionAsync());
 
        Assert.NotEqual(
            await documentToFreeze.Project.GetSemanticVersionAsync(),
            await frozenDocument.Project.GetSemanticVersionAsync());
 
        var frozenDocument2 = frozenDocument.WithFrozenPartialSemantics(CancellationToken.None);
        Assert.Same(frozenDocument, frozenDocument2);
    }
 
    [Fact]
    public async Task TestFrozenPartialProjectHasDifferentSemanticVersions_ChangedDoc1()
    {
        using var workspace = WorkspaceTestUtilities.CreateWorkspaceWithPartialSemantics();
        var project = workspace.CurrentSolution.AddProject("CSharpProject", "CSharpProject", LanguageNames.CSharp);
        project = project.AddDocument("Extra.cs", SourceText.From("class Extra { }")).Project;
 
        var documentToFreezeOriginal = project.AddDocument("DocumentToFreeze.cs", SourceText.From("class DocumentToFreeze { void M() { } }"));
        project = documentToFreezeOriginal.Project;
        var compilation = await project.GetCompilationAsync();
 
        var solution = project.Solution.WithDocumentText(documentToFreezeOriginal.Id, SourceText.From("class DocumentToFreeze { void M() { /*no top level change*/ } }"));
        var documentToFreezeChanged = solution.GetDocument(documentToFreezeOriginal.Id);
        var tree = await documentToFreezeChanged.GetSyntaxTreeAsync();
 
        var frozenDocument = documentToFreezeChanged.WithFrozenPartialSemantics(CancellationToken.None);
 
        Assert.NotSame(frozenDocument, documentToFreezeChanged);
 
        // Versions should the same since there wasn't a top level change different
        Assert.Equal(
            await documentToFreezeOriginal.GetTopLevelChangeTextVersionAsync(),
            await frozenDocument.GetTopLevelChangeTextVersionAsync());
 
        Assert.Equal(
            await documentToFreezeChanged.GetTopLevelChangeTextVersionAsync(),
            await frozenDocument.GetTopLevelChangeTextVersionAsync());
 
        Assert.Equal(
            await documentToFreezeOriginal.Project.GetDependentSemanticVersionAsync(),
            await frozenDocument.Project.GetDependentSemanticVersionAsync());
 
        Assert.Equal(
            await documentToFreezeChanged.Project.GetDependentSemanticVersionAsync(),
            await frozenDocument.Project.GetDependentSemanticVersionAsync());
 
        Assert.Equal(
            await documentToFreezeOriginal.Project.GetSemanticVersionAsync(),
            await frozenDocument.Project.GetSemanticVersionAsync());
 
        Assert.Equal(
            await documentToFreezeChanged.Project.GetSemanticVersionAsync(),
            await frozenDocument.Project.GetSemanticVersionAsync());
    }
 
    [Fact]
    public async Task TestFrozenPartialProjectHasDifferentSemanticVersions_ChangedDoc2()
    {
        using var workspace = WorkspaceTestUtilities.CreateWorkspaceWithPartialSemantics();
        var project = workspace.CurrentSolution.AddProject("CSharpProject", "CSharpProject", LanguageNames.CSharp);
        project = project.AddDocument("Extra.cs", SourceText.From("class Extra { }")).Project;
 
        var documentToFreezeOriginal = project.AddDocument("DocumentToFreeze.cs", SourceText.From("class DocumentToFreeze { void M() { } }"));
        project = documentToFreezeOriginal.Project;
        var compilation = await project.GetCompilationAsync();
 
        var solution = project.Solution.WithDocumentText(documentToFreezeOriginal.Id, SourceText.From("class DocumentToFreeze { void M() { } public void NewMethod() { } }"));
        var documentToFreezeChanged = solution.GetDocument(documentToFreezeOriginal.Id);
        var tree = await documentToFreezeChanged.GetSyntaxTreeAsync();
 
        var frozenDocument = documentToFreezeChanged.WithFrozenPartialSemantics(CancellationToken.None);
 
        Assert.NotSame(frozenDocument, documentToFreezeChanged);
 
        // Before/after the change must always result in a top level change.
        // After the change, we should get the same version between the doc and its frozen version.
        Assert.NotEqual(
            await documentToFreezeOriginal.GetTopLevelChangeTextVersionAsync(),
            await frozenDocument.GetTopLevelChangeTextVersionAsync());
 
        Assert.Equal(
            await documentToFreezeChanged.GetTopLevelChangeTextVersionAsync(),
            await frozenDocument.GetTopLevelChangeTextVersionAsync());
 
        Assert.NotEqual(
            await documentToFreezeOriginal.Project.GetDependentSemanticVersionAsync(),
            await frozenDocument.Project.GetDependentSemanticVersionAsync());
 
        Assert.Equal(
            await documentToFreezeChanged.Project.GetDependentSemanticVersionAsync(),
            await frozenDocument.Project.GetDependentSemanticVersionAsync());
 
        Assert.NotEqual(
            await documentToFreezeOriginal.Project.GetSemanticVersionAsync(),
            await frozenDocument.Project.GetSemanticVersionAsync());
 
        Assert.Equal(
            await documentToFreezeChanged.Project.GetSemanticVersionAsync(),
            await frozenDocument.Project.GetSemanticVersionAsync());
    }
 
    [Fact]
    public async Task TestFrozenPartialProjectAlwaysIsIncomplete()
    {
        var workspace = new AdhocWorkspace();
        var project1 = workspace.AddProject(
            ProjectInfo.Create(
                ProjectId.CreateNewId(),
                VersionStamp.Create(),
                "CSharpProject",
                "CSharpProject",
                LanguageNames.CSharp,
                metadataReferences: [MscorlibRef],
                compilationOptions: workspace.Services
                    .GetLanguageService<ICompilationFactoryService>(LanguageNames.CSharp)
                    .GetDefaultCompilationOptions()
                    .WithOutputKind(OutputKind.DynamicallyLinkedLibrary)));
 
        var project2 = workspace.AddProject(
            ProjectInfo.Create(
                ProjectId.CreateNewId(),
                VersionStamp.Create(),
                "VisualBasicProject",
                "VisualBasicProject",
                LanguageNames.VisualBasic,
                metadataReferences: [MscorlibRef],
                projectReferences: [new ProjectReference(project1.Id)]));
 
        var document = workspace.AddDocument(project2.Id, "Test.cs", SourceText.From(""));
 
        // Nothing should have incomplete references, and everything should build
        var frozenSolution = document.WithFrozenPartialSemantics(CancellationToken.None).Project.Solution;
 
        Assert.True(await frozenSolution.GetProject(project1.Id).HasSuccessfullyLoadedAsync(CancellationToken.None));
        Assert.True(await frozenSolution.GetProject(project2.Id).HasSuccessfullyLoadedAsync(CancellationToken.None));
    }
 
    [Fact]
    public async Task TestFrozenPartialSemanticsProjectDoesNotHaveAdditionalDocumentsFromInProgressChange()
    {
        using var workspace = CreateWorkspaceWithPartialSemantics();
        var project = workspace.CurrentSolution.AddProject("TestProject", "TestProject", LanguageNames.CSharp)
            .AddDocument("RegularDocument.cs", "// Source File", filePath: "RegularDocument.cs").Project;
 
        // Fetch the compilation to ensure further changes produce in progress states
        var originalCompilation = await project.GetCompilationAsync();
        project = project.AddAdditionalDocument("Test.txt", "").Project;
 
        // Freeze semantics -- this should give us a compilation and state that don't include the additional file,
        // since the compilation won't represent that either
        var frozenDocument = project.Documents.Single().WithFrozenPartialSemantics(CancellationToken.None);
 
        Assert.Empty(frozenDocument.Project.AdditionalDocuments);
    }
 
    [Fact]
    public async Task TestFrozenPartialSemanticsNoCompilationYetBuilt()
    {
        using var workspace = CreateWorkspaceWithPartialSemantics();
        var project = workspace.CurrentSolution.AddProject("TestProject", "TestProject", LanguageNames.CSharp)
            .AddDocument("RegularDocument.cs", "// Source File", filePath: "RegularDocument.cs").Project
            .AddDocument("RegularDocument2.cs", "// Source File", filePath: "RegularDocument2.cs").Project;
 
        // Freeze semantics -- that document should be there, but nothing else will be yet.
        var frozenDocument = project.Documents.First().WithFrozenPartialSemantics(CancellationToken.None);
 
        Assert.Single(frozenDocument.Project.Documents);
        var singleTree = Assert.Single((await frozenDocument.Project.GetCompilationAsync()).SyntaxTrees);
        Assert.Same(await frozenDocument.GetSyntaxTreeAsync(), singleTree);
    }
 
    [Fact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1467404")]
    public async Task TestFrozenPartialSemanticsHandlesDocumentWithSamePathBeingRemovedAndAdded()
    {
        using var workspace = CreateWorkspaceWithPartialSemantics();
        var originalProject = workspace.CurrentSolution.AddProject("TestProject", "TestProject", LanguageNames.CSharp)
            .AddDocument("RegularDocument.cs", "// Source File", filePath: "RegularDocument.cs").Project;
 
        // Fetch the compilation to ensure further changes produce in progress states
        var originalCompilation = await originalProject.GetCompilationAsync();
        var forkedProject = originalProject.RemoveDocument(originalProject.DocumentIds.Single())
            .AddDocument("RegularDocument.cs", "// Source File", filePath: "RegularDocument.cs").Project;
 
        var frozenDocument = forkedProject.Documents.Single().WithFrozenPartialSemantics(CancellationToken.None);
 
        // There will be two documents.  That's because freezing the solution ends up jumping back to hte point in
        // time before the remove/add happened (so the original doc is there).  Then, the new doc is added as a
        // sibling. That they have the same path is not relevant.  They have different IDs and thus are considered
        // different.
        Assert.Equal(2, frozenDocument.Project.Documents.Count());
        var frozenCompilation = await frozenDocument.Project.GetCompilationAsync();
        Assert.True(frozenCompilation.ContainsSyntaxTree(await frozenDocument.GetSyntaxTreeAsync()));
        Assert.True(frozenCompilation.ContainsSyntaxTree(await originalProject.Documents.Single().GetSyntaxTreeAsync()));
    }
 
    [Fact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1467404")]
    public async Task TestFrozenPartialSemanticsHandlesRemoveAndAddWithNullPathAndDifferentNames()
    {
        using var workspace = CreateWorkspaceWithPartialSemantics();
        var originalProject = workspace.CurrentSolution.AddProject("TestProject", "TestProject", LanguageNames.CSharp)
            .AddDocument("RegularDocument.cs", "// Source File", filePath: null).Project;
 
        // Fetch the compilation to ensure further changes produce in progress states
        var originalCompilation = await originalProject.GetCompilationAsync();
        var forkedProject = originalProject.RemoveDocument(originalProject.DocumentIds.Single())
            .AddDocument("RegularDocument2.cs", "// Source File", filePath: null).Project;
 
        var frozenDocument = forkedProject.Documents.Single().WithFrozenPartialSemantics(CancellationToken.None);
 
        // There will be two documents.  That's because freezing the solution ends up jumping back to hte point in
        // time before the remove/add happened (so the original doc is there).  Then, the new doc is added as a
        // sibling. That they have the same path is not relevant.  They have different IDs and thus are considered
        // different.
        // There will be two documents.  That's because freezing the solution ends up jumping back to hte point in
        // time before the remove/add happened (so the original doc is there).  Then, the new doc is added as a
        // sibling. That they have the same path is not relevant.  They have different IDs and thus are considered
        // different.
        Assert.Equal(2, frozenDocument.Project.Documents.Count());
        var frozenCompilation = await frozenDocument.Project.GetCompilationAsync();
        Assert.True(frozenCompilation.ContainsSyntaxTree(await frozenDocument.GetSyntaxTreeAsync()));
        Assert.True(frozenCompilation.ContainsSyntaxTree(await originalProject.Documents.Single().GetSyntaxTreeAsync()));
    }
 
    [Fact]
    public async Task TestFrozenPartialSemanticsAfterSingleTextEdit()
    {
        using var workspace = CreateWorkspaceWithPartialSemantics();
        var document = workspace.CurrentSolution.AddProject("TestProject", "TestProject", LanguageNames.CSharp)
            .AddDocument("RegularDocument.cs", "// Source File", filePath: null);
 
        // Fetch the compilation to ensure further changes produce in progress states
        var originalCompilation = await document.Project.GetCompilationAsync();
        document = document.WithText(SourceText.From("// Source File with Changes"));
 
        var frozenDocument = document.WithFrozenPartialSemantics(CancellationToken.None);
 
        Assert.Contains(await frozenDocument.GetSyntaxTreeAsync(), (await frozenDocument.Project.GetCompilationAsync()).SyntaxTrees);
    }
 
    [Theory, CombinatorialData]
    public async Task TestFrozenPartialSemanticsWithMulitipleUnrelatedEdits([CombinatorialValues(1, 2, 3)] int documentToFreeze)
    {
        using var workspace = CreateWorkspaceWithPartialSemantics();
        var solution = workspace.CurrentSolution.AddProject("TestProject", "TestProject", LanguageNames.CSharp).Solution;
 
        var documentId1 = DocumentId.CreateNewId(solution.ProjectIds.Single());
        var documentId2 = DocumentId.CreateNewId(solution.ProjectIds.Single());
        var documentId3 = DocumentId.CreateNewId(solution.ProjectIds.Single());
 
        solution = solution
            .AddDocument(documentId1, nameof(documentId1), "// Document 1")
            .AddDocument(documentId2, nameof(documentId2), "// Document 2")
            .AddDocument(documentId3, nameof(documentId3), "// Document 3");
 
        // Fetch the compilation to ensure further changes produce in progress states
        var originalCompilation = await solution.Projects.Single().GetCompilationAsync();
 
        solution = solution
            .WithDocumentText(documentId1, SourceText.From("// Document 1 Changed"))
            .WithDocumentText(documentId2, SourceText.From("// Document 2 Changed"))
            .WithDocumentText(documentId3, SourceText.From("// Document 3 Changed"));
 
        // We will freeze the appropriate document -- the reason we have three here is the code path might work accidentally if it
        // was the first or last change made, so covering "first", "middle" and "last" ensures that's covered.
        var documentIdToFreeze = documentToFreeze == 1 ? documentId1 : documentToFreeze == 2 ? documentId2 : documentId3;
 
        var frozen = solution.GetRequiredDocument(documentIdToFreeze).WithFrozenPartialSemantics(CancellationToken.None);
 
        var tree = await frozen.GetSyntaxTreeAsync();
        Assert.Contains("Changed", tree.ToString());
        Assert.Contains(tree, (await frozen.Project.GetCompilationAsync()).SyntaxTrees);
    }
 
    [Fact]
    public async Task TestFrozenPartialSemanticsOfLinkedDocuments()
    {
        const int ClassDeclaration = 8855;
        const int StructDeclaration = 8856;
 
        using var workspace = CreateWorkspaceWithPartialSemantics();
 
        var contents = """
            #if X
 
            class C
            {
            }
 
            #else
 
            struct D
            {
            }
 
            #endif
            """;
 
        // Create a normal solution with a linked doc, where each project should see a different tree for the above contents.
 
        var currentSolution = workspace.CurrentSolution;
        var document1 = currentSolution.AddProject("TestProject1", "TestProject1", LanguageNames.CSharp)
            .AddDocument("RegularDocument.cs", contents, filePath: "RegularDocument.cs");
        currentSolution = document1.Project.Solution;
 
        var document2 = currentSolution.AddProject("TestProject2", "TestProject2", LanguageNames.CSharp)
            .AddDocument("RegularDocument.cs", contents, filePath: "RegularDocument.cs");
        currentSolution = document2.Project.Solution;
 
        var options = (CSharpParseOptions)document1.Project.ParseOptions;
        currentSolution = currentSolution.WithProjectParseOptions(document1.Project.Id, options.WithPreprocessorSymbols("X"));
 
        var relatedIds1 = currentSolution.GetRelatedDocumentIds(document1.Id);
        var relatedIds2 = currentSolution.GetRelatedDocumentIds(document2.Id);
        AssertEx.SetEqual(relatedIds1, ImmutableArray.Create(document1.Id, document2.Id));
        AssertEx.SetEqual(relatedIds2, ImmutableArray.Create(document1.Id, document2.Id));
 
        document1 = currentSolution.GetRequiredDocument(document1.Id);
        document2 = currentSolution.GetRequiredDocument(document2.Id);
 
        var doc1Root = await document1.GetRequiredSyntaxRootAsync(CancellationToken.None);
        var doc2Root = await document2.GetRequiredSyntaxRootAsync(CancellationToken.None);
 
        Assert.NotSame(doc1Root, doc2Root);
        Assert.False(doc1Root.IsEquivalentTo(doc2Root));
 
        {
            // Now get the frozen version of each document.  Freezing will update the siblings to have the same tree contents.
            var frozenDoc1 = document1.WithFrozenPartialSemantics(CancellationToken.None);
            var frozenDoc2 = frozenDoc1.Project.Solution.GetRequiredDocument(document2.Id);
 
            var frozenDoc1Root = await frozenDoc1.GetRequiredSyntaxRootAsync(CancellationToken.None);
            var frozenDoc2Root = await frozenDoc2.GetRequiredSyntaxRootAsync(CancellationToken.None);
            Assert.NotSame(frozenDoc1Root, frozenDoc2Root);
            Assert.True(frozenDoc1Root.IsEquivalentTo(frozenDoc2Root));
 
            // We're seeing project1's view of the file.  so we should only see a class decl and no struct decl.
            Assert.True(frozenDoc1Root.DescendantNodes().Any(n => n.RawKind == ClassDeclaration));
            Assert.False(frozenDoc1Root.DescendantNodes().Any(n => n.RawKind == StructDeclaration));
        }
 
        {
            // Now get the frozen version of each document.  Freezing will update the siblings to have the same tree contents.
            var frozenDoc2 = document2.WithFrozenPartialSemantics(CancellationToken.None);
            var frozenDoc1 = frozenDoc2.Project.Solution.GetRequiredDocument(document1.Id);
 
            var frozenDoc1Root = await frozenDoc1.GetRequiredSyntaxRootAsync(CancellationToken.None);
            var frozenDoc2Root = await frozenDoc2.GetRequiredSyntaxRootAsync(CancellationToken.None);
            Assert.NotSame(frozenDoc1Root, frozenDoc2Root);
            Assert.True(frozenDoc1Root.IsEquivalentTo(frozenDoc2Root));
 
            // We're seeing project2's view of the file.  so we should only see a struct decl and no class decl.
            Assert.False(frozenDoc1Root.DescendantNodes().Any(n => n.RawKind == ClassDeclaration));
            Assert.True(frozenDoc1Root.DescendantNodes().Any(n => n.RawKind == StructDeclaration));
        }
    }
 
    [Fact]
    public async Task TestProjectCompletenessWithMultipleProjects()
    {
        GetMultipleProjects(out var csBrokenProject, out var vbNormalProject, out var dependsOnBrokenProject, out var dependsOnVbNormalProject, out var transitivelyDependsOnBrokenProjects, out var transitivelyDependsOnNormalProjects);
 
        // check flag for a broken project itself
        Assert.False(await csBrokenProject.HasSuccessfullyLoadedAsync(CancellationToken.None));
 
        // check flag for a normal project itself
        Assert.True(await vbNormalProject.HasSuccessfullyLoadedAsync(CancellationToken.None));
 
        // check flag for normal project that directly reference a broken project
        Assert.True(await dependsOnBrokenProject.HasSuccessfullyLoadedAsync(CancellationToken.None));
 
        // check flag for normal project that directly reference only normal project
        Assert.True(await dependsOnVbNormalProject.HasSuccessfullyLoadedAsync(CancellationToken.None));
 
        // check flag for normal project that indirectly reference a borken project
        // normal project -> normal project -> broken project
        Assert.True(await transitivelyDependsOnBrokenProjects.HasSuccessfullyLoadedAsync(CancellationToken.None));
 
        // check flag for normal project that indirectly reference only normal project
        // normal project -> normal project -> normal project
        Assert.True(await transitivelyDependsOnNormalProjects.HasSuccessfullyLoadedAsync(CancellationToken.None));
    }
 
    private sealed class TestSmallFileTextLoader : FileTextLoader
    {
        public TestSmallFileTextLoader(string path, Encoding encoding)
            : base(path, encoding)
        {
        }
 
        // set max file length to 1 byte
        internal override int MaxFileLength => 1;
    }
 
    [Fact]
    public async Task TestMassiveFileSize()
    {
        using var root = new TempRoot();
        var file = root.CreateFile(prefix: "massiveFile", extension: ".cs").WriteAllText("hello");
 
        var loader = new TestSmallFileTextLoader(file.Path, Encoding.UTF8);
 
        var textLength = FileUtilities.GetFileLength(file.Path);
 
        var expected = string.Format(WorkspacesResources.File_0_size_of_1_exceeds_maximum_allowed_size_of_2, file.Path, textLength, 1);
        var exceptionThrown = false;
 
        try
        {
            // test async one
            var unused = await loader.LoadTextAndVersionAsync(new LoadTextOptions(SourceHashAlgorithms.Default), CancellationToken.None);
        }
        catch (InvalidDataException ex)
        {
            exceptionThrown = true;
            Assert.Equal(expected, ex.Message);
        }
 
        Assert.True(exceptionThrown);
 
        exceptionThrown = false;
        try
        {
            // test sync one
            var unused = loader.LoadTextAndVersionSynchronously(new LoadTextOptions(SourceHashAlgorithms.Default), CancellationToken.None);
        }
        catch (InvalidDataException ex)
        {
            exceptionThrown = true;
            Assert.Equal(expected, ex.Message);
        }
 
        Assert.True(exceptionThrown);
    }
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/18697")]
    public void TestWithSyntaxTree()
    {
        // get one to get to syntax tree factory
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution;
        var dummyProject = solution.AddProject("dummy", "dummy", LanguageNames.CSharp);
 
        var factory = dummyProject.Services.GetService<ISyntaxTreeFactoryService>();
 
        // create the origin tree
        var text = SourceText.From("// empty", encoding: null, SourceHashAlgorithms.Default);
        var strongTree = factory.ParseSyntaxTree("dummy", dummyProject.ParseOptions, text, CancellationToken.None);
 
        var sourceText = strongTree.GetText();
    }
 
    [Fact]
    public void TestUpdateDocumentsOrder()
    {
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution;
        var pid = ProjectId.CreateNewId();
 
        VersionStamp GetVersion() => solution.GetProject(pid).Version;
        ImmutableArray<DocumentId> GetDocumentIds() => [.. solution.GetProject(pid).DocumentIds];
        ImmutableArray<SyntaxTree> GetSyntaxTrees()
        {
            return solution.GetProject(pid).GetCompilationAsync().Result.SyntaxTrees.ToImmutableArray();
        }
 
        solution = solution.AddProject(pid, "test", "test.dll", LanguageNames.CSharp);
 
        var text1 = "public class Test1 {}";
        var did1 = DocumentId.CreateNewId(pid);
        solution = solution.AddDocument(did1, "test1.cs", text1);
 
        var text2 = "public class Test2 {}";
        var did2 = DocumentId.CreateNewId(pid);
        solution = solution.AddDocument(did2, "test2.cs", text2);
 
        var text3 = "public class Test3 {}";
        var did3 = DocumentId.CreateNewId(pid);
        solution = solution.AddDocument(did3, "test3.cs", text3);
 
        var text4 = "public class Test4 {}";
        var did4 = DocumentId.CreateNewId(pid);
        solution = solution.AddDocument(did4, "test4.cs", text4);
 
        var text5 = "public class Test5 {}";
        var did5 = DocumentId.CreateNewId(pid);
        solution = solution.AddDocument(did5, "test5.cs", text5);
 
        var oldVersion = GetVersion();
 
        solution = solution.WithProjectDocumentsOrder(pid, ImmutableList.CreateRange([did5, did4, did3, did2, did1]));
 
        var newVersion = GetVersion();
 
        // Make sure we have a new version because the order changed.
        Assert.NotEqual(oldVersion, newVersion);
 
        var documentIds = GetDocumentIds();
 
        Assert.Equal(did5, documentIds[0]);
        Assert.Equal(did4, documentIds[1]);
        Assert.Equal(did3, documentIds[2]);
        Assert.Equal(did2, documentIds[3]);
        Assert.Equal(did1, documentIds[4]);
 
        var syntaxTrees = GetSyntaxTrees();
 
        Assert.Equal(documentIds.Length, syntaxTrees.Length);
 
        Assert.Equal("test5.cs", syntaxTrees[0].FilePath, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("test4.cs", syntaxTrees[1].FilePath, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("test3.cs", syntaxTrees[2].FilePath, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("test2.cs", syntaxTrees[3].FilePath, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("test1.cs", syntaxTrees[4].FilePath, StringComparer.OrdinalIgnoreCase);
 
        solution = solution.WithProjectDocumentsOrder(pid, ImmutableList.CreateRange([did5, did4, did3, did2, did1]));
 
        var newSameVersion = GetVersion();
 
        // Make sure we have the same new version because the order hasn't changed.
        Assert.Equal(newVersion, newSameVersion);
    }
 
    [Fact]
    public void TestUpdateDocumentsOrderExceptions()
    {
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution;
        var pid = ProjectId.CreateNewId();
 
        solution = solution.AddProject(pid, "test", "test.dll", LanguageNames.CSharp);
 
        var text1 = "public class Test1 {}";
        var did1 = DocumentId.CreateNewId(pid);
        solution = solution.AddDocument(did1, "test1.cs", text1);
 
        var text2 = "public class Test2 {}";
        var did2 = DocumentId.CreateNewId(pid);
        solution = solution.AddDocument(did2, "test2.cs", text2);
 
        var text3 = "public class Test3 {}";
        var did3 = DocumentId.CreateNewId(pid);
        solution = solution.AddDocument(did3, "test3.cs", text3);
 
        var text4 = "public class Test4 {}";
        var did4 = DocumentId.CreateNewId(pid);
        solution = solution.AddDocument(did4, "test4.cs", text4);
 
        var text5 = "public class Test5 {}";
        var did5 = DocumentId.CreateNewId(pid);
        solution = solution.AddDocument(did5, "test5.cs", text5);
 
        solution = solution.RemoveDocument(did5);
 
        Assert.Throws<ArgumentException>(() => solution = solution.WithProjectDocumentsOrder(pid, ImmutableList.Create<DocumentId>()));
        Assert.Throws<ArgumentNullException>(() => solution = solution.WithProjectDocumentsOrder(pid, null));
        Assert.Throws<InvalidOperationException>(() => solution = solution.WithProjectDocumentsOrder(pid, ImmutableList.CreateRange([did5, did3, did2, did1])));
        Assert.Throws<ArgumentException>(() => solution = solution.WithProjectDocumentsOrder(pid, ImmutableList.CreateRange([did3, did2, did1])));
    }
 
    [Theory, CombinatorialData]
    public async Task TestAddingEditorConfigFileWithDiagnosticSeverity([CombinatorialValues(LanguageNames.CSharp, LanguageNames.VisualBasic)] string languageName)
    {
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution;
        var extension = languageName == LanguageNames.CSharp ? ".cs" : ".vb";
        var projectId = ProjectId.CreateNewId();
        var sourceDocumentId = DocumentId.CreateNewId(projectId);
 
        solution = solution.AddProject(projectId, "Test", "Test.dll", languageName);
        solution = solution.AddDocument(sourceDocumentId, "Test" + extension, "", filePath: @"Z:\Test" + extension);
 
        var originalSyntaxTree = await solution.GetDocument(sourceDocumentId).GetSyntaxTreeAsync();
        var originalCompilation = await solution.GetProject(projectId).GetCompilationAsync();
 
        var editorConfigDocumentId = DocumentId.CreateNewId(projectId);
        solution = solution.AddAnalyzerConfigDocuments(ImmutableArray.Create(
            DocumentInfo.Create(
                editorConfigDocumentId,
                ".editorconfig",
                filePath: @"Z:\.editorconfig",
                loader: TextLoader.From(TextAndVersion.Create(SourceText.From("[*.*]\r\n\r\ndotnet_diagnostic.CA1234.severity = error"), VersionStamp.Default)))));
 
        var newSyntaxTree = await solution.GetDocument(sourceDocumentId).GetSyntaxTreeAsync();
        var project = solution.GetProject(projectId);
        var newCompilation = await project.GetCompilationAsync();
 
        Assert.Same(originalSyntaxTree, newSyntaxTree);
        Assert.NotSame(originalCompilation, newCompilation);
        Assert.NotEqual(originalCompilation.Options, newCompilation.Options);
 
        var provider = project.CompilationOptions.SyntaxTreeOptionsProvider;
        Assert.True(provider.TryGetDiagnosticValue(newSyntaxTree, "CA1234", CancellationToken.None, out var severity));
        Assert.Equal(ReportDiagnostic.Error, severity);
    }
 
    [Theory, CombinatorialData]
    public async Task TestAddingAndRemovingEditorConfigFileWithDiagnosticSeverity([CombinatorialValues(LanguageNames.CSharp, LanguageNames.VisualBasic)] string languageName)
    {
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution;
        var extension = languageName == LanguageNames.CSharp ? ".cs" : ".vb";
        var projectId = ProjectId.CreateNewId();
        var sourceDocumentId = DocumentId.CreateNewId(projectId);
 
        solution = solution.AddProject(projectId, "Test", "Test.dll", languageName);
        solution = solution.AddDocument(sourceDocumentId, "Test" + extension, "", filePath: @"Z:\Test" + extension);
 
        var editorConfigDocumentId = DocumentId.CreateNewId(projectId);
        solution = solution.AddAnalyzerConfigDocuments(ImmutableArray.Create(
            DocumentInfo.Create(
                editorConfigDocumentId,
                ".editorconfig",
                filePath: @"Z:\.editorconfig",
                loader: TextLoader.From(TextAndVersion.Create(SourceText.From("[*.*]\r\n\r\ndotnet_diagnostic.CA1234.severity = error"), VersionStamp.Default)))));
 
        var syntaxTreeAfterAddingEditorConfig = await solution.GetDocument(sourceDocumentId).GetSyntaxTreeAsync();
 
        var project = solution.GetProject(projectId);
 
        var provider = project.CompilationOptions.SyntaxTreeOptionsProvider;
        Assert.True(provider.TryGetDiagnosticValue(syntaxTreeAfterAddingEditorConfig, "CA1234", CancellationToken.None, out var severity));
        Assert.Equal(ReportDiagnostic.Error, severity);
 
        solution = solution.RemoveAnalyzerConfigDocument(editorConfigDocumentId);
        project = solution.GetProject(projectId);
 
        var syntaxTreeAfterRemovingEditorConfig = await solution.GetDocument(sourceDocumentId).GetSyntaxTreeAsync();
 
        provider = project.CompilationOptions.SyntaxTreeOptionsProvider;
        Assert.False(provider.TryGetDiagnosticValue(syntaxTreeAfterAddingEditorConfig, "CA1234", CancellationToken.None, out _));
 
        var finalCompilation = await project.GetCompilationAsync();
 
        Assert.True(finalCompilation.ContainsSyntaxTree(syntaxTreeAfterRemovingEditorConfig));
    }
 
    [Theory, CombinatorialData]
    public async Task TestChangingAnEditorConfigFile([CombinatorialValues(LanguageNames.CSharp, LanguageNames.VisualBasic)] string languageName)
    {
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution;
        var extension = languageName == LanguageNames.CSharp ? ".cs" : ".vb";
        var projectId = ProjectId.CreateNewId();
        var sourceDocumentId = DocumentId.CreateNewId(projectId);
 
        solution = solution.AddProject(projectId, "Test", "Test.dll", languageName);
        solution = solution.AddDocument(sourceDocumentId, "Test" + extension, "", filePath: @"Z:\Test" + extension);
 
        var editorConfigDocumentId = DocumentId.CreateNewId(projectId);
        solution = solution.AddAnalyzerConfigDocuments(ImmutableArray.Create(
            DocumentInfo.Create(
                editorConfigDocumentId,
                ".editorconfig",
                filePath: @"Z:\.editorconfig",
                loader: TextLoader.From(TextAndVersion.Create(SourceText.From("[*.*]\r\n\r\ndotnet_diagnostic.CA1234.severity = error"), VersionStamp.Default)))));
 
        var syntaxTreeBeforeEditorConfigChange = await solution.GetDocument(sourceDocumentId).GetSyntaxTreeAsync();
 
        var project = solution.GetProject(projectId);
        var provider = project.CompilationOptions.SyntaxTreeOptionsProvider;
        Assert.Equal(provider, (await project.GetCompilationAsync()).Options.SyntaxTreeOptionsProvider);
        Assert.True(provider.TryGetDiagnosticValue(syntaxTreeBeforeEditorConfigChange, "CA1234", CancellationToken.None, out var severity));
        Assert.Equal(ReportDiagnostic.Error, severity);
 
        solution = solution.WithAnalyzerConfigDocumentTextLoader(
            editorConfigDocumentId,
            TextLoader.From(TextAndVersion.Create(SourceText.From("[*.*]\r\n\r\ndotnet_diagnostic.CA6789.severity = error"), VersionStamp.Default)),
            PreservationMode.PreserveValue);
 
        var syntaxTreeAfterEditorConfigChange = await solution.GetDocument(sourceDocumentId).GetSyntaxTreeAsync();
 
        project = solution.GetProject(projectId);
        provider = project.CompilationOptions.SyntaxTreeOptionsProvider;
        Assert.Equal(provider, (await project.GetCompilationAsync()).Options.SyntaxTreeOptionsProvider);
        Assert.True(provider.TryGetDiagnosticValue(syntaxTreeBeforeEditorConfigChange, "CA6789", CancellationToken.None, out severity));
        Assert.Equal(ReportDiagnostic.Error, severity);
 
        var finalCompilation = await project.GetCompilationAsync();
 
        Assert.True(finalCompilation.ContainsSyntaxTree(syntaxTreeAfterEditorConfigChange));
    }
 
    [Fact]
    public void TestAddingAndRemovingGlobalEditorConfigFileWithDiagnosticSeverity()
    {
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution;
        var projectId = ProjectId.CreateNewId();
        var sourceDocumentId = DocumentId.CreateNewId(projectId);
 
        solution = solution.AddProject(projectId, "Test", "Test.dll", LanguageNames.CSharp);
        solution = solution.AddDocument(sourceDocumentId, "Test.cs", "", filePath: @"Z:\Test.cs");
 
        var originalProvider = solution.GetProject(projectId).CompilationOptions.SyntaxTreeOptionsProvider;
        Assert.False(originalProvider.TryGetGlobalDiagnosticValue("CA1234", default, out _));
 
        var editorConfigDocumentId = DocumentId.CreateNewId(projectId);
        solution = solution.AddAnalyzerConfigDocuments(ImmutableArray.Create(
            DocumentInfo.Create(
                editorConfigDocumentId,
                ".globalconfig",
                filePath: @"Z:\.globalconfig",
                loader: TextLoader.From(TextAndVersion.Create(SourceText.From("is_global = true\r\n\r\ndotnet_diagnostic.CA1234.severity = error"), VersionStamp.Default)))));
 
        var newProvider = solution.GetProject(projectId).CompilationOptions.SyntaxTreeOptionsProvider;
        Assert.True(newProvider.TryGetGlobalDiagnosticValue("CA1234", default, out var severity));
        Assert.Equal(ReportDiagnostic.Error, severity);
 
        solution = solution.RemoveAnalyzerConfigDocument(editorConfigDocumentId);
        var finalProvider = solution.GetProject(projectId).CompilationOptions.SyntaxTreeOptionsProvider;
        Assert.False(finalProvider.TryGetGlobalDiagnosticValue("CA1234", default, out _));
    }
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/3705")]
    public async Task TestAddingEditorConfigFileWithIsGeneratedCodeOption()
    {
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution;
        var projectId = ProjectId.CreateNewId();
        var sourceDocumentId = DocumentId.CreateNewId(projectId);
 
        solution = solution.AddProject(projectId, "Test", "Test.dll", LanguageNames.CSharp)
            .WithProjectMetadataReferences(projectId, [NetFramework.mscorlib])
            .WithProjectCompilationOptions(projectId, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary).WithNullableContextOptions(NullableContextOptions.Enable));
        var src = @"
class C
{
    void M(C? c)
    {
        _ = c.ToString();   // warning CS8602: Dereference of a possibly null reference.
    }
}";
        solution = solution.AddDocument(sourceDocumentId, "Test.cs", src, filePath: @"Z:\Test.cs");
 
        var originalSyntaxTree = await solution.GetDocument(sourceDocumentId).GetSyntaxTreeAsync();
        var originalCompilation = await solution.GetProject(projectId).GetCompilationAsync();
 
        // warning CS8602: Dereference of a possibly null reference.
        var diagnostics = originalCompilation.GetDiagnostics();
        var diagnostic = Assert.Single(diagnostics);
        Assert.Equal("CS8602", diagnostic.Id);
 
        var editorConfigDocumentId = DocumentId.CreateNewId(projectId);
        solution = solution.AddAnalyzerConfigDocuments(ImmutableArray.Create(
            DocumentInfo.Create(
                editorConfigDocumentId,
                ".editorconfig",
                filePath: @"Z:\.editorconfig",
                loader: TextLoader.From(TextAndVersion.Create(SourceText.From("[*.*]\r\n\r\ngenerated_code = true"), VersionStamp.Default)))));
 
        var newSyntaxTree = await solution.GetDocument(sourceDocumentId).GetSyntaxTreeAsync();
        var newCompilation = await solution.GetProject(projectId).GetCompilationAsync();
 
        Assert.Same(originalSyntaxTree, newSyntaxTree);
        Assert.NotSame(originalCompilation, newCompilation);
        Assert.NotEqual(originalCompilation.Options, newCompilation.Options);
 
        // warning CS8669: The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
        // Auto-generated code requires an explicit '#nullable' directive in source.
        diagnostics = newCompilation.GetDiagnostics();
        diagnostic = Assert.Single(diagnostics);
        Assert.Contains("CS8669", diagnostic.Id);
    }
 
    [Fact]
    public void NoCompilationProjectsHaveNullSyntaxTreesAndSemanticModels()
    {
        using var workspace = CreateWorkspace([typeof(NoCompilationLanguageService)]);
        var solution = workspace.CurrentSolution;
        var projectId = ProjectId.CreateNewId();
        var documentId = DocumentId.CreateNewId(projectId);
 
        solution = solution.AddProject(projectId, "Test", "Test.dll", NoCompilationConstants.LanguageName);
        solution = solution.AddDocument(documentId, "Test.cs", "", filePath: @"Z:\Test.txt");
 
        var document = solution.GetDocument(documentId)!;
 
        Assert.False(document.TryGetSyntaxTree(out _));
        Assert.Null(document.GetSyntaxTreeAsync().Result);
        Assert.Null(document.GetSyntaxTreeSynchronously(CancellationToken.None));
 
        Assert.False(document.TryGetSemanticModel(out _));
        Assert.Null(document.GetSemanticModelAsync().Result);
    }
 
    [Fact]
    public void NoCompilation_SourceCodeKind()
    {
        using var workspace = CreateWorkspace([typeof(NoCompilationLanguageService)]);
        var projectId = ProjectId.CreateNewId();
        var documentId = DocumentId.CreateNewId(projectId);
 
        var solution = workspace.CurrentSolution
            .AddProject(projectId, "Test", "Test.dll", NoCompilationConstants.LanguageName)
            .AddDocument(DocumentInfo.Create(documentId, "Test", sourceCodeKind: SourceCodeKind.Script));
 
        var document1 = solution.GetRequiredDocument(documentId);
        Assert.Equal(SourceCodeKind.Script, document1.SourceCodeKind);
        Assert.Null(document1.DocumentState.ParseOptions);
 
        var document2 = document1.WithSourceCodeKind(SourceCodeKind.Regular);
        Assert.Equal(SourceCodeKind.Regular, document2.SourceCodeKind);
        Assert.Null(document2.DocumentState.ParseOptions);
    }
 
    [Fact]
    public void ChangingFilePathOfFileInNoCompilationProjectWorks()
    {
        using var workspace = CreateWorkspace([typeof(NoCompilationLanguageService)]);
        var solution = workspace.CurrentSolution;
        var projectId = ProjectId.CreateNewId();
        var documentId = DocumentId.CreateNewId(projectId);
 
        solution = solution.AddProject(projectId, "Test", "Test.dll", NoCompilationConstants.LanguageName);
        solution = solution.AddDocument(documentId, "Test.cs", "", filePath: @"Z:\Test.txt");
 
        Assert.Null(solution.GetDocument(documentId)!.GetSyntaxTreeAsync().Result);
 
        solution = solution.WithDocumentFilePath(documentId, @"Z:\NewPath.txt");
 
        Assert.Null(solution.GetDocument(documentId)!.GetSyntaxTreeAsync().Result);
    }
 
    [Fact]
    public void AddingAndRemovingProjectsUpdatesFilePathMap()
    {
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution;
        var projectId = ProjectId.CreateNewId();
        var editorConfigDocumentId = DocumentId.CreateNewId(projectId);
 
        const string editorConfigFilePath = @"Z:\.editorconfig";
 
        var projectInfo =
            ProjectInfo.Create(projectId, VersionStamp.Default, "Test", "Test", LanguageNames.CSharp)
                .WithAnalyzerConfigDocuments([DocumentInfo.Create(editorConfigDocumentId, ".editorconfig", filePath: editorConfigFilePath)]);
 
        solution = solution.AddProject(projectInfo);
 
        Assert.Equal(editorConfigDocumentId, Assert.Single(solution.GetDocumentIdsWithFilePath(editorConfigFilePath)));
 
        solution = solution.RemoveProject(projectId);
 
        Assert.Empty(solution.GetDocumentIdsWithFilePath(editorConfigFilePath));
    }
 
    [Fact]
    public async Task AddMultipleProjects1()
    {
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution;
 
        var projectId1 = ProjectId.CreateNewId();
        var projectId2 = ProjectId.CreateNewId();
 
        using var _ = ArrayBuilder<ProjectInfo>.GetInstance(out var projects);
        projects.Add(ProjectInfo.Create(projectId1, VersionStamp.Default, "Test1", "Test1", LanguageNames.CSharp));
        projects.Add(ProjectInfo.Create(projectId2, VersionStamp.Default, "Test2", "Test2", LanguageNames.CSharp));
 
        solution = solution.AddProjects(projects);
 
        Assert.Equal(2, solution.ProjectIds.Count);
        Assert.Equal(2, solution.SolutionState.ProjectCountByLanguage[LanguageNames.CSharp]);
 
        var compilation1 = await solution.GetProject(projectId1).GetCompilationAsync();
        var compilation2 = await solution.GetProject(projectId2).GetCompilationAsync();
    }
 
    [Fact]
    public async Task AddMultipleProjects2()
    {
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution;
 
        var projectId1 = ProjectId.CreateNewId();
        var projectId2 = ProjectId.CreateNewId();
 
        using var _ = ArrayBuilder<ProjectInfo>.GetInstance(out var projects);
 
        // Add a project that has a reference to the project that follows.
        projects.Add(ProjectInfo.Create(projectId1, VersionStamp.Default, "Test1", "Test1", LanguageNames.CSharp, projectReferences: [new ProjectReference(projectId2)]));
        projects.Add(ProjectInfo.Create(projectId2, VersionStamp.Default, "Test2", "Test2", LanguageNames.CSharp));
 
        solution = solution.AddProjects(projects);
 
        Assert.Equal(2, solution.ProjectIds.Count);
        Assert.Equal(2, solution.SolutionState.ProjectCountByLanguage[LanguageNames.CSharp]);
 
        Assert.True(solution.GetProject(projectId1).ProjectReferences.Contains(p => p.ProjectId == projectId2));
 
        var compilation1 = await solution.GetProject(projectId1).GetCompilationAsync();
        var compilation2 = await solution.GetProject(projectId2).GetCompilationAsync();
 
        Assert.True(compilation1.References.Any(r => r is CompilationReference compilationReference && compilationReference.Compilation == compilation2));
    }
 
    [Fact]
    public async Task AddMultipleProjects3()
    {
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution;
 
        var projectId1 = ProjectId.CreateNewId();
        var projectId2 = ProjectId.CreateNewId();
 
        using var _ = ArrayBuilder<ProjectInfo>.GetInstance(out var projects);
 
        // Add a project that has a reference to the project that precedes it.
        projects.Add(ProjectInfo.Create(projectId1, VersionStamp.Default, "Test1", "Test1", LanguageNames.CSharp));
        projects.Add(ProjectInfo.Create(projectId2, VersionStamp.Default, "Test2", "Test2", LanguageNames.CSharp, projectReferences: [new ProjectReference(projectId1)]));
 
        solution = solution.AddProjects(projects);
 
        Assert.Equal(2, solution.ProjectIds.Count);
        Assert.Equal(2, solution.SolutionState.ProjectCountByLanguage[LanguageNames.CSharp]);
 
        Assert.True(solution.GetProject(projectId2).ProjectReferences.Contains(p => p.ProjectId == projectId1));
 
        var compilation1 = await solution.GetProject(projectId1).GetCompilationAsync();
        var compilation2 = await solution.GetProject(projectId2).GetCompilationAsync();
 
        Assert.True(compilation2.References.Any(r => r is CompilationReference compilationReference && compilationReference.Compilation == compilation1));
    }
 
    [Fact]
    public async Task AddMultipleProjects4()
    {
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution;
 
        var projectId1 = ProjectId.CreateNewId();
        var projectId2 = ProjectId.CreateNewId();
        var projectId3 = ProjectId.CreateNewId();
 
        solution = solution.AddProject(ProjectInfo.Create(projectId1, VersionStamp.Default, "Test1", "Test1", LanguageNames.CSharp));
        var compilation1 = await solution.GetProject(projectId1).GetCompilationAsync();
 
        using var _ = ArrayBuilder<ProjectInfo>.GetInstance(out var projects);
 
        projects.Add(ProjectInfo.Create(projectId2, VersionStamp.Default, "Test2", "Test2", LanguageNames.CSharp));
        projects.Add(ProjectInfo.Create(projectId3, VersionStamp.Default, "Test3", "Test3", LanguageNames.CSharp));
 
        solution = solution.AddProjects(projects);
 
        Assert.Equal(3, solution.ProjectIds.Count);
        Assert.Equal(3, solution.SolutionState.ProjectCountByLanguage[LanguageNames.CSharp]);
 
        var compilation1New = await solution.GetProject(projectId1).GetCompilationAsync();
 
        // These compilations should be the same as adding the later projects should have no impact on the first project.
        Assert.Same(compilation1, compilation1New);
    }
 
    [Fact]
    public async Task AddMultipleProjects5()
    {
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution;
 
        var projectId1 = ProjectId.CreateNewId();
        var projectId2 = ProjectId.CreateNewId();
        var projectId3 = ProjectId.CreateNewId();
 
        // Reference a project that will be added later.
        solution = solution.AddProject(ProjectInfo.Create(projectId1, VersionStamp.Default, "Test1", "Test1", LanguageNames.CSharp, projectReferences: [new ProjectReference(projectId2)]));
        var compilation1 = await solution.GetProject(projectId1).GetCompilationAsync();
 
        using var _ = ArrayBuilder<ProjectInfo>.GetInstance(out var projects);
 
        // Add a project that has a reference to the project that precedes it.
        projects.Add(ProjectInfo.Create(projectId2, VersionStamp.Default, "Test2", "Test2", LanguageNames.CSharp));
        projects.Add(ProjectInfo.Create(projectId3, VersionStamp.Default, "Test3", "Test3", LanguageNames.CSharp));
 
        solution = solution.AddProjects(projects);
 
        Assert.Equal(3, solution.ProjectIds.Count);
        Assert.Equal(3, solution.SolutionState.ProjectCountByLanguage[LanguageNames.CSharp]);
 
        var compilation1New = await solution.GetProject(projectId1).GetCompilationAsync();
 
        // These compilations should not be the same as adding the later projects should fork the first project.
        Assert.NotSame(compilation1, compilation1New);
    }
 
    [Fact]
    public async Task AddMultipleProjects6()
    {
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution;
 
        var projectId1 = ProjectId.CreateNewId();
        var projectId2 = ProjectId.CreateNewId();
        var projectId3 = ProjectId.CreateNewId();
 
        // Reference both projects that will be added later.
        solution = solution.AddProject(ProjectInfo.Create(projectId1, VersionStamp.Default, "Test1", "Test1", LanguageNames.CSharp,
            projectReferences: [new ProjectReference(projectId2), new ProjectReference(projectId3)]));
        var compilation1 = await solution.GetProject(projectId1).GetCompilationAsync();
 
        using var _ = ArrayBuilder<ProjectInfo>.GetInstance(out var projects);
 
        // Add a project that has a reference to the project that precedes it.
        projects.Add(ProjectInfo.Create(projectId2, VersionStamp.Default, "Test2", "Test2", LanguageNames.CSharp));
        projects.Add(ProjectInfo.Create(projectId3, VersionStamp.Default, "Test3", "Test3", LanguageNames.CSharp));
 
        solution = solution.AddProjects(projects);
 
        Assert.Equal(3, solution.ProjectIds.Count);
        Assert.Equal(3, solution.SolutionState.ProjectCountByLanguage[LanguageNames.CSharp]);
 
        var compilation1New = await solution.GetProject(projectId1).GetCompilationAsync();
 
        // These compilations should not be the same as adding the later projects should fork the first project.
        Assert.NotSame(compilation1, compilation1New);
 
        var compilation2 = await solution.GetProject(projectId2).GetCompilationAsync();
        var compilation3 = await solution.GetProject(projectId2).GetCompilationAsync();
 
        Assert.True(compilation1New.References.Any(r => r is CompilationReference compilationReference && compilationReference.Compilation == compilation2));
        Assert.True(compilation1New.References.Any(r => r is CompilationReference compilationReference && compilationReference.Compilation == compilation3));
    }
 
    [Fact]
    public void AddMultipleProjects_ThrowOnDuplicateId()
    {
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution;
 
        var projectId1 = ProjectId.CreateNewId();
 
        using var _ = ArrayBuilder<ProjectInfo>.GetInstance(out var projects);
 
        // Add a project that has a reference to the project that precedes it.
        projects.Add(ProjectInfo.Create(projectId1, VersionStamp.Default, "Test2", "Test2", LanguageNames.CSharp));
        projects.Add(ProjectInfo.Create(projectId1, VersionStamp.Default, "Test3", "Test3", LanguageNames.CSharp));
 
        Assert.Throws<InvalidOperationException>(() => solution.AddProjects(projects));
    }
 
    [Fact]
    public async Task RemoveMultipleProjects1()
    {
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution;
 
        var projectId1 = ProjectId.CreateNewId();
        var projectId2 = ProjectId.CreateNewId();
        var projectId3 = ProjectId.CreateNewId();
 
        using var _ = ArrayBuilder<ProjectInfo>.GetInstance(out var projects);
 
        // Add a project that has a reference to the later projects
        projects.Add(ProjectInfo.Create(projectId1, VersionStamp.Default, "Test1", "Test1", LanguageNames.CSharp,
            projectReferences: [new ProjectReference(projectId2), new ProjectReference(projectId3)]));
        projects.Add(ProjectInfo.Create(projectId2, VersionStamp.Default, "Test2", "Test2", LanguageNames.CSharp));
        projects.Add(ProjectInfo.Create(projectId3, VersionStamp.Default, "Test3", "Test3", LanguageNames.CSharp));
 
        solution = solution.AddProjects(projects);
 
        Assert.Equal(3, solution.ProjectIds.Count);
        Assert.Equal(3, solution.SolutionState.ProjectCountByLanguage[LanguageNames.CSharp]);
 
        var compilation1 = await solution.GetProject(projectId1).GetCompilationAsync();
        var compilation2 = await solution.GetProject(projectId2).GetCompilationAsync();
        var compilation3 = await solution.GetProject(projectId2).GetCompilationAsync();
 
        Assert.True(compilation1.References.Any(r => r is CompilationReference compilationReference && compilationReference.Compilation == compilation2));
        Assert.True(compilation1.References.Any(r => r is CompilationReference compilationReference && compilationReference.Compilation == compilation3));
 
        using var _2 = ArrayBuilder<ProjectId>.GetInstance(out var projectsToRemove);
        projectsToRemove.Add(projectId2);
        projectsToRemove.Add(projectId3);
 
        solution = solution.RemoveProjects(projectsToRemove);
 
        Assert.Equal(1, solution.ProjectIds.Count);
        Assert.Equal(1, solution.SolutionState.ProjectCountByLanguage[LanguageNames.CSharp]);
 
        Assert.Equal(projectId1, solution.ProjectIds.Single());
        var compilation1New = await solution.Projects.Single().GetCompilationAsync();
 
        Assert.NotSame(compilation1, compilation1New);
        Assert.True(compilation1New.References.All(r => r is not CompilationReference));
    }
 
    [Fact]
    public async Task RemoveMultipleProjects2()
    {
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution;
 
        var projectId1 = ProjectId.CreateNewId();
        var projectId2 = ProjectId.CreateNewId();
        var projectId3 = ProjectId.CreateNewId();
 
        using var _ = ArrayBuilder<ProjectInfo>.GetInstance(out var projects);
 
        // All projects are independent
        projects.Add(ProjectInfo.Create(projectId1, VersionStamp.Default, "Test1", "Test1", LanguageNames.CSharp));
        projects.Add(ProjectInfo.Create(projectId2, VersionStamp.Default, "Test2", "Test2", LanguageNames.CSharp));
        projects.Add(ProjectInfo.Create(projectId3, VersionStamp.Default, "Test3", "Test3", LanguageNames.CSharp));
 
        solution = solution.AddProjects(projects);
 
        Assert.Equal(3, solution.ProjectIds.Count);
        Assert.Equal(3, solution.SolutionState.ProjectCountByLanguage[LanguageNames.CSharp]);
 
        var compilation1 = await solution.GetProject(projectId1).GetCompilationAsync();
        var compilation2 = await solution.GetProject(projectId2).GetCompilationAsync();
        var compilation3 = await solution.GetProject(projectId2).GetCompilationAsync();
 
        Assert.False(compilation1.References.Any(r => r is CompilationReference));
 
        using var _2 = ArrayBuilder<ProjectId>.GetInstance(out var projectsToRemove);
        projectsToRemove.Add(projectId2);
        projectsToRemove.Add(projectId3);
 
        solution = solution.RemoveProjects(projectsToRemove);
 
        Assert.Equal(1, solution.ProjectIds.Count);
        Assert.Equal(1, solution.SolutionState.ProjectCountByLanguage[LanguageNames.CSharp]);
 
        Assert.Equal(projectId1, solution.ProjectIds.Single());
        var compilation1New = await solution.Projects.Single().GetCompilationAsync();
 
        // Removing project2 and project3 should not change project1 here.
        Assert.Same(compilation1, compilation1New);
    }
 
    [Fact]
    public void RemoveMultipleProjects_ThrowOnDuplicateId()
    {
        using var workspace = CreateWorkspace();
        var solution = workspace.CurrentSolution;
 
        var projectId1 = ProjectId.CreateNewId();
        var projectId2 = ProjectId.CreateNewId();
 
        using var _ = ArrayBuilder<ProjectInfo>.GetInstance(out var projects);
 
        // Add a project that has a reference to the project that precedes it.
        projects.Add(ProjectInfo.Create(projectId1, VersionStamp.Default, "Test2", "Test2", LanguageNames.CSharp));
        projects.Add(ProjectInfo.Create(projectId2, VersionStamp.Default, "Test3", "Test3", LanguageNames.CSharp));
 
        solution = solution.AddProjects(projects);
 
        using var _2 = ArrayBuilder<ProjectId>.GetInstance(out var projectsToRemove);
        projectsToRemove.Add(projectId1);
        projectsToRemove.Add(projectId1);
 
        Assert.Throws<InvalidOperationException>(() => solution.RemoveProjects(projectsToRemove));
    }
 
    [Fact]
    public void AddAndRemoveProjectsUpdatesLanguageCount()
    {
        using var workspace = CreateWorkspace(additionalParts: [typeof(NoCompilationLanguageService)]);
        var solution = workspace.CurrentSolution;
 
        var csProjectId = ProjectId.CreateNewId();
        var vbProjectId = ProjectId.CreateNewId();
 
        solution = solution.AddProjects(
        [
            ProjectInfo.Create(csProjectId, VersionStamp.Default, "CS1", "CS1", LanguageNames.CSharp),
            ProjectInfo.Create(vbProjectId, VersionStamp.Default, "VB1", "VB1", LanguageNames.VisualBasic)
        ]);
 
        solution = solution
            .AddProject("CS2", "CS2", LanguageNames.CSharp).Solution
            .AddProject("NC1", "NC1", NoCompilationConstants.LanguageName).Solution;
 
        AssertEx.SetEqual(
        [
            (LanguageNames.CSharp, 2),
            (LanguageNames.VisualBasic, 1),
            (NoCompilationConstants.LanguageName, 1)
        ], solution.SolutionState.ProjectCountByLanguage.Select(e => (e.Key, e.Value)));
 
        solution = solution.RemoveProject(csProjectId);
        AssertEx.SetEqual(
        [
            (LanguageNames.CSharp, 1),
            (LanguageNames.VisualBasic, 1),
            (NoCompilationConstants.LanguageName, 1)
        ], solution.SolutionState.ProjectCountByLanguage.Select(e => (e.Key, e.Value)));
 
        solution = solution.RemoveProject(vbProjectId);
        AssertEx.SetEqual(
        [
            (LanguageNames.CSharp, 1),
            (NoCompilationConstants.LanguageName, 1)
        ], solution.SolutionState.ProjectCountByLanguage.Select(e => (e.Key, e.Value)));
 
        solution = solution.RemoveProject(solution.Projects.Single(p => p.Name == "CS2").Id);
        AssertEx.SetEqual(
        [
            (NoCompilationConstants.LanguageName, 1)
        ], solution.SolutionState.ProjectCountByLanguage.Select(e => (e.Key, e.Value)));
 
        solution = solution.RemoveProject(solution.Projects.Single(p => p.Name == "NC1").Id);
        Assert.Empty(solution.SolutionState.ProjectCountByLanguage);
    }
 
    private static void GetMultipleProjects(
        out Project csBrokenProject,
        out Project vbNormalProject,
        out Project dependsOnBrokenProject,
        out Project dependsOnVbNormalProject,
        out Project transitivelyDependsOnBrokenProjects,
        out Project transitivelyDependsOnNormalProjects)
    {
        var workspace = new AdhocWorkspace();
 
        csBrokenProject = workspace.AddProject(
            ProjectInfo.Create(
                ProjectId.CreateNewId(),
                VersionStamp.Create(),
                "CSharpProject",
                "CSharpProject",
                LanguageNames.CSharp,
                metadataReferences: [MscorlibRef],
                compilationOptions: workspace.Services
                    .GetLanguageService<ICompilationFactoryService>(LanguageNames.CSharp)
                    .GetDefaultCompilationOptions()
                    .WithOutputKind(OutputKind.DynamicallyLinkedLibrary))
            .WithHasAllInformation(hasAllInformation: false));
 
        vbNormalProject = workspace.AddProject(
            ProjectInfo.Create(
                ProjectId.CreateNewId(),
                VersionStamp.Create(),
                "VisualBasicProject",
                "VisualBasicProject",
                LanguageNames.VisualBasic,
                metadataReferences: [MscorlibRef],
                compilationOptions: workspace.Services
                    .GetLanguageService<ICompilationFactoryService>(LanguageNames.VisualBasic)
                    .GetDefaultCompilationOptions()
                    .WithOutputKind(OutputKind.DynamicallyLinkedLibrary)));
 
        dependsOnBrokenProject = workspace.AddProject(
            ProjectInfo.Create(
                ProjectId.CreateNewId(),
                VersionStamp.Create(),
                "VisualBasicProject",
                "VisualBasicProject",
                LanguageNames.VisualBasic,
                metadataReferences: [MscorlibRef],
                projectReferences: [new ProjectReference(csBrokenProject.Id), new ProjectReference(vbNormalProject.Id)],
                compilationOptions: workspace.Services
                    .GetLanguageService<ICompilationFactoryService>(LanguageNames.VisualBasic)
                    .GetDefaultCompilationOptions()
                    .WithOutputKind(OutputKind.DynamicallyLinkedLibrary)));
 
        dependsOnVbNormalProject = workspace.AddProject(
            ProjectInfo.Create(
                ProjectId.CreateNewId(),
                VersionStamp.Create(),
                "CSharpProject",
                "CSharpProject",
                LanguageNames.CSharp,
                metadataReferences: [MscorlibRef],
                projectReferences: [new ProjectReference(vbNormalProject.Id)],
                compilationOptions: workspace.Services
                    .GetLanguageService<ICompilationFactoryService>(LanguageNames.CSharp)
                    .GetDefaultCompilationOptions()
                    .WithOutputKind(OutputKind.DynamicallyLinkedLibrary)));
 
        transitivelyDependsOnBrokenProjects = workspace.AddProject(
            ProjectInfo.Create(
                ProjectId.CreateNewId(),
                VersionStamp.Create(),
                "CSharpProject",
                "CSharpProject",
                LanguageNames.CSharp,
                metadataReferences: [MscorlibRef],
                projectReferences: [new ProjectReference(dependsOnBrokenProject.Id)]));
 
        transitivelyDependsOnNormalProjects = workspace.AddProject(
            ProjectInfo.Create(
                ProjectId.CreateNewId(),
                VersionStamp.Create(),
                "VisualBasicProject",
                "VisualBasicProject",
                LanguageNames.VisualBasic,
                metadataReferences: [MscorlibRef],
                projectReferences: [new ProjectReference(dependsOnVbNormalProject.Id)]));
    }
 
    [Fact]
    public void TestOptionChangesForLanguagesNotInSolution()
    {
        // Create an empty solution with no projects.
        using var workspace = CreateWorkspace();
        var s0 = workspace.CurrentSolution;
        var optionService = workspace.Services.GetRequiredService<ILegacyWorkspaceOptionService>().LegacyGlobalOptions;
 
        // Apply an option change to a C# option.
        var option = FormattingOptions.UseTabs;
        var defaultValue = option.DefaultValue;
        var changedValue = !defaultValue;
        var options = s0.Options.WithChangedOption(option, LanguageNames.CSharp, changedValue);
 
        // Verify option change is preserved even if the solution has no project with that language.
        var s1 = s0.WithOptions(options);
        VerifyOptionSet(s1.Options);
 
        // Verify option value is preserved on adding a project for a different language.
        var s2 = s1.AddProject("P1", "A1", LanguageNames.VisualBasic).Solution;
        VerifyOptionSet(s2.Options);
 
        // Verify option value is preserved on removing a project.
        var s4 = s2.RemoveProject(s2.Projects.Single(p => p.Name == "P1").Id);
        VerifyOptionSet(s4.Options);
 
        return;
 
        void VerifyOptionSet(OptionSet optionSet)
        {
            Assert.Equal(changedValue, optionSet.GetOption(option, LanguageNames.CSharp));
            Assert.Equal(defaultValue, optionSet.GetOption(option, LanguageNames.VisualBasic));
        }
    }
 
    [Fact]
    public async Task TestUpdatedDocumentTextIsObservablyConstantAsync()
    {
        using var workspace = CreateWorkspace();
        var pid = ProjectId.CreateNewId();
        var text = SourceText.From("public class C { }");
        var version = VersionStamp.Create();
        var docInfo = DocumentInfo.Create(DocumentId.CreateNewId(pid), "c.cs", loader: TextLoader.From(TextAndVersion.Create(text, version)));
        var projInfo = ProjectInfo.Create(
            pid,
            version: VersionStamp.Default,
            name: "TestProject",
            assemblyName: "TestProject.dll",
            language: LanguageNames.CSharp,
            documents: [docInfo]);
 
        var solution = workspace.CurrentSolution.AddProject(projInfo);
        var doc = solution.GetDocument(docInfo.Id);
 
        // change document
        var root = await doc.GetSyntaxRootAsync();
        var newRoot = root.WithAdditionalAnnotations(new SyntaxAnnotation());
        Assert.NotSame(root, newRoot);
        var newDoc = doc.Project.Solution.WithDocumentSyntaxRoot(doc.Id, newRoot).GetDocument(doc.Id);
        Assert.NotSame(doc, newDoc);
 
        var newDocText = await newDoc.GetTextAsync();
        var sameText = await newDoc.GetTextAsync();
        Assert.Same(newDocText, sameText);
 
        var newDocTree = await newDoc.GetSyntaxTreeAsync();
        var treeText = newDocTree.GetText();
        Assert.Same(newDocText, treeText);
    }
 
    [Fact]
    public async Task ReplacingTextMultipleTimesDoesNotRootIntermediateCopiesIfCompilationNotAskedFor()
    {
        // This test replicates the pattern of some operation changing a bunch of files, but the files aren't kept open.
        // In Visual Studio we do large refactorings by opening files with an invisible editor, making changes, and closing
        // again. This process means we'll queue up intermediate changes to those files, but we don't want to hold onto
        // the intermediate edits when we don't really need to since the final version will be all that matters.
 
        using var workspace = CreateWorkspaceWithProjectAndDocuments();
 
        var solution = workspace.CurrentSolution;
        var documentId = solution.Projects.Single().DocumentIds.Single();
 
        // Fetch the compilation, so further edits are going to be incremental updates of this one
        var originalCompilation = await solution.Projects.Single().GetCompilationAsync();
 
        // Create a source text we'll release and ensure it disappears. We'll also make sure we don't accidentally root
        // that solution in the middle.
        var sourceTextToRelease = ObjectReference.CreateFromFactory(static () => SourceText.From(Guid.NewGuid().ToString()));
        var solutionWithSourceTextToRelease = sourceTextToRelease.GetObjectReference(
            static (sourceText, document) => document.Project.Solution.WithDocumentText(document.Id, sourceText, PreservationMode.PreserveIdentity),
            solution.GetDocument(documentId));
 
        // Change it again, this time by editing the text loader; this replicates us closing a file, and we don't want to pin the changes from the
        // prior change.
        var finalSolution = solutionWithSourceTextToRelease.GetObjectReference(
            static (s, documentId) => s.WithDocumentTextLoader(documentId, new TestTextLoader(Guid.NewGuid().ToString()), PreservationMode.PreserveValue), documentId).GetReference();
 
        // The text in the middle shouldn't be held at all, since we replaced it.
        solutionWithSourceTextToRelease.ReleaseStrongReference();
        sourceTextToRelease.AssertReleased();
 
        GC.KeepAlive(finalSolution);
    }
 
    [Theory]
    [InlineData("a/proj.csproj", "a/.editorconfig", "a/b/test.cs", "*.cs", true, true)]
    [InlineData("a/proj.csproj", "a/b/.editorconfig", "a/b/test.cs", "*.cs", false, true)]
    [InlineData("a/proj.csproj", "a/.editorconfig", null, "*.cs", true, true)]
    [InlineData("a/proj.csproj", "a/b/.editorconfig", null, "*.cs", false, false)]
    [InlineData("a/proj.csproj", "a/.editorconfig", "", "*.cs", true, true)]
    [InlineData("a/proj.csproj", "a/b/.editorconfig", "", "*.cs", false, false)]
    [InlineData(null, "a/.editorconfig", "a/b/test.cs", "*.cs", false, true)]
    [InlineData(null, "a/.editorconfig", null, "*.cs", false, false)]
    [InlineData("a/proj.csproj", "a/.editorconfig", null, "*test.cs", false, true)]
    public async Task EditorConfigOptions(string projectPath, string configPath, string sourcePath, string pattern, bool appliedToEntireProject, bool appliedToDocument)
    {
        projectPath = string.IsNullOrEmpty(projectPath) ? projectPath : Path.Combine(TempRoot.Root, projectPath);
        configPath = Path.Combine(TempRoot.Root, configPath);
        sourcePath = string.IsNullOrEmpty(sourcePath) ? sourcePath : Path.Combine(TempRoot.Root, sourcePath);
 
        using var workspace = CreateWorkspace();
        var projectId = ProjectId.CreateNewId();
 
        var projectInfo = ProjectInfo.Create(
            projectId,
            VersionStamp.Default,
            name: "proj1",
            assemblyName: "proj1.dll",
            language: LanguageNames.CSharp,
            filePath: projectPath);
 
        var documentId = DocumentId.CreateNewId(projectId);
 
        var solution = workspace.CurrentSolution
            .AddProject(projectInfo)
            .AddDocument(documentId, "test.cs", SourceText.From("public class C { }"), filePath: sourcePath)
            .AddAnalyzerConfigDocument(DocumentId.CreateNewId(projectId), ".editorconfig", SourceText.From($"[{pattern}]\nindent_style = tab"), filePath: configPath);
 
        var document = solution.GetRequiredDocument(documentId);
 
#pragma warning disable RS0030 // Do not used banned APIs
        var documentOptions = await document.GetOptionsAsync(CancellationToken.None);
        Assert.Equal(appliedToDocument, documentOptions.GetOption(FormattingOptions.UseTabs));
#pragma warning restore
 
        var syntaxTree = await document.GetSyntaxTreeAsync();
        var documentOptionsViaSyntaxTree = document.Project.State.ProjectAnalyzerOptions.AnalyzerConfigOptionsProvider.GetOptions(syntaxTree);
        Assert.Equal(appliedToDocument, documentOptionsViaSyntaxTree.TryGetValue("indent_style", out var value) == true && value == "tab");
 
        var projectOptions = document.Project.GetAnalyzerConfigOptions();
        Assert.Equal(appliedToEntireProject, projectOptions?.ConfigOptionsWithoutFallback.TryGetValue("indent_style", out value) == true && value == "tab");
        Assert.Equal(appliedToEntireProject, projectOptions?.ConfigOptionsWithFallback.TryGetValue("indent_style", out value) == true && value == "tab");
    }
 
    [Fact]
    public void GetRelatedDocumentsDoesNotReturnOtherTypesOfDocuments()
    {
        using var workspace = CreateWorkspace();
 
        const string FilePath = "File.cs";
 
        var solution = workspace.CurrentSolution
            .AddProject("TestProject", "TestProject", LanguageNames.CSharp)
            .AddDocument("File.cs", "", filePath: FilePath).Project
            .AddAdditionalDocument("File.cs", text: "", filePath: FilePath).Project.Solution;
 
        // GetDocumentIdsWithFilePath should return two, since it'll count all types of documents
        Assert.Equal(2, solution.GetDocumentIdsWithFilePath(FilePath).Length);
 
        var regularDocumentId = solution.Projects.Single().DocumentIds.Single();
 
        Assert.Single(solution.GetRelatedDocumentIds(regularDocumentId));
    }
 
    [Theory, CombinatorialData]
    public void GetRelatedDocumentsCaseInsensitive(
        [CombinatorialValues("file", "File", "FILE", "FiLe")] string prefix,
        [CombinatorialValues("cs", "Cs", "cS", "CS")] string extension)
    {
        using var workspace = CreateWorkspace();
 
        var solution = workspace.CurrentSolution
            .AddProject("TestProject1", "TestProject1", LanguageNames.CSharp)
            .AddDocument("File.cs", "", filePath: "File.cs").Project.Solution
            .AddProject("TestProject2", "TestProject2", LanguageNames.CSharp)
            .AddDocument("file.cs", "", filePath: "file.cs").Project.Solution;
 
        // GetDocumentIdsWithFilePath should return two, since it'll count all types of documents
        Assert.Equal(2, solution.GetDocumentIdsWithFilePath($"{prefix}.{extension}").Length);
    }
 
    [Fact]
    public async Task TestFrozenPartialSolution1()
    {
        using var workspace = WorkspaceTestUtilities.CreateWorkspaceWithPartialSemantics();
        var project = workspace.CurrentSolution.AddProject("CSharpProject", "CSharpProject", LanguageNames.CSharp);
        project = project.AddDocument("Extra.cs", SourceText.From("class Extra { }")).Project;
 
        // Because we froze before ever even looking at anything semantics related, we should have no documents in
        // this project.
        var frozenSolution = project.Solution.WithFrozenPartialCompilations(CancellationToken.None);
        var frozenProject = frozenSolution.Projects.Single();
        Assert.Empty(frozenProject.Documents);
 
        var frozenCompilation = await frozenProject.GetCompilationAsync();
        Assert.Empty(frozenCompilation.SyntaxTrees);
    }
 
    [Fact]
    public async Task TestFrozenPartialSolution2()
    {
        using var workspace = WorkspaceTestUtilities.CreateWorkspaceWithPartialSemantics();
        var project = workspace.CurrentSolution.AddProject("CSharpProject", "CSharpProject", LanguageNames.CSharp);
        project = project.AddDocument("Extra.cs", SourceText.From("class Extra { }")).Project;
 
        await project.GetCompilationAsync();
 
        // Because we froze after looking at anything semantics related, we should have the documents in this
        // project.
        var frozenSolution = project.Solution.WithFrozenPartialCompilations(CancellationToken.None);
        var frozenProject = frozenSolution.Projects.Single();
        Assert.Single(frozenProject.Documents);
 
        var frozenCompilation = await frozenProject.GetCompilationAsync();
        Assert.Single(frozenCompilation.SyntaxTrees);
        Assert.True(frozenCompilation.ContainsSyntaxTree(await frozenProject.Documents.Single().GetSyntaxTreeAsync()));
    }
 
    [Fact]
    public async Task TestFrozenPartialSolution3()
    {
        using var workspace = WorkspaceTestUtilities.CreateWorkspaceWithPartialSemantics();
        var project1 = workspace.CurrentSolution.AddProject("CSharpProject1", "CSharpProject1", LanguageNames.CSharp);
        var project2 = project1.Solution.AddProject("CSharpProject2", "CSharpProject2", LanguageNames.CSharp);
        project1 = project2.Solution.GetProject(project1.Id).AddDocument("Doc1", SourceText.From("class Doc1 { }")).Project;
        project2 = project1.Solution.GetProject(project2.Id).AddDocument("Doc2", SourceText.From("class Doc2 { }")).Project;
        project1 = project2.Solution.GetProject(project1.Id);
 
        // Getting compilation from one should not affect frozen-ness of other project.
        await project1.GetCompilationAsync();
 
        var frozenSolution = project1.Solution.WithFrozenPartialCompilations(CancellationToken.None);
        var frozenProject1 = frozenSolution.GetProject(project1.Id);
        Assert.Single(frozenProject1.Documents);
 
        var frozenProject2 = frozenSolution.GetProject(project2.Id);
        Assert.Empty(frozenProject2.Documents);
 
        var frozenCompilation1 = await frozenProject1.GetCompilationAsync();
        Assert.Single(frozenCompilation1.SyntaxTrees);
        Assert.True(frozenCompilation1.ContainsSyntaxTree(await frozenProject1.Documents.Single().GetSyntaxTreeAsync()));
 
        var frozenCompilation2 = await frozenProject2.GetCompilationAsync();
        Assert.Empty(frozenCompilation2.SyntaxTrees);
    }
 
    [Fact]
    public async Task TestFrozenPartialSolution4()
    {
        using var workspace = WorkspaceTestUtilities.CreateWorkspaceWithPartialSemantics();
        var project1 = workspace.CurrentSolution.AddProject("CSharpProject1", "CSharpProject1", LanguageNames.CSharp);
        var project2 = project1.Solution.AddProject("CSharpProject2", "CSharpProject2", LanguageNames.CSharp);
        project1 = project2.Solution.GetProject(project1.Id).AddDocument("Doc1", SourceText.From("class Doc1 { }")).Project;
        project2 = project1.Solution.GetProject(project2.Id).AddDocument("Doc2", SourceText.From("class Doc2 { }")).Project;
 
        project2 = project2.AddProjectReference(new(project1.Id));
        project1 = project2.Solution.GetProject(project1.Id);
 
        // Getting compilation from project1 should not affect project 2.
        await project1.GetCompilationAsync();
 
        var frozenSolution = project1.Solution.WithFrozenPartialCompilations(CancellationToken.None);
        var frozenProject1 = frozenSolution.GetProject(project1.Id);
        Assert.Single(frozenProject1.Documents);
 
        var frozenCompilation1 = await frozenProject1.GetCompilationAsync();
        Assert.Single(frozenCompilation1.SyntaxTrees);
        Assert.True(frozenCompilation1.ContainsSyntaxTree(await frozenProject1.Documents.Single().GetSyntaxTreeAsync()));
 
        var frozenProject2 = frozenSolution.GetProject(project2.Id);
        Assert.Empty(frozenProject2.Documents);
 
        var frozenCompilation2 = await frozenProject2.GetCompilationAsync();
        Assert.Empty(frozenCompilation2.SyntaxTrees);
    }
 
    [Fact]
    public async Task TestFrozenPartialSolution5()
    {
        using var workspace = WorkspaceTestUtilities.CreateWorkspaceWithPartialSemantics();
        var project1 = workspace.CurrentSolution.AddProject("CSharpProject1", "CSharpProject1", LanguageNames.CSharp);
        var project2 = project1.Solution.AddProject("CSharpProject2", "CSharpProject2", LanguageNames.CSharp);
        project1 = project2.Solution.GetProject(project1.Id).AddDocument("Doc1", SourceText.From("class Doc1 { }")).Project;
        project2 = project1.Solution.GetProject(project2.Id).AddDocument("Doc2", SourceText.From("class Doc2 { }")).Project;
 
        project2 = project2.AddProjectReference(new(project1.Id));
        project1 = project2.Solution.GetProject(project1.Id);
 
        // Getting compilation from project2 should affect project 1 as there's a ptp relationship with it.
        await project2.GetCompilationAsync();
 
        var frozenSolution = project1.Solution.WithFrozenPartialCompilations(CancellationToken.None);
        var frozenProject1 = frozenSolution.GetProject(project1.Id);
        Assert.Single(frozenProject1.Documents);
 
        var frozenCompilation1 = await frozenProject1.GetCompilationAsync();
        Assert.Single(frozenCompilation1.SyntaxTrees);
        Assert.True(frozenCompilation1.ContainsSyntaxTree(await frozenProject1.Documents.Single().GetSyntaxTreeAsync()));
 
        var frozenProject2 = frozenSolution.GetProject(project2.Id);
        Assert.Single(frozenProject2.Documents);
 
        var frozenCompilation2 = await frozenProject2.GetCompilationAsync();
        Assert.Single(frozenCompilation2.SyntaxTrees);
        Assert.True(frozenCompilation2.ContainsSyntaxTree(await frozenProject2.Documents.Single().GetSyntaxTreeAsync()));
 
        Assert.Single(frozenCompilation2.References, r => r is CompilationReference c && c.Compilation == frozenCompilation1);
    }
 
    [Fact]
    public async Task TestFrozenPartialSolution6()
    {
        using var workspace = WorkspaceTestUtilities.CreateWorkspaceWithPartialSemantics();
        var project1 = workspace.CurrentSolution.AddProject("CSharpProject1", "CSharpProject1", LanguageNames.CSharp);
        project1 = project1.AddDocument("Doc1", SourceText.From("class Doc1 { }")).Project;
 
        // If we freeze after getting the compilation, we should see those documents then show up in frozen
        // compilation as well.
        var compilation1 = await project1.GetCompilationAsync();
        var syntaxTree1 = await project1.Documents.Single().GetSyntaxTreeAsync();
 
        var frozenSolution = project1.Solution.WithFrozenPartialCompilations(CancellationToken.None);
 
        var forkedProject1 = frozenSolution.WithDocumentText(project1.Documents.Single().Id, SourceText.From("class Doc2 { }")).GetProject(project1.Id);
        var forkedDocument1 = forkedProject1.Documents.Single();
        var forkedSyntaxTree1 = await forkedDocument1.GetSyntaxTreeAsync();
 
        Assert.NotEqual(syntaxTree1, forkedSyntaxTree1);
 
        var forkedCompilation1 = await forkedProject1.GetCompilationAsync();
        Assert.NotEqual(compilation1, forkedCompilation1);
        Assert.True(forkedCompilation1.ContainsSyntaxTree(forkedSyntaxTree1));
    }
 
    [Theory, CombinatorialData]
    public async Task TestFrozenPartialSolution7(bool freeze)
    {
        using var workspace = WorkspaceTestUtilities.CreateWorkspaceWithPartialSemantics();
        var project1 = workspace.CurrentSolution
            .AddProject("CSharpProject1", "CSharpProject1", LanguageNames.CSharp)
            .WithCompilationOutputInfo(new CompilationOutputInfo(assemblyPath: Path.Combine(TempRoot.Root, "assembly.dll"), generatedFilesOutputDirectory: null));
 
        project1 = project1.AddDocument("Doc1", SourceText.From("class Doc1 { }")).Project;
 
        var invokeIndex = 1;
        project1 = project1.AddAnalyzerReference(new TestGeneratorReference(new CallbackGenerator(() =>
        {
            var index = invokeIndex++;
            return ("hintname" + index, "// source" + index);
        })));
 
        var compilation1 = await project1.GetCompilationAsync();
        var syntaxTree1 = await project1.Documents.Single().GetSyntaxTreeAsync();
        var generatedDocuments = await project1.GetSourceGeneratedDocumentsAsync();
 
        Assert.Single(generatedDocuments);
        Assert.Equal("// source1", generatedDocuments.Single().GetTextSynchronously(CancellationToken.None).ToString());
 
        var frozenSolution = freeze
            ? project1.Solution.WithFrozenPartialCompilations(CancellationToken.None)
            : project1.Solution;
 
        var forkedProject1 = frozenSolution.WithDocumentText(project1.Documents.Single().Id, SourceText.From("class Doc2 { }")).GetProject(project1.Id);
        var forkedDocument1 = forkedProject1.Documents.Single();
        var forkedSyntaxTree1 = await forkedDocument1.GetSyntaxTreeAsync();
        var forkedGeneratedDocuments = await forkedProject1.GetSourceGeneratedDocumentsAsync();
 
        Assert.Single(forkedGeneratedDocuments);
        Assert.NotEqual(syntaxTree1, forkedSyntaxTree1);
 
        var forkedCompilation1 = await forkedProject1.GetCompilationAsync();
 
        Assert.NotEqual(compilation1, forkedCompilation1);
        Assert.True(forkedCompilation1.ContainsSyntaxTree(forkedSyntaxTree1));
 
        Assert.NotSame(generatedDocuments.Single(), forkedGeneratedDocuments.Single());
        if (freeze)
        {
            Assert.Equal("// source1", forkedGeneratedDocuments.Single().GetTextSynchronously(CancellationToken.None).ToString());
        }
        else
        {
            Assert.Equal("// source2", forkedGeneratedDocuments.Single().GetTextSynchronously(CancellationToken.None).ToString());
        }
    }
 
    [Fact]
    public async Task TestFrozenPartialSolutionOtherLanguage()
    {
        using var workspace = WorkspaceTestUtilities.CreateWorkspaceWithPartialSemantics();
        var project = workspace.CurrentSolution.AddProject("TypeScript", "TypeScript", "TypeScript");
        project = project.AddDocument("Extra.ts", SourceText.From("class Extra { }")).Project;
 
        // Freeze should have no impact on non-c#/vb projects.
        var frozenSolution = project.Solution.WithFrozenPartialCompilations(CancellationToken.None);
        var frozenProject = frozenSolution.Projects.Single();
        Assert.Single(frozenProject.Documents);
 
        var frozenCompilation = await frozenProject.GetCompilationAsync();
        Assert.Null(frozenCompilation);
    }
 
    [Theory]
    [InlineData(1000)]
    [InlineData(2000)]
    [InlineData(4000)]
    [InlineData(8000)]
    public async Task TestLargeLinkedFileChain(int intermediatePullCount)
    {
        using var workspace = CreateWorkspace();
 
        var project1 = workspace.CurrentSolution
            .AddProject($"Project1", $"Project1", LanguageNames.CSharp)
            .WithParseOptions(CSharpParseOptions.Default.WithPreprocessorSymbols("DEBUG"))
            .AddDocument($"Document", SourceText.From("class C { }"), filePath: @"c:\test\Document.cs").Project;
        var documentId1 = project1.DocumentIds.Single();
 
        // make another project, give a separate set of pp directives, so that we do *not* try to use the sibling
        // root (from project1), but instead incrementally parse using the *contents* of the file in project1 again
        // our actual tree.  This used to stack overflow since we'd create a long chain of incremental parsing steps
        // for each edit made to the sibling file.
        var project2 = project1.Solution
            .AddProject($"Project2", $"Project2", LanguageNames.CSharp)
            .WithParseOptions(CSharpParseOptions.Default.WithPreprocessorSymbols("RELEASE"))
            .AddDocument($"Document", SourceText.From("class C { }"), filePath: @"c:\test\Document.cs").Project;
        var documentId2 = project2.DocumentIds.Single();
 
        workspace.SetCurrentSolution(
            _ => project2.Solution,
            (_, _) => (WorkspaceChangeKind.SolutionAdded, null, null));
 
        for (var i = 1; i <= 8000; i++)
        {
            var lastContents = $"#if true //{new string('.', i)}//";
            workspace.SetCurrentSolution(
                old => old.WithDocumentText(documentId1, SourceText.From(lastContents)),
                (_, _) => (WorkspaceChangeKind.DocumentChanged, documentId1.ProjectId, documentId1));
 
            // Ensure that the first document is fine, and we're not stack overflowing on simply getting the tree
            // from it. Do this on a disparate cadence from our pulls of the second document to ensure we are
            // testing the case where we haven't necessarily immediately pulled on hte first doc before pulling on
            // the second.
            if (i % 33 == 0)
            {
                var document1 = workspace.CurrentSolution.GetRequiredDocument(documentId1);
                await document1.GetSyntaxRootAsync();
            }
 
            if (i % intermediatePullCount == 0)
            {
                var document2 = workspace.CurrentSolution.GetRequiredDocument(documentId2);
 
                // Getting the second document should both be fine, and have contents equivalent to what is in the first document.
                var root = await document2.GetSyntaxRootAsync();
                Assert.Equal(lastContents, root.ToFullString());
            }
        }
    }
}