File: VS\IFSharpWorkspaceProjectContextFactory.cs
Web Access
Project: src\src\VisualStudio\ExternalAccess\FSharp\Microsoft.CodeAnalysis.ExternalAccess.FSharp.csproj (Microsoft.CodeAnalysis.ExternalAccess.FSharp)
// 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.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.VisualStudio.LanguageServices.ProjectSystem;
using Microsoft.VisualStudio.Shell.Interop;
 
namespace Microsoft.CodeAnalysis.ExternalAccess.FSharp
{
    internal interface IFSharpWorkspaceProjectContextFactory
    {
        IFSharpWorkspaceProjectContext CreateProjectContext(string filePath, string uniqueName);
    }
 
    internal interface IFSharpWorkspaceProjectContext : IDisposable
    {
        string DisplayName { get; set; }
        ProjectId Id { get; }
        string FilePath { get; }
        int ProjectReferenceCount { get; }
        bool HasProjectReference(string filePath);
        int MetadataReferenceCount { get; }
        bool HasMetadataReference(string referencePath);
        void SetProjectReferences(IEnumerable<IFSharpWorkspaceProjectContext> projRefs);
        void SetMetadataReferences(IEnumerable<string> referencePaths);
        void AddMetadataReference(string referencePath);
        void AddSourceFile(string path, SourceCodeKind kind);
    }
 
    [Shared]
    [Export(typeof(FSharpWorkspaceProjectContextFactory))]
    [Export(typeof(IFSharpWorkspaceProjectContextFactory))]
    internal sealed class FSharpWorkspaceProjectContextFactory : IFSharpWorkspaceProjectContextFactory
    {
        private readonly IWorkspaceProjectContextFactory _factory;
        private readonly IThreadingContext _threadingContext;
 
        [ImportingConstructor]
        [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
        public FSharpWorkspaceProjectContextFactory(IWorkspaceProjectContextFactory factory, IThreadingContext threadingContext)
        {
            _factory = factory;
            _threadingContext = threadingContext;
        }
 
        IFSharpWorkspaceProjectContext IFSharpWorkspaceProjectContextFactory.CreateProjectContext(string filePath, string uniqueName)
            => CreateProjectContext(filePath, uniqueName);
 
        public FSharpWorkspaceProjectContext CreateProjectContext(string filePath, string uniqueName)
            => CreateProjectContext(
                projectUniqueName: uniqueName,
                projectFilePath: filePath,
                projectGuid: Guid.NewGuid(),
                hierarchy: null,
                binOutputPath: null);
 
        public FSharpWorkspaceProjectContext CreateProjectContext(string projectUniqueName, string projectFilePath, Guid projectGuid, object? hierarchy, string? binOutputPath)
            => new(_threadingContext.JoinableTaskFactory.Run(() => _factory.CreateProjectContextAsync(
                id: projectGuid,
                uniqueName: projectUniqueName,
                languageName: LanguageNames.FSharp,
                data: new FSharpEvaluationData(projectFilePath, binOutputPath),
                hostObject: hierarchy,
                CancellationToken.None)));
 
        private sealed class FSharpEvaluationData : EvaluationData
        {
            private readonly string _projectFilePath;
            private readonly string? _binOutputPath;
 
            public FSharpEvaluationData(string projectFilePath, string? binOutputPath)
            {
                _projectFilePath = projectFilePath;
                _binOutputPath = binOutputPath;
            }
 
            public override string GetPropertyValue(string name)
                => name switch
                {
                    BuildPropertyNames.MSBuildProjectFullPath => _projectFilePath,
                    BuildPropertyNames.TargetPath => _binOutputPath ?? "",
                    _ => "",
                };
 
            public override ImmutableArray<string> GetItemValues(string name)
                => ImmutableArray<string>.Empty;
        }
    }
 
    internal sealed class FSharpWorkspaceProjectContext : IFSharpWorkspaceProjectContext
    {
        private readonly IWorkspaceProjectContext _vsProjectContext;
 
        private ImmutableDictionary<string, IFSharpWorkspaceProjectContext> _projectReferences;
        private ImmutableHashSet<string> _metadataReferences;
 
        public FSharpWorkspaceProjectContext(IWorkspaceProjectContext vsProjectContext)
        {
            _vsProjectContext = vsProjectContext;
            _projectReferences = ImmutableDictionary.Create<string, IFSharpWorkspaceProjectContext>(StringComparer.OrdinalIgnoreCase);
            _metadataReferences = ImmutableHashSet.Create<string>(StringComparer.OrdinalIgnoreCase);
        }
 
        public void Dispose()
            => _vsProjectContext.Dispose();
 
        public IVsLanguageServiceBuildErrorReporter2? BuildErrorReporter
            => _vsProjectContext as IVsLanguageServiceBuildErrorReporter2;
 
        public string DisplayName
        {
            get => _vsProjectContext.DisplayName;
            set => _vsProjectContext.DisplayName = value;
        }
 
        public string BinOutputPath
        {
            get => _vsProjectContext.BinOutputPath!;
            set => _vsProjectContext.BinOutputPath = value;
        }
 
        public ProjectId Id
            => _vsProjectContext.Id;
 
        public string FilePath
            => _vsProjectContext.ProjectFilePath!;
 
        public int ProjectReferenceCount
            => _projectReferences.Count;
 
        public bool HasProjectReference(string filePath)
            => _projectReferences.ContainsKey(filePath);
 
        public int MetadataReferenceCount
            => _metadataReferences.Count;
 
        public bool HasMetadataReference(string referencePath)
            => _metadataReferences.Contains(referencePath);
 
        public void SetProjectReferences(IEnumerable<IFSharpWorkspaceProjectContext> projRefs)
        {
            var builder = ImmutableDictionary.CreateBuilder<string, IFSharpWorkspaceProjectContext>();
 
            foreach (var reference in _projectReferences.Values.Cast<FSharpWorkspaceProjectContext>())
            {
                _vsProjectContext.RemoveProjectReference(reference._vsProjectContext);
            }
 
            foreach (var reference in projRefs.Cast<FSharpWorkspaceProjectContext>())
            {
                _vsProjectContext.AddProjectReference(reference._vsProjectContext, MetadataReferenceProperties.Assembly);
                builder.Add(reference.FilePath, reference);
            }
 
            _projectReferences = builder.ToImmutable();
        }
 
        public void SetMetadataReferences(IEnumerable<string> referencePaths)
        {
            var builder = ImmutableHashSet.CreateBuilder<string>();
 
            foreach (var referencePath in _metadataReferences)
            {
                RemoveMetadataReference(referencePath);
            }
 
            foreach (var referencePath in referencePaths)
            {
                AddMetadataReference(referencePath);
                builder.Add(referencePath);
            }
 
            _metadataReferences = builder.ToImmutable();
        }
 
        public void RemoveMetadataReference(string referencePath)
            => _vsProjectContext.RemoveMetadataReference(referencePath);
 
        public void AddMetadataReference(string referencePath)
            => _vsProjectContext.AddMetadataReference(referencePath, MetadataReferenceProperties.Assembly);
 
        public void AddSourceFile(string path, SourceCodeKind kind)
            => _vsProjectContext.AddSourceFile(path, sourceCodeKind: kind);
 
        public void RemoveSourceFile(string path)
            => _vsProjectContext.RemoveSourceFile(path);
    }
}