diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/DefaultRazorProjectHost.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/DefaultRazorProjectHost.cs index f68ae2cd11..122505b002 100644 --- a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/DefaultRazorProjectHost.cs +++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/DefaultRazorProjectHost.cs @@ -73,53 +73,56 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem // Internal for testing internal async Task OnProjectChanged(IProjectVersionedValue update) { - await ExecuteWithLock(async () => + if (IsDisposing || IsDisposed) { - if (IsDisposing || IsDisposed) - { - return; - } + return; + } - var languageVersion = update.Value.CurrentState[Rules.RazorGeneral.SchemaName].Properties[Rules.RazorGeneral.RazorLangVersionProperty]; - var defaultConfiguration = update.Value.CurrentState[Rules.RazorGeneral.SchemaName].Properties[Rules.RazorGeneral.RazorDefaultConfigurationProperty]; - - RazorConfiguration configuration = null; - if (!string.IsNullOrEmpty(languageVersion) && !string.IsNullOrEmpty(defaultConfiguration)) + await CommonServices.TasksService.LoadedProjectAsync(async () => + { + await ExecuteWithLock(async () => { - if (!RazorLanguageVersion.TryParse(languageVersion, out var parsedVersion)) + var languageVersion = update.Value.CurrentState[Rules.RazorGeneral.SchemaName].Properties[Rules.RazorGeneral.RazorLangVersionProperty]; + var defaultConfiguration = update.Value.CurrentState[Rules.RazorGeneral.SchemaName].Properties[Rules.RazorGeneral.RazorDefaultConfigurationProperty]; + + RazorConfiguration configuration = null; + if (!string.IsNullOrEmpty(languageVersion) && !string.IsNullOrEmpty(defaultConfiguration)) { - parsedVersion = RazorLanguageVersion.Latest; + if (!RazorLanguageVersion.TryParse(languageVersion, out var parsedVersion)) + { + parsedVersion = RazorLanguageVersion.Latest; + } + + var extensions = update.Value.CurrentState[Rules.RazorExtension.PrimaryDataSourceItemType].Items.Select(e => + { + return new ProjectSystemRazorExtension(e.Key); + }).ToArray(); + + var configurations = update.Value.CurrentState[Rules.RazorConfiguration.PrimaryDataSourceItemType].Items.Select(c => + { + var includedExtensions = c.Value[Rules.RazorConfiguration.ExtensionsProperty] + .Split(';') + .Select(name => extensions.Where(e => e.ExtensionName == name).FirstOrDefault()) + .Where(e => e != null) + .ToArray(); + + return new ProjectSystemRazorConfiguration(parsedVersion, c.Key, includedExtensions); + }).ToArray(); + + configuration = configurations.Where(c => c.ConfigurationName == defaultConfiguration).FirstOrDefault(); } - var extensions = update.Value.CurrentState[Rules.RazorExtension.PrimaryDataSourceItemType].Items.Select(e => + if (configuration == null) { - return new ProjectSystemRazorExtension(e.Key); - }).ToArray(); + // Ok we can't find a language version. Let's assume this project isn't using Razor then. + await UpdateProjectUnsafeAsync(null).ConfigureAwait(false); + return; + } - var configurations = update.Value.CurrentState[Rules.RazorConfiguration.PrimaryDataSourceItemType].Items.Select(c => - { - var includedExtensions = c.Value[Rules.RazorConfiguration.ExtensionsProperty] - .Split(';') - .Select(name => extensions.Where(e => e.ExtensionName == name).FirstOrDefault()) - .Where(e => e != null) - .ToArray(); - - return new ProjectSystemRazorConfiguration(parsedVersion, c.Key, includedExtensions); - }).ToArray(); - - configuration = configurations.Where(c => c.ConfigurationName == defaultConfiguration).FirstOrDefault(); - } - - if (configuration == null) - { - // Ok we can't find a language version. Let's assume this project isn't using Razor then. - await UpdateProjectUnsafeAsync(null).ConfigureAwait(false); - return; - } - - var hostProject = new HostProject(CommonServices.UnconfiguredProject.FullPath, configuration); - await UpdateProjectUnsafeAsync(hostProject).ConfigureAwait(false); - }); + var hostProject = new HostProject(CommonServices.UnconfiguredProject.FullPath, configuration); + await UpdateProjectUnsafeAsync(hostProject).ConfigureAwait(false); + }); + }, registerFaultHandler: true); } } } \ No newline at end of file diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/FallbackRazorProjectHost.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/FallbackRazorProjectHost.cs index 60dd780e51..8e7fb4d493 100644 --- a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/FallbackRazorProjectHost.cs +++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/FallbackRazorProjectHost.cs @@ -58,7 +58,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem receiver, initialDataAsNew: true, suppressVersionOnlyUpdates: true, - ruleNames: new string[] { ResolvedCompilationReference.SchemaName }); + ruleNames: new string[] { ResolvedCompilationReference.SchemaName }, + linkOptions: new DataflowLinkOptions() { PropagateCompletion = true }); } protected override async Task DisposeCoreAsync(bool initialized) @@ -74,43 +75,46 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem // Internal for testing internal async Task OnProjectChanged(IProjectVersionedValue update) { - await ExecuteWithLock(async () => + if (IsDisposing || IsDisposed) { - if (IsDisposing || IsDisposed) - { - return; - } + return; + } - string mvcReferenceFullPath = null; - var references = update.Value.CurrentState[ResolvedCompilationReference.SchemaName].Items; - foreach (var reference in references) + await CommonServices.TasksService.LoadedProjectAsync(async () => + { + await ExecuteWithLock(async () => { - if (reference.Key.EndsWith(MvcAssemblyFileName, StringComparison.OrdinalIgnoreCase)) + string mvcReferenceFullPath = null; + var references = update.Value.CurrentState[ResolvedCompilationReference.SchemaName].Items; + foreach (var reference in references) { - mvcReferenceFullPath = reference.Key; - break; + if (reference.Key.EndsWith(MvcAssemblyFileName, StringComparison.OrdinalIgnoreCase)) + { + mvcReferenceFullPath = reference.Key; + break; + } } - } - if (mvcReferenceFullPath == null) - { - // Ok we can't find an MVC version. Let's assume this project isn't using Razor then. - await UpdateProjectUnsafeAsync(null).ConfigureAwait(false); - return; - } + if (mvcReferenceFullPath == null) + { + // Ok we can't find an MVC version. Let's assume this project isn't using Razor then. + await UpdateProjectUnsafeAsync(null).ConfigureAwait(false); + return; + } - var version = GetAssemblyVersion(mvcReferenceFullPath); - if (version == null) - { - // Ok we can't find an MVC version. Let's assume this project isn't using Razor then. - await UpdateProjectUnsafeAsync(null).ConfigureAwait(false); - return; - } + var version = GetAssemblyVersion(mvcReferenceFullPath); + if (version == null) + { + // Ok we can't find an MVC version. Let's assume this project isn't using Razor then. + await UpdateProjectUnsafeAsync(null).ConfigureAwait(false); + return; + } - var configuration = FallbackRazorConfiguration.SelectConfiguration(version); - var hostProject = new HostProject(CommonServices.UnconfiguredProject.FullPath, configuration); - await UpdateProjectUnsafeAsync(hostProject).ConfigureAwait(false); - }); + var configuration = FallbackRazorConfiguration.SelectConfiguration(version); + var hostProject = new HostProject(CommonServices.UnconfiguredProject.FullPath, configuration); + await UpdateProjectUnsafeAsync(hostProject).ConfigureAwait(false); + }); + }, registerFaultHandler: true); } // virtual for overriding in tests diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/IUnconfiguredProjectCommonServices.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/IUnconfiguredProjectCommonServices.cs index 1e98e9c732..f520a6e046 100644 --- a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/IUnconfiguredProjectCommonServices.cs +++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/IUnconfiguredProjectCommonServices.cs @@ -1,88 +1,32 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; -using System.ComponentModel.Composition; using Microsoft.VisualStudio.ProjectSystem; using Microsoft.VisualStudio.ProjectSystem.References; namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { - [Export(typeof(IUnconfiguredProjectCommonServices))] - internal class UnconfiguredProjectCommonServices : IUnconfiguredProjectCommonServices + // This defines the set of services that we frequently need for working with UnconfiguredProject. + // + // We're following a somewhat common pattern for code that uses CPS. It's really easy to end up + // relying on service location inside CPS, which can be hard to test. This approach makes it easy + // for us to build reusable mocks instead. + internal interface IUnconfiguredProjectCommonServices { - private readonly ActiveConfiguredProject _activeConfiguredProject; - private readonly ActiveConfiguredProject _activeConfiguredProjectAssemblyReferences; - private readonly ActiveConfiguredProject _activeConfiguredProjectPackageReferences; - private readonly ActiveConfiguredProject _activeConfiguredProjectProperties; + ConfiguredProject ActiveConfiguredProject { get; } - [ImportingConstructor] - public UnconfiguredProjectCommonServices( - IProjectThreadingService threadingService, - UnconfiguredProject unconfiguredProject, - IActiveConfiguredProjectSubscriptionService activeConfiguredProjectSubscription, - ActiveConfiguredProject activeConfiguredProject, - ActiveConfiguredProject activeConfiguredProjectAssemblyReferences, - ActiveConfiguredProject activeConfiguredProjectPackageReferences, - ActiveConfiguredProject activeConfiguredProjectRazorProperties) - { - if (threadingService == null) - { - throw new ArgumentNullException(nameof(threadingService)); - } + IAssemblyReferencesService ActiveConfiguredProjectAssemblyReferences { get; } - if (unconfiguredProject == null) - { - throw new ArgumentNullException(nameof(unconfiguredProject)); - } + IPackageReferencesService ActiveConfiguredProjectPackageReferences { get; } - if (activeConfiguredProjectSubscription == null) - { - throw new ArgumentNullException(nameof(ActiveConfiguredProjectSubscription)); - } + Rules.RazorProjectProperties ActiveConfiguredProjectRazorProperties { get; } - if (activeConfiguredProject == null) - { - throw new ArgumentNullException(nameof(activeConfiguredProject)); - } + IActiveConfiguredProjectSubscriptionService ActiveConfiguredProjectSubscription { get; } - if (activeConfiguredProjectAssemblyReferences == null) - { - throw new ArgumentNullException(nameof(activeConfiguredProjectAssemblyReferences)); - } + IProjectAsynchronousTasksService TasksService { get; } - if (activeConfiguredProjectPackageReferences == null) - { - throw new ArgumentNullException(nameof(activeConfiguredProjectPackageReferences)); - } - - if (activeConfiguredProjectRazorProperties == null) - { - throw new ArgumentNullException(nameof(activeConfiguredProjectRazorProperties)); - } - - ThreadingService = threadingService; - UnconfiguredProject = unconfiguredProject; - ActiveConfiguredProjectSubscription = activeConfiguredProjectSubscription; - _activeConfiguredProject = activeConfiguredProject; - _activeConfiguredProjectAssemblyReferences = activeConfiguredProjectAssemblyReferences; - _activeConfiguredProjectPackageReferences = activeConfiguredProjectPackageReferences; - _activeConfiguredProjectProperties = activeConfiguredProjectRazorProperties; - } - - public ConfiguredProject ActiveConfiguredProject => _activeConfiguredProject.Value; - - public IAssemblyReferencesService ActiveConfiguredProjectAssemblyReferences => _activeConfiguredProjectAssemblyReferences.Value; - - public IPackageReferencesService ActiveConfiguredProjectPackageReferences => _activeConfiguredProjectPackageReferences.Value; - - public Rules.RazorProjectProperties ActiveConfiguredProjectRazorProperties => _activeConfiguredProjectProperties.Value; + IProjectThreadingService ThreadingService { get; } - public IActiveConfiguredProjectSubscriptionService ActiveConfiguredProjectSubscription { get; } - - - public IProjectThreadingService ThreadingService { get; } - - public UnconfiguredProject UnconfiguredProject { get; } + UnconfiguredProject UnconfiguredProject { get; } } } diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/UnconfiguredProjectCommonServices.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/UnconfiguredProjectCommonServices.cs index 9e235012d2..d84595a1c3 100644 --- a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/UnconfiguredProjectCommonServices.cs +++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/UnconfiguredProjectCommonServices.cs @@ -1,30 +1,96 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; +using System.ComponentModel.Composition; using Microsoft.VisualStudio.ProjectSystem; using Microsoft.VisualStudio.ProjectSystem.References; namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { - // This defines the set of services that we frequently need for working with UnconfiguredProject. - // - // We're following a somewhat common pattern for code that uses CPS. It's really easy to end up - // relying on service location inside CPS, which can be hard to test. This approach makes it easy - // for us to build reusable mocks instead. - internal interface IUnconfiguredProjectCommonServices + [Export(typeof(IUnconfiguredProjectCommonServices))] + internal class UnconfiguredProjectCommonServices : IUnconfiguredProjectCommonServices { - ConfiguredProject ActiveConfiguredProject { get; } + private readonly ActiveConfiguredProject _activeConfiguredProject; + private readonly ActiveConfiguredProject _activeConfiguredProjectAssemblyReferences; + private readonly ActiveConfiguredProject _activeConfiguredProjectPackageReferences; + private readonly ActiveConfiguredProject _activeConfiguredProjectProperties; - IAssemblyReferencesService ActiveConfiguredProjectAssemblyReferences { get; } + [ImportingConstructor] + public UnconfiguredProjectCommonServices( + [Import(ExportContractNames.Scopes.UnconfiguredProject)] IProjectAsynchronousTasksService tasksService, + IProjectThreadingService threadingService, + UnconfiguredProject unconfiguredProject, + IActiveConfiguredProjectSubscriptionService activeConfiguredProjectSubscription, + ActiveConfiguredProject activeConfiguredProject, + ActiveConfiguredProject activeConfiguredProjectAssemblyReferences, + ActiveConfiguredProject activeConfiguredProjectPackageReferences, + ActiveConfiguredProject activeConfiguredProjectRazorProperties) + { + if (tasksService == null) + { + throw new ArgumentNullException(nameof(tasksService)); + } - IPackageReferencesService ActiveConfiguredProjectPackageReferences { get; } + if (threadingService == null) + { + throw new ArgumentNullException(nameof(threadingService)); + } - Rules.RazorProjectProperties ActiveConfiguredProjectRazorProperties { get; } + if (unconfiguredProject == null) + { + throw new ArgumentNullException(nameof(unconfiguredProject)); + } - IActiveConfiguredProjectSubscriptionService ActiveConfiguredProjectSubscription { get; } + if (activeConfiguredProjectSubscription == null) + { + throw new ArgumentNullException(nameof(ActiveConfiguredProjectSubscription)); + } + + if (activeConfiguredProject == null) + { + throw new ArgumentNullException(nameof(activeConfiguredProject)); + } + + if (activeConfiguredProjectAssemblyReferences == null) + { + throw new ArgumentNullException(nameof(activeConfiguredProjectAssemblyReferences)); + } + + if (activeConfiguredProjectPackageReferences == null) + { + throw new ArgumentNullException(nameof(activeConfiguredProjectPackageReferences)); + } + + if (activeConfiguredProjectRazorProperties == null) + { + throw new ArgumentNullException(nameof(activeConfiguredProjectRazorProperties)); + } + + TasksService = tasksService; + ThreadingService = threadingService; + UnconfiguredProject = unconfiguredProject; + ActiveConfiguredProjectSubscription = activeConfiguredProjectSubscription; + _activeConfiguredProject = activeConfiguredProject; + _activeConfiguredProjectAssemblyReferences = activeConfiguredProjectAssemblyReferences; + _activeConfiguredProjectPackageReferences = activeConfiguredProjectPackageReferences; + _activeConfiguredProjectProperties = activeConfiguredProjectRazorProperties; + } + + public ConfiguredProject ActiveConfiguredProject => _activeConfiguredProject.Value; + + public IAssemblyReferencesService ActiveConfiguredProjectAssemblyReferences => _activeConfiguredProjectAssemblyReferences.Value; + + public IPackageReferencesService ActiveConfiguredProjectPackageReferences => _activeConfiguredProjectPackageReferences.Value; + + public Rules.RazorProjectProperties ActiveConfiguredProjectRazorProperties => _activeConfiguredProjectProperties.Value; - IProjectThreadingService ThreadingService { get; } + public IActiveConfiguredProjectSubscriptionService ActiveConfiguredProjectSubscription { get; } - UnconfiguredProject UnconfiguredProject { get; } + public IProjectAsynchronousTasksService TasksService { get; } + + public IProjectThreadingService ThreadingService { get; } + + public UnconfiguredProject UnconfiguredProject { get; } } } diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/TestProjectSystemServices.cs b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/TestProjectSystemServices.cs index 161600f625..7333883233 100644 --- a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/TestProjectSystemServices.cs +++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/TestProjectSystemServices.cs @@ -40,6 +40,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem ActiveConfiguredProjectAssemblyReferences = new TestAssemblyReferencesService(); ActiveConfiguredProjectRazorProperties = new Rules.RazorProjectProperties(ActiveConfiguredProject, UnconfiguredProject); ActiveConfiguredProjectSubscription = new TestActiveConfiguredProjectSubscriptionService(); + + TasksService = new TestProjectAsynchronousTasksService(ProjectService, UnconfiguredProject, ActiveConfiguredProject); } public TestProjectServices Services { get; } @@ -56,6 +58,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem public TestActiveConfiguredProjectSubscriptionService ActiveConfiguredProjectSubscription { get; } + public TestProjectAsynchronousTasksService TasksService { get; } + public TestThreadingService ThreadingService { get; } ConfiguredProject IUnconfiguredProjectCommonServices.ActiveConfiguredProject => ActiveConfiguredProject; @@ -68,6 +72,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem IActiveConfiguredProjectSubscriptionService IUnconfiguredProjectCommonServices.ActiveConfiguredProjectSubscription => ActiveConfiguredProjectSubscription; + IProjectAsynchronousTasksService IUnconfiguredProjectCommonServices.TasksService => TasksService; + IProjectThreadingService IUnconfiguredProjectCommonServices.ThreadingService => ThreadingService; UnconfiguredProject IUnconfiguredProjectCommonServices.UnconfiguredProject => UnconfiguredProject; @@ -746,6 +752,63 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem } } + public class TestProjectAsynchronousTasksService : IProjectAsynchronousTasksService, IProjectContext + { + public CancellationToken UnloadCancellationToken => CancellationToken.None; + + public TestProjectAsynchronousTasksService( + IProjectService projectService, + UnconfiguredProject unconfiguredProject, + ConfiguredProject configuredProject) + { + ProjectService = projectService; + UnconfiguredProject = unconfiguredProject; + ConfiguredProject = configuredProject; + } + + public IProjectService ProjectService { get; } + + public UnconfiguredProject UnconfiguredProject { get; } + + public ConfiguredProject ConfiguredProject { get; } + + public Task DrainCriticalTaskQueueAsync(bool drainCurrentQueueOnly = false, bool throwExceptions = false, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task DrainTaskQueueAsync(bool drainCurrentQueueOnly = false, bool throwExceptions = false, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task DrainTaskQueueAsync(ProjectCriticalOperation operation, bool drainCurrentQueueOnly = false, bool throwExceptions = false, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public bool IsTaskQueueEmpty(ProjectCriticalOperation projectCriticalOperation) + { + throw new NotImplementedException(); + } + + public void RegisterAsyncTask(JoinableTask joinableTask, bool registerFaultHandler = false) + { + } + + public void RegisterAsyncTask(Task task, bool registerFaultHandler = false) + { + } + + public void RegisterAsyncTask(JoinableTask joinableTask, ProjectCriticalOperation operationFlags, bool registerFaultHandler = false) + { + } + + public void RegisterCriticalAsyncTask(JoinableTask joinableTask, bool registerFaultHandler = false) + { + } + } + public class TestThreadingService : IProjectThreadingService { public TestThreadingService()