File: ProjectSystem\VisualStudioProjectFactory.cs
Web Access
Project: src\src\VisualStudio\Core\Def\Microsoft.VisualStudio.LanguageServices_dl2txo1f_wpftmp.csproj (Microsoft.VisualStudio.LanguageServices)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Workspaces.AnalyzerRedirecting;
using Microsoft.CodeAnalysis.Workspaces.ProjectSystem;
using Microsoft.Internal.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.LanguageServices.ExternalAccess.VSTypeScript.Api;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Telemetry;
using Microsoft.VisualStudio.Threading;
 
namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem;
 
[Export(typeof(VisualStudioProjectFactory))]
[Export(typeof(IVsTypeScriptVisualStudioProjectFactory))]
internal sealed class VisualStudioProjectFactory : IVsTypeScriptVisualStudioProjectFactory
{
    private const string SolutionContextName = "Solution";
    private const string SolutionSessionIdPropertyName = "SolutionSessionID";
 
    private readonly IThreadingContext _threadingContext;
    private readonly VisualStudioWorkspaceImpl _visualStudioWorkspaceImpl;
    private readonly ImmutableArray<Lazy<IDynamicFileInfoProvider, FileExtensionsMetadata>> _dynamicFileInfoProviders;
    private readonly ImmutableArray<IAnalyzerAssemblyRedirector> _analyzerAssemblyRedirectors;
    private readonly IVsService<SVsBackgroundSolution, IVsBackgroundSolution> _solution;
 
    private readonly JoinableTask _initializationTask;
 
    [ImportingConstructor]
    [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
    public VisualStudioProjectFactory(
        IThreadingContext threadingContext,
        VisualStudioWorkspaceImpl visualStudioWorkspaceImpl,
        [ImportMany] IEnumerable<Lazy<IDynamicFileInfoProvider, FileExtensionsMetadata>> fileInfoProviders,
        [ImportMany] IEnumerable<IAnalyzerAssemblyRedirector> analyzerAssemblyRedirectors,
        IVsService<SVsBackgroundSolution, IVsBackgroundSolution> solution)
    {
        _threadingContext = threadingContext;
        _visualStudioWorkspaceImpl = visualStudioWorkspaceImpl;
        _dynamicFileInfoProviders = fileInfoProviders.AsImmutableOrEmpty();
        _analyzerAssemblyRedirectors = analyzerAssemblyRedirectors.AsImmutableOrEmpty();
        _solution = solution;
 
        _initializationTask = _threadingContext.JoinableTaskFactory.RunAsync(
            async () =>
            {
                var cancellationToken = _threadingContext.DisposalToken;
 
                // HACK: Fetch this service to ensure it's still created on the UI thread; once this is
                // moved off we'll need to fix up it's constructor to be free-threaded.
 
                // yield if on the main thread, as the VisualStudioMetadataReferenceManager construction can be fairly expensive
                // and we don't want the case where VisualStudioProjectFactory is constructed on the main thread to block on that.
                await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken);
                _visualStudioWorkspaceImpl.Services.GetRequiredService<VisualStudioMetadataReferenceManager>();
            });
    }
 
    public Task<ProjectSystemProject> CreateAndAddToWorkspaceAsync(string projectSystemName, string language, CancellationToken cancellationToken)
        => CreateAndAddToWorkspaceAsync(projectSystemName, language, new VisualStudioProjectCreationInfo(), cancellationToken);
 
    public async Task<ProjectSystemProject> CreateAndAddToWorkspaceAsync(
        string projectSystemName, string language, VisualStudioProjectCreationInfo creationInfo, CancellationToken cancellationToken)
    {
        // Since we're following JTF/VS threading rules here, we just use ConfigureAwait(true) in this method, which does what we want for performance:
        //
        // - For CPS where we expect this to be called on a thread pool thread, we'll stay on the thread pool thread and that's what we want.
        // - For csproj/msvbprj, this gets called on the UI thread from AbstractLegacyProject's constructor, which since that's under a
        //    JTF.Run() on the UI thread, ensures we don't find ourselves blocked on a busy thread pool.
        await _initializationTask.JoinAsync(cancellationToken).ConfigureAwait(true);
 
        var solution = await _solution.GetValueOrNullAsync(cancellationToken).ConfigureAwait(true);
 
        // From this point on, we start mutating the solution.  So make us non cancellable.
        cancellationToken = CancellationToken.None;
 
        _visualStudioWorkspaceImpl.ProjectSystemProjectFactory.SolutionPath = solution?.SolutionFileName;
        _visualStudioWorkspaceImpl.ProjectSystemProjectFactory.SolutionTelemetryId = GetSolutionSessionId();
 
        var hostInfo = new ProjectSystemHostInfo(_dynamicFileInfoProviders, _analyzerAssemblyRedirectors);
        var project = await _visualStudioWorkspaceImpl.ProjectSystemProjectFactory.CreateAndAddToWorkspaceAsync(projectSystemName, language, creationInfo, hostInfo).ConfigureAwait(true);
 
        _visualStudioWorkspaceImpl.AddProjectToInternalMaps(project, creationInfo.Hierarchy, creationInfo.ProjectGuid, projectSystemName);
 
        // Ensure that other VS contexts get accurate information that the UIContext for this language is now active.
        await _visualStudioWorkspaceImpl.RefreshProjectExistsUIContextForLanguageAsync(language, cancellationToken).ConfigureAwait(true);
 
        return project;
 
        static Guid GetSolutionSessionId()
        {
            var dataModelTelemetrySession = TelemetryService.DefaultSession;
            var solutionContext = dataModelTelemetrySession.GetContext(SolutionContextName);
            var sessionIdProperty = solutionContext is object
                ? (string)solutionContext.SharedProperties[SolutionSessionIdPropertyName]
                : "";
            _ = Guid.TryParse(sessionIdProperty, out var solutionSessionId);
            return solutionSessionId;
        }
    }
 
    VSTypeScriptVisualStudioProjectWrapper IVsTypeScriptVisualStudioProjectFactory.CreateAndAddToWorkspace(string projectSystemName, string language, string projectFilePath, IVsHierarchy hierarchy, Guid projectGuid)
    {
        return _threadingContext.JoinableTaskFactory.Run(async () =>
            await ((IVsTypeScriptVisualStudioProjectFactory)this).CreateAndAddToWorkspaceAsync(projectSystemName, language, projectFilePath, hierarchy, projectGuid, CancellationToken.None).ConfigureAwait(false));
    }
 
    async ValueTask<VSTypeScriptVisualStudioProjectWrapper> IVsTypeScriptVisualStudioProjectFactory.CreateAndAddToWorkspaceAsync(
        string projectSystemName, string language, string projectFilePath, IVsHierarchy hierarchy, Guid projectGuid, CancellationToken cancellationToken)
    {
        var projectInfo = new VisualStudioProjectCreationInfo
        {
            FilePath = projectFilePath,
            Hierarchy = hierarchy,
            ProjectGuid = projectGuid,
        };
        var visualStudioProject = await this.CreateAndAddToWorkspaceAsync(projectSystemName, language, projectInfo, cancellationToken).ConfigureAwait(false);
        return new VSTypeScriptVisualStudioProjectWrapper(visualStudioProject);
    }
}